closes #71132: column alias scope should be handled as variable scope, not bound to subquery
--- 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: