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(vref.name, i)) |
|
290 for i, vref in enumerate(rqlst.selection)] |
|
291 newselect.set_with([nodes.SubQuery(aliases, rqlst)], check=False) |
|
292 newunion = stmts.Union() |
|
293 newunion.append(newselect) |
|
294 rql = rqlst.as_string(kwargs=self.args) |
|
295 rqlst.parent = None |
|
296 return rql |
254 |
297 |
255 def limit(self, limit, offset=0, inplace=False): |
298 def limit(self, limit, offset=0, inplace=False): |
256 """limit the result set to the given number of rows optionaly starting |
299 """limit the result set to the given number of rows optionaly starting |
257 from an index different than 0 |
300 from an index different than 0 |
258 |
301 |
280 if offset: |
323 if offset: |
281 clear_cache(rset, 'get_entity') |
324 clear_cache(rset, 'get_entity') |
282 # we also have to fix/remove from the request entity cache entities |
325 # we also have to fix/remove from the request entity cache entities |
283 # which get a wrong rset reference by this limit call |
326 # which get a wrong rset reference by this limit call |
284 for entity in self.req.cached_entities(): |
327 for entity in self.req.cached_entities(): |
285 if entity.rset is self: |
328 if entity.cw_rset is self: |
286 if offset <= entity.row < stop: |
329 if offset <= entity.cw_row < stop: |
287 entity.row = entity.row - offset |
330 entity.cw_row = entity.cw_row - offset |
288 else: |
331 else: |
289 self.req.drop_entity_cache(entity.eid) |
332 self.req.drop_entity_cache(entity.eid) |
290 else: |
333 else: |
291 rset = self.copy(rows, descr) |
334 rset = self.copy(rows, descr) |
292 if not offset: |
335 if not offset: |
319 # may have None values in case of outer join (or aggregat on eid |
362 # may have None values in case of outer join (or aggregat on eid |
320 # hacks) |
363 # hacks) |
321 if self.rows[i][col] is not None: |
364 if self.rows[i][col] is not None: |
322 yield self.get_entity(i, col) |
365 yield self.get_entity(i, col) |
323 |
366 |
|
367 def complete_entity(self, row, col=0, skip_bytes=True): |
|
368 """short cut to get an completed entity instance for a particular |
|
369 row (all instance's attributes have been fetched) |
|
370 """ |
|
371 entity = self.get_entity(row, col) |
|
372 entity.complete(skip_bytes=skip_bytes) |
|
373 return entity |
|
374 |
324 @cached |
375 @cached |
325 def get_entity(self, row, col=None): |
376 def get_entity(self, row, col): |
326 """special method for query retreiving a single entity, returns a |
377 """special method for query retreiving a single entity, returns a |
327 partially initialized Entity instance. |
378 partially initialized Entity instance. |
328 |
379 |
329 WARNING: due to the cache wrapping this function, you should NEVER |
380 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) |
381 give row as a named parameter (i.e. rset.get_entity(req, 0) |
334 :param row,col: |
385 :param row,col: |
335 row and col numbers localizing the entity among the result's table |
386 row and col numbers localizing the entity among the result's table |
336 |
387 |
337 :return: the partially initialized `Entity` instance |
388 :return: the partially initialized `Entity` instance |
338 """ |
389 """ |
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] |
390 etype = self.description[row][col] |
345 try: |
391 try: |
346 eschema = self.vreg.schema.eschema(etype) |
392 eschema = self.vreg.schema.eschema(etype) |
347 if eschema.final: |
393 if eschema.final: |
348 raise NotAnEntity(etype) |
394 raise NotAnEntity(etype) |
372 # return cached entity if exists. This also avoids potential recursion |
418 # return cached entity if exists. This also avoids potential recursion |
373 # XXX should we consider updating a cached entity with possible |
419 # XXX should we consider updating a cached entity with possible |
374 # new attributes found in this resultset ? |
420 # new attributes found in this resultset ? |
375 try: |
421 try: |
376 entity = req.entity_cache(eid) |
422 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: |
423 except KeyError: |
386 pass |
424 pass |
|
425 else: |
|
426 if entity.cw_rset is None: |
|
427 # entity has no rset set, this means entity has been created by |
|
428 # the querier (req is a repository session) and so jas no rset |
|
429 # info. Add it. |
|
430 entity.cw_rset = self |
|
431 entity.cw_row = row |
|
432 entity.cw_col = col |
|
433 return entity |
387 # build entity instance |
434 # build entity instance |
388 etype = self.description[row][col] |
435 etype = self.description[row][col] |
389 entity = self.vreg['etypes'].etype_class(etype)(req, rset=self, |
436 entity = self.vreg['etypes'].etype_class(etype)(req, rset=self, |
390 row=row, col=col) |
437 row=row, col=col) |
391 entity.set_eid(eid) |
438 entity.set_eid(eid) |
401 select, col = rqlst.locate_subquery(col, etype, self.args) |
448 select, col = rqlst.locate_subquery(col, etype, self.args) |
402 else: |
449 else: |
403 select = rqlst |
450 select = rqlst |
404 # take care, due to outer join support, we may find None |
451 # take care, due to outer join support, we may find None |
405 # values for non final relation |
452 # values for non final relation |
406 for i, attr, x in attr_desc_iterator(select, col): |
453 for i, attr, role in attr_desc_iterator(select, col): |
407 outerselidx = rqlst.subquery_selection_index(select, i) |
454 outerselidx = rqlst.subquery_selection_index(select, i) |
408 if outerselidx is None: |
455 if outerselidx is None: |
409 continue |
456 continue |
410 if x == 'subject': |
457 if role == 'subject': |
411 rschema = eschema.subjrels[attr] |
458 rschema = eschema.subjrels[attr] |
412 if rschema.final: |
459 if rschema.final: |
413 entity[attr] = rowvalues[outerselidx] |
460 entity[attr] = rowvalues[outerselidx] |
414 continue |
461 continue |
415 tetype = rschema.objects(etype)[0] |
|
416 card = rschema.rproperty(etype, tetype, 'cardinality')[0] |
|
417 else: |
462 else: |
418 rschema = eschema.objrels[attr] |
463 rschema = eschema.objrels[attr] |
419 tetype = rschema.subjects(etype)[0] |
464 rdef = eschema.rdef(attr, role) |
420 card = rschema.rproperty(tetype, etype, 'cardinality')[1] |
|
421 # only keep value if it can't be multivalued |
465 # only keep value if it can't be multivalued |
422 if card in '1?': |
466 if rdef.role_cardinality(role) in '1?': |
423 if rowvalues[outerselidx] is None: |
467 if rowvalues[outerselidx] is None: |
424 if x == 'subject': |
468 if role == 'subject': |
425 rql = 'Any Y WHERE X %s Y, X eid %s' |
469 rql = 'Any Y WHERE X %s Y, X eid %s' |
426 else: |
470 else: |
427 rql = 'Any Y WHERE Y %s X, X eid %s' |
471 rql = 'Any Y WHERE Y %s X, X eid %s' |
428 rrset = ResultSet([], rql % (attr, entity.eid)) |
472 rrset = ResultSet([], rql % (attr, entity.eid)) |
429 req.decorate_rset(rrset) |
473 req.decorate_rset(rrset) |
430 else: |
474 else: |
431 rrset = self._build_entity(row, outerselidx).as_rset() |
475 rrset = self._build_entity(row, outerselidx).as_rset() |
432 entity.set_related_cache(attr, x, rrset) |
476 entity.set_related_cache(attr, role, rrset) |
433 return entity |
477 return entity |
434 |
478 |
435 @cached |
479 @cached |
436 def syntax_tree(self): |
480 def syntax_tree(self): |
437 """get the syntax tree for the source query. |
481 """get the syntax tree for the source query. |
479 last = row |
523 last = row |
480 if last is not None: |
524 if last is not None: |
481 result[-1][1] = i |
525 result[-1][1] = i |
482 return result |
526 return result |
483 |
527 |
|
528 def _locate_query_params(self, rqlst, row, col): |
|
529 locate_query_col = col |
|
530 etype = self.description[row][col] |
|
531 # final type, find a better one to locate the correct subquery |
|
532 # (ambiguous if possible) |
|
533 eschema = self.vreg.schema.eschema |
|
534 if eschema(etype).final: |
|
535 for select in rqlst.children: |
|
536 try: |
|
537 myvar = select.selection[col].variable |
|
538 except AttributeError: |
|
539 # not a variable |
|
540 continue |
|
541 for i in xrange(len(select.selection)): |
|
542 if i == col: |
|
543 continue |
|
544 coletype = self.description[row][i] |
|
545 # None description possible on column resulting from an outer join |
|
546 if coletype is None or eschema(coletype).final: |
|
547 continue |
|
548 try: |
|
549 ivar = select.selection[i].variable |
|
550 except AttributeError: |
|
551 # not a variable |
|
552 continue |
|
553 # check variables don't comes from a subquery or are both |
|
554 # coming from the same subquery |
|
555 if getattr(ivar, 'query', None) is getattr(myvar, 'query', None): |
|
556 etype = coletype |
|
557 locate_query_col = i |
|
558 if len(self.column_types(i)) > 1: |
|
559 return etype, locate_query_col |
|
560 return etype, locate_query_col |
|
561 |
484 @cached |
562 @cached |
485 def related_entity(self, row, col): |
563 def related_entity(self, row, col): |
486 """try to get the related entity to extract format information if any""" |
564 """try to get the related entity to extract format information if any""" |
487 locate_query_col = col |
|
488 rqlst = self.syntax_tree() |
565 rqlst = self.syntax_tree() |
489 etype = self.description[row][col] |
566 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 |
567 # UNION query, find the subquery from which this entity has been found |
505 select = rqlst.locate_subquery(locate_query_col, etype, self.args)[0] |
568 select = rqlst.locate_subquery(locate_query_col, etype, self.args)[0] |
506 col = rqlst.subquery_selection_index(select, col) |
569 col = rqlst.subquery_selection_index(select, col) |
507 if col is None: |
570 if col is None: |
508 # XXX unexpected, should fix subquery_selection_index ? |
571 # XXX unexpected, should fix subquery_selection_index ? |