Skip to content

Commit 1346187

Browse files
author
Ralf Schlatterbeck
committed
Transitive-property support.
- Fixed some of the pet-peeves from pep8 - Better parameter names for new _subselect method - use new-style class for support.Proptree but needed a new-style class for the property I introduced anyway. - Fix a bug where searching did the wrong thing (interestingly enough the same wrong thing for all backends): A search for {'messages': ['1'], 'messages.author': ['2']} would ignore the 'messages' part (messages being non-leaf node in proptree). Fixed and added a regression test for this. - Added the transitive searching to the SearchAction. New method get_transitive_prop introduced in hyperdb that does the transitive version of getprops()[name]. Fixed two tests to use the (faked) method instead of getprop. Now searching for transitive props via the web-interface works for me. Thanks to alexander smishlajev for pointing me at the coding style. Sorry for stepping on the peeves -- I'm using a different coding style in most other projects I'm doing ...
1 parent f7d0708 commit 1346187

File tree

8 files changed

+133
-89
lines changed

8 files changed

+133
-89
lines changed

roundup/backends/back_mysql.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#$Id: back_mysql.py,v 1.69 2006-07-07 15:04:28 schlatterbeck Exp $
1+
#$Id: back_mysql.py,v 1.70 2006-07-13 10:14:56 schlatterbeck Exp $
22
#
33
# Copyright (c) 2003 Martynas Sklyzmantas, Andrey Lebedev <[email protected]>
44
#
@@ -550,13 +550,13 @@ def sql_close(self):
550550
raise
551551

552552
class MysqlClass:
553-
def _subselect (self, cn, tn) :
553+
def _subselect(self, classname, multilink_table):
554554
''' "I can't believe it's not a toy RDBMS"
555555
see, even toy RDBMSes like gadfly and sqlite can do sub-selects...
556556
'''
557-
self.db.sql('select nodeid from %s'%tn)
557+
self.db.sql('select nodeid from %s'%multilink_table)
558558
s = ','.join([x[0] for x in self.db.sql_fetchall()])
559-
return '_%s.id not in (%s)'%(cn, s)
559+
return '_%s.id not in (%s)'%(classname, s)
560560

561561
class Class(MysqlClass, rdbms_common.Class):
562562
pass

roundup/backends/rdbms_common.py

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
#$Id: rdbms_common.py,v 1.172 2006-07-08 18:28:18 schlatterbeck Exp $
18+
#$Id: rdbms_common.py,v 1.173 2006-07-13 10:14:56 schlatterbeck Exp $
1919
''' Relational database (SQL) backend common code.
2020
2121
Basics:
@@ -2014,13 +2014,14 @@ def getnodeids(self, retired=None):
20142014
ids = [str(x[0]) for x in self.db.cursor.fetchall()]
20152015
return ids
20162016

2017-
def _subselect (self, cn, tn) :
2017+
def _subselect(self, classname, multilink_table):
20182018
'''Create a subselect. This is factored out because some
20192019
databases (hmm only one, so far) doesn't support subselects
20202020
look for "I can't believe it's not a toy RDBMS" in the mysql
20212021
backend.
20222022
'''
2023-
return '_%s.id not in (select nodeid from %s)'%(cn, tn)
2023+
return '_%s.id not in (select nodeid from %s)'%(classname,
2024+
multilink_table)
20242025

20252026
def filter(self, search_matches, filterspec, sort=(None,None),
20262027
group=(None,None)):
@@ -2059,29 +2060,20 @@ def filter(self, search_matches, filterspec, sort=(None,None),
20592060

20602061
# figure the WHERE clause from the filterspec
20612062
mlfilt = 0 # are we joining with Multilink tables?
2062-
proptree = self._proptree (filterspec)
2063-
for p in proptree :
2063+
proptree = self._proptree(filterspec)
2064+
for p in proptree:
20642065
cn = p.classname
20652066
ln = p.uniqname
20662067
pln = p.parent.uniqname
20672068
pcn = p.parent.classname
20682069
k = p.name
20692070
v = p.val
20702071
propclass = p.prcls
2071-
if p.children :
2072-
if isinstance (propclass, Multilink) :
2073-
mlfilt = 1
2074-
mn = '%s_%s'%(pcn, p.name)
2075-
frum.append(mn)
2076-
frum.append('_%s as _%s' % (cn, ln))
2077-
where.append('_%s.id=%s.nodeid and %s.linkid=_%s.id'%(pln,
2078-
mn, mn, ln))
2079-
else :
2080-
if not isinstance (propclass, Link) :
2081-
raise ValueError,"%s must be Link/Multilink property"%k
2082-
frum.append('_%s as _%s' % (cn, ln))
2083-
where.append('_%s._%s=_%s.id'%(pln, k, ln))
2084-
continue
2072+
if p.children and not isinstance(propclass, Multilink):
2073+
if not isinstance(propclass, Link):
2074+
raise ValueError,"%s must be Link/Multilink property"%k
2075+
frum.append('_%s as _%s' % (cn, ln))
2076+
where.append('_%s._%s=_%s.id'%(pln, k, ln))
20852077
# now do other where clause stuff
20862078
elif isinstance(propclass, Multilink):
20872079
mlfilt = 1
@@ -2090,17 +2082,20 @@ def filter(self, search_matches, filterspec, sort=(None,None),
20902082
# only match rows that have count(linkid)=0 in the
20912083
# corresponding multilink table)
20922084
where.append(self._subselect(pcn, tn))
2093-
elif isinstance(v, type([])):
2094-
frum.append(tn)
2095-
s = ','.join([a for x in v])
2096-
where.append('_%s.id=%s.nodeid and %s.linkid in (%s)'%(pln,
2097-
tn, tn, s))
2098-
args = args + v
20992085
else:
21002086
frum.append(tn)
2101-
where.append('_%s.id=%s.nodeid and %s.linkid=%s'%(pln, tn,
2102-
tn, a))
2103-
args.append(v)
2087+
where.append('_%s.id=%s.nodeid'%(pln,tn))
2088+
if p.children:
2089+
frum.append('_%s as _%s' % (cn, ln))
2090+
where.append('%s.linkid=_%s.id'%(tn, ln))
2091+
if v:
2092+
if isinstance(v, type([])):
2093+
s = ','.join([a for x in v])
2094+
where.append('%s.linkid in (%s)'%(tn, s))
2095+
args = args + v
2096+
else:
2097+
where.append('%s.linkid=%s'%(tn, a))
2098+
args.append(v)
21042099
elif k == 'id':
21052100
if isinstance(v, type([])):
21062101
s = ','.join([a for x in v])

roundup/cgi/actions.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#$Id: actions.py,v 1.60 2006-04-27 03:44:47 richard Exp $
1+
#$Id: actions.py,v 1.61 2006-07-13 10:14:56 schlatterbeck Exp $
22

33
import re, cgi, StringIO, urllib, Cookie, time, random, csv, codecs
44

@@ -221,9 +221,10 @@ def handle(self):
221221

222222
def fakeFilterVars(self):
223223
"""Add a faked :filter form variable for each filtering prop."""
224-
props = self.db.classes[self.classname].getprops()
224+
cls = self.db.classes[self.classname]
225225
for key in self.form.keys():
226-
if not props.has_key(key):
226+
prop = cls.get_transitive_prop(key)
227+
if not prop:
227228
continue
228229
if isinstance(self.form[key], type([])):
229230
# search for at least one entry which is not empty
@@ -235,7 +236,7 @@ def fakeFilterVars(self):
235236
else:
236237
if not self.form[key].value:
237238
continue
238-
if isinstance(props[key], hyperdb.String):
239+
if isinstance(prop, hyperdb.String):
239240
v = self.form[key].value
240241
l = token.token_split(v)
241242
if len(l) > 1 or l[0] != v:

roundup/cgi/templating.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,11 +2132,11 @@ def _post_init(self):
21322132
self.filterspec = {}
21332133
db = self.client.db
21342134
if self.classname is not None:
2135-
props = db.getclass(self.classname).getprops()
2135+
cls = db.getclass (self.classname)
21362136
for name in self.filter:
21372137
if not self.form.has_key(name):
21382138
continue
2139-
prop = props[name]
2139+
prop = cls.get_transitive_prop (name)
21402140
fv = self.form[name]
21412141
if (isinstance(prop, hyperdb.Link) or
21422142
isinstance(prop, hyperdb.Multilink)):
@@ -2314,11 +2314,12 @@ def indexargs_url(self, url, args):
23142314

23152315
# finally, the remainder of the filter args in the request
23162316
if self.classname and self.filterspec:
2317-
props = self.client.db.getclass(self.classname).getprops()
2317+
cls = self.client.db.getclass(self.classname)
23182318
for k,v in self.filterspec.items():
23192319
if not args.has_key(k):
23202320
if type(v) == type([]):
2321-
if isinstance(props[k], hyperdb.String):
2321+
prop = cls.get_transitive_prop(k)
2322+
if isinstance(prop, hyperdb.String):
23222323
l.append('%s=%s'%(k, '%20'.join([q(i) for i in v])))
23232324
else:
23242325
l.append('%s=%s'%(k, ','.join([q(i) for i in v])))

roundup/hyperdb.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: hyperdb.py,v 1.121 2006-07-08 18:28:18 schlatterbeck Exp $
18+
# $Id: hyperdb.py,v 1.122 2006-07-13 10:14:56 schlatterbeck Exp $
1919

2020
"""Hyperdatabase implementation, especially field types.
2121
"""
@@ -713,30 +713,50 @@ def _filter(self, search_matches, filterspec, sort=(None,None),
713713
"""
714714
raise NotImplementedError
715715

716-
def _proptree (self, filterspec) :
716+
def _proptree (self, filterspec):
717717
"""Build a tree of all transitive properties in the given
718718
filterspec.
719719
"""
720720
proptree = Proptree (self.db, self, '', self.getprops ())
721-
for key, v in filterspec.iteritems () :
721+
for key, v in filterspec.iteritems ():
722722
keys = key.split ('.')
723723
p = proptree
724-
for k in keys :
724+
for k in keys:
725725
p = p.append (k)
726726
p.val = v
727727
return proptree
728728

729-
def _propsearch (self, search_matches, proptree, sort, group) :
729+
def _propsearch (self, search_matches, proptree, sort, group):
730730
""" Recursively search for the given properties in proptree.
731731
Once all properties are non-transitive, the search generates a
732732
simple _filter call which does the real work
733733
"""
734-
for p in proptree.children :
735-
if not p.children : continue
734+
for p in proptree.children:
735+
if not p.children:
736+
continue
736737
p.val = p.cls._propsearch (None, p, (None, None), (None, None))
737738
filterspec = dict ([(p.name, p.val) for p in proptree.children])
738739
return self._filter (search_matches, filterspec, sort, group)
739740

741+
def get_transitive_prop (self, propname_path, default = None) :
742+
"""Expand a transitive property (individual property names
743+
separated by '.' into a new property at the end of the path. If
744+
one of the names does not refer to a valid property, we return
745+
None.
746+
Example propname_path (for class issue): "messages.author"
747+
"""
748+
props = self.db.getclass(self.classname).getprops()
749+
for k in propname_path.split('.'):
750+
try :
751+
prop = props[k]
752+
except KeyError, TypeError:
753+
return default
754+
cl = getattr (prop, 'classname', None)
755+
props = None
756+
if cl:
757+
props = self.db.getclass (cl).getprops()
758+
return prop
759+
740760
def filter(self, search_matches, filterspec, sort=(None,None),
741761
group=(None,None)):
742762
"""Return a list of the ids of the active nodes in this class that

roundup/support.py

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import os, time, sys
88
import hyperdb
99

10+
from sets import Set
11+
1012
class TruthDict:
1113
'''Returns True for valid keys, False for others.
1214
'''
@@ -32,11 +34,11 @@ class PrioList:
3234
full list interface.
3335
Implementation: We manage a "sorted" status and sort on demand.
3436
Appending to the list will require re-sorting before use.
35-
>>> p = PrioList ()
36-
>>> for i in 5,7,1,-1 :
37-
... p.append (i)
37+
>>> p = PrioList()
38+
>>> for i in 5,7,1,-1:
39+
... p.append(i)
3840
...
39-
>>> for k in p :
41+
>>> for k in p:
4042
... print k
4143
...
4244
-1
@@ -50,14 +52,14 @@ def __init__(self):
5052
self.sorted = True
5153

5254
def append(self, item):
53-
self.list.append (item)
55+
self.list.append(item)
5456
self.sorted = False
5557

5658
def __iter__(self):
57-
if not self.sorted :
58-
self.list.sort ()
59+
if not self.sorted:
60+
self.list.sort()
5961
self.sorted = True
60-
return iter (self.list)
62+
return iter(self.list)
6163

6264
class Progress:
6365
'''Progress display for console applications.
@@ -119,54 +121,73 @@ def display(self):
119121
sys.stdout.write(s + ' '*(75-len(s)) + '\r')
120122
sys.stdout.flush()
121123

122-
class Proptree :
124+
class Proptree(object):
123125
''' Simple tree data structure for optimizing searching of properties
124126
'''
125127

126-
def __init__ (self, db, cls, name, props, parent = None, val = None) :
127-
self.db = db
128-
self.name = name
129-
self.props = props
130-
self.parent = parent
131-
self.val = val
132-
self.cls = cls
128+
def __init__(self, db, cls, name, props, parent = None, val = None):
129+
self.db = db
130+
self.name = name
131+
self.props = props
132+
self.parent = parent
133+
self._val = val
134+
self.cls = cls
133135
self.classname = None
134-
self.uniqname = None
135-
self.children = []
136+
self.uniqname = None
137+
self.children = []
136138
self.propnames = {}
137-
if parent :
138-
self.root = parent.root
139+
if parent:
140+
self.root = parent.root
139141
self.prcls = self.parent.props [name]
140-
else :
141-
self.root = self
142+
else:
143+
self.root = self
142144
self.seqno = 1
143145
self.id = self.root.seqno
144146
self.root.seqno += 1
145-
if self.cls :
147+
if self.cls:
146148
self.classname = self.cls.classname
147-
self.uniqname = '%s%s' % (self.cls.classname, self.id)
148-
if not self.parent :
149-
self.uniqname = self.cls.classname
150-
151-
def append (self, name) :
152-
if name in self.propnames :
149+
self.uniqname = '%s%s' % (self.cls.classname, self.id)
150+
if not self.parent:
151+
self.uniqname = self.cls.classname
152+
153+
def append(self, name):
154+
"""Append a property to self.children. Will create a new
155+
propclass for the child.
156+
"""
157+
if name in self.propnames:
153158
return self.propnames [name]
154159
propclass = self.props [name]
155-
cls = None
160+
cls = None
156161
props = None
157-
if isinstance (propclass, (hyperdb.Link, hyperdb.Multilink)) :
158-
cls = self.db.getclass (propclass.classname)
159-
props = cls.getprops ()
160-
child = self.__class__ (self.db, cls, name, props, parent = self)
161-
self.children.append (child)
162+
if isinstance(propclass, (hyperdb.Link, hyperdb.Multilink)):
163+
cls = self.db.getclass(propclass.classname)
164+
props = cls.getprops()
165+
child = self.__class__(self.db, cls, name, props, parent = self)
166+
self.children.append(child)
162167
self.propnames [name] = child
163168
return child
164169

165-
def __iter__ (self) :
170+
def _set_val(self, val):
171+
"""Check if self._val is already defined. If yes, we compute the
172+
intersection of the old and the new value(s)
173+
"""
174+
if self._val:
175+
v = self._val
176+
if not isinstance(self._val, type([])):
177+
v = [self._val]
178+
vals = Set(v)
179+
vals.intersection_update(val)
180+
self._val = [v for v in vals]
181+
else:
182+
self._val = val
183+
184+
val = property(lambda self: self._val, _set_val)
185+
186+
def __iter__(self):
166187
""" Yield nodes in depth-first order -- visited nodes first """
167-
for p in self.children :
188+
for p in self.children:
168189
yield p
169-
for c in p :
190+
for c in p:
170191
yield c
171192

172193
# vim: set et sts=4 sw=4 :

0 commit comments

Comments
 (0)