# HG changeset patch # User Adrien Di Mascio # Date 1257873187 -3600 # Node ID 9fcf048e14b7564d40a48fe0108fe37037db3411 # Parent 37b376bb4088bd9d3e8ccf6cc2c36a0da2a60c94# Parent 50b87f759b5d857e9e47f95ece49146ef2ad5c06 merge diff -r 37b376bb4088 -r 9fcf048e14b7 server/msplanner.py --- a/server/msplanner.py Tue Nov 10 18:11:24 2009 +0100 +++ b/server/msplanner.py Tue Nov 10 18:13:07 2009 +0100 @@ -1470,11 +1470,19 @@ def visit_constant(self, node, newroot, terms): return copy_node(newroot, node), node + def visit_comparison(self, node, newroot, terms): + subparts, node = self._visit_children(node, newroot, terms) + copy = copy_node(newroot, node, subparts) + # ignore comparison operator when fetching non final query + if not self.final and isinstance(node.children[0], VariableRef): + copy.operator = '=' + return copy, node + def visit_default(self, node, newroot, terms): subparts, node = self._visit_children(node, newroot, terms) return copy_node(newroot, node, subparts), node - visit_comparison = visit_mathexpression = visit_constant = visit_function = visit_default + visit_mathexpression = visit_constant = visit_function = visit_default visit_sort = visit_sortterm = visit_default def _visit_children(self, node, newroot, terms): diff -r 37b376bb4088 -r 9fcf048e14b7 server/rqlannotation.py --- a/server/rqlannotation.py Tue Nov 10 18:11:24 2009 +0100 +++ b/server/rqlannotation.py Tue Nov 10 18:13:07 2009 +0100 @@ -10,6 +10,7 @@ from logilab.common.compat import any +from rql import BadRQLQuery from rql.nodes import Relation, VariableRef, Constant, Variable, Or from rql.utils import common_parent @@ -177,10 +178,20 @@ """given a list of rqlst relations, select one which will be used as main relation for the rhs variable """ - for rel in relations: + principal = None + # sort for test predictability + for rel in sorted(relations, key=lambda x: (x.children[0].name, x.r_type)): + # only equality relation with a variable as rhs may be principal + if rel.operator() not in ('=', 'IS') \ + or not isinstance(rel.children[1].children[0], VariableRef): + continue if rel.sqlscope is rel.stmt: return rel principal = rel + if principal is None: + print iter(relations).next().root + raise BadRQLQuery('unable to find principal in %s' % ', '.join( + r.as_string() for r in relations)) return principal diff -r 37b376bb4088 -r 9fcf048e14b7 server/sources/rql2sql.py --- a/server/sources/rql2sql.py Tue Nov 10 18:11:24 2009 +0100 +++ b/server/sources/rql2sql.py Tue Nov 10 18:13:07 2009 +0100 @@ -252,10 +252,10 @@ self.actual_tables[-1].append(tsql) self.outer_tables = {} self.duplicate_switches = [] - self.attr_vars = {} self.aliases = {} self.restrictions = [] self._restr_stack = [] + self.ignore_varmap = False def add_restriction(self, restr): if restr: @@ -848,23 +848,23 @@ nothing to do here. """ contextrels = {} - attrvars = self._state.attr_vars for var in rhs_vars: - try: - contextrels[var.name] = attrvars[var.name] - except KeyError: - attrvars[var.name] = relation if var.name in self._varmap: # ensure table is added self._var_info(var.variable) + principal = var.variable.stinfo.get('principal') + if principal is not None and principal is not relation: + contextrels[var.name] = relation if not contextrels: - relation.children[1].accept(self, contextrels) return '' - # at least one variable is already in attr_vars, this means we have to - # generate unification expression + # we have to generate unification expression lhssql = self._inlined_var_sql(relation.children[0].variable, relation.r_type) - return '%s%s' % (lhssql, relation.children[1].accept(self, contextrels)) + try: + self._state.ignore_varmap = True + return '%s%s' % (lhssql, relation.children[1].accept(self)) + finally: + self._state.ignore_varmap = False def _visit_attribute_relation(self, rel): """generate SQL for an attribute relation""" @@ -932,7 +932,7 @@ return self.dbms_helper.fti_restriction_sql(alias, const.eval(self._args), jointo, not_) + restriction - def visit_comparison(self, cmp, contextrels=None): + def visit_comparison(self, cmp): """generate SQL for a comparison""" if len(cmp.children) == 2: # XXX occurs ? @@ -950,16 +950,15 @@ and rhs.eval(self._args) is None): if lhs is None: return ' IS NULL' - return '%s IS NULL' % lhs.accept(self, contextrels) + return '%s IS NULL' % lhs.accept(self) elif isinstance(rhs, Function) and rhs.name == 'IN': assert operator == '=' operator = ' ' if lhs is None: - return '%s%s'% (operator, rhs.accept(self, contextrels)) - return '%s%s%s'% (lhs.accept(self, contextrels), operator, - rhs.accept(self, contextrels)) + return '%s%s'% (operator, rhs.accept(self)) + return '%s%s%s'% (lhs.accept(self), operator, rhs.accept(self)) - def visit_mathexpression(self, mexpr, contextrels=None): + def visit_mathexpression(self, mexpr): """generate SQL for a mathematic expression""" lhs, rhs = mexpr.get_parts() # check for string concatenation @@ -969,17 +968,16 @@ operator = '||' except CoercionError: pass - return '(%s %s %s)'% (lhs.accept(self, contextrels), operator, - rhs.accept(self, contextrels)) + return '(%s %s %s)'% (lhs.accept(self), operator, rhs.accept(self)) - def visit_function(self, func, contextrels=None): + def visit_function(self, func): """generate SQL name for a function""" # function_description will check function is supported by the backend sqlname = self.dbms_helper.func_sqlname(func.name) - return '%s(%s)' % (sqlname, ', '.join(c.accept(self, contextrels) + return '%s(%s)' % (sqlname, ', '.join(c.accept(self) for c in func.children)) - def visit_constant(self, constant, contextrels=None): + def visit_constant(self, constant): """generate SQL name for a constant""" value = constant.value if constant.type is None: @@ -1004,12 +1002,12 @@ self._query_attrs[_id] = value return '%%(%s)s' % _id - def visit_variableref(self, variableref, contextrels=None): + def visit_variableref(self, variableref): """get the sql name for a variable reference""" # use accept, .variable may be a variable or a columnalias - return variableref.variable.accept(self, contextrels) + return variableref.variable.accept(self) - def visit_columnalias(self, colalias, contextrels=None): + def visit_columnalias(self, colalias): """get the sql name for a subquery column alias""" if colalias.name in self._varmap: sql = self._varmap[colalias.name] @@ -1020,20 +1018,21 @@ return sql return colalias._q_sql - def visit_variable(self, variable, contextrels=None): + def visit_variable(self, variable): """get the table name and sql string for a variable""" - if contextrels is None and variable.name in self._state.done: + #if contextrels is None and variable.name in self._state.done: + if variable.name in self._state.done: if self._in_wrapping_query: return 'T1.%s' % self._state.aliases[variable.name] return variable._q_sql self._state.done.add(variable.name) vtablename = None - if contextrels is None and variable.name in self._varmap: + if not self._state.ignore_varmap and variable.name in self._varmap: sql, vtablename = self._var_info(variable) elif variable.stinfo['attrvar']: # attribute variable (systematically used in rhs of final # relation(s)), get table name and sql from any rhs relation - sql = self._linked_var_sql(variable, contextrels) + sql = self._linked_var_sql(variable) elif variable._q_invariant: # since variable is invariant, we know we won't found final relation principal = variable.stinfo['principal'] @@ -1056,7 +1055,7 @@ self.dbms_helper.fti_uid_attr) elif principal in variable.stinfo['rhsrelations']: if self.schema.rschema(principal.r_type).inlined: - sql = self._linked_var_sql(variable, contextrels) + sql = self._linked_var_sql(variable) else: sql = '%s.eid_to' % self._relation_table(principal) else: @@ -1076,8 +1075,14 @@ # generate extra join try: if not var.stinfo['principal'] is relation: - # need a predicable result for tests - return '%s=%s' % tuple(sorted((sql, var.accept(self)))) + op = relation.operator() + if op == '=': + # need a predicable result for tests + args = sorted( (sql, var.accept(self)) ) + args.insert(1, op) + else: + args = (sql, op, var.accept(self)) + return '%s%s%s' % tuple(args) except KeyError: # no principal defined, relation is necessarily the principal and # so nothing to return here @@ -1123,14 +1128,13 @@ #self._state.done.add(var.name) return sql - def _linked_var_sql(self, variable, contextrels=None): - if contextrels is None: + def _linked_var_sql(self, variable): + if not self._state.ignore_varmap: try: return self._varmap[variable.name] except KeyError: pass - rel = (contextrels and contextrels.get(variable.name) or - variable.stinfo.get('principal') or + rel = (variable.stinfo.get('principal') or iter(variable.stinfo['rhsrelations']).next()) linkedvar = rel.children[0].variable if rel.r_type == 'eid': diff -r 37b376bb4088 -r 9fcf048e14b7 server/test/unittest_msplanner.py --- a/server/test/unittest_msplanner.py Tue Nov 10 18:11:24 2009 +0100 +++ b/server/test/unittest_msplanner.py Tue Nov 10 18:13:07 2009 +0100 @@ -706,7 +706,7 @@ self._test('Any V, MAX(VR) WHERE V is Card, V creation_date VR, ' '(V creation_date TODAY OR (V creation_date < TODAY AND NOT EXISTS(' 'X is Card, X creation_date < TODAY, X creation_date >= VR)))', - [('FetchStep', [('Any VR WHERE X creation_date < TODAY, X creation_date >= VR, X is Card', + [('FetchStep', [('Any VR WHERE X creation_date < TODAY, X creation_date VR, X is Card', [{'X': 'Card', 'VR': 'Datetime'}])], [self.cards, self.system], None, {'VR': 'table0.C0', 'X.creation_date': 'table0.C0'}, []), @@ -1349,7 +1349,7 @@ def test_attr_unification_neq_1(self): self._test('Any X,Y WHERE X is Bookmark, Y is Card, X creation_date D, Y creation_date > D', [('FetchStep', - [('Any Y,D WHERE Y creation_date > D, Y is Card', + [('Any Y,D WHERE Y creation_date D, Y is Card', [{'D': 'Datetime', 'Y': 'Card'}])], [self.cards,self.system], None, {'D': 'table0.C1', 'Y': 'table0.C0', 'Y.creation_date': 'table0.C1'}, []), diff -r 37b376bb4088 -r 9fcf048e14b7 server/test/unittest_multisources.py --- a/server/test/unittest_multisources.py Tue Nov 10 18:11:24 2009 +0100 +++ b/server/test/unittest_multisources.py Tue Nov 10 18:13:07 2009 +0100 @@ -184,7 +184,9 @@ def test_attr_unification_1(self): n1 = self.execute('INSERT Note X: X type "AFFREF"')[0][0] n2 = self.execute('INSERT Note X: X type "AFFREU"')[0][0] + self.set_debug('DBG_SQL|DBG_MS') rset = self.execute('Any X,Y WHERE X is Note, Y is Affaire, X type T, Y ref T') + self.set_debug(None) self.assertEquals(len(rset), 1, rset.rows) def test_attr_unification_2(self): diff -r 37b376bb4088 -r 9fcf048e14b7 server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Tue Nov 10 18:11:24 2009 +0100 +++ b/server/test/unittest_rql2sql.py Tue Nov 10 18:13:07 2009 +0100 @@ -164,6 +164,9 @@ '''SELECT _X.cw_eid FROM cw_Personne AS _X WHERE _X.cw_prenom=lulu AND NOT EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, in_group_relation AS rel_in_group1, cw_CWGroup AS _G WHERE rel_owned_by0.eid_from=_X.cw_eid AND rel_in_group1.eid_from=rel_owned_by0.eid_to AND rel_in_group1.eid_to=_G.cw_eid AND ((_G.cw_name=lulufanclub) OR (_G.cw_name=managers)))'''), + + + ] ADVANCED= [ @@ -867,10 +870,21 @@ ] VIRTUAL_VARS = [ - ("Personne P WHERE P travaille S, S tel T, S fax T, S is Societe;", + + ('Any X WHERE X is CWUser, X creation_date > D1, Y creation_date D1, Y login "SWEB09"', + '''SELECT _X.cw_eid +FROM cw_CWUser AS _X, cw_CWUser AS _Y +WHERE _X.cw_creation_date>_Y.cw_creation_date AND _Y.cw_login=SWEB09'''), + + ('Any X WHERE X is CWUser, Y creation_date D1, Y login "SWEB09", X creation_date > D1', + '''SELECT _X.cw_eid +FROM cw_CWUser AS _X, cw_CWUser AS _Y +WHERE _Y.cw_login=SWEB09 AND _X.cw_creation_date>_Y.cw_creation_date'''), + + ('Personne P WHERE P travaille S, S tel T, S fax T, S is Societe', '''SELECT rel_travaille0.eid_from FROM cw_Societe AS _S, travaille_relation AS rel_travaille0 -WHERE rel_travaille0.eid_to=_S.cw_eid AND _S.cw_fax=_S.cw_tel'''), +WHERE rel_travaille0.eid_to=_S.cw_eid AND _S.cw_tel=_S.cw_fax'''), ("Personne P where X eid 0, X creation_date D, P datenaiss < D, X is Affaire", '''SELECT _P.cw_eid