# HG changeset patch # User Sylvain Thénault # Date 1316773389 -7200 # Node ID 3b51806da60bbd89522707d79fca3aaffa6ac82c # Parent fa7e463cae9de63e006b9e12cb7cb7095c8b06c4 [rqlrewrite] if inlined relation has to be moved to a subquery, take care of the object of the relation (closes #1945725) As the object of the relation is moved to the subquery, all variables/relations representing some of its attributes or inlined relations should be moved there as well. This avoid error such as "BadRQLQuery: variable AF should be selected by the subquery" on security insertion. diff -r fa7e463cae9d -r 3b51806da60b rqlrewrite.py --- a/rqlrewrite.py Fri Sep 23 10:42:16 2011 +0200 +++ b/rqlrewrite.py Fri Sep 23 12:23:09 2011 +0200 @@ -337,43 +337,58 @@ """introduce the given snippet in a subquery""" subselect = stmts.Select() snippetrqlst = n.Exists(transformedsnippet.copy(subselect)) + get_rschema = self.schema.rschema aliases = [] - rels_done = set() - for i, (selectvar, snippetvar) in enumerate(varmap): + done = set() + for i, (selectvar, _) in enumerate(varmap): + need_null_test = False subselectvar = subselect.get_variable(selectvar) subselect.append_selected(n.VariableRef(subselectvar)) aliases.append(selectvar) - vi = self.varinfos[i] - need_null_test = False - stinfo = vi['stinfo'] - for rel in stinfo['relations']: - if rel in rels_done: - continue - rels_done.add(rel) - rschema = self.schema.rschema(rel.r_type) - if rschema.final or (rschema.inlined and - not rel in stinfo['rhsrelations']): - rel.children[0].name = selectvar # XXX explain why - subselect.add_restriction(rel.copy(subselect)) - for vref in rel.children[1].iget_nodes(n.VariableRef): - if isinstance(vref.variable, n.ColumnAlias): - # XXX could probably be handled by generating the - # subquery into the detected subquery - raise BadSchemaDefinition( - "cant insert security because of usage two inlined " - "relations in this query. You should probably at " - "least uninline %s" % rel.r_type) - subselect.append_selected(vref.copy(subselect)) - aliases.append(vref.name) - self.select.remove_node(rel) - # when some inlined relation has to be copied in the - # subquery, we need to test that either value is NULL or - # that the snippet condition is satisfied - if rschema.inlined and rel.optional: - need_null_test = True + todo = [(selectvar, self.varinfos[i]['stinfo'])] + while todo: + varname, stinfo = todo.pop() + done.add(varname) + for rel in stinfo['relations'] - stinfo['rhsrelations']: + if rel in done: + continue + done.add(rel) + rschema = get_rschema(rel.r_type) + if rschema.final or rschema.inlined: + rel.children[0].name = varname # XXX explain why + subselect.add_restriction(rel.copy(subselect)) + for vref in rel.children[1].iget_nodes(n.VariableRef): + if isinstance(vref.variable, n.ColumnAlias): + # XXX could probably be handled by generating the + # subquery into the detected subquery + raise BadSchemaDefinition( + "cant insert security because of usage two inlined " + "relations in this query. You should probably at " + "least uninline %s" % rel.r_type) + subselect.append_selected(vref.copy(subselect)) + aliases.append(vref.name) + self.select.remove_node(rel) + # when some inlined relation has to be copied in the + # subquery and that relation is optional, we need to + # test that either value is NULL or that the snippet + # condition is satisfied + if varname == selectvar and rel.optional and rschema.inlined: + need_null_test = True + # also, if some attributes or inlined relation of the + # object variable are accessed, we need to get all those + # from the subquery as well + if vref.name not in done and rschema.inlined: + # we can use vref here define in above for loop + ostinfo = vref.variable.stinfo + for orel in ostinfo['relations'] - ostinfo['rhsrelations']: + orschema = get_rschema(orel.r_type) + if orschema.final or orschema.inlined: + todo.append( (vref.name, ostinfo) ) + break if need_null_test: snippetrqlst = n.Or( - n.make_relation(subselectvar, 'is', (None, None), n.Constant, + n.make_relation(subselect.get_variable(selectvar), 'is', + (None, None), n.Constant, operator='='), snippetrqlst) subselect.add_restriction(snippetrqlst) diff -r fa7e463cae9d -r 3b51806da60b test/data/rewrite/schema.py --- a/test/data/rewrite/schema.py Fri Sep 23 10:42:16 2011 +0200 +++ b/test/data/rewrite/schema.py Fri Sep 23 12:23:09 2011 +0200 @@ -63,3 +63,15 @@ object = 'Card' inlined = True cardinality = '?*' + +class inlined_note(RelationDefinition): + subject = 'Card' + object = 'Note' + inlined = True + cardinality = '?*' + +class inlined_affaire(RelationDefinition): + subject = 'Note' + object = 'Affaire' + inlined = True + cardinality = '?*' diff -r fa7e463cae9d -r 3b51806da60b test/unittest_rqlrewrite.py --- a/test/unittest_rqlrewrite.py Fri Sep 23 10:42:16 2011 +0200 +++ b/test/unittest_rqlrewrite.py Fri Sep 23 12:23:09 2011 +0200 @@ -236,6 +236,18 @@ ('A2', 'X'): (c2,), }, {}) + def test_optional_var_inlined_linked(self): + c1 = ('X require_permission P') + c2 = ('X inlined_card O, O require_permission P') + rqlst = parse('Any A,W WHERE A inlined_card C?, C inlined_note N, ' + 'N inlined_affaire W') + rewrite(rqlst, {('C', 'X'): (c1,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + 'Any A,W WHERE A inlined_card C?, A is Affaire ' + 'WITH C,N,W BEING (Any C,N,W WHERE C inlined_note N, ' + 'N inlined_affaire W, EXISTS(C require_permission B), ' + 'C is Card, N is Note, W is Affaire)') + def test_relation_optimization_1_lhs(self): # since Card in_state State as monovalued cardinality, the in_state # relation used in the rql expression can be ignored and S replaced by @@ -246,6 +258,7 @@ self.failUnlessEqual(rqlst.as_string(), "Any C WHERE C in_state STATE, C is Card, " "EXISTS(STATE name 'hop'), STATE is State") + def test_relation_optimization_1_rhs(self): snippet = ('TW subworkflow_exit X, TW name "hop"') rqlst = parse('WorkflowTransition C WHERE C subworkflow_exit EXIT')