[entity fetchattrs] also fetch ambiguous rtypes even if we do not recurse on them (closes #1720823)
authorFlorent Cayré <florent.cayre@gmail.com>
Fri, 16 Sep 2011 10:36:46 +0200
changeset 7798 8930f7a284dd
parent 7797 a71618a75b53
child 7800 ea496a3ed703
[entity fetchattrs] also fetch ambiguous rtypes even if we do not recurse on them (closes #1720823)
entity.py
test/unittest_entity.py
--- a/entity.py	Thu Sep 15 18:39:55 2011 +0200
+++ b/entity.py	Fri Sep 16 10:36:46 2011 +0200
@@ -65,23 +65,6 @@
     return True
 
 
-def remove_ambiguous_rels(attr_set, subjtypes, schema):
-    '''remove from `attr_set` the relations of entity types `subjtypes` that have
-    different entity type sets as target'''
-    for attr in attr_set.copy():
-        rschema = schema.rschema(attr)
-        if rschema.final:
-            continue
-        ttypes = None
-        for subjtype in subjtypes:
-            cur_ttypes = rschema.objects(subjtype)
-            if ttypes is None:
-                ttypes = cur_ttypes
-            elif cur_ttypes != ttypes:
-                attr_set.remove(attr)
-                break
-
-
 class Entity(AppObject):
     """an entity instance has e_schema automagically set on
     the class and instances has access to their issuing cursor.
@@ -215,6 +198,35 @@
         return select
 
     @classmethod
+    def _fetch_ambiguous_rtypes(cls, select, var, fetchattrs, subjtypes, schema):
+        """find rtypes in `fetchattrs` that relate different subject etypes
+        taken from (`subjtypes`) to different target etypes; these so called
+        "ambiguous" relations, are added directly to the `select` syntax tree
+        selection but removed from `fetchattrs` to avoid the fetch recursion
+        because we have to choose only one targettype for the recursion and
+        adding its own fetch attrs to the selection -when we recurse- would
+        filter out the other possible target types from the result set
+        """
+        for attr in fetchattrs.copy():
+            rschema = schema.rschema(attr)
+            if rschema.final:
+                continue
+            ttypes = None
+            for subjtype in subjtypes:
+                cur_ttypes = set(rschema.objects(subjtype))
+                if ttypes is None:
+                    ttypes = cur_ttypes
+                elif cur_ttypes != ttypes:
+                    # we found an ambiguous relation: remove it from fetchattrs
+                    fetchattrs.remove(attr)
+                    # ... and add it to the selection
+                    targetvar = select.make_variable()
+                    select.add_selected(targetvar)
+                    rel = make_relation(var, attr, (targetvar,), VariableRef)
+                    select.add_restriction(rel)
+                    break
+
+    @classmethod
     def _fetch_restrictions(cls, mainvar, select, fetchattrs,
                             user, ordermethod='fetch_order', visited=None):
         eschema = cls.e_schema
@@ -252,12 +264,14 @@
                 # later information here, systematically add it.
                 rel.change_optional('right')
                 targettypes = rschema.objects(eschema.type)
-                # XXX user._cw.vreg iiiirk
-                etypecls = user._cw.vreg['etypes'].etype_class(targettypes[0])
+                vreg = user._cw.vreg # XXX user._cw.vreg iiiirk
+                etypecls = vreg['etypes'].etype_class(targettypes[0])
                 if len(targettypes) > 1:
                     # find fetch_attrs common to all destination types
-                    fetchattrs = user._cw.vreg['etypes'].fetch_attrs(targettypes)
-                    remove_ambiguous_rels(fetchattrs, targettypes, user._cw.vreg.schema)
+                    fetchattrs = vreg['etypes'].fetch_attrs(targettypes)
+                    # .. and handle ambiguous relations
+                    cls._fetch_ambiguous_rtypes(select, var, fetchattrs,
+                                                targettypes, vreg.schema)
                 else:
                     fetchattrs = etypecls.fetch_attrs
                 etypecls._fetch_restrictions(var, select, fetchattrs,
@@ -772,7 +786,8 @@
         return self.related(rtype, role, limit, entities)
 
     def cw_related_rql(self, rtype, role='subject', targettypes=None):
-        rschema = self._cw.vreg.schema[rtype]
+        vreg = self._cw.vreg
+        rschema = vreg.schema[rtype]
         select = Select()
         mainvar, evar = select.get_variable('X'), select.get_variable('E')
         select.add_selected(mainvar)
@@ -795,13 +810,11 @@
                 select.add_constant_restriction(mainvar, 'is', targettypes,
                                                 'String')
             gcard = greater_card(rschema, targettypes, (self.e_schema,), 1)
-        etypecls = self._cw.vreg['etypes'].etype_class(targettypes[0])
+        etypecls = vreg['etypes'].etype_class(targettypes[0])
         if len(targettypes) > 1:
-            fetchattrs = self._cw.vreg['etypes'].fetch_attrs(targettypes)
-            # XXX we should fetch ambiguous relation objects too but not
-            # recurse on them in _fetch_restrictions; it is easier to remove
-            # them completely for now, as it would require a deeper api rewrite
-            remove_ambiguous_rels(fetchattrs, targettypes, self._cw.vreg.schema)
+            fetchattrs = vreg['etypes'].fetch_attrs(targettypes)
+            self._fetch_ambiguous_rtypes(select, mainvar, fetchattrs,
+                                         targettypes, vreg.schema)
         else:
             fetchattrs = etypecls.fetch_attrs
         etypecls.fetch_rqlst(self._cw.user, select, mainvar, fetchattrs,
--- a/test/unittest_entity.py	Thu Sep 15 18:39:55 2011 +0200
+++ b/test/unittest_entity.py	Fri Sep 16 10:36:46 2011 +0200
@@ -264,7 +264,7 @@
                           'Any X,AA ORDERBY AA DESC '
                           'WHERE E eid %(x)s, E tags X, X modification_date AA')
 
-    def test_related_rql_cant_fetch_ambiguous_rtype(self):
+    def test_related_rql_fetch_ambiguous_rtype(self):
         soc_etype = self.vreg['etypes'].etype_class('Societe')
         soc = soc_etype(self.request())
         soc_etype.fetch_attrs = ('fournit',)
@@ -272,9 +272,8 @@
         self.vreg['etypes'].etype_class('Produit').fetch_attrs = ('fabrique_par',)
         self.vreg['etypes'].etype_class('Usine').fetch_attrs = ('lieu',)
         self.vreg['etypes'].etype_class('Personne').fetch_attrs = ('nom',)
-        # XXX should be improved: we could fetch fabrique_par object too
         self.assertEqual(soc.cw_related_rql('fournit', 'subject'),
-                         'Any X WHERE E eid %(x)s, E fournit X')
+                         'Any X,A WHERE E eid %(x)s, E fournit X, X fabrique_par A')
 
     def test_unrelated_rql_security_1_manager(self):
         user = self.request().user