server/migractions.py
branchtls-sprint
changeset 1477 b056a49c16dc
parent 1414 448c9802d7df
parent 1278 10fa95dd91ab
child 1790 a3e2b079de00
equal deleted inserted replaced
1476:f94b41709ce6 1477:b056a49c16dc
    38     from cubicweb.server.utils import manager_userpasswd
    38     from cubicweb.server.utils import manager_userpasswd
    39     from cubicweb.server.sqlutils import sqlexec, SQL_PREFIX
    39     from cubicweb.server.sqlutils import sqlexec, SQL_PREFIX
    40 except ImportError: # LAX
    40 except ImportError: # LAX
    41     pass
    41     pass
    42 
    42 
    43         
    43 
    44 class ServerMigrationHelper(MigrationHelper):
    44 class ServerMigrationHelper(MigrationHelper):
    45     """specific migration helper for server side  migration scripts,
    45     """specific migration helper for server side  migration scripts,
    46     providind actions related to schema/data migration
    46     providind actions related to schema/data migration
    47     """
    47     """
    48 
    48 
    65 
    65 
    66     @cached
    66     @cached
    67     def repo_connect(self):
    67     def repo_connect(self):
    68         self.repo = get_repository(method='inmemory', config=self.config)
    68         self.repo = get_repository(method='inmemory', config=self.config)
    69         return self.repo
    69         return self.repo
    70     
    70 
    71     def shutdown(self):
    71     def shutdown(self):
    72         if self.repo is not None:
    72         if self.repo is not None:
    73             self.repo.shutdown()
    73             self.repo.shutdown()
    74         
    74 
    75     def rewrite_vcconfiguration(self):
    75     def rewrite_vcconfiguration(self):
    76         """write current installed versions (of cubicweb software
    76         """write current installed versions (of cubicweb software
    77         and of each used cube) into the database
    77         and of each used cube) into the database
    78         """
    78         """
    79         self.cmd_set_property('system.version.cubicweb', self.config.cubicweb_version())
    79         self.cmd_set_property('system.version.cubicweb', self.config.cubicweb_version())
    80         for pkg in self.config.cubes():
    80         for pkg in self.config.cubes():
    81             pkgversion = self.config.cube_version(pkg)
    81             pkgversion = self.config.cube_version(pkg)
    82             self.cmd_set_property('system.version.%s' % pkg.lower(), pkgversion)
    82             self.cmd_set_property('system.version.%s' % pkg.lower(), pkgversion)
    83         self.commit()
    83         self.commit()
    84         
    84 
    85     def backup_database(self, backupfile=None, askconfirm=True):
    85     def backup_database(self, backupfile=None, askconfirm=True):
    86         config = self.config
    86         config = self.config
    87         source = config.sources()['system']
    87         source = config.sources()['system']
    88         helper = get_adv_func_helper(source['db-driver'])
    88         helper = get_adv_func_helper(source['db-driver'])
    89         date = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
    89         date = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
   111             else:
   111             else:
   112                 from cubicweb.toolsutils import restrict_perms_to_user
   112                 from cubicweb.toolsutils import restrict_perms_to_user
   113                 print 'database backup:', backupfile
   113                 print 'database backup:', backupfile
   114                 restrict_perms_to_user(backupfile, self.info)
   114                 restrict_perms_to_user(backupfile, self.info)
   115                 break
   115                 break
   116         
   116 
   117     def restore_database(self, backupfile, drop=True):
   117     def restore_database(self, backupfile, drop=True):
   118         config = self.config
   118         config = self.config
   119         source = config.sources()['system']
   119         source = config.sources()['system']
   120         helper = get_adv_func_helper(source['db-driver'])
   120         helper = get_adv_func_helper(source['db-driver'])
   121         app = config.appid
   121         app = config.appid
   137                         if answer == 1: # 1: continue, 2: retry
   137                         if answer == 1: # 1: continue, 2: retry
   138                             break
   138                             break
   139                     else:
   139                     else:
   140                         break
   140                         break
   141             print 'database restored'
   141             print 'database restored'
   142         
   142 
   143     def migrate(self, vcconf, toupgrade, options):
   143     def migrate(self, vcconf, toupgrade, options):
   144         if not options.fs_only:
   144         if not options.fs_only:
   145             if options.backup_db is None:
   145             if options.backup_db is None:
   146                 self.backup_database()
   146                 self.backup_database()
   147             elif options.backup_db:
   147             elif options.backup_db:
   148                 self.backup_database(askconfirm=False)
   148                 self.backup_database(askconfirm=False)
   149         super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
   149         super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
   150     
   150 
   151     def process_script(self, migrscript, funcname=None, *args, **kwargs):
   151     def process_script(self, migrscript, funcname=None, *args, **kwargs):
   152         """execute a migration script
   152         """execute a migration script
   153         in interactive mode,  display the migration script path, ask for
   153         in interactive mode,  display the migration script path, ask for
   154         confirmation and execute it if confirmed
   154         confirmation and execute it if confirmed
   155         """
   155         """
   157             if self.execscript_confirm(migrscript):
   157             if self.execscript_confirm(migrscript):
   158                 sqlexec(open(migrscript).read(), self.session.system_sql)
   158                 sqlexec(open(migrscript).read(), self.session.system_sql)
   159         else:
   159         else:
   160             return super(ServerMigrationHelper, self).process_script(
   160             return super(ServerMigrationHelper, self).process_script(
   161                 migrscript, funcname, *args, **kwargs)
   161                 migrscript, funcname, *args, **kwargs)
   162         
   162 
   163     @property
   163     @property
   164     def cnx(self):
   164     def cnx(self):
   165         """lazy connection"""
   165         """lazy connection"""
   166         try:
   166         try:
   167             return self._cnx
   167             return self._cnx
   192             return self._cnx
   192             return self._cnx
   193 
   193 
   194     @property
   194     @property
   195     def session(self):
   195     def session(self):
   196         return self.repo._get_session(self.cnx.sessionid)
   196         return self.repo._get_session(self.cnx.sessionid)
   197     
   197 
   198     @property
   198     @property
   199     @cached
   199     @cached
   200     def rqlcursor(self):
   200     def rqlcursor(self):
   201         """lazy rql cursor"""
   201         """lazy rql cursor"""
   202         # should not give session as cnx.cursor(), else we may try to execute
   202         # should not give session as cnx.cursor(), else we may try to execute
   203         # some query while no pool is set on the session (eg on entity attribute
   203         # some query while no pool is set on the session (eg on entity attribute
   204         # access for instance)
   204         # access for instance)
   205         return self.cnx.cursor()
   205         return self.cnx.cursor()
   206     
   206 
   207     def commit(self):
   207     def commit(self):
   208         if hasattr(self, '_cnx'):
   208         if hasattr(self, '_cnx'):
   209             self._cnx.commit()
   209             self._cnx.commit()
   210             
   210 
   211     def rollback(self):
   211     def rollback(self):
   212         if hasattr(self, '_cnx'):
   212         if hasattr(self, '_cnx'):
   213             self._cnx.rollback()
   213             self._cnx.rollback()
   214                    
   214 
   215     def rqlexecall(self, rqliter, cachekey=None, ask_confirm=True):
   215     def rqlexecall(self, rqliter, cachekey=None, ask_confirm=True):
   216         for rql, kwargs in rqliter:
   216         for rql, kwargs in rqliter:
   217             self.rqlexec(rql, kwargs, cachekey, ask_confirm)
   217             self.rqlexec(rql, kwargs, cachekey, ask_confirm)
   218 
   218 
   219     @cached
   219     @cached
   236 
   236 
   237     @cached
   237     @cached
   238     def group_mapping(self):
   238     def group_mapping(self):
   239         """cached group mapping"""
   239         """cached group mapping"""
   240         return ss.group_mapping(self.rqlcursor)
   240         return ss.group_mapping(self.rqlcursor)
   241         
   241 
   242     def exec_event_script(self, event, cubepath=None, funcname=None,
   242     def exec_event_script(self, event, cubepath=None, funcname=None,
   243                           *args, **kwargs):            
   243                           *args, **kwargs):
   244         if cubepath:
   244         if cubepath:
   245             apc = join(cubepath, 'migration', '%s.py' % event)
   245             apc = join(cubepath, 'migration', '%s.py' % event)
   246         else:
   246         else:
   247             apc = join(self.config.migration_scripts_dir(), '%s.py' % event)
   247             apc = join(self.config.migration_scripts_dir(), '%s.py' % event)
   248         if exists(apc):
   248         if exists(apc):
   263                 self.execscript_confirm = execscript_confirm
   263                 self.execscript_confirm = execscript_confirm
   264                 if self.config.free_wheel:
   264                 if self.config.free_wheel:
   265                     self.repo.hm.register_hook(setowner_after_add_entity,
   265                     self.repo.hm.register_hook(setowner_after_add_entity,
   266                                                'after_add_entity', '')
   266                                                'after_add_entity', '')
   267                     self.reactivate_verification_hooks()
   267                     self.reactivate_verification_hooks()
   268     
   268 
   269     # schema synchronization internals ########################################
   269     # schema synchronization internals ########################################
   270     
   270 
   271     def _synchronize_permissions(self, ertype):
   271     def _synchronize_permissions(self, ertype):
   272         """permission synchronization for an entity or relation type"""
   272         """permission synchronization for an entity or relation type"""
   273         if ertype in ('eid', 'has_text', 'identity'):
   273         if ertype in ('eid', 'has_text', 'identity'):
   274             return
   274             return
   275         newrschema = self.fs_schema[ertype]
   275         newrschema = self.fs_schema[ertype]
   330                                  'X expression %%(expr)s, X mainvars %%(vars)s, T %s X '
   330                                  'X expression %%(expr)s, X mainvars %%(vars)s, T %s X '
   331                                  'WHERE T eid %%(x)s' % perm,
   331                                  'WHERE T eid %%(x)s' % perm,
   332                                  {'expr': expr, 'exprtype': exprtype,
   332                                  {'expr': expr, 'exprtype': exprtype,
   333                                   'vars': expression.mainvars, 'x': teid}, 'x',
   333                                   'vars': expression.mainvars, 'x': teid}, 'x',
   334                                  ask_confirm=False)
   334                                  ask_confirm=False)
   335         
   335 
   336     def _synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True):
   336     def _synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True):
   337         """synchronize properties of the persistent relation schema against its
   337         """synchronize properties of the persistent relation schema against its
   338         current definition:
   338         current definition:
   339         
   339 
   340         * description
   340         * description
   341         * symetric, meta
   341         * symetric, meta
   342         * inlined
   342         * inlined
   343         * relation definitions if `syncrdefs`
   343         * relation definitions if `syncrdefs`
   344         * permissions if `syncperms`
   344         * permissions if `syncperms`
   345         
   345 
   346         physical schema changes should be handled by repository's schema hooks
   346         physical schema changes should be handled by repository's schema hooks
   347         """
   347         """
   348         rtype = str(rtype)
   348         rtype = str(rtype)
   349         if rtype in self._synchronized:
   349         if rtype in self._synchronized:
   350             return
   350             return
   358                 if not reporschema.has_rdef(subj, obj):
   358                 if not reporschema.has_rdef(subj, obj):
   359                     continue
   359                     continue
   360                 self._synchronize_rdef_schema(subj, rschema, obj)
   360                 self._synchronize_rdef_schema(subj, rschema, obj)
   361         if syncperms:
   361         if syncperms:
   362             self._synchronize_permissions(rtype)
   362             self._synchronize_permissions(rtype)
   363                 
   363 
   364     def _synchronize_eschema(self, etype, syncperms=True):
   364     def _synchronize_eschema(self, etype, syncperms=True):
   365         """synchronize properties of the persistent entity schema against
   365         """synchronize properties of the persistent entity schema against
   366         its current definition:
   366         its current definition:
   367         
   367 
   368         * description
   368         * description
   369         * internationalizable, fulltextindexed, indexed, meta
   369         * internationalizable, fulltextindexed, indexed, meta
   370         * relations from/to this entity
   370         * relations from/to this entity
   371         * permissions if `syncperms`
   371         * permissions if `syncperms`
   372         """
   372         """
   456         # 2. add new constraints
   456         # 2. add new constraints
   457         for newcstr in newconstraints:
   457         for newcstr in newconstraints:
   458             self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
   458             self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
   459                                               newcstr),
   459                                               newcstr),
   460                             ask_confirm=confirm)
   460                             ask_confirm=confirm)
   461             
   461 
   462     # base actions ############################################################
   462     # base actions ############################################################
   463 
   463 
   464     def checkpoint(self):
   464     def checkpoint(self):
   465         """checkpoint action"""
   465         """checkpoint action"""
   466         if self.confirm('commit now ?', shell=False):
   466         if self.confirm('commit now ?', shell=False):
   467             self.commit()
   467             self.commit()
   468 
   468 
   469     def cmd_add_cube(self, cube, update_database=True):
   469     def cmd_add_cube(self, cube, update_database=True):
   470         self.cmd_add_cubes( (cube,), update_database)
   470         self.cmd_add_cubes( (cube,), update_database)
   471     
   471 
   472     def cmd_add_cubes(self, cubes, update_database=True):
   472     def cmd_add_cubes(self, cubes, update_database=True):
   473         """update_database is telling if the database schema should be updated
   473         """update_database is telling if the database schema should be updated
   474         or if only the relevant eproperty should be inserted (for the case where
   474         or if only the relevant eproperty should be inserted (for the case where
   475         a cube has been extracted from an existing application, so the
   475         a cube has been extracted from an existing application, so the
   476         cube schema is already in there)
   476         cube schema is already in there)
   505                 if existingschema.has_rdef(fromtype, totype):
   505                 if existingschema.has_rdef(fromtype, totype):
   506                     continue
   506                     continue
   507                 # check we should actually add the relation definition
   507                 # check we should actually add the relation definition
   508                 if not (fromtype in new or totype in new or rschema in new):
   508                 if not (fromtype in new or totype in new or rschema in new):
   509                     continue
   509                     continue
   510                 self.cmd_add_relation_definition(str(fromtype), rschema.type, 
   510                 self.cmd_add_relation_definition(str(fromtype), rschema.type,
   511                                                  str(totype))
   511                                                  str(totype))
   512         # execute post-create files
   512         # execute post-create files
   513         for pack in reversed(newcubes):
   513         for pack in reversed(newcubes):
   514             self.exec_event_script('postcreate', self.config.cube_dir(pack))
   514             self.exec_event_script('postcreate', self.config.cube_dir(pack))
   515             self.commit()        
   515             self.commit()
   516                 
   516 
   517     def cmd_remove_cube(self, cube):
   517     def cmd_remove_cube(self, cube):
   518         removedcubes = super(ServerMigrationHelper, self).cmd_remove_cube(cube)
   518         removedcubes = super(ServerMigrationHelper, self).cmd_remove_cube(cube)
   519         if not removedcubes:
   519         if not removedcubes:
   520             return
   520             return
   521         fsschema = self.fs_schema
   521         fsschema = self.fs_schema
   530                 self.cmd_drop_relation_type(rschema.type)
   530                 self.cmd_drop_relation_type(rschema.type)
   531         for eschema in fsschema.entities():
   531         for eschema in fsschema.entities():
   532             if not eschema in removedcubes_schema and eschema in reposchema:
   532             if not eschema in removedcubes_schema and eschema in reposchema:
   533                 self.cmd_drop_entity_type(eschema.type)
   533                 self.cmd_drop_entity_type(eschema.type)
   534         for rschema in fsschema.relations():
   534         for rschema in fsschema.relations():
   535             if rschema in removedcubes_schema and rschema in reposchema: 
   535             if rschema in removedcubes_schema and rschema in reposchema:
   536                 # check if attributes/relations has been added to entities from 
   536                 # check if attributes/relations has been added to entities from
   537                 # other cubes
   537                 # other cubes
   538                 for fromtype, totype in rschema.iter_rdefs():
   538                 for fromtype, totype in rschema.iter_rdefs():
   539                     if not removedcubes_schema[rschema.type].has_rdef(fromtype, totype) and \
   539                     if not removedcubes_schema[rschema.type].has_rdef(fromtype, totype) and \
   540                            reposchema[rschema.type].has_rdef(fromtype, totype):
   540                            reposchema[rschema.type].has_rdef(fromtype, totype):
   541                         self.cmd_drop_relation_definition(
   541                         self.cmd_drop_relation_definition(
   544         for pack in reversed(removedcubes):
   544         for pack in reversed(removedcubes):
   545             self.exec_event_script('postremove', self.config.cube_dir(pack))
   545             self.exec_event_script('postremove', self.config.cube_dir(pack))
   546             self.rqlexec('DELETE CWProperty X WHERE X pkey %(pk)s',
   546             self.rqlexec('DELETE CWProperty X WHERE X pkey %(pk)s',
   547                          {'pk': u'system.version.'+pack}, ask_confirm=False)
   547                          {'pk': u'system.version.'+pack}, ask_confirm=False)
   548             self.commit()
   548             self.commit()
   549             
   549 
   550     # schema migration actions ################################################
   550     # schema migration actions ################################################
   551     
   551 
   552     def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True):
   552     def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True):
   553         """add a new attribute on the given entity type"""
   553         """add a new attribute on the given entity type"""
   554         if attrtype is None:
   554         if attrtype is None:
   555             rschema = self.fs_schema.rschema(attrname)
   555             rschema = self.fs_schema.rschema(attrname)
   556             attrtype = rschema.objects(etype)[0]
   556             attrtype = rschema.objects(etype)[0]
   557         self.cmd_add_relation_definition(etype, attrname, attrtype, commit=commit)
   557         self.cmd_add_relation_definition(etype, attrname, attrtype, commit=commit)
   558         
   558 
   559     def cmd_drop_attribute(self, etype, attrname, commit=True):
   559     def cmd_drop_attribute(self, etype, attrname, commit=True):
   560         """drop an existing attribute from the given entity type
   560         """drop an existing attribute from the given entity type
   561         
   561 
   562         `attrname` is a string giving the name of the attribute to drop
   562         `attrname` is a string giving the name of the attribute to drop
   563         """
   563         """
   564         rschema = self.repo.schema.rschema(attrname)
   564         rschema = self.repo.schema.rschema(attrname)
   565         attrtype = rschema.objects(etype)[0]
   565         attrtype = rschema.objects(etype)[0]
   566         self.cmd_drop_relation_definition(etype, attrname, attrtype, commit=commit)
   566         self.cmd_drop_relation_definition(etype, attrname, attrtype, commit=commit)
   567 
   567 
   568     def cmd_rename_attribute(self, etype, oldname, newname, commit=True):
   568     def cmd_rename_attribute(self, etype, oldname, newname, commit=True):
   569         """rename an existing attribute of the given entity type
   569         """rename an existing attribute of the given entity type
   570         
   570 
   571         `oldname` is a string giving the name of the existing attribute
   571         `oldname` is a string giving the name of the existing attribute
   572         `newname` is a string giving the name of the renamed attribute
   572         `newname` is a string giving the name of the renamed attribute
   573         """
   573         """
   574         eschema = self.fs_schema.eschema(etype)
   574         eschema = self.fs_schema.eschema(etype)
   575         attrtype = eschema.destination(newname)
   575         attrtype = eschema.destination(newname)
   581         card = eschema.rproperty(newname, 'cardinality')[0]
   581         card = eschema.rproperty(newname, 'cardinality')[0]
   582         if card == '1':
   582         if card == '1':
   583             rql += ', NOT X %s NULL' % oldname
   583             rql += ', NOT X %s NULL' % oldname
   584         self.rqlexec(rql, ask_confirm=self.verbosity>=2)
   584         self.rqlexec(rql, ask_confirm=self.verbosity>=2)
   585         self.cmd_drop_attribute(etype, oldname, commit=commit)
   585         self.cmd_drop_attribute(etype, oldname, commit=commit)
   586             
   586 
   587     def cmd_add_entity_type(self, etype, auto=True, commit=True):
   587     def cmd_add_entity_type(self, etype, auto=True, commit=True):
   588         """register a new entity type
   588         """register a new entity type
   589         
   589 
   590         in auto mode, automatically register entity's relation where the
   590         in auto mode, automatically register entity's relation where the
   591         targeted type is known
   591         targeted type is known
   592         """
   592         """
   593         applschema = self.repo.schema
   593         applschema = self.repo.schema
   594         if etype in applschema:
   594         if etype in applschema:
   622             self.commit()
   622             self.commit()
   623             added = []
   623             added = []
   624             for rschema in eschema.subject_relations():
   624             for rschema in eschema.subject_relations():
   625                 # attribute relation have already been processed and
   625                 # attribute relation have already been processed and
   626                 # 'owned_by'/'created_by' will be automatically added
   626                 # 'owned_by'/'created_by' will be automatically added
   627                 if rschema.final or rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'): 
   627                 if rschema.final or rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'):
   628                     continue
   628                     continue
   629                 rtypeadded = rschema.type in applschema
   629                 rtypeadded = rschema.type in applschema
   630                 for targetschema in rschema.objects(etype):
   630                 for targetschema in rschema.objects(etype):
   631                     # ignore relations where the targeted type is not in the
   631                     # ignore relations where the targeted type is not in the
   632                     # current application schema
   632                     # current application schema
   665                     # register relation definition
   665                     # register relation definition
   666                     self.rqlexecall(ss.rdef2rql(rschema, targettype, etype),
   666                     self.rqlexecall(ss.rdef2rql(rschema, targettype, etype),
   667                                     ask_confirm=confirm)
   667                                     ask_confirm=confirm)
   668         if commit:
   668         if commit:
   669             self.commit()
   669             self.commit()
   670                 
   670 
   671     def cmd_drop_entity_type(self, etype, commit=True):
   671     def cmd_drop_entity_type(self, etype, commit=True):
   672         """unregister an existing entity type
   672         """unregister an existing entity type
   673         
   673 
   674         This will trigger deletion of necessary relation types and definitions
   674         This will trigger deletion of necessary relation types and definitions
   675         """
   675         """
   676         # XXX what if we delete an entity type which is specialized by other types
   676         # XXX what if we delete an entity type which is specialized by other types
   677         # unregister the entity from CWEType
   677         # unregister the entity from CWEType
   678         self.rqlexec('DELETE CWEType X WHERE X name %(etype)s', {'etype': etype},
   678         self.rqlexec('DELETE CWEType X WHERE X name %(etype)s', {'etype': etype},
   680         if commit:
   680         if commit:
   681             self.commit()
   681             self.commit()
   682 
   682 
   683     def cmd_rename_entity_type(self, oldname, newname, commit=True):
   683     def cmd_rename_entity_type(self, oldname, newname, commit=True):
   684         """rename an existing entity type in the persistent schema
   684         """rename an existing entity type in the persistent schema
   685         
   685 
   686         `oldname` is a string giving the name of the existing entity type
   686         `oldname` is a string giving the name of the existing entity type
   687         `newname` is a string giving the name of the renamed entity type
   687         `newname` is a string giving the name of the renamed entity type
   688         """
   688         """
   689         self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(oldname)s',
   689         self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(oldname)s',
   690                      {'newname' : unicode(newname), 'oldname' : oldname})
   690                      {'newname' : unicode(newname), 'oldname' : oldname})
   691         if commit:
   691         if commit:
   692             self.commit()
   692             self.commit()
   693         
   693 
   694     def cmd_add_relation_type(self, rtype, addrdef=True, commit=True):
   694     def cmd_add_relation_type(self, rtype, addrdef=True, commit=True):
   695         """register a new relation type named `rtype`, as described in the
   695         """register a new relation type named `rtype`, as described in the
   696         schema description file.
   696         schema description file.
   697 
   697 
   698         `addrdef` is a boolean value; when True, it will also add all relations
   698         `addrdef` is a boolean value; when True, it will also add all relations
   699         of the type just added found in the schema definition file. Note that it
   699         of the type just added found in the schema definition file. Note that it
   700         implies an intermediate "commit" which commits the relation type
   700         implies an intermediate "commit" which commits the relation type
   701         creation (but not the relation definitions themselves, for which
   701         creation (but not the relation definitions themselves, for which
   702         committing depends on the `commit` argument value).
   702         committing depends on the `commit` argument value).
   703         
   703 
   704         """
   704         """
   705         rschema = self.fs_schema.rschema(rtype)
   705         rschema = self.fs_schema.rschema(rtype)
   706         # register the relation into CWRType and insert necessary relation
   706         # register the relation into CWRType and insert necessary relation
   707         # definitions
   707         # definitions
   708         self.rqlexecall(ss.rschema2rql(rschema, addrdef=False),
   708         self.rqlexecall(ss.rschema2rql(rschema, addrdef=False),
   714             self.commit()
   714             self.commit()
   715             self.rqlexecall(ss.rdef2rql(rschema),
   715             self.rqlexecall(ss.rdef2rql(rschema),
   716                             ask_confirm=self.verbosity>=2)
   716                             ask_confirm=self.verbosity>=2)
   717         if commit:
   717         if commit:
   718             self.commit()
   718             self.commit()
   719         
   719 
   720     def cmd_drop_relation_type(self, rtype, commit=True):
   720     def cmd_drop_relation_type(self, rtype, commit=True):
   721         """unregister an existing relation type"""
   721         """unregister an existing relation type"""
   722         # unregister the relation from CWRType
   722         # unregister the relation from CWRType
   723         self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype,
   723         self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype,
   724                      ask_confirm=self.verbosity>=2)
   724                      ask_confirm=self.verbosity>=2)
   725         if commit:
   725         if commit:
   726             self.commit()
   726             self.commit()
   727         
   727 
   728     def cmd_rename_relation(self, oldname, newname, commit=True):
   728     def cmd_rename_relation(self, oldname, newname, commit=True):
   729         """rename an existing relation
   729         """rename an existing relation
   730         
   730 
   731         `oldname` is a string giving the name of the existing relation
   731         `oldname` is a string giving the name of the existing relation
   732         `newname` is a string giving the name of the renamed relation
   732         `newname` is a string giving the name of the renamed relation
   733         """
   733         """
   734         self.cmd_add_relation_type(newname, commit=True)
   734         self.cmd_add_relation_type(newname, commit=True)
   735         self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname),
   735         self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname),
   745             self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
   745             self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
   746         self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype),
   746         self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype),
   747                         ask_confirm=self.verbosity>=2)
   747                         ask_confirm=self.verbosity>=2)
   748         if commit:
   748         if commit:
   749             self.commit()
   749             self.commit()
   750         
   750 
   751     def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
   751     def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
   752         """unregister an existing relation definition"""
   752         """unregister an existing relation definition"""
   753         rschema = self.repo.schema.rschema(rtype)
   753         rschema = self.repo.schema.rschema(rtype)
   754         # unregister the definition from CWAttribute or CWRelation
   754         # unregister the definition from CWAttribute or CWRelation
   755         if rschema.is_final():
   755         if rschema.is_final():
   760                'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"')
   760                'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"')
   761         self.rqlexec(rql % (etype, subjtype, rtype, objtype),
   761         self.rqlexec(rql % (etype, subjtype, rtype, objtype),
   762                      ask_confirm=self.verbosity>=2)
   762                      ask_confirm=self.verbosity>=2)
   763         if commit:
   763         if commit:
   764             self.commit()
   764             self.commit()
   765         
   765 
   766     def cmd_sync_schema_props_perms(self, ertype=None, syncperms=True,
   766     def cmd_sync_schema_props_perms(self, ertype=None, syncperms=True,
   767                                     syncprops=True, syncrdefs=True, commit=True):
   767                                     syncprops=True, syncrdefs=True, commit=True):
   768         """synchronize the persistent schema against the current definition
   768         """synchronize the persistent schema against the current definition
   769         schema.
   769         schema.
   770         
   770 
   771         It will synch common stuff between the definition schema and the
   771         It will synch common stuff between the definition schema and the
   772         actual persistent schema, it won't add/remove any entity or relation.
   772         actual persistent schema, it won't add/remove any entity or relation.
   773         """
   773         """
   774         assert syncperms or syncprops, 'nothing to do'
   774         assert syncperms or syncprops, 'nothing to do'
   775         if ertype is not None:
   775         if ertype is not None:
   792                     self._synchronize_eschema(etype, syncperms=syncperms)
   792                     self._synchronize_eschema(etype, syncperms=syncperms)
   793                 else:
   793                 else:
   794                     self._synchronize_permissions(etype)
   794                     self._synchronize_permissions(etype)
   795         if commit:
   795         if commit:
   796             self.commit()
   796             self.commit()
   797                 
   797 
   798     def cmd_change_relation_props(self, subjtype, rtype, objtype,
   798     def cmd_change_relation_props(self, subjtype, rtype, objtype,
   799                                   commit=True, **kwargs):
   799                                   commit=True, **kwargs):
   800         """change some properties of a relation definition
   800         """change some properties of a relation definition
   801 
   801 
   802         you usually want to use sync_schema_props_perms instead.
   802         you usually want to use sync_schema_props_perms instead.
   822 
   822 
   823     def cmd_set_size_constraint(self, etype, rtype, size, commit=True):
   823     def cmd_set_size_constraint(self, etype, rtype, size, commit=True):
   824         """set change size constraint of a string attribute
   824         """set change size constraint of a string attribute
   825 
   825 
   826         if size is None any size constraint will be removed.
   826         if size is None any size constraint will be removed.
   827         
   827 
   828         you usually want to use sync_schema_props_perms instead.        
   828         you usually want to use sync_schema_props_perms instead.
   829         """
   829         """
   830         oldvalue = None
   830         oldvalue = None
   831         for constr in self.repo.schema.eschema(etype).constraints(rtype):
   831         for constr in self.repo.schema.eschema(etype).constraints(rtype):
   832             if isinstance(constr, SizeConstraint):
   832             if isinstance(constr, SizeConstraint):
   833                 oldvalue = constr.max
   833                 oldvalue = constr.max
   859             self.commit()
   859             self.commit()
   860 
   860 
   861     @obsolete('use sync_schema_props_perms(ertype, syncprops=False)')
   861     @obsolete('use sync_schema_props_perms(ertype, syncprops=False)')
   862     def cmd_synchronize_permissions(self, ertype, commit=True):
   862     def cmd_synchronize_permissions(self, ertype, commit=True):
   863         self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit)
   863         self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit)
   864     
   864 
   865     # Workflows handling ######################################################
   865     # Workflows handling ######################################################
   866     
   866 
   867     def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs):
   867     def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs):
   868         """method to ease workflow definition: add a state for one or more
   868         """method to ease workflow definition: add a state for one or more
   869         entity type(s)
   869         entity type(s)
   870         """
   870         """
   871         stateeid = self.cmd_add_entity('State', name=name, **kwargs)
   871         stateeid = self.cmd_add_entity('State', name=name, **kwargs)
   879                 self.rqlexec('SET ET initial_state S WHERE ET name %(et)s, S eid %(x)s',
   879                 self.rqlexec('SET ET initial_state S WHERE ET name %(et)s, S eid %(x)s',
   880                              {'x': stateeid, 'et': etype}, 'x', ask_confirm=False)
   880                              {'x': stateeid, 'et': etype}, 'x', ask_confirm=False)
   881         if commit:
   881         if commit:
   882             self.commit()
   882             self.commit()
   883         return stateeid
   883         return stateeid
   884     
   884 
   885     def cmd_add_transition(self, name, transitionof, fromstates, tostate,
   885     def cmd_add_transition(self, name, transitionof, fromstates, tostate,
   886                            requiredgroups=(), conditions=(), commit=False, **kwargs):
   886                            requiredgroups=(), conditions=(), commit=False, **kwargs):
   887         """method to ease workflow definition: add a transition for one or more
   887         """method to ease workflow definition: add a transition for one or more
   888         entity type(s), from one or more state and to a single state
   888         entity type(s), from one or more state and to a single state
   889         """
   889         """
   936         self.session.set_pool() # ensure pool is set
   936         self.session.set_pool() # ensure pool is set
   937         entity = self.session.eid_rset(eid).get_entity(0, 0)
   937         entity = self.session.eid_rset(eid).get_entity(0, 0)
   938         entity.change_state(entity.wf_state(statename).eid)
   938         entity.change_state(entity.wf_state(statename).eid)
   939         if commit:
   939         if commit:
   940             self.commit()
   940             self.commit()
   941         
   941 
   942     # CWProperty handling ######################################################
   942     # CWProperty handling ######################################################
   943 
   943 
   944     def cmd_property_value(self, pkey):
   944     def cmd_property_value(self, pkey):
   945         rql = 'Any V WHERE X is CWProperty, X pkey %(k)s, X value V'
   945         rql = 'Any V WHERE X is CWProperty, X pkey %(k)s, X value V'
   946         rset = self.rqlexec(rql, {'k': pkey}, ask_confirm=False)
   946         rset = self.rqlexec(rql, {'k': pkey}, ask_confirm=False)
   956         else:
   956         else:
   957             self.rqlexec('SET X value %(v)s WHERE X pkey %(k)s',
   957             self.rqlexec('SET X value %(v)s WHERE X pkey %(k)s',
   958                          {'k': pkey, 'v': value}, ask_confirm=False)
   958                          {'k': pkey, 'v': value}, ask_confirm=False)
   959 
   959 
   960     # other data migration commands ###########################################
   960     # other data migration commands ###########################################
   961         
   961 
   962     def cmd_add_entity(self, etype, *args, **kwargs):
   962     def cmd_add_entity(self, etype, *args, **kwargs):
   963         """add a new entity of the given type"""
   963         """add a new entity of the given type"""
   964         rql = 'INSERT %s X' % etype
   964         rql = 'INSERT %s X' % etype
   965         relations = []
   965         relations = []
   966         restrictions = []
   966         restrictions = []
   976             rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
   976             rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
   977         eid = self.rqlexec(rql, kwargs, ask_confirm=self.verbosity>=2).rows[0][0]
   977         eid = self.rqlexec(rql, kwargs, ask_confirm=self.verbosity>=2).rows[0][0]
   978         if commit:
   978         if commit:
   979             self.commit()
   979             self.commit()
   980         return eid
   980         return eid
   981     
   981 
   982     def sqlexec(self, sql, args=None, ask_confirm=True):
   982     def sqlexec(self, sql, args=None, ask_confirm=True):
   983         """execute the given sql if confirmed
   983         """execute the given sql if confirmed
   984         
   984 
   985         should only be used for low level stuff undoable with existing higher
   985         should only be used for low level stuff undoable with existing higher
   986         level actions
   986         level actions
   987         """
   987         """
   988         if not ask_confirm or self.confirm('execute sql: %s ?' % sql):
   988         if not ask_confirm or self.confirm('execute sql: %s ?' % sql):
   989             self.session.set_pool() # ensure pool is set
   989             self.session.set_pool() # ensure pool is set
   997             try:
   997             try:
   998                 return cu.fetchall()
   998                 return cu.fetchall()
   999             except:
   999             except:
  1000                 # no result to fetch
  1000                 # no result to fetch
  1001                 return
  1001                 return
  1002     
  1002 
  1003     def rqlexec(self, rql, kwargs=None, cachekey=None, ask_confirm=True):
  1003     def rqlexec(self, rql, kwargs=None, cachekey=None, ask_confirm=True):
  1004         """rql action"""
  1004         """rql action"""
  1005         if not isinstance(rql, (tuple, list)):
  1005         if not isinstance(rql, (tuple, list)):
  1006             rql = ( (rql, kwargs), )
  1006             rql = ( (rql, kwargs), )
  1007         res = None
  1007         res = None
  1024     def cmd_deactivate_verification_hooks(self):
  1024     def cmd_deactivate_verification_hooks(self):
  1025         self.repo.hm.deactivate_verification_hooks()
  1025         self.repo.hm.deactivate_verification_hooks()
  1026 
  1026 
  1027     def cmd_reactivate_verification_hooks(self):
  1027     def cmd_reactivate_verification_hooks(self):
  1028         self.repo.hm.reactivate_verification_hooks()
  1028         self.repo.hm.reactivate_verification_hooks()
  1029         
  1029 
  1030     # broken db commands ######################################################
  1030     # broken db commands ######################################################
  1031 
  1031 
  1032     def cmd_change_attribute_type(self, etype, attr, newtype, commit=True):
  1032     def cmd_change_attribute_type(self, etype, attr, newtype, commit=True):
  1033         """low level method to change the type of an entity attribute. This is
  1033         """low level method to change the type of an entity attribute. This is
  1034         a quick hack which has some drawback:
  1034         a quick hack which has some drawback:
  1047         sqltype = dbhelper.TYPE_MAPPING[newtype]
  1047         sqltype = dbhelper.TYPE_MAPPING[newtype]
  1048         sql = 'ALTER TABLE %s ALTER COLUMN %s TYPE %s' % (etype, attr, sqltype)
  1048         sql = 'ALTER TABLE %s ALTER COLUMN %s TYPE %s' % (etype, attr, sqltype)
  1049         self.sqlexec(sql, ask_confirm=False)
  1049         self.sqlexec(sql, ask_confirm=False)
  1050         if commit:
  1050         if commit:
  1051             self.commit()
  1051             self.commit()
  1052         
  1052 
  1053     def cmd_add_entity_type_table(self, etype, commit=True):
  1053     def cmd_add_entity_type_table(self, etype, commit=True):
  1054         """low level method to create the sql table for an existing entity.
  1054         """low level method to create the sql table for an existing entity.
  1055         This may be useful on accidental desync between the repository schema
  1055         This may be useful on accidental desync between the repository schema
  1056         and a sql database
  1056         and a sql database
  1057         """
  1057         """
  1061         for sql in tablesql.split(';'):
  1061         for sql in tablesql.split(';'):
  1062             if sql.strip():
  1062             if sql.strip():
  1063                 self.sqlexec(sql)
  1063                 self.sqlexec(sql)
  1064         if commit:
  1064         if commit:
  1065             self.commit()
  1065             self.commit()
  1066             
  1066 
  1067     def cmd_add_relation_type_table(self, rtype, commit=True):
  1067     def cmd_add_relation_type_table(self, rtype, commit=True):
  1068         """low level method to create the sql table for an existing relation.
  1068         """low level method to create the sql table for an existing relation.
  1069         This may be useful on accidental desync between the repository schema
  1069         This may be useful on accidental desync between the repository schema
  1070         and a sql database
  1070         and a sql database
  1071         """
  1071         """
  1074         for sql in tablesql.split(';'):
  1074         for sql in tablesql.split(';'):
  1075             if sql.strip():
  1075             if sql.strip():
  1076                 self.sqlexec(sql)
  1076                 self.sqlexec(sql)
  1077         if commit:
  1077         if commit:
  1078             self.commit()
  1078             self.commit()
  1079             
  1079 
  1080 
  1080 
  1081 class ForRqlIterator:
  1081 class ForRqlIterator:
  1082     """specific rql iterator to make the loop skipable"""
  1082     """specific rql iterator to make the loop skipable"""
  1083     def __init__(self, helper, rql, kwargs, ask_confirm):
  1083     def __init__(self, helper, rql, kwargs, ask_confirm):
  1084         self._h = helper
  1084         self._h = helper
  1085         self.rql = rql
  1085         self.rql = rql
  1086         self.kwargs = kwargs
  1086         self.kwargs = kwargs
  1087         self.ask_confirm = ask_confirm
  1087         self.ask_confirm = ask_confirm
  1088         self._rsetit = None
  1088         self._rsetit = None
  1089         
  1089 
  1090     def __iter__(self):
  1090     def __iter__(self):
  1091         return self
  1091         return self
  1092     
  1092 
  1093     def next(self):
  1093     def next(self):
  1094         if self._rsetit is not None:
  1094         if self._rsetit is not None:
  1095             return self._rsetit.next()
  1095             return self._rsetit.next()
  1096         rql, kwargs = self.rql, self.kwargs
  1096         rql, kwargs = self.rql, self.kwargs
  1097         if kwargs:
  1097         if kwargs: