rset.py
brancholdstable
changeset 4985 02b52bf9f5f8
parent 4939 349af486f5ed
child 4951 7dc54e12c606
--- a/rset.py	Fri Feb 12 15:18:00 2010 +0100
+++ b/rset.py	Wed Mar 24 10:23:31 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,53 @@
         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(chr(65+i), i))
+                       for i in xrange(len(rqlst.children[0].selection))]
+            for vref in aliases:
+                newselect.append_selected(nodes.VariableRef(vref.variable))
+            newselect.set_with([nodes.SubQuery(aliases, rqlst)], check=False)
+            newunion = stmts.Union()
+            newunion.append(newselect)
+            rql = newunion.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 +327,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 +366,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 +389,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 +422,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 +452,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 +475,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 +527,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)