schema.py
changeset 9967 e65873ad0371
parent 9966 6c2d57d1b6de
child 9974 b240b33c7125
--- a/schema.py	Mon Apr 28 14:11:23 2014 +0200
+++ b/schema.py	Tue Sep 16 16:39:23 2014 +0200
@@ -144,6 +144,44 @@
     return u', '.join(' '.join(expr.split()) for expr in rqlstring.split(','))
 
 
+def _check_valid_formula(rdef, formula_rqlst):
+    """Check the formula is a valid RQL query with some restriction (no union,
+    single selected node, etc.), raise BadSchemaDefinition if not
+    """
+    if len(formula_rqlst.children) != 1:
+        raise BadSchemaDefinition('computed attribute %(attr)s on %(etype)s: '
+                                  'can not use UNION in formula %(form)r' %
+                                  {'attr' : rdef.rtype,
+                                   'etype' : rdef.subject.type,
+                                   'form' : rdef.formula})
+    select = formula_rqlst.children[0]
+    if len(select.selection) != 1:
+        raise BadSchemaDefinition('computed attribute %(attr)s on %(etype)s: '
+                                  'can only select one term in formula %(form)r' %
+                                  {'attr' : rdef.rtype,
+                                   'etype' : rdef.subject.type,
+                                   '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)r' %
+                                  {'attr' : rdef.rtype,
+                                   'etype' : rdef.subject.type,
+                                   '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)' %
+                                  {'attr' : rdef.rtype,
+                                   'etype' : rdef.subject.type,
+                                   'comp_type' : computed_type,
+                                   'attr_type' : expected_type})
+
+
 class RQLExpression(object):
     """Base class for RQL expression used in schema (constraints and
     permissions)
@@ -1010,6 +1048,12 @@
     def schema_by_eid(self, eid):
         return self._eid_index[eid]
 
+    def iter_computed_attributes(self):
+        for relation in self.relations():
+            for rdef in relation.rdefs.itervalues():
+                if rdef.final and rdef.formula is not None:
+                    yield rdef
+
     def iter_computed_relations(self):
         for relation in self.relations():
             if relation.rule:
@@ -1021,51 +1065,17 @@
         self.finalize_computed_relations()
 
     def finalize_computed_attributes(self):
-        """Check consistency of computed attributes types"""
+        """Check computed attributes validity (if any), else raise
+        `BadSchemaDefinition`
+        """
         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))
+        for rdef in self.iter_computed_attributes():
+            rqlst = parse(rdef.formula)
+            select = rqlst.children[0]
+            analyzer.visit(select)
+            _check_valid_formula(rdef, rqlst)
+            rdef.formula_select = select # avoid later recomputation
+
 
     def finalize_computed_relations(self):
         """Build relation definitions for computed relations
@@ -1353,6 +1363,7 @@
     # only defining here to prevent pylint from complaining
     info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
 
+
 set_log_methods(CubicWebSchemaLoader, getLogger('cubicweb.schemaloader'))
 set_log_methods(BootstrapSchemaLoader, getLogger('cubicweb.bootstrapschemaloader'))
 set_log_methods(RQLExpression, getLogger('cubicweb.schema'))