[CWEP002 migration] support add_relation_type/add_attribute for computed attributes
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 28 Aug 2014 18:31:18 +0200
changeset 9969 0f64ef873f7a
parent 9968 50f046bf0e50
child 9970 671bbfed459b
[CWEP002 migration] support add_relation_type/add_attribute for computed attributes Related to #3546717.
hooks/syncschema.py
server/test/datacomputed/migratedapp/schema.py
server/test/datacomputed/schema.py
server/test/unittest_migractions.py
--- a/hooks/syncschema.py	Tue Sep 16 15:28:35 2014 +0200
+++ b/hooks/syncschema.py	Thu Aug 28 18:31:18 2014 +0200
@@ -39,6 +39,7 @@
                              CONSTRAINTS, ETYPE_NAME_MAP, display_name)
 from cubicweb.server import hook, schemaserial as ss
 from cubicweb.server.sqlutils import SQL_PREFIX
+from cubicweb.hooks.synccomputed import RecomputeAttributeOperation
 
 # core entity and relation types which can't be removed
 CORE_TYPES = BASE_TYPES | SCHEMA_TYPES | META_RTYPES | set(
@@ -71,14 +72,14 @@
     table = SQL_PREFIX + etype
     column = SQL_PREFIX + rtype
     try:
-        cnx.system_sql(str('ALTER TABLE %s ADD %s integer'
-                               % (table, column)), rollback_on_failure=False)
+        cnx.system_sql(str('ALTER TABLE %s ADD %s integer' % (table, column)),
+                       rollback_on_failure=False)
         cnx.info('added column %s to table %s', column, table)
     except Exception:
         # silent exception here, if this error has not been raised because the
         # column already exists, index creation will fail anyway
         cnx.exception('error while adding column %s to table %s',
-                          table, column)
+                      table, column)
     # create index before alter table which may expectingly fail during test
     # (sqlite) while index creation should never fail (test for index existence
     # is done by the dbhelper)
@@ -167,8 +168,8 @@
         # drop index if any
         source.drop_index(cnx, table, column)
         if source.dbhelper.alter_column_support:
-            cnx.system_sql('ALTER TABLE %s DROP COLUMN %s'
-                               % (table, column), rollback_on_failure=False)
+            cnx.system_sql('ALTER TABLE %s DROP COLUMN %s' % (table, column),
+                           rollback_on_failure=False)
             self.info('dropped column %s from table %s', column, table)
         else:
             # not supported by sqlite for instance
@@ -471,8 +472,8 @@
         column = SQL_PREFIX + rdefdef.name
         try:
             cnx.system_sql(str('ALTER TABLE %s ADD %s %s'
-                                   % (table, column, attrtype)),
-                               rollback_on_failure=False)
+                               % (table, column, attrtype)),
+                           rollback_on_failure=False)
             self.info('added column %s to table %s', table, column)
         except Exception as ex:
             # the column probably already exists. this occurs when
@@ -503,6 +504,12 @@
             default = convert_default_value(self.rdefdef, default)
             cnx.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
                                {'default': default})
+        # if attribute is computed, compute it
+        if entity.formula:
+            # add rtype attribute for RelationDefinitionSchema api compat, this
+            # is what RecomputeAttributeOperation expect
+            rdefdef.rtype = rdefdef.name
+            RecomputeAttributeOperation.get_instance(cnx).add_data(rdefdef)
 
     def revertprecommit_event(self):
         # revert changes on in memory schema
--- a/server/test/datacomputed/migratedapp/schema.py	Tue Sep 16 15:28:35 2014 +0200
+++ b/server/test/datacomputed/migratedapp/schema.py	Thu Aug 28 18:31:18 2014 +0200
@@ -16,7 +16,8 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
-from yams.buildobjs import EntityType, RelationDefinition, ComputedRelation
+from yams.buildobjs import (EntityType, RelationDefinition, ComputedRelation,
+                            Int, Float)
 
 
 class Employee(EntityType):
@@ -38,11 +39,11 @@
 
 
 class Company(EntityType):
-    pass
+    score = Float(formula='Any AVG(NN) WHERE X employees E, N concerns E, N note NN')
 
 
 class Note(EntityType):
-    pass
+    note = Int()
 
 
 class concerns(RelationDefinition):
--- a/server/test/datacomputed/schema.py	Tue Sep 16 15:28:35 2014 +0200
+++ b/server/test/datacomputed/schema.py	Thu Aug 28 18:31:18 2014 +0200
@@ -16,7 +16,7 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
-from yams.buildobjs import EntityType, RelationDefinition, ComputedRelation
+from yams.buildobjs import EntityType, RelationDefinition, ComputedRelation, Int
 
 
 class Employee(EntityType):
@@ -36,9 +36,8 @@
 class Company(EntityType):
     pass
 
-
 class Note(EntityType):
-    pass
+    note = Int()
 
 
 class concerns(RelationDefinition):
--- a/server/test/unittest_migractions.py	Tue Sep 16 15:28:35 2014 +0200
+++ b/server/test/unittest_migractions.py	Thu Aug 28 18:31:18 2014 +0200
@@ -691,6 +691,12 @@
     """
     appid = 'datacomputed'
 
+    def setUp(self):
+        MigrationTC.setUp(self)
+        # ensure vregistry is reloaded, needed by generated hooks for computed
+        # attributes
+        self.repo.vreg.set_schema(self.repo.schema)
+
     def test_computed_relation_add_relation_definition(self):
         self.assertNotIn('works_for', self.schema)
         with self.mh() as (cnx, mh):
@@ -755,6 +761,44 @@
                          'Cannot synchronize a relation definition for a computed '
                          'relation (whatever)')
 
+    # computed attributes migration ############################################
+
+    def setup_add_score(self):
+        with self.admin_access.client_cnx() as cnx:
+            assert not cnx.execute('Company X')
+            c = cnx.create_entity('Company')
+            e1 = cnx.create_entity('Employee', reverse_employees=c)
+            n1 = cnx.create_entity('Note', note=2, concerns=e1)
+            e2 = cnx.create_entity('Employee', reverse_employees=c)
+            n2 = cnx.create_entity('Note', note=4, concerns=e2)
+            cnx.commit()
+
+    def assert_score_initialized(self, mh):
+        self.assertEqual(self.schema['score'].rdefs['Company', 'Float'].formula,
+                         'Any AVG(NN) WHERE X employees E, N concerns E, N note NN')
+        fields = self.table_schema(mh, '%sCompany' % SQL_PREFIX)
+        self.assertEqual(fields['%sscore' % SQL_PREFIX], 'float')
+        self.assertEqual([[3.0]],
+                         mh.rqlexec('Any CS WHERE C score CS, C is Company').rows)
+
+    def test_computed_attribute_add_relation_type(self):
+        self.assertNotIn('score', self.schema)
+        self.setup_add_score()
+        with self.mh() as (cnx, mh):
+            mh.cmd_add_relation_type('score')
+            self.assertIn('score', self.schema)
+            self.assertEqual(self.schema['score'].objects(), ('Float',))
+            self.assertEqual(self.schema['score'].subjects(), ('Company',))
+            self.assert_score_initialized(mh)
+
+    def test_computed_attribute_add_attribute(self):
+        self.assertNotIn('score', self.schema)
+        self.setup_add_score()
+        with self.mh() as (cnx, mh):
+            mh.cmd_add_attribute('Company', 'score')
+            self.assertIn('score', self.schema)
+            self.assert_score_initialized(mh)
+
 
 if __name__ == '__main__':
     unittest_main()