diff -r 8968c50818db -r 43c14e0e8972 rset.py --- a/rset.py Fri Feb 12 12:57:14 2010 +0100 +++ b/rset.py Fri Feb 12 12:57:56 2010 +0100 @@ -9,7 +9,7 @@ from logilab.common.decorators import cached, clear_cache, copy_cache -from rql import nodes +from rql import nodes, stmts from cubicweb import NotAnEntity @@ -83,7 +83,7 @@ try: return self._rsetactions[key] except KeyError: - actions = self.vreg['actions'].possible_vobjects( + actions = self.vreg['actions'].poss_visible_objects( self.req, rset=self, **kwargs) self._rsetactions[key] = actions return actions @@ -243,8 +243,6 @@ rset = mapping[key] rset.rows.append(self.rows[idx]) rset.description.append(self.description[idx]) - - for rset in result: rset.rowcount = len(rset.rows) if return_dict: @@ -252,6 +250,51 @@ else: return result + def limited_rql(self): + """return a printable rql for the result set associated to the object, + with limit/offset correctly set according to maximum page size and + currently displayed page when necessary + """ + # try to get page boundaries from the navigation component + # XXX we should probably not have a ref to this component here (eg in + # cubicweb) + nav = self.vreg['components'].select_or_none('navigation', self.req, + rset=self) + if nav: + start, stop = nav.page_boundaries() + rql = self._limit_offset_rql(stop - start, start) + # result set may have be limited manually in which case navigation won't + # apply + elif self.limited: + rql = self._limit_offset_rql(*self.limited) + # navigation component doesn't apply and rset has not been limited, no + # need to limit query + else: + rql = self.printable_rql() + return rql + + def _limit_offset_rql(self, limit, offset): + rqlst = self.syntax_tree() + if len(rqlst.children) == 1: + select = rqlst.children[0] + olimit, ooffset = select.limit, select.offset + select.limit, select.offset = limit, offset + rql = rqlst.as_string(kwargs=self.args) + # restore original limit/offset + select.limit, select.offset = olimit, ooffset + else: + newselect = stmts.Select() + newselect.limit = limit + newselect.offset = offset + aliases = [nodes.VariableRef(newselect.get_variable(vref.name, i)) + for i, vref in enumerate(rqlst.selection)] + newselect.set_with([nodes.SubQuery(aliases, rqlst)], check=False) + newunion = stmts.Union() + newunion.append(newselect) + rql = rqlst.as_string(kwargs=self.args) + rqlst.parent = None + return rql + def limit(self, limit, offset=0, inplace=False): """limit the result set to the given number of rows optionaly starting from an index different than 0 @@ -282,9 +325,9 @@ # we also have to fix/remove from the request entity cache entities # which get a wrong rset reference by this limit call for entity in self.req.cached_entities(): - if entity.rset is self: - if offset <= entity.row < stop: - entity.row = entity.row - offset + if entity.cw_rset is self: + if offset <= entity.cw_row < stop: + entity.cw_row = entity.cw_row - offset else: self.req.drop_entity_cache(entity.eid) else: @@ -321,8 +364,16 @@ if self.rows[i][col] is not None: yield self.get_entity(i, col) + def complete_entity(self, row, col=0, skip_bytes=True): + """short cut to get an completed entity instance for a particular + row (all instance's attributes have been fetched) + """ + entity = self.get_entity(row, col) + entity.complete(skip_bytes=skip_bytes) + return entity + @cached - def get_entity(self, row, col=None): + def get_entity(self, row, col): """special method for query retreiving a single entity, returns a partially initialized Entity instance. @@ -336,11 +387,6 @@ :return: the partially initialized `Entity` instance """ - if col is None: - from warnings import warn - msg = 'col parameter will become mandatory in future version' - warn(msg, DeprecationWarning, stacklevel=3) - col = 0 etype = self.description[row][col] try: eschema = self.vreg.schema.eschema(etype) @@ -374,16 +420,17 @@ # new attributes found in this resultset ? try: entity = req.entity_cache(eid) - if entity.rset is None: - # entity has no rset set, this means entity has been cached by - # the repository (req is a repository session) which had no rset - # info. Add id. - entity.rset = self - entity.row = row - entity.col = col - return entity except KeyError: pass + else: + if entity.cw_rset is None: + # entity has no rset set, this means entity has been created by + # the querier (req is a repository session) and so jas no rset + # info. Add it. + entity.cw_rset = self + entity.cw_row = row + entity.cw_col = col + return entity # build entity instance etype = self.description[row][col] entity = self.vreg['etypes'].etype_class(etype)(req, rset=self, @@ -403,25 +450,22 @@ select = rqlst # take care, due to outer join support, we may find None # values for non final relation - for i, attr, x in attr_desc_iterator(select, col): + for i, attr, role in attr_desc_iterator(select, col): outerselidx = rqlst.subquery_selection_index(select, i) if outerselidx is None: continue - if x == 'subject': + if role == 'subject': rschema = eschema.subjrels[attr] if rschema.final: entity[attr] = rowvalues[outerselidx] continue - tetype = rschema.objects(etype)[0] - card = rschema.rproperty(etype, tetype, 'cardinality')[0] else: rschema = eschema.objrels[attr] - tetype = rschema.subjects(etype)[0] - card = rschema.rproperty(tetype, etype, 'cardinality')[1] + rdef = eschema.rdef(attr, role) # only keep value if it can't be multivalued - if card in '1?': + if rdef.role_cardinality(role) in '1?': if rowvalues[outerselidx] is None: - if x == 'subject': + if role == 'subject': rql = 'Any Y WHERE X %s Y, X eid %s' else: rql = 'Any Y WHERE Y %s X, X eid %s' @@ -429,7 +473,7 @@ req.decorate_rset(rrset) else: rrset = self._build_entity(row, outerselidx).as_rset() - entity.set_related_cache(attr, x, rrset) + entity.set_related_cache(attr, role, rrset) return entity @cached @@ -481,26 +525,45 @@ result[-1][1] = i return result + def _locate_query_params(self, rqlst, row, col): + locate_query_col = col + etype = self.description[row][col] + # final type, find a better one to locate the correct subquery + # (ambiguous if possible) + eschema = self.vreg.schema.eschema + if eschema(etype).final: + for select in rqlst.children: + try: + myvar = select.selection[col].variable + except AttributeError: + # not a variable + continue + for i in xrange(len(select.selection)): + if i == col: + continue + coletype = self.description[row][i] + # None description possible on column resulting from an outer join + if coletype is None or eschema(coletype).final: + continue + try: + ivar = select.selection[i].variable + except AttributeError: + # not a variable + continue + # check variables don't comes from a subquery or are both + # coming from the same subquery + if getattr(ivar, 'query', None) is getattr(myvar, 'query', None): + etype = coletype + locate_query_col = i + if len(self.column_types(i)) > 1: + return etype, locate_query_col + return etype, locate_query_col + @cached def related_entity(self, row, col): """try to get the related entity to extract format information if any""" - locate_query_col = col rqlst = self.syntax_tree() - etype = self.description[row][col] - if self.vreg.schema.eschema(etype).final: - # final type, find a better one to locate the correct subquery - # (ambiguous if possible) - for i in xrange(len(rqlst.children[0].selection)): - if i == col: - continue - coletype = self.description[row][i] - if coletype is None: - continue - if not self.vreg.schema.eschema(coletype).final: - etype = coletype - locate_query_col = i - if len(self.column_types(i)) > 1: - break + etype, locate_query_col = self._locate_query_params(rqlst, row, col) # UNION query, find the subquery from which this entity has been found select = rqlst.locate_subquery(locate_query_col, etype, self.args)[0] col = rqlst.subquery_selection_index(select, col)