[rql rewriting] handle case where we've and optional inlined relation in the original query. Also, we should append EXISTS even in subquery to avoid inserting duplicates in results
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 15 Mar 2010 18:07:54 +0100
changeset 4907 e623afd49356
parent 4906 9a50539f01d1
child 4908 b3ad329cbe17
[rql rewriting] handle case where we've and optional inlined relation in the original query. Also, we should append EXISTS even in subquery to avoid inserting duplicates in results
rqlrewrite.py
test/data/rewrite/schema.py
test/unittest_rqlrewrite.py
--- a/rqlrewrite.py	Mon Mar 15 18:06:32 2010 +0100
+++ b/rqlrewrite.py	Mon Mar 15 18:07:54 2010 +0100
@@ -304,21 +304,33 @@
         """introduce the given snippet in a subquery"""
         subselect = stmts.Select()
         selectvar = varmap[0]
-        subselect.append_selected(n.VariableRef(
-            subselect.get_variable(selectvar)))
+        subselectvar = subselect.get_variable(selectvar)
+        subselect.append_selected(n.VariableRef(subselectvar))
+        snippetrqlst = n.Exists(transformedsnippet.copy(subselect))
         aliases = [selectvar]
-        subselect.add_restriction(transformedsnippet.copy(subselect))
         stinfo = self.varinfo['stinfo']
+        need_null_test = False
         for rel in stinfo['relations']:
             rschema = self.schema.rschema(rel.r_type)
             if rschema.final or (rschema.inlined and
-                                      not rel in stinfo['rhsrelations']):
-                self.select.remove_node(rel)
-                rel.children[0].name = selectvar
+                                 not rel in stinfo['rhsrelations']):
+                rel.children[0].name = selectvar # XXX explain why
                 subselect.add_restriction(rel.copy(subselect))
                 for vref in rel.children[1].iget_nodes(n.VariableRef):
                     subselect.append_selected(vref.copy(subselect))
                     aliases.append(vref.name)
+                self.select.remove_node(rel)
+                # when some inlined relation has to be copied in the subquery,
+                # we need to test that either value is NULL or that the snippet
+                # condition is satisfied
+                if rschema.inlined and rel.optional:
+                    need_null_test = True
+        if need_null_test:
+            snippetrqlst = n.Or(
+                n.make_relation(subselectvar, 'is', (None, None), n.Constant,
+                                operator='IS'),
+                snippetrqlst)
+        subselect.add_restriction(snippetrqlst)
         if self.u_varname:
             # generate an identifier for the substitution
             argname = subselect.allocate_varname()
--- a/test/data/rewrite/schema.py	Mon Mar 15 18:06:32 2010 +0100
+++ b/test/data/rewrite/schema.py	Mon Mar 15 18:07:54 2010 +0100
@@ -39,3 +39,10 @@
 class require_state(RelationDefinition):
     subject = 'CWPermission'
     object = 'State'
+
+
+class inlined_card(RelationDefinition):
+    subject = 'Affaire'
+    object = 'Card'
+    inlined = True
+    cardinality = '?*'
--- a/test/unittest_rqlrewrite.py	Mon Mar 15 18:06:32 2010 +0100
+++ b/test/unittest_rqlrewrite.py	Mon Mar 15 18:07:54 2010 +0100
@@ -123,7 +123,7 @@
                              "EXISTS(2 in_state A, B in_group D, E require_state A, "
                              "E name 'read', E require_group D, A is State, D is CWGroup, E is CWPermission)")
 
-    def test_optional_var(self):
+    def test_optional_var_base(self):
         card_constraint = ('X in_state S, U in_group G, P require_state S,'
                            'P name "read", P require_group G')
         rqlst = parse('Any A,C WHERE A documented_by C?')
@@ -131,15 +131,41 @@
         self.failUnlessEqual(rqlst.as_string(),
                              "Any A,C WHERE A documented_by C?, A is Affaire "
                              "WITH C BEING "
-                             "(Any C WHERE C in_state B, D in_group F, G require_state B, G name 'read', "
-                             "G require_group F, D eid %(A)s, C is Card)")
+                             "(Any C WHERE EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', "
+                             "G require_group F), D eid %(A)s, C is Card)")
         rqlst = parse('Any A,C,T WHERE A documented_by C?, C title T')
         rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {})
         self.failUnlessEqual(rqlst.as_string(),
                              "Any A,C,T WHERE A documented_by C?, A is Affaire "
                              "WITH C,T BEING "
-                             "(Any C,T WHERE C in_state B, D in_group F, G require_state B, G name 'read', "
-                             "G require_group F, C title T, D eid %(A)s, C is Card)")
+                             "(Any C,T WHERE C title T, EXISTS(C in_state B, D in_group F, "
+                             "G require_state B, G name 'read', G require_group F), "
+                             "D eid %(A)s, C is Card)")
+
+    def test_optional_var_inlined(self):
+        c1 = ('X require_permission P')
+        c2 = ('X inlined_card O, O require_permission P')
+        rqlst = parse('Any C,A,R WHERE A? inlined_card C, A ref R')
+        rewrite(rqlst, {('C', 'X'): (c1,),
+                        ('A', 'X'): (c2,),
+                        }, {})
+        # XXX suboptimal
+        self.failUnlessEqual(rqlst.as_string(),
+                             "Any C,A,R WITH A,R,C BEING "
+                             "(Any A,R,C WHERE A ref R, A? inlined_card C, "
+                             "(A is NULL) OR (EXISTS(A inlined_card B, B require_permission D, "
+                             "B is Card, D is CWPermission)), "
+                             "A is Affaire, C is Card, EXISTS(C require_permission E, E is CWPermission))")
+
+    # def test_optional_var_inlined_has_perm(self):
+    #     c1 = ('X require_permission P')
+    #     c2 = ('X inlined_card O, U has_read_permission O')
+    #     rqlst = parse('Any C,A,R WHERE A? inlined_card C, A ref R')
+    #     rewrite(rqlst, {('C', 'X'): (c1,),
+    #                     ('A', 'X'): (c2,),
+    #                     }, {})
+    #     self.failUnlessEqual(rqlst.as_string(),
+    #                          "")
 
     def test_relation_optimization_1_lhs(self):
         # since Card in_state State as monovalued cardinality, the in_state
@@ -243,7 +269,7 @@
         rewrite(rqlst, {('X', 'X'): (constraint,)}, {})
         # ambiguity are kept in the sub-query, no need to be resolved using OR
         self.failUnlessEqual(rqlst.as_string(),
-                             u"Any X,C WHERE X? documented_by C, C is Card WITH X BEING (Any X WHERE X concerne A, X is Affaire)")
+                             u"Any X,C WHERE X? documented_by C, C is Card WITH X BEING (Any X WHERE EXISTS(X concerne A), X is Affaire)")
 
 
     def test_rrqlexpr_nonexistant_subject_1(self):