[CWEP002] properly handle serialization of computed relations
We now:
* have CWComputedRelation in the bootstrap schema to store computed relations
* properly serialize/deserialize it
* test first if the database has been migrated and contains the related table
Related to #3546717
[jcr: adjust unittest_querier to pass with the added entity type]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.20.0_Any.py Thu Aug 28 18:29:14 2014 +0200
@@ -0,0 +1,1 @@
+add_relation_type('CWComputedRType')
--- a/schemas/bootstrap.py Fri Jun 27 16:11:53 2014 +0200
+++ b/schemas/bootstrap.py Thu Aug 28 18:29:14 2014 +0200
@@ -57,6 +57,16 @@
final = Boolean(description=_('automatic'))
+class CWComputedRType(EntityType):
+ """define a virtual relation type, used to build the instance schema"""
+ __permissions__ = PUB_SYSTEM_ENTITY_PERMS
+ name = String(required=True, indexed=True, internationalizable=True,
+ unique=True, maxsize=64)
+ description = RichString(internationalizable=True,
+ description=_('semantic description of this relation type'))
+ rule = String(required=True)
+
+
class CWAttribute(EntityType):
"""define a final relation: link a final relation type from a non final
entity to a final entity type.
--- a/server/schemaserial.py Fri Jun 27 16:11:53 2014 +0200
+++ b/server/schemaserial.py Thu Aug 28 18:29:14 2014 +0200
@@ -87,6 +87,20 @@
"""
repo = cnx.repo
dbhelper = repo.system_source.dbhelper
+
+ # Computed Rtype
+ with cnx.ensure_cnx_set:
+ tables = set(dbhelper.list_tables(cnx.cnxset.cu))
+ has_computed_relations = 'cw_CWComputedRType' in tables
+ if has_computed_relations:
+ rset = cnx.execute(
+ 'Any X, N, R, D WHERE X is CWComputedRType, X name N, '
+ 'X rule R, X description D')
+ for eid, rule_name, rule, description in rset.rows:
+ rtype = ybo.ComputedRelation(name=rule_name, rule=rule, eid=eid,
+ description=description)
+ schema.add_relation_type(rtype)
+
# XXX bw compat (3.6 migration)
with cnx.ensure_cnx_set:
sqlcu = cnx.system_sql("SELECT * FROM cw_CWRType WHERE cw_name='symetric'")
@@ -252,6 +266,7 @@
eschema._unique_together.append(tuple(sorted(unique_together)))
schema.infer_specialization_rules()
cnx.commit()
+ schema.finalize()
schema.reading_from_database = False
@@ -341,6 +356,9 @@
if pb is not None:
pb.update()
continue
+ if rschema.rule:
+ execschemarql(execute, rschema, crschema2rql(rschema))
+ continue
execschemarql(execute, rschema, rschema2rql(rschema, addrdef=False))
if rschema.symmetric:
rdefs = [rdef for k, rdef in rschema.rdefs.iteritems()
@@ -456,7 +474,7 @@
# rtype serialization
def rschema2rql(rschema, cstrtypemap=None, addrdef=True, groupmap=None):
- """return a list of rql insert statements to enter a relation schema
+ """generate rql insert statements to enter a relation schema
in the database as an CWRType entity
"""
if rschema.type == 'has_text':
@@ -483,10 +501,22 @@
relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
return relations, values
+def crschema2rql(crschema):
+ relations, values = crschema_relations_values(crschema)
+ yield 'INSERT CWComputedRType X: %s' % ','.join(relations), values
+
+def crschema_relations_values(crschema):
+ values = _ervalues(crschema)
+ values['rule'] = crschema.rule
+ # XXX why oh why?
+ del values['final']
+ relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
+ return relations, values
+
# rdef serialization
def rdef2rql(rdef, cstrtypemap, groupmap=None):
- # don't serialize infered relations
+ # don't serialize inferred relations
if rdef.infered:
return
relations, values = _rdef_values(rdef)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/data-cwep002/schema.py Thu Aug 28 18:29:14 2014 +0200
@@ -0,0 +1,34 @@
+# copyright 2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# 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, Int, ComputedRelation
+
+class Person(EntityType):
+ salary = Int()
+
+class works_for(RelationDefinition):
+ subject = 'Person'
+ object = 'Company'
+ cardinality = '?*'
+
+class Company(EntityType):
+ total_salary = Int()
+
+class has_employee(ComputedRelation):
+ rule = 'O works_for S'
+
--- a/server/test/unittest_querier.py Fri Jun 27 16:11:53 2014 +0200
+++ b/server/test/unittest_querier.py Thu Aug 28 18:29:14 2014 +0200
@@ -173,11 +173,11 @@
'ET': 'CWEType', 'ETN': 'String'}])
rql, solutions = partrqls[1]
self.assertRQLEqual(rql, 'Any ETN,X WHERE X is ET, ET name ETN, ET is CWEType, '
- 'X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, '
- ' CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, '
- ' CWRelation, CWSource, CWUniqueTogetherConstraint, CWUser, Card, Comment, '
- ' Division, Email, EmailPart, EmailThread, ExternalUri, File, Folder, Note, '
- ' Old, Personne, RQLExpression, Societe, State, SubDivision, '
+ 'X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWComputedRType, '
+ ' CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, '
+ ' CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, CWUser, Card, '
+ ' Comment, Division, Email, EmailPart, EmailThread, ExternalUri, File, '
+ ' Folder, Note, Old, Personne, RQLExpression, Societe, State, SubDivision, '
' SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)')
self.assertListEqual(sorted(solutions),
sorted([{'X': 'BaseTransition', 'ETN': 'String', 'ET': 'CWEType'},
@@ -186,6 +186,7 @@
{'X': 'Comment', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'Division', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'CWCache', 'ETN': 'String', 'ET': 'CWEType'},
+ {'X': 'CWComputedRType', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'CWConstraint', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'CWConstraintType', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'CWEType', 'ETN': 'String', 'ET': 'CWEType'},
@@ -602,18 +603,18 @@
'WHERE RT name N, RDEF relation_type RT '
'HAVING COUNT(RDEF) > 10')
self.assertListEqual(rset.rows,
- [[u'description_format', 12],
- [u'description', 13],
- [u'name', 17],
- [u'created_by', 43],
- [u'creation_date', 43],
- [u'cw_source', 43],
- [u'cwuri', 43],
- [u'in_basket', 43],
- [u'is', 43],
- [u'is_instance_of', 43],
- [u'modification_date', 43],
- [u'owned_by', 43]])
+ [[u'description_format', 13],
+ [u'description', 14],
+ [u'name', 18],
+ [u'created_by', 44],
+ [u'creation_date', 44],
+ [u'cw_source', 44],
+ [u'cwuri', 44],
+ [u'in_basket', 44],
+ [u'is', 44],
+ [u'is_instance_of', 44],
+ [u'modification_date', 44],
+ [u'owned_by', 44]])
def test_select_aggregat_having_dumb(self):
# dumb but should not raise an error
--- a/server/test/unittest_schemaserial.py Fri Jun 27 16:11:53 2014 +0200
+++ b/server/test/unittest_schemaserial.py Thu Aug 28 18:29:14 2014 +0200
@@ -25,6 +25,7 @@
from cubicweb import Binary
from cubicweb.schema import CubicWebSchemaLoader
from cubicweb.devtools import TestServerConfiguration
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.server.schemaserial import (updateeschema2rql, updaterschema2rql, rschema2rql,
eschema2rql, rdef2rql, specialize2rql,
@@ -425,7 +426,17 @@
# self.assertListEqual(perms2rql(schema, self.GROUP_MAPPING),
# ['INSERT CWEType X: X name 'Societe', X final FALSE'])
+class ComputedAttributeAndRelationTC(CubicWebTC):
+ appid = 'data-cwep002'
+ def test(self):
+ # force to read schema from the database
+ self.repo.set_schema(self.repo.deserialize_schema(), resetvreg=False)
+ schema = self.repo.schema
+ self.assertEqual([('Company', 'Person')], list(schema['has_employee'].rdefs))
+ self.assertEqual('O works_for S',
+ schema['has_employee'].rule)
+ self.assertEqual([('Company', 'Int')], list(schema['total_salary'].rdefs))
if __name__ == '__main__':
unittest_main()
--- a/test/unittest_schema.py Fri Jun 27 16:11:53 2014 +0200
+++ b/test/unittest_schema.py Thu Aug 28 18:29:14 2014 +0200
@@ -163,9 +163,10 @@
entities = sorted([str(e) for e in schema.entities()])
expected_entities = ['Ami', 'BaseTransition', 'BigInt', 'Bookmark', 'Boolean', 'Bytes', 'Card',
'Date', 'Datetime', 'Decimal',
- 'CWCache', 'CWConstraint', 'CWConstraintType', 'CWDataImport',
- 'CWEType', 'CWAttribute', 'CWGroup', 'EmailAddress', 'CWRelation',
- 'CWPermission', 'CWProperty', 'CWRType',
+ 'CWCache', 'CWComputedRType', 'CWConstraint',
+ 'CWConstraintType', 'CWDataImport', 'CWEType',
+ 'CWAttribute', 'CWGroup', 'EmailAddress',
+ 'CWRelation', 'CWPermission', 'CWProperty', 'CWRType',
'CWSource', 'CWSourceHostConfig', 'CWSourceSchemaConfig',
'CWUniqueTogetherConstraint', 'CWUser',
'ExternalUri', 'File', 'Float', 'Int', 'Interval', 'Note',
@@ -209,7 +210,7 @@
'parser', 'path', 'pkey', 'prefered_form', 'prenom', 'primary_email',
- 'read_permission', 'relation_type', 'relations', 'require_group',
+ 'read_permission', 'relation_type', 'relations', 'require_group', 'rule',
'specializes', 'start_timestamp', 'state_of', 'status', 'subworkflow', 'subworkflow_exit', 'subworkflow_state', 'surname', 'symmetric', 'synopsis',
@@ -452,6 +453,7 @@
('cw_source', 'Bookmark', 'CWSource', 'object'),
('cw_source', 'CWAttribute', 'CWSource', 'object'),
('cw_source', 'CWCache', 'CWSource', 'object'),
+ ('cw_source', 'CWComputedRType', 'CWSource', 'object'),
('cw_source', 'CWConstraint', 'CWSource', 'object'),
('cw_source', 'CWConstraintType', 'CWSource', 'object'),
('cw_source', 'CWDataImport', 'CWSource', 'object'),