8 __docformat__ = "restructuredtext en" |
8 __docformat__ = "restructuredtext en" |
9 |
9 |
10 from copy import copy |
10 from copy import copy |
11 |
11 |
12 from rql.stmts import Union, Select |
12 from rql.stmts import Union, Select |
13 from rql.nodes import Constant |
13 from rql.nodes import Constant, Relation |
14 |
14 |
15 from cubicweb import QueryError, typed_eid |
15 from cubicweb import QueryError, typed_eid |
16 from cubicweb.schema import VIRTUAL_RTYPES |
16 from cubicweb.schema import VIRTUAL_RTYPES |
17 from cubicweb.rqlrewrite import add_types_restriction |
17 from cubicweb.rqlrewrite import add_types_restriction |
|
18 |
|
19 READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity')) |
|
20 |
|
21 _CONSTANT = object() |
|
22 _FROM_SUBSTEP = object() |
|
23 |
|
24 def _extract_const_attributes(plan, rqlst, to_build): |
|
25 """add constant values to entity def, mark variables to be selected |
|
26 """ |
|
27 to_select = {} |
|
28 for relation in rqlst.main_relations: |
|
29 lhs, rhs = relation.get_variable_parts() |
|
30 rtype = relation.r_type |
|
31 if rtype in READ_ONLY_RTYPES: |
|
32 raise QueryError("can't assign to %s" % rtype) |
|
33 try: |
|
34 edef = to_build[str(lhs)] |
|
35 except KeyError: |
|
36 # lhs var is not to build, should be selected and added as an |
|
37 # object relation |
|
38 edef = to_build[str(rhs)] |
|
39 to_select.setdefault(edef, []).append((rtype, lhs, 1)) |
|
40 else: |
|
41 if isinstance(rhs, Constant) and not rhs.uid: |
|
42 # add constant values to entity def |
|
43 value = rhs.eval(plan.args) |
|
44 eschema = edef.e_schema |
|
45 attrtype = eschema.subjrels[rtype].objects(eschema)[0] |
|
46 if attrtype == 'Password' and isinstance(value, unicode): |
|
47 value = value.encode('UTF8') |
|
48 edef[rtype] = value |
|
49 elif to_build.has_key(str(rhs)): |
|
50 # create a relation between two newly created variables |
|
51 plan.add_relation_def((edef, rtype, to_build[rhs.name])) |
|
52 else: |
|
53 to_select.setdefault(edef, []).append( (rtype, rhs, 0) ) |
|
54 return to_select |
|
55 |
|
56 def _extract_eid_consts(plan, rqlst): |
|
57 """return a dict mapping rqlst variable object to their eid if specified in |
|
58 the syntax tree |
|
59 """ |
|
60 session = plan.session |
|
61 eschema = session.vreg.schema.eschema |
|
62 if rqlst.where is None: |
|
63 return {} |
|
64 eidconsts = {} |
|
65 neweids = session.transaction_data.get('neweids', ()) |
|
66 for rel in rqlst.where.get_nodes(Relation): |
|
67 if rel.r_type == 'eid' and not rel.neged(strict=True): |
|
68 lhs, rhs = rel.get_variable_parts() |
|
69 if isinstance(rhs, Constant): |
|
70 eid = typed_eid(rhs.eval(plan.args)) |
|
71 # check read permission here since it may not be done by |
|
72 # the generated select substep if not emited (eg nothing |
|
73 # to be selected) |
|
74 if eid not in neweids: |
|
75 eschema(session.describe(eid)[0]).check_perm(session, 'read') |
|
76 eidconsts[lhs.variable] = eid |
|
77 return eidconsts |
18 |
78 |
19 |
79 |
20 class SSPlanner(object): |
80 class SSPlanner(object): |
21 """SingleSourcePlanner: build execution plan for rql queries |
81 """SingleSourcePlanner: build execution plan for rql queries |
22 |
82 |
54 for etype, var in rqlst.main_variables: |
114 for etype, var in rqlst.main_variables: |
55 # need to do this since entity class is shared w. web client code ! |
115 # need to do this since entity class is shared w. web client code ! |
56 to_build[var.name] = etype_class(etype)(session) |
116 to_build[var.name] = etype_class(etype)(session) |
57 plan.add_entity_def(to_build[var.name]) |
117 plan.add_entity_def(to_build[var.name]) |
58 # add constant values to entity def, mark variables to be selected |
118 # add constant values to entity def, mark variables to be selected |
59 to_select = plan.relation_definitions(rqlst, to_build) |
119 to_select = _extract_const_attributes(plan, rqlst, to_build) |
60 # add necessary steps to add relations and update attributes |
120 # add necessary steps to add relations and update attributes |
61 step = InsertStep(plan) # insert each entity and its relations |
121 step = InsertStep(plan) # insert each entity and its relations |
62 step.children += self._compute_relation_steps(plan, rqlst.solutions, |
122 step.children += self._compute_relation_steps(plan, rqlst, to_select) |
63 rqlst.where, to_select) |
|
64 return (step,) |
123 return (step,) |
65 |
124 |
66 def _compute_relation_steps(self, plan, solutions, restriction, to_select): |
125 def _compute_relation_steps(self, plan, rqlst, to_select): |
67 """handle the selection of relations for an insert query""" |
126 """handle the selection of relations for an insert query""" |
|
127 eidconsts = _extract_eid_consts(plan, rqlst) |
68 for edef, rdefs in to_select.items(): |
128 for edef, rdefs in to_select.items(): |
69 # create a select rql st to fetch needed data |
129 # create a select rql st to fetch needed data |
70 select = Select() |
130 select = Select() |
71 eschema = edef.e_schema |
131 eschema = edef.e_schema |
72 for i in range(len(rdefs)): |
132 for i, (rtype, term, reverse) in enumerate(rdefs): |
73 rtype, term, reverse = rdefs[i] |
133 if getattr(term, 'variable', None) in eidconsts: |
74 select.append_selected(term.copy(select)) |
134 value = eidconsts[term.variable] |
|
135 else: |
|
136 select.append_selected(term.copy(select)) |
|
137 value = _FROM_SUBSTEP |
75 if reverse: |
138 if reverse: |
76 rdefs[i] = rtype, RelationsStep.REVERSE_RELATION |
139 rdefs[i] = (rtype, InsertRelationsStep.REVERSE_RELATION, value) |
77 else: |
140 else: |
78 rschema = eschema.subjrels[rtype] |
141 rschema = eschema.subjrels[rtype] |
79 if rschema.final or rschema.inlined: |
142 if rschema.final or rschema.inlined: |
80 rdefs[i] = rtype, RelationsStep.FINAL |
143 rdefs[i] = (rtype, InsertRelationsStep.FINAL, value) |
81 else: |
144 else: |
82 rdefs[i] = rtype, RelationsStep.RELATION |
145 rdefs[i] = (rtype, InsertRelationsStep.RELATION, value) |
83 if restriction is not None: |
146 step = InsertRelationsStep(plan, edef, rdefs) |
84 select.set_where(restriction.copy(select)) |
147 if select.selection: |
85 step = RelationsStep(plan, edef, rdefs) |
148 if rqlst.where is not None: |
86 step.children += self._select_plan(plan, select, solutions) |
149 select.set_where(rqlst.where.copy(select)) |
|
150 step.children += self._select_plan(plan, select, rqlst.solutions) |
87 yield step |
151 yield step |
88 |
152 |
89 def build_delete_plan(self, plan, rqlst): |
153 def build_delete_plan(self, plan, rqlst): |
90 """get an execution plan from a DELETE RQL query""" |
154 """get an execution plan from a DELETE RQL query""" |
91 # build a select query to fetch entities to delete |
155 # build a select query to fetch entities to delete |
125 select.add_restriction(restriction.copy(select)) |
189 select.add_restriction(restriction.copy(select)) |
126 return self._select_plan(plan, select, solutions) |
190 return self._select_plan(plan, select, solutions) |
127 |
191 |
128 def build_set_plan(self, plan, rqlst): |
192 def build_set_plan(self, plan, rqlst): |
129 """get an execution plan from an SET RQL query""" |
193 """get an execution plan from an SET RQL query""" |
130 select = Select() |
|
131 # extract variables to add to the selection |
|
132 selected_index = {} |
|
133 index = 0 |
|
134 relations, attrrelations = [], [] |
|
135 getrschema = self.schema.rschema |
194 getrschema = self.schema.rschema |
136 for relation in rqlst.main_relations: |
195 select = Select() # potential substep query |
|
196 selectedidx = {} # local state |
|
197 attributes = set() # edited attributes |
|
198 updatedefs = [] # definition of update attributes/relations |
|
199 selidx = residx = 0 # substep selection / resulting rset indexes |
|
200 # search for eid const in the WHERE clause |
|
201 eidconsts = _extract_eid_consts(plan, rqlst) |
|
202 # build `updatedefs` describing things to update and add necessary |
|
203 # variables to the substep selection |
|
204 for i, relation in enumerate(rqlst.main_relations): |
137 if relation.r_type in VIRTUAL_RTYPES: |
205 if relation.r_type in VIRTUAL_RTYPES: |
138 raise QueryError('can not assign to %r relation' |
206 raise QueryError('can not assign to %r relation' |
139 % relation.r_type) |
207 % relation.r_type) |
140 lhs, rhs = relation.get_variable_parts() |
208 lhs, rhs = relation.get_variable_parts() |
141 if not lhs.as_string('utf-8') in selected_index: |
209 lhskey = lhs.as_string('utf-8') |
142 select.append_selected(lhs.copy(select)) |
210 if not lhskey in selectedidx: |
143 selected_index[lhs.as_string('utf-8')] = index |
211 if lhs.variable in eidconsts: |
144 index += 1 |
212 eid = eidconsts[lhs.variable] |
145 if not rhs.as_string('utf-8') in selected_index: |
213 lhsinfo = (_CONSTANT, eid, residx) |
146 select.append_selected(rhs.copy(select)) |
214 else: |
147 selected_index[rhs.as_string('utf-8')] = index |
215 select.append_selected(lhs.copy(select)) |
148 index += 1 |
216 lhsinfo = (_FROM_SUBSTEP, selidx, residx) |
|
217 selidx += 1 |
|
218 residx += 1 |
|
219 selectedidx[lhskey] = lhsinfo |
|
220 else: |
|
221 lhsinfo = selectedidx[lhskey][:-1] + (None,) |
|
222 rhskey = rhs.as_string('utf-8') |
|
223 if not rhskey in selectedidx: |
|
224 if isinstance(rhs, Constant): |
|
225 rhsinfo = (_CONSTANT, rhs.eval(plan.args), residx) |
|
226 elif getattr(rhs, 'variable', None) in eidconsts: |
|
227 eid = eidconsts[rhs.variable] |
|
228 rhsinfo = (_CONSTANT, eid, residx) |
|
229 else: |
|
230 select.append_selected(rhs.copy(select)) |
|
231 rhsinfo = (_FROM_SUBSTEP, selidx, residx) |
|
232 selidx += 1 |
|
233 residx += 1 |
|
234 selectedidx[rhskey] = rhsinfo |
|
235 else: |
|
236 rhsinfo = selectedidx[rhskey][:-1] + (None,) |
149 rschema = getrschema(relation.r_type) |
237 rschema = getrschema(relation.r_type) |
|
238 updatedefs.append( (lhsinfo, rhsinfo, rschema) ) |
150 if rschema.final or rschema.inlined: |
239 if rschema.final or rschema.inlined: |
151 attrrelations.append(relation) |
240 attributes.add(relation.r_type) |
152 else: |
241 # the update step |
153 relations.append(relation) |
242 step = UpdateStep(plan, updatedefs, attributes) |
154 # add step necessary to fetch all selected variables values |
243 # when necessary add substep to fetch yet unknown values |
155 if rqlst.where is not None: |
244 if select.selection: |
156 select.set_where(rqlst.where.copy(select)) |
245 if rqlst.where is not None: |
157 # set distinct to avoid potential duplicate key error |
246 select.set_where(rqlst.where.copy(select)) |
158 select.distinct = True |
247 # set distinct to avoid potential duplicate key error |
159 step = UpdateStep(plan, attrrelations, relations, selected_index) |
248 select.distinct = True |
160 step.children += self._select_plan(plan, select, rqlst.solutions) |
249 step.children += self._select_plan(plan, select, rqlst.solutions) |
161 return (step,) |
250 return (step,) |
162 |
251 |
163 # internal methods ######################################################## |
252 # internal methods ######################################################## |
164 |
253 |
165 def _select_plan(self, plan, select, solutions): |
254 def _select_plan(self, plan, select, solutions): |
332 |
421 |
333 def execute(self): |
422 def execute(self): |
334 """execute this step""" |
423 """execute this step""" |
335 base_edef = self.edef |
424 base_edef = self.edef |
336 edefs = [] |
425 edefs = [] |
337 result = self.execute_child() |
426 if self.children: |
|
427 result = self.execute_child() |
|
428 else: |
|
429 result = [[]] |
338 for row in result: |
430 for row in result: |
339 # get a new entity definition for this row |
431 # get a new entity definition for this row |
340 edef = copy(base_edef) |
432 edef = copy(base_edef) |
341 # complete this entity def using row values |
433 # complete this entity def using row values |
342 for i in range(len(self.rdefs)): |
434 index = 0 |
343 rtype, rorder = self.rdefs[i] |
435 for rtype, rorder, value in self.rdefs: |
344 if rorder == RelationsStep.FINAL: |
436 if value is _FROM_SUBSTEP: |
345 edef[rtype] = row[i] |
437 value = row[index] |
346 elif rorder == RelationsStep.RELATION: |
438 index += 1 |
347 self.plan.add_relation_def( (edef, rtype, row[i]) ) |
439 if rorder == InsertRelationsStep.FINAL: |
348 edef.querier_pending_relations[(rtype, 'subject')] = row[i] |
440 edef[rtype] = value |
|
441 elif rorder == InsertRelationsStep.RELATION: |
|
442 self.plan.add_relation_def( (edef, rtype, value) ) |
|
443 edef.querier_pending_relations[(rtype, 'subject')] = value |
349 else: |
444 else: |
350 self.plan.add_relation_def( (row[i], rtype, edef) ) |
445 self.plan.add_relation_def( (value, rtype, edef) ) |
351 edef.querier_pending_relations[(rtype, 'object')] = row[i] |
446 edef.querier_pending_relations[(rtype, 'object')] = value |
352 edefs.append(edef) |
447 edefs.append(edef) |
353 self.plan.substitute_entity_def(base_edef, edefs) |
448 self.plan.substitute_entity_def(base_edef, edefs) |
354 return result |
449 return result |
355 |
450 |
356 |
|
357 class InsertStep(Step): |
451 class InsertStep(Step): |
358 """step consisting in inserting new entities / relations""" |
452 """step consisting in inserting new entities / relations""" |
359 |
453 |
360 def execute(self): |
454 def execute(self): |
361 """execute this step""" |
455 """execute this step""" |
362 for step in self.children: |
456 for step in self.children: |
363 assert isinstance(step, RelationsStep) |
457 assert isinstance(step, InsertRelationsStep) |
364 step.plan = self.plan |
458 step.plan = self.plan |
365 step.execute() |
459 step.execute() |
366 # insert entities first |
460 # insert entities first |
367 result = self.plan.insert_entity_defs() |
461 result = self.plan.insert_entity_defs() |
368 # then relation |
462 # then relation |
406 class UpdateStep(Step): |
500 class UpdateStep(Step): |
407 """step consisting in updating entities / adding relations from relations |
501 """step consisting in updating entities / adding relations from relations |
408 definitions and from results fetched in previous step |
502 definitions and from results fetched in previous step |
409 """ |
503 """ |
410 |
504 |
411 def __init__(self, plan, attribute_relations, relations, selected_index): |
505 def __init__(self, plan, updatedefs, attributes): |
412 Step.__init__(self, plan) |
506 Step.__init__(self, plan) |
413 self.attribute_relations = attribute_relations |
507 self.updatedefs = updatedefs |
414 self.relations = relations |
508 self.attributes = attributes |
415 self.selected_index = selected_index |
|
416 |
509 |
417 def execute(self): |
510 def execute(self): |
418 """execute this step""" |
511 """execute this step""" |
419 plan = self.plan |
|
420 session = self.plan.session |
512 session = self.plan.session |
421 repo = session.repo |
513 repo = session.repo |
422 edefs = {} |
514 edefs = {} |
423 # insert relations |
515 # insert relations |
424 attributes = set([relation.r_type for relation in self.attribute_relations]) |
516 if self.children: |
425 result = self.execute_child() |
517 result = self.execute_child() |
426 for row in result: |
518 else: |
427 for relation in self.attribute_relations: |
519 result = [[]] |
428 lhs, rhs = relation.get_variable_parts() |
520 for i, row in enumerate(result): |
429 eid = typed_eid(row[self.selected_index[str(lhs)]]) |
521 newrow = [] |
430 try: |
522 for (lhsinfo, rhsinfo, rschema) in self.updatedefs: |
431 edef = edefs[eid] |
523 lhsval = _handle_relterm(lhsinfo, row, newrow) |
432 except KeyError: |
524 rhsval = _handle_relterm(rhsinfo, row, newrow) |
433 edefs[eid] = edef = session.entity_from_eid(eid) |
525 if rschema.final or rschema.inlined: |
434 if isinstance(rhs, Constant): |
526 eid = typed_eid(lhsval) |
435 # add constant values to entity def |
527 try: |
436 value = rhs.eval(plan.args) |
528 edef = edefs[eid] |
437 edef[relation.r_type] = value |
529 except KeyError: |
|
530 edefs[eid] = edef = session.entity_from_eid(eid) |
|
531 edef[str(rschema)] = rhsval |
438 else: |
532 else: |
439 edef[relation.r_type] = row[self.selected_index[str(rhs)]] |
533 repo.glob_add_relation(session, lhsval, str(rschema), rhsval) |
440 for relation in self.relations: |
534 result[i] = newrow |
441 subj = row[self.selected_index[str(relation.children[0])]] |
|
442 obj = row[self.selected_index[str(relation.children[1])]] |
|
443 repo.glob_add_relation(session, subj, relation.r_type, obj) |
|
444 # update entities |
535 # update entities |
445 for eid, edef in edefs.iteritems(): |
536 for eid, edef in edefs.iteritems(): |
446 repo.glob_update_entity(session, edef, attributes) |
537 repo.glob_update_entity(session, edef, self.attributes) |
447 return result |
538 return result |
|
539 |
|
540 def _handle_relterm(info, row, newrow): |
|
541 if info[0] is _CONSTANT: |
|
542 val = info[1] |
|
543 else: # _FROM_SUBSTEP |
|
544 val = row[info[1]] |
|
545 if info[-1] is not None: |
|
546 newrow.append(val) |
|
547 return val |