need_intersect test and fixes
authorsylvain.thenault@logilab.fr
Fri, 16 Jan 2009 18:31:13 +0100
changeset 438 69b79faefa94
parent 437 5d8dc9678707
child 439 7cb7cb318983
need_intersect test and fixes
server/querier.py
server/rqlannotation.py
server/sources/native.py
server/sources/rql2sql.py
server/test/unittest_rql2sql.py
--- a/server/querier.py	Thu Jan 15 17:37:43 2009 +0100
+++ b/server/querier.py	Fri Jan 16 18:31:13 2009 +0100
@@ -196,7 +196,7 @@
             self._insert_security(union, noinvariant)
         self.rqlhelper.simplify(union)
         self.sqlannotate(union)
-        set_qdata(union, noinvariant)
+        set_qdata(self.schema.rschema, union, noinvariant)
         if union.has_text_query:
             self.cache_key = None
 
--- a/server/rqlannotation.py	Thu Jan 15 17:37:43 2009 +0100
+++ b/server/rqlannotation.py	Fri Jan 16 18:31:13 2009 +0100
@@ -20,7 +20,7 @@
     #if server.DEBUG:
     #    print '-------- sql annotate', repr(rqlst)
     getrschema = annotator.schema.rschema
-    has_text_query = need_intersect = False
+    has_text_query = False
     need_distinct = rqlst.distinct
     for rel in rqlst.iget_nodes(Relation):
         if rel.neged(strict=True):
@@ -29,13 +29,6 @@
             else:
                 rschema = getrschema(rel.r_type)
                 if not rschema.is_final():
-                    # if one of the relation's variable is ambiguous, an intersection
-                    # will be necessary
-                    for vref in rel.get_nodes(VariableRef):
-                        var = vref.variable
-                        if not var.stinfo['selected'] and len(var.stinfo['possibletypes']) > 1:
-                            need_intersect = True
-                            break
                     if rschema.inlined:
                         try:
                             var = rel.children[1].children[0].variable
@@ -147,7 +140,6 @@
             except CantSelectPrincipal:
                 stinfo['invariant'] = False
     rqlst.need_distinct = need_distinct
-    rqlst.need_intersect = need_intersect
     return has_text_query
 
 
@@ -207,12 +199,12 @@
     return principal
 
 
-def set_qdata(union, noinvariant):
+def set_qdata(getrschema, union, noinvariant):
     """recursive function to set querier data on variables in the syntax tree
     """
     for select in union.children:
         for subquery in select.with_:
-            set_qdata(subquery.query, noinvariant)
+            set_qdata(getrschema, subquery.query, noinvariant)
         for var in select.defined_vars.itervalues():
             if var.stinfo['invariant']:
                 if var in noinvariant and not var.stinfo['principal'].r_type == 'has_text':
@@ -221,6 +213,23 @@
                     var._q_invariant = True
             else:
                 var._q_invariant = False
+        for rel in select.iget_nodes(Relation):
+            if rel.neged(strict=True) and not rel.is_types_restriction():
+                rschema = getrschema(rel.r_type)
+                if not rschema.is_final():
+                    # if one of the relation's variable is ambiguous but not
+                    # invariant, an intersection will be necessary
+                    for vref in rel.get_nodes(VariableRef):
+                        var = vref.variable
+                        if (not var._q_invariant and var.valuable_references() == 1
+                            and len(var.stinfo['possibletypes']) > 1):
+                            select.need_intersect = True
+                            break
+                    else:
+                        continue
+                    break
+        else:
+            select.need_intersect = False
 
 
 class SQLGenAnnotator(object):
--- a/server/sources/native.py	Thu Jan 15 17:37:43 2009 +0100
+++ b/server/sources/native.py	Fri Jan 16 18:31:13 2009 +0100
@@ -188,7 +188,7 @@
         rqlst.restricted_vars = ()
         rqlst.children[0].solutions = self._sols
         self.repo.querier.sqlgen_annotate(rqlst)
-        set_qdata(rqlst, ())
+        set_qdata(self.schema.rschema, rqlst, ())
         return rqlst
     
     def set_schema(self, schema):
--- a/server/sources/rql2sql.py	Thu Jan 15 17:37:43 2009 +0100
+++ b/server/sources/rql2sql.py	Fri Jan 16 18:31:13 2009 +0100
@@ -488,7 +488,8 @@
                 sql.insert(1, 'FROM (SELECT 1) AS _T')
             sqls.append('\n'.join(sql))
         if select.need_intersect:
-            if distinct:
+            # XXX use getattr for lgc bw compat, remove once 0.37.3 is out
+            if distinct or not getattr(self.dbms_helper, 'intersect_all_support', True):
                 return '\nINTERSECT\n'.join(sqls)
             else:
                 return '\nINTERSECT ALL\n'.join(sqls)
--- a/server/test/unittest_rql2sql.py	Thu Jan 15 17:37:43 2009 +0100
+++ b/server/test/unittest_rql2sql.py	Fri Jan 16 18:31:13 2009 +0100
@@ -156,11 +156,6 @@
 ]
 
 ADVANCED= [
-    ('Any X WHERE X is ET, ET eid 2',
-     '''SELECT rel_is0.eid_from
-FROM is_relation AS rel_is0
-WHERE rel_is0.eid_to=2'''),
-
 
     ("Societe S WHERE S nom 'Logilab' OR S nom 'Caesium'",
      '''SELECT S.eid
@@ -1064,7 +1059,57 @@
     
     ]
 
+INTERSECT = [
+    ('Any SN WHERE NOT X in_state S, S name SN',
+     '''SELECT DISTINCT S.name
+FROM Affaire AS X, State AS S
+WHERE (X.in_state IS NULL OR X.in_state!=S.eid)
+INTERSECT
+SELECT DISTINCT S.name
+FROM EUser AS X, State AS S
+WHERE (X.in_state IS NULL OR X.in_state!=S.eid)
+INTERSECT
+SELECT DISTINCT S.name
+FROM Note AS X, State AS S
+WHERE (X.in_state IS NULL OR X.in_state!=S.eid)'''),
 
+    ('Any PN WHERE NOT X travaille S, X nom PN, S is IN(Division, Societe)',
+     '''SELECT X.nom
+FROM Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,Division AS S WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid)
+INTERSECT ALL
+SELECT X.nom
+FROM Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,Societe AS S WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid)'''),
+    
+    ('Any PN WHERE NOT X travaille S, S nom PN, S is IN(Division, Societe)',
+     '''SELECT S.nom
+FROM Division AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=S.eid)
+UNION ALL
+SELECT S.nom
+FROM Societe AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=S.eid)'''),
+    
+    ('Personne X WHERE NOT X travaille S, S nom "chouette"',
+     '''SELECT X.eid
+FROM Division AS S, Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid) AND S.nom=chouette
+UNION ALL
+SELECT X.eid
+FROM Personne AS X, Societe AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid) AND S.nom=chouette
+UNION ALL
+SELECT X.eid
+FROM Personne AS X, SubDivision AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid) AND S.nom=chouette'''),
+    
+    ('Any X WHERE X is ET, ET eid 2',
+     '''SELECT rel_is0.eid_from
+FROM is_relation AS rel_is0
+WHERE rel_is0.eid_to=2'''),
+
+    ]
 from logilab.common.adbh import ADV_FUNC_HELPER_DIRECTORY
     
 class PostgresSQLGeneratorTC(RQLGeneratorTC):
@@ -1196,6 +1241,10 @@
     def test_negation(self):
         for t in self._parse(NEGATIONS):
             yield t
+        
+    def test_intersection(self):
+        for t in self._parse(INTERSECT):
+            yield t
 
     def test_union(self):
         for t in self._parse((
@@ -1370,7 +1419,7 @@
         self.o = SQLGenerator(schema, dbms_helper)
 
     def _norm_sql(self, sql):
-        return sql.strip().replace(' ILIKE ', ' LIKE ')
+        return sql.strip().replace(' ILIKE ', ' LIKE ').replace('\nINTERSECT ALL\n', '\nINTERSECT\n')
 
     def test_union(self):
         for t in self._parse((