241 result.append(rset) |
241 result.append(rset) |
242 else: |
242 else: |
243 rset = mapping[key] |
243 rset = mapping[key] |
244 rset.rows.append(self.rows[idx]) |
244 rset.rows.append(self.rows[idx]) |
245 rset.description.append(self.description[idx]) |
245 rset.description.append(self.description[idx]) |
246 |
|
247 |
|
248 for rset in result: |
246 for rset in result: |
249 rset.rowcount = len(rset.rows) |
247 rset.rowcount = len(rset.rows) |
250 if return_dict: |
248 if return_dict: |
251 return mapping |
249 return mapping |
252 else: |
250 else: |
253 return result |
251 return result |
|
252 |
|
253 def limited_rql(self): |
|
254 """return a printable rql for the result set associated to the object, |
|
255 with limit/offset correctly set according to maximum page size and |
|
256 currently displayed page when necessary |
|
257 """ |
|
258 # try to get page boundaries from the navigation component |
|
259 # XXX we should probably not have a ref to this component here (eg in |
|
260 # cubicweb) |
|
261 nav = self.vreg['components'].select_or_none('navigation', self.req, |
|
262 rset=self) |
|
263 if nav: |
|
264 start, stop = nav.page_boundaries() |
|
265 rql = self._limit_offset_rql(stop - start, start) |
|
266 # result set may have be limited manually in which case navigation won't |
|
267 # apply |
|
268 elif self.limited: |
|
269 rql = self._limit_offset_rql(*self.limited) |
|
270 # navigation component doesn't apply and rset has not been limited, no |
|
271 # need to limit query |
|
272 else: |
|
273 rql = self.printable_rql() |
|
274 return rql |
|
275 |
|
276 def _limit_offset_rql(self, limit, offset): |
|
277 rqlst = self.syntax_tree() |
|
278 if len(rqlst.children) == 1: |
|
279 select = rqlst.children[0] |
|
280 olimit, ooffset = select.limit, select.offset |
|
281 select.limit, select.offset = limit, offset |
|
282 rql = rqlst.as_string(kwargs=self.args) |
|
283 # restore original limit/offset |
|
284 select.limit, select.offset = olimit, ooffset |
|
285 else: |
|
286 newselect = stmts.Select() |
|
287 newselect.limit = limit |
|
288 newselect.offset = offset |
|
289 aliases = [nodes.VariableRef(newselect.get_variable(chr(65+i), i)) |
|
290 for i in xrange(len(rqlst.children[0].selection))] |
|
291 for vref in aliases: |
|
292 newselect.append_selected(nodes.VariableRef(vref.variable)) |
|
293 newselect.set_with([nodes.SubQuery(aliases, rqlst)], check=False) |
|
294 newunion = stmts.Union() |
|
295 newunion.append(newselect) |
|
296 rql = newunion.as_string(kwargs=self.args) |
|
297 rqlst.parent = None |
|
298 return rql |
254 |
299 |
255 def limit(self, limit, offset=0, inplace=False): |
300 def limit(self, limit, offset=0, inplace=False): |
256 """limit the result set to the given number of rows optionaly starting |
301 """limit the result set to the given number of rows optionaly starting |
257 from an index different than 0 |
302 from an index different than 0 |
258 |
303 |
280 if offset: |
325 if offset: |
281 clear_cache(rset, 'get_entity') |
326 clear_cache(rset, 'get_entity') |
282 # we also have to fix/remove from the request entity cache entities |
327 # we also have to fix/remove from the request entity cache entities |
283 # which get a wrong rset reference by this limit call |
328 # which get a wrong rset reference by this limit call |
284 for entity in self.req.cached_entities(): |
329 for entity in self.req.cached_entities(): |
285 if entity.rset is self: |
330 if entity.cw_rset is self: |
286 if offset <= entity.row < stop: |
331 if offset <= entity.cw_row < stop: |
287 entity.row = entity.row - offset |
332 entity.cw_row = entity.cw_row - offset |
288 else: |
333 else: |
289 self.req.drop_entity_cache(entity.eid) |
334 self.req.drop_entity_cache(entity.eid) |
290 else: |
335 else: |
291 rset = self.copy(rows, descr) |
336 rset = self.copy(rows, descr) |
292 if not offset: |
337 if not offset: |
319 # may have None values in case of outer join (or aggregat on eid |
364 # may have None values in case of outer join (or aggregat on eid |
320 # hacks) |
365 # hacks) |
321 if self.rows[i][col] is not None: |
366 if self.rows[i][col] is not None: |
322 yield self.get_entity(i, col) |
367 yield self.get_entity(i, col) |
323 |
368 |
|
369 def complete_entity(self, row, col=0, skip_bytes=True): |
|
370 """short cut to get an completed entity instance for a particular |
|
371 row (all instance's attributes have been fetched) |
|
372 """ |
|
373 entity = self.get_entity(row, col) |
|
374 entity.complete(skip_bytes=skip_bytes) |
|
375 return entity |
|
376 |
324 @cached |
377 @cached |
325 def get_entity(self, row, col=None): |
378 def get_entity(self, row, col): |
326 """special method for query retreiving a single entity, returns a |
379 """special method for query retreiving a single entity, returns a |
327 partially initialized Entity instance. |
380 partially initialized Entity instance. |
328 |
381 |
329 WARNING: due to the cache wrapping this function, you should NEVER |
382 WARNING: due to the cache wrapping this function, you should NEVER |
330 give row as a named parameter (i.e. rset.get_entity(req, 0) |
383 give row as a named parameter (i.e. rset.get_entity(req, 0) |
334 :param row,col: |
387 :param row,col: |
335 row and col numbers localizing the entity among the result's table |
388 row and col numbers localizing the entity among the result's table |
336 |
389 |
337 :return: the partially initialized `Entity` instance |
390 :return: the partially initialized `Entity` instance |
338 """ |
391 """ |
339 if col is None: |
|
340 from warnings import warn |
|
341 msg = 'col parameter will become mandatory in future version' |
|
342 warn(msg, DeprecationWarning, stacklevel=3) |
|
343 col = 0 |
|
344 etype = self.description[row][col] |
392 etype = self.description[row][col] |
345 try: |
393 try: |
346 eschema = self.vreg.schema.eschema(etype) |
394 eschema = self.vreg.schema.eschema(etype) |
347 if eschema.final: |
395 if eschema.final: |
348 raise NotAnEntity(etype) |
396 raise NotAnEntity(etype) |
372 # return cached entity if exists. This also avoids potential recursion |
420 # return cached entity if exists. This also avoids potential recursion |
373 # XXX should we consider updating a cached entity with possible |
421 # XXX should we consider updating a cached entity with possible |
374 # new attributes found in this resultset ? |
422 # new attributes found in this resultset ? |
375 try: |
423 try: |
376 entity = req.entity_cache(eid) |
424 entity = req.entity_cache(eid) |
377 if entity.rset is None: |
|
378 # entity has no rset set, this means entity has been cached by |
|
379 # the repository (req is a repository session) which had no rset |
|
380 # info. Add id. |
|
381 entity.rset = self |
|
382 entity.row = row |
|
383 entity.col = col |
|
384 return entity |
|
385 except KeyError: |
425 except KeyError: |
386 pass |
426 pass |
|
427 else: |
|
428 if entity.cw_rset is None: |
|
429 # entity has no rset set, this means entity has been created by |
|
430 # the querier (req is a repository session) and so jas no rset |
|
431 # info. Add it. |
|
432 entity.cw_rset = self |
|
433 entity.cw_row = row |
|
434 entity.cw_col = col |
|
435 return entity |
387 # build entity instance |
436 # build entity instance |
388 etype = self.description[row][col] |
437 etype = self.description[row][col] |
389 entity = self.vreg['etypes'].etype_class(etype)(req, rset=self, |
438 entity = self.vreg['etypes'].etype_class(etype)(req, rset=self, |
390 row=row, col=col) |
439 row=row, col=col) |
391 entity.set_eid(eid) |
440 entity.set_eid(eid) |
401 select, col = rqlst.locate_subquery(col, etype, self.args) |
450 select, col = rqlst.locate_subquery(col, etype, self.args) |
402 else: |
451 else: |
403 select = rqlst |
452 select = rqlst |
404 # take care, due to outer join support, we may find None |
453 # take care, due to outer join support, we may find None |
405 # values for non final relation |
454 # values for non final relation |
406 for i, attr, x in attr_desc_iterator(select, col): |
455 for i, attr, role in attr_desc_iterator(select, col): |
407 outerselidx = rqlst.subquery_selection_index(select, i) |
456 outerselidx = rqlst.subquery_selection_index(select, i) |
408 if outerselidx is None: |
457 if outerselidx is None: |
409 continue |
458 continue |
410 if x == 'subject': |
459 if role == 'subject': |
411 rschema = eschema.subjrels[attr] |
460 rschema = eschema.subjrels[attr] |
412 if rschema.final: |
461 if rschema.final: |
413 entity[attr] = rowvalues[outerselidx] |
462 entity[attr] = rowvalues[outerselidx] |
414 continue |
463 continue |
415 tetype = rschema.objects(etype)[0] |
|
416 card = rschema.rproperty(etype, tetype, 'cardinality')[0] |
|
417 else: |
464 else: |
418 rschema = eschema.objrels[attr] |
465 rschema = eschema.objrels[attr] |
419 tetype = rschema.subjects(etype)[0] |
466 rdef = eschema.rdef(attr, role) |
420 card = rschema.rproperty(tetype, etype, 'cardinality')[1] |
|
421 # only keep value if it can't be multivalued |
467 # only keep value if it can't be multivalued |
422 if card in '1?': |
468 if rdef.role_cardinality(role) in '1?': |
423 if rowvalues[outerselidx] is None: |
469 if rowvalues[outerselidx] is None: |
424 if x == 'subject': |
470 if role == 'subject': |
425 rql = 'Any Y WHERE X %s Y, X eid %s' |
471 rql = 'Any Y WHERE X %s Y, X eid %s' |
426 else: |
472 else: |
427 rql = 'Any Y WHERE Y %s X, X eid %s' |
473 rql = 'Any Y WHERE Y %s X, X eid %s' |
428 rrset = ResultSet([], rql % (attr, entity.eid)) |
474 rrset = ResultSet([], rql % (attr, entity.eid)) |
429 req.decorate_rset(rrset) |
475 req.decorate_rset(rrset) |
430 else: |
476 else: |
431 rrset = self._build_entity(row, outerselidx).as_rset() |
477 rrset = self._build_entity(row, outerselidx).as_rset() |
432 entity.set_related_cache(attr, x, rrset) |
478 entity.set_related_cache(attr, role, rrset) |
433 return entity |
479 return entity |
434 |
480 |
435 @cached |
481 @cached |
436 def syntax_tree(self): |
482 def syntax_tree(self): |
437 """get the syntax tree for the source query. |
483 """get the syntax tree for the source query. |
479 last = row |
525 last = row |
480 if last is not None: |
526 if last is not None: |
481 result[-1][1] = i |
527 result[-1][1] = i |
482 return result |
528 return result |
483 |
529 |
|
530 def _locate_query_params(self, rqlst, row, col): |
|
531 locate_query_col = col |
|
532 etype = self.description[row][col] |
|
533 # final type, find a better one to locate the correct subquery |
|
534 # (ambiguous if possible) |
|
535 eschema = self.vreg.schema.eschema |
|
536 if eschema(etype).final: |
|
537 for select in rqlst.children: |
|
538 try: |
|
539 myvar = select.selection[col].variable |
|
540 except AttributeError: |
|
541 # not a variable |
|
542 continue |
|
543 for i in xrange(len(select.selection)): |
|
544 if i == col: |
|
545 continue |
|
546 coletype = self.description[row][i] |
|
547 # None description possible on column resulting from an outer join |
|
548 if coletype is None or eschema(coletype).final: |
|
549 continue |
|
550 try: |
|
551 ivar = select.selection[i].variable |
|
552 except AttributeError: |
|
553 # not a variable |
|
554 continue |
|
555 # check variables don't comes from a subquery or are both |
|
556 # coming from the same subquery |
|
557 if getattr(ivar, 'query', None) is getattr(myvar, 'query', None): |
|
558 etype = coletype |
|
559 locate_query_col = i |
|
560 if len(self.column_types(i)) > 1: |
|
561 return etype, locate_query_col |
|
562 return etype, locate_query_col |
|
563 |
484 @cached |
564 @cached |
485 def related_entity(self, row, col): |
565 def related_entity(self, row, col): |
486 """try to get the related entity to extract format information if any""" |
566 """try to get the related entity to extract format information if any""" |
487 locate_query_col = col |
|
488 rqlst = self.syntax_tree() |
567 rqlst = self.syntax_tree() |
489 etype = self.description[row][col] |
568 etype, locate_query_col = self._locate_query_params(rqlst, row, col) |
490 if self.vreg.schema.eschema(etype).final: |
|
491 # final type, find a better one to locate the correct subquery |
|
492 # (ambiguous if possible) |
|
493 for i in xrange(len(rqlst.children[0].selection)): |
|
494 if i == col: |
|
495 continue |
|
496 coletype = self.description[row][i] |
|
497 if coletype is None: |
|
498 continue |
|
499 if not self.vreg.schema.eschema(coletype).final: |
|
500 etype = coletype |
|
501 locate_query_col = i |
|
502 if len(self.column_types(i)) > 1: |
|
503 break |
|
504 # UNION query, find the subquery from which this entity has been found |
569 # UNION query, find the subquery from which this entity has been found |
505 select = rqlst.locate_subquery(locate_query_col, etype, self.args)[0] |
570 select = rqlst.locate_subquery(locate_query_col, etype, self.args)[0] |
506 col = rqlst.subquery_selection_index(select, col) |
571 col = rqlst.subquery_selection_index(select, col) |
507 if col is None: |
572 if col is None: |
508 # XXX unexpected, should fix subquery_selection_index ? |
573 # XXX unexpected, should fix subquery_selection_index ? |