diff roundup/backends/rdbms_common.py @ 6396:75a53956cf13

Multilink expressions with simple "or" .. also when searching for empty multilinks with '-1' as part of the list of IDs.
author Ralf Schlatterbeck <rsc@runtux.com>
date Fri, 07 May 2021 20:50:08 +0200
parents 8baf81d1cfc1
children f3fcd6628c0c
line wrap: on
line diff
--- a/roundup/backends/rdbms_common.py	Fri May 07 16:01:57 2021 +0200
+++ b/roundup/backends/rdbms_common.py	Fri May 07 20:50:08 2021 +0200
@@ -1558,6 +1558,17 @@
     # We define the default here, can be changed in derivative class
     case_sensitive_equal = '='
 
+    # Some DBs order NULL values last. Set this variable in the backend
+    # for prepending an order by clause for each attribute that causes
+    # correct sort order for NULLs. Examples:
+    # order_by_null_values = '(%s is not NULL)'
+    # order_by_null_values = 'notnull(%s)'
+    # The format parameter is replaced with the attribute.
+    order_by_null_values = None
+
+    # Assuming DBs can do subselects, overwrite if they cannot.
+    supports_subselects = True
+
     def schema(self):
         """ A dumpable version of the schema that we can store in the
             database
@@ -2383,19 +2394,6 @@
         return '_%s.id not in (select %s from %s%s)'%(classname, nodeid_name,
             multilink_table, w)
 
-    # Some DBs order NULL values last. Set this variable in the backend
-    # for prepending an order by clause for each attribute that causes
-    # correct sort order for NULLs. Examples:
-    # order_by_null_values = '(%s is not NULL)'
-    # order_by_null_values = 'notnull(%s)'
-    # The format parameter is replaced with the attribute.
-    order_by_null_values = None
-
-    def supports_subselects(self): 
-        '''Assuming DBs can do subselects, overwrite if they cannot.
-	'''
-        return True
-
     def _filter_multilink_expression_fallback(
         self, classname, multilink_table, expr):
         '''This is a fallback for database that do not support
@@ -2441,19 +2439,24 @@
         # we have ids of the classname table
         return ids.where("_%s.id" % classname, self.db.arg)
 
-    def _filter_multilink_expression(self, classname, multilink_table,
-        linkid_name, nodeid_name, v):
+    def _filter_multilink_expression(self, proptree, v):
         """ Filters out elements of the classname table that do not
             match the given expression.
             Returns tuple of 'WHERE' introns for the overall filter.
         """
+        classname = proptree.parent.uniqname
+        multilink_table = proptree.propclass.table_name
+        nid = proptree.propclass.nodeid_name
+        lid = proptree.propclass.linkid_name
+
         try:
             opcodes = [int(x) for x in v]
-            if min(opcodes) >= -1: raise ValueError()
+            if min(opcodes) >= -1:
+                raise ValueError()
 
             expr = compile_expression(opcodes)
 
-            if not self.supports_subselects():
+            if not self.supports_subselects:
                 # We heavily rely on subselects. If there is
                 # no decent support fall back to slower variant.
                 return self._filter_multilink_expression_fallback(
@@ -2461,7 +2464,7 @@
 
             atom = \
                 "%s IN(SELECT %s FROM %s WHERE %s=a.id)" % (
-                self.db.arg, linkid_name, multilink_table, nodeid_name)
+                self.db.arg, lid, multilink_table, nid)
 
             intron = \
                 "_%(classname)s.id in (SELECT id " \
@@ -2475,10 +2478,20 @@
 
             return intron, values
         except:
-            # original behavior
-            where = "%s.%s in (%s)" % (
-                multilink_table, linkid_name, ','.join([self.db.arg] * len(v)))
-            return where, v, True # True to indicate original
+            # fallback behavior when expression parsing above fails
+            orclause = ''
+            if '-1' in v :
+                v = [x for x in v if int (x) > 0]
+                orclause = self._subselect(proptree)
+            where = []
+            where.append("%s.%s in (%s)" % (multilink_table, lid,
+                ','.join([self.db.arg] * len(v))))
+            where.append('_%s.id=%s.%s'%(classname, multilink_table, nid))
+            where = ' and '.join (where)
+            if orclause :
+                where = '((' + ' or '.join ((where + ')', orclause)) + ')'
+
+            return where, v
 
     def _filter_sql (self, search_matches, filterspec, srt=[], grp=[], retr=0,
                      retired=False, exact_match_spec={}, limit=None,
@@ -2550,16 +2563,6 @@
                         where.append(self._subselect(p))
                     else:
                         frum.append(tn)
-                        gen_join = True
-
-                        if p.has_values and isinstance(v, type([])):
-                            result = self._filter_multilink_expression(pln,
-                                tn, lid, nid, v)
-                            # XXX: We dont need an id join if we used the filter
-                            gen_join = len(result) == 3
-
-                        if gen_join:
-                            where.append('_%s.id=%s.%s'%(pln, tn, nid))
 
                         if p.children or p.need_child_retired:
                             frum.append('_%s as _%s' % (cn, ln))
@@ -2567,10 +2570,15 @@
                             if p.need_child_retired:
                                 where.append('_%s.__retired__=0'%(ln))
 
+                        if not p.has_values or not isinstance(v, type([])):
+                            where.append('_%s.id=%s.%s'%(pln, tn, nid))
                         if p.has_values:
                             if isinstance(v, type([])):
-                                where.append(result[0])
-                                args += result[1]
+                                # The where-clause above is conditionally
+                                # created in _filter_multilink_expression
+                                w, arg = self._filter_multilink_expression(p, v)
+                                where.append(w)
+                                args += arg
                             else:
                                 where.append('%s.%s=%s'%(tn, lid, a))
                                 args.append(v)

Roundup Issue Tracker: http://roundup-tracker.org/