cubicweb/server/migractions.py
changeset 11348 70337ad23145
parent 11273 c655e19cbc35
child 11413 c172fa18565e
equal deleted inserted replaced
11347:b4dcfd734686 11348:70337ad23145
     1 # copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     3 #
     3 #
     4 # This file is part of CubicWeb.
     4 # This file is part of CubicWeb.
     5 #
     5 #
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
    37 import shutil
    37 import shutil
    38 import os.path as osp
    38 import os.path as osp
    39 from datetime import datetime
    39 from datetime import datetime
    40 from glob import glob
    40 from glob import glob
    41 from copy import copy
    41 from copy import copy
    42 from warnings import warn
       
    43 from contextlib import contextmanager
    42 from contextlib import contextmanager
    44 
    43 
    45 from six import PY2, text_type
    44 from six import PY2, text_type
    46 
    45 
    47 from logilab.common.deprecation import deprecated
    46 from logilab.common.deprecation import deprecated
    67 
    66 
    68 class ClearGroupMap(hook.Hook):
    67 class ClearGroupMap(hook.Hook):
    69     __regid__ = 'cw.migration.clear_group_mapping'
    68     __regid__ = 'cw.migration.clear_group_mapping'
    70     __select__ = hook.Hook.__select__ & is_instance('CWGroup')
    69     __select__ = hook.Hook.__select__ & is_instance('CWGroup')
    71     events = ('after_add_entity', 'after_update_entity',)
    70     events = ('after_add_entity', 'after_update_entity',)
       
    71 
    72     def __call__(self):
    72     def __call__(self):
    73         clear_cache(self.mih, 'group_mapping')
    73         clear_cache(self.mih, 'group_mapping')
    74         self.mih._synchronized.clear()
    74         self.mih._synchronized.clear()
    75 
    75 
    76     @classmethod
    76     @classmethod
    77     def mih_register(cls, repo):
    77     def mih_register(cls, repo):
    78         # may be already registered in tests (e.g. unittest_migractions at
    78         # may be already registered in tests (e.g. unittest_migractions at
    79         # least)
    79         # least)
    80         if not cls.__regid__ in repo.vreg['after_add_entity_hooks']:
    80         if cls.__regid__ not in repo.vreg['after_add_entity_hooks']:
    81             repo.vreg.register(ClearGroupMap)
    81             repo.vreg.register(ClearGroupMap)
    82 
    82 
    83 
    83 
    84 class ServerMigrationHelper(MigrationHelper):
    84 class ServerMigrationHelper(MigrationHelper):
    85     """specific migration helper for server side migration scripts,
    85     """specific migration helper for server side migration scripts,
   174             super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
   174             super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
   175 
   175 
   176     def cmd_process_script(self, migrscript, funcname=None, *args, **kwargs):
   176     def cmd_process_script(self, migrscript, funcname=None, *args, **kwargs):
   177         try:
   177         try:
   178             return super(ServerMigrationHelper, self).cmd_process_script(
   178             return super(ServerMigrationHelper, self).cmd_process_script(
   179                   migrscript, funcname, *args, **kwargs)
   179                 migrscript, funcname, *args, **kwargs)
   180         except ExecutionError as err:
   180         except ExecutionError as err:
   181             sys.stderr.write("-> %s\n" % err)
   181             sys.stderr.write("-> %s\n" % err)
   182         except BaseException:
   182         except BaseException:
   183             self.rollback()
   183             self.rollback()
   184             raise
   184             raise
   204             print('-> no backup done.')
   204             print('-> no backup done.')
   205             return
   205             return
   206         elif askconfirm and not self.confirm('Backup %s database?' % config.appid):
   206         elif askconfirm and not self.confirm('Backup %s database?' % config.appid):
   207             print('-> no backup done.')
   207             print('-> no backup done.')
   208             return
   208             return
   209         open(backupfile,'w').close() # kinda lock
   209         open(backupfile,'w').close()  # kinda lock
   210         os.chmod(backupfile, 0o600)
   210         os.chmod(backupfile, 0o600)
   211         # backup
   211         # backup
   212         source = repo.system_source
   212         source = repo.system_source
   213         tmpdir = tempfile.mkdtemp()
   213         tmpdir = tempfile.mkdtemp()
   214         try:
   214         try:
   233                     bkup.add(osp.join(tmpdir, filename), filename)
   233                     bkup.add(osp.join(tmpdir, filename), filename)
   234                 bkup.close()
   234                 bkup.close()
   235                 # call hooks
   235                 # call hooks
   236                 repo.hm.call_hooks('server_backup', repo=repo, timestamp=timestamp)
   236                 repo.hm.call_hooks('server_backup', repo=repo, timestamp=timestamp)
   237                 # done
   237                 # done
   238                 print('-> backup file',  backupfile)
   238                 print('-> backup file', backupfile)
   239         finally:
   239         finally:
   240             shutil.rmtree(tmpdir)
   240             shutil.rmtree(tmpdir)
   241 
   241 
   242     def restore_database(self, backupfile, drop=True, askconfirm=True, format='native'):
   242     def restore_database(self, backupfile, drop=True, askconfirm=True, format='native'):
   243         # check
   243         # check
   255             shutil.copy(backupfile, osp.join(tmpdir, 'system'))
   255             shutil.copy(backupfile, osp.join(tmpdir, 'system'))
   256         else:
   256         else:
   257             for name in bkup.getnames():
   257             for name in bkup.getnames():
   258                 if name[0] in '/.':
   258                 if name[0] in '/.':
   259                     raise ExecutionError('Security check failed, path starts with "/" or "."')
   259                     raise ExecutionError('Security check failed, path starts with "/" or "."')
   260             bkup.close() # XXX seek error if not close+open !?!
   260             bkup.close()  # XXX seek error if not close+open !?!
   261             bkup = tarfile.open(backupfile, 'r|gz')
   261             bkup = tarfile.open(backupfile, 'r|gz')
   262             bkup.extractall(path=tmpdir)
   262             bkup.extractall(path=tmpdir)
   263             bkup.close()
   263             bkup.close()
   264         if osp.isfile(osp.join(tmpdir, 'format.txt')):
   264         if osp.isfile(osp.join(tmpdir, 'format.txt')):
   265             with open(osp.join(tmpdir, 'format.txt')) as format_file:
   265             with open(osp.join(tmpdir, 'format.txt')) as format_file:
   301                         'rql': self.rqlexec,
   301                         'rql': self.rqlexec,
   302                         'rqliter': self.rqliter,
   302                         'rqliter': self.rqliter,
   303                         'schema': self.repo.get_schema(),
   303                         'schema': self.repo.get_schema(),
   304                         'cnx': self.cnx,
   304                         'cnx': self.cnx,
   305                         'fsschema': self.fs_schema,
   305                         'fsschema': self.fs_schema,
   306                         'session' : self.cnx,
   306                         'session': self.cnx,
   307                         'repo' : self.repo,
   307                         'repo': self.repo,
   308                         })
   308                         })
   309         return context
   309         return context
   310 
   310 
   311     @cached
   311     @cached
   312     def group_mapping(self):
   312     def group_mapping(self):
   389             # handle groups
   389             # handle groups
   390             newgroups = list(erschema.get_groups(action))
   390             newgroups = list(erschema.get_groups(action))
   391             for geid, gname in self.rqlexec('Any G, GN WHERE T %s G, G name GN, '
   391             for geid, gname in self.rqlexec('Any G, GN WHERE T %s G, G name GN, '
   392                                             'T eid %%(x)s' % perm, {'x': teid},
   392                                             'T eid %%(x)s' % perm, {'x': teid},
   393                                             ask_confirm=False):
   393                                             ask_confirm=False):
   394                 if not gname in newgroups:
   394                 if gname not in newgroups:
   395                     if not confirm or self.confirm('Remove %s permission of %s to %s?'
   395                     if not confirm or self.confirm('Remove %s permission of %s to %s?'
   396                                                    % (action, erschema, gname)):
   396                                                    % (action, erschema, gname)):
   397                         self.rqlexec('DELETE T %s G WHERE G eid %%(x)s, T eid %s'
   397                         self.rqlexec('DELETE T %s G WHERE G eid %%(x)s, T eid %s'
   398                                      % (perm, teid),
   398                                      % (perm, teid),
   399                                      {'x': geid}, ask_confirm=False)
   399                                      {'x': geid}, ask_confirm=False)
   412             # handle rql expressions
   412             # handle rql expressions
   413             newexprs = dict((expr.expression, expr) for expr in erschema.get_rqlexprs(action))
   413             newexprs = dict((expr.expression, expr) for expr in erschema.get_rqlexprs(action))
   414             for expreid, expression in self.rqlexec('Any E, EX WHERE T %s E, E expression EX, '
   414             for expreid, expression in self.rqlexec('Any E, EX WHERE T %s E, E expression EX, '
   415                                                     'T eid %s' % (perm, teid),
   415                                                     'T eid %s' % (perm, teid),
   416                                                     ask_confirm=False):
   416                                                     ask_confirm=False):
   417                 if not expression in newexprs:
   417                 if expression not in newexprs:
   418                     if not confirm or self.confirm('Remove %s expression for %s permission of %s?'
   418                     if not confirm or self.confirm('Remove %s expression for %s permission of %s?'
   419                                                    % (expression, action, erschema)):
   419                                                    % (expression, action, erschema)):
   420                         # deleting the relation will delete the expression entity
   420                         # deleting the relation will delete the expression entity
   421                         self.rqlexec('DELETE T %s E WHERE E eid %%(x)s, T eid %s'
   421                         self.rqlexec('DELETE T %s E WHERE E eid %%(x)s, T eid %s'
   422                                      % (perm, teid),
   422                                      % (perm, teid),
   456         rschema = self.fs_schema.rschema(rtype)
   456         rschema = self.fs_schema.rschema(rtype)
   457         reporschema = self.repo.schema.rschema(rtype)
   457         reporschema = self.repo.schema.rschema(rtype)
   458         if syncprops:
   458         if syncprops:
   459             assert reporschema.eid, reporschema
   459             assert reporschema.eid, reporschema
   460             self.rqlexecall(ss.updaterschema2rql(rschema, reporschema.eid),
   460             self.rqlexecall(ss.updaterschema2rql(rschema, reporschema.eid),
   461                             ask_confirm=self.verbosity>=2)
   461                             ask_confirm=self.verbosity >= 2)
   462         if rschema.rule:
   462         if rschema.rule:
   463             if syncperms:
   463             if syncperms:
   464                 self._synchronize_permissions(rschema, reporschema.eid)
   464                 self._synchronize_permissions(rschema, reporschema.eid)
   465         elif syncrdefs:
   465         elif syncrdefs:
   466             for subj, obj in rschema.rdefs:
   466             for subj, obj in rschema.rdefs:
   490             self._synchronized.add(etype)
   490             self._synchronized.add(etype)
   491         repoeschema = self.repo.schema.eschema(etype)
   491         repoeschema = self.repo.schema.eschema(etype)
   492         try:
   492         try:
   493             eschema = self.fs_schema.eschema(etype)
   493             eschema = self.fs_schema.eschema(etype)
   494         except KeyError:
   494         except KeyError:
   495             return # XXX somewhat unexpected, no?...
   495             return  # XXX somewhat unexpected, no?...
   496         if syncprops:
   496         if syncprops:
   497             repospschema = repoeschema.specializes()
   497             repospschema = repoeschema.specializes()
   498             espschema = eschema.specializes()
   498             espschema = eschema.specializes()
   499             if repospschema and not espschema:
   499             if repospschema and not espschema:
   500                 self.rqlexec('DELETE X specializes Y WHERE X is CWEType, X name %(x)s',
   500                 self.rqlexec('DELETE X specializes Y WHERE X is CWEType, X name %(x)s',
   511         if syncrdefs:
   511         if syncrdefs:
   512             for rschema, targettypes, role in eschema.relation_definitions(True):
   512             for rschema, targettypes, role in eschema.relation_definitions(True):
   513                 if rschema in VIRTUAL_RTYPES:
   513                 if rschema in VIRTUAL_RTYPES:
   514                     continue
   514                     continue
   515                 if role == 'subject':
   515                 if role == 'subject':
   516                     if not rschema in repoeschema.subject_relations():
   516                     if rschema not in repoeschema.subject_relations():
   517                         continue
   517                         continue
   518                     subjtypes, objtypes = [etype], targettypes
   518                     subjtypes, objtypes = [etype], targettypes
   519                 else: # role == 'object'
   519                 else:  # role == 'object'
   520                     if not rschema in repoeschema.object_relations():
   520                     if rschema not in repoeschema.object_relations():
   521                         continue
   521                         continue
   522                     subjtypes, objtypes = targettypes, [etype]
   522                     subjtypes, objtypes = targettypes, [etype]
   523                 self._synchronize_rschema(rschema, syncrdefs=False,
   523                 self._synchronize_rschema(rschema, syncrdefs=False,
   524                                           syncprops=syncprops, syncperms=syncperms)
   524                                           syncprops=syncprops, syncperms=syncperms)
   525                 if rschema.rule: # rdef for computed rtype are infered hence should not be
   525                 if rschema.rule:  # rdef for computed rtype are infered hence should not be
   526                                  # synchronized
   526                                   # synchronized
   527                     continue
   527                     continue
   528                 reporschema = self.repo.schema.rschema(rschema)
   528                 reporschema = self.repo.schema.rschema(rschema)
   529                 for subj in subjtypes:
   529                 for subj in subjtypes:
   530                     for obj in objtypes:
   530                     for obj in objtypes:
   531                         if (subj, obj) not in reporschema.rdefs:
   531                         if (subj, obj) not in reporschema.rdefs:
   532                             continue
   532                             continue
   533                         self._synchronize_rdef_schema(subj, rschema, obj,
   533                         self._synchronize_rdef_schema(subj, rschema, obj,
   534                                                       syncprops=syncprops, syncperms=syncperms)
   534                                                       syncprops=syncprops, syncperms=syncperms)
   535         if syncprops: # need to process __unique_together__ after rdefs were processed
   535         if syncprops:  # need to process __unique_together__ after rdefs were processed
   536             # mappings from constraint name to columns
   536             # mappings from constraint name to columns
   537             # filesystem (fs) and repository (repo) wise
   537             # filesystem (fs) and repository (repo) wise
   538             fs = {}
   538             fs = {}
   539             repo = {}
   539             repo = {}
   540             for cols in eschema._unique_together or ():
   540             for cols in eschema._unique_together or ():
   590             self._synchronized.add((subjtype, rschema, objtype))
   590             self._synchronized.add((subjtype, rschema, objtype))
   591             if rschema.symmetric:
   591             if rschema.symmetric:
   592                 self._synchronized.add((objtype, rschema, subjtype))
   592                 self._synchronized.add((objtype, rschema, subjtype))
   593         rdef = rschema.rdef(subjtype, objtype)
   593         rdef = rschema.rdef(subjtype, objtype)
   594         if rdef.infered:
   594         if rdef.infered:
   595             return # don't try to synchronize infered relation defs
   595             return  # don't try to synchronize infered relation defs
   596         repordef = reporschema.rdef(subjtype, objtype)
   596         repordef = reporschema.rdef(subjtype, objtype)
   597         confirm = self.verbosity >= 2
   597         confirm = self.verbosity >= 2
   598         if syncprops:
   598         if syncprops:
   599             # properties
   599             # properties
   600             self.rqlexecall(ss.updaterdef2rql(rdef, repordef.eid),
   600             self.rqlexecall(ss.updaterdef2rql(rdef, repordef.eid),
   617             # 2. add new constraints
   617             # 2. add new constraints
   618             cstrtype_map = self.cstrtype_mapping()
   618             cstrtype_map = self.cstrtype_mapping()
   619             self.rqlexecall(ss.constraints2rql(cstrtype_map, newconstraints,
   619             self.rqlexecall(ss.constraints2rql(cstrtype_map, newconstraints,
   620                                                repordef.eid),
   620                                                repordef.eid),
   621                             ask_confirm=confirm)
   621                             ask_confirm=confirm)
   622         if syncperms and not rschema in VIRTUAL_RTYPES:
   622         if syncperms and rschema not in VIRTUAL_RTYPES:
   623             self._synchronize_permissions(rdef, repordef.eid)
   623             self._synchronize_permissions(rdef, repordef.eid)
   624 
   624 
   625     # base actions ############################################################
   625     # base actions ############################################################
   626 
   626 
   627     def checkpoint(self, ask_confirm=True):
   627     def checkpoint(self, ask_confirm=True):
   628         """checkpoint action"""
   628         """checkpoint action"""
   629         if not ask_confirm or self.confirm('Commit now ?', shell=False):
   629         if not ask_confirm or self.confirm('Commit now ?', shell=False):
   630             self.commit()
   630             self.commit()
   631 
   631 
   632     def cmd_add_cube(self, cube, update_database=True):
   632     def cmd_add_cube(self, cube, update_database=True):
   633         self.cmd_add_cubes( (cube,), update_database)
   633         self.cmd_add_cubes((cube,), update_database)
   634 
   634 
   635     def cmd_add_cubes(self, cubes, update_database=True):
   635     def cmd_add_cubes(self, cubes, update_database=True):
   636         """update_database is telling if the database schema should be updated
   636         """update_database is telling if the database schema should be updated
   637         or if only the relevant eproperty should be inserted (for the case where
   637         or if only the relevant eproperty should be inserted (for the case where
   638         a cube has been extracted from an existing instance, so the
   638         a cube has been extracted from an existing instance, so the
   640         """
   640         """
   641         newcubes = super(ServerMigrationHelper, self).cmd_add_cubes(cubes)
   641         newcubes = super(ServerMigrationHelper, self).cmd_add_cubes(cubes)
   642         if not newcubes:
   642         if not newcubes:
   643             return
   643             return
   644         for cube in newcubes:
   644         for cube in newcubes:
   645             self.cmd_set_property('system.version.'+cube,
   645             self.cmd_set_property('system.version.' + cube,
   646                                   self.config.cube_version(cube))
   646                                   self.config.cube_version(cube))
   647             # ensure added cube is in config cubes
   647             # ensure added cube is in config cubes
   648             # XXX worth restoring on error?
   648             # XXX worth restoring on error?
   649             if not cube in self.config._cubes:
   649             if cube not in self.config._cubes:
   650                 self.config._cubes += (cube,)
   650                 self.config._cubes += (cube,)
   651         if not update_database:
   651         if not update_database:
   652             self.commit()
   652             self.commit()
   653             return
   653             return
   654         newcubes_schema = self.config.load_schema(construction_mode='non-strict')
   654         newcubes_schema = self.config.load_schema(construction_mode='non-strict')
   656         # etc. and fsschema of migration script contexts
   656         # etc. and fsschema of migration script contexts
   657         self.fs_schema = newcubes_schema
   657         self.fs_schema = newcubes_schema
   658         self.update_context('fsschema', self.fs_schema)
   658         self.update_context('fsschema', self.fs_schema)
   659         new = set()
   659         new = set()
   660         # execute pre-create files
   660         # execute pre-create files
   661         driver = self.repo.system_source.dbdriver
       
   662         for cube in reversed(newcubes):
   661         for cube in reversed(newcubes):
   663             self.cmd_install_custom_sql_scripts(cube)
   662             self.cmd_install_custom_sql_scripts(cube)
   664             self.cmd_exec_event_script('precreate', cube)
   663             self.cmd_exec_event_script('precreate', cube)
   665         # add new entity and relation types
   664         # add new entity and relation types
   666         for rschema in newcubes_schema.relations():
   665         for rschema in newcubes_schema.relations():
   667             if not rschema in self.repo.schema:
   666             if rschema not in self.repo.schema:
   668                 self.cmd_add_relation_type(rschema.type)
   667                 self.cmd_add_relation_type(rschema.type)
   669                 new.add(rschema.type)
   668                 new.add(rschema.type)
   670         toadd = [eschema for eschema in newcubes_schema.entities()
   669         toadd = [eschema for eschema in newcubes_schema.entities()
   671                  if not eschema in self.repo.schema]
   670                  if eschema not in self.repo.schema]
   672         for eschema in order_eschemas(toadd):
   671         for eschema in order_eschemas(toadd):
   673             self.cmd_add_entity_type(eschema.type)
   672             self.cmd_add_entity_type(eschema.type)
   674             new.add(eschema.type)
   673             new.add(eschema.type)
   675         # check if attributes has been added to existing entities
   674         # check if attributes has been added to existing entities
   676         for rschema in newcubes_schema.relations():
   675         for rschema in newcubes_schema.relations():
   703         # execute pre-remove files
   702         # execute pre-remove files
   704         for cube in reversed(removedcubes):
   703         for cube in reversed(removedcubes):
   705             self.cmd_exec_event_script('preremove', cube)
   704             self.cmd_exec_event_script('preremove', cube)
   706         # remove cubes'entity and relation types
   705         # remove cubes'entity and relation types
   707         for rschema in fsschema.relations():
   706         for rschema in fsschema.relations():
   708             if not rschema in removedcubes_schema and rschema in reposchema:
   707             if rschema not in removedcubes_schema and rschema in reposchema:
   709                 self.cmd_drop_relation_type(rschema.type)
   708                 self.cmd_drop_relation_type(rschema.type)
   710         toremove = [eschema for eschema in fsschema.entities()
   709         toremove = [eschema for eschema in fsschema.entities()
   711                     if not eschema in removedcubes_schema
   710                     if eschema not in removedcubes_schema and eschema in reposchema]
   712                     and eschema in reposchema]
       
   713         for eschema in reversed(order_eschemas(toremove)):
   711         for eschema in reversed(order_eschemas(toremove)):
   714             self.cmd_drop_entity_type(eschema.type)
   712             self.cmd_drop_entity_type(eschema.type)
   715         for rschema in fsschema.relations():
   713         for rschema in fsschema.relations():
   716             if rschema in removedcubes_schema and rschema in reposchema:
   714             if rschema in removedcubes_schema and rschema in reposchema:
   717                 # check if attributes/relations has been added to entities from
   715                 # check if attributes/relations has been added to entities from
   718                 # other cubes
   716                 # other cubes
   719                 for fromtype, totype in rschema.rdefs:
   717                 for fromtype, totype in rschema.rdefs:
   720                     if (fromtype, totype) not in removedcubes_schema[rschema.type].rdefs and \
   718                     if (fromtype, totype) not in removedcubes_schema[rschema.type].rdefs and \
   721                            (fromtype, totype) in reposchema[rschema.type].rdefs:
   719                        (fromtype, totype) in reposchema[rschema.type].rdefs:
   722                         self.cmd_drop_relation_definition(
   720                         self.cmd_drop_relation_definition(
   723                             str(fromtype), rschema.type, str(totype))
   721                             str(fromtype), rschema.type, str(totype))
   724         # execute post-remove files
   722         # execute post-remove files
   725         for cube in reversed(removedcubes):
   723         for cube in reversed(removedcubes):
   726             self.cmd_exec_event_script('postremove', cube)
   724             self.cmd_exec_event_script('postremove', cube)
   727             self.rqlexec('DELETE CWProperty X WHERE X pkey %(pk)s',
   725             self.rqlexec('DELETE CWProperty X WHERE X pkey %(pk)s',
   728                          {'pk': u'system.version.'+cube}, ask_confirm=False)
   726                          {'pk': u'system.version.' + cube}, ask_confirm=False)
   729             self.commit()
   727             self.commit()
   730 
   728 
   731     # schema migration actions ################################################
   729     # schema migration actions ################################################
   732 
   730 
   733     def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True):
   731     def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True):
   766         # skipp NULL values if the attribute is required
   764         # skipp NULL values if the attribute is required
   767         rql = 'SET X %s VAL WHERE X is %s, X %s VAL' % (newname, etype, oldname)
   765         rql = 'SET X %s VAL WHERE X is %s, X %s VAL' % (newname, etype, oldname)
   768         card = eschema.rdef(newname).cardinality[0]
   766         card = eschema.rdef(newname).cardinality[0]
   769         if card == '1':
   767         if card == '1':
   770             rql += ', NOT X %s NULL' % oldname
   768             rql += ', NOT X %s NULL' % oldname
   771         self.rqlexec(rql, ask_confirm=self.verbosity>=2)
   769         self.rqlexec(rql, ask_confirm=self.verbosity >= 2)
   772         # XXX if both attributes fulltext indexed, should skip fti rebuild
   770         # XXX if both attributes fulltext indexed, should skip fti rebuild
   773         # XXX if old attribute was fti indexed but not the new one old value
   771         # XXX if old attribute was fti indexed but not the new one old value
   774         # won't be removed from the index (this occurs on other kind of
   772         # won't be removed from the index (this occurs on other kind of
   775         # fulltextindexed change...)
   773         # fulltextindexed change...)
   776         self.cmd_drop_attribute(etype, oldname, commit=commit)
   774         self.cmd_drop_attribute(etype, oldname, commit=commit)
   809         # register entity's attributes
   807         # register entity's attributes
   810         for rschema, attrschema in eschema.attribute_definitions():
   808         for rschema, attrschema in eschema.attribute_definitions():
   811             # ignore those meta relations, they will be automatically added
   809             # ignore those meta relations, they will be automatically added
   812             if rschema.type in META_RTYPES:
   810             if rschema.type in META_RTYPES:
   813                 continue
   811                 continue
   814             if not attrschema.type in instschema:
   812             if attrschema.type not in instschema:
   815                 self.cmd_add_entity_type(attrschema.type, False, False)
   813                 self.cmd_add_entity_type(attrschema.type, False, False)
   816             if not rschema.type in instschema:
   814             if rschema.type not in instschema:
   817                 # need to add the relation type and to commit to get it
   815                 # need to add the relation type and to commit to get it
   818                 # actually in the schema
   816                 # actually in the schema
   819                 self.cmd_add_relation_type(rschema.type, False, commit=True)
   817                 self.cmd_add_relation_type(rschema.type, False, commit=True)
   820             # register relation definition
   818             # register relation definition
   821             rdef = self._get_rdef(rschema, eschema, eschema.destination(rschema))
   819             rdef = self._get_rdef(rschema, eschema, eschema.destination(rschema))
   832                 self.rqlexec('SET D specializes P WHERE D eid %(d)s, P name %(pn)s',
   830                 self.rqlexec('SET D specializes P WHERE D eid %(d)s, P name %(pn)s',
   833                              {'d': instspschema.eid, 'pn': eschema.type},
   831                              {'d': instspschema.eid, 'pn': eschema.type},
   834                              ask_confirm=confirm)
   832                              ask_confirm=confirm)
   835                 for rschema, tschemas, role in spschema.relation_definitions(True):
   833                 for rschema, tschemas, role in spschema.relation_definitions(True):
   836                     for tschema in tschemas:
   834                     for tschema in tschemas:
   837                         if not tschema in instschema:
   835                         if tschema not in instschema:
   838                             continue
   836                             continue
   839                         if role == 'subject':
   837                         if role == 'subject':
   840                             subjschema = spschema
   838                             subjschema = spschema
   841                             objschema = tschema
   839                             objschema = tschema
   842                             if rschema.final and rschema in instspschema.subjrels:
   840                             if rschema.final and rschema in instspschema.subjrels:
   865                 rtypeadded = rschema.type in instschema
   863                 rtypeadded = rschema.type in instschema
   866                 for targetschema in rschema.objects(etype):
   864                 for targetschema in rschema.objects(etype):
   867                     # ignore relations where the targeted type is not in the
   865                     # ignore relations where the targeted type is not in the
   868                     # current instance schema
   866                     # current instance schema
   869                     targettype = targetschema.type
   867                     targettype = targetschema.type
   870                     if not targettype in instschema and targettype != etype:
   868                     if targettype not in instschema and targettype != etype:
   871                         continue
   869                         continue
   872                     if not rtypeadded:
   870                     if not rtypeadded:
   873                         # need to add the relation type and to commit to get it
   871                         # need to add the relation type and to commit to get it
   874                         # actually in the schema
   872                         # actually in the schema
   875                         added.append(rschema.type)
   873                         added.append(rschema.type)
   890                     # ignore relations where the targeted type is not in the
   888                     # ignore relations where the targeted type is not in the
   891                     # current instance schema
   889                     # current instance schema
   892                     targettype = targetschema.type
   890                     targettype = targetschema.type
   893                     # don't check targettype != etype since in this case the
   891                     # don't check targettype != etype since in this case the
   894                     # relation has already been added as a subject relation
   892                     # relation has already been added as a subject relation
   895                     if not targettype in instschema:
   893                     if targettype not in instschema:
   896                         continue
   894                         continue
   897                     if not rtypeadded:
   895                     if not rtypeadded:
   898                         # need to add the relation type and to commit to get it
   896                         # need to add the relation type and to commit to get it
   899                         # actually in the schema
   897                         # actually in the schema
   900                         self.cmd_add_relation_type(rschema.type, False, commit=True)
   898                         self.cmd_add_relation_type(rschema.type, False, commit=True)
   916         any hooks called.
   914         any hooks called.
   917         """
   915         """
   918         # XXX what if we delete an entity type which is specialized by other types
   916         # XXX what if we delete an entity type which is specialized by other types
   919         # unregister the entity from CWEType
   917         # unregister the entity from CWEType
   920         self.rqlexec('DELETE CWEType X WHERE X name %(etype)s', {'etype': etype},
   918         self.rqlexec('DELETE CWEType X WHERE X name %(etype)s', {'etype': etype},
   921                      ask_confirm=self.verbosity>=2)
   919                      ask_confirm=self.verbosity >= 2)
   922         if commit:
   920         if commit:
   923             self.commit()
   921             self.commit()
   924 
   922 
   925     def cmd_rename_entity_type(self, oldname, newname, attrs=None, commit=True):
   923     def cmd_rename_entity_type(self, oldname, newname, attrs=None, commit=True):
   926         """rename an existing entity type in the persistent schema
   924         """rename an existing entity type in the persistent schema
   933             print('warning: entity type %s is unknown, skip renaming' % oldname)
   931             print('warning: entity type %s is unknown, skip renaming' % oldname)
   934             return
   932             return
   935         # if merging two existing entity types
   933         # if merging two existing entity types
   936         if newname in schema:
   934         if newname in schema:
   937             assert oldname in ETYPE_NAME_MAP, \
   935             assert oldname in ETYPE_NAME_MAP, \
   938                    '%s should be mapped to %s in ETYPE_NAME_MAP' % (oldname,
   936                 '%s should be mapped to %s in ETYPE_NAME_MAP' % (oldname, newname)
   939                                                                     newname)
       
   940             if attrs is None:
   937             if attrs is None:
   941                 attrs = ','.join(SQL_PREFIX + rschema.type
   938                 attrs = ','.join(SQL_PREFIX + rschema.type
   942                                  for rschema in schema[newname].subject_relations()
   939                                  for rschema in schema[newname].subject_relations()
   943                                  if (rschema.final or rschema.inlined)
   940                                  if (rschema.final or rschema.inlined)
   944                                  and not rschema in PURE_VIRTUAL_RTYPES)
   941                                  and rschema not in PURE_VIRTUAL_RTYPES)
   945             else:
   942             else:
   946                 attrs += ('eid', 'creation_date', 'modification_date', 'cwuri')
   943                 attrs += ('eid', 'creation_date', 'modification_date', 'cwuri')
   947                 attrs = ','.join(SQL_PREFIX + attr for attr in attrs)
   944                 attrs = ','.join(SQL_PREFIX + attr for attr in attrs)
   948             self.sqlexec('INSERT INTO %s%s(%s) SELECT %s FROM %s%s' % (
   945             self.sqlexec('INSERT INTO %s%s(%s) SELECT %s FROM %s%s' % (
   949                 SQL_PREFIX, newname, attrs, attrs, SQL_PREFIX, oldname),
   946                 SQL_PREFIX, newname, attrs, attrs, SQL_PREFIX, oldname),
   966                 self.sqlexec('UPDATE %s_relation SET eid_to=%s WHERE eid_to=%s'
   963                 self.sqlexec('UPDATE %s_relation SET eid_to=%s WHERE eid_to=%s'
   967                              % (rtype, new.eid, oldeid), ask_confirm=False)
   964                              % (rtype, new.eid, oldeid), ask_confirm=False)
   968             # delete relations using SQL to avoid relations content removal
   965             # delete relations using SQL to avoid relations content removal
   969             # triggered by schema synchronization hooks.
   966             # triggered by schema synchronization hooks.
   970             for rdeftype in ('CWRelation', 'CWAttribute'):
   967             for rdeftype in ('CWRelation', 'CWAttribute'):
   971                 thispending = set( (eid for eid, in self.sqlexec(
   968                 thispending = set((eid for eid, in self.sqlexec(
   972                     'SELECT cw_eid FROM cw_%s WHERE cw_from_entity=%%(eid)s OR '
   969                     'SELECT cw_eid FROM cw_%s WHERE cw_from_entity=%%(eid)s OR '
   973                     ' cw_to_entity=%%(eid)s' % rdeftype,
   970                     ' cw_to_entity=%%(eid)s' % rdeftype,
   974                     {'eid': oldeid}, ask_confirm=False)) )
   971                     {'eid': oldeid}, ask_confirm=False)))
   975                 # we should add deleted eids into pending eids else we may
   972                 # we should add deleted eids into pending eids else we may
   976                 # get some validation error on commit since integrity hooks
   973                 # get some validation error on commit since integrity hooks
   977                 # may think some required relation is missing... This also ensure
   974                 # may think some required relation is missing... This also ensure
   978                 # repository caches are properly cleanup
   975                 # repository caches are properly cleanup
   979                 hook.CleanupDeletedEidsCacheOp.get_instance(self.cnx).union(thispending)
   976                 hook.CleanupDeletedEidsCacheOp.get_instance(self.cnx).union(thispending)
  1007             self.rqlexec('DELETE CWEType ET WHERE ET name %(on)s', {'on': oldname},
  1004             self.rqlexec('DELETE CWEType ET WHERE ET name %(on)s', {'on': oldname},
  1008                          ask_confirm=False)
  1005                          ask_confirm=False)
  1009         # elif simply renaming an entity type
  1006         # elif simply renaming an entity type
  1010         else:
  1007         else:
  1011             self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(on)s',
  1008             self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(on)s',
  1012                          {'newname' : text_type(newname), 'on' : oldname},
  1009                          {'newname': text_type(newname), 'on': oldname},
  1013                          ask_confirm=False)
  1010                          ask_confirm=False)
  1014         if commit:
  1011         if commit:
  1015             self.commit()
  1012             self.commit()
  1016 
  1013 
  1017     def cmd_add_relation_type(self, rtype, addrdef=True, commit=True):
  1014     def cmd_add_relation_type(self, rtype, addrdef=True, commit=True):
  1048                         and reposchema.has_entity(obj)):
  1045                         and reposchema.has_entity(obj)):
  1049                     continue
  1046                     continue
  1050                 # symmetric relations appears twice
  1047                 # symmetric relations appears twice
  1051                 if (subj, obj) in done:
  1048                 if (subj, obj) in done:
  1052                     continue
  1049                     continue
  1053                 done.add( (subj, obj) )
  1050                 done.add((subj, obj))
  1054                 self.cmd_add_relation_definition(subj, rtype, obj)
  1051                 self.cmd_add_relation_definition(subj, rtype, obj)
  1055             if rtype in META_RTYPES:
  1052             if rtype in META_RTYPES:
  1056                 # if the relation is in META_RTYPES, ensure we're adding it for
  1053                 # if the relation is in META_RTYPES, ensure we're adding it for
  1057                 # all entity types *in the persistent schema*, not only those in
  1054                 # all entity types *in the persistent schema*, not only those in
  1058                 # the fs schema
  1055                 # the fs schema
  1059                 for etype in self.repo.schema.entities():
  1056                 for etype in self.repo.schema.entities():
  1060                     if not etype in self.fs_schema:
  1057                     if etype not in self.fs_schema:
  1061                         # get sample object type and rproperties
  1058                         # get sample object type and rproperties
  1062                         objtypes = rschema.objects()
  1059                         objtypes = rschema.objects()
  1063                         assert len(objtypes) == 1, objtypes
  1060                         assert len(objtypes) == 1, objtypes
  1064                         objtype = objtypes[0]
  1061                         objtype = objtypes[0]
  1065                         rdef = copy(rschema.rdef(rschema.subjects(objtype)[0], objtype))
  1062                         rdef = copy(rschema.rdef(rschema.subjects(objtype)[0], objtype))
  1076 
  1073 
  1077         Note that existing relations of the given type will be deleted without
  1074         Note that existing relations of the given type will be deleted without
  1078         any hooks called.
  1075         any hooks called.
  1079         """
  1076         """
  1080         self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype,
  1077         self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype,
  1081                      ask_confirm=self.verbosity>=2)
  1078                      ask_confirm=self.verbosity >= 2)
  1082         self.rqlexec('DELETE CWComputedRType X WHERE X name %r' % rtype,
  1079         self.rqlexec('DELETE CWComputedRType X WHERE X name %r' % rtype,
  1083                      ask_confirm=self.verbosity>=2)
  1080                      ask_confirm=self.verbosity >= 2)
  1084         if commit:
  1081         if commit:
  1085             self.commit()
  1082             self.commit()
  1086 
  1083 
  1087     def cmd_rename_relation_type(self, oldname, newname, commit=True, force=False):
  1084     def cmd_rename_relation_type(self, oldname, newname, commit=True, force=False):
  1088         """rename an existing relation
  1085         """rename an existing relation
  1098                                 default='n'):
  1095                                 default='n'):
  1099                 return
  1096                 return
  1100         self.cmd_add_relation_type(newname, commit=True)
  1097         self.cmd_add_relation_type(newname, commit=True)
  1101         if not self.repo.schema[oldname].rule:
  1098         if not self.repo.schema[oldname].rule:
  1102             self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname),
  1099             self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname),
  1103                          ask_confirm=self.verbosity>=2)
  1100                          ask_confirm=self.verbosity >= 2)
  1104         self.cmd_drop_relation_type(oldname, commit=commit)
  1101         self.cmd_drop_relation_type(oldname, commit=commit)
  1105 
  1102 
  1106     def cmd_add_relation_definition(self, subjtype, rtype, objtype, commit=True):
  1103     def cmd_add_relation_definition(self, subjtype, rtype, objtype, commit=True):
  1107         """register a new relation definition, from its definition found in the
  1104         """register a new relation definition, from its definition found in the
  1108         schema definition file
  1105         schema definition file
  1109         """
  1106         """
  1110         rschema = self.fs_schema.rschema(rtype)
  1107         rschema = self.fs_schema.rschema(rtype)
  1111         if rschema.rule:
  1108         if rschema.rule:
  1112             raise ExecutionError('Cannot add a relation definition for a '
  1109             raise ExecutionError('Cannot add a relation definition for a '
  1113                                  'computed relation (%s)' % rschema)
  1110                                  'computed relation (%s)' % rschema)
  1114         if not rtype in self.repo.schema:
  1111         if rtype not in self.repo.schema:
  1115             self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
  1112             self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
  1116         if (subjtype, objtype) in self.repo.schema.rschema(rtype).rdefs:
  1113         if (subjtype, objtype) in self.repo.schema.rschema(rtype).rdefs:
  1117             print('warning: relation %s %s %s is already known, skip addition' % (
  1114             print('warning: relation %s %s %s is already known, skip addition' % (
  1118                 subjtype, rtype, objtype))
  1115                 subjtype, rtype, objtype))
  1119             return
  1116             return
  1129 
  1126 
  1130     def _set_rdef_eid(self, rdef):
  1127     def _set_rdef_eid(self, rdef):
  1131         for attr in ('rtype', 'subject', 'object'):
  1128         for attr in ('rtype', 'subject', 'object'):
  1132             schemaobj = getattr(rdef, attr)
  1129             schemaobj = getattr(rdef, attr)
  1133             if getattr(schemaobj, 'eid', None) is None:
  1130             if getattr(schemaobj, 'eid', None) is None:
  1134                 schemaobj.eid =  self.repo.schema[schemaobj].eid
  1131                 schemaobj.eid = self.repo.schema[schemaobj].eid
  1135                 assert schemaobj.eid is not None, schemaobj
  1132                 assert schemaobj.eid is not None, schemaobj
  1136         return rdef
  1133         return rdef
  1137 
  1134 
  1138     def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
  1135     def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
  1139         """Drop an existing relation definition.
  1136         """Drop an existing relation definition.
  1151         else:
  1148         else:
  1152             etype = 'CWRelation'
  1149             etype = 'CWRelation'
  1153         rql = ('DELETE %s X WHERE X from_entity FE, FE name "%s",'
  1150         rql = ('DELETE %s X WHERE X from_entity FE, FE name "%s",'
  1154                'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"')
  1151                'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"')
  1155         self.rqlexec(rql % (etype, subjtype, rtype, objtype),
  1152         self.rqlexec(rql % (etype, subjtype, rtype, objtype),
  1156                      ask_confirm=self.verbosity>=2)
  1153                      ask_confirm=self.verbosity >= 2)
  1157         if commit:
  1154         if commit:
  1158             self.commit()
  1155             self.commit()
  1159 
  1156 
  1160     def cmd_sync_schema_props_perms(self, ertype=None, syncperms=True,
  1157     def cmd_sync_schema_props_perms(self, ertype=None, syncperms=True,
  1161                                     syncprops=True, syncrdefs=True, commit=True):
  1158                                     syncprops=True, syncrdefs=True, commit=True):
  1192                                               syncperms=syncperms,
  1189                                               syncperms=syncperms,
  1193                                               syncprops=syncprops)
  1190                                               syncprops=syncprops)
  1194         else:
  1191         else:
  1195             for etype in self.repo.schema.entities():
  1192             for etype in self.repo.schema.entities():
  1196                 if etype.eid is None:
  1193                 if etype.eid is None:
  1197                      # not yet added final etype (thing to BigInt defined in
  1194                     # not yet added final etype (thing to BigInt defined in
  1198                      # yams though 3.13 migration not done yet)
  1195                     # yams though 3.13 migration not done yet)
  1199                     continue
  1196                     continue
  1200                 self._synchronize_eschema(etype, syncrdefs=syncrdefs,
  1197                 self._synchronize_eschema(etype, syncrdefs=syncrdefs,
  1201                                           syncprops=syncprops, syncperms=syncperms)
  1198                                           syncprops=syncprops, syncperms=syncperms)
  1202         if commit:
  1199         if commit:
  1203             self.commit()
  1200             self.commit()
  1221         for k, v in kwargs.items():
  1218         for k, v in kwargs.items():
  1222             values.append('X %s %%(%s)s' % (k, k))
  1219             values.append('X %s %%(%s)s' % (k, k))
  1223             if PY2 and isinstance(v, str):
  1220             if PY2 and isinstance(v, str):
  1224                 kwargs[k] = unicode(v)
  1221                 kwargs[k] = unicode(v)
  1225         rql = 'SET %s WHERE %s' % (','.join(values), ','.join(restriction))
  1222         rql = 'SET %s WHERE %s' % (','.join(values), ','.join(restriction))
  1226         self.rqlexec(rql, kwargs, ask_confirm=self.verbosity>=2)
  1223         self.rqlexec(rql, kwargs, ask_confirm=self.verbosity >= 2)
  1227         if commit:
  1224         if commit:
  1228             self.commit()
  1225             self.commit()
  1229 
  1226 
  1230     def cmd_set_size_constraint(self, etype, rtype, size, commit=True):
  1227     def cmd_set_size_constraint(self, etype, rtype, size, commit=True):
  1231         """set change size constraint of a string attribute
  1228         """set change size constraint of a string attribute
  1238         for constr in self.repo.schema.eschema(etype).rdef(rtype).constraints:
  1235         for constr in self.repo.schema.eschema(etype).rdef(rtype).constraints:
  1239             if isinstance(constr, SizeConstraint):
  1236             if isinstance(constr, SizeConstraint):
  1240                 oldvalue = constr.max
  1237                 oldvalue = constr.max
  1241         if oldvalue == size:
  1238         if oldvalue == size:
  1242             return
  1239             return
  1243         if oldvalue is None and not size is None:
  1240         if oldvalue is None and size is not None:
  1244             ceid = self.rqlexec('INSERT CWConstraint C: C value %(v)s, C cstrtype CT '
  1241             ceid = self.rqlexec('INSERT CWConstraint C: C value %(v)s, C cstrtype CT '
  1245                                 'WHERE CT name "SizeConstraint"',
  1242                                 'WHERE CT name "SizeConstraint"',
  1246                                 {'v': SizeConstraint(size).serialize()},
  1243                                 {'v': SizeConstraint(size).serialize()},
  1247                                 ask_confirm=self.verbosity>=2)[0][0]
  1244                                 ask_confirm=self.verbosity >= 2)[0][0]
  1248             self.rqlexec('SET X constrained_by C WHERE X from_entity S, X relation_type R, '
  1245             self.rqlexec('SET X constrained_by C WHERE X from_entity S, X relation_type R, '
  1249                          'S name "%s", R name "%s", C eid %s' % (etype, rtype, ceid),
  1246                          'S name "%s", R name "%s", C eid %s' % (etype, rtype, ceid),
  1250                          ask_confirm=self.verbosity>=2)
  1247                          ask_confirm=self.verbosity >= 2)
  1251         elif not oldvalue is None:
  1248         elif oldvalue is not None:
  1252             if not size is None:
  1249             if size is not None:
  1253                 self.rqlexec('SET C value %%(v)s WHERE X from_entity S, X relation_type R,'
  1250                 self.rqlexec('SET C value %%(v)s WHERE X from_entity S, X relation_type R,'
  1254                              'X constrained_by C, C cstrtype CT, CT name "SizeConstraint",'
  1251                              'X constrained_by C, C cstrtype CT, CT name "SizeConstraint",'
  1255                              'S name "%s", R name "%s"' % (etype, rtype),
  1252                              'S name "%s", R name "%s"' % (etype, rtype),
  1256                              {'v': text_type(SizeConstraint(size).serialize())},
  1253                              {'v': text_type(SizeConstraint(size).serialize())},
  1257                              ask_confirm=self.verbosity>=2)
  1254                              ask_confirm=self.verbosity >= 2)
  1258             else:
  1255             else:
  1259                 self.rqlexec('DELETE X constrained_by C WHERE X from_entity S, X relation_type R,'
  1256                 self.rqlexec('DELETE X constrained_by C WHERE X from_entity S, X relation_type R,'
  1260                              'X constrained_by C, C cstrtype CT, CT name "SizeConstraint",'
  1257                              'X constrained_by C, C cstrtype CT, CT name "SizeConstraint",'
  1261                              'S name "%s", R name "%s"' % (etype, rtype),
  1258                              'S name "%s", R name "%s"' % (etype, rtype),
  1262                              ask_confirm=self.verbosity>=2)
  1259                              ask_confirm=self.verbosity >= 2)
  1263                 # cleanup unused constraints
  1260                 # cleanup unused constraints
  1264                 self.rqlexec('DELETE CWConstraint C WHERE NOT X constrained_by C')
  1261                 self.rqlexec('DELETE CWConstraint C WHERE NOT X constrained_by C')
  1265         if commit:
  1262         if commit:
  1266             self.commit()
  1263             self.commit()
  1267 
  1264 
  1292         """
  1289         """
  1293         wf = self.cmd_create_entity('Workflow', name=text_type(name),
  1290         wf = self.cmd_create_entity('Workflow', name=text_type(name),
  1294                                     **kwargs)
  1291                                     **kwargs)
  1295         if not isinstance(wfof, (list, tuple)):
  1292         if not isinstance(wfof, (list, tuple)):
  1296             wfof = (wfof,)
  1293             wfof = (wfof,)
       
  1294 
  1297         def _missing_wf_rel(etype):
  1295         def _missing_wf_rel(etype):
  1298             return 'missing workflow relations, see make_workflowable(%s)' % etype
  1296             return 'missing workflow relations, see make_workflowable(%s)' % etype
       
  1297 
  1299         for etype in wfof:
  1298         for etype in wfof:
  1300             eschema = self.repo.schema[etype]
  1299             eschema = self.repo.schema[etype]
  1301             etype = text_type(etype)
  1300             etype = text_type(etype)
  1302             if ensure_workflowable:
  1301             if ensure_workflowable:
  1303                 assert 'in_state' in eschema.subjrels, _missing_wf_rel(etype)
  1302                 assert 'in_state' in eschema.subjrels, _missing_wf_rel(etype)
  1470 
  1469 
  1471     def rqlexec(self, rql, kwargs=None, build_descr=True,
  1470     def rqlexec(self, rql, kwargs=None, build_descr=True,
  1472                 ask_confirm=False):
  1471                 ask_confirm=False):
  1473         """rql action"""
  1472         """rql action"""
  1474         if not isinstance(rql, (tuple, list)):
  1473         if not isinstance(rql, (tuple, list)):
  1475             rql = ( (rql, kwargs), )
  1474             rql = ((rql, kwargs),)
  1476         res = None
  1475         res = None
  1477         execute = self.cnx.execute
  1476         execute = self.cnx.execute
  1478         for rql, kwargs in rql:
  1477         for rql, kwargs in rql:
  1479             if kwargs:
  1478             if kwargs:
  1480                 msg = '%s (%s)' % (rql, kwargs)
  1479                 msg = '%s (%s)' % (rql, kwargs)