[migration] Fix addition of entity type including boundary constraints on its own attributes 3.24
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 18 Jan 2017 15:04:27 +0100
branch3.24
changeset 11904 e760c54490b1
parent 11903 6f36275a6e74
child 11905 2f36115d38b1
[migration] Fix addition of entity type including boundary constraints on its own attributes This was failing because of the sequence of sql executing when adding an attribute: 1. add entity type <commit> 2. add relation type for attribute 1 <commit> 3. add attribute 1 and associated constraints <commit> etc. In the case of e.g. start/end constraint, we were trying to add the constraint before addition of the constrained attribute (e.g. add constraint on 'start' referencing 'end', but only 'start' has been added yet, not 'end'). This patch fix this by: * adding the relation type to the schema without having to commit, but keeping the operation to revert the addition if necessary - this allows to a single commit for all attributes of the entity type ; * using a LateOperation on constraint operation, so we ensure attributes are actually added before any constraint is added.
cubicweb/hooks/syncschema.py
cubicweb/server/migractions.py
cubicweb/server/test/data-migractions/migratedapp/schema.py
cubicweb/server/test/unittest_migractions.py
--- a/cubicweb/hooks/syncschema.py	Thu Jan 19 09:53:31 2017 +0100
+++ b/cubicweb/hooks/syncschema.py	Wed Jan 18 15:04:27 2017 +0100
@@ -778,7 +778,7 @@
             syssource.update_rdef_unique(self.cnx, self.rdef)
 
 
-class CWConstraintAddOp(CWConstraintDelOp):
+class CWConstraintAddOp(hook.LateOperation, CWConstraintDelOp):
     """actually update constraint of a relation definition"""
     entity = None  # make pylint happy
 
@@ -886,12 +886,10 @@
 
 
 class MemSchemaCWRTypeAdd(MemSchemaOperation):
-    """actually add the relation type to the instance's schema"""
+    """Revert addition of the relation type from the instance's schema if something goes wrong.
+    """
     rtypedef = None  # make pylint happy
 
-    def precommit_event(self):
-        self.cnx.vreg.schema.add_relation_type(self.rtypedef)
-
     def revertprecommit_event(self):
         self.cnx.vreg.schema.del_relation_type(self.rtypedef.name)
 
@@ -1101,6 +1099,10 @@
 
     def __call__(self):
         rtypedef = self.rtype_def()
+        # modify the instance's schema now since we'll usually need the type definition to do
+        # further thing (e.g. add relation def of this type) but register and operation to revert
+        # this if necessary
+        self._cw.vreg.schema.add_relation_type(rtypedef)
         MemSchemaCWRTypeAdd(self._cw, rtypedef=rtypedef)
 
     def rtype_def(self):
--- a/cubicweb/server/migractions.py	Thu Jan 19 09:53:31 2017 +0100
+++ b/cubicweb/server/migractions.py	Wed Jan 18 15:04:27 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -814,12 +814,12 @@
             if attrschema.type not in instschema:
                 self.cmd_add_entity_type(attrschema.type, False, False)
             if rschema.type not in instschema:
-                # need to add the relation type and to commit to get it
-                # actually in the schema
-                self.cmd_add_relation_type(rschema.type, False, commit=True)
+                # need to add the relation type
+                self.cmd_add_relation_type(rschema.type, False, commit=False)
             # register relation definition
             rdef = self._get_rdef(rschema, eschema, eschema.destination(rschema))
             ss.execschemarql(execute, rdef, ss.rdef2rql(rdef, cstrtypemap, groupmap),)
+        self.commit()
         # take care to newly introduced base class
         # XXX some part of this should probably be under the "if auto" block
         for spschema in eschema.specialized_by(recursive=False):
--- a/cubicweb/server/test/data-migractions/migratedapp/schema.py	Thu Jan 19 09:53:31 2017 +0100
+++ b/cubicweb/server/test/data-migractions/migratedapp/schema.py	Wed Jan 18 15:04:27 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -20,7 +20,7 @@
 from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
                             SubjectRelation, Bytes,
                             RichString, String, Int, Boolean, Datetime, Date, Float)
-from yams.constraints import SizeConstraint, UniqueConstraint
+from yams.constraints import SizeConstraint, UniqueConstraint, BoundaryConstraint, Attribute
 from cubicweb import _
 from cubicweb.schema import (WorkflowableEntityType, RQLConstraint,
                              RQLVocabularyConstraint,
@@ -216,3 +216,10 @@
     subject = object = 'Folder2'
     inlined = True
     cardinality = '??'
+
+
+class Activity(EntityType):
+    start = Datetime(constraints=[BoundaryConstraint('<=', Attribute('end'))],
+                     description=_('when the activity started'))
+    end = Datetime(constraints=[BoundaryConstraint('>=', Attribute('start'))],
+                   description=_('when the activity ended'))
--- a/cubicweb/server/test/unittest_migractions.py	Thu Jan 19 09:53:31 2017 +0100
+++ b/cubicweb/server/test/unittest_migractions.py	Wed Jan 18 15:04:27 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -313,6 +313,12 @@
             for cstr in eschema.rdef('name').constraints:
                 self.assertTrue(hasattr(cstr, 'eid'))
 
+    def test_add_entity_type_with_constraint(self):
+        with self.mh() as (cnx, mh):
+            mh.cmd_add_entity_type('Activity')
+            constraints = self.table_constraints(mh, 'cw_Activity')
+            self.assertEqual(len(constraints), 2, constraints)
+
     def test_add_cube_with_custom_final_type(self):
         with self.mh() as (cnx, mh):
             try: