server/schemaserial.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """functions for schema / permissions (de)serialization using RQL"""
       
    19 from __future__ import print_function
       
    20 
       
    21 __docformat__ = "restructuredtext en"
       
    22 
       
    23 import os
       
    24 import json
       
    25 import sys
       
    26 
       
    27 from six import PY2, text_type, string_types
       
    28 
       
    29 from logilab.common.shellutils import ProgressBar, DummyProgressBar
       
    30 
       
    31 from yams import BadSchemaDefinition, schema as schemamod, buildobjs as ybo
       
    32 
       
    33 from cubicweb import Binary
       
    34 from cubicweb.schema import (KNOWN_RPROPERTIES, CONSTRAINTS, ETYPE_NAME_MAP,
       
    35                              VIRTUAL_RTYPES)
       
    36 from cubicweb.server import sqlutils, schema2sql as y2sql
       
    37 
       
    38 
       
    39 def group_mapping(cnx, interactive=True):
       
    40     """create a group mapping from an rql cursor
       
    41 
       
    42     A group mapping has standard group names as key (managers, owners at least)
       
    43     and the actual CWGroup entity's eid as associated value.
       
    44     In interactive mode (the default), missing groups'eid will be prompted
       
    45     from the user.
       
    46     """
       
    47     res = {}
       
    48     for eid, name in cnx.execute('Any G, N WHERE G is CWGroup, G name N',
       
    49                                     build_descr=False):
       
    50         res[name] = eid
       
    51     if not interactive:
       
    52         return res
       
    53     missing = [g for g in ('owners', 'managers', 'users', 'guests') if not g in res]
       
    54     if missing:
       
    55         print('some native groups are missing but the following groups have been found:')
       
    56         print('\n'.join('* %s (%s)' % (n, eid) for n, eid in res.items()))
       
    57         print()
       
    58         print('enter the eid of a to group to map to each missing native group')
       
    59         print('or just type enter to skip permissions granted to a group')
       
    60         for group in missing:
       
    61             while True:
       
    62                 value = raw_input('eid for group %s: ' % group).strip()
       
    63                 if not value:
       
    64                     continue
       
    65                 try:
       
    66                     eid = int(value)
       
    67                 except ValueError:
       
    68                     print('eid should be an integer')
       
    69                     continue
       
    70                 for eid_ in res.values():
       
    71                     if eid == eid_:
       
    72                         break
       
    73                 else:
       
    74                     print('eid is not a group eid')
       
    75                     continue
       
    76                 res[name] = eid
       
    77                 break
       
    78     return res
       
    79 
       
    80 def cstrtype_mapping(cnx):
       
    81     """cached constraint types mapping"""
       
    82     map = dict(cnx.execute('Any T, X WHERE X is CWConstraintType, X name T'))
       
    83     return map
       
    84 
       
    85 # schema / perms deserialization ##############################################
       
    86 
       
    87 def deserialize_schema(schema, cnx):
       
    88     """return a schema according to information stored in an rql database
       
    89     as CWRType and CWEType entities
       
    90     """
       
    91     repo = cnx.repo
       
    92     dbhelper = repo.system_source.dbhelper
       
    93 
       
    94     # Computed Rtype
       
    95     with cnx.ensure_cnx_set:
       
    96         tables = set(t.lower() for t in dbhelper.list_tables(cnx.cnxset.cu))
       
    97         has_computed_relations = 'cw_cwcomputedrtype' in tables
       
    98     # computed attribute
       
    99     try:
       
   100         cnx.system_sql("SELECT cw_formula FROM cw_CWAttribute")
       
   101         has_computed_attributes = True
       
   102     except Exception:
       
   103         cnx.rollback()
       
   104         has_computed_attributes = False
       
   105 
       
   106     # XXX bw compat (3.6 migration)
       
   107     sqlcu = cnx.system_sql("SELECT * FROM cw_CWRType WHERE cw_name='symetric'")
       
   108     if sqlcu.fetchall():
       
   109         sql = dbhelper.sql_rename_col('cw_CWRType', 'cw_symetric', 'cw_symmetric',
       
   110                                       dbhelper.TYPE_MAPPING['Boolean'], True)
       
   111         sqlcu.execute(sql)
       
   112         sqlcu.execute("UPDATE cw_CWRType SET cw_name='symmetric' WHERE cw_name='symetric'")
       
   113         cnx.commit()
       
   114     ertidx = {}
       
   115     copiedeids = set()
       
   116     permsidx = deserialize_ertype_permissions(cnx)
       
   117     schema.reading_from_database = True
       
   118     # load every entity types
       
   119     for eid, etype, desc in cnx.execute(
       
   120         'Any X, N, D WHERE X is CWEType, X name N, X description D',
       
   121         build_descr=False):
       
   122         # base types are already in the schema, skip them
       
   123         if etype in schemamod.BASE_TYPES:
       
   124             # just set the eid
       
   125             eschema = schema.eschema(etype)
       
   126             eschema.eid = eid
       
   127             ertidx[eid] = etype
       
   128             continue
       
   129         if etype in ETYPE_NAME_MAP:
       
   130             needcopy = False
       
   131             netype = ETYPE_NAME_MAP[etype]
       
   132             # can't use write rql queries at this point, use raw sql
       
   133             sqlexec = cnx.system_sql
       
   134             if sqlexec('SELECT 1 FROM %(p)sCWEType WHERE %(p)sname=%%(n)s'
       
   135                        % {'p': sqlutils.SQL_PREFIX}, {'n': netype}).fetchone():
       
   136                 # the new type already exists, we should copy (eg make existing
       
   137                 # instances of the old type instances of the new type)
       
   138                 assert etype.lower() != netype.lower()
       
   139                 needcopy = True
       
   140             else:
       
   141                 # the new type doesn't exist, we should rename
       
   142                 sqlexec('UPDATE %(p)sCWEType SET %(p)sname=%%(n)s WHERE %(p)seid=%%(x)s'
       
   143                         % {'p': sqlutils.SQL_PREFIX}, {'x': eid, 'n': netype})
       
   144                 if etype.lower() != netype.lower():
       
   145                     alter_table_sql = dbhelper.sql_rename_table(sqlutils.SQL_PREFIX+etype,
       
   146                                                                 sqlutils.SQL_PREFIX+netype)
       
   147                     sqlexec(alter_table_sql)
       
   148             sqlexec('UPDATE entities SET type=%(n)s WHERE type=%(x)s',
       
   149                     {'x': etype, 'n': netype})
       
   150             cnx.commit(False)
       
   151             tocleanup = [eid]
       
   152             tocleanup += (eid for eid, cached in repo._type_source_cache.items()
       
   153                           if etype == cached[0])
       
   154             repo.clear_caches(tocleanup)
       
   155             cnx.commit(False)
       
   156             if needcopy:
       
   157                 ertidx[eid] = netype
       
   158                 copiedeids.add(eid)
       
   159                 # copy / CWEType entity removal expected to be done through
       
   160                 # rename_entity_type in a migration script
       
   161                 continue
       
   162             etype = netype
       
   163         ertidx[eid] = etype
       
   164         eschema = schema.add_entity_type(
       
   165             ybo.EntityType(name=etype, description=desc, eid=eid))
       
   166         set_perms(eschema, permsidx)
       
   167     # load inheritance relations
       
   168     for etype, stype in cnx.execute(
       
   169         'Any XN, ETN WHERE X is CWEType, X name XN, X specializes ET, ET name ETN',
       
   170         build_descr=False):
       
   171         etype = ETYPE_NAME_MAP.get(etype, etype)
       
   172         stype = ETYPE_NAME_MAP.get(stype, stype)
       
   173         schema.eschema(etype)._specialized_type = stype
       
   174         schema.eschema(stype)._specialized_by.append(etype)
       
   175     if has_computed_relations:
       
   176         rset = cnx.execute(
       
   177             'Any X, N, R, D WHERE X is CWComputedRType, X name N, '
       
   178             'X rule R, X description D')
       
   179         for eid, rule_name, rule, description in rset.rows:
       
   180             rtype = ybo.ComputedRelation(name=rule_name, rule=rule, eid=eid,
       
   181                                          description=description)
       
   182             rschema = schema.add_relation_type(rtype)
       
   183             set_perms(rschema, permsidx)
       
   184     # load every relation types
       
   185     for eid, rtype, desc, sym, il, ftc in cnx.execute(
       
   186         'Any X,N,D,S,I,FTC WHERE X is CWRType, X name N, X description D, '
       
   187         'X symmetric S, X inlined I, X fulltext_container FTC', build_descr=False):
       
   188         ertidx[eid] = rtype
       
   189         rschema = schema.add_relation_type(
       
   190             ybo.RelationType(name=rtype, description=desc,
       
   191                              symmetric=bool(sym), inlined=bool(il),
       
   192                              fulltext_container=ftc, eid=eid))
       
   193     # remains to load every relation definitions (ie relations and attributes)
       
   194     cstrsidx = deserialize_rdef_constraints(cnx)
       
   195     pendingrdefs = []
       
   196     # closure to factorize common code of attribute/relation rdef addition
       
   197     def _add_rdef(rdefeid, seid, reid, oeid, **kwargs):
       
   198         rdef = ybo.RelationDefinition(ertidx[seid], ertidx[reid], ertidx[oeid],
       
   199                                       constraints=cstrsidx.get(rdefeid, ()),
       
   200                                       eid=rdefeid, **kwargs)
       
   201         if seid in copiedeids or oeid in copiedeids:
       
   202             # delay addition of this rdef. We'll insert them later if needed. We
       
   203             # have to do this because:
       
   204             #
       
   205             # * on etype renaming, we want relation of the old entity type being
       
   206             #   redirected to the new type during migration
       
   207             #
       
   208             # * in the case of a copy, we've to take care that rdef already
       
   209             #   existing in the schema are not overwritten by a redirected one,
       
   210             #   since we want correct eid on them (redirected rdef will be
       
   211             #   removed in rename_entity_type)
       
   212             pendingrdefs.append(rdef)
       
   213         else:
       
   214             # add_relation_def return a RelationDefinitionSchema if it has been
       
   215             # actually added (can be None on duplicated relation definitions,
       
   216             # e.g. if the relation type is marked as beeing symmetric)
       
   217             rdefs = schema.add_relation_def(rdef)
       
   218             if rdefs is not None:
       
   219                 ertidx[rdefeid] = rdefs
       
   220                 set_perms(rdefs, permsidx)
       
   221     # Get the type parameters for additional base types.
       
   222     try:
       
   223         extra_props = dict(cnx.execute('Any X, XTP WHERE X is CWAttribute, '
       
   224                                        'X extra_props XTP'))
       
   225     except Exception:
       
   226         cnx.critical('Previous CRITICAL notification about extra_props is not '
       
   227                      'a problem if you are migrating to cubicweb 3.17')
       
   228         extra_props = {} # not yet in the schema (introduced by 3.17 migration)
       
   229 
       
   230     # load attributes
       
   231     rql = ('Any X,SE,RT,OE,CARD,ORD,DESC,IDX,FTIDX,I18N,DFLT%(fm)s '
       
   232            'WHERE X is CWAttribute, X relation_type RT, X cardinality CARD,'
       
   233            '      X ordernum ORD, X indexed IDX, X description DESC, '
       
   234            '      X internationalizable I18N, X defaultval DFLT,%(fmsnip)s'
       
   235            '      X fulltextindexed FTIDX, X from_entity SE, X to_entity OE')
       
   236     if has_computed_attributes:
       
   237         rql = rql % {'fm': ',FM', 'fmsnip': 'X formula FM,'}
       
   238     else:
       
   239         rql = rql % {'fm': '', 'fmsnip': ''}
       
   240     for values in cnx.execute(rql, build_descr=False):
       
   241         attrs = dict(zip(
       
   242             ('rdefeid', 'seid', 'reid', 'oeid', 'cardinality',
       
   243              'order', 'description', 'indexed', 'fulltextindexed',
       
   244              'internationalizable', 'default', 'formula'), values))
       
   245         typeparams = extra_props.get(attrs['rdefeid'])
       
   246         attrs.update(json.loads(typeparams.getvalue().decode('ascii')) if typeparams else {})
       
   247         default = attrs['default']
       
   248         if default is not None:
       
   249             if isinstance(default, Binary):
       
   250                 # while migrating from 3.17 to 3.18, we still have to
       
   251                 # handle String defaults
       
   252                 attrs['default'] = default.unzpickle()
       
   253         _add_rdef(**attrs)
       
   254     # load relations
       
   255     for values in cnx.execute(
       
   256         'Any X,SE,RT,OE,CARD,ORD,DESC,C WHERE X is CWRelation, X relation_type RT,'
       
   257         'X cardinality CARD, X ordernum ORD, X description DESC, '
       
   258         'X from_entity SE, X to_entity OE, X composite C', build_descr=False):
       
   259         rdefeid, seid, reid, oeid, card, ord, desc, comp = values
       
   260         _add_rdef(rdefeid, seid, reid, oeid,
       
   261                   cardinality=card, description=desc, order=ord,
       
   262                   composite=comp)
       
   263     for rdef in pendingrdefs:
       
   264         try:
       
   265             rdefs = schema.add_relation_def(rdef)
       
   266         except BadSchemaDefinition:
       
   267             continue
       
   268         if rdefs is not None:
       
   269             set_perms(rdefs, permsidx)
       
   270     unique_togethers = {}
       
   271     rset = cnx.execute(
       
   272     'Any X,E,R WHERE '
       
   273     'X is CWUniqueTogetherConstraint, '
       
   274     'X constraint_of E, X relations R', build_descr=False)
       
   275     for values in rset:
       
   276         uniquecstreid, eeid, releid = values
       
   277         eschema = schema.schema_by_eid(eeid)
       
   278         relations = unique_togethers.setdefault(uniquecstreid, (eschema, []))
       
   279         rel = ertidx[releid]
       
   280         if isinstance(rel, schemamod.RelationDefinitionSchema):
       
   281             # not yet migrated 3.9 database ('relations' target type changed
       
   282             # to CWRType in 3.10)
       
   283             rtype = rel.rtype.type
       
   284         else:
       
   285             rtype = str(rel)
       
   286         relations[1].append(rtype)
       
   287     for eschema, unique_together in unique_togethers.values():
       
   288         eschema._unique_together.append(tuple(sorted(unique_together)))
       
   289     schema.infer_specialization_rules()
       
   290     cnx.commit()
       
   291     schema.finalize()
       
   292     schema.reading_from_database = False
       
   293 
       
   294 
       
   295 def deserialize_ertype_permissions(cnx):
       
   296     """return sect action:groups associations for the given
       
   297     entity or relation schema with its eid, according to schema's
       
   298     permissions stored in the database as [read|add|delete|update]_permission
       
   299     relations between CWEType/CWRType and CWGroup entities
       
   300     """
       
   301     res = {}
       
   302     for action in ('read', 'add', 'update', 'delete'):
       
   303         rql = 'Any E,N WHERE G is CWGroup, G name N, E %s_permission G' % action
       
   304         for eid, gname in cnx.execute(rql, build_descr=False):
       
   305             res.setdefault(eid, {}).setdefault(action, []).append(gname)
       
   306         rql = ('Any E,X,EXPR,V WHERE X is RQLExpression, X expression EXPR, '
       
   307                'E %s_permission X, X mainvars V' % action)
       
   308         for eid, expreid, expr, mainvars in cnx.execute(rql, build_descr=False):
       
   309             # we don't know yet if it's a rql expr for an entity or a relation,
       
   310             # so append a tuple to differentiate from groups and so we'll be
       
   311             # able to instantiate it later
       
   312             res.setdefault(eid, {}).setdefault(action, []).append( (expr, mainvars, expreid) )
       
   313     return res
       
   314 
       
   315 def deserialize_rdef_constraints(cnx):
       
   316     """return the list of relation definition's constraints as instances"""
       
   317     res = {}
       
   318     for rdefeid, ceid, ct, val in cnx.execute(
       
   319         'Any E, X,TN,V WHERE E constrained_by X, X is CWConstraint, '
       
   320         'X cstrtype T, T name TN, X value V', build_descr=False):
       
   321         cstr = CONSTRAINTS[ct].deserialize(val)
       
   322         cstr.eid = ceid
       
   323         res.setdefault(rdefeid, []).append(cstr)
       
   324     return res
       
   325 
       
   326 def set_perms(erschema, permsidx):
       
   327     """set permissions on the given erschema according to the permission
       
   328     definition dictionary as built by deserialize_ertype_permissions for a
       
   329     given erschema's eid
       
   330     """
       
   331     # reset erschema permissions here to avoid getting yams default anyway
       
   332     erschema.permissions = dict((action, ()) for action in erschema.ACTIONS)
       
   333     try:
       
   334         thispermsdict = permsidx[erschema.eid]
       
   335     except KeyError:
       
   336         return
       
   337     for action, somethings in thispermsdict.items():
       
   338         erschema.permissions[action] = tuple(
       
   339             isinstance(p, tuple) and erschema.rql_expression(*p) or p
       
   340             for p in somethings)
       
   341 
       
   342 
       
   343 # schema / perms serialization ################################################
       
   344 
       
   345 def serialize_schema(cnx, schema):
       
   346     """synchronize schema and permissions in the database according to
       
   347     current schema
       
   348     """
       
   349     _title = '-> storing the schema in the database '
       
   350     print(_title, end=' ')
       
   351     execute = cnx.execute
       
   352     eschemas = schema.entities()
       
   353     pb_size = (len(eschemas + schema.relations())
       
   354                + len(CONSTRAINTS)
       
   355                + len([x for x in eschemas if x.specializes()]))
       
   356     if sys.stdout.isatty():
       
   357         pb = ProgressBar(pb_size, title=_title)
       
   358     else:
       
   359         pb = DummyProgressBar()
       
   360     groupmap = group_mapping(cnx, interactive=False)
       
   361     # serialize all entity types, assuring CWEType is serialized first for proper
       
   362     # is / is_instance_of insertion
       
   363     eschemas.remove(schema.eschema('CWEType'))
       
   364     eschemas.insert(0, schema.eschema('CWEType'))
       
   365     for eschema in eschemas:
       
   366         execschemarql(execute, eschema, eschema2rql(eschema, groupmap))
       
   367         pb.update()
       
   368     # serialize constraint types
       
   369     cstrtypemap = {}
       
   370     rql = 'INSERT CWConstraintType X: X name %(ct)s'
       
   371     for cstrtype in CONSTRAINTS:
       
   372         cstrtypemap[cstrtype] = execute(rql, {'ct': text_type(cstrtype)},
       
   373                                         build_descr=False)[0][0]
       
   374         pb.update()
       
   375     # serialize relations
       
   376     for rschema in schema.relations():
       
   377         # skip virtual relations such as eid, has_text and identity
       
   378         if rschema in VIRTUAL_RTYPES:
       
   379             pb.update()
       
   380             continue
       
   381         if rschema.rule:
       
   382             execschemarql(execute, rschema, crschema2rql(rschema, groupmap))
       
   383             pb.update()
       
   384             continue
       
   385         execschemarql(execute, rschema, rschema2rql(rschema, addrdef=False))
       
   386         if rschema.symmetric:
       
   387             rdefs = [rdef for k, rdef in rschema.rdefs.items()
       
   388                      if (rdef.subject, rdef.object) == k]
       
   389         else:
       
   390             rdefs = rschema.rdefs.values()
       
   391         for rdef in rdefs:
       
   392             execschemarql(execute, rdef,
       
   393                           rdef2rql(rdef, cstrtypemap, groupmap))
       
   394         pb.update()
       
   395     # serialize unique_together constraints
       
   396     for eschema in eschemas:
       
   397         if eschema._unique_together:
       
   398             execschemarql(execute, eschema, uniquetogether2rqls(eschema))
       
   399     # serialize yams inheritance relationships
       
   400     for rql, kwargs in specialize2rql(schema):
       
   401         execute(rql, kwargs, build_descr=False)
       
   402         pb.update()
       
   403     print()
       
   404 
       
   405 
       
   406 # high level serialization functions
       
   407 
       
   408 def execschemarql(execute, schema, rqls):
       
   409     for rql, kwargs in rqls:
       
   410         kwargs['x'] = schema.eid
       
   411         rset = execute(rql, kwargs, build_descr=False)
       
   412         if schema.eid is None:
       
   413             schema.eid = rset[0][0]
       
   414         else:
       
   415             assert rset
       
   416 
       
   417 def erschema2rql(erschema, groupmap):
       
   418     if isinstance(erschema, schemamod.EntitySchema):
       
   419         return eschema2rql(erschema, groupmap=groupmap)
       
   420     return rschema2rql(erschema, groupmap=groupmap)
       
   421 
       
   422 def specialize2rql(schema):
       
   423     for eschema in schema.entities():
       
   424         if eschema.final:
       
   425             continue
       
   426         for rql, kwargs in eschemaspecialize2rql(eschema):
       
   427             yield rql, kwargs
       
   428 
       
   429 # etype serialization
       
   430 
       
   431 def eschema2rql(eschema, groupmap=None):
       
   432     """return a list of rql insert statements to enter an entity schema
       
   433     in the database as an CWEType entity
       
   434     """
       
   435     relations, values = eschema_relations_values(eschema)
       
   436     # NOTE: 'specializes' relation can't be inserted here since there's no
       
   437     # way to make sure the parent type is inserted before the child type
       
   438     yield 'INSERT CWEType X: %s' % ','.join(relations) , values
       
   439     # entity permissions
       
   440     if groupmap is not None:
       
   441         for rql, args in _erperms2rql(eschema, groupmap):
       
   442             yield rql, args
       
   443 
       
   444 def eschema_relations_values(eschema):
       
   445     values = _ervalues(eschema)
       
   446     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
       
   447     return relations, values
       
   448 
       
   449 def eschemaspecialize2rql(eschema):
       
   450     specialized_type = eschema.specializes()
       
   451     if specialized_type:
       
   452         values = {'x': eschema.eid, 'et': specialized_type.eid}
       
   453         yield 'SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s', values
       
   454 
       
   455 def uniquetogether2rqls(eschema):
       
   456     rql_args = []
       
   457     # robustness against duplicated CWUniqueTogetherConstraint (pre 3.18)
       
   458     columnset = set()
       
   459     for columns in eschema._unique_together:
       
   460         if columns in columnset:
       
   461             print('schemaserial: skipping duplicate unique together %r %r' %
       
   462                   (eschema.type, columns))
       
   463             continue
       
   464         columnset.add(columns)
       
   465         rql, args = _uniquetogether2rql(eschema, columns)
       
   466         args['name'] = y2sql.unique_index_name(eschema, columns)
       
   467         rql_args.append((rql, args))
       
   468     return rql_args
       
   469 
       
   470 def _uniquetogether2rql(eschema, unique_together):
       
   471     relations = []
       
   472     restrictions = []
       
   473     substs = {}
       
   474     for i, name in enumerate(unique_together):
       
   475         rschema = eschema.schema.rschema(name)
       
   476         rtype = 'T%d' % i
       
   477         substs[rtype] = text_type(rschema.type)
       
   478         relations.append('C relations %s' % rtype)
       
   479         restrictions.append('%(rtype)s name %%(%(rtype)s)s' % {'rtype': rtype})
       
   480     relations = ', '.join(relations)
       
   481     restrictions = ', '.join(restrictions)
       
   482     rql = ('INSERT CWUniqueTogetherConstraint C: C name %%(name)s, C constraint_of X, %s '
       
   483            'WHERE X eid %%(x)s, %s')
       
   484     return rql % (relations, restrictions), substs
       
   485 
       
   486 
       
   487 def _ervalues(erschema):
       
   488     try:
       
   489         type_ = text_type(erschema.type)
       
   490     except UnicodeDecodeError as e:
       
   491         raise Exception("can't decode %s [was %s]" % (erschema.type, e))
       
   492     try:
       
   493         desc = text_type(erschema.description) or u''
       
   494     except UnicodeDecodeError as e:
       
   495         raise Exception("can't decode %s [was %s]" % (erschema.description, e))
       
   496     return {
       
   497         'name': type_,
       
   498         'final': erschema.final,
       
   499         'description': desc,
       
   500         }
       
   501 
       
   502 # rtype serialization
       
   503 
       
   504 def rschema2rql(rschema, cstrtypemap=None, addrdef=True, groupmap=None):
       
   505     """generate rql insert statements to enter a relation schema
       
   506     in the database as an CWRType entity
       
   507     """
       
   508     if rschema.type == 'has_text':
       
   509         return
       
   510     relations, values = rschema_relations_values(rschema)
       
   511     yield 'INSERT CWRType X: %s' % ','.join(relations), values
       
   512     if addrdef:
       
   513         assert cstrtypemap
       
   514         # sort for testing purpose
       
   515         for rdef in sorted(rschema.rdefs.values(),
       
   516                            key=lambda x: (x.subject, x.object)):
       
   517             for rql, values in rdef2rql(rdef, cstrtypemap, groupmap):
       
   518                 yield rql, values
       
   519 
       
   520 def rschema_relations_values(rschema):
       
   521     values = _ervalues(rschema)
       
   522     values['final'] = rschema.final
       
   523     values['symmetric'] = rschema.symmetric
       
   524     values['inlined'] = rschema.inlined
       
   525     if PY2 and isinstance(rschema.fulltext_container, str):
       
   526         values['fulltext_container'] = unicode(rschema.fulltext_container)
       
   527     else:
       
   528         values['fulltext_container'] = rschema.fulltext_container
       
   529     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
       
   530     return relations, values
       
   531 
       
   532 def crschema2rql(crschema, groupmap):
       
   533     relations, values = crschema_relations_values(crschema)
       
   534     yield 'INSERT CWComputedRType X: %s' % ','.join(relations), values
       
   535     if groupmap:
       
   536         for rql, args in _erperms2rql(crschema, groupmap):
       
   537             yield rql, args
       
   538 
       
   539 def crschema_relations_values(crschema):
       
   540     values = _ervalues(crschema)
       
   541     values['rule'] = text_type(crschema.rule)
       
   542     # XXX why oh why?
       
   543     del values['final']
       
   544     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
       
   545     return relations, values
       
   546 
       
   547 # rdef serialization
       
   548 
       
   549 def rdef2rql(rdef, cstrtypemap, groupmap=None):
       
   550     # don't serialize inferred relations
       
   551     if rdef.infered:
       
   552         return
       
   553     relations, values = _rdef_values(rdef)
       
   554     relations.append('X relation_type ER,X from_entity SE,X to_entity OE')
       
   555     values.update({'se': rdef.subject.eid, 'rt': rdef.rtype.eid, 'oe': rdef.object.eid})
       
   556     if rdef.final:
       
   557         etype = 'CWAttribute'
       
   558     else:
       
   559         etype = 'CWRelation'
       
   560     yield 'INSERT %s X: %s WHERE SE eid %%(se)s,ER eid %%(rt)s,OE eid %%(oe)s' % (
       
   561         etype, ','.join(relations), ), values
       
   562     for rql, values in constraints2rql(cstrtypemap, rdef.constraints):
       
   563         yield rql, values
       
   564     # no groupmap means "no security insertion"
       
   565     if groupmap:
       
   566         for rql, args in _erperms2rql(rdef, groupmap):
       
   567             yield rql, args
       
   568 
       
   569 _IGNORED_PROPS = ['eid', 'constraints', 'uid', 'infered', 'permissions']
       
   570 
       
   571 def _rdef_values(rdef):
       
   572     amap = {'order': 'ordernum', 'default': 'defaultval'}
       
   573     values = {}
       
   574     extra = {}
       
   575     for prop in rdef.rproperty_defs(rdef.object):
       
   576         if prop in _IGNORED_PROPS:
       
   577             continue
       
   578         value = getattr(rdef, prop)
       
   579         if prop not in KNOWN_RPROPERTIES:
       
   580             extra[prop] = value
       
   581             continue
       
   582         # XXX type cast really necessary?
       
   583         if prop in ('indexed', 'fulltextindexed', 'internationalizable'):
       
   584             value = bool(value)
       
   585         elif prop == 'ordernum':
       
   586             value = int(value)
       
   587         elif PY2 and isinstance(value, str):
       
   588             value = unicode(value)
       
   589         if value is not None and prop == 'default':
       
   590             value = Binary.zpickle(value)
       
   591         values[amap.get(prop, prop)] = value
       
   592     if extra:
       
   593         values['extra_props'] = Binary(json.dumps(extra).encode('ascii'))
       
   594     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
       
   595     return relations, values
       
   596 
       
   597 def constraints2rql(cstrtypemap, constraints, rdefeid=None):
       
   598     for constraint in constraints:
       
   599         values = {'ct': cstrtypemap[constraint.type()],
       
   600                   'value': text_type(constraint.serialize()),
       
   601                   'x': rdefeid} # when not specified, will have to be set by the caller
       
   602         yield 'INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE \
       
   603 CT eid %(ct)s, EDEF eid %(x)s', values
       
   604 
       
   605 
       
   606 def _erperms2rql(erschema, groupmap):
       
   607     """return rql insert statements to enter the entity or relation
       
   608     schema's permissions in the database as
       
   609     [read|add|delete|update]_permission relations between CWEType/CWRType
       
   610     and CWGroup entities
       
   611     """
       
   612     for action in erschema.ACTIONS:
       
   613         try:
       
   614             grantedto = erschema.action_permissions(action)
       
   615         except KeyError:
       
   616             # may occurs when modifying persistent schema
       
   617             continue
       
   618         for group_or_rqlexpr in grantedto:
       
   619             if isinstance(group_or_rqlexpr, string_types):
       
   620                 # group
       
   621                 try:
       
   622                     yield ('SET X %s_permission Y WHERE Y eid %%(g)s, X eid %%(x)s' % action,
       
   623                            {'g': groupmap[group_or_rqlexpr]})
       
   624                 except KeyError:
       
   625                     print("WARNING: group %s used in permissions for %s was ignored because it doesn't exist."
       
   626                           " You may want to add it into a precreate.py file" % (group_or_rqlexpr, erschema))
       
   627                     continue
       
   628             else:
       
   629                 # rqlexpr
       
   630                 rqlexpr = group_or_rqlexpr
       
   631                 yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
       
   632                        'E mainvars %%(v)s, X %s_permission E WHERE X eid %%(x)s' % action,
       
   633                        {'e': text_type(rqlexpr.expression),
       
   634                         'v': text_type(','.join(sorted(rqlexpr.mainvars))),
       
   635                         't': text_type(rqlexpr.__class__.__name__)})
       
   636 
       
   637 # update functions
       
   638 
       
   639 def updateeschema2rql(eschema, eid):
       
   640     relations, values = eschema_relations_values(eschema)
       
   641     values['x'] = eid
       
   642     yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values
       
   643 
       
   644 def updaterschema2rql(rschema, eid):
       
   645     if rschema.rule:
       
   646         yield ('SET X rule %(r)s WHERE X eid %(x)s',
       
   647                {'x': eid, 'r': text_type(rschema.rule)})
       
   648     else:
       
   649         relations, values = rschema_relations_values(rschema)
       
   650         values['x'] = eid
       
   651         yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values
       
   652 
       
   653 def updaterdef2rql(rdef, eid):
       
   654     relations, values = _rdef_values(rdef)
       
   655     values['x'] = eid
       
   656     yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values