76 # tree is not annotated yet, no scope set so add the restriction |
76 # tree is not annotated yet, no scope set so add the restriction |
77 # to the root |
77 # to the root |
78 rel = newroot.add_type_restriction(var, possibletypes) |
78 rel = newroot.add_type_restriction(var, possibletypes) |
79 stinfo['typerels'] = frozenset((rel,)) |
79 stinfo['typerels'] = frozenset((rel,)) |
80 stinfo['possibletypes'] = possibletypes |
80 stinfo['possibletypes'] = possibletypes |
81 |
81 |
82 class SSPlanner(object): |
82 class SSPlanner(object): |
83 """SingleSourcePlanner: build execution plan for rql queries |
83 """SingleSourcePlanner: build execution plan for rql queries |
84 |
84 |
85 optimized for single source repositories |
85 optimized for single source repositories |
86 """ |
86 """ |
87 |
87 |
88 def __init__(self, schema, rqlhelper): |
88 def __init__(self, schema, rqlhelper): |
89 self.schema = schema |
89 self.schema = schema |
90 self.rqlhelper = rqlhelper |
90 self.rqlhelper = rqlhelper |
91 |
91 |
92 def build_plan(self, plan): |
92 def build_plan(self, plan): |
93 """build an execution plan from a RQL query |
93 """build an execution plan from a RQL query |
94 |
94 |
95 do nothing here, dispatch according to the statement type |
95 do nothing here, dispatch according to the statement type |
96 """ |
96 """ |
97 build_plan = getattr(self, 'build_%s_plan' % plan.rqlst.TYPE) |
97 build_plan = getattr(self, 'build_%s_plan' % plan.rqlst.TYPE) |
98 for step in build_plan(plan, plan.rqlst): |
98 for step in build_plan(plan, plan.rqlst): |
99 plan.add_step(step) |
99 plan.add_step(step) |
100 |
100 |
101 def build_select_plan(self, plan, rqlst): |
101 def build_select_plan(self, plan, rqlst): |
102 """build execution plan for a SELECT RQL query. Suppose only one source |
102 """build execution plan for a SELECT RQL query. Suppose only one source |
103 is available and so avoid work need for query decomposition among sources |
103 is available and so avoid work need for query decomposition among sources |
104 |
104 |
105 the rqlst should not be tagged at this point. |
105 the rqlst should not be tagged at this point. |
106 """ |
106 """ |
107 plan.preprocess(rqlst) |
107 plan.preprocess(rqlst) |
108 return (OneFetchStep(plan, rqlst, plan.session.repo.sources),) |
108 return (OneFetchStep(plan, rqlst, plan.session.repo.sources),) |
109 |
109 |
110 def build_insert_plan(self, plan, rqlst): |
110 def build_insert_plan(self, plan, rqlst): |
111 """get an execution plan from an INSERT RQL query""" |
111 """get an execution plan from an INSERT RQL query""" |
112 # each variable in main variables is a new entity to insert |
112 # each variable in main variables is a new entity to insert |
113 to_build = {} |
113 to_build = {} |
114 session = plan.session |
114 session = plan.session |
121 # add necessary steps to add relations and update attributes |
121 # add necessary steps to add relations and update attributes |
122 step = InsertStep(plan) # insert each entity and its relations |
122 step = InsertStep(plan) # insert each entity and its relations |
123 step.children += self._compute_relation_steps(plan, rqlst.solutions, |
123 step.children += self._compute_relation_steps(plan, rqlst.solutions, |
124 rqlst.where, to_select) |
124 rqlst.where, to_select) |
125 return (step,) |
125 return (step,) |
126 |
126 |
127 def _compute_relation_steps(self, plan, solutions, restriction, to_select): |
127 def _compute_relation_steps(self, plan, solutions, restriction, to_select): |
128 """handle the selection of relations for an insert query""" |
128 """handle the selection of relations for an insert query""" |
129 for edef, rdefs in to_select.items(): |
129 for edef, rdefs in to_select.items(): |
130 # create a select rql st to fetch needed data |
130 # create a select rql st to fetch needed data |
131 select = Select() |
131 select = Select() |
144 if restriction is not None: |
144 if restriction is not None: |
145 select.set_where(restriction.copy(select)) |
145 select.set_where(restriction.copy(select)) |
146 step = RelationsStep(plan, edef, rdefs) |
146 step = RelationsStep(plan, edef, rdefs) |
147 step.children += self._select_plan(plan, select, solutions) |
147 step.children += self._select_plan(plan, select, solutions) |
148 yield step |
148 yield step |
149 |
149 |
150 def build_delete_plan(self, plan, rqlst): |
150 def build_delete_plan(self, plan, rqlst): |
151 """get an execution plan from a DELETE RQL query""" |
151 """get an execution plan from a DELETE RQL query""" |
152 # build a select query to fetch entities to delete |
152 # build a select query to fetch entities to delete |
153 steps = [] |
153 steps = [] |
154 for etype, var in rqlst.main_variables: |
154 for etype, var in rqlst.main_variables: |
172 if restriction is not None: |
172 if restriction is not None: |
173 select.set_where(restriction.copy(select)) |
173 select.set_where(restriction.copy(select)) |
174 if etype != 'Any': |
174 if etype != 'Any': |
175 select.add_type_restriction(varref.variable, etype) |
175 select.add_type_restriction(varref.variable, etype) |
176 return self._select_plan(plan, select, solutions) |
176 return self._select_plan(plan, select, solutions) |
177 |
177 |
178 def _sel_relation_steps(self, plan, solutions, restriction, relation): |
178 def _sel_relation_steps(self, plan, solutions, restriction, relation): |
179 """handle the selection of relations for a delete query""" |
179 """handle the selection of relations for a delete query""" |
180 select = Select() |
180 select = Select() |
181 lhs, rhs = relation.get_variable_parts() |
181 lhs, rhs = relation.get_variable_parts() |
182 select.append_selected(lhs.copy(select)) |
182 select.append_selected(lhs.copy(select)) |
183 select.append_selected(rhs.copy(select)) |
183 select.append_selected(rhs.copy(select)) |
184 select.set_where(relation.copy(select)) |
184 select.set_where(relation.copy(select)) |
185 if restriction is not None: |
185 if restriction is not None: |
186 select.add_restriction(restriction.copy(select)) |
186 select.add_restriction(restriction.copy(select)) |
187 return self._select_plan(plan, select, solutions) |
187 return self._select_plan(plan, select, solutions) |
188 |
188 |
189 def build_set_plan(self, plan, rqlst): |
189 def build_set_plan(self, plan, rqlst): |
190 """get an execution plan from an SET RQL query""" |
190 """get an execution plan from an SET RQL query""" |
191 select = Select() |
191 select = Select() |
192 # extract variables to add to the selection |
192 # extract variables to add to the selection |
193 selected_index = {} |
193 selected_index = {} |
220 step = UpdateStep(plan, attrrelations, relations, selected_index) |
220 step = UpdateStep(plan, attrrelations, relations, selected_index) |
221 step.children += self._select_plan(plan, select, rqlst.solutions) |
221 step.children += self._select_plan(plan, select, rqlst.solutions) |
222 return (step,) |
222 return (step,) |
223 |
223 |
224 # internal methods ######################################################## |
224 # internal methods ######################################################## |
225 |
225 |
226 def _select_plan(self, plan, select, solutions): |
226 def _select_plan(self, plan, select, solutions): |
227 union = Union() |
227 union = Union() |
228 union.append(select) |
228 union.append(select) |
229 select.clean_solutions(solutions) |
229 select.clean_solutions(solutions) |
230 add_types_restriction(self.schema, select) |
230 add_types_restriction(self.schema, select) |
231 self.rqlhelper.annotate(union) |
231 self.rqlhelper.annotate(union) |
232 return self.build_select_plan(plan, union) |
232 return self.build_select_plan(plan, union) |
233 |
233 |
234 |
234 |
235 # execution steps and helper functions ######################################## |
235 # execution steps and helper functions ######################################## |
258 limit = offset = None |
258 limit = offset = None |
259 def set_limit_offset(self, limit, offset): |
259 def set_limit_offset(self, limit, offset): |
260 self.limit = limit |
260 self.limit = limit |
261 self.offset = offset or None |
261 self.offset = offset or None |
262 |
262 |
263 |
263 |
264 class Step(object): |
264 class Step(object): |
265 """base abstract class for execution step""" |
265 """base abstract class for execution step""" |
266 def __init__(self, plan): |
266 def __init__(self, plan): |
267 self.plan = plan |
267 self.plan = plan |
268 self.children = [] |
268 self.children = [] |
269 |
269 |
270 def execute_child(self): |
270 def execute_child(self): |
271 assert len(self.children) == 1 |
271 assert len(self.children) == 1 |
272 return self.children[0].execute() |
272 return self.children[0].execute() |
273 |
273 |
274 def execute_children(self): |
274 def execute_children(self): |
275 for step in self.children: |
275 for step in self.children: |
276 step.execute() |
276 step.execute() |
277 |
277 |
278 def execute(self): |
278 def execute(self): |
279 """execute this step and store partial (eg this step) results""" |
279 """execute this step and store partial (eg this step) results""" |
280 raise NotImplementedError() |
280 raise NotImplementedError() |
281 |
281 |
282 def mytest_repr(self): |
282 def mytest_repr(self): |
283 """return a representation of this step suitable for test""" |
283 """return a representation of this step suitable for test""" |
284 return (self.__class__.__name__,) |
284 return (self.__class__.__name__,) |
285 |
285 |
286 def test_repr(self): |
286 def test_repr(self): |
287 """return a representation of this step suitable for test""" |
287 """return a representation of this step suitable for test""" |
288 return self.mytest_repr() + ( |
288 return self.mytest_repr() + ( |
289 [step.test_repr() for step in self.children],) |
289 [step.test_repr() for step in self.children],) |
290 |
290 |
291 |
291 |
292 class OneFetchStep(LimitOffsetMixIn, Step): |
292 class OneFetchStep(LimitOffsetMixIn, Step): |
293 """step consisting in fetching data from sources and directly returning |
293 """step consisting in fetching data from sources and directly returning |
294 results |
294 results |
295 """ |
295 """ |
296 def __init__(self, plan, union, sources, inputmap=None): |
296 def __init__(self, plan, union, sources, inputmap=None): |
303 def set_limit_offset(self, limit, offset): |
303 def set_limit_offset(self, limit, offset): |
304 LimitOffsetMixIn.set_limit_offset(self, limit, offset) |
304 LimitOffsetMixIn.set_limit_offset(self, limit, offset) |
305 for select in self.union.children: |
305 for select in self.union.children: |
306 select.limit = limit |
306 select.limit = limit |
307 select.offset = offset |
307 select.offset = offset |
308 |
308 |
309 def execute(self): |
309 def execute(self): |
310 """call .syntax_tree_search with the given syntax tree on each |
310 """call .syntax_tree_search with the given syntax tree on each |
311 source for each solution |
311 source for each solution |
312 """ |
312 """ |
313 self.execute_children() |
313 self.execute_children() |
373 """step consisting in adding attributes/relations to entity defs from a |
373 """step consisting in adding attributes/relations to entity defs from a |
374 previous FetchStep |
374 previous FetchStep |
375 |
375 |
376 relations values comes from the latest result, with one columns for |
376 relations values comes from the latest result, with one columns for |
377 each relation defined in self.r_defs |
377 each relation defined in self.r_defs |
378 |
378 |
379 for one entity definition, we'll construct N entity, where N is the |
379 for one entity definition, we'll construct N entity, where N is the |
380 number of the latest result |
380 number of the latest result |
381 """ |
381 """ |
382 |
382 |
383 FINAL = 0 |
383 FINAL = 0 |
384 RELATION = 1 |
384 RELATION = 1 |
385 REVERSE_RELATION = 2 |
385 REVERSE_RELATION = 2 |
386 |
386 |
387 def __init__(self, plan, e_def, r_defs): |
387 def __init__(self, plan, e_def, r_defs): |
388 Step.__init__(self, plan) |
388 Step.__init__(self, plan) |
389 # partial entity definition to expand |
389 # partial entity definition to expand |
390 self.e_def = e_def |
390 self.e_def = e_def |
391 # definition of relations to complete |
391 # definition of relations to complete |
392 self.r_defs = r_defs |
392 self.r_defs = r_defs |
393 |
393 |
394 def execute(self): |
394 def execute(self): |
395 """execute this step""" |
395 """execute this step""" |
396 base_e_def = self.e_def |
396 base_e_def = self.e_def |
397 result = [] |
397 result = [] |
398 for row in self.execute_child(): |
398 for row in self.execute_child(): |
413 self.plan.substitute_entity_def(base_e_def, result) |
413 self.plan.substitute_entity_def(base_e_def, result) |
414 |
414 |
415 |
415 |
416 class InsertStep(Step): |
416 class InsertStep(Step): |
417 """step consisting in inserting new entities / relations""" |
417 """step consisting in inserting new entities / relations""" |
418 |
418 |
419 def execute(self): |
419 def execute(self): |
420 """execute this step""" |
420 """execute this step""" |
421 for step in self.children: |
421 for step in self.children: |
422 assert isinstance(step, RelationsStep) |
422 assert isinstance(step, RelationsStep) |
423 step.plan = self.plan |
423 step.plan = self.plan |
442 pending = session.query_data('pendingeids', set(), setdefault=True) |
442 pending = session.query_data('pendingeids', set(), setdefault=True) |
443 actual = todelete - pending |
443 actual = todelete - pending |
444 pending |= actual |
444 pending |= actual |
445 for eid in actual: |
445 for eid in actual: |
446 delete(session, eid) |
446 delete(session, eid) |
447 |
447 |
448 |
448 |
449 class DeleteRelationsStep(Step): |
449 class DeleteRelationsStep(Step): |
450 """step consisting in deleting relations""" |
450 """step consisting in deleting relations""" |
451 |
451 |
452 def __init__(self, plan, rtype): |
452 def __init__(self, plan, rtype): |
453 Step.__init__(self, plan) |
453 Step.__init__(self, plan) |
454 self.rtype = rtype |
454 self.rtype = rtype |
455 |
455 |
456 def execute(self): |
456 def execute(self): |
457 """execute this step""" |
457 """execute this step""" |
458 session = self.plan.session |
458 session = self.plan.session |
459 delete = session.repo.glob_delete_relation |
459 delete = session.repo.glob_delete_relation |
460 for subj, obj in self.execute_child(): |
460 for subj, obj in self.execute_child(): |
461 delete(session, subj, self.rtype, obj) |
461 delete(session, subj, self.rtype, obj) |
462 |
462 |
463 |
463 |
464 class UpdateStep(Step): |
464 class UpdateStep(Step): |
465 """step consisting in updating entities / adding relations from relations |
465 """step consisting in updating entities / adding relations from relations |
466 definitions and from results fetched in previous step |
466 definitions and from results fetched in previous step |
467 """ |
467 """ |
468 |
468 |
469 def __init__(self, plan, attribute_relations, relations, selected_index): |
469 def __init__(self, plan, attribute_relations, relations, selected_index): |
470 Step.__init__(self, plan) |
470 Step.__init__(self, plan) |
471 self.attribute_relations = attribute_relations |
471 self.attribute_relations = attribute_relations |
472 self.relations = relations |
472 self.relations = relations |
473 self.selected_index = selected_index |
473 self.selected_index = selected_index |
474 |
474 |
475 def execute(self): |
475 def execute(self): |
476 """execute this step""" |
476 """execute this step""" |
477 plan = self.plan |
477 plan = self.plan |
478 session = self.plan.session |
478 session = self.plan.session |
479 repo = session.repo |
479 repo = session.repo |