--- a/ChangeLog Tue Jul 05 12:49:38 2011 +0200
+++ b/ChangeLog Thu Jul 21 08:11:07 2011 +0200
@@ -2,10 +2,20 @@
=================
--
- * remove_group_var renamed into remove_group_term and fixed implementation
+ * #70264: remove_group_var renamed into remove_group_term and fixed
+ implementation
+
+ * #70416: rql annotator add 'having' list into variable's stinfo, and
+ properly update variable graph
- * rql annotator add 'having' list into variable's stinfo, and
- properly update variable graph
+ * #71131: as_string doesn't propagate encoding/kwargs to subqueries
+
+ * #71132: column alias scope should be handled as variable scope, not bound
+ to subquery
+
+ * #71157: bad analyze when using functions
+
+ * Select.replace must properly reset old node's parent attribute
* new undo_modification context manager on select nodes
--- a/analyze.py Tue Jul 05 12:49:38 2011 +0200
+++ b/analyze.py Thu Jul 21 08:11:07 2011 +0200
@@ -493,45 +493,33 @@
if not isinstance(lhs, nodes.VariableRef) or rhs.type is None:
return True
self._extract_constraint(constraints, lhs.name, rhs, rschema.subjects)
- else:
- if not isinstance(lhs, nodes.VariableRef):
- # XXX: check relation is valid
- return True
+ elif not isinstance(lhs, nodes.VariableRef):
+ # XXX: check relation is valid
+ return True
+ elif isinstance(rhs, nodes.VariableRef):
lhsvar = lhs.name
- rhsvars = []
- samevar = False
- if not isinstance(rhs, nodes.MathExpression):
- # rhs type is the result of the math expression, not of
- # individual variables, so don't add constraints on rhs
- # variables
- for v in rhs.iget_nodes(nodes.VariableRef):
- if v.name == lhsvar:
- samevar = True
- else:
- rhsvars.append(v.name)
+ rhsvar = rhs.name
lhsdomain = constraints.domains[lhsvar]
- if rhsvars:
- s2 = '=='.join(rhsvars)
- # filter according to domain necessary for column aliases
- rhsdomain = constraints.domains[rhsvars[0]]
- res = []
- for fromtype, totypes in rschema.associations():
- if not fromtype in lhsdomain:
- continue
- ptypes = [str(t) for t in totypes if t in rhsdomain]
- res.append( [ ( [lhsvar], [str(fromtype)]), (rhsvars, ptypes) ] )
- constraints.or_and( res )
- else:
- ptypes = [str(subj) for subj in rschema.subjects()
- if subj in lhsdomain]
- constraints.var_has_types( lhsvar, ptypes )
- if samevar:
- res = []
- for fromtype, totypes in rschema.associations():
- if not (fromtype in totypes and fromtype in lhsdomain):
- continue
- res.append(str(fromtype))
+ # filter according to domain necessary for column aliases
+ rhsdomain = constraints.domains[rhsvar]
+ res = []
+ for fromtype, totypes in rschema.associations():
+ if not fromtype in lhsdomain:
+ continue
+ ptypes = [str(t) for t in totypes if t in rhsdomain]
+ res.append( [ ([lhsvar], [str(fromtype)]),
+ ([rhsvar], ptypes) ] )
+ constraints.or_and(res)
+ if rhsvar == lhsvar:
+ res = [str(fromtype) for fromtype, totypes in rschema.associations()
+ if (fromtype in totypes and fromtype in lhsdomain)]
constraints.var_has_types( lhsvar, res )
+ else:
+ # XXX consider rhs.get_type?
+ lhsdomain = constraints.domains[lhs.name]
+ ptypes = [str(subj) for subj in rschema.subjects()
+ if subj in lhsdomain]
+ constraints.var_has_types( lhs.name, ptypes )
return True
def visit_type_restriction(self, relation, constraints):
--- a/nodes.py Tue Jul 05 12:49:38 2011 +0200
+++ b/nodes.py Thu Jul 21 08:11:07 2011 +0200
@@ -195,6 +195,7 @@
"""builds a restriction node to express : variable is etype"""
return self.add_constant_restriction(var, 'is', etype, 'etype')
+
# base RQL nodes ##############################################################
class SubQuery(BaseNode):
@@ -224,7 +225,7 @@
def as_string(self, encoding=None, kwargs=None):
return '%s BEING (%s)' % (','.join(v.name for v in self.aliases),
- self.query.as_string())
+ self.query.as_string(encoding, kwargs))
def __repr__(self):
return '%s BEING (%s)' % (','.join(repr(v) for v in self.aliases),
repr(self.query))
@@ -825,7 +826,7 @@
###############################################################################
class Referenceable(object):
- __slots__ = ('name', 'stinfo')
+ __slots__ = ('name', 'stinfo', 'stmt')
def __init__(self, name):
self.name = name.strip().encode()
@@ -834,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
@@ -862,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(),
@@ -882,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)
@@ -985,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)
@@ -1013,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):
"""
@@ -1027,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 05 12:49:38 2011 +0200
+++ b/stmts.py Thu Jul 21 08:11:07 2011 +0200
@@ -484,7 +484,7 @@
s.append('WHERE ' + as_string(self.where))
if self.having:
s.append('HAVING ' + ','.join(as_string(term)
- for term in self.having))
+ for term in self.having))
if self.with_:
s.append('WITH ' + ','.join(as_string(term)
for term in self.with_))
@@ -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)
@@ -708,8 +709,7 @@
raise Exception('duh XXX %s' % oldnode)
# XXX no undo/reference support 'by design' (eg breaks things if you add
# it...)
- # XXX resetting oldnode parent cause pb with cw.test_views (w/ facets)
- #oldnode.parent = None
+ oldnode.parent = None
newnode.parent = self
return oldnode, self, None
--- a/test/unittest_analyze.py Tue Jul 05 12:49:38 2011 +0200
+++ b/test/unittest_analyze.py Thu Jul 21 08:11:07 2011 +0200
@@ -531,7 +531,7 @@
{'P': 'Student', 'S': 'Company', 'N': 'Int'}])
- def test_nongrer_not_u_ownedby_u(self):
+ def test_nonregr_not_u_ownedby_u(self):
node = self.helper.parse('Any U WHERE NOT U owned_by U')
self.helper.compute_solutions(node, debug=DEBUG)
sols = sorted(node.children[0].solutions)
--- a/test/unittest_stcheck.py Tue Jul 05 12:49:38 2011 +0200
+++ b/test/unittest_stcheck.py Thu Jul 21 08:11:07 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: