[CWEP002] properly handle serialization of computed relations
authorLea Capgen <lea.capgen@logilab.fr>
Thu, 28 Aug 2014 18:29:14 +0200
changeset 9956 19a683a0047c
parent 9955 60a9cd1b3a4b
child 9957 5def1d98fce7
[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]
misc/migration/3.20.0_Any.py
schemas/bootstrap.py
server/schemaserial.py
server/test/data-cwep002/schema.py
server/test/unittest_querier.py
server/test/unittest_schemaserial.py
test/unittest_schema.py
--- /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'),