# HG changeset patch # User Sylvain Thénault # Date 1319544232 -7200 # Node ID f01c80513274828dbbecf572412500b88ad7ebb4 # Parent 1a1b23c37013108c0c3b3ea99838e6972c05af34 [rql] closes #2054468: support for HAVING in SET/DELETE queries diff -r 1a1b23c37013 -r f01c80513274 server/ssplanner.py --- a/server/ssplanner.py Tue Oct 25 13:51:27 2011 +0200 +++ b/server/ssplanner.py Tue Oct 25 14:03:52 2011 +0200 @@ -103,28 +103,26 @@ When select has nothing selected, search in origrqlst for restriction that should be considered. """ + if origrqlst.where is not None and not select.selection: + # no selection, append one randomly by searching for a relation which is + # neither a type restriction (is) nor an eid specification (not neged + # eid with constant node) + for rel in origrqlst.where.iget_nodes(Relation): + if rel.neged(strict=True) or not ( + rel.is_types_restriction() or + (rel.r_type == 'eid' + and isinstance(rel.get_variable_parts()[1], Constant))): + select.append_selected(rel.children[0].copy(select)) + break + else: + return if select.selection: if origrqlst.where is not None: select.set_where(origrqlst.where.copy(select)) + if getattr(origrqlst, 'having', None): + select.set_having([sq.copy(select) for sq in origrqlst.having]) return select - if origrqlst.where is None: - return - for rel in origrqlst.where.iget_nodes(Relation): - # search for a relation which is neither a type restriction (is) nor an - # eid specification (not neged eid with constant node - if rel.neged(strict=True) or not ( - rel.is_types_restriction() or - (rel.r_type == 'eid' - and isinstance(rel.get_variable_parts()[1], Constant))): - break - else: - return - select.set_where(origrqlst.where.copy(select)) - if not select.selection: - # no selection, append one randomly - select.append_selected(rel.children[0].copy(select)) - return select - + return None class SSPlanner(object): """SingleSourcePlanner: build execution plan for rql queries @@ -204,38 +202,40 @@ steps = [] for etype, var in rqlst.main_variables: step = DeleteEntitiesStep(plan) - step.children += self._sel_variable_step(plan, rqlst.solutions, - rqlst.where, etype, var) + step.children += self._sel_variable_step(plan, rqlst, etype, var) steps.append(step) for relation in rqlst.main_relations: step = DeleteRelationsStep(plan, relation.r_type) - step.children += self._sel_relation_steps(plan, rqlst.solutions, - rqlst.where, relation) + step.children += self._sel_relation_steps(plan, rqlst, relation) steps.append(step) return steps - def _sel_variable_step(self, plan, solutions, restriction, etype, varref): + def _sel_variable_step(self, plan, rqlst, etype, varref): """handle the selection of variables for a delete query""" select = Select() varref = varref.copy(select) select.defined_vars = {varref.name: varref.variable} select.append_selected(varref) - if restriction is not None: - select.set_where(restriction.copy(select)) + if rqlst.where is not None: + select.set_where(rqlst.where.copy(select)) + if getattr(rqlst, 'having', None): + select.set_having([x.copy(select) for x in rqlst.having]) if etype != 'Any': select.add_type_restriction(varref.variable, etype) - return self._select_plan(plan, select, solutions) + return self._select_plan(plan, select, rqlst.solutions) - def _sel_relation_steps(self, plan, solutions, restriction, relation): + def _sel_relation_steps(self, plan, rqlst, relation): """handle the selection of relations for a delete query""" select = Select() lhs, rhs = relation.get_variable_parts() select.append_selected(lhs.copy(select)) select.append_selected(rhs.copy(select)) select.set_where(relation.copy(select)) - if restriction is not None: - select.add_restriction(restriction.copy(select)) - return self._select_plan(plan, select, solutions) + if rqlst.where is not None: + select.set_where(rqlst.where.copy(select)) + if getattr(rqlst, 'having', None): + select.set_having([x.copy(select) for x in rqlst.having]) + return self._select_plan(plan, select, rqlst.solutions) def build_set_plan(self, plan, rqlst): """get an execution plan from an SET RQL query""" diff -r 1a1b23c37013 -r f01c80513274 server/test/unittest_querier.py --- a/server/test/unittest_querier.py Tue Oct 25 13:51:27 2011 +0200 +++ b/server/test/unittest_querier.py Tue Oct 25 14:03:52 2011 +0200 @@ -1227,6 +1227,26 @@ self.assertRaises(QueryError, self.execute, "SET X nom 'toto', X has_text 'tutu' WHERE X is Personne") self.assertRaises(QueryError, self.execute, "SET X login 'tutu', X eid %s" % cnx.user(self.session).eid) + # HAVING on write queries test ############################################# + + def test_update_having(self): + peid1 = self.execute("INSERT Personne Y: Y nom 'hop', Y tel 1")[0][0] + peid2 = self.execute("INSERT Personne Y: Y nom 'hop', Y tel 2")[0][0] + rset = self.execute("SET X tel 3 WHERE X tel TEL HAVING TEL&1=1") + self.assertEqual(tuplify(rset.rows), [(peid1, 3)]) + + def test_insert_having(self): + self.skip('unsupported yet') + self.execute("INSERT Personne Y: Y nom 'hop', Y tel 1")[0][0] + with self.debugged('DBG_SQL'): + self.assertFalse(self.execute("INSERT Personne Y: Y nom 'hop', Y tel 2 WHERE X tel XT HAVING XT&2=2")) + self.assertTrue(self.execute("INSERT Personne Y: Y nom 'hop', Y tel 2 WHERE X tel XT HAVING XT&1=1")) + + def test_delete_having(self): + self.execute("INSERT Personne Y: Y nom 'hop', Y tel 1")[0][0] + self.assertFalse(self.execute("DELETE Personne Y WHERE X tel XT HAVING XT&2=2")) + self.assertTrue(self.execute("DELETE Personne Y WHERE X tel XT HAVING XT&1=1")) + # upassword encryption tests ################################################# def test_insert_upassword(self):