[rql2sql] Test and fix some case of sql generation
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 06 Jun 2016 21:19:04 +0200
changeset 11293 63c589e6b076
parent 11289 3e69bccc2022
child 11294 cd1183a65ebb
[rql2sql] Test and fix some case of sql generation when an unstable variable (ie whose type change from one solution to another) is used in an EXISTS node but belongs to an outer scope. Closes #13518646.
server/sources/rql2sql.py
server/test/unittest_rql2sql.py
--- a/server/sources/rql2sql.py	Thu Jun 16 17:45:16 2016 +0200
+++ b/server/sources/rql2sql.py	Mon Jun 06 21:19:04 2016 +0200
@@ -187,7 +187,8 @@
             try:
                 thisexistssols, thisexistsvars = existssols[var.scope]
             except KeyError:
-                thisexistssols = [newsols[0]]
+                # copy to avoid shared dict in newsols and exists sols
+                thisexistssols = [newsols[0].copy()]
                 thisexistsvars = set()
                 existssols[var.scope] = thisexistssols, thisexistsvars
             for i in range(len(newsols)-1, 0, -1):
@@ -199,6 +200,13 @@
             for i in range(1, len(newsols)):
                 if vtype != newsols[i][vname]:
                     unstable.add(vname)
+    # remove unstable variables from exists solutions: the possible types of these variables are not
+    # properly represented in exists solutions, so we have to remove and reinject them later
+    # according to the outer solution (see `iter_exists_sols`)
+    for sols, _ in existssols.values():
+        for vname in unstable:
+            for sol in sols:
+                sol.pop(vname, None)
     if invariants:
         # filter out duplicates
         newsols_ = []
@@ -400,24 +408,30 @@
         thisexistssols, thisexistsvars = self.existssols[exists]
         notdone_outside_vars = set()
         # when iterating other solutions inner to an EXISTS subquery, we should
-        # reset variables which have this exists node as scope at each iteration
+        # reset variables which have this EXISTS node as scope at each iteration
         for var in exists.stmt.defined_vars.values():
             if var.scope is exists:
                 thisexistsvars.add(var.name)
             elif var.name not in self.done:
                 notdone_outside_vars.add(var)
-        origsol = self.solution
+        # make a copy of the outer statement's solution for later restore
+        origsol = self.solution.copy()
         origtables = self.tables
         done = self.done
         for thisexistssol in thisexistssols:
             for vname in self.unstablevars:
-                if thisexistssol[vname] != origsol[vname] and vname in thisexistsvars:
+                # check first if variable belong to the EXISTS's scope, else it may be missing from
+                # `thisexistssol`
+                if vname in thisexistsvars and thisexistssol[vname] != origsol[vname]:
                     break
             else:
                 self.tables = origtables.copy()
-                self.solution = thisexistssol
+                # overwrite current outer solution by EXISTS solution (the later will be missing
+                # unstable outer variables)
+                self.solution.update(thisexistssol)
                 yield 1
-                # cleanup self.done from stuff specific to exists
+                # cleanup self.done from stuff specific to EXISTS, so they will be reconsidered in
+                # the next round
                 for var in thisexistsvars:
                     if var in done:
                         done.remove(var)
@@ -428,6 +442,7 @@
                 for rel in exists.iget_nodes(Relation):
                     if rel in done:
                         done.remove(rel)
+        # restore original solution
         self.solution = origsol
         self.tables = origtables
 
--- a/server/test/unittest_rql2sql.py	Thu Jun 16 17:45:16 2016 +0200
+++ b/server/test/unittest_rql2sql.py	Mon Jun 06 21:19:04 2016 +0200
@@ -574,8 +574,18 @@
     ''', '''SELECT _X.cw_eid
 FROM cw_CWSourceSchemaConfig AS _X LEFT OUTER JOIN owned_by_relation AS rel_owned_by1 ON (rel_owned_by1.eid_from=_X.cw_eid)
 WHERE EXISTS(SELECT 1 FROM created_by_relation AS rel_created_by0, cw_CWUser AS _U WHERE rel_created_by0.eid_from=_X.cw_eid AND rel_created_by0.eid_to=_U.cw_eid) AND _X.cw_cw_schema IS NOT NULL
-''')
-    ]
+'''),
+
+    ('Any X WHERE EXISTS(X in_state S, S name "state name"), X is in (Affaire, Note)',
+     '''SELECT _X.cw_eid
+FROM cw_Affaire AS _X
+WHERE EXISTS(SELECT 1 FROM cw_State AS _S WHERE _X.cw_in_state=_S.cw_eid AND _S.cw_name=state name)
+UNION ALL
+SELECT _X.cw_eid
+FROM cw_Note AS _X
+WHERE EXISTS(SELECT 1 FROM cw_State AS _S WHERE _X.cw_in_state=_S.cw_eid AND _S.cw_name=state name)'''),
+
+]
 
 ADVANCED_WITH_GROUP_CONCAT = [
         ("Any X,GROUP_CONCAT(TN) GROUPBY X ORDERBY XN WHERE T tags X, X name XN, T name TN, X is CWGroup",