server/migractions.py
branchtls-sprint
changeset 1399 3f408c7a164e
parent 1398 5fe84a5f7035
child 1409 f4dee84a618f
equal deleted inserted replaced
1398:5fe84a5f7035 1399:3f408c7a164e
    19 import sys
    19 import sys
    20 import os
    20 import os
    21 from os.path import join, exists
    21 from os.path import join, exists
    22 from datetime import datetime
    22 from datetime import datetime
    23 
    23 
       
    24 from logilab.common.deprecation import deprecated_function, obsolete
    24 from logilab.common.decorators import cached
    25 from logilab.common.decorators import cached
    25 from logilab.common.adbh import get_adv_func_helper
    26 from logilab.common.adbh import get_adv_func_helper
    26 
    27 
    27 from yams.constraints import SizeConstraint
    28 from yams.constraints import SizeConstraint
    28 from yams.schema2sql import eschema2sql, rschema2sql
    29 from yams.schema2sql import eschema2sql, rschema2sql
    29 
    30 
    30 from cubicweb import AuthenticationError, ETYPE_NAME_MAP
    31 from cubicweb import AuthenticationError, ETYPE_NAME_MAP
       
    32 from cubicweb.schema import CubicWebRelationSchema
    31 from cubicweb.dbapi import get_repository, repo_connect
    33 from cubicweb.dbapi import get_repository, repo_connect
    32 from cubicweb.common.migration import MigrationHelper, yes
    34 from cubicweb.common.migration import MigrationHelper, yes
    33 
    35 
    34 try:
    36 try:
    35     from cubicweb.server import schemaserial as ss
    37     from cubicweb.server import schemaserial as ss
   221         context.update({'checkpoint': self.checkpoint,
   223         context.update({'checkpoint': self.checkpoint,
   222                         'sql': self.sqlexec,
   224                         'sql': self.sqlexec,
   223                         'rql': self.rqlexec,
   225                         'rql': self.rqlexec,
   224                         'rqliter': self.rqliter,
   226                         'rqliter': self.rqliter,
   225                         'schema': self.repo.schema,
   227                         'schema': self.repo.schema,
   226                         # XXX deprecate
       
   227                         'newschema': self.fs_schema,
       
   228                         'fsschema': self.fs_schema,
   228                         'fsschema': self.fs_schema,
   229                         'cnx': self.cnx,
       
   230                         'session' : self.session,
   229                         'session' : self.session,
   231                         'repo' : self.repo,
   230                         'repo' : self.repo,
       
   231                         'synchronize_schema': deprecated_function(self.sync_schema_props_perms),
       
   232                         'synchronize_eschema': deprecated_function(self.sync_schema_props_perms),
       
   233                         'synchronize_rschema': deprecated_function(self.sync_schema_props_perms),
   232                         })
   234                         })
   233         return context
   235         return context
   234 
   236 
   235     @cached
   237     @cached
   236     def group_mapping(self):
   238     def group_mapping(self):
   262                 if self.config.free_wheel:
   264                 if self.config.free_wheel:
   263                     self.repo.hm.register_hook(setowner_after_add_entity,
   265                     self.repo.hm.register_hook(setowner_after_add_entity,
   264                                                'after_add_entity', '')
   266                                                'after_add_entity', '')
   265                     self.reactivate_verification_hooks()
   267                     self.reactivate_verification_hooks()
   266     
   268     
   267     # base actions ############################################################
   269     # schema synchronization internals ########################################
   268 
   270     
   269     def checkpoint(self):
   271     def _synchronize_permissions(self, ertype):
   270         """checkpoint action"""
       
   271         if self.confirm('commit now ?', shell=False):
       
   272             self.commit()
       
   273 
       
   274     def cmd_add_cube(self, cube, update_database=True):
       
   275         self.cmd_add_cubes( (cube,), update_database)
       
   276     
       
   277     def cmd_add_cubes(self, cubes, update_database=True):
       
   278         """update_database is telling if the database schema should be updated
       
   279         or if only the relevant eproperty should be inserted (for the case where
       
   280         a cube has been extracted from an existing application, so the
       
   281         cube schema is already in there)
       
   282         """
       
   283         newcubes = super(ServerMigrationHelper, self).cmd_add_cubes(cubes)
       
   284         if not newcubes:
       
   285             return
       
   286         for pack in newcubes:
       
   287             self.cmd_set_property('system.version.'+pack,
       
   288                                   self.config.cube_version(pack))
       
   289         if not update_database:
       
   290             self.commit()
       
   291             return
       
   292         newcubes_schema = self.config.load_schema(construction_mode='non-strict')
       
   293         new = set()
       
   294         # execute pre-create files
       
   295         for pack in reversed(newcubes):
       
   296             self.exec_event_script('precreate', self.config.cube_dir(pack))
       
   297         # add new entity and relation types
       
   298         for rschema in newcubes_schema.relations():
       
   299             if not rschema in self.repo.schema:
       
   300                 self.cmd_add_relation_type(rschema.type)
       
   301                 new.add(rschema.type)
       
   302         for eschema in newcubes_schema.entities():
       
   303             if not eschema in self.repo.schema:
       
   304                 self.cmd_add_entity_type(eschema.type)
       
   305                 new.add(eschema.type)
       
   306         # check if attributes has been added to existing entities
       
   307         for rschema in newcubes_schema.relations():
       
   308             existingschema = self.repo.schema.rschema(rschema.type)
       
   309             for (fromtype, totype) in rschema.iter_rdefs():
       
   310                 if existingschema.has_rdef(fromtype, totype):
       
   311                     continue
       
   312                 # check we should actually add the relation definition
       
   313                 if not (fromtype in new or totype in new or rschema in new):
       
   314                     continue
       
   315                 self.cmd_add_relation_definition(str(fromtype), rschema.type, 
       
   316                                                  str(totype))
       
   317         # execute post-create files
       
   318         for pack in reversed(newcubes):
       
   319             self.exec_event_script('postcreate', self.config.cube_dir(pack))
       
   320             self.commit()        
       
   321                 
       
   322     def cmd_remove_cube(self, cube):
       
   323         removedcubes = super(ServerMigrationHelper, self).cmd_remove_cube(cube)
       
   324         if not removedcubes:
       
   325             return
       
   326         fsschema = self.fs_schema
       
   327         removedcubes_schema = self.config.load_schema(construction_mode='non-strict')
       
   328         reposchema = self.repo.schema
       
   329         # execute pre-remove files
       
   330         for pack in reversed(removedcubes):
       
   331             self.exec_event_script('preremove', self.config.cube_dir(pack))
       
   332         # remove cubes'entity and relation types
       
   333         for rschema in fsschema.relations():
       
   334             if not rschema in removedcubes_schema and rschema in reposchema:
       
   335                 self.cmd_drop_relation_type(rschema.type)
       
   336         for eschema in fsschema.entities():
       
   337             if not eschema in removedcubes_schema and eschema in reposchema:
       
   338                 self.cmd_drop_entity_type(eschema.type)
       
   339         for rschema in fsschema.relations():
       
   340             if rschema in removedcubes_schema and rschema in reposchema: 
       
   341                 # check if attributes/relations has been added to entities from 
       
   342                 # other cubes
       
   343                 for fromtype, totype in rschema.iter_rdefs():
       
   344                     if not removedcubes_schema[rschema.type].has_rdef(fromtype, totype) and \
       
   345                            reposchema[rschema.type].has_rdef(fromtype, totype):
       
   346                         self.cmd_drop_relation_definition(
       
   347                             str(fromtype), rschema.type, str(totype))
       
   348         # execute post-remove files
       
   349         for pack in reversed(removedcubes):
       
   350             self.exec_event_script('postremove', self.config.cube_dir(pack))
       
   351             self.rqlexec('DELETE CWProperty X WHERE X pkey %(pk)s',
       
   352                          {'pk': u'system.version.'+pack}, ask_confirm=False)
       
   353             self.commit()
       
   354             
       
   355     # schema migration actions ################################################
       
   356     
       
   357     def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True):
       
   358         """add a new attribute on the given entity type"""
       
   359         if attrtype is None:
       
   360             rschema = self.fs_schema.rschema(attrname)
       
   361             attrtype = rschema.objects(etype)[0]
       
   362         self.cmd_add_relation_definition(etype, attrname, attrtype, commit=commit)
       
   363         
       
   364     def cmd_drop_attribute(self, etype, attrname, commit=True):
       
   365         """drop an existing attribute from the given entity type
       
   366         
       
   367         `attrname` is a string giving the name of the attribute to drop
       
   368         """
       
   369         rschema = self.repo.schema.rschema(attrname)
       
   370         attrtype = rschema.objects(etype)[0]
       
   371         self.cmd_drop_relation_definition(etype, attrname, attrtype, commit=commit)
       
   372 
       
   373     def cmd_rename_attribute(self, etype, oldname, newname, commit=True):
       
   374         """rename an existing attribute of the given entity type
       
   375         
       
   376         `oldname` is a string giving the name of the existing attribute
       
   377         `newname` is a string giving the name of the renamed attribute
       
   378         """
       
   379         eschema = self.fs_schema.eschema(etype)
       
   380         attrtype = eschema.destination(newname)
       
   381         # have to commit this first step anyway to get the definition
       
   382         # actually in the schema
       
   383         self.cmd_add_attribute(etype, newname, attrtype, commit=True)
       
   384         # skipp NULL values if the attribute is required
       
   385         rql = 'SET X %s VAL WHERE X is %s, X %s VAL' % (newname, etype, oldname)
       
   386         card = eschema.rproperty(newname, 'cardinality')[0]
       
   387         if card == '1':
       
   388             rql += ', NOT X %s NULL' % oldname
       
   389         self.rqlexec(rql, ask_confirm=self.verbosity>=2)
       
   390         self.cmd_drop_attribute(etype, oldname, commit=commit)
       
   391             
       
   392     def cmd_add_entity_type(self, etype, auto=True, commit=True):
       
   393         """register a new entity type
       
   394         
       
   395         in auto mode, automatically register entity's relation where the
       
   396         targeted type is known
       
   397         """
       
   398         applschema = self.repo.schema
       
   399         if etype in applschema:
       
   400             eschema = applschema[etype]
       
   401             if eschema.is_final():
       
   402                 applschema.del_entity_type(etype)
       
   403         else:
       
   404             eschema = self.fs_schema.eschema(etype)
       
   405         confirm = self.verbosity >= 2
       
   406         # register the entity into CWEType
       
   407         self.rqlexecall(ss.eschema2rql(eschema), ask_confirm=confirm)
       
   408         # add specializes relation if needed
       
   409         self.rqlexecall(ss.eschemaspecialize2rql(eschema), ask_confirm=confirm)
       
   410         # register groups / permissions for the entity
       
   411         self.rqlexecall(ss.erperms2rql(eschema, self.group_mapping()),
       
   412                         ask_confirm=confirm)
       
   413         # register entity's attributes
       
   414         for rschema, attrschema in eschema.attribute_definitions():
       
   415             # ignore those meta relations, they will be automatically added
       
   416             if rschema.type in ('eid', 'creation_date', 'modification_date'):
       
   417                 continue
       
   418             if not rschema.type in applschema:
       
   419                 # need to add the relation type and to commit to get it
       
   420                 # actually in the schema
       
   421                 self.cmd_add_relation_type(rschema.type, False, commit=True)
       
   422             # register relation definition
       
   423             self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type),
       
   424                             ask_confirm=confirm)
       
   425         if auto:
       
   426             # we have commit here to get relation types actually in the schema
       
   427             self.commit()
       
   428             added = []
       
   429             for rschema in eschema.subject_relations():
       
   430                 # attribute relation have already been processed and
       
   431                 # 'owned_by'/'created_by' will be automatically added
       
   432                 if rschema.final or rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'): 
       
   433                     continue
       
   434                 rtypeadded = rschema.type in applschema
       
   435                 for targetschema in rschema.objects(etype):
       
   436                     # ignore relations where the targeted type is not in the
       
   437                     # current application schema
       
   438                     targettype = targetschema.type
       
   439                     if not targettype in applschema and targettype != etype:
       
   440                         continue
       
   441                     if not rtypeadded:
       
   442                         # need to add the relation type and to commit to get it
       
   443                         # actually in the schema
       
   444                         added.append(rschema.type)
       
   445                         self.cmd_add_relation_type(rschema.type, False, commit=True)
       
   446                         rtypeadded = True
       
   447                     # register relation definition
       
   448                     # remember this two avoid adding twice non symetric relation
       
   449                     # such as "Emailthread forked_from Emailthread"
       
   450                     added.append((etype, rschema.type, targettype))
       
   451                     self.rqlexecall(ss.rdef2rql(rschema, etype, targettype),
       
   452                                     ask_confirm=confirm)
       
   453             for rschema in eschema.object_relations():
       
   454                 rtypeadded = rschema.type in applschema or rschema.type in added
       
   455                 for targetschema in rschema.subjects(etype):
       
   456                     # ignore relations where the targeted type is not in the
       
   457                     # current application schema
       
   458                     targettype = targetschema.type
       
   459                     # don't check targettype != etype since in this case the
       
   460                     # relation has already been added as a subject relation
       
   461                     if not targettype in applschema:
       
   462                         continue
       
   463                     if not rtypeadded:
       
   464                         # need to add the relation type and to commit to get it
       
   465                         # actually in the schema
       
   466                         self.cmd_add_relation_type(rschema.type, False, commit=True)
       
   467                         rtypeadded = True
       
   468                     elif (targettype, rschema.type, etype) in added:
       
   469                         continue
       
   470                     # register relation definition
       
   471                     self.rqlexecall(ss.rdef2rql(rschema, targettype, etype),
       
   472                                     ask_confirm=confirm)
       
   473         if commit:
       
   474             self.commit()
       
   475                 
       
   476     def cmd_drop_entity_type(self, etype, commit=True):
       
   477         """unregister an existing entity type
       
   478         
       
   479         This will trigger deletion of necessary relation types and definitions
       
   480         """
       
   481         # XXX what if we delete an entity type which is specialized by other types
       
   482         # unregister the entity from CWEType
       
   483         self.rqlexec('DELETE CWEType X WHERE X name %(etype)s', {'etype': etype},
       
   484                      ask_confirm=self.verbosity>=2)
       
   485         if commit:
       
   486             self.commit()
       
   487 
       
   488     def cmd_rename_entity_type(self, oldname, newname, commit=True):
       
   489         """rename an existing entity type in the persistent schema
       
   490         
       
   491         `oldname` is a string giving the name of the existing entity type
       
   492         `newname` is a string giving the name of the renamed entity type
       
   493         """
       
   494         self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(oldname)s',
       
   495                      {'newname' : unicode(newname), 'oldname' : oldname})
       
   496         if commit:
       
   497             self.commit()
       
   498         
       
   499     def cmd_add_relation_type(self, rtype, addrdef=True, commit=True):
       
   500         """register a new relation type named `rtype`, as described in the
       
   501         schema description file.
       
   502 
       
   503         `addrdef` is a boolean value; when True, it will also add all relations
       
   504         of the type just added found in the schema definition file. Note that it
       
   505         implies an intermediate "commit" which commits the relation type
       
   506         creation (but not the relation definitions themselves, for which
       
   507         committing depends on the `commit` argument value).
       
   508         
       
   509         """
       
   510         rschema = self.fs_schema.rschema(rtype)
       
   511         # register the relation into CWRType and insert necessary relation
       
   512         # definitions
       
   513         self.rqlexecall(ss.rschema2rql(rschema, addrdef=False),
       
   514                         ask_confirm=self.verbosity>=2)
       
   515         # register groups / permissions for the relation
       
   516         self.rqlexecall(ss.erperms2rql(rschema, self.group_mapping()),
       
   517                         ask_confirm=self.verbosity>=2)
       
   518         if addrdef:
       
   519             self.commit()
       
   520             self.rqlexecall(ss.rdef2rql(rschema),
       
   521                             ask_confirm=self.verbosity>=2)
       
   522         if commit:
       
   523             self.commit()
       
   524         
       
   525     def cmd_drop_relation_type(self, rtype, commit=True):
       
   526         """unregister an existing relation type"""
       
   527         # unregister the relation from CWRType
       
   528         self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype,
       
   529                      ask_confirm=self.verbosity>=2)
       
   530         if commit:
       
   531             self.commit()
       
   532         
       
   533     def cmd_rename_relation(self, oldname, newname, commit=True):
       
   534         """rename an existing relation
       
   535         
       
   536         `oldname` is a string giving the name of the existing relation
       
   537         `newname` is a string giving the name of the renamed relation
       
   538         """
       
   539         self.cmd_add_relation_type(newname, commit=True)
       
   540         self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname),
       
   541                      ask_confirm=self.verbosity>=2)
       
   542         self.cmd_drop_relation_type(oldname, commit=commit)
       
   543 
       
   544     def cmd_add_relation_definition(self, subjtype, rtype, objtype, commit=True):
       
   545         """register a new relation definition, from its definition found in the
       
   546         schema definition file
       
   547         """
       
   548         rschema = self.fs_schema.rschema(rtype)
       
   549         if not rtype in self.repo.schema:
       
   550             self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
       
   551         self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype),
       
   552                         ask_confirm=self.verbosity>=2)
       
   553         if commit:
       
   554             self.commit()
       
   555         
       
   556     def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
       
   557         """unregister an existing relation definition"""
       
   558         rschema = self.repo.schema.rschema(rtype)
       
   559         # unregister the definition from CWAttribute or CWRelation
       
   560         if rschema.is_final():
       
   561             etype = 'CWAttribute'
       
   562         else:
       
   563             etype = 'CWRelation'
       
   564         rql = ('DELETE %s X WHERE X from_entity FE, FE name "%s",'
       
   565                'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"')
       
   566         self.rqlexec(rql % (etype, subjtype, rtype, objtype),
       
   567                      ask_confirm=self.verbosity>=2)
       
   568         if commit:
       
   569             self.commit()
       
   570         
       
   571     def cmd_synchronize_permissions(self, ertype, commit=True):
       
   572         """permission synchronization for an entity or relation type"""
   272         """permission synchronization for an entity or relation type"""
   573         if ertype in ('eid', 'has_text', 'identity'):
   273         if ertype in ('eid', 'has_text', 'identity'):
   574             return
   274             return
   575         newrschema = self.fs_schema[ertype]
   275         newrschema = self.fs_schema[ertype]
   576         teid = self.repo.schema[ertype].eid
   276         teid = self.repo.schema[ertype].eid
   630                                  'X expression %%(expr)s, X mainvars %%(vars)s, T %s X '
   330                                  'X expression %%(expr)s, X mainvars %%(vars)s, T %s X '
   631                                  'WHERE T eid %%(x)s' % perm,
   331                                  'WHERE T eid %%(x)s' % perm,
   632                                  {'expr': expr, 'exprtype': exprtype,
   332                                  {'expr': expr, 'exprtype': exprtype,
   633                                   'vars': expression.mainvars, 'x': teid}, 'x',
   333                                   'vars': expression.mainvars, 'x': teid}, 'x',
   634                                  ask_confirm=False)
   334                                  ask_confirm=False)
   635         if commit:
   335         
   636             self.commit()
   336     def _synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True):
   637         
       
   638     def cmd_synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True,
       
   639                                 commit=True):
       
   640         """synchronize properties of the persistent relation schema against its
   337         """synchronize properties of the persistent relation schema against its
   641         current definition:
   338         current definition:
   642         
   339         
   643         * description
   340         * description
   644         * symetric, meta
   341         * symetric, meta
   658         reporschema = self.repo.schema.rschema(rtype)
   355         reporschema = self.repo.schema.rschema(rtype)
   659         if syncrdefs:
   356         if syncrdefs:
   660             for subj, obj in rschema.iter_rdefs():
   357             for subj, obj in rschema.iter_rdefs():
   661                 if not reporschema.has_rdef(subj, obj):
   358                 if not reporschema.has_rdef(subj, obj):
   662                     continue
   359                     continue
   663                 self.cmd_synchronize_rdef_schema(subj, rschema, obj,
   360                 self._synchronize_rdef_schema(subj, rschema, obj)
   664                                                  commit=False)
       
   665         if syncperms:
   361         if syncperms:
   666             self.cmd_synchronize_permissions(rtype, commit=False)
   362             self._synchronize_permissions(rtype)
   667         if commit:
       
   668             self.commit()
       
   669                 
   363                 
   670     def cmd_synchronize_eschema(self, etype, syncperms=True, commit=True):
   364     def _synchronize_eschema(self, etype, syncperms=True):
   671         """synchronize properties of the persistent entity schema against
   365         """synchronize properties of the persistent entity schema against
   672         its current definition:
   366         its current definition:
   673         
   367         
   674         * description
   368         * description
   675         * internationalizable, fulltextindexed, indexed, meta
   369         * internationalizable, fulltextindexed, indexed, meta
   703                 subjtypes, objtypes = [etype], targettypes
   397                 subjtypes, objtypes = [etype], targettypes
   704             else: # x == 'object'
   398             else: # x == 'object'
   705                 if not rschema in repoeschema.object_relations():
   399                 if not rschema in repoeschema.object_relations():
   706                     continue
   400                     continue
   707                 subjtypes, objtypes = targettypes, [etype]
   401                 subjtypes, objtypes = targettypes, [etype]
   708             self.cmd_synchronize_rschema(rschema, syncperms=syncperms,
   402             self._synchronize_rschema(rschema, syncperms=syncperms,
   709                                          syncrdefs=False, commit=False)
   403                                       syncrdefs=False)
   710             reporschema = self.repo.schema.rschema(rschema)
   404             reporschema = self.repo.schema.rschema(rschema)
   711             for subj in subjtypes:
   405             for subj in subjtypes:
   712                 for obj in objtypes:
   406                 for obj in objtypes:
   713                     if not reporschema.has_rdef(subj, obj):
   407                     if not reporschema.has_rdef(subj, obj):
   714                         continue
   408                         continue
   715                     self.cmd_synchronize_rdef_schema(subj, rschema, obj,
   409                     self._synchronize_rdef_schema(subj, rschema, obj,
   716                                                      commit=False)
   410                                                      commit=False)
   717         if syncperms:
   411         if syncperms:
   718             self.cmd_synchronize_permissions(etype, commit=False)
   412             self._synchronize_permissions(etype)
   719         if commit:
   413 
   720             self.commit()
   414     def _synchronize_rdef_schema(self, subjtype, rtype, objtype):
   721 
       
   722     def cmd_synchronize_rdef_schema(self, subjtype, rtype, objtype,
       
   723                                     commit=True):
       
   724         """synchronize properties of the persistent relation definition schema
   415         """synchronize properties of the persistent relation definition schema
   725         against its current definition:
   416         against its current definition:
   726         * order and other properties
   417         * order and other properties
   727         * constraints
   418         * constraints
   728         """
   419         """
   766         # 2. add new constraints
   457         # 2. add new constraints
   767         for newcstr in newconstraints:
   458         for newcstr in newconstraints:
   768             self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
   459             self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
   769                                               newcstr),
   460                                               newcstr),
   770                             ask_confirm=confirm)
   461                             ask_confirm=confirm)
   771         if commit:
   462             
   772             self.commit()
   463     # base actions ############################################################
   773         
   464 
   774     def cmd_synchronize_schema(self, syncperms=True, commit=True):
   465     def checkpoint(self):
       
   466         """checkpoint action"""
       
   467         if self.confirm('commit now ?', shell=False):
       
   468             self.commit()
       
   469 
       
   470     def cmd_add_cube(self, cube, update_database=True):
       
   471         self.cmd_add_cubes( (cube,), update_database)
       
   472     
       
   473     def cmd_add_cubes(self, cubes, update_database=True):
       
   474         """update_database is telling if the database schema should be updated
       
   475         or if only the relevant eproperty should be inserted (for the case where
       
   476         a cube has been extracted from an existing application, so the
       
   477         cube schema is already in there)
       
   478         """
       
   479         newcubes = super(ServerMigrationHelper, self).cmd_add_cubes(cubes)
       
   480         if not newcubes:
       
   481             return
       
   482         for pack in newcubes:
       
   483             self.cmd_set_property('system.version.'+pack,
       
   484                                   self.config.cube_version(pack))
       
   485         if not update_database:
       
   486             self.commit()
       
   487             return
       
   488         newcubes_schema = self.config.load_schema(construction_mode='non-strict')
       
   489         new = set()
       
   490         # execute pre-create files
       
   491         for pack in reversed(newcubes):
       
   492             self.exec_event_script('precreate', self.config.cube_dir(pack))
       
   493         # add new entity and relation types
       
   494         for rschema in newcubes_schema.relations():
       
   495             if not rschema in self.repo.schema:
       
   496                 self.cmd_add_relation_type(rschema.type)
       
   497                 new.add(rschema.type)
       
   498         for eschema in newcubes_schema.entities():
       
   499             if not eschema in self.repo.schema:
       
   500                 self.cmd_add_entity_type(eschema.type)
       
   501                 new.add(eschema.type)
       
   502         # check if attributes has been added to existing entities
       
   503         for rschema in newcubes_schema.relations():
       
   504             existingschema = self.repo.schema.rschema(rschema.type)
       
   505             for (fromtype, totype) in rschema.iter_rdefs():
       
   506                 if existingschema.has_rdef(fromtype, totype):
       
   507                     continue
       
   508                 # check we should actually add the relation definition
       
   509                 if not (fromtype in new or totype in new or rschema in new):
       
   510                     continue
       
   511                 self.cmd_add_relation_definition(str(fromtype), rschema.type, 
       
   512                                                  str(totype))
       
   513         # execute post-create files
       
   514         for pack in reversed(newcubes):
       
   515             self.exec_event_script('postcreate', self.config.cube_dir(pack))
       
   516             self.commit()        
       
   517                 
       
   518     def cmd_remove_cube(self, cube):
       
   519         removedcubes = super(ServerMigrationHelper, self).cmd_remove_cube(cube)
       
   520         if not removedcubes:
       
   521             return
       
   522         fsschema = self.fs_schema
       
   523         removedcubes_schema = self.config.load_schema(construction_mode='non-strict')
       
   524         reposchema = self.repo.schema
       
   525         # execute pre-remove files
       
   526         for pack in reversed(removedcubes):
       
   527             self.exec_event_script('preremove', self.config.cube_dir(pack))
       
   528         # remove cubes'entity and relation types
       
   529         for rschema in fsschema.relations():
       
   530             if not rschema in removedcubes_schema and rschema in reposchema:
       
   531                 self.cmd_drop_relation_type(rschema.type)
       
   532         for eschema in fsschema.entities():
       
   533             if not eschema in removedcubes_schema and eschema in reposchema:
       
   534                 self.cmd_drop_entity_type(eschema.type)
       
   535         for rschema in fsschema.relations():
       
   536             if rschema in removedcubes_schema and rschema in reposchema: 
       
   537                 # check if attributes/relations has been added to entities from 
       
   538                 # other cubes
       
   539                 for fromtype, totype in rschema.iter_rdefs():
       
   540                     if not removedcubes_schema[rschema.type].has_rdef(fromtype, totype) and \
       
   541                            reposchema[rschema.type].has_rdef(fromtype, totype):
       
   542                         self.cmd_drop_relation_definition(
       
   543                             str(fromtype), rschema.type, str(totype))
       
   544         # execute post-remove files
       
   545         for pack in reversed(removedcubes):
       
   546             self.exec_event_script('postremove', self.config.cube_dir(pack))
       
   547             self.rqlexec('DELETE CWProperty X WHERE X pkey %(pk)s',
       
   548                          {'pk': u'system.version.'+pack}, ask_confirm=False)
       
   549             self.commit()
       
   550             
       
   551     # schema migration actions ################################################
       
   552     
       
   553     def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True):
       
   554         """add a new attribute on the given entity type"""
       
   555         if attrtype is None:
       
   556             rschema = self.fs_schema.rschema(attrname)
       
   557             attrtype = rschema.objects(etype)[0]
       
   558         self.cmd_add_relation_definition(etype, attrname, attrtype, commit=commit)
       
   559         
       
   560     def cmd_drop_attribute(self, etype, attrname, commit=True):
       
   561         """drop an existing attribute from the given entity type
       
   562         
       
   563         `attrname` is a string giving the name of the attribute to drop
       
   564         """
       
   565         rschema = self.repo.schema.rschema(attrname)
       
   566         attrtype = rschema.objects(etype)[0]
       
   567         self.cmd_drop_relation_definition(etype, attrname, attrtype, commit=commit)
       
   568 
       
   569     def cmd_rename_attribute(self, etype, oldname, newname, commit=True):
       
   570         """rename an existing attribute of the given entity type
       
   571         
       
   572         `oldname` is a string giving the name of the existing attribute
       
   573         `newname` is a string giving the name of the renamed attribute
       
   574         """
       
   575         eschema = self.fs_schema.eschema(etype)
       
   576         attrtype = eschema.destination(newname)
       
   577         # have to commit this first step anyway to get the definition
       
   578         # actually in the schema
       
   579         self.cmd_add_attribute(etype, newname, attrtype, commit=True)
       
   580         # skipp NULL values if the attribute is required
       
   581         rql = 'SET X %s VAL WHERE X is %s, X %s VAL' % (newname, etype, oldname)
       
   582         card = eschema.rproperty(newname, 'cardinality')[0]
       
   583         if card == '1':
       
   584             rql += ', NOT X %s NULL' % oldname
       
   585         self.rqlexec(rql, ask_confirm=self.verbosity>=2)
       
   586         self.cmd_drop_attribute(etype, oldname, commit=commit)
       
   587             
       
   588     def cmd_add_entity_type(self, etype, auto=True, commit=True):
       
   589         """register a new entity type
       
   590         
       
   591         in auto mode, automatically register entity's relation where the
       
   592         targeted type is known
       
   593         """
       
   594         applschema = self.repo.schema
       
   595         if etype in applschema:
       
   596             eschema = applschema[etype]
       
   597             if eschema.is_final():
       
   598                 applschema.del_entity_type(etype)
       
   599         else:
       
   600             eschema = self.fs_schema.eschema(etype)
       
   601         confirm = self.verbosity >= 2
       
   602         # register the entity into CWEType
       
   603         self.rqlexecall(ss.eschema2rql(eschema), ask_confirm=confirm)
       
   604         # add specializes relation if needed
       
   605         self.rqlexecall(ss.eschemaspecialize2rql(eschema), ask_confirm=confirm)
       
   606         # register groups / permissions for the entity
       
   607         self.rqlexecall(ss.erperms2rql(eschema, self.group_mapping()),
       
   608                         ask_confirm=confirm)
       
   609         # register entity's attributes
       
   610         for rschema, attrschema in eschema.attribute_definitions():
       
   611             # ignore those meta relations, they will be automatically added
       
   612             if rschema.type in ('eid', 'creation_date', 'modification_date'):
       
   613                 continue
       
   614             if not rschema.type in applschema:
       
   615                 # need to add the relation type and to commit to get it
       
   616                 # actually in the schema
       
   617                 self.cmd_add_relation_type(rschema.type, False, commit=True)
       
   618             # register relation definition
       
   619             self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type),
       
   620                             ask_confirm=confirm)
       
   621         if auto:
       
   622             # we have commit here to get relation types actually in the schema
       
   623             self.commit()
       
   624             added = []
       
   625             for rschema in eschema.subject_relations():
       
   626                 # attribute relation have already been processed and
       
   627                 # 'owned_by'/'created_by' will be automatically added
       
   628                 if rschema.final or rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'): 
       
   629                     continue
       
   630                 rtypeadded = rschema.type in applschema
       
   631                 for targetschema in rschema.objects(etype):
       
   632                     # ignore relations where the targeted type is not in the
       
   633                     # current application schema
       
   634                     targettype = targetschema.type
       
   635                     if not targettype in applschema and targettype != etype:
       
   636                         continue
       
   637                     if not rtypeadded:
       
   638                         # need to add the relation type and to commit to get it
       
   639                         # actually in the schema
       
   640                         added.append(rschema.type)
       
   641                         self.cmd_add_relation_type(rschema.type, False, commit=True)
       
   642                         rtypeadded = True
       
   643                     # register relation definition
       
   644                     # remember this two avoid adding twice non symetric relation
       
   645                     # such as "Emailthread forked_from Emailthread"
       
   646                     added.append((etype, rschema.type, targettype))
       
   647                     self.rqlexecall(ss.rdef2rql(rschema, etype, targettype),
       
   648                                     ask_confirm=confirm)
       
   649             for rschema in eschema.object_relations():
       
   650                 rtypeadded = rschema.type in applschema or rschema.type in added
       
   651                 for targetschema in rschema.subjects(etype):
       
   652                     # ignore relations where the targeted type is not in the
       
   653                     # current application schema
       
   654                     targettype = targetschema.type
       
   655                     # don't check targettype != etype since in this case the
       
   656                     # relation has already been added as a subject relation
       
   657                     if not targettype in applschema:
       
   658                         continue
       
   659                     if not rtypeadded:
       
   660                         # need to add the relation type and to commit to get it
       
   661                         # actually in the schema
       
   662                         self.cmd_add_relation_type(rschema.type, False, commit=True)
       
   663                         rtypeadded = True
       
   664                     elif (targettype, rschema.type, etype) in added:
       
   665                         continue
       
   666                     # register relation definition
       
   667                     self.rqlexecall(ss.rdef2rql(rschema, targettype, etype),
       
   668                                     ask_confirm=confirm)
       
   669         if commit:
       
   670             self.commit()
       
   671                 
       
   672     def cmd_drop_entity_type(self, etype, commit=True):
       
   673         """unregister an existing entity type
       
   674         
       
   675         This will trigger deletion of necessary relation types and definitions
       
   676         """
       
   677         # XXX what if we delete an entity type which is specialized by other types
       
   678         # unregister the entity from CWEType
       
   679         self.rqlexec('DELETE CWEType X WHERE X name %(etype)s', {'etype': etype},
       
   680                      ask_confirm=self.verbosity>=2)
       
   681         if commit:
       
   682             self.commit()
       
   683 
       
   684     def cmd_rename_entity_type(self, oldname, newname, commit=True):
       
   685         """rename an existing entity type in the persistent schema
       
   686         
       
   687         `oldname` is a string giving the name of the existing entity type
       
   688         `newname` is a string giving the name of the renamed entity type
       
   689         """
       
   690         self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(oldname)s',
       
   691                      {'newname' : unicode(newname), 'oldname' : oldname})
       
   692         if commit:
       
   693             self.commit()
       
   694         
       
   695     def cmd_add_relation_type(self, rtype, addrdef=True, commit=True):
       
   696         """register a new relation type named `rtype`, as described in the
       
   697         schema description file.
       
   698 
       
   699         `addrdef` is a boolean value; when True, it will also add all relations
       
   700         of the type just added found in the schema definition file. Note that it
       
   701         implies an intermediate "commit" which commits the relation type
       
   702         creation (but not the relation definitions themselves, for which
       
   703         committing depends on the `commit` argument value).
       
   704         
       
   705         """
       
   706         rschema = self.fs_schema.rschema(rtype)
       
   707         # register the relation into CWRType and insert necessary relation
       
   708         # definitions
       
   709         self.rqlexecall(ss.rschema2rql(rschema, addrdef=False),
       
   710                         ask_confirm=self.verbosity>=2)
       
   711         # register groups / permissions for the relation
       
   712         self.rqlexecall(ss.erperms2rql(rschema, self.group_mapping()),
       
   713                         ask_confirm=self.verbosity>=2)
       
   714         if addrdef:
       
   715             self.commit()
       
   716             self.rqlexecall(ss.rdef2rql(rschema),
       
   717                             ask_confirm=self.verbosity>=2)
       
   718         if commit:
       
   719             self.commit()
       
   720         
       
   721     def cmd_drop_relation_type(self, rtype, commit=True):
       
   722         """unregister an existing relation type"""
       
   723         # unregister the relation from CWRType
       
   724         self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype,
       
   725                      ask_confirm=self.verbosity>=2)
       
   726         if commit:
       
   727             self.commit()
       
   728         
       
   729     def cmd_rename_relation(self, oldname, newname, commit=True):
       
   730         """rename an existing relation
       
   731         
       
   732         `oldname` is a string giving the name of the existing relation
       
   733         `newname` is a string giving the name of the renamed relation
       
   734         """
       
   735         self.cmd_add_relation_type(newname, commit=True)
       
   736         self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname),
       
   737                      ask_confirm=self.verbosity>=2)
       
   738         self.cmd_drop_relation_type(oldname, commit=commit)
       
   739 
       
   740     def cmd_add_relation_definition(self, subjtype, rtype, objtype, commit=True):
       
   741         """register a new relation definition, from its definition found in the
       
   742         schema definition file
       
   743         """
       
   744         rschema = self.fs_schema.rschema(rtype)
       
   745         if not rtype in self.repo.schema:
       
   746             self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
       
   747         self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype),
       
   748                         ask_confirm=self.verbosity>=2)
       
   749         if commit:
       
   750             self.commit()
       
   751         
       
   752     def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
       
   753         """unregister an existing relation definition"""
       
   754         rschema = self.repo.schema.rschema(rtype)
       
   755         # unregister the definition from CWAttribute or CWRelation
       
   756         if rschema.is_final():
       
   757             etype = 'CWAttribute'
       
   758         else:
       
   759             etype = 'CWRelation'
       
   760         rql = ('DELETE %s X WHERE X from_entity FE, FE name "%s",'
       
   761                'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"')
       
   762         self.rqlexec(rql % (etype, subjtype, rtype, objtype),
       
   763                      ask_confirm=self.verbosity>=2)
       
   764         if commit:
       
   765             self.commit()
       
   766         
       
   767     def cmd_sync_schema_props_perms(self, ertype=None, syncperms=True,
       
   768                                     syncprops=True, syncrdefs=True, commit=True)
   775         """synchronize the persistent schema against the current definition
   769         """synchronize the persistent schema against the current definition
   776         schema.
   770         schema.
   777         
   771         
   778         It will synch common stuff between the definition schema and the
   772         It will synch common stuff between the definition schema and the
   779         actual persistent schema, it won't add/remove any entity or relation.
   773         actual persistent schema, it won't add/remove any entity or relation.
   780         """
   774         """
   781         for etype in self.repo.schema.entities():
   775         assert syncperms or syncprops, 'nothing to do'
   782             self.cmd_synchronize_eschema(etype, syncperms=syncperms, commit=False)
   776         if ertype is not None:
       
   777             if isinstance(ertype, (tuple, list)):
       
   778                 assert len(ertype) == 3, 'not a relation definition'
       
   779                 assert syncprops, 'can\'t update permission for a relation definition'
       
   780                 self._synchronize_rdef_schema(*ertype)
       
   781             elif syncprops:
       
   782                 erschema = self.repo.schema[ertype]
       
   783                 if isinstance(erschema, CubicWebRelationSchema):
       
   784                     self._synchronize_rschema(erschema, syncperms=syncperms,
       
   785                                               syncrdefs=syncrdefs)
       
   786                 else:
       
   787                     self._synchronize_eschema(erschema, syncperms=syncperms)
       
   788             else:
       
   789                 self._synchronize_permissions(ertype)
       
   790         else:
       
   791             for etype in self.repo.schema.entities():
       
   792                 if syncprops:
       
   793                     self._synchronize_eschema(etype, syncperms=syncperms)
       
   794                 else:
       
   795                     self._synchronize_permissions(etype)
   783         if commit:
   796         if commit:
   784             self.commit()
   797             self.commit()
   785                 
   798                 
   786     def cmd_change_relation_props(self, subjtype, rtype, objtype,
   799     def cmd_change_relation_props(self, subjtype, rtype, objtype,
   787                                   commit=True, **kwargs):
   800                                   commit=True, **kwargs):
   788         """change some properties of a relation definition"""
   801         """change some properties of a relation definition
       
   802 
       
   803         you usually want to use sync_schema_props_perms instead.
       
   804         """
   789         assert kwargs
   805         assert kwargs
   790         restriction = []
   806         restriction = []
   791         if subjtype and subjtype != 'Any':
   807         if subjtype and subjtype != 'Any':
   792             restriction.append('X from_entity FE, FE name "%s"' % subjtype)
   808             restriction.append('X from_entity FE, FE name "%s"' % subjtype)
   793         if objtype and objtype != 'Any':
   809         if objtype and objtype != 'Any':
   806             self.commit()
   822             self.commit()
   807 
   823 
   808     def cmd_set_size_constraint(self, etype, rtype, size, commit=True):
   824     def cmd_set_size_constraint(self, etype, rtype, size, commit=True):
   809         """set change size constraint of a string attribute
   825         """set change size constraint of a string attribute
   810 
   826 
   811         if size is None any size constraint will be removed
   827         if size is None any size constraint will be removed.
       
   828         
       
   829         you usually want to use sync_schema_props_perms instead.        
   812         """
   830         """
   813         oldvalue = None
   831         oldvalue = None
   814         for constr in self.repo.schema.eschema(etype).constraints(rtype):
   832         for constr in self.repo.schema.eschema(etype).constraints(rtype):
   815             if isinstance(constr, SizeConstraint):
   833             if isinstance(constr, SizeConstraint):
   816                 oldvalue = constr.max
   834                 oldvalue = constr.max
   838                              ask_confirm=self.verbosity>=2)
   856                              ask_confirm=self.verbosity>=2)
   839                 # cleanup unused constraints
   857                 # cleanup unused constraints
   840                 self.rqlexec('DELETE CWConstraint C WHERE NOT X constrained_by C')
   858                 self.rqlexec('DELETE CWConstraint C WHERE NOT X constrained_by C')
   841         if commit:
   859         if commit:
   842             self.commit()
   860             self.commit()
       
   861 
       
   862     @obsolete('use sync_schema_props_perms(ertype, syncprops=False)')
       
   863     def cmd_synchronize_permissions(self, ertype, commit=True):
       
   864         self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit)
   843     
   865     
   844     # Workflows handling ######################################################
   866     # Workflows handling ######################################################
   845     
   867     
   846     def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs):
   868     def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs):
   847         """method to ease workflow definition: add a state for one or more
   869         """method to ease workflow definition: add a state for one or more