62 for solution in rqlst.solutions: |
62 for solution in rqlst.solutions: |
63 for var, etype in solution.iteritems(): |
63 for var, etype in solution.iteritems(): |
64 if etype == 'Password': |
64 if etype == 'Password': |
65 raise Unauthorized('Password selection is not allowed (%s)' % var) |
65 raise Unauthorized('Password selection is not allowed (%s)' % var) |
66 |
66 |
67 def term_etype(session, term, solution, args): |
67 def term_etype(cnx, term, solution, args): |
68 """return the entity type for the given term (a VariableRef or a Constant |
68 """return the entity type for the given term (a VariableRef or a Constant |
69 node) |
69 node) |
70 """ |
70 """ |
71 try: |
71 try: |
72 return solution[term.name] |
72 return solution[term.name] |
73 except AttributeError: |
73 except AttributeError: |
74 return session.entity_metas(term.eval(args))['type'] |
74 return cnx.entity_metas(term.eval(args))['type'] |
75 |
75 |
76 def check_read_access(session, rqlst, solution, args): |
76 def check_read_access(cnx, rqlst, solution, args): |
77 """Check that the given user has credentials to access data read by the |
77 """Check that the given user has credentials to access data read by the |
78 query and return a dict defining necessary "local checks" (i.e. rql |
78 query and return a dict defining necessary "local checks" (i.e. rql |
79 expression in read permission defined in the schema) where no group grants |
79 expression in read permission defined in the schema) where no group grants |
80 him the permission. |
80 him the permission. |
81 |
81 |
84 """ |
84 """ |
85 # use `term_etype` since we've to deal with rewritten constants here, |
85 # use `term_etype` since we've to deal with rewritten constants here, |
86 # when used as an external source by another repository. |
86 # when used as an external source by another repository. |
87 # XXX what about local read security w/ those rewritten constants... |
87 # XXX what about local read security w/ those rewritten constants... |
88 DBG = (server.DEBUG & server.DBG_SEC) and 'read' in server._SECURITY_CAPS |
88 DBG = (server.DEBUG & server.DBG_SEC) and 'read' in server._SECURITY_CAPS |
89 schema = session.repo.schema |
89 schema = cnx.repo.schema |
90 if rqlst.where is not None: |
90 if rqlst.where is not None: |
91 for rel in rqlst.where.iget_nodes(Relation): |
91 for rel in rqlst.where.iget_nodes(Relation): |
92 # XXX has_text may have specific perm ? |
92 # XXX has_text may have specific perm ? |
93 if rel.r_type in READ_ONLY_RTYPES: |
93 if rel.r_type in READ_ONLY_RTYPES: |
94 continue |
94 continue |
95 rschema = schema.rschema(rel.r_type) |
95 rschema = schema.rschema(rel.r_type) |
96 if rschema.final: |
96 if rschema.final: |
97 eschema = schema.eschema(term_etype(session, rel.children[0], |
97 eschema = schema.eschema(term_etype(cnx, rel.children[0], |
98 solution, args)) |
98 solution, args)) |
99 rdef = eschema.rdef(rschema) |
99 rdef = eschema.rdef(rschema) |
100 else: |
100 else: |
101 rdef = rschema.rdef(term_etype(session, rel.children[0], |
101 rdef = rschema.rdef(term_etype(cnx, rel.children[0], |
102 solution, args), |
102 solution, args), |
103 term_etype(session, rel.children[1].children[0], |
103 term_etype(cnx, rel.children[1].children[0], |
104 solution, args)) |
104 solution, args)) |
105 if not session.user.matching_groups(rdef.get_groups('read')): |
105 if not cnx.user.matching_groups(rdef.get_groups('read')): |
106 if DBG: |
106 if DBG: |
107 print ('check_read_access: %s %s does not match %s' % |
107 print ('check_read_access: %s %s does not match %s' % |
108 (rdef, session.user.groups, rdef.get_groups('read'))) |
108 (rdef, cnx.user.groups, rdef.get_groups('read'))) |
109 # XXX rqlexpr not allowed |
109 # XXX rqlexpr not allowed |
110 raise Unauthorized('read', rel.r_type) |
110 raise Unauthorized('read', rel.r_type) |
111 if DBG: |
111 if DBG: |
112 print ('check_read_access: %s %s matches %s' % |
112 print ('check_read_access: %s %s matches %s' % |
113 (rdef, session.user.groups, rdef.get_groups('read'))) |
113 (rdef, cnx.user.groups, rdef.get_groups('read'))) |
114 localchecks = {} |
114 localchecks = {} |
115 # iterate on defined_vars and not on solutions to ignore column aliases |
115 # iterate on defined_vars and not on solutions to ignore column aliases |
116 for varname in rqlst.defined_vars: |
116 for varname in rqlst.defined_vars: |
117 eschema = schema.eschema(solution[varname]) |
117 eschema = schema.eschema(solution[varname]) |
118 if eschema.final: |
118 if eschema.final: |
119 continue |
119 continue |
120 if not session.user.matching_groups(eschema.get_groups('read')): |
120 if not cnx.user.matching_groups(eschema.get_groups('read')): |
121 erqlexprs = eschema.get_rqlexprs('read') |
121 erqlexprs = eschema.get_rqlexprs('read') |
122 if not erqlexprs: |
122 if not erqlexprs: |
123 ex = Unauthorized('read', solution[varname]) |
123 ex = Unauthorized('read', solution[varname]) |
124 ex.var = varname |
124 ex.var = varname |
125 if DBG: |
125 if DBG: |
126 print ('check_read_access: %s %s %s %s' % |
126 print ('check_read_access: %s %s %s %s' % |
127 (varname, eschema, session.user.groups, eschema.get_groups('read'))) |
127 (varname, eschema, cnx.user.groups, eschema.get_groups('read'))) |
128 raise ex |
128 raise ex |
129 # don't insert security on variable only referenced by 'NOT X relation Y' or |
129 # don't insert security on variable only referenced by 'NOT X relation Y' or |
130 # 'NOT EXISTS(X relation Y)' |
130 # 'NOT EXISTS(X relation Y)' |
131 varinfo = rqlst.defined_vars[varname].stinfo |
131 varinfo = rqlst.defined_vars[varname].stinfo |
132 if varinfo['selected'] or ( |
132 if varinfo['selected'] or ( |
142 # Plans ####################################################################### |
142 # Plans ####################################################################### |
143 |
143 |
144 class ExecutionPlan(object): |
144 class ExecutionPlan(object): |
145 """the execution model of a rql query, composed of querier steps""" |
145 """the execution model of a rql query, composed of querier steps""" |
146 |
146 |
147 def __init__(self, querier, rqlst, args, session): |
147 def __init__(self, querier, rqlst, args, cnx): |
148 # original rql syntax tree |
148 # original rql syntax tree |
149 self.rqlst = rqlst |
149 self.rqlst = rqlst |
150 self.args = args or {} |
150 self.args = args or {} |
151 # session executing the query |
151 # cnx executing the query |
152 self.session = session |
152 self.cnx = cnx |
153 # quick reference to the system source |
153 # quick reference to the system source |
154 self.syssource = session.repo.system_source |
154 self.syssource = cnx.repo.system_source |
155 # execution steps |
155 # execution steps |
156 self.steps = [] |
156 self.steps = [] |
157 # various resource accesors |
157 # various resource accesors |
158 self.querier = querier |
158 self.querier = querier |
159 self.schema = querier.schema |
159 self.schema = querier.schema |
160 self.sqlannotate = querier.sqlgen_annotate |
160 self.sqlannotate = querier.sqlgen_annotate |
161 self.rqlhelper = session.vreg.rqlhelper |
161 self.rqlhelper = cnx.vreg.rqlhelper |
162 |
162 |
163 def annotate_rqlst(self): |
163 def annotate_rqlst(self): |
164 if not self.rqlst.annotated: |
164 if not self.rqlst.annotated: |
165 self.rqlhelper.annotate(self.rqlst) |
165 self.rqlhelper.annotate(self.rqlst) |
166 |
166 |
167 def add_step(self, step): |
167 def add_step(self, step): |
168 """add a step to the plan""" |
168 """add a step to the plan""" |
169 self.steps.append(step) |
169 self.steps.append(step) |
170 |
170 |
171 def sqlexec(self, sql, args=None): |
171 def sqlexec(self, sql, args=None): |
172 return self.syssource.sqlexec(self.session, sql, args) |
172 return self.syssource.sqlexec(self.cnx, sql, args) |
173 |
173 |
174 def execute(self): |
174 def execute(self): |
175 """execute a plan and return resulting rows""" |
175 """execute a plan and return resulting rows""" |
176 for step in self.steps: |
176 for step in self.steps: |
177 result = step.execute() |
177 result = step.execute() |
182 """insert security when necessary then annotate rql st for sql generation |
182 """insert security when necessary then annotate rql st for sql generation |
183 |
183 |
184 return rqlst to actually execute |
184 return rqlst to actually execute |
185 """ |
185 """ |
186 cached = None |
186 cached = None |
187 if security and self.session.read_security: |
187 if security and self.cnx.read_security: |
188 # ensure security is turned of when security is inserted, |
188 # ensure security is turned of when security is inserted, |
189 # else we may loop for ever... |
189 # else we may loop for ever... |
190 if self.session.transaction_data.get('security-rqlst-cache'): |
190 if self.cnx.transaction_data.get('security-rqlst-cache'): |
191 key = self.cache_key |
191 key = self.cache_key |
192 else: |
192 else: |
193 key = None |
193 key = None |
194 if key is not None and key in self.session.transaction_data: |
194 if key is not None and key in self.cnx.transaction_data: |
195 cachedunion, args = self.session.transaction_data[key] |
195 cachedunion, args = self.cnx.transaction_data[key] |
196 union.children[:] = [] |
196 union.children[:] = [] |
197 for select in cachedunion.children: |
197 for select in cachedunion.children: |
198 union.append(select) |
198 union.append(select) |
199 union.has_text_query = cachedunion.has_text_query |
199 union.has_text_query = cachedunion.has_text_query |
200 args.update(self.args) |
200 args.update(self.args) |
201 self.args = args |
201 self.args = args |
202 cached = True |
202 cached = True |
203 else: |
203 else: |
204 with self.session.security_enabled(read=False): |
204 with self.cnx.security_enabled(read=False): |
205 noinvariant = self._insert_security(union) |
205 noinvariant = self._insert_security(union) |
206 if key is not None: |
206 if key is not None: |
207 self.session.transaction_data[key] = (union, self.args) |
207 self.cnx.transaction_data[key] = (union, self.args) |
208 else: |
208 else: |
209 noinvariant = () |
209 noinvariant = () |
210 if cached is None: |
210 if cached is None: |
211 self.rqlhelper.simplify(union) |
211 self.rqlhelper.simplify(union) |
212 self.sqlannotate(union) |
212 self.sqlannotate(union) |
219 for select in union.children[:]: |
219 for select in union.children[:]: |
220 for subquery in select.with_: |
220 for subquery in select.with_: |
221 self._insert_security(subquery.query) |
221 self._insert_security(subquery.query) |
222 localchecks, restricted = self._check_permissions(select) |
222 localchecks, restricted = self._check_permissions(select) |
223 if any(localchecks): |
223 if any(localchecks): |
224 self.session.rql_rewriter.insert_local_checks( |
224 self.cnx.rql_rewriter.insert_local_checks( |
225 select, self.args, localchecks, restricted, noinvariant) |
225 select, self.args, localchecks, restricted, noinvariant) |
226 return noinvariant |
226 return noinvariant |
227 |
227 |
228 def _check_permissions(self, rqlst): |
228 def _check_permissions(self, rqlst): |
229 """Return a dict defining "local checks", i.e. RQLExpression defined in |
229 """Return a dict defining "local checks", i.e. RQLExpression defined in |
241 variable which has to be checked. Solutions which don't require local |
241 variable which has to be checked. Solutions which don't require local |
242 checks will be associated to the empty tuple key. |
242 checks will be associated to the empty tuple key. |
243 |
243 |
244 Note rqlst should not have been simplified at this point. |
244 Note rqlst should not have been simplified at this point. |
245 """ |
245 """ |
246 session = self.session |
246 cnx = self.cnx |
247 msgs = [] |
247 msgs = [] |
248 # dict(varname: eid), allowing to check rql expression for variables |
248 # dict(varname: eid), allowing to check rql expression for variables |
249 # which have a known eid |
249 # which have a known eid |
250 varkwargs = {} |
250 varkwargs = {} |
251 if not session.transaction_data.get('security-rqlst-cache'): |
251 if not cnx.transaction_data.get('security-rqlst-cache'): |
252 for var in rqlst.defined_vars.itervalues(): |
252 for var in rqlst.defined_vars.itervalues(): |
253 if var.stinfo['constnode'] is not None: |
253 if var.stinfo['constnode'] is not None: |
254 eid = var.stinfo['constnode'].eval(self.args) |
254 eid = var.stinfo['constnode'].eval(self.args) |
255 varkwargs[var.name] = int(eid) |
255 varkwargs[var.name] = int(eid) |
256 # dictionary of variables restricted for security reason |
256 # dictionary of variables restricted for security reason |
257 localchecks = {} |
257 localchecks = {} |
258 restricted_vars = set() |
258 restricted_vars = set() |
259 newsolutions = [] |
259 newsolutions = [] |
260 for solution in rqlst.solutions: |
260 for solution in rqlst.solutions: |
261 try: |
261 try: |
262 localcheck = check_read_access(session, rqlst, solution, self.args) |
262 localcheck = check_read_access(cnx, rqlst, solution, self.args) |
263 except Unauthorized as ex: |
263 except Unauthorized as ex: |
264 msg = 'remove %s from solutions since %s has no %s access to %s' |
264 msg = 'remove %s from solutions since %s has no %s access to %s' |
265 msg %= (solution, session.user.login, ex.args[0], ex.args[1]) |
265 msg %= (solution, cnx.user.login, ex.args[0], ex.args[1]) |
266 msgs.append(msg) |
266 msgs.append(msg) |
267 LOGGER.info(msg) |
267 LOGGER.info(msg) |
268 else: |
268 else: |
269 newsolutions.append(solution) |
269 newsolutions.append(solution) |
270 # try to benefit of rqlexpr.check cache for entities which |
270 # try to benefit of rqlexpr.check cache for entities which |
275 except KeyError: |
275 except KeyError: |
276 continue |
276 continue |
277 # if entity has been added in the current transaction, the |
277 # if entity has been added in the current transaction, the |
278 # user can read it whatever rql expressions are associated |
278 # user can read it whatever rql expressions are associated |
279 # to its type |
279 # to its type |
280 if session.added_in_transaction(eid): |
280 if cnx.added_in_transaction(eid): |
281 continue |
281 continue |
282 for rqlexpr in rqlexprs: |
282 for rqlexpr in rqlexprs: |
283 if rqlexpr.check(session, eid): |
283 if rqlexpr.check(cnx, eid): |
284 break |
284 break |
285 else: |
285 else: |
286 raise Unauthorized('No read acces on %r with eid %i.' % (var, eid)) |
286 raise Unauthorized('No read acces on %r with eid %i.' % (var, eid)) |
287 # mark variables protected by an rql expression |
287 # mark variables protected by an rql expression |
288 restricted_vars.update(localcheck) |
288 restricted_vars.update(localcheck) |
314 |
314 |
315 class InsertPlan(ExecutionPlan): |
315 class InsertPlan(ExecutionPlan): |
316 """an execution model specific to the INSERT rql query |
316 """an execution model specific to the INSERT rql query |
317 """ |
317 """ |
318 |
318 |
319 def __init__(self, querier, rqlst, args, session): |
319 def __init__(self, querier, rqlst, args, cnx): |
320 ExecutionPlan.__init__(self, querier, rqlst, args, session) |
320 ExecutionPlan.__init__(self, querier, rqlst, args, cnx) |
321 # save originaly selected variable, we may modify this |
321 # save originaly selected variable, we may modify this |
322 # dictionary for substitution (query parameters) |
322 # dictionary for substitution (query parameters) |
323 self.selected = rqlst.selection |
323 self.selected = rqlst.selection |
324 # list of rows of entities definition (ssplanner.EditedEntity) |
324 # list of rows of entities definition (ssplanner.EditedEntity) |
325 self.e_defs = [[]] |
325 self.e_defs = [[]] |
413 WHERE U login 'admin', U login N |
413 WHERE U login 'admin', U login N |
414 |
414 |
415 if there is two entities matching U, the result set will look like |
415 if there is two entities matching U, the result set will look like |
416 [(eidX1, eidY1), (eidX2, eidY2)] |
416 [(eidX1, eidY1), (eidX2, eidY2)] |
417 """ |
417 """ |
418 session = self.session |
418 cnx = self.cnx |
419 repo = session.repo |
419 repo = cnx.repo |
420 results = [] |
420 results = [] |
421 for row in self.e_defs: |
421 for row in self.e_defs: |
422 results.append([repo.glob_add_entity(session, edef) |
422 results.append([repo.glob_add_entity(cnx, edef) |
423 for edef in row]) |
423 for edef in row]) |
424 return results |
424 return results |
425 |
425 |
426 def insert_relation_defs(self): |
426 def insert_relation_defs(self): |
427 session = self.session |
427 cnx = self.cnx |
428 repo = session.repo |
428 repo = cnx.repo |
429 edited_entities = {} |
429 edited_entities = {} |
430 relations = {} |
430 relations = {} |
431 for subj, rtype, obj in self.relation_defs(): |
431 for subj, rtype, obj in self.relation_defs(): |
432 # if a string is given into args instead of an int, we get it here |
432 # if a string is given into args instead of an int, we get it here |
433 if isinstance(subj, basestring): |
433 if isinstance(subj, basestring): |
438 obj = int(obj) |
438 obj = int(obj) |
439 elif not isinstance(obj, (int, long)): |
439 elif not isinstance(obj, (int, long)): |
440 obj = obj.entity.eid |
440 obj = obj.entity.eid |
441 if repo.schema.rschema(rtype).inlined: |
441 if repo.schema.rschema(rtype).inlined: |
442 if subj not in edited_entities: |
442 if subj not in edited_entities: |
443 entity = session.entity_from_eid(subj) |
443 entity = cnx.entity_from_eid(subj) |
444 edited = EditedEntity(entity) |
444 edited = EditedEntity(entity) |
445 edited_entities[subj] = edited |
445 edited_entities[subj] = edited |
446 else: |
446 else: |
447 edited = edited_entities[subj] |
447 edited = edited_entities[subj] |
448 edited.edited_attribute(rtype, obj) |
448 edited.edited_attribute(rtype, obj) |
449 else: |
449 else: |
450 if rtype in relations: |
450 if rtype in relations: |
451 relations[rtype].append((subj, obj)) |
451 relations[rtype].append((subj, obj)) |
452 else: |
452 else: |
453 relations[rtype] = [(subj, obj)] |
453 relations[rtype] = [(subj, obj)] |
454 repo.glob_add_relations(session, relations) |
454 repo.glob_add_relations(cnx, relations) |
455 for edited in edited_entities.itervalues(): |
455 for edited in edited_entities.itervalues(): |
456 repo.glob_update_entity(session, edited) |
456 repo.glob_update_entity(cnx, edited) |
457 |
457 |
458 |
458 |
459 class QuerierHelper(object): |
459 class QuerierHelper(object): |
460 """helper class to execute rql queries, putting all things together""" |
460 """helper class to execute rql queries, putting all things together""" |
461 |
461 |
493 try: |
493 try: |
494 return self._parse(unicode(rql), annotate=annotate) |
494 return self._parse(unicode(rql), annotate=annotate) |
495 except UnicodeError: |
495 except UnicodeError: |
496 raise RQLSyntaxError(rql) |
496 raise RQLSyntaxError(rql) |
497 |
497 |
498 def plan_factory(self, rqlst, args, session): |
498 def plan_factory(self, rqlst, args, cnx): |
499 """create an execution plan for an INSERT RQL query""" |
499 """create an execution plan for an INSERT RQL query""" |
500 if rqlst.TYPE == 'insert': |
500 if rqlst.TYPE == 'insert': |
501 return InsertPlan(self, rqlst, args, session) |
501 return InsertPlan(self, rqlst, args, cnx) |
502 return ExecutionPlan(self, rqlst, args, session) |
502 return ExecutionPlan(self, rqlst, args, cnx) |
503 |
503 |
504 def execute(self, session, rql, args=None, build_descr=True): |
504 def execute(self, cnx, rql, args=None, build_descr=True): |
505 """execute a rql query, return resulting rows and their description in |
505 """execute a rql query, return resulting rows and their description in |
506 a `ResultSet` object |
506 a `ResultSet` object |
507 |
507 |
508 * `rql` should be an Unicode string or a plain ASCII string |
508 * `rql` should be an Unicode string or a plain ASCII string |
509 * `args` the optional parameters dictionary associated to the query |
509 * `args` the optional parameters dictionary associated to the query |
533 eidkeys = self._rql_ck_cache[rql] |
533 eidkeys = self._rql_ck_cache[rql] |
534 if eidkeys: |
534 if eidkeys: |
535 # if there are some, we need a better cache key, eg (rql + |
535 # if there are some, we need a better cache key, eg (rql + |
536 # entity type of each eid) |
536 # entity type of each eid) |
537 try: |
537 try: |
538 cachekey = self._repo.querier_cache_key(session, rql, |
538 cachekey = self._repo.querier_cache_key(cnx, rql, |
539 args, eidkeys) |
539 args, eidkeys) |
540 except UnknownEid: |
540 except UnknownEid: |
541 # we want queries such as "Any X WHERE X eid 9999" |
541 # we want queries such as "Any X WHERE X eid 9999" |
542 # return an empty result instead of raising UnknownEid |
542 # return an empty result instead of raising UnknownEid |
543 return empty_rset(rql, args) |
543 return empty_rset(rql, args) |
549 try: |
549 try: |
550 # compute solutions for rqlst and return named args in query |
550 # compute solutions for rqlst and return named args in query |
551 # which are eids. Notice that if you may not need `eidkeys`, we |
551 # which are eids. Notice that if you may not need `eidkeys`, we |
552 # have to compute solutions anyway (kept as annotation on the |
552 # have to compute solutions anyway (kept as annotation on the |
553 # tree) |
553 # tree) |
554 eidkeys = self.solutions(session, rqlst, args) |
554 eidkeys = self.solutions(cnx, rqlst, args) |
555 except UnknownEid: |
555 except UnknownEid: |
556 # we want queries such as "Any X WHERE X eid 9999" return an |
556 # we want queries such as "Any X WHERE X eid 9999" return an |
557 # empty result instead of raising UnknownEid |
557 # empty result instead of raising UnknownEid |
558 return empty_rset(rql, args, rqlst) |
558 return empty_rset(rql, args, rqlst) |
559 if args and rql not in self._rql_ck_cache: |
559 if args and rql not in self._rql_ck_cache: |
560 self._rql_ck_cache[rql] = eidkeys |
560 self._rql_ck_cache[rql] = eidkeys |
561 if eidkeys: |
561 if eidkeys: |
562 cachekey = self._repo.querier_cache_key(session, rql, args, |
562 cachekey = self._repo.querier_cache_key(cnx, rql, args, |
563 eidkeys) |
563 eidkeys) |
564 self._rql_cache[cachekey] = rqlst |
564 self._rql_cache[cachekey] = rqlst |
565 orig_rqlst = rqlst |
565 orig_rqlst = rqlst |
566 if rqlst.TYPE != 'select': |
566 if rqlst.TYPE != 'select': |
567 if session.read_security: |
567 if cnx.read_security: |
568 check_no_password_selected(rqlst) |
568 check_no_password_selected(rqlst) |
569 # write query, ensure session's mode is 'write' so connections won't |
569 # write query, ensure connection's mode is 'write' so connections |
570 # be released until commit/rollback |
570 # won't be released until commit/rollback |
571 session.mode = 'write' |
571 cnx.mode = 'write' |
572 cachekey = None |
572 cachekey = None |
573 else: |
573 else: |
574 if session.read_security: |
574 if cnx.read_security: |
575 for select in rqlst.children: |
575 for select in rqlst.children: |
576 check_no_password_selected(select) |
576 check_no_password_selected(select) |
577 # on select query, always copy the cached rqlst so we don't have to |
577 # on select query, always copy the cached rqlst so we don't have to |
578 # bother modifying it. This is not necessary on write queries since |
578 # bother modifying it. This is not necessary on write queries since |
579 # a new syntax tree is built from them. |
579 # a new syntax tree is built from them. |
583 # different SQL generated when some argument is None or not (IS |
583 # different SQL generated when some argument is None or not (IS |
584 # NULL). This should be considered when computing sql cache key |
584 # NULL). This should be considered when computing sql cache key |
585 cachekey += tuple(sorted([k for k, v in args.iteritems() |
585 cachekey += tuple(sorted([k for k, v in args.iteritems() |
586 if v is None])) |
586 if v is None])) |
587 # make an execution plan |
587 # make an execution plan |
588 plan = self.plan_factory(rqlst, args, session) |
588 plan = self.plan_factory(rqlst, args, cnx) |
589 plan.cache_key = cachekey |
589 plan.cache_key = cachekey |
590 self._planner.build_plan(plan) |
590 self._planner.build_plan(plan) |
591 # execute the plan |
591 # execute the plan |
592 try: |
592 try: |
593 results = plan.execute() |
593 results = plan.execute() |
595 # getting an Unauthorized/ValidationError exception means the |
595 # getting an Unauthorized/ValidationError exception means the |
596 # transaction must be rolled back |
596 # transaction must be rolled back |
597 # |
597 # |
598 # notes: |
598 # notes: |
599 # * we should not reset the connections set here, since we don't want the |
599 # * we should not reset the connections set here, since we don't want the |
600 # session to loose it during processing |
600 # connection to loose it during processing |
601 # * don't rollback if we're in the commit process, will be handled |
601 # * don't rollback if we're in the commit process, will be handled |
602 # by the session |
602 # by the connection |
603 if session.commit_state is None: |
603 if cnx.commit_state is None: |
604 session.commit_state = 'uncommitable' |
604 cnx.commit_state = 'uncommitable' |
605 raise |
605 raise |
606 # build a description for the results if necessary |
606 # build a description for the results if necessary |
607 descr = () |
607 descr = () |
608 if build_descr: |
608 if build_descr: |
609 if rqlst.TYPE == 'select': |
609 if rqlst.TYPE == 'select': |
614 solution = rqlst.children[0].solutions[0] |
614 solution = rqlst.children[0].solutions[0] |
615 description = _make_description(selected, args, solution) |
615 description = _make_description(selected, args, solution) |
616 descr = RepeatList(len(results), tuple(description)) |
616 descr = RepeatList(len(results), tuple(description)) |
617 else: |
617 else: |
618 # hard, delegate the work :o) |
618 # hard, delegate the work :o) |
619 descr = manual_build_descr(session, rqlst, args, results) |
619 descr = manual_build_descr(cnx, rqlst, args, results) |
620 elif rqlst.TYPE == 'insert': |
620 elif rqlst.TYPE == 'insert': |
621 # on insert plan, some entities may have been auto-casted, |
621 # on insert plan, some entities may have been auto-casted, |
622 # so compute description manually even if there is only |
622 # so compute description manually even if there is only |
623 # one solution |
623 # one solution |
624 basedescr = [None] * len(plan.selected) |
624 basedescr = [None] * len(plan.selected) |
625 todetermine = zip(xrange(len(plan.selected)), repeat(False)) |
625 todetermine = zip(xrange(len(plan.selected)), repeat(False)) |
626 descr = _build_descr(session, results, basedescr, todetermine) |
626 descr = _build_descr(cnx, results, basedescr, todetermine) |
627 # FIXME: get number of affected entities / relations on non |
627 # FIXME: get number of affected entities / relations on non |
628 # selection queries ? |
628 # selection queries ? |
629 # return a result set object |
629 # return a result set object |
630 return ResultSet(results, rql, args, descr, orig_rqlst) |
630 return ResultSet(results, rql, args, descr, orig_rqlst) |
631 |
631 |
637 from cubicweb import set_log_methods |
637 from cubicweb import set_log_methods |
638 LOGGER = getLogger('cubicweb.querier') |
638 LOGGER = getLogger('cubicweb.querier') |
639 set_log_methods(QuerierHelper, LOGGER) |
639 set_log_methods(QuerierHelper, LOGGER) |
640 |
640 |
641 |
641 |
642 def manual_build_descr(tx, rqlst, args, result): |
642 def manual_build_descr(cnx, rqlst, args, result): |
643 """build a description for a given result by analysing each row |
643 """build a description for a given result by analysing each row |
644 |
644 |
645 XXX could probably be done more efficiently during execution of query |
645 XXX could probably be done more efficiently during execution of query |
646 """ |
646 """ |
647 # not so easy, looks for variable which changes from one solution |
647 # not so easy, looks for variable which changes from one solution |
661 todetermine.append( (i, isfinal) ) |
661 todetermine.append( (i, isfinal) ) |
662 else: |
662 else: |
663 basedescr.append(ttype) |
663 basedescr.append(ttype) |
664 if not todetermine: |
664 if not todetermine: |
665 return RepeatList(len(result), tuple(basedescr)) |
665 return RepeatList(len(result), tuple(basedescr)) |
666 return _build_descr(tx, result, basedescr, todetermine) |
666 return _build_descr(cnx, result, basedescr, todetermine) |
667 |
667 |
668 def _build_descr(tx, result, basedescription, todetermine): |
668 def _build_descr(cnx, result, basedescription, todetermine): |
669 description = [] |
669 description = [] |
670 entity_metas = tx.entity_metas |
670 entity_metas = cnx.entity_metas |
671 todel = [] |
671 todel = [] |
672 for i, row in enumerate(result): |
672 for i, row in enumerate(result): |
673 row_descr = basedescription[:] |
673 row_descr = basedescription[:] |
674 for index, isfinal in todetermine: |
674 for index, isfinal in todetermine: |
675 value = row[index] |
675 value = row[index] |