server/migractions.py
changeset 0 b97547f5f1fa
child 366 6d73bb2e9f6a
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """a class implementing basic actions used in migration scripts.
       
     2 
       
     3 The following schema actions are supported for now:
       
     4 * add/drop/rename attribute
       
     5 * add/drop entity/relation type
       
     6 * rename entity type
       
     7 
       
     8 The following data actions are supported for now:
       
     9 * add an entity
       
    10 * execute raw RQL queries
       
    11 
       
    12 
       
    13 :organization: Logilab
       
    14 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
    15 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
    16 """
       
    17 __docformat__ = "restructuredtext en"
       
    18 
       
    19 import sys
       
    20 import os
       
    21 from os.path import join, exists
       
    22 
       
    23 from mx.DateTime import now
       
    24 from logilab.common.decorators import cached
       
    25 from logilab.common.adbh import get_adv_func_helper
       
    26 
       
    27 from yams.constraints import SizeConstraint
       
    28 from yams.schema2sql import eschema2sql, rschema2sql
       
    29 
       
    30 from cubicweb import AuthenticationError
       
    31 from cubicweb.dbapi import get_repository, repo_connect
       
    32 from cubicweb.common.migration import MigrationHelper, yes
       
    33 
       
    34 try:
       
    35     from cubicweb.server import schemaserial as ss
       
    36     from cubicweb.server.utils import manager_userpasswd
       
    37     from cubicweb.server.sqlutils import sqlexec
       
    38 except ImportError: # LAX
       
    39     pass
       
    40 
       
    41 class ServerMigrationHelper(MigrationHelper):
       
    42     """specific migration helper for server side  migration scripts,
       
    43     providind actions related to schema/data migration
       
    44     """
       
    45 
       
    46     def __init__(self, config, schema, interactive=True,
       
    47                  repo=None, cnx=None, verbosity=1, connect=True):
       
    48         MigrationHelper.__init__(self, config, interactive, verbosity)
       
    49         if not interactive:
       
    50             assert cnx
       
    51             assert repo
       
    52         if cnx is not None:
       
    53             assert repo
       
    54             self._cnx = cnx
       
    55             self.repo = repo
       
    56         elif connect:
       
    57             self.repo_connect()
       
    58         if not schema:
       
    59             schema = config.load_schema(expand_cubes=True)
       
    60         self.new_schema = schema
       
    61         self._synchronized = set()
       
    62 
       
    63     @cached
       
    64     def repo_connect(self):
       
    65         self.repo = get_repository(method='inmemory', config=self.config)
       
    66         return self.repo
       
    67     
       
    68     def shutdown(self):
       
    69         if self.repo is not None:
       
    70             self.repo.shutdown()
       
    71         
       
    72     def rewrite_vcconfiguration(self):
       
    73         """write current installed versions (of cubicweb software
       
    74         and of each used cube) into the database
       
    75         """
       
    76         self.cmd_set_property('system.version.cubicweb', self.config.cubicweb_version())
       
    77         for pkg in self.config.cubes():
       
    78             pkgversion = self.config.cube_version(pkg)
       
    79             self.cmd_set_property('system.version.%s' % pkg.lower(), pkgversion)
       
    80         self.commit()
       
    81         
       
    82     def backup_database(self, backupfile=None, askconfirm=True):
       
    83         config = self.config
       
    84         source = config.sources()['system']
       
    85         helper = get_adv_func_helper(source['db-driver'])
       
    86         date = now().strftime('%Y-%m-%d_%H:%M:%S')
       
    87         app = config.appid
       
    88         backupfile = backupfile or join(config.backup_dir(),
       
    89                                         '%s-%s.dump' % (app, date))
       
    90         if exists(backupfile):
       
    91             if not self.confirm('a backup already exists for %s, overwrite it?' % app):
       
    92                 return
       
    93         elif askconfirm and not self.confirm('backup %s database?' % app):
       
    94             return
       
    95         cmd = helper.backup_command(source['db-name'], source.get('db-host'),
       
    96                                     source.get('db-user'), backupfile,
       
    97                                     keepownership=False)
       
    98         while True:
       
    99             print cmd
       
   100             if os.system(cmd):
       
   101                 print 'error while backuping the base'
       
   102                 answer = self.confirm('continue anyway?',
       
   103                                       shell=False, abort=False, retry=True)
       
   104                 if not answer:
       
   105                     raise SystemExit(1)
       
   106                 if answer == 1: # 1: continue, 2: retry
       
   107                     break
       
   108             else:
       
   109                 print 'database backup:', backupfile
       
   110                 break
       
   111         
       
   112     def restore_database(self, backupfile, drop=True):
       
   113         config = self.config
       
   114         source = config.sources()['system']
       
   115         helper = get_adv_func_helper(source['db-driver'])
       
   116         app = config.appid
       
   117         if not exists(backupfile):
       
   118             raise Exception("backup file %s doesn't exist" % backupfile)
       
   119         if self.confirm('restore %s database from %s ?' % (app, backupfile)):
       
   120             for cmd in helper.restore_commands(source['db-name'], source.get('db-host'),
       
   121                                                source.get('db-user'), backupfile,
       
   122                                                source['db-encoding'],
       
   123                                                keepownership=False, drop=drop):
       
   124                 while True:
       
   125                     print cmd
       
   126                     if os.system(cmd):
       
   127                         print 'error while restoring the base'
       
   128                         answer = self.confirm('continue anyway?',
       
   129                                               shell=False, abort=False, retry=True)
       
   130                         if not answer:
       
   131                             raise SystemExit(1)
       
   132                         if answer == 1: # 1: continue, 2: retry
       
   133                             break
       
   134                     else:
       
   135                         break
       
   136             print 'database restored'
       
   137         
       
   138     def migrate(self, vcconf, toupgrade, options):
       
   139         if not options.fs_only:
       
   140             if options.backup_db is None:
       
   141                 self.backup_database()
       
   142             elif options.backup_db:
       
   143                 self.backup_database(askconfirm=False)
       
   144         super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
       
   145         self.rewrite_configuration()
       
   146     
       
   147     def process_script(self, migrscript, funcname=None, *args, **kwargs):
       
   148         """execute a migration script
       
   149         in interactive mode,  display the migration script path, ask for
       
   150         confirmation and execute it if confirmed
       
   151         """
       
   152         if migrscript.endswith('.sql'):
       
   153             if self.execscript_confirm(migrscript):
       
   154                 sqlexec(open(migrscript).read(), self.session.system_sql)
       
   155         else:
       
   156             return super(ServerMigrationHelper, self).process_script(
       
   157                 migrscript, funcname, *args, **kwargs)
       
   158         
       
   159     @property
       
   160     def cnx(self):
       
   161         """lazy connection"""
       
   162         try:
       
   163             return self._cnx
       
   164         except AttributeError:
       
   165             sourcescfg = self.repo.config.sources()
       
   166             try:
       
   167                 login = sourcescfg['admin']['login']
       
   168                 pwd = sourcescfg['admin']['password']
       
   169             except KeyError:
       
   170                 login, pwd = manager_userpasswd()
       
   171             while True:
       
   172                 try:
       
   173                     self._cnx = repo_connect(self.repo, login, pwd)
       
   174                     if not 'managers' in self._cnx.user(self.session).groups:
       
   175                         print 'migration need an account in the managers group'
       
   176                     else:
       
   177                         break
       
   178                 except AuthenticationError:
       
   179                     print 'wrong user/password'
       
   180                 except (KeyboardInterrupt, EOFError):
       
   181                     print 'aborting...'
       
   182                     sys.exit(0)
       
   183                 try:
       
   184                     login, pwd = manager_userpasswd()
       
   185                 except (KeyboardInterrupt, EOFError):
       
   186                     print 'aborting...'
       
   187                     sys.exit(0)
       
   188             return self._cnx
       
   189 
       
   190     @property
       
   191     def session(self):
       
   192         return self.repo._get_session(self.cnx.sessionid)
       
   193     
       
   194     @property
       
   195     @cached
       
   196     def rqlcursor(self):
       
   197         """lazy rql cursor"""
       
   198         return self.cnx.cursor(self.session)    
       
   199     
       
   200     def commit(self):
       
   201         if hasattr(self, '_cnx'):
       
   202             self._cnx.commit()
       
   203             
       
   204     def rollback(self):
       
   205         if hasattr(self, '_cnx'):
       
   206             self._cnx.rollback()
       
   207                    
       
   208     def rqlexecall(self, rqliter, cachekey=None, ask_confirm=True):
       
   209         for rql, kwargs in rqliter:
       
   210             self.rqlexec(rql, kwargs, cachekey, ask_confirm)
       
   211 
       
   212     @cached
       
   213     def _create_context(self):
       
   214         """return a dictionary to use as migration script execution context"""
       
   215         context = super(ServerMigrationHelper, self)._create_context()
       
   216         context.update({'checkpoint': self.checkpoint,
       
   217                         'sql': self.sqlexec,
       
   218                         'rql': self.rqlexec,
       
   219                         'rqliter': self.rqliter,
       
   220                         'schema': self.repo.schema,
       
   221                         'newschema': self.new_schema,
       
   222                         'cnx': self.cnx,
       
   223                         'session' : self.session,
       
   224                         'repo' : self.repo,
       
   225                         })
       
   226         return context
       
   227 
       
   228     @cached
       
   229     def group_mapping(self):
       
   230         """cached group mapping"""
       
   231         return ss.group_mapping(self.rqlcursor)
       
   232         
       
   233     def exec_event_script(self, event, cubepath=None, funcname=None,
       
   234                           *args, **kwargs):            
       
   235         if cubepath:
       
   236             apc = join(cubepath, 'migration', '%s.py' % event)
       
   237         else:
       
   238             apc = join(self.config.migration_scripts_dir(), '%s.py' % event)
       
   239         if exists(apc):
       
   240             if self.config.free_wheel:
       
   241                 from cubicweb.server.hooks import setowner_after_add_entity
       
   242                 self.repo.hm.unregister_hook(setowner_after_add_entity,
       
   243                                              'after_add_entity', '')
       
   244                 self.deactivate_verification_hooks()
       
   245             self.info('executing %s', apc)
       
   246             confirm = self.confirm
       
   247             execscript_confirm = self.execscript_confirm
       
   248             self.confirm = yes
       
   249             self.execscript_confirm = yes
       
   250             try:
       
   251                 return self.process_script(apc, funcname, *args, **kwargs)
       
   252             finally:
       
   253                 self.confirm = confirm
       
   254                 self.execscript_confirm = execscript_confirm
       
   255                 if self.config.free_wheel:
       
   256                     self.repo.hm.register_hook(setowner_after_add_entity,
       
   257                                                'after_add_entity', '')
       
   258                     self.reactivate_verification_hooks()
       
   259     
       
   260     # base actions ############################################################
       
   261 
       
   262     def checkpoint(self):
       
   263         """checkpoint action"""
       
   264         if self.confirm('commit now ?', shell=False):
       
   265             self.commit()
       
   266 
       
   267     def cmd_add_cube(self, cube, update_database=True):
       
   268         """update_database is telling if the database schema should be updated
       
   269         or if only the relevant eproperty should be inserted (for the case where
       
   270         a cube has been extracted from an existing application, so the
       
   271         cube schema is already in there)
       
   272         """
       
   273         newcubes = super(ServerMigrationHelper, self).cmd_add_cube(
       
   274             cube)
       
   275         if not newcubes:
       
   276             return
       
   277         for pack in newcubes:
       
   278             self.cmd_set_property('system.version.'+pack,
       
   279                                   self.config.cube_version(pack))
       
   280         if not update_database:
       
   281             self.commit()
       
   282             return
       
   283         self.new_schema = self.config.load_schema()
       
   284         new = set()
       
   285         # execute pre-create files
       
   286         for pack in reversed(newcubes):
       
   287             self.exec_event_script('precreate', self.config.cube_dir(pack))
       
   288         # add new entity and relation types
       
   289         for rschema in self.new_schema.relations():
       
   290             if not rschema in self.repo.schema:
       
   291                 self.cmd_add_relation_type(rschema.type)
       
   292                 new.add(rschema.type)
       
   293         for eschema in self.new_schema.entities():
       
   294             if not eschema in self.repo.schema:
       
   295                 self.cmd_add_entity_type(eschema.type)
       
   296                 new.add(eschema.type)
       
   297         # check if attributes has been added to existing entities
       
   298         for rschema in self.new_schema.relations():
       
   299             existingschema = self.repo.schema.rschema(rschema.type)
       
   300             for (fromtype, totype) in rschema.iter_rdefs():
       
   301                 if existingschema.has_rdef(fromtype, totype):
       
   302                     continue
       
   303                 # check we should actually add the relation definition
       
   304                 if not (fromtype in new or totype in new or rschema in new):
       
   305                     continue
       
   306                 self.cmd_add_relation_definition(str(fromtype), rschema.type, 
       
   307                                                  str(totype))
       
   308         # execute post-create files
       
   309         for pack in reversed(newcubes):
       
   310             self.exec_event_script('postcreate', self.config.cube_dir(pack))
       
   311             self.commit()        
       
   312                 
       
   313     def cmd_remove_cube(self, cube):
       
   314         removedcubes = super(ServerMigrationHelper, self).cmd_remove_cube(cube)
       
   315         if not removedcubes:
       
   316             return
       
   317         oldschema = self.new_schema
       
   318         self.new_schema = newschema = self.config.load_schema()
       
   319         reposchema = self.repo.schema
       
   320         # execute pre-remove files
       
   321         for pack in reversed(removedcubes):
       
   322             self.exec_event_script('preremove', self.config.cube_dir(pack))
       
   323         # remove cubes'entity and relation types
       
   324         for rschema in oldschema.relations():
       
   325             if not rschema in newschema and rschema in reposchema:
       
   326                 self.cmd_drop_relation_type(rschema.type)
       
   327         for eschema in oldschema.entities():
       
   328             if not eschema in newschema and eschema in reposchema:
       
   329                 self.cmd_drop_entity_type(eschema.type)
       
   330         for rschema in oldschema.relations():
       
   331             if rschema in newschema and rschema in reposchema: 
       
   332                 # check if attributes/relations has been added to entities from 
       
   333                 # other cubes
       
   334                 for fromtype, totype in rschema.iter_rdefs():
       
   335                     if not newschema[rschema.type].has_rdef(fromtype, totype) and \
       
   336                            reposchema[rschema.type].has_rdef(fromtype, totype):
       
   337                         self.cmd_drop_relation_definition(
       
   338                             str(fromtype), rschema.type, str(totype))
       
   339         # execute post-remove files
       
   340         for pack in reversed(removedcubes):
       
   341             self.exec_event_script('postremove', self.config.cube_dir(pack))
       
   342             self.rqlexec('DELETE EProperty X WHERE X pkey %(pk)s',
       
   343                          {'pk': u'system.version.'+pack}, ask_confirm=False)
       
   344             self.commit()
       
   345             
       
   346     # schema migration actions ################################################
       
   347     
       
   348     def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True):
       
   349         """add a new attribute on the given entity type"""
       
   350         if attrtype is None:
       
   351             rschema = self.new_schema.rschema(attrname)
       
   352             attrtype = rschema.objects(etype)[0]
       
   353         self.cmd_add_relation_definition(etype, attrname, attrtype, commit=commit)
       
   354         
       
   355     def cmd_drop_attribute(self, etype, attrname, commit=True):
       
   356         """drop an existing attribute from the given entity type
       
   357         
       
   358         `attrname` is a string giving the name of the attribute to drop
       
   359         """
       
   360         rschema = self.repo.schema.rschema(attrname)
       
   361         attrtype = rschema.objects(etype)[0]
       
   362         self.cmd_drop_relation_definition(etype, attrname, attrtype, commit=commit)
       
   363 
       
   364     def cmd_rename_attribute(self, etype, oldname, newname, commit=True):
       
   365         """rename an existing attribute of the given entity type
       
   366         
       
   367         `oldname` is a string giving the name of the existing attribute
       
   368         `newname` is a string giving the name of the renamed attribute
       
   369         """
       
   370         eschema = self.new_schema.eschema(etype)
       
   371         attrtype = eschema.destination(newname)
       
   372         # have to commit this first step anyway to get the definition
       
   373         # actually in the schema
       
   374         self.cmd_add_attribute(etype, newname, attrtype, commit=True)
       
   375         # skipp NULL values if the attribute is required
       
   376         rql = 'SET X %s VAL WHERE X is %s, X %s VAL' % (newname, etype, oldname)
       
   377         card = eschema.rproperty(newname, 'cardinality')[0]
       
   378         if card == '1':
       
   379             rql += ', NOT X %s NULL' % oldname
       
   380         self.rqlexec(rql, ask_confirm=self.verbosity>=2)
       
   381         self.cmd_drop_attribute(etype, oldname, commit=commit)
       
   382             
       
   383     def cmd_add_entity_type(self, etype, auto=True, commit=True):
       
   384         """register a new entity type
       
   385         
       
   386         in auto mode, automatically register entity's relation where the
       
   387         targeted type is known
       
   388         """
       
   389         applschema = self.repo.schema
       
   390         if etype in applschema:
       
   391             eschema = applschema[etype]
       
   392             if eschema.is_final():
       
   393                 applschema.del_entity_type(etype)
       
   394         else:
       
   395             eschema = self.new_schema.eschema(etype)
       
   396         confirm = self.verbosity >= 2
       
   397         # register the entity into EEType
       
   398         self.rqlexecall(ss.eschema2rql(eschema), ask_confirm=confirm)
       
   399         # add specializes relation if needed
       
   400         self.rqlexecall(ss.eschemaspecialize2rql(eschema), ask_confirm=confirm)
       
   401         # register groups / permissions for the entity
       
   402         self.rqlexecall(ss.erperms2rql(eschema, self.group_mapping()),
       
   403                         ask_confirm=confirm)
       
   404         # register entity's attributes
       
   405         for rschema, attrschema in eschema.attribute_definitions():
       
   406             # ignore those meta relations, they will be automatically added
       
   407             if rschema.type in ('eid', 'creation_date', 'modification_date'):
       
   408                 continue
       
   409             if not rschema.type in applschema:
       
   410                 # need to add the relation type and to commit to get it
       
   411                 # actually in the schema
       
   412                 self.cmd_add_relation_type(rschema.type, False, commit=True)
       
   413             # register relation definition
       
   414             self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type),
       
   415                             ask_confirm=confirm)
       
   416         if auto:
       
   417             # we have commit here to get relation types actually in the schema
       
   418             self.commit()
       
   419             added = []
       
   420             for rschema in eschema.subject_relations():
       
   421                 # attribute relation have already been processed and
       
   422                 # 'owned_by'/'created_by' will be automatically added
       
   423                 if rschema.final or rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'): 
       
   424                     continue
       
   425                 rtypeadded = rschema.type in applschema
       
   426                 for targetschema in rschema.objects(etype):
       
   427                     # ignore relations where the targeted type is not in the
       
   428                     # current application schema
       
   429                     targettype = targetschema.type
       
   430                     if not targettype in applschema and targettype != etype:
       
   431                         continue
       
   432                     if not rtypeadded:
       
   433                         # need to add the relation type and to commit to get it
       
   434                         # actually in the schema
       
   435                         added.append(rschema.type)
       
   436                         self.cmd_add_relation_type(rschema.type, False, commit=True)
       
   437                         rtypeadded = True
       
   438                     # register relation definition
       
   439                     # remember this two avoid adding twice non symetric relation
       
   440                     # such as "Emailthread forked_from Emailthread"
       
   441                     added.append((etype, rschema.type, targettype))
       
   442                     self.rqlexecall(ss.rdef2rql(rschema, etype, targettype),
       
   443                                     ask_confirm=confirm)
       
   444             for rschema in eschema.object_relations():
       
   445                 rtypeadded = rschema.type in applschema or rschema.type in added
       
   446                 for targetschema in rschema.subjects(etype):
       
   447                     # ignore relations where the targeted type is not in the
       
   448                     # current application schema
       
   449                     targettype = targetschema.type
       
   450                     # don't check targettype != etype since in this case the
       
   451                     # relation has already been added as a subject relation
       
   452                     if not targettype in applschema:
       
   453                         continue
       
   454                     if not rtypeadded:
       
   455                         # need to add the relation type and to commit to get it
       
   456                         # actually in the schema
       
   457                         self.cmd_add_relation_type(rschema.type, False, commit=True)
       
   458                         rtypeadded = True
       
   459                     elif (targettype, rschema.type, etype) in added:
       
   460                         continue
       
   461                     # register relation definition
       
   462                     self.rqlexecall(ss.rdef2rql(rschema, targettype, etype),
       
   463                                     ask_confirm=confirm)
       
   464         if commit:
       
   465             self.commit()
       
   466                 
       
   467     def cmd_drop_entity_type(self, etype, commit=True):
       
   468         """unregister an existing entity type
       
   469         
       
   470         This will trigger deletion of necessary relation types and definitions
       
   471         """
       
   472         # XXX what if we delete an entity type which is specialized by other types
       
   473         # unregister the entity from EEType
       
   474         self.rqlexec('DELETE EEType X WHERE X name %(etype)s', {'etype': etype},
       
   475                      ask_confirm=self.verbosity>=2)
       
   476         if commit:
       
   477             self.commit()
       
   478 
       
   479     def cmd_rename_entity_type(self, oldname, newname, commit=True):
       
   480         """rename an existing entity type in the persistent schema
       
   481         
       
   482         `oldname` is a string giving the name of the existing entity type
       
   483         `newname` is a string giving the name of the renamed entity type
       
   484         """
       
   485         self.rqlexec('SET ET name %(newname)s WHERE ET is EEType, ET name %(oldname)s',
       
   486                      {'newname' : unicode(newname), 'oldname' : oldname})
       
   487         if commit:
       
   488             self.commit()
       
   489         
       
   490     def cmd_add_relation_type(self, rtype, addrdef=True, commit=True):
       
   491         """register a new relation type named `rtype`, as described in the
       
   492         schema description file.
       
   493 
       
   494         `addrdef` is a boolean value; when True, it will also add all relations
       
   495         of the type just added found in the schema definition file. Note that it
       
   496         implies an intermediate "commit" which commits the relation type
       
   497         creation (but not the relation definitions themselves, for which
       
   498         committing depends on the `commit` argument value).
       
   499         
       
   500         """
       
   501         rschema = self.new_schema.rschema(rtype)
       
   502         # register the relation into ERType and insert necessary relation
       
   503         # definitions
       
   504         self.rqlexecall(ss.rschema2rql(rschema, addrdef=False),
       
   505                         ask_confirm=self.verbosity>=2)
       
   506         # register groups / permissions for the relation
       
   507         self.rqlexecall(ss.erperms2rql(rschema, self.group_mapping()),
       
   508                         ask_confirm=self.verbosity>=2)
       
   509         if addrdef:
       
   510             self.commit()
       
   511             self.rqlexecall(ss.rdef2rql(rschema),
       
   512                             ask_confirm=self.verbosity>=2)
       
   513         if commit:
       
   514             self.commit()
       
   515         
       
   516     def cmd_drop_relation_type(self, rtype, commit=True):
       
   517         """unregister an existing relation type"""
       
   518         # unregister the relation from ERType
       
   519         self.rqlexec('DELETE ERType X WHERE X name %r' % rtype,
       
   520                      ask_confirm=self.verbosity>=2)
       
   521         if commit:
       
   522             self.commit()
       
   523         
       
   524     def cmd_rename_relation(self, oldname, newname, commit=True):
       
   525         """rename an existing relation
       
   526         
       
   527         `oldname` is a string giving the name of the existing relation
       
   528         `newname` is a string giving the name of the renamed relation
       
   529         """
       
   530         self.cmd_add_relation_type(newname, commit=True)
       
   531         self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname),
       
   532                      ask_confirm=self.verbosity>=2)
       
   533         self.cmd_drop_relation_type(oldname, commit=commit)
       
   534 
       
   535     def cmd_add_relation_definition(self, subjtype, rtype, objtype, commit=True):
       
   536         """register a new relation definition, from its definition found in the
       
   537         schema definition file
       
   538         """
       
   539         rschema = self.new_schema.rschema(rtype)
       
   540         if not rtype in self.repo.schema:
       
   541             self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
       
   542         self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype),
       
   543                         ask_confirm=self.verbosity>=2)
       
   544         if commit:
       
   545             self.commit()
       
   546         
       
   547     def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
       
   548         """unregister an existing relation definition"""
       
   549         rschema = self.repo.schema.rschema(rtype)
       
   550         # unregister the definition from EFRDef or ENFRDef
       
   551         if rschema.is_final():
       
   552             etype = 'EFRDef'
       
   553         else:
       
   554             etype = 'ENFRDef'
       
   555         rql = ('DELETE %s X WHERE X from_entity FE, FE name "%s",'
       
   556                'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"')
       
   557         self.rqlexec(rql % (etype, subjtype, rtype, objtype),
       
   558                      ask_confirm=self.verbosity>=2)
       
   559         if commit:
       
   560             self.commit()
       
   561         
       
   562     def cmd_synchronize_permissions(self, ertype, commit=True):
       
   563         """permission synchronization for an entity or relation type"""
       
   564         if ertype in ('eid', 'has_text', 'identity'):
       
   565             return
       
   566         newrschema = self.new_schema[ertype]
       
   567         teid = self.repo.schema[ertype].eid
       
   568         if 'update' in newrschema.ACTIONS or newrschema.is_final():
       
   569             # entity type
       
   570             exprtype = u'ERQLExpression'
       
   571         else:
       
   572             # relation type
       
   573             exprtype = u'RRQLExpression'
       
   574         assert teid, ertype
       
   575         gm = self.group_mapping()
       
   576         confirm = self.verbosity >= 2
       
   577         # * remove possibly deprecated permission (eg in the persistent schema
       
   578         #   but not in the new schema)
       
   579         # * synchronize existing expressions
       
   580         # * add new groups/expressions
       
   581         for action in newrschema.ACTIONS:
       
   582             perm = '%s_permission' % action
       
   583             # handle groups
       
   584             newgroups = list(newrschema.get_groups(action))
       
   585             for geid, gname in self.rqlexec('Any G, GN WHERE T %s G, G name GN, '
       
   586                                             'T eid %%(x)s' % perm, {'x': teid}, 'x',
       
   587                                             ask_confirm=False):
       
   588                 if not gname in newgroups:
       
   589                     if not confirm or self.confirm('remove %s permission of %s to %s?'
       
   590                                                    % (action, ertype, gname)):
       
   591                         self.rqlexec('DELETE T %s G WHERE G eid %%(x)s, T eid %s'
       
   592                                      % (perm, teid),
       
   593                                      {'x': geid}, 'x', ask_confirm=False)
       
   594                 else:
       
   595                     newgroups.remove(gname)
       
   596             for gname in newgroups:
       
   597                 if not confirm or self.confirm('grant %s permission of %s to %s?'
       
   598                                                % (action, ertype, gname)):
       
   599                     self.rqlexec('SET T %s G WHERE G eid %%(x)s, T eid %s'
       
   600                                  % (perm, teid),
       
   601                                  {'x': gm[gname]}, 'x', ask_confirm=False)
       
   602             # handle rql expressions
       
   603             newexprs = dict((expr.expression, expr) for expr in newrschema.get_rqlexprs(action))
       
   604             for expreid, expression in self.rqlexec('Any E, EX WHERE T %s E, E expression EX, '
       
   605                                                     'T eid %s' % (perm, teid),
       
   606                                                     ask_confirm=False):
       
   607                 if not expression in newexprs:
       
   608                     if not confirm or self.confirm('remove %s expression for %s permission of %s?'
       
   609                                                    % (expression, action, ertype)):
       
   610                         # deleting the relation will delete the expression entity
       
   611                         self.rqlexec('DELETE T %s E WHERE E eid %%(x)s, T eid %s'
       
   612                                      % (perm, teid),
       
   613                                      {'x': expreid}, 'x', ask_confirm=False)
       
   614                 else:
       
   615                     newexprs.pop(expression)
       
   616             for expression in newexprs.values():
       
   617                 expr = expression.expression
       
   618                 if not confirm or self.confirm('add %s expression for %s permission of %s?'
       
   619                                                % (expr, action, ertype)):
       
   620                     self.rqlexec('INSERT RQLExpression X: X exprtype %%(exprtype)s, '
       
   621                                  'X expression %%(expr)s, X mainvars %%(vars)s, T %s X '
       
   622                                  'WHERE T eid %%(x)s' % perm,
       
   623                                  {'expr': expr, 'exprtype': exprtype,
       
   624                                   'vars': expression.mainvars, 'x': teid}, 'x',
       
   625                                  ask_confirm=False)
       
   626         if commit:
       
   627             self.commit()
       
   628         
       
   629     def cmd_synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True,
       
   630                                 commit=True):
       
   631         """synchronize properties of the persistent relation schema against its
       
   632         current definition:
       
   633         
       
   634         * description
       
   635         * symetric, meta
       
   636         * inlined
       
   637         * relation definitions if `syncrdefs`
       
   638         * permissions if `syncperms`
       
   639         
       
   640         physical schema changes should be handled by repository's schema hooks
       
   641         """
       
   642         rtype = str(rtype)
       
   643         if rtype in self._synchronized:
       
   644             return
       
   645         self._synchronized.add(rtype)
       
   646         rschema = self.new_schema.rschema(rtype)
       
   647         self.rqlexecall(ss.updaterschema2rql(rschema),
       
   648                         ask_confirm=self.verbosity>=2)
       
   649         reporschema = self.repo.schema.rschema(rtype)
       
   650         if syncrdefs:
       
   651             for subj, obj in rschema.iter_rdefs():
       
   652                 if not reporschema.has_rdef(subj, obj):
       
   653                     continue
       
   654                 self.cmd_synchronize_rdef_schema(subj, rschema, obj,
       
   655                                                  commit=False)
       
   656         if syncperms:
       
   657             self.cmd_synchronize_permissions(rtype, commit=False)
       
   658         if commit:
       
   659             self.commit()
       
   660                 
       
   661     def cmd_synchronize_eschema(self, etype, syncperms=True, commit=True):
       
   662         """synchronize properties of the persistent entity schema against
       
   663         its current definition:
       
   664         
       
   665         * description
       
   666         * internationalizable, fulltextindexed, indexed, meta
       
   667         * relations from/to this entity
       
   668         * permissions if `syncperms`
       
   669         """
       
   670         etype = str(etype)
       
   671         if etype in self._synchronized:
       
   672             return
       
   673         self._synchronized.add(etype)
       
   674         repoeschema = self.repo.schema.eschema(etype)
       
   675         try:
       
   676             eschema = self.new_schema.eschema(etype)
       
   677         except KeyError:
       
   678             return
       
   679         repospschema = repoeschema.specializes()
       
   680         espschema = eschema.specializes()
       
   681         if repospschema and not espschema:
       
   682             self.rqlexec('DELETE X specializes Y WHERE X is EEType, X name %(x)s',
       
   683                          {'x': str(repoechema)})
       
   684         elif not repospschema and espschema:
       
   685             self.rqlexec('SET X specializes Y WHERE X is EEType, X name %(x)s, '
       
   686                          'Y is EEType, Y name %(y)s',
       
   687                          {'x': str(repoechema), 'y': str(epschema)})
       
   688         self.rqlexecall(ss.updateeschema2rql(eschema),
       
   689                         ask_confirm=self.verbosity >= 2)
       
   690         for rschema, targettypes, x in eschema.relation_definitions(True):
       
   691             if x == 'subject':
       
   692                 if not rschema in repoeschema.subject_relations():
       
   693                     continue
       
   694                 subjtypes, objtypes = [etype], targettypes
       
   695             else: # x == 'object'
       
   696                 if not rschema in repoeschema.object_relations():
       
   697                     continue
       
   698                 subjtypes, objtypes = targettypes, [etype]
       
   699             self.cmd_synchronize_rschema(rschema, syncperms=syncperms,
       
   700                                          syncrdefs=False, commit=False)
       
   701             reporschema = self.repo.schema.rschema(rschema)
       
   702             for subj in subjtypes:
       
   703                 for obj in objtypes:
       
   704                     if not reporschema.has_rdef(subj, obj):
       
   705                         continue
       
   706                     self.cmd_synchronize_rdef_schema(subj, rschema, obj,
       
   707                                                      commit=False)
       
   708         if syncperms:
       
   709             self.cmd_synchronize_permissions(etype, commit=False)
       
   710         if commit:
       
   711             self.commit()
       
   712 
       
   713     def cmd_synchronize_rdef_schema(self, subjtype, rtype, objtype,
       
   714                                     commit=True):
       
   715         """synchronize properties of the persistent relation definition schema
       
   716         against its current definition:
       
   717         * order and other properties
       
   718         * constraints
       
   719         """
       
   720         subjtype, objtype = str(subjtype), str(objtype)
       
   721         rschema = self.new_schema.rschema(rtype)
       
   722         reporschema = self.repo.schema.rschema(rschema)
       
   723         if (subjtype, rschema, objtype) in self._synchronized:
       
   724             return
       
   725         self._synchronized.add((subjtype, rschema, objtype))
       
   726         if rschema.symetric:
       
   727             self._synchronized.add((objtype, rschema, subjtype))
       
   728         confirm = self.verbosity >= 2
       
   729         # properties
       
   730         self.rqlexecall(ss.updaterdef2rql(rschema, subjtype, objtype),
       
   731                         ask_confirm=confirm)
       
   732         # constraints
       
   733         newconstraints = list(rschema.rproperty(subjtype, objtype, 'constraints'))
       
   734         # 1. remove old constraints and update constraints of the same type
       
   735         # NOTE: don't use rschema.constraint_by_type because it may be
       
   736         #       out of sync with newconstraints when multiple
       
   737         #       constraints of the same type are used
       
   738         for cstr in reporschema.rproperty(subjtype, objtype, 'constraints'):
       
   739             for newcstr in newconstraints:
       
   740                 if newcstr.type() == cstr.type():
       
   741                     break
       
   742             else:
       
   743                 newcstr = None
       
   744             if newcstr is None:
       
   745                 self.rqlexec('DELETE X constrained_by C WHERE C eid %(x)s',
       
   746                              {'x': cstr.eid}, 'x',
       
   747                              ask_confirm=confirm)
       
   748                 self.rqlexec('DELETE EConstraint C WHERE C eid %(x)s',
       
   749                              {'x': cstr.eid}, 'x',
       
   750                              ask_confirm=confirm)
       
   751             else:
       
   752                 newconstraints.remove(newcstr)
       
   753                 values = {'x': cstr.eid,
       
   754                           'v': unicode(newcstr.serialize())}
       
   755                 self.rqlexec('SET X value %(v)s WHERE X eid %(x)s',
       
   756                              values, 'x', ask_confirm=confirm)
       
   757         # 2. add new constraints
       
   758         for newcstr in newconstraints:
       
   759             self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
       
   760                                               newcstr),
       
   761                             ask_confirm=confirm)
       
   762         if commit:
       
   763             self.commit()
       
   764         
       
   765     def cmd_synchronize_schema(self, syncperms=True, commit=True):
       
   766         """synchronize the persistent schema against the current definition
       
   767         schema.
       
   768         
       
   769         It will synch common stuff between the definition schema and the
       
   770         actual persistent schema, it won't add/remove any entity or relation.
       
   771         """
       
   772         for etype in self.repo.schema.entities():
       
   773             self.cmd_synchronize_eschema(etype, syncperms=syncperms, commit=False)
       
   774         if commit:
       
   775             self.commit()
       
   776                 
       
   777     def cmd_change_relation_props(self, subjtype, rtype, objtype,
       
   778                                   commit=True, **kwargs):
       
   779         """change some properties of a relation definition"""
       
   780         assert kwargs
       
   781         restriction = []
       
   782         if subjtype and subjtype != 'Any':
       
   783             restriction.append('X from_entity FE, FE name "%s"' % subjtype)
       
   784         if objtype and objtype != 'Any':
       
   785             restriction.append('X to_entity TE, TE name "%s"' % objtype)
       
   786         if rtype and rtype != 'Any':
       
   787             restriction.append('X relation_type RT, RT name "%s"' % rtype)
       
   788         assert restriction
       
   789         values = []
       
   790         for k, v in kwargs.items():
       
   791             values.append('X %s %%(%s)s' % (k, k))
       
   792             if isinstance(v, str):
       
   793                 kwargs[k] = unicode(v)
       
   794         rql = 'SET %s WHERE %s' % (','.join(values), ','.join(restriction))
       
   795         self.rqlexec(rql, kwargs, ask_confirm=self.verbosity>=2)
       
   796         if commit:
       
   797             self.commit()
       
   798 
       
   799     def cmd_set_size_constraint(self, etype, rtype, size, commit=True):
       
   800         """set change size constraint of a string attribute
       
   801 
       
   802         if size is None any size constraint will be removed
       
   803         """
       
   804         oldvalue = None
       
   805         for constr in self.repo.schema.eschema(etype).constraints(rtype):
       
   806             if isinstance(constr, SizeConstraint):
       
   807                 oldvalue = constr.max
       
   808         if oldvalue == size:
       
   809             return
       
   810         if oldvalue is None and not size is None:
       
   811             ceid = self.rqlexec('INSERT EConstraint C: C value %(v)s, C cstrtype CT '
       
   812                                 'WHERE CT name "SizeConstraint"',
       
   813                                 {'v': SizeConstraint(size).serialize()},
       
   814                                 ask_confirm=self.verbosity>=2)[0][0]
       
   815             self.rqlexec('SET X constrained_by C WHERE X from_entity S, X relation_type R, '
       
   816                          'S name "%s", R name "%s", C eid %s' % (etype, rtype, ceid),
       
   817                          ask_confirm=self.verbosity>=2)
       
   818         elif not oldvalue is None:
       
   819             if not size is None:
       
   820                 self.rqlexec('SET C value %%(v)s WHERE X from_entity S, X relation_type R,'
       
   821                              'X constrained_by C, C cstrtype CT, CT name "SizeConstraint",'
       
   822                              'S name "%s", R name "%s"' % (etype, rtype),
       
   823                              {'v': unicode(SizeConstraint(size).serialize())},
       
   824                              ask_confirm=self.verbosity>=2)
       
   825             else:
       
   826                 self.rqlexec('DELETE X constrained_by C WHERE X from_entity S, X relation_type R,'
       
   827                              'X constrained_by C, C cstrtype CT, CT name "SizeConstraint",'
       
   828                              'S name "%s", R name "%s"' % (etype, rtype),
       
   829                              ask_confirm=self.verbosity>=2)
       
   830                 # cleanup unused constraints
       
   831                 self.rqlexec('DELETE EConstraint C WHERE NOT X constrained_by C')
       
   832         if commit:
       
   833             self.commit()
       
   834     
       
   835     # Workflows handling ######################################################
       
   836     
       
   837     def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs):
       
   838         """method to ease workflow definition: add a state for one or more
       
   839         entity type(s)
       
   840         """
       
   841         stateeid = self.cmd_add_entity('State', name=name, **kwargs)
       
   842         if not isinstance(stateof, (list, tuple)):
       
   843             stateof = (stateof,)
       
   844         for etype in stateof:
       
   845             # XXX ensure etype validity
       
   846             self.rqlexec('SET X state_of Y WHERE X eid %(x)s, Y name %(et)s',
       
   847                          {'x': stateeid, 'et': etype}, 'x', ask_confirm=False)
       
   848             if initial:
       
   849                 self.rqlexec('SET ET initial_state S WHERE ET name %(et)s, S eid %(x)s',
       
   850                              {'x': stateeid, 'et': etype}, 'x', ask_confirm=False)
       
   851         if commit:
       
   852             self.commit()
       
   853         return stateeid
       
   854     
       
   855     def cmd_add_transition(self, name, transitionof, fromstates, tostate,
       
   856                            requiredgroups=(), conditions=(), commit=False, **kwargs):
       
   857         """method to ease workflow definition: add a transition for one or more
       
   858         entity type(s), from one or more state and to a single state
       
   859         """
       
   860         treid = self.cmd_add_entity('Transition', name=name, **kwargs)
       
   861         if not isinstance(transitionof, (list, tuple)):
       
   862             transitionof = (transitionof,)
       
   863         for etype in transitionof:
       
   864             # XXX ensure etype validity
       
   865             self.rqlexec('SET X transition_of Y WHERE X eid %(x)s, Y name %(et)s',
       
   866                          {'x': treid, 'et': etype}, 'x', ask_confirm=False)
       
   867         for stateeid in fromstates:
       
   868             self.rqlexec('SET X allowed_transition Y WHERE X eid %(x)s, Y eid %(y)s',
       
   869                          {'x': stateeid, 'y': treid}, 'x', ask_confirm=False)
       
   870         self.rqlexec('SET X destination_state Y WHERE X eid %(x)s, Y eid %(y)s',
       
   871                      {'x': treid, 'y': tostate}, 'x', ask_confirm=False)
       
   872         self.cmd_set_transition_permissions(treid, requiredgroups, conditions,
       
   873                                             reset=False)
       
   874         if commit:
       
   875             self.commit()
       
   876         return treid
       
   877 
       
   878     def cmd_set_transition_permissions(self, treid,
       
   879                                        requiredgroups=(), conditions=(),
       
   880                                        reset=True, commit=False):
       
   881         """set or add (if `reset` is False) groups and conditions for a
       
   882         transition
       
   883         """
       
   884         if reset:
       
   885             self.rqlexec('DELETE T require_group G WHERE T eid %(x)s',
       
   886                          {'x': treid}, 'x', ask_confirm=False)
       
   887             self.rqlexec('DELETE T condition R WHERE T eid %(x)s',
       
   888                          {'x': treid}, 'x', ask_confirm=False)
       
   889         for gname in requiredgroups:
       
   890             ### XXX ensure gname validity
       
   891             self.rqlexec('SET T require_group G WHERE T eid %(x)s, G name %(gn)s',
       
   892                          {'x': treid, 'gn': gname}, 'x', ask_confirm=False)
       
   893         if isinstance(conditions, basestring):
       
   894             conditions = (conditions,)
       
   895         for expr in conditions:
       
   896             if isinstance(expr, str):
       
   897                 expr = unicode(expr)
       
   898             self.rqlexec('INSERT RQLExpression X: X exprtype "ERQLExpression", '
       
   899                          'X expression %(expr)s, T condition X '
       
   900                          'WHERE T eid %(x)s',
       
   901                          {'x': treid, 'expr': expr}, 'x', ask_confirm=False)
       
   902         if commit:
       
   903             self.commit()
       
   904 
       
   905     # EProperty handling ######################################################
       
   906 
       
   907     def cmd_property_value(self, pkey):
       
   908         rql = 'Any V WHERE X is EProperty, X pkey %(k)s, X value V'
       
   909         rset = self.rqlexec(rql, {'k': pkey}, ask_confirm=False)
       
   910         return rset[0][0]
       
   911 
       
   912     def cmd_set_property(self, pkey, value):
       
   913         value = unicode(value)
       
   914         try:
       
   915             prop = self.rqlexec('EProperty X WHERE X pkey %(k)s', {'k': pkey},
       
   916                                 ask_confirm=False).get_entity(0, 0)
       
   917         except:
       
   918             self.cmd_add_entity('EProperty', pkey=unicode(pkey), value=value)
       
   919         else:
       
   920             self.rqlexec('SET X value %(v)s WHERE X pkey %(k)s',
       
   921                          {'k': pkey, 'v': value}, ask_confirm=False)
       
   922 
       
   923     # other data migration commands ###########################################
       
   924         
       
   925     def cmd_add_entity(self, etype, *args, **kwargs):
       
   926         """add a new entity of the given type"""
       
   927         rql = 'INSERT %s X' % etype
       
   928         relations = []
       
   929         restrictions = []
       
   930         for rtype, rvar in args:
       
   931             relations.append('X %s %s' % (rtype, rvar))
       
   932             restrictions.append('%s eid %s' % (rvar, kwargs.pop(rvar)))
       
   933         commit = kwargs.pop('commit', False)
       
   934         for attr in kwargs:
       
   935             relations.append('X %s %%(%s)s' % (attr, attr))
       
   936         if relations:
       
   937             rql = '%s: %s' % (rql, ', '.join(relations))
       
   938         if restrictions:
       
   939             rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
       
   940         eid = self.rqlexec(rql, kwargs, ask_confirm=self.verbosity>=2).rows[0][0]
       
   941         if commit:
       
   942             self.commit()
       
   943         return eid
       
   944     
       
   945     def sqlexec(self, sql, args=None, ask_confirm=True):
       
   946         """execute the given sql if confirmed
       
   947         
       
   948         should only be used for low level stuff undoable with existing higher
       
   949         level actions
       
   950         """
       
   951         if not ask_confirm or self.confirm('execute sql: %s ?' % sql):
       
   952             self.session.set_pool() # ensure pool is set
       
   953             try:
       
   954                 cu = self.session.system_sql(sql, args)
       
   955             except:
       
   956                 ex = sys.exc_info()[1]
       
   957                 if self.confirm('error: %s\nabort?' % ex):
       
   958                     raise
       
   959                 return
       
   960             try:
       
   961                 return cu.fetchall()
       
   962             except:
       
   963                 # no result to fetch
       
   964                 return
       
   965     
       
   966     def rqlexec(self, rql, kwargs=None, cachekey=None, ask_confirm=True):
       
   967         """rql action"""
       
   968         if not isinstance(rql, (tuple, list)):
       
   969             rql = ( (rql, kwargs), )
       
   970         res = None
       
   971         for rql, kwargs in rql:
       
   972             if kwargs:
       
   973                 msg = '%s (%s)' % (rql, kwargs)
       
   974             else:
       
   975                 msg = rql
       
   976             if not ask_confirm or self.confirm('execute rql: %s ?' % msg):
       
   977                 try:
       
   978                     res = self.rqlcursor.execute(rql, kwargs, cachekey)
       
   979                 except Exception, ex:
       
   980                     if self.confirm('error: %s\nabort?' % ex):
       
   981                         raise
       
   982         return res
       
   983 
       
   984     def rqliter(self, rql, kwargs=None, ask_confirm=True):
       
   985         return ForRqlIterator(self, rql, None, ask_confirm)
       
   986 
       
   987     def cmd_deactivate_verification_hooks(self):
       
   988         self.repo.hm.deactivate_verification_hooks()
       
   989 
       
   990     def cmd_reactivate_verification_hooks(self):
       
   991         self.repo.hm.reactivate_verification_hooks()
       
   992         
       
   993     # broken db commands ######################################################
       
   994 
       
   995     def cmd_change_attribute_type(self, etype, attr, newtype, commit=True):
       
   996         """low level method to change the type of an entity attribute. This is
       
   997         a quick hack which has some drawback:
       
   998         * only works when the old type can be changed to the new type by the
       
   999           underlying rdbms (eg using ALTER TABLE)
       
  1000         * the actual schema won't be updated until next startup
       
  1001         """
       
  1002         rschema = self.repo.schema.rschema(attr)
       
  1003         oldtype = rschema.objects(etype)[0]
       
  1004         rdefeid = rschema.rproperty(etype, oldtype, 'eid')
       
  1005         sql = ("UPDATE EFRDef "
       
  1006                "SET to_entity=(SELECT eid FROM EEType WHERE name='%s')"
       
  1007                "WHERE eid=%s") % (newtype, rdefeid)
       
  1008         self.sqlexec(sql, ask_confirm=False)
       
  1009         dbhelper = self.repo.system_source.dbhelper
       
  1010         sqltype = dbhelper.TYPE_MAPPING[newtype]
       
  1011         sql = 'ALTER TABLE %s ALTER COLUMN %s TYPE %s' % (etype, attr, sqltype)
       
  1012         self.sqlexec(sql, ask_confirm=False)
       
  1013         if commit:
       
  1014             self.commit()
       
  1015         
       
  1016     def cmd_add_entity_type_table(self, etype, commit=True):
       
  1017         """low level method to create the sql table for an existing entity.
       
  1018         This may be useful on accidental desync between the repository schema
       
  1019         and a sql database
       
  1020         """
       
  1021         dbhelper = self.repo.system_source.dbhelper
       
  1022         tablesql = eschema2sql(dbhelper, self.repo.schema.eschema(etype))
       
  1023         for sql in tablesql.split(';'):
       
  1024             if sql.strip():
       
  1025                 self.sqlexec(sql)
       
  1026         if commit:
       
  1027             self.commit()
       
  1028             
       
  1029     def cmd_add_relation_type_table(self, rtype, commit=True):
       
  1030         """low level method to create the sql table for an existing relation.
       
  1031         This may be useful on accidental desync between the repository schema
       
  1032         and a sql database
       
  1033         """
       
  1034         dbhelper = self.repo.system_source.dbhelper
       
  1035         tablesql = rschema2sql(dbhelper, self.repo.schema.rschema(rtype))
       
  1036         for sql in tablesql.split(';'):
       
  1037             if sql.strip():
       
  1038                 self.sqlexec(sql)
       
  1039         if commit:
       
  1040             self.commit()
       
  1041             
       
  1042 
       
  1043 class ForRqlIterator:
       
  1044     """specific rql iterator to make the loop skipable"""
       
  1045     def __init__(self, helper, rql, kwargs, ask_confirm):
       
  1046         self._h = helper
       
  1047         self.rql = rql
       
  1048         self.kwargs = kwargs
       
  1049         self.ask_confirm = ask_confirm
       
  1050         self._rsetit = None
       
  1051         
       
  1052     def __iter__(self):
       
  1053         return self
       
  1054     
       
  1055     def next(self):
       
  1056         if self._rsetit is not None:
       
  1057             return self._rsetit.next()
       
  1058         rql, kwargs = self.rql, self.kwargs
       
  1059         if kwargs:
       
  1060             msg = '%s (%s)' % (rql, kwargs)
       
  1061         else:
       
  1062             msg = rql
       
  1063         if self.ask_confirm:
       
  1064             if not self._h.confirm('execute rql: %s ?' % msg):
       
  1065                 raise StopIteration
       
  1066         try:
       
  1067             #print rql, kwargs
       
  1068             rset = self._h.rqlcursor.execute(rql, kwargs)
       
  1069         except Exception, ex:
       
  1070             if self._h.confirm('error: %s\nabort?' % ex):
       
  1071                 raise
       
  1072             else:
       
  1073                 raise StopIteration
       
  1074         self._rsetit = iter(rset)
       
  1075         return self._rsetit.next()