1 # copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
1 # copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # |
3 # |
4 # This file is part of CubicWeb. |
4 # This file is part of CubicWeb. |
5 # |
5 # |
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
29 |
29 |
30 READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity')) |
30 READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity')) |
31 |
31 |
32 _CONSTANT = object() |
32 _CONSTANT = object() |
33 _FROM_SUBSTEP = object() |
33 _FROM_SUBSTEP = object() |
|
34 |
34 |
35 |
35 def _extract_const_attributes(plan, rqlst, to_build): |
36 def _extract_const_attributes(plan, rqlst, to_build): |
36 """add constant values to entity def, mark variables to be selected |
37 """add constant values to entity def, mark variables to be selected |
37 """ |
38 """ |
38 to_select = {} |
39 to_select = {} |
59 edef.edited_attribute(rtype, value) |
60 edef.edited_attribute(rtype, value) |
60 elif str(rhs) in to_build: |
61 elif str(rhs) in to_build: |
61 # create a relation between two newly created variables |
62 # create a relation between two newly created variables |
62 plan.add_relation_def((edef, rtype, to_build[rhs.name])) |
63 plan.add_relation_def((edef, rtype, to_build[rhs.name])) |
63 else: |
64 else: |
64 to_select.setdefault(edef, []).append( (rtype, rhs, 0) ) |
65 to_select.setdefault(edef, []).append((rtype, rhs, 0)) |
65 return to_select |
66 return to_select |
|
67 |
66 |
68 |
67 def _extract_eid_consts(plan, rqlst): |
69 def _extract_eid_consts(plan, rqlst): |
68 """return a dict mapping rqlst variable object to their eid if specified in |
70 """return a dict mapping rqlst variable object to their eid if specified in |
69 the syntax tree |
71 the syntax tree |
70 """ |
72 """ |
76 checkread = cnx.read_security |
78 checkread = cnx.read_security |
77 eschema = cnx.vreg.schema.eschema |
79 eschema = cnx.vreg.schema.eschema |
78 for rel in rqlst.where.get_nodes(Relation): |
80 for rel in rqlst.where.get_nodes(Relation): |
79 # only care for 'eid' relations ... |
81 # only care for 'eid' relations ... |
80 if (rel.r_type == 'eid' |
82 if (rel.r_type == 'eid' |
81 # ... that are not part of a NOT clause ... |
83 # ... that are not part of a NOT clause ... |
82 and not rel.neged(strict=True) |
84 and not rel.neged(strict=True) |
83 # ... and where eid is specified by '=' operator. |
85 # ... and where eid is specified by '=' operator. |
84 and rel.children[1].operator == '='): |
86 and rel.children[1].operator == '='): |
85 lhs, rhs = rel.get_variable_parts() |
87 lhs, rhs = rel.get_variable_parts() |
86 if isinstance(rhs, Constant): |
88 if isinstance(rhs, Constant): |
87 eid = int(rhs.eval(plan.args)) |
89 eid = int(rhs.eval(plan.args)) |
88 # check read permission here since it may not be done by |
90 # check read permission here since it may not be done by |
89 # the generated select substep if not emited (eg nothing |
91 # the generated select substep if not emited (eg nothing |
92 with cnx.security_enabled(read=False): |
94 with cnx.security_enabled(read=False): |
93 eschema(cnx.entity_type(eid)).check_perm( |
95 eschema(cnx.entity_type(eid)).check_perm( |
94 cnx, 'read', eid=eid) |
96 cnx, 'read', eid=eid) |
95 eidconsts[lhs.variable] = eid |
97 eidconsts[lhs.variable] = eid |
96 return eidconsts |
98 return eidconsts |
|
99 |
97 |
100 |
98 def _build_substep_query(select, origrqlst): |
101 def _build_substep_query(select, origrqlst): |
99 """Finalize substep select query that should be executed to get proper |
102 """Finalize substep select query that should be executed to get proper |
100 selection of stuff to insert/update. |
103 selection of stuff to insert/update. |
101 |
104 |
117 if getattr(origrqlst, 'having', None): |
120 if getattr(origrqlst, 'having', None): |
118 select.set_having([sq.copy(select) for sq in origrqlst.having]) |
121 select.set_having([sq.copy(select) for sq in origrqlst.having]) |
119 return select |
122 return select |
120 return None |
123 return None |
121 |
124 |
|
125 |
122 class SSPlanner(object): |
126 class SSPlanner(object): |
123 """SingleSourcePlanner: build execution plan for rql queries |
127 """SingleSourcePlanner: build execution plan for rql queries |
124 |
128 |
125 optimized for single source repositories |
129 optimized for single source repositories |
126 """ |
130 """ |
158 to_build[var.name] = EditedEntity(etype_class(etype)(cnx)) |
162 to_build[var.name] = EditedEntity(etype_class(etype)(cnx)) |
159 plan.add_entity_def(to_build[var.name]) |
163 plan.add_entity_def(to_build[var.name]) |
160 # add constant values to entity def, mark variables to be selected |
164 # add constant values to entity def, mark variables to be selected |
161 to_select = _extract_const_attributes(plan, rqlst, to_build) |
165 to_select = _extract_const_attributes(plan, rqlst, to_build) |
162 # add necessary steps to add relations and update attributes |
166 # add necessary steps to add relations and update attributes |
163 step = InsertStep(plan) # insert each entity and its relations |
167 step = InsertStep(plan) # insert each entity and its relations |
164 step.children += self._compute_relation_steps(plan, rqlst, to_select) |
168 step.children += self._compute_relation_steps(plan, rqlst, to_select) |
165 return (step,) |
169 return (step,) |
166 |
170 |
167 def _compute_relation_steps(self, plan, rqlst, to_select): |
171 def _compute_relation_steps(self, plan, rqlst, to_select): |
168 """handle the selection of relations for an insert query""" |
172 """handle the selection of relations for an insert query""" |
235 def build_set_plan(self, plan, rqlst): |
239 def build_set_plan(self, plan, rqlst): |
236 """get an execution plan from an SET RQL query""" |
240 """get an execution plan from an SET RQL query""" |
237 getrschema = self.schema.rschema |
241 getrschema = self.schema.rschema |
238 select = Select() # potential substep query |
242 select = Select() # potential substep query |
239 selectedidx = {} # local state |
243 selectedidx = {} # local state |
240 attributes = set() # edited attributes |
|
241 updatedefs = [] # definition of update attributes/relations |
244 updatedefs = [] # definition of update attributes/relations |
242 selidx = residx = 0 # substep selection / resulting rset indexes |
245 selidx = residx = 0 # substep selection / resulting rset indexes |
243 # search for eid const in the WHERE clause |
246 # search for eid const in the WHERE clause |
244 eidconsts = _extract_eid_consts(plan, rqlst) |
247 eidconsts = _extract_eid_consts(plan, rqlst) |
245 # build `updatedefs` describing things to update and add necessary |
248 # build `updatedefs` describing things to update and add necessary |
246 # variables to the substep selection |
249 # variables to the substep selection |
247 for i, relation in enumerate(rqlst.main_relations): |
250 for i, relation in enumerate(rqlst.main_relations): |
248 if relation.r_type in VIRTUAL_RTYPES: |
251 if relation.r_type in VIRTUAL_RTYPES: |
249 raise QueryError('can not assign to %r relation' |
252 raise QueryError('can not assign to %r relation' |
250 % relation.r_type) |
253 % relation.r_type) |
251 lhs, rhs = relation.get_variable_parts() |
254 lhs, rhs = relation.get_variable_parts() |
252 lhskey = lhs.as_string() |
255 lhskey = lhs.as_string() |
253 if not lhskey in selectedidx: |
256 if lhskey not in selectedidx: |
254 if lhs.variable in eidconsts: |
257 if lhs.variable in eidconsts: |
255 eid = eidconsts[lhs.variable] |
258 eid = eidconsts[lhs.variable] |
256 lhsinfo = (_CONSTANT, eid, residx) |
259 lhsinfo = (_CONSTANT, eid, residx) |
257 else: |
260 else: |
258 select.append_selected(lhs.copy(select)) |
261 select.append_selected(lhs.copy(select)) |
261 residx += 1 |
264 residx += 1 |
262 selectedidx[lhskey] = lhsinfo |
265 selectedidx[lhskey] = lhsinfo |
263 else: |
266 else: |
264 lhsinfo = selectedidx[lhskey][:-1] + (None,) |
267 lhsinfo = selectedidx[lhskey][:-1] + (None,) |
265 rhskey = rhs.as_string() |
268 rhskey = rhs.as_string() |
266 if not rhskey in selectedidx: |
269 if rhskey not in selectedidx: |
267 if isinstance(rhs, Constant): |
270 if isinstance(rhs, Constant): |
268 rhsinfo = (_CONSTANT, rhs.eval(plan.args), residx) |
271 rhsinfo = (_CONSTANT, rhs.eval(plan.args), residx) |
269 elif getattr(rhs, 'variable', None) in eidconsts: |
272 elif getattr(rhs, 'variable', None) in eidconsts: |
270 eid = eidconsts[rhs.variable] |
273 eid = eidconsts[rhs.variable] |
271 rhsinfo = (_CONSTANT, eid, residx) |
274 rhsinfo = (_CONSTANT, eid, residx) |
276 residx += 1 |
279 residx += 1 |
277 selectedidx[rhskey] = rhsinfo |
280 selectedidx[rhskey] = rhsinfo |
278 else: |
281 else: |
279 rhsinfo = selectedidx[rhskey][:-1] + (None,) |
282 rhsinfo = selectedidx[rhskey][:-1] + (None,) |
280 rschema = getrschema(relation.r_type) |
283 rschema = getrschema(relation.r_type) |
281 updatedefs.append( (lhsinfo, rhsinfo, rschema) ) |
284 updatedefs.append((lhsinfo, rhsinfo, rschema)) |
282 # the update step |
285 # the update step |
283 step = UpdateStep(plan, updatedefs) |
286 step = UpdateStep(plan, updatedefs) |
284 # when necessary add substep to fetch yet unknown values |
287 # when necessary add substep to fetch yet unknown values |
285 select = _build_substep_query(select, rqlst) |
288 select = _build_substep_query(select, rqlst) |
286 if select is not None: |
289 if select is not None: |
357 else: |
360 else: |
358 cachekey = union.as_string() |
361 cachekey = union.as_string() |
359 # get results for query |
362 # get results for query |
360 source = cnx.repo.system_source |
363 source = cnx.repo.system_source |
361 result = source.syntax_tree_search(cnx, union, args, cachekey) |
364 result = source.syntax_tree_search(cnx, union, args, cachekey) |
362 #print 'ONEFETCH RESULT %s' % (result) |
|
363 return result |
365 return result |
364 |
366 |
365 def mytest_repr(self): |
367 def mytest_repr(self): |
366 """return a representation of this step suitable for test""" |
368 """return a representation of this step suitable for test""" |
367 return (self.__class__.__name__, |
369 return (self.__class__.__name__, |
411 value = row[index] |
413 value = row[index] |
412 index += 1 |
414 index += 1 |
413 if rorder == InsertRelationsStep.FINAL: |
415 if rorder == InsertRelationsStep.FINAL: |
414 edef.edited_attribute(rtype, value) |
416 edef.edited_attribute(rtype, value) |
415 elif rorder == InsertRelationsStep.RELATION: |
417 elif rorder == InsertRelationsStep.RELATION: |
416 self.plan.add_relation_def( (edef, rtype, value) ) |
418 self.plan.add_relation_def((edef, rtype, value)) |
417 edef.querier_pending_relations[(rtype, 'subject')] = value |
419 edef.querier_pending_relations[(rtype, 'subject')] = value |
418 else: |
420 else: |
419 self.plan.add_relation_def( (value, rtype, edef) ) |
421 self.plan.add_relation_def((value, rtype, edef)) |
420 edef.querier_pending_relations[(rtype, 'object')] = value |
422 edef.querier_pending_relations[(rtype, 'object')] = value |
421 edefs.append(edef) |
423 edefs.append(edef) |
422 self.plan.substitute_entity_def(base_edef, edefs) |
424 self.plan.substitute_entity_def(base_edef, edefs) |
423 return result |
425 return result |
424 |
426 |
449 if results: |
451 if results: |
450 todelete = frozenset(int(eid) for eid, in results) |
452 todelete = frozenset(int(eid) for eid, in results) |
451 cnx = self.plan.cnx |
453 cnx = self.plan.cnx |
452 cnx.repo.glob_delete_entities(cnx, todelete) |
454 cnx.repo.glob_delete_entities(cnx, todelete) |
453 return results |
455 return results |
|
456 |
454 |
457 |
455 class DeleteRelationsStep(Step): |
458 class DeleteRelationsStep(Step): |
456 """step consisting in deleting relations""" |
459 """step consisting in deleting relations""" |
457 |
460 |
458 def __init__(self, plan, rtype): |
461 def __init__(self, plan, rtype): |
511 repo.glob_add_relations(cnx, relations) |
514 repo.glob_add_relations(cnx, relations) |
512 for eid, edited in edefs.items(): |
515 for eid, edited in edefs.items(): |
513 repo.glob_update_entity(cnx, edited) |
516 repo.glob_update_entity(cnx, edited) |
514 return result |
517 return result |
515 |
518 |
|
519 |
516 def _handle_relterm(info, row, newrow): |
520 def _handle_relterm(info, row, newrow): |
517 if info[0] is _CONSTANT: |
521 if info[0] is _CONSTANT: |
518 val = info[1] |
522 val = info[1] |
519 else: # _FROM_SUBSTEP |
523 else: # _FROM_SUBSTEP |
520 val = row[info[1]] |
524 val = row[info[1]] |
521 if info[-1] is not None: |
525 if info[-1] is not None: |
522 newrow.append(val) |
526 newrow.append(val) |
523 return val |
527 return val |