--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/server/schemaserial.py Sat Jan 16 13:48:51 2016 +0100
@@ -0,0 +1,656 @@
+# copyright 2003-2013 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/>.
+"""functions for schema / permissions (de)serialization using RQL"""
+from __future__ import print_function
+
+__docformat__ = "restructuredtext en"
+
+import os
+import json
+import sys
+
+from six import PY2, text_type, string_types
+
+from logilab.common.shellutils import ProgressBar, DummyProgressBar
+
+from yams import BadSchemaDefinition, schema as schemamod, buildobjs as ybo
+
+from cubicweb import Binary
+from cubicweb.schema import (KNOWN_RPROPERTIES, CONSTRAINTS, ETYPE_NAME_MAP,
+ VIRTUAL_RTYPES)
+from cubicweb.server import sqlutils, schema2sql as y2sql
+
+
+def group_mapping(cnx, interactive=True):
+ """create a group mapping from an rql cursor
+
+ A group mapping has standard group names as key (managers, owners at least)
+ and the actual CWGroup entity's eid as associated value.
+ In interactive mode (the default), missing groups'eid will be prompted
+ from the user.
+ """
+ res = {}
+ for eid, name in cnx.execute('Any G, N WHERE G is CWGroup, G name N',
+ build_descr=False):
+ res[name] = eid
+ if not interactive:
+ return res
+ missing = [g for g in ('owners', 'managers', 'users', 'guests') if not g in res]
+ if missing:
+ print('some native groups are missing but the following groups have been found:')
+ print('\n'.join('* %s (%s)' % (n, eid) for n, eid in res.items()))
+ print()
+ print('enter the eid of a to group to map to each missing native group')
+ print('or just type enter to skip permissions granted to a group')
+ for group in missing:
+ while True:
+ value = raw_input('eid for group %s: ' % group).strip()
+ if not value:
+ continue
+ try:
+ eid = int(value)
+ except ValueError:
+ print('eid should be an integer')
+ continue
+ for eid_ in res.values():
+ if eid == eid_:
+ break
+ else:
+ print('eid is not a group eid')
+ continue
+ res[name] = eid
+ break
+ return res
+
+def cstrtype_mapping(cnx):
+ """cached constraint types mapping"""
+ map = dict(cnx.execute('Any T, X WHERE X is CWConstraintType, X name T'))
+ return map
+
+# schema / perms deserialization ##############################################
+
+def deserialize_schema(schema, cnx):
+ """return a schema according to information stored in an rql database
+ as CWRType and CWEType entities
+ """
+ repo = cnx.repo
+ dbhelper = repo.system_source.dbhelper
+
+ # Computed Rtype
+ with cnx.ensure_cnx_set:
+ tables = set(t.lower() for t in dbhelper.list_tables(cnx.cnxset.cu))
+ has_computed_relations = 'cw_cwcomputedrtype' in tables
+ # computed attribute
+ try:
+ cnx.system_sql("SELECT cw_formula FROM cw_CWAttribute")
+ has_computed_attributes = True
+ except Exception:
+ cnx.rollback()
+ has_computed_attributes = False
+
+ # XXX bw compat (3.6 migration)
+ sqlcu = cnx.system_sql("SELECT * FROM cw_CWRType WHERE cw_name='symetric'")
+ if sqlcu.fetchall():
+ sql = dbhelper.sql_rename_col('cw_CWRType', 'cw_symetric', 'cw_symmetric',
+ dbhelper.TYPE_MAPPING['Boolean'], True)
+ sqlcu.execute(sql)
+ sqlcu.execute("UPDATE cw_CWRType SET cw_name='symmetric' WHERE cw_name='symetric'")
+ cnx.commit()
+ ertidx = {}
+ copiedeids = set()
+ permsidx = deserialize_ertype_permissions(cnx)
+ schema.reading_from_database = True
+ # load every entity types
+ for eid, etype, desc in cnx.execute(
+ 'Any X, N, D WHERE X is CWEType, X name N, X description D',
+ build_descr=False):
+ # base types are already in the schema, skip them
+ if etype in schemamod.BASE_TYPES:
+ # just set the eid
+ eschema = schema.eschema(etype)
+ eschema.eid = eid
+ ertidx[eid] = etype
+ continue
+ if etype in ETYPE_NAME_MAP:
+ needcopy = False
+ netype = ETYPE_NAME_MAP[etype]
+ # can't use write rql queries at this point, use raw sql
+ sqlexec = cnx.system_sql
+ if sqlexec('SELECT 1 FROM %(p)sCWEType WHERE %(p)sname=%%(n)s'
+ % {'p': sqlutils.SQL_PREFIX}, {'n': netype}).fetchone():
+ # the new type already exists, we should copy (eg make existing
+ # instances of the old type instances of the new type)
+ assert etype.lower() != netype.lower()
+ needcopy = True
+ else:
+ # the new type doesn't exist, we should rename
+ sqlexec('UPDATE %(p)sCWEType SET %(p)sname=%%(n)s WHERE %(p)seid=%%(x)s'
+ % {'p': sqlutils.SQL_PREFIX}, {'x': eid, 'n': netype})
+ if etype.lower() != netype.lower():
+ alter_table_sql = dbhelper.sql_rename_table(sqlutils.SQL_PREFIX+etype,
+ sqlutils.SQL_PREFIX+netype)
+ sqlexec(alter_table_sql)
+ sqlexec('UPDATE entities SET type=%(n)s WHERE type=%(x)s',
+ {'x': etype, 'n': netype})
+ cnx.commit(False)
+ tocleanup = [eid]
+ tocleanup += (eid for eid, cached in repo._type_source_cache.items()
+ if etype == cached[0])
+ repo.clear_caches(tocleanup)
+ cnx.commit(False)
+ if needcopy:
+ ertidx[eid] = netype
+ copiedeids.add(eid)
+ # copy / CWEType entity removal expected to be done through
+ # rename_entity_type in a migration script
+ continue
+ etype = netype
+ ertidx[eid] = etype
+ eschema = schema.add_entity_type(
+ ybo.EntityType(name=etype, description=desc, eid=eid))
+ set_perms(eschema, permsidx)
+ # load inheritance relations
+ for etype, stype in cnx.execute(
+ 'Any XN, ETN WHERE X is CWEType, X name XN, X specializes ET, ET name ETN',
+ build_descr=False):
+ etype = ETYPE_NAME_MAP.get(etype, etype)
+ stype = ETYPE_NAME_MAP.get(stype, stype)
+ schema.eschema(etype)._specialized_type = stype
+ schema.eschema(stype)._specialized_by.append(etype)
+ 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)
+ rschema = schema.add_relation_type(rtype)
+ set_perms(rschema, permsidx)
+ # load every relation types
+ for eid, rtype, desc, sym, il, ftc in cnx.execute(
+ 'Any X,N,D,S,I,FTC WHERE X is CWRType, X name N, X description D, '
+ 'X symmetric S, X inlined I, X fulltext_container FTC', build_descr=False):
+ ertidx[eid] = rtype
+ rschema = schema.add_relation_type(
+ ybo.RelationType(name=rtype, description=desc,
+ symmetric=bool(sym), inlined=bool(il),
+ fulltext_container=ftc, eid=eid))
+ # remains to load every relation definitions (ie relations and attributes)
+ cstrsidx = deserialize_rdef_constraints(cnx)
+ pendingrdefs = []
+ # closure to factorize common code of attribute/relation rdef addition
+ def _add_rdef(rdefeid, seid, reid, oeid, **kwargs):
+ rdef = ybo.RelationDefinition(ertidx[seid], ertidx[reid], ertidx[oeid],
+ constraints=cstrsidx.get(rdefeid, ()),
+ eid=rdefeid, **kwargs)
+ if seid in copiedeids or oeid in copiedeids:
+ # delay addition of this rdef. We'll insert them later if needed. We
+ # have to do this because:
+ #
+ # * on etype renaming, we want relation of the old entity type being
+ # redirected to the new type during migration
+ #
+ # * in the case of a copy, we've to take care that rdef already
+ # existing in the schema are not overwritten by a redirected one,
+ # since we want correct eid on them (redirected rdef will be
+ # removed in rename_entity_type)
+ pendingrdefs.append(rdef)
+ else:
+ # add_relation_def return a RelationDefinitionSchema if it has been
+ # actually added (can be None on duplicated relation definitions,
+ # e.g. if the relation type is marked as beeing symmetric)
+ rdefs = schema.add_relation_def(rdef)
+ if rdefs is not None:
+ ertidx[rdefeid] = rdefs
+ set_perms(rdefs, permsidx)
+ # Get the type parameters for additional base types.
+ try:
+ extra_props = dict(cnx.execute('Any X, XTP WHERE X is CWAttribute, '
+ 'X extra_props XTP'))
+ except Exception:
+ cnx.critical('Previous CRITICAL notification about extra_props is not '
+ 'a problem if you are migrating to cubicweb 3.17')
+ extra_props = {} # not yet in the schema (introduced by 3.17 migration)
+
+ # load attributes
+ rql = ('Any X,SE,RT,OE,CARD,ORD,DESC,IDX,FTIDX,I18N,DFLT%(fm)s '
+ 'WHERE X is CWAttribute, X relation_type RT, X cardinality CARD,'
+ ' X ordernum ORD, X indexed IDX, X description DESC, '
+ ' X internationalizable I18N, X defaultval DFLT,%(fmsnip)s'
+ ' X fulltextindexed FTIDX, X from_entity SE, X to_entity OE')
+ if has_computed_attributes:
+ rql = rql % {'fm': ',FM', 'fmsnip': 'X formula FM,'}
+ else:
+ rql = rql % {'fm': '', 'fmsnip': ''}
+ for values in cnx.execute(rql, build_descr=False):
+ attrs = dict(zip(
+ ('rdefeid', 'seid', 'reid', 'oeid', 'cardinality',
+ 'order', 'description', 'indexed', 'fulltextindexed',
+ 'internationalizable', 'default', 'formula'), values))
+ typeparams = extra_props.get(attrs['rdefeid'])
+ attrs.update(json.loads(typeparams.getvalue().decode('ascii')) if typeparams else {})
+ default = attrs['default']
+ if default is not None:
+ if isinstance(default, Binary):
+ # while migrating from 3.17 to 3.18, we still have to
+ # handle String defaults
+ attrs['default'] = default.unzpickle()
+ _add_rdef(**attrs)
+ # load relations
+ for values in cnx.execute(
+ 'Any X,SE,RT,OE,CARD,ORD,DESC,C WHERE X is CWRelation, X relation_type RT,'
+ 'X cardinality CARD, X ordernum ORD, X description DESC, '
+ 'X from_entity SE, X to_entity OE, X composite C', build_descr=False):
+ rdefeid, seid, reid, oeid, card, ord, desc, comp = values
+ _add_rdef(rdefeid, seid, reid, oeid,
+ cardinality=card, description=desc, order=ord,
+ composite=comp)
+ for rdef in pendingrdefs:
+ try:
+ rdefs = schema.add_relation_def(rdef)
+ except BadSchemaDefinition:
+ continue
+ if rdefs is not None:
+ set_perms(rdefs, permsidx)
+ unique_togethers = {}
+ rset = cnx.execute(
+ 'Any X,E,R WHERE '
+ 'X is CWUniqueTogetherConstraint, '
+ 'X constraint_of E, X relations R', build_descr=False)
+ for values in rset:
+ uniquecstreid, eeid, releid = values
+ eschema = schema.schema_by_eid(eeid)
+ relations = unique_togethers.setdefault(uniquecstreid, (eschema, []))
+ rel = ertidx[releid]
+ if isinstance(rel, schemamod.RelationDefinitionSchema):
+ # not yet migrated 3.9 database ('relations' target type changed
+ # to CWRType in 3.10)
+ rtype = rel.rtype.type
+ else:
+ rtype = str(rel)
+ relations[1].append(rtype)
+ for eschema, unique_together in unique_togethers.values():
+ eschema._unique_together.append(tuple(sorted(unique_together)))
+ schema.infer_specialization_rules()
+ cnx.commit()
+ schema.finalize()
+ schema.reading_from_database = False
+
+
+def deserialize_ertype_permissions(cnx):
+ """return sect action:groups associations for the given
+ entity or relation schema with its eid, according to schema's
+ permissions stored in the database as [read|add|delete|update]_permission
+ relations between CWEType/CWRType and CWGroup entities
+ """
+ res = {}
+ for action in ('read', 'add', 'update', 'delete'):
+ rql = 'Any E,N WHERE G is CWGroup, G name N, E %s_permission G' % action
+ for eid, gname in cnx.execute(rql, build_descr=False):
+ res.setdefault(eid, {}).setdefault(action, []).append(gname)
+ rql = ('Any E,X,EXPR,V WHERE X is RQLExpression, X expression EXPR, '
+ 'E %s_permission X, X mainvars V' % action)
+ for eid, expreid, expr, mainvars in cnx.execute(rql, build_descr=False):
+ # we don't know yet if it's a rql expr for an entity or a relation,
+ # so append a tuple to differentiate from groups and so we'll be
+ # able to instantiate it later
+ res.setdefault(eid, {}).setdefault(action, []).append( (expr, mainvars, expreid) )
+ return res
+
+def deserialize_rdef_constraints(cnx):
+ """return the list of relation definition's constraints as instances"""
+ res = {}
+ for rdefeid, ceid, ct, val in cnx.execute(
+ 'Any E, X,TN,V WHERE E constrained_by X, X is CWConstraint, '
+ 'X cstrtype T, T name TN, X value V', build_descr=False):
+ cstr = CONSTRAINTS[ct].deserialize(val)
+ cstr.eid = ceid
+ res.setdefault(rdefeid, []).append(cstr)
+ return res
+
+def set_perms(erschema, permsidx):
+ """set permissions on the given erschema according to the permission
+ definition dictionary as built by deserialize_ertype_permissions for a
+ given erschema's eid
+ """
+ # reset erschema permissions here to avoid getting yams default anyway
+ erschema.permissions = dict((action, ()) for action in erschema.ACTIONS)
+ try:
+ thispermsdict = permsidx[erschema.eid]
+ except KeyError:
+ return
+ for action, somethings in thispermsdict.items():
+ erschema.permissions[action] = tuple(
+ isinstance(p, tuple) and erschema.rql_expression(*p) or p
+ for p in somethings)
+
+
+# schema / perms serialization ################################################
+
+def serialize_schema(cnx, schema):
+ """synchronize schema and permissions in the database according to
+ current schema
+ """
+ _title = '-> storing the schema in the database '
+ print(_title, end=' ')
+ execute = cnx.execute
+ eschemas = schema.entities()
+ pb_size = (len(eschemas + schema.relations())
+ + len(CONSTRAINTS)
+ + len([x for x in eschemas if x.specializes()]))
+ if sys.stdout.isatty():
+ pb = ProgressBar(pb_size, title=_title)
+ else:
+ pb = DummyProgressBar()
+ groupmap = group_mapping(cnx, interactive=False)
+ # serialize all entity types, assuring CWEType is serialized first for proper
+ # is / is_instance_of insertion
+ eschemas.remove(schema.eschema('CWEType'))
+ eschemas.insert(0, schema.eschema('CWEType'))
+ for eschema in eschemas:
+ execschemarql(execute, eschema, eschema2rql(eschema, groupmap))
+ pb.update()
+ # serialize constraint types
+ cstrtypemap = {}
+ rql = 'INSERT CWConstraintType X: X name %(ct)s'
+ for cstrtype in CONSTRAINTS:
+ cstrtypemap[cstrtype] = execute(rql, {'ct': text_type(cstrtype)},
+ build_descr=False)[0][0]
+ pb.update()
+ # serialize relations
+ for rschema in schema.relations():
+ # skip virtual relations such as eid, has_text and identity
+ if rschema in VIRTUAL_RTYPES:
+ pb.update()
+ continue
+ if rschema.rule:
+ execschemarql(execute, rschema, crschema2rql(rschema, groupmap))
+ pb.update()
+ continue
+ execschemarql(execute, rschema, rschema2rql(rschema, addrdef=False))
+ if rschema.symmetric:
+ rdefs = [rdef for k, rdef in rschema.rdefs.items()
+ if (rdef.subject, rdef.object) == k]
+ else:
+ rdefs = rschema.rdefs.values()
+ for rdef in rdefs:
+ execschemarql(execute, rdef,
+ rdef2rql(rdef, cstrtypemap, groupmap))
+ pb.update()
+ # serialize unique_together constraints
+ for eschema in eschemas:
+ if eschema._unique_together:
+ execschemarql(execute, eschema, uniquetogether2rqls(eschema))
+ # serialize yams inheritance relationships
+ for rql, kwargs in specialize2rql(schema):
+ execute(rql, kwargs, build_descr=False)
+ pb.update()
+ print()
+
+
+# high level serialization functions
+
+def execschemarql(execute, schema, rqls):
+ for rql, kwargs in rqls:
+ kwargs['x'] = schema.eid
+ rset = execute(rql, kwargs, build_descr=False)
+ if schema.eid is None:
+ schema.eid = rset[0][0]
+ else:
+ assert rset
+
+def erschema2rql(erschema, groupmap):
+ if isinstance(erschema, schemamod.EntitySchema):
+ return eschema2rql(erschema, groupmap=groupmap)
+ return rschema2rql(erschema, groupmap=groupmap)
+
+def specialize2rql(schema):
+ for eschema in schema.entities():
+ if eschema.final:
+ continue
+ for rql, kwargs in eschemaspecialize2rql(eschema):
+ yield rql, kwargs
+
+# etype serialization
+
+def eschema2rql(eschema, groupmap=None):
+ """return a list of rql insert statements to enter an entity schema
+ in the database as an CWEType entity
+ """
+ relations, values = eschema_relations_values(eschema)
+ # NOTE: 'specializes' relation can't be inserted here since there's no
+ # way to make sure the parent type is inserted before the child type
+ yield 'INSERT CWEType X: %s' % ','.join(relations) , values
+ # entity permissions
+ if groupmap is not None:
+ for rql, args in _erperms2rql(eschema, groupmap):
+ yield rql, args
+
+def eschema_relations_values(eschema):
+ values = _ervalues(eschema)
+ relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
+ return relations, values
+
+def eschemaspecialize2rql(eschema):
+ specialized_type = eschema.specializes()
+ if specialized_type:
+ values = {'x': eschema.eid, 'et': specialized_type.eid}
+ yield 'SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s', values
+
+def uniquetogether2rqls(eschema):
+ rql_args = []
+ # robustness against duplicated CWUniqueTogetherConstraint (pre 3.18)
+ columnset = set()
+ for columns in eschema._unique_together:
+ if columns in columnset:
+ print('schemaserial: skipping duplicate unique together %r %r' %
+ (eschema.type, columns))
+ continue
+ columnset.add(columns)
+ rql, args = _uniquetogether2rql(eschema, columns)
+ args['name'] = y2sql.unique_index_name(eschema, columns)
+ rql_args.append((rql, args))
+ return rql_args
+
+def _uniquetogether2rql(eschema, unique_together):
+ relations = []
+ restrictions = []
+ substs = {}
+ for i, name in enumerate(unique_together):
+ rschema = eschema.schema.rschema(name)
+ rtype = 'T%d' % i
+ substs[rtype] = text_type(rschema.type)
+ relations.append('C relations %s' % rtype)
+ restrictions.append('%(rtype)s name %%(%(rtype)s)s' % {'rtype': rtype})
+ relations = ', '.join(relations)
+ restrictions = ', '.join(restrictions)
+ rql = ('INSERT CWUniqueTogetherConstraint C: C name %%(name)s, C constraint_of X, %s '
+ 'WHERE X eid %%(x)s, %s')
+ return rql % (relations, restrictions), substs
+
+
+def _ervalues(erschema):
+ try:
+ type_ = text_type(erschema.type)
+ except UnicodeDecodeError as e:
+ raise Exception("can't decode %s [was %s]" % (erschema.type, e))
+ try:
+ desc = text_type(erschema.description) or u''
+ except UnicodeDecodeError as e:
+ raise Exception("can't decode %s [was %s]" % (erschema.description, e))
+ return {
+ 'name': type_,
+ 'final': erschema.final,
+ 'description': desc,
+ }
+
+# rtype serialization
+
+def rschema2rql(rschema, cstrtypemap=None, addrdef=True, groupmap=None):
+ """generate rql insert statements to enter a relation schema
+ in the database as an CWRType entity
+ """
+ if rschema.type == 'has_text':
+ return
+ relations, values = rschema_relations_values(rschema)
+ yield 'INSERT CWRType X: %s' % ','.join(relations), values
+ if addrdef:
+ assert cstrtypemap
+ # sort for testing purpose
+ for rdef in sorted(rschema.rdefs.values(),
+ key=lambda x: (x.subject, x.object)):
+ for rql, values in rdef2rql(rdef, cstrtypemap, groupmap):
+ yield rql, values
+
+def rschema_relations_values(rschema):
+ values = _ervalues(rschema)
+ values['final'] = rschema.final
+ values['symmetric'] = rschema.symmetric
+ values['inlined'] = rschema.inlined
+ if PY2 and isinstance(rschema.fulltext_container, str):
+ values['fulltext_container'] = unicode(rschema.fulltext_container)
+ else:
+ values['fulltext_container'] = rschema.fulltext_container
+ relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
+ return relations, values
+
+def crschema2rql(crschema, groupmap):
+ relations, values = crschema_relations_values(crschema)
+ yield 'INSERT CWComputedRType X: %s' % ','.join(relations), values
+ if groupmap:
+ for rql, args in _erperms2rql(crschema, groupmap):
+ yield rql, args
+
+def crschema_relations_values(crschema):
+ values = _ervalues(crschema)
+ values['rule'] = text_type(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 inferred relations
+ if rdef.infered:
+ return
+ relations, values = _rdef_values(rdef)
+ relations.append('X relation_type ER,X from_entity SE,X to_entity OE')
+ values.update({'se': rdef.subject.eid, 'rt': rdef.rtype.eid, 'oe': rdef.object.eid})
+ if rdef.final:
+ etype = 'CWAttribute'
+ else:
+ etype = 'CWRelation'
+ yield 'INSERT %s X: %s WHERE SE eid %%(se)s,ER eid %%(rt)s,OE eid %%(oe)s' % (
+ etype, ','.join(relations), ), values
+ for rql, values in constraints2rql(cstrtypemap, rdef.constraints):
+ yield rql, values
+ # no groupmap means "no security insertion"
+ if groupmap:
+ for rql, args in _erperms2rql(rdef, groupmap):
+ yield rql, args
+
+_IGNORED_PROPS = ['eid', 'constraints', 'uid', 'infered', 'permissions']
+
+def _rdef_values(rdef):
+ amap = {'order': 'ordernum', 'default': 'defaultval'}
+ values = {}
+ extra = {}
+ for prop in rdef.rproperty_defs(rdef.object):
+ if prop in _IGNORED_PROPS:
+ continue
+ value = getattr(rdef, prop)
+ if prop not in KNOWN_RPROPERTIES:
+ extra[prop] = value
+ continue
+ # XXX type cast really necessary?
+ if prop in ('indexed', 'fulltextindexed', 'internationalizable'):
+ value = bool(value)
+ elif prop == 'ordernum':
+ value = int(value)
+ elif PY2 and isinstance(value, str):
+ value = unicode(value)
+ if value is not None and prop == 'default':
+ value = Binary.zpickle(value)
+ values[amap.get(prop, prop)] = value
+ if extra:
+ values['extra_props'] = Binary(json.dumps(extra).encode('ascii'))
+ relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
+ return relations, values
+
+def constraints2rql(cstrtypemap, constraints, rdefeid=None):
+ for constraint in constraints:
+ values = {'ct': cstrtypemap[constraint.type()],
+ 'value': text_type(constraint.serialize()),
+ 'x': rdefeid} # when not specified, will have to be set by the caller
+ yield 'INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE \
+CT eid %(ct)s, EDEF eid %(x)s', values
+
+
+def _erperms2rql(erschema, groupmap):
+ """return rql insert statements to enter the entity or relation
+ schema's permissions in the database as
+ [read|add|delete|update]_permission relations between CWEType/CWRType
+ and CWGroup entities
+ """
+ for action in erschema.ACTIONS:
+ try:
+ grantedto = erschema.action_permissions(action)
+ except KeyError:
+ # may occurs when modifying persistent schema
+ continue
+ for group_or_rqlexpr in grantedto:
+ if isinstance(group_or_rqlexpr, string_types):
+ # group
+ try:
+ yield ('SET X %s_permission Y WHERE Y eid %%(g)s, X eid %%(x)s' % action,
+ {'g': groupmap[group_or_rqlexpr]})
+ except KeyError:
+ print("WARNING: group %s used in permissions for %s was ignored because it doesn't exist."
+ " You may want to add it into a precreate.py file" % (group_or_rqlexpr, erschema))
+ continue
+ else:
+ # rqlexpr
+ rqlexpr = group_or_rqlexpr
+ yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
+ 'E mainvars %%(v)s, X %s_permission E WHERE X eid %%(x)s' % action,
+ {'e': text_type(rqlexpr.expression),
+ 'v': text_type(','.join(sorted(rqlexpr.mainvars))),
+ 't': text_type(rqlexpr.__class__.__name__)})
+
+# update functions
+
+def updateeschema2rql(eschema, eid):
+ relations, values = eschema_relations_values(eschema)
+ values['x'] = eid
+ yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values
+
+def updaterschema2rql(rschema, eid):
+ if rschema.rule:
+ yield ('SET X rule %(r)s WHERE X eid %(x)s',
+ {'x': eid, 'r': text_type(rschema.rule)})
+ else:
+ relations, values = rschema_relations_values(rschema)
+ values['x'] = eid
+ yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values
+
+def updaterdef2rql(rdef, eid):
+ relations, values = _rdef_values(rdef)
+ values['x'] = eid
+ yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values