[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.
--- 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)
--- 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 = '?*'
--- 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')