[schema] use json to serialize constraints
authorJulien Cristau <julien.cristau@logilab.fr>
Wed, 16 Mar 2016 11:56:32 +0100
changeset 11215 4e79acdc36a6
parent 11212 3309ddb97059
child 11216 efecb78bf929
[schema] use json to serialize constraints Require yams 0.43: constraints are serialized to json, which means we need to recreate the actual checks in the database on upgrade. Temporary deps in tox.ini to pull respective changes in yams.
cubicweb.spec
cubicweb/__pkginfo__.py
cubicweb/misc/migration/3.23.0_Any.py
cubicweb/schema.py
cubicweb/server/test/unittest_schema2sql.py
cubicweb/server/test/unittest_schemaserial.py
debian/control
tox.ini
--- a/cubicweb.spec	Wed Mar 16 17:59:10 2016 +0100
+++ b/cubicweb.spec	Wed Mar 16 11:56:32 2016 +0100
@@ -25,7 +25,7 @@
 Requires:       %{python}-logilab-common >= 1.2.0
 Requires:       %{python}-logilab-mtconverter >= 0.8.0
 Requires:       %{python}-rql >= 0.34.0
-Requires:       %{python}-yams >= 0.42.0
+Requires:       %{python}-yams >= 0.43.0
 Requires:       %{python}-logilab-database >= 1.15.0
 Requires:       %{python}-passlib
 Requires:       %{python}-lxml
--- a/cubicweb/__pkginfo__.py	Wed Mar 16 17:59:10 2016 +0100
+++ b/cubicweb/__pkginfo__.py	Wed Mar 16 11:56:32 2016 +0100
@@ -43,7 +43,7 @@
     'logilab-common': '>= 1.2.0',
     'logilab-mtconverter': '>= 0.8.0',
     'rql': '>= 0.34.0',
-    'yams': '>= 0.42.0',
+    'yams': '>= 0.42.0',  # TODO '>= 0.43.0' upon release.
     #gettext                    # for xgettext, msgcat, etc...
     # web dependencies
     'lxml': '',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/misc/migration/3.23.0_Any.py	Wed Mar 16 11:56:32 2016 +0100
@@ -0,0 +1,24 @@
+# we changed constraint serialization, which also changes their name
+from cubicweb.server.schema2sql import check_constraint
+
+helper = repo.system_source.dbhelper
+
+for table, cstr in sql("""
+    SELECT table_name, constraint_name FROM information_schema.constraint_column_usage
+    WHERE constraint_name LIKE 'cstr%'"""):
+    sql("ALTER TABLE %(table)s DROP CONSTRAINT %(cstr)s" % locals())
+
+for cwconstraint in rql('Any C WHERE R constrained_by C').entities():
+    cwrdef = cwconstraint.reverse_constrained_by[0]
+    rdef = cwrdef.yams_schema()
+    cstr = rdef.constraint_by_eid(cwconstraint.eid)
+    if cstr.type() not in ('BoundaryConstraint', 'IntervalBoundConstraint',
+                           'StaticVocabularyConstraint'):
+        # These cannot be translate into backend CHECK.
+        continue
+    cstrname, check = check_constraint(rdef.subject, rdef.object, rdef.rtype.type,
+                                       cstr, helper, prefix='cw_')
+    args = {'e': rdef.subject.type, 'c': cstrname, 'v': check}
+    sql('ALTER TABLE cw_%(e)s ADD CONSTRAINT %(c)s CHECK(%(v)s)' % args)
+
+commit()
--- a/cubicweb/schema.py	Wed Mar 16 17:59:10 2016 +0100
+++ b/cubicweb/schema.py	Wed Mar 16 11:56:32 2016 +0100
@@ -40,7 +40,8 @@
 from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema, \
      RelationDefinitionSchema, PermissionMixIn, role_name
 from yams.constraints import (BaseConstraint, FormatConstraint, BoundaryConstraint,
-                              IntervalBoundConstraint, StaticVocabularyConstraint)
+                              IntervalBoundConstraint, StaticVocabularyConstraint,
+                              cstr_json_dumps, cstr_json_loads)
 from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
                          cleanup_sys_modules, fill_schema_from_namespace)
 
@@ -1145,13 +1146,17 @@
     distinct_query = None
 
     def serialize(self):
-        # start with a semicolon for bw compat, see below
-        return ';' + ','.join(sorted(self.mainvars)) + ';' + self.expression
+        return cstr_json_dumps({u'mainvars': sorted(self.mainvars),
+                                u'expression': self.expression})
 
     @classmethod
     def deserialize(cls, value):
-        _, mainvars, expression = value.split(';', 2)
-        return cls(expression, mainvars)
+        try:
+            d = cstr_json_loads(value)
+            return cls(d['expression'], d['mainvars'])
+        except ValueError:
+            _, mainvars, expression = value.split(';', 2)
+            return cls(expression, mainvars)
 
     def check(self, entity, rtype, value):
         """return true if the value satisfy the constraint, else false"""
@@ -1199,15 +1204,20 @@
         self.msg = msg
 
     def serialize(self):
-        # start with a semicolon for bw compat, see below
-        return ';%s;%s\n%s' % (','.join(sorted(self.mainvars)), self.expression,
-                               self.msg or '')
+        return cstr_json_dumps({
+            u'mainvars': sorted(self.mainvars),
+            u'expression': self.expression,
+            u'msg': self.msg})
 
     @classmethod
     def deserialize(cls, value):
-        value, msg = value.split('\n', 1)
-        _, mainvars, expression = value.split(';', 2)
-        return cls(expression, mainvars, msg)
+        try:
+            d = cstr_json_loads(value)
+            return cls(d['expression'], d['mainvars'], d['msg'])
+        except ValueError:
+            value, msg = value.split('\n', 1)
+            _, mainvars, expression = value.split(';', 2)
+            return cls(expression, mainvars, msg)
 
     def repo_check(self, session, eidfrom, rtype, eidto=None):
         """raise ValidationError if the relation doesn't satisfy the constraint
--- a/cubicweb/server/test/unittest_schema2sql.py	Wed Mar 16 17:59:10 2016 +0100
+++ b/cubicweb/server/test/unittest_schema2sql.py	Wed Mar 16 11:56:32 2016 +0100
@@ -52,7 +52,7 @@
  d2 date,
  t1 time,
  t2 time
-, CONSTRAINT cstredd407706bdfbd2285714dd689e8fcc0 CHECK(d1 <= CAST(clock_timestamp() AS DATE))
+, CONSTRAINT cstr67c656afbcbfadd4be34d75656a2521a CHECK(d1 <= CAST(clock_timestamp() AS DATE))
 );
 
 CREATE TABLE Division(
@@ -97,7 +97,7 @@
  datenaiss date,
  test boolean,
  salary float
-, CONSTRAINT cstr41fe7db9ce1d5be95de2477e26590386 CHECK(promo IN ('bon', 'pasbon'))
+, CONSTRAINT cstrdedefafc86dc831341c33547388c25bb CHECK(promo IN ('bon', 'pasbon'))
 );
 CREATE UNIQUE INDEX unique_e6c2d219772dbf1715597f7d9a6b3892 ON Person(nom,prenom);
 
@@ -115,7 +115,7 @@
  datenaiss date,
  test boolean,
  salary float
-, CONSTRAINT cstrc8556fcc665865217761cdbcd220cae0 CHECK(promo IN ('bon', 'pasbon'))
+, CONSTRAINT cstrb62a1623de9e9b92eb552706b6ce0890 CHECK(promo IN ('bon', 'pasbon'))
 );
 CREATE UNIQUE INDEX unique_98da0f9de8588baa8966f0b1a6f850a3 ON Salaried(nom,prenom);
 
@@ -130,7 +130,7 @@
  ad3 varchar(128),
  cp varchar(12),
  ville varchar(32)
-, CONSTRAINT cstrc51dd462e9f6115506a0fe468d4c8114 CHECK(fax <= tel)
+, CONSTRAINT cstraf91cb60287eec6d5c1175075edcccc0 CHECK(fax <= tel)
 );
 
 CREATE TABLE State(
@@ -159,8 +159,8 @@
  author_email varchar(100) NOT NULL,
  mailinglist varchar(100),
  debian_handler varchar(6)
-, CONSTRAINT cstr70f766f834557c715815d76f0a0db956 CHECK(license IN ('GPL', 'ZPL'))
-, CONSTRAINT cstr831a117424d0007ae0278cc15f344f5e CHECK(debian_handler IN ('machin', 'bidule'))
+, CONSTRAINT cstree063a486aa92d4f721ced56c819f38a CHECK(license IN ('GPL', 'ZPL'))
+, CONSTRAINT cstrafb4b6de5d50b5a2eca60b33b7acf59b CHECK(debian_handler IN ('machin', 'bidule'))
 );
 
 
--- a/cubicweb/server/test/unittest_schemaserial.py	Wed Mar 16 17:59:10 2016 +0100
+++ b/cubicweb/server/test/unittest_schemaserial.py	Wed Mar 16 11:56:32 2016 +0100
@@ -115,7 +115,7 @@
             ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X '
              'WHERE CT eid %(ct)s, EDEF eid %(x)s',
              {'x': None, 'ct': u'RQLConstraint_eid',
-              'value': u';O;O final TRUE\n'}),
+              'value': u'{"expression": "O final TRUE", "mainvars": ["O"], "msg": null}'}),
 
             ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,'
              'X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,'
@@ -125,7 +125,7 @@
               'ordernum': 1, 'cardinality': u'1*'}),
             ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X '
              'WHERE CT eid %(ct)s, EDEF eid %(x)s',
-             {'x': None, 'ct': u'RQLConstraint_eid', 'value': u';O;O final FALSE\n'}),
+             {'x': None, 'ct': u'RQLConstraint_eid', 'value': u'{"expression": "O final FALSE", "mainvars": ["O"], "msg": null}'}),
         ],
                              list(rschema2rql(schema.rschema('relation_type'), cstrtypemap)))
 
@@ -236,12 +236,12 @@
              'WHERE CT eid %(ct)s, EDEF eid %(x)s',
              {'x': None,
               'ct': u'SizeConstraint_eid',
-              'value': u'max=2'}),
+              'value': u'{"max": 2, "min": null}'}),
             ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X '
              'WHERE CT eid %(ct)s, EDEF eid %(x)s',
              {'x': None,
               'ct': u'StaticVocabularyConstraint_eid',
-              'value': u"u'?1', u'11'"}),
+              'value': u'["?1", "11"]'}),
 
             ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,'
              'X description %(description)s,X formula %(formula)s,X fulltextindexed %(fulltextindexed)s,'
@@ -263,13 +263,13 @@
              'WHERE CT eid %(ct)s, EDEF eid %(x)s',
              {'x': None,
               'ct': u'SizeConstraint_eid',
-              'value': u'max=2'}),
+              'value': u'{"max": 2, "min": null}'}),
             ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X '
              'WHERE CT eid %(ct)s, EDEF eid %(x)s',
              {'x': None,
               'ct': u'StaticVocabularyConstraint_eid',
-              'value': (u"u'?*', u'1*', u'+*', u'**', u'?+', u'1+', u'++', u'*+', u'?1', "
-                        "u'11', u'+1', u'*1', u'??', u'1?', u'+?', u'*?'")})],
+              "value": (u'["?*", "1*", "+*", "**", "?+", "1+", "++", "*+", "?1", '
+                        u'"11", "+1", "*1", "??", "1?", "+?", "*?"]')})],
               list(rschema2rql(schema.rschema('cardinality'), cstrtypemap)))
 
     def test_rschema2rql_custom_type(self):
@@ -334,7 +334,7 @@
             ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X '
              'WHERE CT eid %(ct)s, EDEF eid %(x)s',
              {'x': None,
-              'value': u'max=50',
+              'value': u'{"max": 50, "min": null}',
               'ct': 'SizeConstraint_eid'})],
                              list(rdef2rql(schema['description_format'].rdefs[('CWRType', 'String')],
                                            cstrtypemap)))
--- a/debian/control	Wed Mar 16 17:59:10 2016 +0100
+++ b/debian/control	Wed Mar 16 11:56:32 2016 +0100
@@ -16,7 +16,7 @@
  python-markdown,
  python-tz,
  python-rql (>= 0.34.0),
- python-yams (>= 0.42.0),
+ python-yams (>= 0.43.0),
  python-lxml,
 Standards-Version: 3.9.1
 Homepage: https://www.cubicweb.org
@@ -160,7 +160,7 @@
  python-logilab-mtconverter (>= 0.8.0),
  python-logilab-common (>= 1.2.0),
  python-markdown,
- python-yams (>= 0.42.0),
+ python-yams (>= 0.43.0),
  python-rql (>= 0.34.0),
  python-lxml
 Recommends:
--- a/tox.ini	Wed Mar 16 17:59:10 2016 +0100
+++ b/tox.ini	Wed Mar 16 11:56:32 2016 +0100
@@ -9,6 +9,7 @@
 whitelist_externals =
   /usr/bin/touch
 deps =
+  hg+https://hg.logilab.org/review/yams@default#egg=yams
   py34: -e.
   cubicweb: -r{toxinidir}/cubicweb/test/requirements.txt
   devtools: -r{toxinidir}/cubicweb/devtools/test/requirements.txt