[CWEP002] Add schema finalization checks for computed attributes
authorLaura Médioni <laura.medioni@logilab.fr>
Mon, 28 Apr 2014 14:11:23 +0200
changeset 9966 6c2d57d1b6de
parent 9965 a8769b752299
child 9967 e65873ad0371
[CWEP002] Add schema finalization checks for computed attributes ``finalize_computed_attributes`` essentially checks that computed attributes types match with the type declaration of the attribute. Related to #3546717.
schema.py
test/unittest_schema.py
--- a/schema.py	Mon Jun 16 10:08:32 2014 +0200
+++ b/schema.py	Mon Apr 28 14:11:23 2014 +0200
@@ -1017,8 +1017,56 @@
 
     def finalize(self):
         super(CubicWebSchema, self).finalize()
+        self.finalize_computed_attributes()
         self.finalize_computed_relations()
 
+    def finalize_computed_attributes(self):
+        """Check consistency of computed attributes types"""
+        analyzer = ETypeResolver(self)
+        for relation in self.relations():
+            for rdef in relation.rdefs.itervalues():
+                if rdef.final and rdef.formula is not None:
+                    computed_etype = rdef.subject.type
+                    computed_attr = rdef.rtype
+                    rqlst = parse(rdef.formula)
+                    if len(rqlst.children) != 1:
+                        raise BadSchemaDefinition(
+                            'computed attribute %(attr)s on %(etype)s: '
+                            'can not use UNION in formula %(form)r' %
+                            dict(attr=computed_attr,
+                                 etype=computed_etype,
+                                 form=rdef.formula))
+                    select = rqlst.children[0]
+                    analyzer.visit(select)
+                    if len(select.selection) != 1:
+                        raise BadSchemaDefinition(
+                            'computed attribute %(attr)s on %(etype)s: '
+                            'can only select one term in formula %(form)r' %
+                            dict(attr=computed_attr,
+                                 etype=computed_etype,
+                                 form=rdef.formula))
+                    term = select.selection[0]
+                    types = set(term.get_type(sol) for sol in select.solutions)
+                    if len(types) != 1:
+                        raise BadSchemaDefinition(
+                            'computed attribute %(attr)s on %(etype)s: '
+                            'multiple possible types (%(types)s) for formula %(form)s' %
+                            dict(attr=computed_attr,
+                                 etype=computed_etype,
+                                 types=list(types),
+                                 form=rdef.formula))
+                    computed_type = types.pop()
+                    expected_type = rdef.object.type
+                    if computed_type != expected_type:
+                        raise BadSchemaDefinition(
+                            'computed attribute %(attr)s on %(etype)s: '
+                            'computed attribute type (%(comp_type)s) mismatch with '
+                            'specified type (%(attr_type)s)' %
+                            dict(attr=computed_attr,
+                                 etype=computed_etype,
+                                 comp_type=computed_type,
+                                 attr_type=expected_type))
+
     def finalize_computed_relations(self):
         """Build relation definitions for computed relations
 
--- a/test/unittest_schema.py	Mon Jun 16 10:08:32 2014 +0200
+++ b/test/unittest_schema.py	Mon Apr 28 14:11:23 2014 +0200
@@ -27,7 +27,7 @@
 from yams import ValidationError, BadSchemaDefinition
 from yams.constraints import SizeConstraint, StaticVocabularyConstraint
 from yams.buildobjs import (RelationDefinition, EntityType, RelationType,
-                            String, SubjectRelation, ComputedRelation)
+                            Int, String, SubjectRelation, ComputedRelation)
 from yams.reader import fill_schema
 
 from cubicweb.schema import (
@@ -284,6 +284,33 @@
                           'add': ('managers',),
                           'delete': ('managers',)})
 
+    def test_computed_attribute(self):
+        """Check schema finalization for computed attributes."""
+        class Person(EntityType):
+            salary = Int()
+
+        class works_for(RelationDefinition):
+            subject = 'Person'
+            object  = 'Company'
+            cardinality = '?*'
+
+        class Company(EntityType):
+            total_salary = Int(formula='Any SUM(SA) GROUPBY X WHERE '
+                                       'P works_for X, P salary SA')
+        good_schema = build_schema_from_namespace(vars().items())
+
+        class Company(EntityType):
+            total_salary = String(formula='Any SUM(SA) GROUPBY X WHERE '
+                                          'P works_for X, P salary SA')
+
+        with self.assertRaises(BadSchemaDefinition) as exc:
+            bad_schema = build_schema_from_namespace(vars().items())
+
+        self.assertEqual(str(exc.exception),
+                         'computed attribute total_salary on Company: '
+                         'computed attribute type (Int) mismatch with '
+                         'specified type (String)')
+
 
 class SchemaReaderComputedRelationAndAttributesTest(TestCase):