backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 10 Jun 2010 10:42:05 +0200
changeset 556 14b976e1bb13
parent 548 f9ead7086f2a (current diff)
parent 555 0314d8a14d0d (diff)
child 561 73216278aa9e
backport stable
--- a/ChangeLog	Mon Jun 07 13:07:44 2010 +0200
+++ b/ChangeLog	Thu Jun 10 10:42:05 2010 +0200
@@ -1,6 +1,9 @@
 ChangeLog for RQL
 =================
 
+	--
+    * fix simplification bug with ored uid relations
+
 2010-06-04  --  0.26.1
     * normalize NOT() to NOT EXISTS() when it makes sense
 
--- a/debian/control	Mon Jun 07 13:07:44 2010 +0200
+++ b/debian/control	Thu Jun 10 10:42:05 2010 +0200
@@ -13,7 +13,7 @@
 Architecture: any
 XB-Python-Version: ${python:Versions}
 Depends: ${python:Depends}, ${misc:Depends}, ${shlibs:Depends}, python-logilab-common (>= 0.35.3-1), yapps2-runtime, python-logilab-database
-Conflicts: cubicweb-common (< 3.8.3)
+Conflicts: cubicweb-common (< 3.8.4)
 Provides: ${python:Provides}
 Description: relationship query language (RQL) utilities
  A library providing the base utilities to handle RQL queries,
--- a/nodes.py	Mon Jun 07 13:07:44 2010 +0200
+++ b/nodes.py	Thu Jun 10 10:42:05 2010 +0200
@@ -466,6 +466,8 @@
         self.optional= value
 
 
+OPERATORS = frozenset(('=', '<', '<=', '>=', '>', 'ILIKE', 'LIKE'))
+
 class Comparison(HSMixin, Node):
     """handle comparisons:
 
@@ -477,10 +479,7 @@
         Node.__init__(self)
         if operator == '~=':
             operator = 'ILIKE'
-        elif operator == '=' and isinstance(value, Constant) and \
-                 value.type is None:
-            operator = 'IS'
-        assert operator in ('=', '<', '<=', '>=', '>', 'ILIKE', 'LIKE', 'IS'), operator
+        assert operator in OPERATORS, operator
         self.operator = operator.encode()
         if value is not None:
             self.append(value)
@@ -502,7 +501,7 @@
             return '%s %s %s' % (self.children[0].as_string(encoding, kwargs),
                                  self.operator.encode(),
                                  self.children[1].as_string(encoding, kwargs))
-        if self.operator in ('=', 'IS'):
+        if self.operator == '=':
             return self.children[0].as_string(encoding, kwargs)
         return '%s %s' % (self.operator.encode(),
                           self.children[0].as_string(encoding, kwargs))
--- a/parser.g	Mon Jun 07 13:07:44 2010 +0200
+++ b/parser.g	Thu Jun 10 10:42:05 2010 +0200
@@ -88,7 +88,7 @@
     token FALSE:       r'(?i)FALSE'
     token NULL:        r'(?i)NULL'
     token EXISTS:      r'(?i)EXISTS'
-    token CMP_OP:      r'(?i)<=|<|>=|>|~=|=|LIKE|ILIKE|IS'
+    token CMP_OP:      r'(?i)<=|<|>=|>|~=|=|LIKE|ILIKE'
     token ADD_OP:      r'\+|-'
     token MUL_OP:      r'\*|/'
     token FUNCTION:    r'[A-Za-z_]+\s*(?=\()'
--- a/parser.py	Mon Jun 07 13:07:44 2010 +0200
+++ b/parser.py	Thu Jun 10 10:42:05 2010 +0200
@@ -109,7 +109,7 @@
         ('FALSE', re.compile('(?i)FALSE')),
         ('NULL', re.compile('(?i)NULL')),
         ('EXISTS', re.compile('(?i)EXISTS')),
-        ('CMP_OP', re.compile('(?i)<=|<|>=|>|~=|=|LIKE|ILIKE|IS')),
+        ('CMP_OP', re.compile('(?i)<=|<|>=|>|~=|=|LIKE|ILIKE')),
         ('ADD_OP', re.compile('\\+|-')),
         ('MUL_OP', re.compile('\\*|/')),
         ('FUNCTION', re.compile('[A-Za-z_]+\\s*(?=\\()')),
--- a/stcheck.py	Mon Jun 07 13:07:44 2010 +0200
+++ b/stcheck.py	Thu Jun 10 10:42:05 2010 +0200
@@ -473,6 +473,7 @@
         #assert not node.annotated
         node.accept(self)
         node.annotated = True
+
     def _visit_stmt(self, node):
         for var in node.defined_vars.itervalues():
             var.prepare_annotation()
@@ -627,7 +628,10 @@
                 if key == 'uidrels':
                     constnode = relation.get_variable_parts()[1]
                     if not (relation.operator() != '=' or
-                            isinstance(relation.parent, Not)):
+                            # XXX use state to detect relation under NOT/OR
+                            # + check variable's scope
+                            isinstance(relation.parent, Not) or
+                            relation.parent.ored()):
                         if isinstance(constnode, Constant):
                             lhsvar.stinfo['constnode'] = constnode
                         lhsvar.stinfo['uidrel'] = relation
@@ -643,7 +647,6 @@
             if vref is rhs.children[0] and rschema.final:
                 update_attrvars(var, relation, lhs)
 
-
 def update_attrvars(var, relation, lhs):
     # stinfo['attrvars'] is set of couple (lhs variable name, relation name)
     # where the `var` attribute variable is used
--- a/stmts.py	Mon Jun 07 13:07:44 2010 +0200
+++ b/stmts.py	Thu Jun 10 10:42:05 2010 +0200
@@ -271,14 +271,27 @@
 
     # union specific methods ##################################################
 
+    # XXX for bw compat, should now use get_variable_indices (cw > 3.8.4)
     def get_variable_variables(self):
-        """return the set of variable names which take different type according
-        to the solutions
+        change = set()
+        for idx in self.get_variable_indices():
+            for vref in self.children[0].selection[idx].iget_nodes(nodes.VariableRef):
+                change.add(vref.name)
+        return change
+
+    def get_variable_indices(self):
+        """return the set of selection indexes which take different types
+        according to the solutions
         """
         change = set()
         values = {}
         for select in self.children:
-            change.update(select.get_variable_variables(values))
+            for descr in select.get_selection_solutions():
+                for i, etype in enumerate(descr):
+                    values.setdefault(i, set()).add(etype)
+        for idx, etypes in values.iteritems():
+            if len(etypes) > 1:
+                change.add(idx)
         return change
 
     def _locate_subquery(self, col, etype, kwargs):
@@ -623,20 +636,20 @@
                     newsolutions.append(asol)
             self.solutions = newsolutions
 
-    def get_variable_variables(self, _values=None):
+    def get_selection_solutions(self):
         """return the set of variable names which take different type according
         to the solutions
         """
-        change = set()
-        if _values is None:
-            _values = {}
+        descriptions = set()
         for solution in self.solutions:
-            for vname, etype in solution.iteritems():
-                if not vname in _values:
-                    _values[vname] = etype
-                elif _values[vname] != etype:
-                    change.add(vname)
-        return change
+            descr = []
+            for term in self.selection:
+                try:
+                    descr.append(term.get_type(solution=solution))
+                except CoercionError:
+                    pass
+            descriptions.add(tuple(descr))
+        return descriptions
 
     # quick accessors #########################################################
 
--- a/test/unittest_nodes.py	Mon Jun 07 13:07:44 2010 +0200
+++ b/test/unittest_nodes.py	Thu Jun 10 10:42:05 2010 +0200
@@ -355,12 +355,31 @@
         self.assertEquals(tree.defined_vars['X'].selected_index(), 0)
         self.assertEquals(tree.defined_vars['N'].selected_index(), None)
 
-    def test_get_variable_variables(self):
-        dummy = self._simpleparse("Any X")
-        dummy.solutions = [{'A': 'String', 'B': 'EUser', 'C': 'EGroup'},
-                           {'A': 'String', 'B': 'Personne', 'C': 'EGroup'},
-                           {'A': 'String', 'B': 'EUser', 'C': 'Societe'}]
-        self.assertEquals(dummy.get_variable_variables(), set(['B', 'C']))
+    def test_get_variable_indices_1(self):
+        dummy = self._parse("Any A,B,C")
+        dummy.children[0].solutions = [{'A': 'String', 'B': 'EUser', 'C': 'EGroup'},
+                                       {'A': 'String', 'B': 'Personne', 'C': 'EGroup'},
+                                       {'A': 'String', 'B': 'EUser', 'C': 'Societe'}]
+        self.assertEquals(dummy.get_variable_indices(), set([1, 2]))
+
+    def test_get_variable_indices_2(self):
+        dummy = self._parse("Any A,B WHERE B relation C")
+        dummy.children[0].solutions = [{'A': 'String', 'B': 'EUser', 'C': 'EGroup'},
+                                       {'A': 'String', 'B': 'Personne', 'C': 'EGroup'},
+                                       {'A': 'String', 'B': 'EUser', 'C': 'Societe'}]
+        self.assertEquals(dummy.get_variable_indices(), set([1]))
+
+    def test_get_variable_indices_3(self):
+        dummy = self._parse("(Any X WHERE X is EGroup) UNION (Any C WHERE C is EUser)")
+        dummy.children[0].solutions = [{'X': 'EGroup'}]
+        dummy.children[1].solutions = [{'C': 'EUser'}]
+        self.assertEquals(dummy.get_variable_indices(), set([0]))
+
+    def test_get_variable_indices_4(self):
+        dummy = self._parse("(Any X,XN WHERE X is EGroup, X name XN) UNION (Any C,CL WHERE C is EUser, C login CL)")
+        dummy.children[0].solutions = [{'X': 'EGroup', 'XN': 'String'}]
+        dummy.children[1].solutions = [{'C': 'EUser', 'CL': 'String'}]
+        self.assertEquals(dummy.get_variable_indices(), set([0]))
 
     # insertion tests #########################################################
 
--- a/test/unittest_stcheck.py	Mon Jun 07 13:07:44 2010 +0200
+++ b/test/unittest_stcheck.py	Thu Jun 10 10:42:05 2010 +0200
@@ -170,6 +170,12 @@
             # A eid 12 can be removed since the type analyzer checked its existence
             ('Any X WHERE A eid 12, X connait Y',
              'Any X WHERE X connait Y'),
+
+            ('Any X WHERE EXISTS(X work_for Y, Y eid 12) OR X eid 12',
+             'Any X WHERE (EXISTS(X work_for 12)) OR (X eid 12)'),
+
+            ('Any X WHERE EXISTS(X work_for Y, Y eid IN (12)) OR X eid IN (12)',
+             'Any X WHERE (EXISTS(X work_for 12)) OR (X eid 12)'),
             ):
             yield self._test_rewrite, rql, expected