Skip to content

Commit 5bedb01

Browse files
committed
Multilink expressions with simple "or"
.. also when searching for empty multilinks with '-1' as part of the list of IDs.
1 parent 536b197 commit 5bedb01

File tree

4 files changed

+57
-44
lines changed

4 files changed

+57
-44
lines changed

roundup/backends/back_anydbm.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,17 @@ class Expression:
122122
def __init__(self, v):
123123
try:
124124
opcodes = [int(x) for x in v]
125-
if min(opcodes) >= -1: raise ValueError()
125+
if min(opcodes) >= -1:
126+
raise ValueError()
126127

127128
compiled = compile_expression(opcodes)
128129
self.evaluate = lambda x: compiled.evaluate([int(y) for y in x])
129130
except:
130-
self.evaluate = lambda x: bool(set(x) & set(v))
131+
if '-1' in v:
132+
v = [x for x in v if int(x) > 0]
133+
self.evaluate = lambda x: bool(set(x) & set(v)) or not x
134+
else:
135+
self.evaluate = lambda x: bool(set(x) & set(v))
131136

132137
#
133138
# Now the database
@@ -1908,7 +1913,8 @@ def _filter(self, search_matches, filterspec, proptree,
19081913
# otherwise, make sure this node has each of the
19091914
# required values
19101915
expr = Expression(v)
1911-
if expr.evaluate(nv): match = 1
1916+
if expr.evaluate(nv):
1917+
match = 1
19121918
elif t == STRING:
19131919
if nv is None:
19141920
nv = ''

roundup/backends/back_mysql.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -602,9 +602,8 @@ def sql_close(self):
602602
class MysqlClass:
603603
case_sensitive_equal = 'COLLATE utf8_bin ='
604604

605-
def supports_subselects(self):
606-
# TODO: AFAIK its version dependent for MySQL
607-
return False
605+
# TODO: AFAIK its version dependent for MySQL
606+
supports_subselects = False
608607

609608
def _subselect(self, proptree):
610609
''' "I can't believe it's not a toy RDBMS"

roundup/backends/rdbms_common.py

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,6 +1558,17 @@ class Class(hyperdb.Class):
15581558
# We define the default here, can be changed in derivative class
15591559
case_sensitive_equal = '='
15601560

1561+
# Some DBs order NULL values last. Set this variable in the backend
1562+
# for prepending an order by clause for each attribute that causes
1563+
# correct sort order for NULLs. Examples:
1564+
# order_by_null_values = '(%s is not NULL)'
1565+
# order_by_null_values = 'notnull(%s)'
1566+
# The format parameter is replaced with the attribute.
1567+
order_by_null_values = None
1568+
1569+
# Assuming DBs can do subselects, overwrite if they cannot.
1570+
supports_subselects = True
1571+
15611572
def schema(self):
15621573
""" A dumpable version of the schema that we can store in the
15631574
database
@@ -2383,19 +2394,6 @@ def _subselect(self, proptree):
23832394
return '_%s.id not in (select %s from %s%s)'%(classname, nodeid_name,
23842395
multilink_table, w)
23852396

2386-
# Some DBs order NULL values last. Set this variable in the backend
2387-
# for prepending an order by clause for each attribute that causes
2388-
# correct sort order for NULLs. Examples:
2389-
# order_by_null_values = '(%s is not NULL)'
2390-
# order_by_null_values = 'notnull(%s)'
2391-
# The format parameter is replaced with the attribute.
2392-
order_by_null_values = None
2393-
2394-
def supports_subselects(self):
2395-
'''Assuming DBs can do subselects, overwrite if they cannot.
2396-
'''
2397-
return True
2398-
23992397
def _filter_multilink_expression_fallback(
24002398
self, classname, multilink_table, expr):
24012399
'''This is a fallback for database that do not support
@@ -2441,27 +2439,32 @@ def _filter_multilink_expression_fallback(
24412439
# we have ids of the classname table
24422440
return ids.where("_%s.id" % classname, self.db.arg)
24432441

2444-
def _filter_multilink_expression(self, classname, multilink_table,
2445-
linkid_name, nodeid_name, v):
2442+
def _filter_multilink_expression(self, proptree, v):
24462443
""" Filters out elements of the classname table that do not
24472444
match the given expression.
24482445
Returns tuple of 'WHERE' introns for the overall filter.
24492446
"""
2447+
classname = proptree.parent.uniqname
2448+
multilink_table = proptree.propclass.table_name
2449+
nid = proptree.propclass.nodeid_name
2450+
lid = proptree.propclass.linkid_name
2451+
24502452
try:
24512453
opcodes = [int(x) for x in v]
2452-
if min(opcodes) >= -1: raise ValueError()
2454+
if min(opcodes) >= -1:
2455+
raise ValueError()
24532456

24542457
expr = compile_expression(opcodes)
24552458

2456-
if not self.supports_subselects():
2459+
if not self.supports_subselects:
24572460
# We heavily rely on subselects. If there is
24582461
# no decent support fall back to slower variant.
24592462
return self._filter_multilink_expression_fallback(
24602463
classname, multilink_table, expr)
24612464

24622465
atom = \
24632466
"%s IN(SELECT %s FROM %s WHERE %s=a.id)" % (
2464-
self.db.arg, linkid_name, multilink_table, nodeid_name)
2467+
self.db.arg, lid, multilink_table, nid)
24652468

24662469
intron = \
24672470
"_%(classname)s.id in (SELECT id " \
@@ -2475,10 +2478,20 @@ def collect_values(n): values.append(n.x)
24752478

24762479
return intron, values
24772480
except:
2478-
# original behavior
2479-
where = "%s.%s in (%s)" % (
2480-
multilink_table, linkid_name, ','.join([self.db.arg] * len(v)))
2481-
return where, v, True # True to indicate original
2481+
# fallback behavior when expression parsing above fails
2482+
orclause = ''
2483+
if '-1' in v :
2484+
v = [x for x in v if int (x) > 0]
2485+
orclause = self._subselect(proptree)
2486+
where = []
2487+
where.append("%s.%s in (%s)" % (multilink_table, lid,
2488+
','.join([self.db.arg] * len(v))))
2489+
where.append('_%s.id=%s.%s'%(classname, multilink_table, nid))
2490+
where = ' and '.join (where)
2491+
if orclause :
2492+
where = '((' + ' or '.join ((where + ')', orclause)) + ')'
2493+
2494+
return where, v
24822495

24832496
def _filter_sql (self, search_matches, filterspec, srt=[], grp=[], retr=0,
24842497
retired=False, exact_match_spec={}, limit=None,
@@ -2550,27 +2563,22 @@ def _filter_sql (self, search_matches, filterspec, srt=[], grp=[], retr=0,
25502563
where.append(self._subselect(p))
25512564
else:
25522565
frum.append(tn)
2553-
gen_join = True
2554-
2555-
if p.has_values and isinstance(v, type([])):
2556-
result = self._filter_multilink_expression(pln,
2557-
tn, lid, nid, v)
2558-
# XXX: We dont need an id join if we used the filter
2559-
gen_join = len(result) == 3
2560-
2561-
if gen_join:
2562-
where.append('_%s.id=%s.%s'%(pln, tn, nid))
25632566

25642567
if p.children or p.need_child_retired:
25652568
frum.append('_%s as _%s' % (cn, ln))
25662569
where.append('%s.%s=_%s.id'%(tn, lid, ln))
25672570
if p.need_child_retired:
25682571
where.append('_%s.__retired__=0'%(ln))
25692572

2573+
if not p.has_values or not isinstance(v, type([])):
2574+
where.append('_%s.id=%s.%s'%(pln, tn, nid))
25702575
if p.has_values:
25712576
if isinstance(v, type([])):
2572-
where.append(result[0])
2573-
args += result[1]
2577+
# The where-clause above is conditionally
2578+
# created in _filter_multilink_expression
2579+
w, arg = self._filter_multilink_expression(p, v)
2580+
where.append(w)
2581+
args += arg
25742582
else:
25752583
where.append('%s.%s=%s'%(tn, lid, a))
25762584
args.append(v)

test/db_test_base.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,13 +1977,13 @@ def testFilteringMultilinkExpression(self):
19771977
for filt in iiter():
19781978
ae(filt(None, {kw: ['-1']}),
19791979
['1'])
1980-
# These do not work with any of the backends currently
1980+
# '3' or empty (without explicit 'or')
1981+
ae(filt(None, {kw: ['3', '-1']}),
1982+
['1', '2', '3'])
1983+
# This does not work with any of the backends currently:
19811984
# '3' or empty (with explicit 'or')
19821985
#ae(filt(None, {kw: ['3', '-1', '-4']}),
19831986
# ['1', '2', '3'])
1984-
# '3' or empty (without explicit 'or')
1985-
#ae(filt(None, {kw: ['3', '-1']}),
1986-
# ['1', '2', '3'])
19871987

19881988
def testFilteringRevMultilink(self):
19891989
ae, iiter = self.filteringSetupTransitiveSearch('user')

0 commit comments

Comments
 (0)