[schema2sql] insert some constraints into the backend
BoundaryConstraint, IntervalBoundConstraint and
StaticVocabularyConstraint can be implemented in SQL with CHECK
constraints.
Next steps, not part of this changeset:
- migrations
- removing no longer necessary pre-insertion work on the python side
- translating backend exception due to constraint violation into
ValidationError
Note: this means that these constraints can no longer be ignored by
disabling integrity hooks.
Related to #5154406
--- a/server/schema2sql.py Tue Jun 23 10:05:34 2015 +0200
+++ b/server/schema2sql.py Mon May 18 11:36:07 2015 +0200
@@ -21,9 +21,10 @@
from hashlib import md5
+from six import string_types
from six.moves import range
-from yams.constraints import SizeConstraint, UniqueConstraint
+from yams.constraints import SizeConstraint, UniqueConstraint, Attribute
# default are usually not handled at the sql level. If you want them, set
# SET_DEFAULT to True
@@ -122,6 +123,15 @@
w(' %s%s %s' % (prefix, rschema.type, sqltype))
else:
w(' %s%s %s,' % (prefix, rschema.type, sqltype))
+ for rschema, aschema in attrs:
+ if aschema is None: # inline relation
+ continue
+ attr = rschema.type
+ rdef = rschema.rdef(eschema.type, aschema.type)
+ for constraint in rdef.constraints:
+ cstrname, check = check_constraint(eschema, aschema, attr, constraint, prefix=prefix)
+ if cstrname is not None:
+ w(', CONSTRAINT %s CHECK(%s)' % (cstrname, check))
w(');')
# create indexes
for i in range(len(attrs)):
@@ -136,6 +146,40 @@
w('')
return '\n'.join(output)
+def check_constraint(eschema, aschema, attr, constraint, prefix=''):
+ # XXX should find a better name
+ cstrname = 'cstr' + md5(eschema.type + attr + constraint.type() +
+ (constraint.serialize() or '')).hexdigest()
+ if constraint.type() == 'BoundaryConstraint':
+ if isinstance(constraint.boundary, Attribute):
+ value = prefix + constraint.boundary.attr
+ else:
+ value = constraint.boundary
+ return cstrname, '%s%s %s %s' % (prefix, attr, constraint.operator, value)
+ elif constraint.type() == 'IntervalBoundConstraint':
+ condition = []
+ if constraint.minvalue is not None:
+ if isinstance(constraint.minvalue, Attribute):
+ value = prefix + constraint.minvalue.attr
+ else:
+ value = constraint.minvalue
+ condition.append('%s%s >= %s' % (prefix, attr, value))
+ if constraint.maxvalue is not None:
+ if isinstance(constraint.maxvalue, Attribute):
+ value = prefix + constraint.maxvalue.attr
+ else:
+ value = constraint.maxvalue
+ condition.append('%s%s <= %s' % (prefix, attr, value))
+ return cstrname, ' AND '.join(condition)
+ elif constraint.type() == 'StaticVocabularyConstraint':
+ sample = next(iter(constraint.vocabulary()))
+ if not isinstance(sample, string_types):
+ values = ', '.join(str(word) for word in constraint.vocabulary())
+ else:
+ # XXX better quoting?
+ values = ', '.join("'%s'" % word.replace("'", "''") for word in constraint.vocabulary())
+ return cstrname, '%s%s IN (%s)' % (prefix, attr, values)
+ return None, None
def aschema2sql(dbhelper, eschema, rschema, aschema, creating=True, indent=''):
"""write an attribute schema as SQL statements to stdout"""
--- a/server/test/data-schema2sql/schema/schema.py Tue Jun 23 10:05:34 2015 +0200
+++ b/server/test/data-schema2sql/schema/schema.py Mon May 18 11:36:07 2015 +0200
@@ -17,6 +17,7 @@
# with yams. If not, see <http://www.gnu.org/licenses/>.
from yams.buildobjs import (EntityType, RelationDefinition, RelationType,
SubjectRelation, String, Int, Float, Date, Boolean)
+from yams.constraints import Attribute, BoundaryConstraint
class Affaire(EntityType):
sujet = String(maxsize=128)
@@ -64,7 +65,7 @@
nom = String(maxsize=64, fulltextindexed=True)
web = String(maxsize=128)
tel = Int()
- fax = Int()
+ fax = Int(constraints=[BoundaryConstraint('<=', Attribute('tel'))])
rncs = String(maxsize=32)
ad1 = String(maxsize=128)
ad2 = String(maxsize=128)
--- a/server/test/unittest_schema2sql.py Tue Jun 23 10:05:34 2015 +0200
+++ b/server/test/unittest_schema2sql.py Mon May 18 11:36:07 2015 +0200
@@ -96,6 +96,7 @@
datenaiss date,
test boolean,
salary float
+, CONSTRAINT cstr41fe7db9ce1d5be95de2477e26590386 CHECK(promo IN ('bon', 'pasbon'))
);
CREATE UNIQUE INDEX unique_e6c2d219772dbf1715597f7d9a6b3892 ON Person(nom,prenom);
@@ -113,6 +114,7 @@
datenaiss date,
test boolean,
salary float
+, CONSTRAINT cstrc8556fcc665865217761cdbcd220cae0 CHECK(promo IN ('bon', 'pasbon'))
);
CREATE UNIQUE INDEX unique_98da0f9de8588baa8966f0b1a6f850a3 ON Salaried(nom,prenom);
@@ -127,6 +129,7 @@
ad3 varchar(128),
cp varchar(12),
ville varchar(32)
+, CONSTRAINT cstrc51dd462e9f6115506a0fe468d4c8114 CHECK(fax <= tel)
);
CREATE TABLE State(
@@ -155,6 +158,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'))
);