# HG changeset patch # User Sylvain Thénault # Date 1409243478 -7200 # Node ID 0f64ef873f7a4c45cb920094fa942531ae6d661e # Parent 50f046bf0e50c60be6ba7125b52b7f8657eef388 [CWEP002 migration] support add_relation_type/add_attribute for computed attributes Related to #3546717. diff -r 50f046bf0e50 -r 0f64ef873f7a hooks/syncschema.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 diff -r 50f046bf0e50 -r 0f64ef873f7a server/test/datacomputed/migratedapp/schema.py --- 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 . -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): diff -r 50f046bf0e50 -r 0f64ef873f7a server/test/datacomputed/schema.py --- 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 . -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): diff -r 50f046bf0e50 -r 0f64ef873f7a server/test/unittest_migractions.py --- 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()