closes #71132: column alias scope should be handled as variable scope, not bound to subquery
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 19 Jul 2011 18:15:08 +0200
changeset 643 0e6e5192f32c
parent 642 1f4c9c361cd4
child 644 3142a4184677
closes #71132: column alias scope should be handled as variable scope, not bound to subquery
ChangeLog
nodes.py
stmts.py
test/unittest_stcheck.py
--- a/ChangeLog	Tue Jul 19 18:12:59 2011 +0200
+++ b/ChangeLog	Tue Jul 19 18:15:08 2011 +0200
@@ -9,6 +9,9 @@
 
     * #71131: as_string doesn't  propagate encoding/kwargs to subqueries
 
+    * #71132: column alias scope should be handled as variable scope, not bound
+      to subquery
+
     * Select.replace must properly reset old node's parent attribute
 
     * new undo_modification context manager on select nodes
--- a/nodes.py	Tue Jul 19 18:12:59 2011 +0200
+++ b/nodes.py	Tue Jul 19 18:15:08 2011 +0200
@@ -826,7 +826,7 @@
 ###############################################################################
 
 class Referenceable(object):
-    __slots__ = ('name', 'stinfo')
+    __slots__ = ('name', 'stinfo', 'stmt')
 
     def __init__(self, name):
         self.name = name.strip().encode()
@@ -835,6 +835,12 @@
             # link to VariableReference objects in the syntax tree
             'references': set(),
             }
+        # reference to the selection
+        self.stmt = None
+
+    @property
+    def schema(self):
+        return self.stmt.root.schema
 
     def init_copy(self, old):
         # should copy variable's possibletypes on copy
@@ -863,6 +869,7 @@
 
     def prepare_annotation(self):
         self.stinfo.update({
+            'scope': None,
             # relations where this variable is used on the lhs/rhs
             'relations': set(),
             'rhsrelations': set(),
@@ -883,6 +890,18 @@
         for key in ('optrelations', 'blocsimplification', 'ftirels'):
             self.stinfo.pop(key, None)
 
+    def _set_scope(self, key, scopenode):
+        if scopenode is self.stmt or self.stinfo[key] is None:
+            self.stinfo[key] = scopenode
+        elif not (self.stinfo[key] is self.stmt or scopenode is self.stinfo[key]):
+            self.stinfo[key] = common_parent(self.stinfo[key], scopenode).scope
+
+    def set_scope(self, scopenode):
+        self._set_scope('scope', scopenode)
+    def get_scope(self):
+        return self.stinfo['scope']
+    scope = property(get_scope, set_scope)
+
     def add_optional_relation(self, relation):
         try:
             self.stinfo['optrelations'].add(relation)
@@ -986,10 +1005,6 @@
     def __repr__(self):
         return 'alias %s(%#X)' % (self.name, id(self))
 
-    @property
-    def schema(self):
-        return self.query.root.schema
-
     def get_type(self, solution=None, kwargs=None):
         """return entity type of this object, 'Any' if not found"""
         vtype = super(ColumnAlias, self).get_type(solution, kwargs)
@@ -1014,12 +1029,6 @@
                 return ', '.join(sorted(vtype for vtype in vtypes))
         return vtype
 
-    def set_scope(self, scopenode):
-        pass
-    def get_scope(self):
-        return self.query
-    scope = property(get_scope, set_scope)
-
 
 class Variable(Referenceable):
     """
@@ -1028,37 +1037,11 @@
 
     collects information about a variable use in a syntax tree
     """
-    __slots__ = ('stmt',
-                 '_q_invariant', '_q_sql', '_q_sqltable') # XXX ginco specific
-
-    def __init__(self, name):
-        super(Variable, self).__init__(name)
-        # reference to the selection
-        self.stmt = None
+    __slots__ = ('_q_invariant', '_q_sql', '_q_sqltable') # XXX ginco specific
 
     def __repr__(self):
         return '%s(%#X)' % (self.name, id(self))
 
-    @property
-    def schema(self):
-        return self.stmt.root.schema
-
-    def prepare_annotation(self):
-        super(Variable, self).prepare_annotation()
-        self.stinfo['scope'] = None
-
-    def _set_scope(self, key, scopenode):
-        if scopenode is self.stmt or self.stinfo[key] is None:
-            self.stinfo[key] = scopenode
-        elif not (self.stinfo[key] is self.stmt or scopenode is self.stinfo[key]):
-            self.stinfo[key] = common_parent(self.stinfo[key], scopenode).scope
-
-    def set_scope(self, scopenode):
-        self._set_scope('scope', scopenode)
-    def get_scope(self):
-        return self.stinfo['scope']
-    scope = property(get_scope, set_scope)
-
     def valuable_references(self):
         """return the number of "valuable" references :
         references is in selection or in a non type (is) relations
--- a/stmts.py	Tue Jul 19 18:12:59 2011 +0200
+++ b/stmts.py	Tue Jul 19 18:15:08 2011 +0200
@@ -618,6 +618,7 @@
             return self.aliases[name]
         if colnum is not None: # take care, may be 0
             self.aliases[name] = calias = nodes.ColumnAlias(name, colnum)
+            calias.stmt = self
             # alias may already have been used as a regular variable, replace it
             if name in self.defined_vars:
                 var = self.defined_vars.pop(name)
--- a/test/unittest_stcheck.py	Tue Jul 19 18:12:59 2011 +0200
+++ b/test/unittest_stcheck.py	Tue Jul 19 18:15:08 2011 +0200
@@ -314,6 +314,8 @@
         C = rqlst.with_[0].query.children[0].defined_vars['C']
         self.failUnless(C.scope is rqlst.with_[0].query.children[0], C.scope)
         self.assertEqual(len(C.stinfo['relations']), 2)
+        X = rqlst.get_variable('X')
+        self.failUnless(X.scope is rqlst, X.scope)
 
     def test_no_attr_var_if_uid_rel(self):
         with self.assertRaises(BadRQLQuery) as cm: