rset.py
branchtls-sprint
changeset 1802 d628defebc17
parent 1477 b056a49c16dc
child 1922 1a0ddb675951
equal deleted inserted replaced
1801:672acc730ce5 1802:d628defebc17
     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