# HG changeset patch # User Laura Médioni # Date 1398687083 -7200 # Node ID 6c2d57d1b6de927f818aeb61dd8737801fbab5eb # Parent a8769b752299d0363e552b22995490eaa4c017cb [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. diff -r a8769b752299 -r 6c2d57d1b6de 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 diff -r a8769b752299 -r 6c2d57d1b6de test/unittest_schema.py --- 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):