[server] remove not-so-useful entity_name and entity_attr functions, introduce entity_oldnewvalue
entity_oldnewvalue(entity, attr) is usable in before_update_entity and
returns both old and new value of entity
"""schema hooks:- synchronize the living schema object with the persistent schema- perform physical update on the source when necessarychecking for schema consistency is done in hooks.py:organization: Logilab:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses"""__docformat__="restructuredtext en"fromyams.schemaimportBASE_TYPESfromyams.buildobjsimportEntityType,RelationType,RelationDefinitionfromyams.schema2sqlimporteschema2sql,rschema2sql,type_from_constraintsfromlogilab.common.decoratorsimportclear_cachefromcubicwebimportValidationError,RepositoryErrorfromcubicweb.schemaimportMETA_RTYPES,VIRTUAL_RTYPES,CONSTRAINTSfromcubicweb.serverimportschemaserialasssfromcubicweb.server.sqlutilsimportSQL_PREFIXfromcubicweb.server.poolimportOperation,SingleLastOperation,PreCommitOperationfromcubicweb.server.hookhelperimportentity_oldnewvalue,check_internal_entityTYPE_CONVERTER={# XXX'Boolean':bool,'Int':int,'Float':float,'Password':str,'String':unicode,'Date':unicode,'Datetime':unicode,'Time':unicode,}# core entity and relation types which can't be removedCORE_ETYPES=list(BASE_TYPES)+['CWEType','CWRType','CWUser','CWGroup','CWConstraint','CWAttribute','CWRelation']CORE_RTYPES=['eid','creation_date','modification_date','cwuri','login','upassword','name','is','instanceof','owned_by','created_by','in_group','relation_type','from_entity','to_entity','constrainted_by','read_permission','add_permission','delete_permission','updated_permission',]defget_constraints(session,entity):constraints=[]forcstreidinsession.transaction_data.get(entity.eid,()):cstrent=session.entity_from_eid(cstreid)cstr=CONSTRAINTS[cstrent.type].deserialize(cstrent.value)cstr.eid=cstreidconstraints.append(cstr)returnconstraintsdefadd_inline_relation_column(session,etype,rtype):"""add necessary column and index for an inlined relation"""table=SQL_PREFIX+etypecolumn=SQL_PREFIX+rtypetry:session.system_sql(str('ALTER TABLE %s ADD COLUMN %s integer'%(table,column)),rollback_on_failure=False)session.info('added column %s to table %s',column,table)except:# silent exception here, if this error has not been raised because the# column already exists, index creation will fail anywaysession.exception('error while adding column %s to table %s',table,column)# create index before alter table which may expectingly fail during test# (sqlite) while index creation should never fail (test for index existence# is done by the dbhelper)session.pool.source('system').create_index(session,table,column)session.info('added index on %s(%s)',table,column)session.transaction_data.setdefault('createdattrs',[]).append('%s.%s'%(etype,rtype))# operations for low-level database alteration ################################classDropTable(PreCommitOperation):"""actually remove a database from the instance's schema"""table=None# make pylint happydefprecommit_event(self):dropped=self.session.transaction_data.setdefault('droppedtables',set())ifself.tableindropped:return# already processeddropped.add(self.table)self.session.system_sql('DROP TABLE %s'%self.table)self.info('dropped table %s',self.table)classDropRelationTable(DropTable):def__init__(self,session,rtype):super(DropRelationTable,self).__init__(session,table='%s_relation'%rtype)session.transaction_data.setdefault('pendingrtypes',set()).add(rtype)classDropColumn(PreCommitOperation):"""actually remove the attribut's column from entity table in the system database """table=column=None# make pylint happydefprecommit_event(self):session,table,column=self.session,self.table,self.column# drop index if anysession.pool.source('system').drop_index(session,table,column)try:session.system_sql('ALTER TABLE %s DROP COLUMN %s'%(table,column),rollback_on_failure=False)self.info('dropped column %s from table %s',column,table)exceptException,ex:# not supported by sqlite for instanceself.error('error while altering table %s: %s',table,ex)# base operations for in-memory schema synchronization ########################classMemSchemaNotifyChanges(SingleLastOperation):"""the update schema operation: special operation which should be called once and after all other schema operations. It will trigger internal structures rebuilding to consider schema changes """def__init__(self,session):self.repo=session.repoSingleLastOperation.__init__(self,session)defprecommit_event(self):foreschemainself.repo.schema.entities():ifnoteschema.final:clear_cache(eschema,'ordered_relations')defcommit_event(self):rebuildinfered=self.session.data.get('rebuild-infered',True)self.repo.set_schema(self.repo.schema,rebuildinfered=rebuildinfered)defrollback_event(self):self.precommit_event()classMemSchemaOperation(Operation):"""base class for schema operations"""def__init__(self,session,kobj=None,**kwargs):self.schema=session.schemaself.kobj=kobj# once Operation.__init__ has been called, event may be triggered, so# do this last !Operation.__init__(self,session,**kwargs)# every schema operation is triggering a schema updateMemSchemaNotifyChanges(session)defprepare_constraints(self,subjtype,rtype,objtype):constraints=rtype.rproperty(subjtype,objtype,'constraints')self.constraints=list(constraints)rtype.set_rproperty(subjtype,objtype,'constraints',self.constraints)classMemSchemaEarlyOperation(MemSchemaOperation):definsert_index(self):"""schema operation which are inserted at the begining of the queue (typically to add/remove entity or relation types) """i=-1fori,opinenumerate(self.session.pending_operations):ifnotisinstance(op,MemSchemaEarlyOperation):returnireturni+1classMemSchemaPermissionOperation(MemSchemaOperation):"""base class to synchronize schema permission definitions"""def__init__(self,session,perm,etype_eid):self.perm=permtry:self.name=session.entity_from_eid(etype_eid).nameexceptIndexError:self.error('changing permission of a no more existant type #%s',etype_eid)else:Operation.__init__(self,session)# operations for high-level source database alteration ########################classSourceDbCWETypeRename(PreCommitOperation):"""this operation updates physical storage accordingly"""oldname=newname=None# make pylint happydefprecommit_event(self):# we need sql to operate physical changes on the system databasesqlexec=self.session.system_sqlsqlexec('ALTER TABLE %s%s RENAME TO %s%s'%(SQL_PREFIX,self.oldname,SQL_PREFIX,self.newname))self.info('renamed table %s to %s',self.oldname,self.newname)sqlexec('UPDATE entities SET type=%s WHERE type=%s',(self.newname,self.oldname))sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s',(self.newname,self.oldname))classSourceDbCWRTypeUpdate(PreCommitOperation):"""actually update some properties of a relation definition"""rschema=values=entity=None# make pylint happydefprecommit_event(self):session=self.sessionrschema=self.rschemaifrschema.finalornot'inlined'inself.values:return# nothing to doinlined=self.values['inlined']entity=self.entity# check in-lining is necessary / possibleifnotentity.inlined_changed(inlined):return# nothing to do# inlined changed, make necessary physical changes!sqlexec=self.session.system_sqlrtype=rschema.typeeidcolumn=SQL_PREFIX+'eid'ifnotinlined:# need to create the relation if it has not been already done by# another event of the same transactionifnotrschema.typeinsession.transaction_data.get('createdtables',()):tablesql=rschema2sql(rschema)# create the necessary tableforsqlintablesql.split(';'):ifsql.strip():sqlexec(sql)session.transaction_data.setdefault('createdtables',[]).append(rschema.type)# copy existant datacolumn=SQL_PREFIX+rtypeforetypeinrschema.subjects():table=SQL_PREFIX+str(etype)sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL'%(rtype,eidcolumn,column,table,column))# drop existant columnsforetypeinrschema.subjects():DropColumn(session,table=SQL_PREFIX+str(etype),column=SQL_PREFIX+rtype)else:foretypeinrschema.subjects():try:add_inline_relation_column(session,str(etype),rtype)exceptException,ex:# the column probably already exists. this occurs when the# entity's type has just been added or if the column has not# been previously droppedself.error('error while altering table %s: %s',etype,ex)# copy existant data.# XXX don't use, it's not supported by sqlite (at least at when i tried it)#sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to '# 'FROM %(rtype)s_relation '# 'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from'# % locals())table=SQL_PREFIX+str(etype)cursor=sqlexec('SELECT eid_from, eid_to FROM %(table)s, ''%(rtype)s_relation WHERE %(table)s.%(eidcolumn)s=''%(rtype)s_relation.eid_from'%locals())args=[{'val':eid_to,'x':eid}foreid,eid_toincursor.fetchall()]ifargs:column=SQL_PREFIX+rtypecursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s'%(table,column,eidcolumn),args)# drop existant tableDropRelationTable(session,rtype)classSourceDbCWAttributeAdd(PreCommitOperation):"""an attribute relation (CWAttribute) has been added: * add the necessary column * set default on this column if any and possible * register an operation to add the relation definition to the instance's schema on commit constraints are handled by specific hooks """entity=None# make pylint happydefinit_rdef(self,**kwargs):entity=self.entityfromentity=entity.stypeself.session.execute('SET X ordernum Y+1 ''WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, ''X ordernum >= %(order)s, NOT X eid %(x)s',{'x':entity.eid,'se':fromentity.eid,'order':entity.ordernumor0})subj=str(fromentity.name)rtype=entity.rtype.nameobj=str(entity.otype.name)constraints=get_constraints(self.session,entity)rdef=RelationDefinition(subj,rtype,obj,description=entity.description,cardinality=entity.cardinality,constraints=constraints,order=entity.ordernum,eid=entity.eid,**kwargs)MemSchemaRDefAdd(self.session,rdef)returnrdefdefprecommit_event(self):session=self.sessionentity=self.entity# entity.defaultval is a string or None, but we need a correctly typed# valuedefault=entity.defaultvalifdefaultisnotNone:default=TYPE_CONVERTER[entity.otype.name](default)props={'default':default,'indexed':entity.indexed,'fulltextindexed':entity.fulltextindexed,'internationalizable':entity.internationalizable}rdef=self.init_rdef(**props)sysource=session.pool.source('system')attrtype=type_from_constraints(sysource.dbhelper,rdef.object,rdef.constraints)# XXX should be moved somehow into lgc.adbh: sqlite doesn't support to# add a new column with UNIQUE, it should be added after the ALTER TABLE# using ADD INDEXifsysource.dbdriver=='sqlite'and'UNIQUE'inattrtype:extra_unique_index=Trueattrtype=attrtype.replace(' UNIQUE','')else:extra_unique_index=False# added some str() wrapping query since some backend (eg psycopg) don't# allow unicode queriestable=SQL_PREFIX+rdef.subjectcolumn=SQL_PREFIX+rdef.nametry:session.system_sql(str('ALTER TABLE %s ADD COLUMN %s%s'%(table,column,attrtype)),rollback_on_failure=False)self.info('added column %s to table %s',table,column)exceptException,ex:# the column probably already exists. this occurs when# the entity's type has just been added or if the column# has not been previously droppedself.error('error while altering table %s: %s',table,ex)ifextra_unique_indexorentity.indexed:try:sysource.create_index(session,table,column,unique=extra_unique_index)exceptException,ex:self.error('error while creating index for %s.%s: %s',table,column,ex)# final relations are not infered, propagatetry:eschema=self.schema.eschema(rdef.subject)exceptKeyError:return# entity type currently being added# propagate attribute to children classesrschema=self.schema.rschema(rdef.name)# if relation type has been inserted in the same transaction, its final# attribute is still set to False, so we've to ensure it's Falserschema.final=True# XXX 'infered': True/False, not clear actuallyprops.update({'constraints':rdef.constraints,'description':rdef.description,'cardinality':rdef.cardinality,'constraints':rdef.constraints,'order':rdef.order})forspecializationineschema.specialized_by(False):ifrschema.has_rdef(specialization,rdef.object):continueforrql,argsinss.frdef2rql(rschema,str(specialization),rdef.object,props):session.execute(rql,args)# set default value, using sql for performance and to avoid# modification_date updateifdefault:session.system_sql('UPDATE %s SET %s=%%(default)s'%(table,column),{'default':default})classSourceDbCWRelationAdd(SourceDbCWAttributeAdd):"""an actual relation has been added: * if this is an inlined relation, add the necessary column else if it's the first instance of this relation type, add the necessary table and set default permissions * register an operation to add the relation definition to the instance's schema on commit constraints are handled by specific hooks """entity=None# make pylint happydefprecommit_event(self):session=self.sessionentity=self.entityrdef=self.init_rdef(composite=entity.composite)schema=session.schemartype=rdef.namerschema=session.schema.rschema(rtype)# this have to be done before permissions settingifrschema.inlined:# need to add a column if the relation is inlined and if this is the# first occurence of "Subject relation Something" whatever Something# and if it has not been added during other event of the same# transactionkey='%s.%s'%(rdef.subject,rtype)try:alreadythere=bool(rschema.objects(rdef.subject))exceptKeyError:alreadythere=Falseifnot(alreadythereorkeyinsession.transaction_data.get('createdattrs',())):add_inline_relation_column(session,rdef.subject,rtype)else:# need to create the relation if no relation definition in the# schema and if it has not been added during other event of the same# transactionifnot(rschema.subjects()orrtypeinsession.transaction_data.get('createdtables',())):try:rschema=session.schema.rschema(rtype)tablesql=rschema2sql(rschema)exceptKeyError:# fake we add it to the schema now to get a correctly# initialized schema but remove it before doing anything# more dangerous...rschema=session.schema.add_relation_type(rdef)tablesql=rschema2sql(rschema)session.schema.del_relation_type(rtype)# create the necessary tableforsqlintablesql.split(';'):ifsql.strip():session.system_sql(sql)session.transaction_data.setdefault('createdtables',[]).append(rtype)classSourceDbRDefUpdate(PreCommitOperation):"""actually update some properties of a relation definition"""rschema=values=None# make pylint happydefprecommit_event(self):etype=self.kobj[0]table=SQL_PREFIX+etypecolumn=SQL_PREFIX+self.rschema.typeif'indexed'inself.values:sysource=self.session.pool.source('system')ifself.values['indexed']:sysource.create_index(self.session,table,column)else:sysource.drop_index(self.session,table,column)if'cardinality'inself.valuesandself.rschema.final:adbh=self.session.pool.source('system').dbhelperifnotadbh.alter_column_support:# not supported (and NOT NULL not set by yams in that case, so# no worry)returnatype=self.rschema.objects(etype)[0]constraints=self.rschema.rproperty(etype,atype,'constraints')coltype=type_from_constraints(adbh,atype,constraints,creating=False)# XXX check self.values['cardinality'][0] actually changed?sql=adbh.sql_set_null_allowed(table,column,coltype,self.values['cardinality'][0]!='1')self.session.system_sql(sql)classSourceDbCWConstraintAdd(PreCommitOperation):"""actually update constraint of a relation definition"""entity=None# make pylint happycancelled=Falsedefprecommit_event(self):rdef=self.entity.reverse_constrained_by[0]session=self.session# when the relation is added in the same transaction, the constraint# object is created by the operation adding the attribute or relation,# so there is nothing to do hereifrdef.eidinsession.transaction_data.get('neweids',()):returnsubjtype,rtype,objtype=session.schema.schema_by_eid(rdef.eid)cstrtype=self.entity.typeoldcstr=rtype.constraint_by_type(subjtype,objtype,cstrtype)newcstr=CONSTRAINTS[cstrtype].deserialize(self.entity.value)table=SQL_PREFIX+str(subjtype)column=SQL_PREFIX+str(rtype)# alter the physical schema on size constraint changesifnewcstr.type()=='SizeConstraint'and(oldcstrisNoneoroldcstr.max!=newcstr.max):adbh=self.session.pool.source('system').dbhelpercard=rtype.rproperty(subjtype,objtype,'cardinality')coltype=type_from_constraints(adbh,objtype,[newcstr],creating=False)sql=adbh.sql_change_col_type(table,column,coltype,card!='1')try:session.system_sql(sql,rollback_on_failure=False)self.info('altered column %s of table %s: now VARCHAR(%s)',column,table,newcstr.max)exceptException,ex:# not supported by sqlite for instanceself.error('error while altering table %s: %s',table,ex)elifcstrtype=='UniqueConstraint'andoldcstrisNone:session.pool.source('system').create_index(self.session,table,column,unique=True)classSourceDbCWConstraintDel(PreCommitOperation):"""actually remove a constraint of a relation definition"""rtype=subjtype=objtype=None# make pylint happydefprecommit_event(self):cstrtype=self.cstr.type()table=SQL_PREFIX+str(self.subjtype)column=SQL_PREFIX+str(self.rtype)# alter the physical schema on size/unique constraint changesifcstrtype=='SizeConstraint':try:self.session.system_sql('ALTER TABLE %s ALTER COLUMN %s TYPE TEXT'%(table,column),rollback_on_failure=False)self.info('altered column %s of table %s: now TEXT',column,table)exceptException,ex:# not supported by sqlite for instanceself.error('error while altering table %s: %s',table,ex)elifcstrtype=='UniqueConstraint':self.session.pool.source('system').drop_index(self.session,table,column,unique=True)# operations for in-memory schema synchronization #############################classMemSchemaCWETypeAdd(MemSchemaEarlyOperation):"""actually add the entity type to the instance's schema"""eid=None# make pylint happydefcommit_event(self):self.schema.add_entity_type(self.kobj)classMemSchemaCWETypeRename(MemSchemaOperation):"""this operation updates physical storage accordingly"""oldname=newname=None# make pylint happydefcommit_event(self):self.session.schema.rename_entity_type(self.oldname,self.newname)classMemSchemaCWETypeDel(MemSchemaOperation):"""actually remove the entity type from the instance's schema"""defcommit_event(self):try:# del_entity_type also removes entity's relationsself.schema.del_entity_type(self.kobj)exceptKeyError:# s/o entity type have already been deletedpassclassMemSchemaCWRTypeAdd(MemSchemaEarlyOperation):"""actually add the relation type to the instance's schema"""eid=None# make pylint happydefcommit_event(self):rschema=self.schema.add_relation_type(self.kobj)rschema.set_default_groups()classMemSchemaCWRTypeUpdate(MemSchemaOperation):"""actually update some properties of a relation definition"""rschema=values=None# make pylint happydefcommit_event(self):# structure should be clean, not need to remove entity's relations# at this pointself.rschema.__dict__.update(self.values)classMemSchemaCWRTypeDel(MemSchemaOperation):"""actually remove the relation type from the instance's schema"""defcommit_event(self):try:self.schema.del_relation_type(self.kobj)exceptKeyError:# s/o entity type have already been deletedpassclassMemSchemaRDefAdd(MemSchemaEarlyOperation):"""actually add the attribute relation definition to the instance's schema """defcommit_event(self):self.schema.add_relation_def(self.kobj)classMemSchemaRDefUpdate(MemSchemaOperation):"""actually update some properties of a relation definition"""rschema=values=None# make pylint happydefcommit_event(self):# structure should be clean, not need to remove entity's relations# at this pointself.rschema._rproperties[self.kobj].update(self.values)classMemSchemaRDefDel(MemSchemaOperation):"""actually remove the relation definition from the instance's schema"""defcommit_event(self):subjtype,rtype,objtype=self.kobjtry:self.schema.del_relation_def(subjtype,rtype,objtype)exceptKeyError:# relation type may have been already deletedpassclassMemSchemaCWConstraintAdd(MemSchemaOperation):"""actually update constraint of a relation definition has to be called before SourceDbCWConstraintAdd """cancelled=Falsedefprecommit_event(self):rdef=self.entity.reverse_constrained_by[0]# when the relation is added in the same transaction, the constraint# object is created by the operation adding the attribute or relation,# so there is nothing to do hereifrdef.eidinself.session.transaction_data.get('neweids',()):self.cancelled=Truereturnsubjtype,rtype,objtype=self.session.schema.schema_by_eid(rdef.eid)self.prepare_constraints(subjtype,rtype,objtype)cstrtype=self.entity.typeself.cstr=rtype.constraint_by_type(subjtype,objtype,cstrtype)self.newcstr=CONSTRAINTS[cstrtype].deserialize(self.entity.value)self.newcstr.eid=self.entity.eiddefcommit_event(self):ifself.cancelled:return# in-place modificationifnotself.cstrisNone:self.constraints.remove(self.cstr)self.constraints.append(self.newcstr)classMemSchemaCWConstraintDel(MemSchemaOperation):"""actually remove a constraint of a relation definition has to be called before SourceDbCWConstraintDel """rtype=subjtype=objtype=None# make pylint happydefprecommit_event(self):self.prepare_constraints(self.subjtype,self.rtype,self.objtype)defcommit_event(self):self.constraints.remove(self.cstr)classMemSchemaPermissionCWGroupAdd(MemSchemaPermissionOperation):"""synchronize schema when a *_permission relation has been added on a group """def__init__(self,session,perm,etype_eid,group_eid):self.group=session.entity_from_eid(group_eid).namesuper(MemSchemaPermissionCWGroupAdd,self).__init__(session,perm,etype_eid)defcommit_event(self):"""the observed connections pool has been commited"""try:erschema=self.schema[self.name]exceptKeyError:# duh, schema not found, log error and skip operationself.error('no schema for %s',self.name)returngroups=list(erschema.get_groups(self.perm))try:groups.index(self.group)self.warning('group %s already have permission %s on %s',self.group,self.perm,erschema.type)exceptValueError:groups.append(self.group)erschema.set_groups(self.perm,groups)classMemSchemaPermissionCWGroupDel(MemSchemaPermissionCWGroupAdd):"""synchronize schema when a *_permission relation has been deleted from a group """defcommit_event(self):"""the observed connections pool has been commited"""try:erschema=self.schema[self.name]exceptKeyError:# duh, schema not found, log error and skip operationself.error('no schema for %s',self.name)returngroups=list(erschema.get_groups(self.perm))try:groups.remove(self.group)erschema.set_groups(self.perm,groups)exceptValueError:self.error('can\'t remove permission %s on %s to group %s',self.perm,erschema.type,self.group)classMemSchemaPermissionRQLExpressionAdd(MemSchemaPermissionOperation):"""synchronize schema when a *_permission relation has been added on a rql expression """def__init__(self,session,perm,etype_eid,expression):self.expr=expressionsuper(MemSchemaPermissionRQLExpressionAdd,self).__init__(session,perm,etype_eid)defcommit_event(self):"""the observed connections pool has been commited"""try:erschema=self.schema[self.name]exceptKeyError:# duh, schema not found, log error and skip operationself.error('no schema for %s',self.name)returnexprs=list(erschema.get_rqlexprs(self.perm))exprs.append(erschema.rql_expression(self.expr))erschema.set_rqlexprs(self.perm,exprs)classMemSchemaPermissionRQLExpressionDel(MemSchemaPermissionRQLExpressionAdd):"""synchronize schema when a *_permission relation has been deleted from an rql expression """defcommit_event(self):"""the observed connections pool has been commited"""try:erschema=self.schema[self.name]exceptKeyError:# duh, schema not found, log error and skip operationself.error('no schema for %s',self.name)returnrqlexprs=list(erschema.get_rqlexprs(self.perm))fori,rqlexprinenumerate(rqlexprs):ifrqlexpr.expression==self.expr:rqlexprs.pop(i)breakelse:self.error('can\'t remove permission %s on %s for expression %s',self.perm,erschema.type,self.expr)returnerschema.set_rqlexprs(self.perm,rqlexprs)classMemSchemaSpecializesAdd(MemSchemaOperation):defcommit_event(self):eschema=self.session.schema.schema_by_eid(self.etypeeid)parenteschema=self.session.schema.schema_by_eid(self.parentetypeeid)eschema._specialized_type=parenteschema.typeparenteschema._specialized_by.append(eschema.type)classMemSchemaSpecializesDel(MemSchemaOperation):defcommit_event(self):try:eschema=self.session.schema.schema_by_eid(self.etypeeid)parenteschema=self.session.schema.schema_by_eid(self.parentetypeeid)exceptKeyError:# etype removed, nothing to doreturneschema._specialized_type=Noneparenteschema._specialized_by.remove(eschema.type)# deletion hooks ###############################################################defbefore_del_eetype(session,eid):"""before deleting a CWEType entity: * check that we don't remove a core entity type * cascade to delete related CWAttribute and CWRelation entities * instantiate an operation to delete the entity type on commit """# final entities can't be deleted, don't care about thatname=check_internal_entity(session,eid,CORE_ETYPES)# delete every entities of this typesession.unsafe_execute('DELETE %s X'%name)DropTable(session,table=SQL_PREFIX+name)MemSchemaCWETypeDel(session,name)defafter_del_eetype(session,eid):# workflow cleanupsession.execute('DELETE Workflow X WHERE NOT X workflow_of Y')defbefore_del_ertype(session,eid):"""before deleting a CWRType entity: * check that we don't remove a core relation type * cascade to delete related CWAttribute and CWRelation entities * instantiate an operation to delete the relation type on commit """name=check_internal_entity(session,eid,CORE_RTYPES)# delete relation definitions using this relation typesession.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',{'x':eid})session.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s',{'x':eid})MemSchemaCWRTypeDel(session,name)defafter_del_relation_type(session,rdefeid,rtype,rteid):"""before deleting a CWAttribute or CWRelation entity: * if this is a final or inlined relation definition, instantiate an operation to drop necessary column, else if this is the last instance of a non final relation, instantiate an operation to drop necessary table * instantiate an operation to delete the relation definition on commit * delete the associated relation type when necessary """subjschema,rschema,objschema=session.schema.schema_by_eid(rdefeid)pendings=session.transaction_data.get('pendingeids',())pendingrdefs=session.transaction_data.setdefault('pendingrdefs',set())# first delete existing relation if necessaryifrschema.final:rdeftype='CWAttribute'pendingrdefs.add((subjschema,rschema))else:rdeftype='CWRelation'pendingrdefs.add((subjschema,rschema,objschema))ifnot(subjschema.eidinpendingsorobjschema.eidinpendings):session.execute('DELETE X %s Y WHERE X is %s, Y is %s'%(rschema,subjschema,objschema))execute=session.unsafe_executerset=execute('Any COUNT(X) WHERE X is %s, X relation_type R,''R eid %%(x)s'%rdeftype,{'x':rteid})lastrel=rset[0][0]==0# we have to update physical schema systematically for final and inlined# relations, but only if it's the last instance for this relation type# for other relationsif(rschema.finalorrschema.inlined):rset=execute('Any COUNT(X) WHERE X is %s, X relation_type R, ''R eid %%(x)s, X from_entity E, E name %%(name)s'%rdeftype,{'x':rteid,'name':str(subjschema)})ifrset[0][0]==0andnotsubjschema.eidinpendings:ptypes=session.transaction_data.setdefault('pendingrtypes',set())ptypes.add(rschema.type)DropColumn(session,table=SQL_PREFIX+subjschema.type,column=SQL_PREFIX+rschema.type)eliflastrel:DropRelationTable(session,rschema.type)# if this is the last instance, drop associated relation typeiflastrelandnotrteidinpendings:execute('DELETE CWRType X WHERE X eid %(x)s',{'x':rteid},'x')MemSchemaRDefDel(session,(subjschema,rschema,objschema))# addition hooks ###############################################################defbefore_add_eetype(session,entity):"""before adding a CWEType entity: * check that we are not using an existing entity type, """name=entity['name']schema=session.schemaifnameinschemaandschema[name].eidisnotNone:raiseRepositoryError('an entity type %s already exists'%name)defafter_add_eetype(session,entity):"""after adding a CWEType entity: * create the necessary table * set creation_date and modification_date by creating the necessary CWAttribute entities * add owned_by relation by creating the necessary CWRelation entity * register an operation to add the entity type to the instance's schema on commit """ifentity.get('final'):returnschema=session.schemaname=entity['name']etype=EntityType(name=name,description=entity.get('description'),meta=entity.get('meta'))# don't care about final# fake we add it to the schema now to get a correctly initialized schema# but remove it before doing anything more dangerous...schema=session.schemaeschema=schema.add_entity_type(etype)eschema.set_default_groups()# generate table sql and rql to add metadatatablesql=eschema2sql(session.pool.source('system').dbhelper,eschema,prefix=SQL_PREFIX)relrqls=[]forrtypein(META_RTYPES-VIRTUAL_RTYPES):rschema=schema[rtype]sampletype=rschema.subjects()[0]desttype=rschema.objects()[0]props=rschema.rproperties(sampletype,desttype)relrqls+=list(ss.rdef2rql(rschema,name,desttype,props))# now remove it !schema.del_entity_type(name)# create the necessary tableforsqlintablesql.split(';'):ifsql.strip():session.system_sql(sql)# register operation to modify the schema on commit# this have to be done before adding other relations definitions# or permission settingsetype.eid=entity.eidMemSchemaCWETypeAdd(session,etype)# add meta relationsforrql,kwargsinrelrqls:session.execute(rql,kwargs)defbefore_add_ertype(session,entity):"""before adding a CWRType entity: * check that we are not using an existing relation type, * register an operation to add the relation type to the instance's schema on commit We don't know yeat this point if a table is necessary """name=entity['name']ifnameinsession.schema.relations():raiseRepositoryError('a relation type %s already exists'%name)defafter_add_ertype(session,entity):"""after a CWRType entity has been added: * register an operation to add the relation type to the instance's schema on commit We don't know yeat this point if a table is necessary """rtype=RelationType(name=entity['name'],description=entity.get('description'),meta=entity.get('meta',False),inlined=entity.get('inlined',False),symetric=entity.get('symetric',False))rtype.eid=entity.eidMemSchemaCWRTypeAdd(session,rtype)defafter_add_efrdef(session,entity):SourceDbCWAttributeAdd(session,entity=entity)defafter_add_enfrdef(session,entity):SourceDbCWRelationAdd(session,entity=entity)# update hooks #################################################################defcheck_valid_changes(session,entity,ro_attrs=('name','final')):errors={}# don't use getattr(entity, attr), we would get the modified value if anyforattrinro_attrs:ifattrinentity.edited_attributes:orival,newval=entity_oldnewvalue(entity,attr)ifnewval!=origval:errors[attr]=session._("can't change the %s attribute")% \display_name(session,attr)iferrors:raiseValidationError(entity.eid,errors)defbefore_update_eetype(session,entity):"""check name change, handle final"""check_valid_changes(session,entity,ro_attrs=('final',))# don't use getattr(entity, attr), we would get the modified value if anyoldname,newname=entity_oldnewvalue(entity,'name')ifnewname.lower()!=oldname.lower():SourceDbCWETypeRename(session,oldname=oldname,newname=newname)MemSchemaCWETypeRename(session,oldname=oldname,newname=newname)defbefore_update_ertype(session,entity):"""check name change, handle final"""check_valid_changes(session,entity)defafter_update_erdef(session,entity):ifentity.eidinsession.transaction_data.get('pendingeids',()):returndesttype=entity.otype.namerschema=session.schema[entity.rtype.name]newvalues={}forpropinrschema.rproperty_defs(desttype):ifprop=='constraints':continueifprop=='order':prop='ordernum'ifpropinentity.edited_attributes:newvalues[prop]=entity[prop]ifnewvalues:subjtype=entity.stype.nameMemSchemaRDefUpdate(session,kobj=(subjtype,desttype),rschema=rschema,values=newvalues)SourceDbRDefUpdate(session,kobj=(subjtype,desttype),rschema=rschema,values=newvalues)defafter_update_ertype(session,entity):rschema=session.schema.rschema(entity.name)newvalues={}forpropin('meta','symetric','inlined'):ifpropinentity:newvalues[prop]=entity[prop]ifnewvalues:MemSchemaCWRTypeUpdate(session,rschema=rschema,values=newvalues)SourceDbCWRTypeUpdate(session,rschema=rschema,values=newvalues,entity=entity)# constraints synchronization hooks ############################################defafter_add_econstraint(session,entity):MemSchemaCWConstraintAdd(session,entity=entity)SourceDbCWConstraintAdd(session,entity=entity)defafter_update_econstraint(session,entity):MemSchemaCWConstraintAdd(session,entity=entity)SourceDbCWConstraintAdd(session,entity=entity)defbefore_delete_constrained_by(session,fromeid,rtype,toeid):ifnotfromeidinsession.transaction_data.get('pendingeids',()):schema=session.schemaentity=session.entity_from_eid(toeid)subjtype,rtype,objtype=schema.schema_by_eid(fromeid)try:cstr=rtype.constraint_by_type(subjtype,objtype,entity.cstrtype[0].name)exceptIndexError:session.critical('constraint type no more accessible')else:SourceDbCWConstraintDel(session,subjtype=subjtype,rtype=rtype,objtype=objtype,cstr=cstr)MemSchemaCWConstraintDel(session,subjtype=subjtype,rtype=rtype,objtype=objtype,cstr=cstr)defafter_add_constrained_by(session,fromeid,rtype,toeid):iffromeidinsession.transaction_data.get('neweids',()):session.transaction_data.setdefault(fromeid,[]).append(toeid)# permissions synchronization hooks ############################################defafter_add_permission(session,subject,rtype,object):"""added entity/relation *_permission, need to update schema"""perm=rtype.split('_',1)[0]ifsession.describe(object)[0]=='CWGroup':MemSchemaPermissionCWGroupAdd(session,perm,subject,object)else:# RQLExpressionexpr=session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',{'x':object},'x')[0][0]MemSchemaPermissionRQLExpressionAdd(session,perm,subject,expr)defbefore_del_permission(session,subject,rtype,object):"""delete entity/relation *_permission, need to update schema skip the operation if the related type is being deleted """ifsubjectinsession.transaction_data.get('pendingeids',()):returnperm=rtype.split('_',1)[0]ifsession.describe(object)[0]=='CWGroup':MemSchemaPermissionCWGroupDel(session,perm,subject,object)else:# RQLExpressionexpr=session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',{'x':object},'x')[0][0]MemSchemaPermissionRQLExpressionDel(session,perm,subject,expr)defafter_add_specializes(session,subject,rtype,object):MemSchemaSpecializesAdd(session,etypeeid=subject,parentetypeeid=object)defafter_del_specializes(session,subject,rtype,object):MemSchemaSpecializesDel(session,etypeeid=subject,parentetypeeid=object)def_register_schema_hooks(hm):"""register schema related hooks on the hooks manager"""# schema synchronisation ###################### before/after addhm.register_hook(before_add_eetype,'before_add_entity','CWEType')hm.register_hook(before_add_ertype,'before_add_entity','CWRType')hm.register_hook(after_add_eetype,'after_add_entity','CWEType')hm.register_hook(after_add_ertype,'after_add_entity','CWRType')hm.register_hook(after_add_efrdef,'after_add_entity','CWAttribute')hm.register_hook(after_add_enfrdef,'after_add_entity','CWRelation')# before/after updatehm.register_hook(before_update_eetype,'before_update_entity','CWEType')hm.register_hook(before_update_ertype,'before_update_entity','CWRType')hm.register_hook(after_update_ertype,'after_update_entity','CWRType')hm.register_hook(after_update_erdef,'after_update_entity','CWAttribute')hm.register_hook(after_update_erdef,'after_update_entity','CWRelation')# before/after deletehm.register_hook(before_del_eetype,'before_delete_entity','CWEType')hm.register_hook(after_del_eetype,'after_delete_entity','CWEType')hm.register_hook(before_del_ertype,'before_delete_entity','CWRType')hm.register_hook(after_del_relation_type,'after_delete_relation','relation_type')hm.register_hook(after_add_specializes,'after_add_relation','specializes')hm.register_hook(after_del_specializes,'after_delete_relation','specializes')# constraints synchronization hookshm.register_hook(after_add_econstraint,'after_add_entity','CWConstraint')hm.register_hook(after_update_econstraint,'after_update_entity','CWConstraint')hm.register_hook(before_delete_constrained_by,'before_delete_relation','constrained_by')hm.register_hook(after_add_constrained_by,'after_add_relation','constrained_by')# permissions synchronisation ################forpermin('read_permission','add_permission','delete_permission','update_permission'):hm.register_hook(after_add_permission,'after_add_relation',perm)hm.register_hook(before_del_permission,'before_delete_relation',perm)