server/migractions.py
changeset 2968 0e3460341023
parent 2867 e8581a4f1bae
parent 2967 e7d348134006
child 3023 7864fee8b4ec
equal deleted inserted replaced
2902:dd9f2dd02f85 2968:0e3460341023
    31 
    31 
    32 from yams.constraints import SizeConstraint
    32 from yams.constraints import SizeConstraint
    33 from yams.schema2sql import eschema2sql, rschema2sql
    33 from yams.schema2sql import eschema2sql, rschema2sql
    34 
    34 
    35 from cubicweb import AuthenticationError, ETYPE_NAME_MAP
    35 from cubicweb import AuthenticationError, ETYPE_NAME_MAP
    36 from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CubicWebRelationSchema
    36 from cubicweb.schema import (META_RTYPES, VIRTUAL_RTYPES,
       
    37                              CubicWebRelationSchema, order_eschemas)
    37 from cubicweb.dbapi import get_repository, repo_connect
    38 from cubicweb.dbapi import get_repository, repo_connect
    38 from cubicweb.common.migration import MigrationHelper, yes
    39 from cubicweb.common.migration import MigrationHelper, yes
    39 
    40 
    40 try:
    41 try:
    41     from cubicweb.server import SOURCE_TYPES, schemaserial as ss
    42     from cubicweb.server import SOURCE_TYPES, schemaserial as ss
    58             assert repo
    59             assert repo
    59         if cnx is not None:
    60         if cnx is not None:
    60             assert repo
    61             assert repo
    61             self._cnx = cnx
    62             self._cnx = cnx
    62             self.repo = repo
    63             self.repo = repo
       
    64             self.session.data['rebuild-infered'] = False
    63         elif connect:
    65         elif connect:
    64             self.repo_connect()
    66             self.repo_connect()
    65         if not schema:
    67         if not schema:
    66             schema = config.load_schema(expand_cubes=True)
    68             schema = config.load_schema(expand_cubes=True)
    67         self.fs_schema = schema
    69         self.fs_schema = schema
   130             return
   132             return
   131         open(backupfile,'w').close() # kinda lock
   133         open(backupfile,'w').close() # kinda lock
   132         os.chmod(backupfile, 0600)
   134         os.chmod(backupfile, 0600)
   133         # backup
   135         # backup
   134         tmpdir = tempfile.mkdtemp(dir=instbkdir)
   136         tmpdir = tempfile.mkdtemp(dir=instbkdir)
   135         for source in repo.sources:
   137         try:
   136             try:
   138             for source in repo.sources:
   137                 source.backup(osp.join(tmpdir,source.uri))
   139                 try:
   138             except Exception, exc:
   140                     source.backup(osp.join(tmpdir, source.uri))
   139                 print '-> error trying to backup [%s]' % exc
   141                 except Exception, exc:
   140                 if not self.confirm('Continue anyway?', default='n'):
   142                     print '-> error trying to backup [%s]' % exc
   141                     raise SystemExit(1)
   143                     if not self.confirm('Continue anyway?', default='n'):
   142         bkup = tarfile.open(backupfile, 'w|gz')
   144                         raise SystemExit(1)
   143         for filename in os.listdir(tmpdir):
   145                     else:
   144             bkup.add(osp.join(tmpdir,filename), filename)
   146                         break
   145         bkup.close()
   147             else:
   146         shutil.rmtree(tmpdir)
   148                 bkup = tarfile.open(backupfile, 'w|gz')
   147         # call hooks
   149                 for filename in os.listdir(tmpdir):
   148         repo.hm.call_hooks('server_backup', repo=repo, timestamp=timestamp)
   150                     bkup.add(osp.join(tmpdir,filename), filename)
   149         # done
   151                 bkup.close()
   150         print '-> backup file',  backupfile
   152                 # call hooks
       
   153                 repo.hm.call_hooks('server_backup', repo=repo, timestamp=timestamp)
       
   154                 # done
       
   155                 print '-> backup file',  backupfile
       
   156         finally:
       
   157             shutil.rmtree(tmpdir)
   151 
   158 
   152     def restore_database(self, backupfile, drop=True, systemonly=True,
   159     def restore_database(self, backupfile, drop=True, systemonly=True,
   153                          askconfirm=True):
   160                          askconfirm=True):
   154         config = self.config
       
   155         repo = self.repo_connect()
       
   156         # check
   161         # check
   157         if not osp.exists(backupfile):
   162         if not osp.exists(backupfile):
   158             raise Exception("Backup file %s doesn't exist" % backupfile)
   163             raise Exception("Backup file %s doesn't exist" % backupfile)
   159             return
   164             return
   160         if askconfirm and not self.confirm('Restore %s database from %s ?'
   165         if askconfirm and not self.confirm('Restore %s database from %s ?'
   161                                            % (config.appid, backupfile)):
   166                                            % (self.config.appid, backupfile)):
   162             return
   167             return
   163         # unpack backup
   168         # unpack backup
   164         bkup = tarfile.open(backupfile, 'r|gz')
   169         bkup = tarfile.open(backupfile, 'r|gz')
   165         for name in bkup.getnames():
   170         for name in bkup.getnames():
   166             if name[0] in '/.':
   171             if name[0] in '/.':
   167                 raise Exception('Security check failed, path starts with "/" or "."')
   172                 raise Exception('Security check failed, path starts with "/" or "."')
   168         bkup.close() # XXX seek error if not close+open !?!
   173         bkup.close() # XXX seek error if not close+open !?!
   169         bkup = tarfile.open(backupfile, 'r|gz')
   174         bkup = tarfile.open(backupfile, 'r|gz')
   170         tmpdir = tempfile.mkdtemp()
   175         tmpdir = tempfile.mkdtemp()
   171         bkup.extractall(path=tmpdir)
   176         bkup.extractall(path=tmpdir)
       
   177 
       
   178         self.config.open_connections_pools = False
       
   179         repo = self.repo_connect()
   172         for source in repo.sources:
   180         for source in repo.sources:
   173             if systemonly and source.uri != 'system':
   181             if systemonly and source.uri != 'system':
   174                 continue
   182                 continue
   175             try:
   183             try:
   176                 source.restore(osp.join(tmpdir, source.uri), drop=drop)
   184                 source.restore(osp.join(tmpdir, source.uri), drop=drop)
   179                 if not self.confirm('Continue anyway?', default='n'):
   187                 if not self.confirm('Continue anyway?', default='n'):
   180                     raise SystemExit(1)
   188                     raise SystemExit(1)
   181         bkup.close()
   189         bkup.close()
   182         shutil.rmtree(tmpdir)
   190         shutil.rmtree(tmpdir)
   183         # call hooks
   191         # call hooks
       
   192         repo.open_connections_pools()
   184         repo.hm.call_hooks('server_restore', repo=repo, timestamp=backupfile)
   193         repo.hm.call_hooks('server_restore', repo=repo, timestamp=backupfile)
   185         print '-> database restored.'
   194         print '-> database restored.'
   186 
   195 
   187     @property
   196     @property
   188     def cnx(self):
   197     def cnx(self):
   212                     login, pwd = manager_userpasswd()
   221                     login, pwd = manager_userpasswd()
   213                 except (KeyboardInterrupt, EOFError):
   222                 except (KeyboardInterrupt, EOFError):
   214                     print 'aborting...'
   223                     print 'aborting...'
   215                     sys.exit(0)
   224                     sys.exit(0)
   216             self.session.keep_pool_mode('transaction')
   225             self.session.keep_pool_mode('transaction')
       
   226             self.session.data['rebuild-infered'] = False
   217             return self._cnx
   227             return self._cnx
   218 
   228 
   219     @property
   229     @property
   220     def session(self):
   230     def session(self):
   221         return self.repo._get_session(self.cnx.sessionid)
   231         return self.repo._get_session(self.cnx.sessionid)
   222 
       
   223     @property
       
   224     @cached
       
   225     def rqlcursor(self):
       
   226         """lazy rql cursor"""
       
   227         # should not give session as cnx.cursor(), else we may try to execute
       
   228         # some query while no pool is set on the session (eg on entity attribute
       
   229         # access for instance)
       
   230         return self.cnx.cursor()
       
   231 
   232 
   232     def commit(self):
   233     def commit(self):
   233         if hasattr(self, '_cnx'):
   234         if hasattr(self, '_cnx'):
   234             self._cnx.commit()
   235             self._cnx.commit()
   235 
   236 
   260         return context
   261         return context
   261 
   262 
   262     @cached
   263     @cached
   263     def group_mapping(self):
   264     def group_mapping(self):
   264         """cached group mapping"""
   265         """cached group mapping"""
   265         return ss.group_mapping(self.rqlcursor)
   266         self.session.set_pool()
       
   267         return ss.group_mapping(self.session)
   266 
   268 
   267     def exec_event_script(self, event, cubepath=None, funcname=None,
   269     def exec_event_script(self, event, cubepath=None, funcname=None,
   268                           *args, **kwargs):
   270                           *args, **kwargs):
   269         if cubepath:
   271         if cubepath:
   270             apc = osp.join(cubepath, 'migration', '%s.py' % event)
   272             apc = osp.join(cubepath, 'migration', '%s.py' % event)
   413             self.rqlexec('SET X specializes Y WHERE X is CWEType, X name %(x)s, '
   415             self.rqlexec('SET X specializes Y WHERE X is CWEType, X name %(x)s, '
   414                          'Y is CWEType, Y name %(y)s',
   416                          'Y is CWEType, Y name %(y)s',
   415                          {'x': str(repoeschema), 'y': str(espschema)})
   417                          {'x': str(repoeschema), 'y': str(espschema)})
   416         self.rqlexecall(ss.updateeschema2rql(eschema),
   418         self.rqlexecall(ss.updateeschema2rql(eschema),
   417                         ask_confirm=self.verbosity >= 2)
   419                         ask_confirm=self.verbosity >= 2)
   418         for rschema, targettypes, x in eschema.relation_definitions(True):
   420         for rschema, targettypes, role in eschema.relation_definitions(True):
   419             if x == 'subject':
   421             if role == 'subject':
   420                 if not rschema in repoeschema.subject_relations():
   422                 if not rschema in repoeschema.subject_relations():
   421                     continue
   423                     continue
   422                 subjtypes, objtypes = [etype], targettypes
   424                 subjtypes, objtypes = [etype], targettypes
   423             else: # x == 'object'
   425             else: # role == 'object'
   424                 if not rschema in repoeschema.object_relations():
   426                 if not rschema in repoeschema.object_relations():
   425                     continue
   427                     continue
   426                 subjtypes, objtypes = targettypes, [etype]
   428                 subjtypes, objtypes = targettypes, [etype]
   427             self._synchronize_rschema(rschema, syncperms=syncperms,
   429             self._synchronize_rschema(rschema, syncperms=syncperms,
   428                                       syncrdefs=False)
   430                                       syncrdefs=False)
   524         # add new entity and relation types
   526         # add new entity and relation types
   525         for rschema in newcubes_schema.relations():
   527         for rschema in newcubes_schema.relations():
   526             if not rschema in self.repo.schema:
   528             if not rschema in self.repo.schema:
   527                 self.cmd_add_relation_type(rschema.type)
   529                 self.cmd_add_relation_type(rschema.type)
   528                 new.add(rschema.type)
   530                 new.add(rschema.type)
   529         for eschema in newcubes_schema.entities():
   531         toadd = [eschema for eschema in newcubes_schema.entities()
   530             if not eschema in self.repo.schema:
   532                  if not eschema in self.repo.schema]
   531                 self.cmd_add_entity_type(eschema.type)
   533         for eschema in order_eschemas(toadd):
   532                 new.add(eschema.type)
   534             self.cmd_add_entity_type(eschema.type)
       
   535             new.add(eschema.type)
   533         # check if attributes has been added to existing entities
   536         # check if attributes has been added to existing entities
   534         for rschema in newcubes_schema.relations():
   537         for rschema in newcubes_schema.relations():
   535             existingschema = self.repo.schema.rschema(rschema.type)
   538             existingschema = self.repo.schema.rschema(rschema.type)
   536             for (fromtype, totype) in rschema.iter_rdefs():
   539             for (fromtype, totype) in rschema.iter_rdefs():
   537                 if existingschema.has_rdef(fromtype, totype):
   540                 if existingschema.has_rdef(fromtype, totype):
   559             self.exec_event_script('preremove', self.config.cube_dir(pack))
   562             self.exec_event_script('preremove', self.config.cube_dir(pack))
   560         # remove cubes'entity and relation types
   563         # remove cubes'entity and relation types
   561         for rschema in fsschema.relations():
   564         for rschema in fsschema.relations():
   562             if not rschema in removedcubes_schema and rschema in reposchema:
   565             if not rschema in removedcubes_schema and rschema in reposchema:
   563                 self.cmd_drop_relation_type(rschema.type)
   566                 self.cmd_drop_relation_type(rschema.type)
   564         for eschema in fsschema.entities():
   567         toremove = [eschema for eschema in fsschema.entities()
   565             if not eschema in removedcubes_schema and eschema in reposchema:
   568                     if not eschema in removedcubes_schema
   566                 self.cmd_drop_entity_type(eschema.type)
   569                     and eschema in reposchema]
       
   570         for eschema in reversed(order_eschemas(toremove)):
       
   571             self.cmd_drop_entity_type(eschema.type)
   567         for rschema in fsschema.relations():
   572         for rschema in fsschema.relations():
   568             if rschema in removedcubes_schema and rschema in reposchema:
   573             if rschema in removedcubes_schema and rschema in reposchema:
   569                 # check if attributes/relations has been added to entities from
   574                 # check if attributes/relations has been added to entities from
   570                 # other cubes
   575                 # other cubes
   571                 for fromtype, totype in rschema.iter_rdefs():
   576                 for fromtype, totype in rschema.iter_rdefs():
   621         """register a new entity type
   626         """register a new entity type
   622 
   627 
   623         in auto mode, automatically register entity's relation where the
   628         in auto mode, automatically register entity's relation where the
   624         targeted type is known
   629         targeted type is known
   625         """
   630         """
   626         applschema = self.repo.schema
   631         instschema = self.repo.schema
   627         if etype in applschema:
   632         if etype in instschema:
   628             eschema = applschema[etype]
   633             # XXX (syt) plz explain: if we're adding an entity type, it should
       
   634             # not be there...
       
   635             eschema = instschema[etype]
   629             if eschema.is_final():
   636             if eschema.is_final():
   630                 applschema.del_entity_type(etype)
   637                 instschema.del_entity_type(etype)
   631         else:
   638         else:
   632             eschema = self.fs_schema.eschema(etype)
   639             eschema = self.fs_schema.eschema(etype)
   633         confirm = self.verbosity >= 2
   640         confirm = self.verbosity >= 2
   634         # register the entity into CWEType
   641         # register the entity into CWEType
   635         self.rqlexecall(ss.eschema2rql(eschema), ask_confirm=confirm)
   642         self.rqlexecall(ss.eschema2rql(eschema), ask_confirm=confirm)
   641         # register entity's attributes
   648         # register entity's attributes
   642         for rschema, attrschema in eschema.attribute_definitions():
   649         for rschema, attrschema in eschema.attribute_definitions():
   643             # ignore those meta relations, they will be automatically added
   650             # ignore those meta relations, they will be automatically added
   644             if rschema.type in META_RTYPES:
   651             if rschema.type in META_RTYPES:
   645                 continue
   652                 continue
   646             if not rschema.type in applschema:
   653             if not rschema.type in instschema:
   647                 # need to add the relation type and to commit to get it
   654                 # need to add the relation type and to commit to get it
   648                 # actually in the schema
   655                 # actually in the schema
   649                 self.cmd_add_relation_type(rschema.type, False, commit=True)
   656                 self.cmd_add_relation_type(rschema.type, False, commit=True)
   650             # register relation definition
   657             # register relation definition
   651             self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type),
   658             self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type),
   652                             ask_confirm=confirm)
   659                             ask_confirm=confirm)
       
   660         # take care to newly introduced base class
       
   661         # XXX some part of this should probably be under the "if auto" block
       
   662         for spschema in eschema.specialized_by(recursive=False):
       
   663             try:
       
   664                 instspschema = instschema[spschema]
       
   665             except KeyError:
       
   666                 # specialized entity type not in schema, ignore
       
   667                 continue
       
   668             if instspschema.specializes() != eschema:
       
   669                 self.rqlexec('SET D specializes P WHERE D eid %(d)s, P name %(pn)s',
       
   670                               {'d': instspschema.eid,
       
   671                                'pn': eschema.type}, ask_confirm=confirm)
       
   672                 for rschema, tschemas, role in spschema.relation_definitions(True):
       
   673                     for tschema in tschemas:
       
   674                         if not tschema in instschema:
       
   675                             continue
       
   676                         if role == 'subject':
       
   677                             subjschema = spschema
       
   678                             objschema = tschema
       
   679                             if rschema.final and instspschema.has_subject_relation(rschema):
       
   680                                 # attribute already set, has_rdef would check if
       
   681                                 # it's of the same type, we don't want this so
       
   682                                 # simply skip here
       
   683                                 continue
       
   684                         elif role == 'object':
       
   685                             subjschema = tschema
       
   686                             objschema = spschema
       
   687                         if (rschema.rproperty(subjschema, objschema, 'infered')
       
   688                             or (instschema.has_relation(rschema) and
       
   689                                 instschema[rschema].has_rdef(subjschema, objschema))):
       
   690                                 continue
       
   691                         self.cmd_add_relation_definition(
       
   692                             subjschema.type, rschema.type, objschema.type)
   653         if auto:
   693         if auto:
   654             # we have commit here to get relation types actually in the schema
   694             # we have commit here to get relation types actually in the schema
   655             self.commit()
   695             self.commit()
   656             added = []
   696             added = []
   657             for rschema in eschema.subject_relations():
   697             for rschema in eschema.subject_relations():
   658                 # attribute relation have already been processed and
   698                 # attribute relation have already been processed and
   659                 # 'owned_by'/'created_by' will be automatically added
   699                 # 'owned_by'/'created_by' will be automatically added
   660                 if rschema.final or rschema.type in META_RTYPES:
   700                 if rschema.final or rschema.type in META_RTYPES:
   661                     continue
   701                     continue
   662                 rtypeadded = rschema.type in applschema
   702                 rtypeadded = rschema.type in instschema
   663                 for targetschema in rschema.objects(etype):
   703                 for targetschema in rschema.objects(etype):
   664                     # ignore relations where the targeted type is not in the
   704                     # ignore relations where the targeted type is not in the
   665                     # current instance schema
   705                     # current instance schema
   666                     targettype = targetschema.type
   706                     targettype = targetschema.type
   667                     if not targettype in applschema and targettype != etype:
   707                     if not targettype in instschema and targettype != etype:
   668                         continue
   708                         continue
   669                     if not rtypeadded:
   709                     if not rtypeadded:
   670                         # need to add the relation type and to commit to get it
   710                         # need to add the relation type and to commit to get it
   671                         # actually in the schema
   711                         # actually in the schema
   672                         added.append(rschema.type)
   712                         added.append(rschema.type)
   677                     # such as "Emailthread forked_from Emailthread"
   717                     # such as "Emailthread forked_from Emailthread"
   678                     added.append((etype, rschema.type, targettype))
   718                     added.append((etype, rschema.type, targettype))
   679                     self.rqlexecall(ss.rdef2rql(rschema, etype, targettype),
   719                     self.rqlexecall(ss.rdef2rql(rschema, etype, targettype),
   680                                     ask_confirm=confirm)
   720                                     ask_confirm=confirm)
   681             for rschema in eschema.object_relations():
   721             for rschema in eschema.object_relations():
   682                 rtypeadded = rschema.type in applschema or rschema.type in added
   722                 rtypeadded = rschema.type in instschema or rschema.type in added
   683                 for targetschema in rschema.subjects(etype):
   723                 for targetschema in rschema.subjects(etype):
   684                     # ignore relations where the targeted type is not in the
   724                     # ignore relations where the targeted type is not in the
   685                     # current instance schema
   725                     # current instance schema
   686                     targettype = targetschema.type
   726                     targettype = targetschema.type
   687                     # don't check targettype != etype since in this case the
   727                     # don't check targettype != etype since in this case the
   688                     # relation has already been added as a subject relation
   728                     # relation has already been added as a subject relation
   689                     if not targettype in applschema:
   729                     if not targettype in instschema:
   690                         continue
   730                         continue
   691                     if not rtypeadded:
   731                     if not rtypeadded:
   692                         # need to add the relation type and to commit to get it
   732                         # need to add the relation type and to commit to get it
   693                         # actually in the schema
   733                         # actually in the schema
   694                         self.cmd_add_relation_type(rschema.type, False, commit=True)
   734                         self.cmd_add_relation_type(rschema.type, False, commit=True)
   912     def cmd_synchronize_permissions(self, ertype, commit=True):
   952     def cmd_synchronize_permissions(self, ertype, commit=True):
   913         self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit)
   953         self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit)
   914 
   954 
   915     # Workflows handling ######################################################
   955     # Workflows handling ######################################################
   916 
   956 
       
   957     def cmd_add_workflow(self, name, wfof, default=True, commit=False,
       
   958                          **kwargs):
       
   959         self.session.set_pool() # ensure pool is set
       
   960         wf = self.cmd_create_entity('Workflow', name=unicode(name),
       
   961                                     **kwargs)
       
   962         if not isinstance(wfof, (list, tuple)):
       
   963             wfof = (wfof,)
       
   964         for etype in wfof:
       
   965             rset = self.rqlexec(
       
   966                 'SET X workflow_of ET WHERE X eid %(x)s, ET name %(et)s',
       
   967                 {'x': wf.eid, 'et': etype}, 'x', ask_confirm=False)
       
   968             assert rset, 'unexistant entity type %s' % etype
       
   969             if default:
       
   970                 self.rqlexec(
       
   971                     'SET ET default_workflow X WHERE X eid %(x)s, ET name %(et)s',
       
   972                     {'x': wf.eid, 'et': etype}, 'x', ask_confirm=False)
       
   973         if commit:
       
   974             self.commit()
       
   975         return wf
       
   976 
       
   977     # XXX remove once cmd_add_[state|transition] are removed
       
   978     def _get_or_create_wf(self, etypes):
       
   979         self.session.set_pool() # ensure pool is set
       
   980         if not isinstance(etypes, (list, tuple)):
       
   981             etypes = (etypes,)
       
   982         rset = self.rqlexec('Workflow X WHERE X workflow_of ET, ET name %(et)s',
       
   983                             {'et': etypes[0]})
       
   984         if rset:
       
   985             return rset.get_entity(0, 0)
       
   986         return self.cmd_add_workflow('%s workflow' % ';'.join(etypes), etypes)
       
   987 
       
   988     @deprecated('use add_workflow and Workflow.add_state method')
   917     def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs):
   989     def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs):
   918         """method to ease workflow definition: add a state for one or more
   990         """method to ease workflow definition: add a state for one or more
   919         entity type(s)
   991         entity type(s)
   920         """
   992         """
   921         stateeid = self.cmd_add_entity('State', name=name, **kwargs)
   993         wf = self._get_or_create_wf(stateof)
   922         if not isinstance(stateof, (list, tuple)):
   994         state = wf.add_state(name, initial, **kwargs)
   923             stateof = (stateof,)
   995         if commit:
   924         for etype in stateof:
   996             self.commit()
   925             # XXX ensure etype validity
   997         return state.eid
   926             self.rqlexec('SET X state_of Y WHERE X eid %(x)s, Y name %(et)s',
   998 
   927                          {'x': stateeid, 'et': etype}, 'x', ask_confirm=False)
   999     @deprecated('use add_workflow and Workflow.add_transition method')
   928             if initial:
       
   929                 self.rqlexec('SET ET initial_state S WHERE ET name %(et)s, S eid %(x)s',
       
   930                              {'x': stateeid, 'et': etype}, 'x', ask_confirm=False)
       
   931         if commit:
       
   932             self.commit()
       
   933         return stateeid
       
   934 
       
   935     def cmd_add_transition(self, name, transitionof, fromstates, tostate,
  1000     def cmd_add_transition(self, name, transitionof, fromstates, tostate,
   936                            requiredgroups=(), conditions=(), commit=False, **kwargs):
  1001                            requiredgroups=(), conditions=(), commit=False, **kwargs):
   937         """method to ease workflow definition: add a transition for one or more
  1002         """method to ease workflow definition: add a transition for one or more
   938         entity type(s), from one or more state and to a single state
  1003         entity type(s), from one or more state and to a single state
   939         """
  1004         """
   940         treid = self.cmd_add_entity('Transition', name=name, **kwargs)
  1005         wf = self._get_or_create_wf(transitionof)
   941         if not isinstance(transitionof, (list, tuple)):
  1006         tr = wf.add_transition(name, fromstates, tostate, requiredgroups,
   942             transitionof = (transitionof,)
  1007                                conditions, **kwargs)
   943         for etype in transitionof:
  1008         if commit:
   944             # XXX ensure etype validity
  1009             self.commit()
   945             self.rqlexec('SET X transition_of Y WHERE X eid %(x)s, Y name %(et)s',
  1010         return tr.eid
   946                          {'x': treid, 'et': etype}, 'x', ask_confirm=False)
  1011 
   947         for stateeid in fromstates:
  1012     @deprecated('use Transition.set_transition_permissions method')
   948             self.rqlexec('SET X allowed_transition Y WHERE X eid %(x)s, Y eid %(y)s',
       
   949                          {'x': stateeid, 'y': treid}, 'x', ask_confirm=False)
       
   950         self.rqlexec('SET X destination_state Y WHERE X eid %(x)s, Y eid %(y)s',
       
   951                      {'x': treid, 'y': tostate}, 'x', ask_confirm=False)
       
   952         self.cmd_set_transition_permissions(treid, requiredgroups, conditions,
       
   953                                             reset=False)
       
   954         if commit:
       
   955             self.commit()
       
   956         return treid
       
   957 
       
   958     def cmd_set_transition_permissions(self, treid,
  1013     def cmd_set_transition_permissions(self, treid,
   959                                        requiredgroups=(), conditions=(),
  1014                                        requiredgroups=(), conditions=(),
   960                                        reset=True, commit=False):
  1015                                        reset=True, commit=False):
   961         """set or add (if `reset` is False) groups and conditions for a
  1016         """set or add (if `reset` is False) groups and conditions for a
   962         transition
  1017         transition
   963         """
  1018         """
   964         if reset:
  1019         self.session.set_pool() # ensure pool is set
   965             self.rqlexec('DELETE T require_group G WHERE T eid %(x)s',
  1020         tr = self.session.entity_from_eid(treid)
   966                          {'x': treid}, 'x', ask_confirm=False)
  1021         tr.set_transition_permissions(requiredgroups, conditions, reset)
   967             self.rqlexec('DELETE T condition R WHERE T eid %(x)s',
  1022         if commit:
   968                          {'x': treid}, 'x', ask_confirm=False)
  1023             self.commit()
   969         for gname in requiredgroups:
  1024 
   970             ### XXX ensure gname validity
  1025     @deprecated('use entity.change_state("state")')
   971             self.rqlexec('SET T require_group G WHERE T eid %(x)s, G name %(gn)s',
       
   972                          {'x': treid, 'gn': gname}, 'x', ask_confirm=False)
       
   973         if isinstance(conditions, basestring):
       
   974             conditions = (conditions,)
       
   975         for expr in conditions:
       
   976             if isinstance(expr, str):
       
   977                 expr = unicode(expr)
       
   978             self.rqlexec('INSERT RQLExpression X: X exprtype "ERQLExpression", '
       
   979                          'X expression %(expr)s, T condition X '
       
   980                          'WHERE T eid %(x)s',
       
   981                          {'x': treid, 'expr': expr}, 'x', ask_confirm=False)
       
   982         if commit:
       
   983             self.commit()
       
   984 
       
   985     def cmd_set_state(self, eid, statename, commit=False):
  1026     def cmd_set_state(self, eid, statename, commit=False):
   986         self.session.set_pool() # ensure pool is set
  1027         self.session.set_pool() # ensure pool is set
   987         entity = self.session.entity_from_eid(eid)
  1028         self.session.entity_from_eid(eid).change_state(statename)
   988         entity.change_state(entity.wf_state(statename).eid)
       
   989         if commit:
  1029         if commit:
   990             self.commit()
  1030             self.commit()
   991 
  1031 
   992     # CWProperty handling ######################################################
  1032     # CWProperty handling ######################################################
   993 
  1033 
  1000         value = unicode(value)
  1040         value = unicode(value)
  1001         try:
  1041         try:
  1002             prop = self.rqlexec('CWProperty X WHERE X pkey %(k)s', {'k': pkey},
  1042             prop = self.rqlexec('CWProperty X WHERE X pkey %(k)s', {'k': pkey},
  1003                                 ask_confirm=False).get_entity(0, 0)
  1043                                 ask_confirm=False).get_entity(0, 0)
  1004         except:
  1044         except:
  1005             self.cmd_add_entity('CWProperty', pkey=unicode(pkey), value=value)
  1045             self.cmd_create_entity('CWProperty', pkey=unicode(pkey), value=value)
  1006         else:
  1046         else:
  1007             self.rqlexec('SET X value %(v)s WHERE X pkey %(k)s',
  1047             self.rqlexec('SET X value %(v)s WHERE X pkey %(k)s',
  1008                          {'k': pkey, 'v': value}, ask_confirm=False)
  1048                          {'k': pkey, 'v': value}, ask_confirm=False)
  1009 
  1049 
  1010     # other data migration commands ###########################################
  1050     # other data migration commands ###########################################
  1011 
  1051 
       
  1052     def cmd_create_entity(self, etype, *args, **kwargs):
       
  1053         """add a new entity of the given type"""
       
  1054         commit = kwargs.pop('commit', False)
       
  1055         self.session.set_pool()
       
  1056         entity = self.session.create_entity(etype, *args, **kwargs)
       
  1057         if commit:
       
  1058             self.commit()
       
  1059         return entity
       
  1060 
       
  1061     @deprecated('use create_entity')
  1012     def cmd_add_entity(self, etype, *args, **kwargs):
  1062     def cmd_add_entity(self, etype, *args, **kwargs):
  1013         """add a new entity of the given type"""
  1063         """add a new entity of the given type"""
  1014         rql = 'INSERT %s X' % etype
  1064         return self.cmd_create_entity(etype, *args, **kwargs).eid
  1015         relations = []
       
  1016         restrictions = []
       
  1017         for rtype, rvar in args:
       
  1018             relations.append('X %s %s' % (rtype, rvar))
       
  1019             restrictions.append('%s eid %s' % (rvar, kwargs.pop(rvar)))
       
  1020         commit = kwargs.pop('commit', False)
       
  1021         for attr in kwargs:
       
  1022             relations.append('X %s %%(%s)s' % (attr, attr))
       
  1023         if relations:
       
  1024             rql = '%s: %s' % (rql, ', '.join(relations))
       
  1025         if restrictions:
       
  1026             rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
       
  1027         eid = self.rqlexec(rql, kwargs, ask_confirm=self.verbosity>=2).rows[0][0]
       
  1028         if commit:
       
  1029             self.commit()
       
  1030         return eid
       
  1031 
  1065 
  1032     def sqlexec(self, sql, args=None, ask_confirm=True):
  1066     def sqlexec(self, sql, args=None, ask_confirm=True):
  1033         """execute the given sql if confirmed
  1067         """execute the given sql if confirmed
  1034 
  1068 
  1035         should only be used for low level stuff undoable with existing higher
  1069         should only be used for low level stuff undoable with existing higher
  1053     def rqlexec(self, rql, kwargs=None, cachekey=None, ask_confirm=True):
  1087     def rqlexec(self, rql, kwargs=None, cachekey=None, ask_confirm=True):
  1054         """rql action"""
  1088         """rql action"""
  1055         if not isinstance(rql, (tuple, list)):
  1089         if not isinstance(rql, (tuple, list)):
  1056             rql = ( (rql, kwargs), )
  1090             rql = ( (rql, kwargs), )
  1057         res = None
  1091         res = None
       
  1092         self.session.set_pool()
  1058         for rql, kwargs in rql:
  1093         for rql, kwargs in rql:
  1059             if kwargs:
  1094             if kwargs:
  1060                 msg = '%s (%s)' % (rql, kwargs)
  1095                 msg = '%s (%s)' % (rql, kwargs)
  1061             else:
  1096             else:
  1062                 msg = rql
  1097                 msg = rql
  1063             if not ask_confirm or self.confirm('execute rql: %s ?' % msg):
  1098             if not ask_confirm or self.confirm('execute rql: %s ?' % msg):
  1064                 try:
  1099                 try:
  1065                     res = self.rqlcursor.execute(rql, kwargs, cachekey)
  1100                     res = self.session.execute(rql, kwargs, cachekey)
  1066                 except Exception, ex:
  1101                 except Exception, ex:
  1067                     if self.confirm('error: %s\nabort?' % ex):
  1102                     if self.confirm('error: %s\nabort?' % ex):
  1068                         raise
  1103                         raise
  1069         return res
  1104         return res
  1070 
  1105 
  1149         else:
  1184         else:
  1150             msg = rql
  1185             msg = rql
  1151         if self.ask_confirm:
  1186         if self.ask_confirm:
  1152             if not self._h.confirm('execute rql: %s ?' % msg):
  1187             if not self._h.confirm('execute rql: %s ?' % msg):
  1153                 raise StopIteration
  1188                 raise StopIteration
       
  1189         self._h.session.set_pool()
  1154         try:
  1190         try:
  1155             rset = self._h.rqlcursor.execute(rql, kwargs)
  1191             rset = self._h.session.execute(rql, kwargs)
  1156         except Exception, ex:
  1192         except Exception, ex:
  1157             if self._h.confirm('error: %s\nabort?' % ex):
  1193             if self._h.confirm('error: %s\nabort?' % ex):
  1158                 raise
  1194                 raise
  1159             else:
  1195             else:
  1160                 raise StopIteration
  1196                 raise StopIteration