9 from logilab.common.decorators import cached, clear_cache, copy_cache |
9 from logilab.common.decorators import cached, clear_cache, copy_cache |
10 |
10 |
11 from rql import nodes |
11 from rql import nodes |
12 |
12 |
13 from cubicweb import NotAnEntity |
13 from cubicweb import NotAnEntity |
14 |
14 |
15 |
15 |
16 class ResultSet(object): |
16 class ResultSet(object): |
17 """a result set wrap a RQL query result. This object implements a partial |
17 """a result set wrap a RQL query result. This object implements a partial |
18 list protocol to allow direct use as a list of result rows. |
18 list protocol to allow direct use as a list of result rows. |
19 |
19 |
51 # set by the cursor which returned this resultset |
51 # set by the cursor which returned this resultset |
52 self.vreg = None |
52 self.vreg = None |
53 self.req = None |
53 self.req = None |
54 # actions cache |
54 # actions cache |
55 self._rsetactions = None |
55 self._rsetactions = None |
56 |
56 |
57 def __str__(self): |
57 def __str__(self): |
58 if not self.rows: |
58 if not self.rows: |
59 return '<empty resultset %s>' % self.rql |
59 return '<empty resultset %s>' % self.rql |
60 return '<resultset %s (%s rows)>' % (self.rql, len(self.rows)) |
60 return '<resultset %s (%s rows)>' % (self.rql, len(self.rows)) |
61 |
61 |
62 def __repr__(self): |
62 def __repr__(self): |
63 if not self.rows: |
63 if not self.rows: |
64 return '<empty resultset for %r>' % self.rql |
64 return '<empty resultset for %r>' % self.rql |
65 rows = self.rows |
65 rows = self.rows |
66 if len(rows) > 10: |
66 if len(rows) > 10: |
83 return self._rsetactions[key] |
83 return self._rsetactions[key] |
84 except KeyError: |
84 except KeyError: |
85 actions = self.vreg.possible_vobjects('actions', self.req, self, **kwargs) |
85 actions = self.vreg.possible_vobjects('actions', self.req, self, **kwargs) |
86 self._rsetactions[key] = actions |
86 self._rsetactions[key] = actions |
87 return actions |
87 return actions |
88 |
88 |
89 def __len__(self): |
89 def __len__(self): |
90 """returns the result set's size""" |
90 """returns the result set's size""" |
91 return self.rowcount |
91 return self.rowcount |
92 |
92 |
93 def __nonzero__(self): |
93 def __nonzero__(self): |
94 return self.rowcount |
94 return self.rowcount |
95 |
95 |
96 def __getitem__(self, i): |
96 def __getitem__(self, i): |
97 """returns the ith element of the result set""" |
97 """returns the ith element of the result set""" |
98 return self.rows[i] #ResultSetRow(self.rows[i]) |
98 return self.rows[i] #ResultSetRow(self.rows[i]) |
99 |
99 |
100 def __getslice__(self, i, j): |
100 def __getslice__(self, i, j): |
101 """returns slice [i:j] of the result set""" |
101 """returns slice [i:j] of the result set""" |
102 return self.rows[i:j] |
102 return self.rows[i:j] |
103 |
103 |
104 def __iter__(self): |
104 def __iter__(self): |
105 """Returns an iterator over rows""" |
105 """Returns an iterator over rows""" |
106 return iter(self.rows) |
106 return iter(self.rows) |
107 |
107 |
108 def __add__(self, rset): |
108 def __add__(self, rset): |
124 |
124 |
125 :type transormcb: callable(row, desc) |
125 :type transormcb: callable(row, desc) |
126 :param transformcb: |
126 :param transformcb: |
127 a callable which should take a row and its type description as |
127 a callable which should take a row and its type description as |
128 parameters, and return the transformed row and type description. |
128 parameters, and return the transformed row and type description. |
129 |
129 |
130 |
130 |
131 :type col: int |
131 :type col: int |
132 :param col: the column index |
132 :param col: the column index |
133 |
133 |
134 :rtype: `ResultSet` |
134 :rtype: `ResultSet` |
197 rset.rowcount = len(rows) |
197 rset.rowcount = len(rows) |
198 return rset |
198 return rset |
199 |
199 |
200 def split_rset(self, keyfunc=None, col=0, return_dict=False): |
200 def split_rset(self, keyfunc=None, col=0, return_dict=False): |
201 """Splits the result set in multiple result set according to a given key |
201 """Splits the result set in multiple result set according to a given key |
202 |
202 |
203 :type keyfunc: callable(entity or FinalType) |
203 :type keyfunc: callable(entity or FinalType) |
204 :param keyfunc: |
204 :param keyfunc: |
205 a callable which should take a value of the rset in argument and |
205 a callable which should take a value of the rset in argument and |
206 return the value used to group the value. If not define, raw value |
206 return the value used to group the value. If not define, raw value |
207 of the specified columns is used. |
207 of the specified columns is used. |
254 :type limit: int |
254 :type limit: int |
255 :param limit: the maximum number of results |
255 :param limit: the maximum number of results |
256 |
256 |
257 :type offset: int |
257 :type offset: int |
258 :param offset: the offset index |
258 :param offset: the offset index |
259 |
259 |
260 :type inplace: bool |
260 :type inplace: bool |
261 :param inplace: |
261 :param inplace: |
262 if true, the result set is modified in place, else a new result set |
262 if true, the result set is modified in place, else a new result set |
263 is returned and the original is left unmodified |
263 is returned and the original is left unmodified |
264 |
264 |
287 if not offset: |
287 if not offset: |
288 # can copy built entity caches |
288 # can copy built entity caches |
289 copy_cache(rset, 'get_entity', self) |
289 copy_cache(rset, 'get_entity', self) |
290 rset.limited = (limit, offset) |
290 rset.limited = (limit, offset) |
291 return rset |
291 return rset |
292 |
292 |
293 def printable_rql(self, encoded=False): |
293 def printable_rql(self, encoded=False): |
294 """return the result set's origin rql as a string, with arguments |
294 """return the result set's origin rql as a string, with arguments |
295 substitued |
295 substitued |
296 """ |
296 """ |
297 encoding = self.req.encoding |
297 encoding = self.req.encoding |
299 # sounds like we get encoded or unicode string due to a bug in as_string |
299 # sounds like we get encoded or unicode string due to a bug in as_string |
300 if not encoded: |
300 if not encoded: |
301 if isinstance(rqlstr, unicode): |
301 if isinstance(rqlstr, unicode): |
302 return rqlstr |
302 return rqlstr |
303 return unicode(rqlstr, encoding) |
303 return unicode(rqlstr, encoding) |
304 else: |
304 else: |
305 if isinstance(rqlstr, unicode): |
305 if isinstance(rqlstr, unicode): |
306 return rqlstr.encode(encoding) |
306 return rqlstr.encode(encoding) |
307 return rqlstr |
307 return rqlstr |
308 |
308 |
309 # client helper methods ################################################### |
309 # client helper methods ################################################### |
310 |
310 |
311 def entities(self, col=0): |
311 def entities(self, col=0): |
312 """iter on entities with eid in the `col` column of the result set""" |
312 """iter on entities with eid in the `col` column of the result set""" |
313 for i in xrange(len(self)): |
313 for i in xrange(len(self)): |
318 |
318 |
319 @cached |
319 @cached |
320 def get_entity(self, row, col=None): |
320 def get_entity(self, row, col=None): |
321 """special method for query retreiving a single entity, returns a |
321 """special method for query retreiving a single entity, returns a |
322 partially initialized Entity instance. |
322 partially initialized Entity instance. |
323 |
323 |
324 WARNING: due to the cache wrapping this function, you should NEVER |
324 WARNING: due to the cache wrapping this function, you should NEVER |
325 give row as a named parameter (i.e. rset.get_entity(req, 0) |
325 give row as a named parameter (i.e. rset.get_entity(req, 0) |
326 is OK but rset.get_entity(row=0, req=req) isn't |
326 is OK but rset.get_entity(row=0, req=req) isn't |
327 |
327 |
328 :type row,col: int, int |
328 :type row,col: int, int |
349 """internal method to get a single entity, returns a |
349 """internal method to get a single entity, returns a |
350 partially initialized Entity instance. |
350 partially initialized Entity instance. |
351 |
351 |
352 partially means that only attributes selected in the RQL |
352 partially means that only attributes selected in the RQL |
353 query will be directly assigned to the entity. |
353 query will be directly assigned to the entity. |
354 |
354 |
355 :type row,col: int, int |
355 :type row,col: int, int |
356 :param row,col: |
356 :param row,col: |
357 row and col numbers localizing the entity among the result's table |
357 row and col numbers localizing the entity among the result's table |
358 |
358 |
359 :return: the partially initialized `Entity` instance |
359 :return: the partially initialized `Entity` instance |
425 entity.set_related_cache(attr, x, rrset) |
425 entity.set_related_cache(attr, x, rrset) |
426 return entity |
426 return entity |
427 |
427 |
428 @cached |
428 @cached |
429 def syntax_tree(self): |
429 def syntax_tree(self): |
430 """get the syntax tree for the source query. |
430 """get the syntax tree for the source query. |
431 |
431 |
432 :rtype: rql.stmts.Statement |
432 :rtype: rql.stmts.Statement |
433 :return: the RQL syntax tree of the originating query |
433 :return: the RQL syntax tree of the originating query |
434 """ |
434 """ |
435 if self._rqlst: |
435 if self._rqlst: |
439 rqlst.schema = self.vreg.schema |
439 rqlst.schema = self.vreg.schema |
440 self.vreg.rqlhelper.annotate(rqlst) |
440 self.vreg.rqlhelper.annotate(rqlst) |
441 else: |
441 else: |
442 rqlst = self.vreg.parse(self.req, self.rql, self.args) |
442 rqlst = self.vreg.parse(self.req, self.rql, self.args) |
443 return rqlst |
443 return rqlst |
444 |
444 |
445 @cached |
445 @cached |
446 def column_types(self, col): |
446 def column_types(self, col): |
447 """return the list of different types in the column with the given col |
447 """return the list of different types in the column with the given col |
448 index default to 0 (ie the first column) |
448 index default to 0 (ie the first column) |
449 |
449 |
450 :type col: int |
450 :type col: int |
451 :param col: the index of the desired column |
451 :param col: the index of the desired column |
452 |
452 |
453 :rtype: list |
453 :rtype: list |
454 :return: the different entities type found in the column |
454 :return: the different entities type found in the column |
481 locate_query_col = col |
481 locate_query_col = col |
482 rqlst = self.syntax_tree() |
482 rqlst = self.syntax_tree() |
483 etype = self.description[row][col] |
483 etype = self.description[row][col] |
484 if self.vreg.schema.eschema(etype).is_final(): |
484 if self.vreg.schema.eschema(etype).is_final(): |
485 # final type, find a better one to locate the correct subquery |
485 # final type, find a better one to locate the correct subquery |
486 # (ambiguous if possible) |
486 # (ambiguous if possible) |
487 for i in xrange(len(rqlst.children[0].selection)): |
487 for i in xrange(len(rqlst.children[0].selection)): |
488 if i == col: |
488 if i == col: |
489 continue |
489 continue |
490 coletype = self.description[row][i] |
490 coletype = self.description[row][i] |
491 if coletype is None: |
491 if coletype is None: |
520 for rel in rqlst.iget_nodes(nodes.Relation): |
520 for rel in rqlst.iget_nodes(nodes.Relation): |
521 if rel.r_type == 'has_text': |
521 if rel.r_type == 'has_text': |
522 __, rhs = rel.get_variable_parts() |
522 __, rhs = rel.get_variable_parts() |
523 return rhs.eval(self.args) |
523 return rhs.eval(self.args) |
524 return None |
524 return None |
525 |
525 |
526 |
526 |
527 def attr_desc_iterator(rqlst, index=0): |
527 def attr_desc_iterator(rqlst, index=0): |
528 """return an iterator on a list of 2-uple (index, attr_relation) |
528 """return an iterator on a list of 2-uple (index, attr_relation) |
529 localizing attribute relations of the main variable in a result's row |
529 localizing attribute relations of the main variable in a result's row |
530 |
530 |