[entity fetch_attrs] remove ambiguous relations on different etypes from fetched attrs or it may produce wrong related results; closes #1700896 stable
authorFlorent Cayré <florent.cayre@gmail.com>
Wed, 25 May 2011 08:35:20 +0200
branchstable
changeset 7425 7e9d1d6fcba7
parent 7424 2c72bfbbf1a3
child 7426 254bc099db1a
[entity fetch_attrs] remove ambiguous relations on different etypes from fetched attrs or it may produce wrong related results; closes #1700896
entity.py
test/data/schema.py
test/unittest_entity.py
--- a/entity.py	Tue May 24 10:33:43 2011 +0200
+++ b/entity.py	Wed May 25 08:35:20 2011 +0200
@@ -62,6 +62,23 @@
     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.
@@ -222,6 +239,7 @@
                 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)
                 else:
                     fetchattrs = etypecls.fetch_attrs
                 etypecls._fetch_restrictions(var, varmaker, fetchattrs,
@@ -739,6 +757,10 @@
         etypecls = self._cw.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 an deeper api rewrite
+            remove_ambiguous_rels(fetchattrs, targettypes, self._cw.vreg.schema)
         else:
             fetchattrs = etypecls.fetch_attrs
         rql = etypecls.fetch_rql(self._cw.user, [restriction], fetchattrs,
--- a/test/data/schema.py	Tue May 24 10:33:43 2011 +0200
+++ b/test/data/schema.py	Wed May 25 08:35:20 2011 +0200
@@ -18,9 +18,11 @@
 
 from yams.buildobjs import (EntityType, String, SubjectRelation,
                             RelationDefinition)
+
 from cubicweb.schema import (WorkflowableEntityType,
                              RQLConstraint, RQLVocabularyConstraint)
 
+
 class Personne(EntityType):
     nom = String(required=True)
     prenom = String()
@@ -36,22 +38,40 @@
             RQLVocabularyConstraint('NOT (S connait P, P nom "toto")'),
             RQLVocabularyConstraint('S travaille P, P nom "tutu"')])
 
+
 class Societe(EntityType):
     nom = String()
     evaluee = SubjectRelation('Note')
+    fournit = SubjectRelation(('Service', 'Produit'), cardinality='1*')
+
+
+class Service(EntityType):
+    fabrique_par = SubjectRelation('Personne', cardinality='1*')
+
+
+class Produit(EntityType):
+    fabrique_par = SubjectRelation('Usine', cardinality='1*')
+
+
+class Usine(EntityType):
+    lieu = String(required=True)
+
 
 class Note(EntityType):
     type = String()
     ecrit_par = SubjectRelation('Personne')
 
+
 class SubNote(Note):
     __specializes_schema__ = True
     description = String()
 
+
 class tags(RelationDefinition):
     subject = 'Tag'
     object = ('Personne', 'Note')
 
+
 class evaluee(RelationDefinition):
     subject = 'CWUser'
     object = 'Note'
@@ -59,5 +79,3 @@
 
 class StateFull(WorkflowableEntityType):
     name = String()
-
-
--- a/test/unittest_entity.py	Tue May 24 10:33:43 2011 +0200
+++ b/test/unittest_entity.py	Wed May 25 08:35:20 2011 +0200
@@ -250,7 +250,7 @@
                           'WHERE E eid %(x)s, E tags X, X is IN (Personne), X nom AA, '
                           'X modification_date AB')
 
-    def test_related_rql_ambigous_cant_use_fetch_order(self):
+    def test_related_rql_ambiguous_cant_use_fetch_order(self):
         tag = self.vreg['etypes'].etype_class('Tag')(self.request())
         for ttype in self.schema['tags'].objects():
             self.vreg['etypes'].etype_class(ttype).fetch_attrs = ('modification_date',)
@@ -258,6 +258,18 @@
                           '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):
+        soc_etype = self.vreg['etypes'].etype_class('Societe')
+        soc = soc_etype(self.request())
+        soc_etype.fetch_attrs = ('fournit',)
+        self.vreg['etypes'].etype_class('Service').fetch_attrs = ('fabrique_par',)
+        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')
+
     def test_unrelated_rql_security_1_manager(self):
         user = self.request().user
         rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0]