[server] fix a number of typos, mostly in docstrings
# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""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"""__docformat__="restructuredtext en"_=unicodefromcopyimportcopyfromyams.schemaimportBASE_TYPES,RelationSchema,RelationDefinitionSchemafromyamsimportbuildobjsasybo,schema2sqlasy2sqlfromlogilab.common.decoratorsimportclear_cachefromcubicwebimportvalidation_errorfromcubicweb.predicatesimportis_instancefromcubicweb.schemaimport(SCHEMA_TYPES,META_RTYPES,VIRTUAL_RTYPES,CONSTRAINTS,ETYPE_NAME_MAP,display_name)fromcubicweb.serverimporthook,schemaserialasssfromcubicweb.server.sqlutilsimportSQL_PREFIXTYPE_CONVERTER={# XXX'Boolean':bool,'Int':int,'BigInt':int,'Float':float,'Password':str,'String':unicode,'Date':unicode,'Datetime':unicode,'Time':unicode,'TZDatetime':unicode,'TZTime':unicode,}# core entity and relation types which can't be removedCORE_TYPES=BASE_TYPES|SCHEMA_TYPES|META_RTYPES|set(('CWUser','CWGroup','login','upassword','name','in_group'))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)returnconstraintsdefgroup_mapping(cw):try:returncw.transaction_data['groupmap']exceptKeyError:cw.transaction_data['groupmap']=gmap=ss.group_mapping(cw)returngmapdefadd_inline_relation_column(session,etype,rtype):"""add necessary column and index for an inlined relation"""attrkey='%s.%s'%(etype,rtype)createdattrs=session.transaction_data.setdefault('createdattrs',set())ifattrkeyincreatedattrs:returncreatedattrs.add(attrkey)table=SQL_PREFIX+etypecolumn=SQL_PREFIX+rtypetry:session.system_sql(str('ALTER TABLE %s ADD %s integer'%(table,column)),rollback_on_failure=False)session.info('added column %s to table %s',column,table)exceptException:# 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.cnxset.source('system').create_index(session,table,column)session.info('added index on %s(%s)',table,column)definsert_rdef_on_subclasses(session,eschema,rschema,rdefdef,props):# XXX 'infered': True/False, not clear actuallyprops.update({'constraints':rdefdef.constraints,'description':rdefdef.description,'cardinality':rdefdef.cardinality,'permissions':rdefdef.get_permissions(),'order':rdefdef.order,'infered':False,'eid':None})cstrtypemap=ss.cstrtype_mapping(session)groupmap=group_mapping(session)object=rschema.schema.eschema(rdefdef.object)forspecializationineschema.specialized_by(False):if(specialization,rdefdef.object)inrschema.rdefs:continuesperdef=RelationDefinitionSchema(specialization,rschema,object,props)ss.execschemarql(session.execute,sperdef,ss.rdef2rql(sperdef,cstrtypemap,groupmap))defcheck_valid_changes(session,entity,ro_attrs=('name','final')):errors={}# don't use getattr(entity, attr), we would get the modified value if anyforattrinentity.cw_edited:ifattrinro_attrs:origval,newval=entity.cw_edited.oldnewvalue(attr)ifnewval!=origval:errors[attr]=_("can't change this attribute")iferrors:raisevalidation_error(entity,errors)class_MockEntity(object):# XXX use a named tuple with python 2.6def__init__(self,eid):self.eid=eidclassSyncSchemaHook(hook.Hook):"""abstract class for schema synchronization hooks (in the `syncschema` category) """__abstract__=Truecategory='syncschema'# operations for low-level database alteration ################################classDropTable(hook.Operation):"""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)# XXX revertprecommit_eventclassDropRelationTable(DropTable):def__init__(self,session,rtype):super(DropRelationTable,self).__init__(session,table='%s_relation'%rtype)session.transaction_data.setdefault('pendingrtypes',set()).add(rtype)classDropColumn(hook.Operation):"""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.columnsource=session.repo.system_source# drop index if anysource.drop_index(session,table,column)ifsource.dbhelper.alter_column_support: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)else:# not supported by sqlite for instanceself.error('dropping column not supported by the backend, handle ''it yourself (%s.%s)',table,column)# XXX revertprecommit_event# base operations for in-memory schema synchronization ########################classMemSchemaNotifyChanges(hook.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):hook.SingleLastOperation.__init__(self,session)defprecommit_event(self):foreschemainself.session.repo.schema.entities():ifnoteschema.final:clear_cache(eschema,'ordered_relations')defpostcommit_event(self):rebuildinfered=self.session.data.get('rebuild-infered',True)repo=self.session.repo# commit event should not raise error, while set_schema has chances to# do so because it triggers full vreg reloadingtry:ifrebuildinfered:repo.schema.rebuild_infered_relations()# trigger vreg reloadrepo.set_schema(repo.schema)# CWUser class might have changed, update current session userscwuser_cls=self.session.vreg['etypes'].etype_class('CWUser')forsessioninrepo._sessions.itervalues():session.user.__class__=cwuser_clsexceptException:self.critical('error while setting schema',exc_info=True)defrollback_event(self):self.precommit_event()classMemSchemaOperation(hook.Operation):"""base class for schema operations"""def__init__(self,session,**kwargs):hook.Operation.__init__(self,session,**kwargs)# every schema operation is triggering a schema updateMemSchemaNotifyChanges(session)# operations for high-level source database alteration ########################classCWETypeAddOp(MemSchemaOperation):"""after adding a CWEType entity: * add it to the instance's schema * create the necessary table * set creation_date and modification_date by creating the necessary CWAttribute entities * add <meta rtype> relation by creating the necessary CWRelation entity """entity=None# make pylint happydefprecommit_event(self):session=self.sessionentity=self.entityschema=session.vreg.schemaetype=ybo.EntityType(eid=entity.eid,name=entity.name,description=entity.description)eschema=schema.add_entity_type(etype)# create the necessary tabletablesql=y2sql.eschema2sql(session.cnxset.source('system').dbhelper,eschema,prefix=SQL_PREFIX)forsqlintablesql.split(';'):ifsql.strip():session.system_sql(sql)# add meta relationsgmap=group_mapping(session)cmap=ss.cstrtype_mapping(session)forrtypein(META_RTYPES-VIRTUAL_RTYPES):try:rschema=schema[rtype]exceptKeyError:self.critical('rtype %s was not handled at cwetype creation time',rtype)continueifnotrschema.rdefs:self.warning('rtype %s has no relation definition yet',rtype)continuesampletype=rschema.subjects()[0]desttype=rschema.objects()[0]try:rdef=copy(rschema.rdef(sampletype,desttype))exceptKeyError:# this combo does not exist because this is not a universal META_RTYPEcontinuerdef.subject=_MockEntity(eid=entity.eid)mock=_MockEntity(eid=None)ss.execschemarql(session.execute,mock,ss.rdef2rql(rdef,cmap,gmap))defrevertprecommit_event(self):# revert changes on in memory schemaself.session.vreg.schema.del_entity_type(self.entity.name)# revert changes on databaseself.session.system_sql('DROP TABLE %s%s'%(SQL_PREFIX,self.entity.name))classCWETypeRenameOp(MemSchemaOperation):"""this operation updates physical storage accordingly"""oldname=newname=None# make pylint happydefrename(self,oldname,newname):self.session.vreg.schema.rename_entity_type(oldname,newname)# we need sql to operate physical changes on the system databasesqlexec=self.session.system_sqldbhelper=self.session.cnxset.source('system').dbhelpersql=dbhelper.sql_rename_table(SQL_PREFIX+oldname,SQL_PREFIX+newname)sqlexec(sql)self.info('renamed table %s to %s',oldname,newname)sqlexec('UPDATE entities SET type=%(newname)s WHERE type=%(oldname)s',{'newname':newname,'oldname':oldname})foreid,(etype,uri,extid,auri)inself.session.repo._type_source_cache.items():ifetype==oldname:self.session.repo._type_source_cache[eid]=(newname,uri,extid,auri)sqlexec('UPDATE deleted_entities SET type=%(newname)s WHERE type=%(oldname)s',{'newname':newname,'oldname':oldname})# XXX transaction recordsdefprecommit_event(self):self.rename(self.oldname,self.newname)defrevertprecommit_event(self):self.rename(self.newname,self.oldname)classCWRTypeUpdateOp(MemSchemaOperation):"""actually update some properties of a relation definition"""rschema=entity=values=None# make pylint happyoldvalus=Nonedefprecommit_event(self):rschema=self.rschemaifrschema.final:return# watched changes to final relation type are unexpectedsession=self.sessionif'fulltext_container'inself.values:op=UpdateFTIndexOp.get_instance(session)forsubjtype,objtypeinrschema.rdefs:op.add_data(subjtype)op.add_data(objtype)# update the in-memory schema firstself.oldvalues=dict((attr,getattr(rschema,attr))forattrinself.values)self.rschema.__dict__.update(self.values)# then make necessary changes to the system source databaseifnot'inlined'inself.values:return# nothing to doinlined=self.values['inlined']# check in-lining is possible when inlinedifinlined:self.entity.check_inlined_allowed()# 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=y2sql.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 columns#if session.repo.system_source.dbhelper.alter_column_support:foretypeinrschema.subjects():DropColumn(session,table=SQL_PREFIX+str(etype),column=SQL_PREFIX+rtype)else:foretypeinrschema.subjects():try:add_inline_relation_column(session,str(etype),rtype)exceptExceptionasex:# the column probably already exists. this occurs when the# entity's type has just been added or if the column has not# been previously dropped (eg sqlite)self.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)defrevertprecommit_event(self):# revert changes on in memory schemaself.rschema.__dict__.update(self.oldvalues)# XXX revert changes on databaseclassCWAttributeAddOp(MemSchemaOperation):"""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.styperdefdef=self.rdefdef=ybo.RelationDefinition(str(fromentity.name),entity.rtype.name,str(entity.otype.name),description=entity.description,cardinality=entity.cardinality,constraints=get_constraints(self.session,entity),order=entity.ordernum,eid=entity.eid,**kwargs)self.session.vreg.schema.add_relation_def(rdefdef)self.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})returnrdefdefdefprecommit_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}# update the in-memory schema firstrdefdef=self.init_rdef(**props)# then make necessary changes to the system source databasesyssource=session.cnxset.source('system')attrtype=y2sql.type_from_constraints(syssource.dbhelper,rdefdef.object,rdefdef.constraints)# XXX should be moved somehow into lgdb: sqlite doesn't support to# add a new column with UNIQUE, it should be added after the ALTER TABLE# using ADD INDEXifsyssource.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+rdefdef.subjectcolumn=SQL_PREFIX+rdefdef.nametry:session.system_sql(str('ALTER TABLE %s ADD %s%s'%(table,column,attrtype)),rollback_on_failure=False)self.info('added column %s to table %s',table,column)exceptExceptionasex:# 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:syssource.create_index(session,table,column,unique=extra_unique_index)exceptExceptionasex:self.error('error while creating index for %s.%s: %s',table,column,ex)# final relations are not infered, propagateschema=session.vreg.schematry:eschema=schema.eschema(rdefdef.subject)exceptKeyError:return# entity type currently being added# propagate attribute to children classesrschema=schema.rschema(rdefdef.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=Trueinsert_rdef_on_subclasses(session,eschema,rschema,rdefdef,props)# set default value, using sql for performance and to avoid# modification_date updateifdefault:ifrdefdef.objectin('Date','Datetime','TZDatetime'):# XXX may may want to use creation_dateifdefault=='TODAY':default=syssource.dbhelper.sql_current_date()elifdefault=='NOW':default=syssource.dbhelper.sql_current_timestamp()session.system_sql('UPDATE %s SET %s=%s'%(table,column,default))else:session.system_sql('UPDATE %s SET %s=%%(default)s'%(table,column),{'default':default})defrevertprecommit_event(self):# revert changes on in memory schemaifgetattr(self,'rdefdef',None)isNone:returnself.session.vreg.schema.del_relation_def(self.rdefdef.subject,self.rdefdef.name,self.rdefdef.object)# XXX revert changes on databaseclassCWRelationAddOp(CWAttributeAddOp):"""an actual relation has been added: * add the relation definition to the instance's schema * 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 constraints are handled by specific hooks """entity=None# make pylint happydefprecommit_event(self):session=self.sessionentity=self.entity# update the in-memory schema firstrdefdef=self.init_rdef(composite=entity.composite)# then make necessary changes to the system source databaseschema=session.vreg.schemartype=rdefdef.namerschema=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 Somethingiflen(rschema.objects(rdefdef.subject))==1:add_inline_relation_column(session,rdefdef.subject,rtype)eschema=schema[rdefdef.subject]insert_rdef_on_subclasses(session,eschema,rschema,rdefdef,{'composite':entity.composite})else:ifrschema.symmetric:# for symmetric relations, rdefs will store relation definitions# in both ways (i.e. (subj -> obj) and (obj -> subj))relation_already_defined=len(rschema.rdefs)>2else:relation_already_defined=len(rschema.rdefs)>1# 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(relation_already_definedorrtypeinsession.transaction_data.get('createdtables',())):rschema=schema.rschema(rtype)# create the necessary tableforsqliny2sql.rschema2sql(rschema).split(';'):ifsql.strip():session.system_sql(sql)session.transaction_data.setdefault('createdtables',[]).append(rtype)# XXX revertprecommit_eventclassRDefDelOp(MemSchemaOperation):"""an actual relation has been removed"""rdef=None# make pylint happydefprecommit_event(self):session=self.sessionrdef=self.rdefrschema=rdef.rtype# make necessary changes to the system source database firstrdeftype=rschema.finaland'CWAttribute'or'CWRelation'execute=session.executerset=execute('Any COUNT(X) WHERE X is %s, X relation_type R,''R eid %%(x)s'%rdeftype,{'x':rschema.eid})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 %%(r)s, X from_entity E, E eid %%(e)s'%rdeftype,{'r':rschema.eid,'e':rdef.subject.eid})ifrset[0][0]==0andnotsession.deleted_in_transaction(rdef.subject.eid):ptypes=session.transaction_data.setdefault('pendingrtypes',set())ptypes.add(rschema.type)DropColumn(session,table=SQL_PREFIX+str(rdef.subject),column=SQL_PREFIX+str(rschema))eliflastrel:DropRelationTable(session,str(rschema))# then update the in-memory schemaifrdef.subjectnotinETYPE_NAME_MAPandrdef.objectnotinETYPE_NAME_MAP:rschema.del_relation_def(rdef.subject,rdef.object)# if this is the last relation definition of this type, drop associated# relation typeiflastrelandnotsession.deleted_in_transaction(rschema.eid):execute('DELETE CWRType X WHERE X eid %(x)s',{'x':rschema.eid})defrevertprecommit_event(self):# revert changes on in memory schema## Note: add_relation_def takes a RelationDefinition, not a# RelationDefinitionSchema, needs to fake itrdef=self.rdefrdef.name=str(rdef.rtype)ifrdef.subjectnotinETYPE_NAME_MAPandrdef.objectnotinETYPE_NAME_MAP:self.session.vreg.schema.add_relation_def(rdef)classRDefUpdateOp(MemSchemaOperation):"""actually update some properties of a relation definition"""rschema=rdefkey=values=None# make pylint happyrdef=oldvalues=Noneindexed_changed=null_allowed_changed=Falsedefprecommit_event(self):session=self.sessionrdef=self.rdef=self.rschema.rdefs[self.rdefkey]# update the in-memory schema firstself.oldvalues=dict((attr,getattr(rdef,attr))forattrinself.values)rdef.update(self.values)# then make necessary changes to the system source databasesyssource=session.cnxset.source('system')if'indexed'inself.values:syssource.update_rdef_indexed(session,rdef)self.indexed_changed=Trueif'cardinality'inself.valuesand(rdef.rtype.finalorrdef.rtype.inlined) \andself.values['cardinality'][0]!=self.oldvalues['cardinality'][0]:syssource.update_rdef_null_allowed(self.session,rdef)self.null_allowed_changed=Trueif'fulltextindexed'inself.values:UpdateFTIndexOp.get_instance(session).add_data(rdef.subject)defrevertprecommit_event(self):ifself.rdefisNone:return# revert changes on in memory schemaself.rdef.update(self.oldvalues)# revert changes on databasesyssource=self.session.cnxset.source('system')ifself.indexed_changed:syssource.update_rdef_indexed(self.session,self.rdef)ifself.null_allowed_changed:syssource.update_rdef_null_allowed(self.session,self.rdef)def_set_modifiable_constraints(rdef):# for proper in-place modification of in-memory schema: if rdef.constraints# is already a list, reuse it (we're updating multiple constraints of the# same rdef in the same transactions)ifnotisinstance(rdef.constraints,list):rdef.constraints=list(rdef.constraints)classCWConstraintDelOp(MemSchemaOperation):"""actually remove a constraint of a relation definition"""rdef=oldcstr=newcstr=None# make pylint happysize_cstr_changed=unique_changed=Falsedefprecommit_event(self):session=self.sessionrdef=self.rdef# in-place modification of in-memory schema first_set_modifiable_constraints(rdef)rdef.constraints.remove(self.oldcstr)# then update database: alter the physical schema on size/unique# constraint changessyssource=session.cnxset.source('system')cstrtype=self.oldcstr.type()ifcstrtype=='SizeConstraint':syssource.update_rdef_column(session,rdef)self.size_cstr_changed=Trueelifcstrtype=='UniqueConstraint':syssource.update_rdef_unique(session,rdef)self.unique_changed=Truedefrevertprecommit_event(self):# revert changes on in memory schemaifself.newcstrisnotNone:self.rdef.constraints.remove(self.newcstr)ifself.oldcstrisnotNone:self.rdef.constraints.append(self.oldcstr)# revert changes on databasesyssource=self.session.cnxset.source('system')ifself.size_cstr_changed:syssource.update_rdef_column(self.session,self.rdef)ifself.unique_changed:syssource.update_rdef_unique(self.session,self.rdef)classCWConstraintAddOp(CWConstraintDelOp):"""actually update constraint of a relation definition"""entity=None# make pylint happydefprecommit_event(self):session=self.sessionrdefentity=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 hereifsession.added_in_transaction(rdefentity.eid):returnrdef=self.rdef=session.vreg.schema.schema_by_eid(rdefentity.eid)cstrtype=self.entity.typeoldcstr=self.oldcstr=rdef.constraint_by_type(cstrtype)newcstr=self.newcstr=CONSTRAINTS[cstrtype].deserialize(self.entity.value)# in-place modification of in-memory schema first_set_modifiable_constraints(rdef)newcstr.eid=self.entity.eidifoldcstrisnotNone:rdef.constraints.remove(oldcstr)rdef.constraints.append(newcstr)# then update database: alter the physical schema on size/unique# constraint changessyssource=session.cnxset.source('system')ifcstrtype=='SizeConstraint'and(oldcstrisNoneoroldcstr.max!=newcstr.max):syssource.update_rdef_column(session,rdef)self.size_cstr_changed=Trueelifcstrtype=='UniqueConstraint'andoldcstrisNone:syssource.update_rdef_unique(session,rdef)self.unique_changed=TrueclassCWUniqueTogetherConstraintAddOp(MemSchemaOperation):entity=None# make pylint happydefprecommit_event(self):session=self.sessionprefix=SQL_PREFIXtable='%s%s'%(prefix,self.entity.constraint_of[0].name)cols=['%s%s'%(prefix,r.name)forrinself.entity.relations]dbhelper=session.cnxset.source('system').dbhelpersqls=dbhelper.sqls_create_multicol_unique_index(table,cols)forsqlinsqls:session.system_sql(sql)# XXX revertprecommit_eventdefpostcommit_event(self):eschema=self.session.vreg.schema.schema_by_eid(self.entity.constraint_of[0].eid)attrs=[r.nameforrinself.entity.relations]eschema._unique_together.append(attrs)classCWUniqueTogetherConstraintDelOp(MemSchemaOperation):entity=oldcstr=None# for pylintcols=[]# for pylintdefprecommit_event(self):session=self.sessionprefix=SQL_PREFIXtable='%s%s'%(prefix,self.entity.type)dbhelper=session.cnxset.source('system').dbhelpercols=['%s%s'%(prefix,c)forcinself.cols]sqls=dbhelper.sqls_drop_multicol_unique_index(table,cols)forsqlinsqls:try:session.system_sql(sql)exceptExceptionasexc:# should be ProgrammingErrorifsql.startswith('DROP'):self.error('execute of `%s` failed (cause: %s)',sql,exc)continueraise# XXX revertprecommit_eventdefpostcommit_event(self):eschema=self.session.vreg.schema.schema_by_eid(self.entity.eid)cols=set(self.cols)unique_together=[utforutineschema._unique_togetherifset(ut)!=cols]eschema._unique_together=unique_together# operations for in-memory schema synchronization #############################classMemSchemaCWETypeDel(MemSchemaOperation):"""actually remove the entity type from the instance's schema"""etype=None# make pylint happydefpostcommit_event(self):# del_entity_type also removes entity's relationsself.session.vreg.schema.del_entity_type(self.etype)classMemSchemaCWRTypeAdd(MemSchemaOperation):"""actually add the relation type to the instance's schema"""rtypedef=None# make pylint happydefprecommit_event(self):self.session.vreg.schema.add_relation_type(self.rtypedef)defrevertprecommit_event(self):self.session.vreg.schema.del_relation_type(self.rtypedef.name)classMemSchemaCWRTypeDel(MemSchemaOperation):"""actually remove the relation type from the instance's schema"""rtype=None# make pylint happydefpostcommit_event(self):try:self.session.vreg.schema.del_relation_type(self.rtype)exceptKeyError:# s/o entity type have already been deletedpassclassMemSchemaPermissionAdd(MemSchemaOperation):"""synchronize schema when a *_permission relation has been added on a group """eid=action=group_eid=expr=None# make pylint happydefprecommit_event(self):"""the observed connections.cnxset has been commited"""try:erschema=self.session.vreg.schema.schema_by_eid(self.eid)exceptKeyError:# duh, schema not found, log error and skip operationself.warning('no schema for %s',self.eid)returnperms=list(erschema.action_permissions(self.action))ifself.group_eidisnotNone:perm=self.session.entity_from_eid(self.group_eid).nameelse:perm=erschema.rql_expression(self.expr)try:perms.index(perm)self.warning('%s already in permissions for %s on %s',perm,self.action,erschema)exceptValueError:perms.append(perm)erschema.set_action_permissions(self.action,perms)# XXX revertprecommit_eventclassMemSchemaPermissionDel(MemSchemaPermissionAdd):"""synchronize schema when a *_permission relation has been deleted from a group """defprecommit_event(self):"""the observed connections set has been commited"""try:erschema=self.session.vreg.schema.schema_by_eid(self.eid)exceptKeyError:# duh, schema not found, log error and skip operationself.warning('no schema for %s',self.eid)returnifisinstance(erschema,RelationSchema):# XXX 3.6 migrationreturnifisinstance(erschema,RelationDefinitionSchema)and \self.actionin('delete','add'):# XXX 3.6.1 migrationreturnperms=list(erschema.action_permissions(self.action))ifself.group_eidisnotNone:perm=self.session.entity_from_eid(self.group_eid).nameelse:perm=erschema.rql_expression(self.expr)try:perms.remove(perm)erschema.set_action_permissions(self.action,perms)exceptValueError:self.error('can\'t remove permission %s for %s on %s',perm,self.action,erschema)# XXX revertprecommit_eventclassMemSchemaSpecializesAdd(MemSchemaOperation):etypeeid=parentetypeeid=None# make pylint happydefprecommit_event(self):eschema=self.session.vreg.schema.schema_by_eid(self.etypeeid)parenteschema=self.session.vreg.schema.schema_by_eid(self.parentetypeeid)eschema._specialized_type=parenteschema.typeparenteschema._specialized_by.append(eschema.type)# XXX revertprecommit_eventclassMemSchemaSpecializesDel(MemSchemaOperation):etypeeid=parentetypeeid=None# make pylint happydefprecommit_event(self):try:eschema=self.session.vreg.schema.schema_by_eid(self.etypeeid)parenteschema=self.session.vreg.schema.schema_by_eid(self.parentetypeeid)exceptKeyError:# etype removed, nothing to doreturneschema._specialized_type=Noneparenteschema._specialized_by.remove(eschema.type)# XXX revertprecommit_event# CWEType hooks ################################################################classDelCWETypeHook(SyncSchemaHook):"""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 """__regid__='syncdelcwetype'__select__=SyncSchemaHook.__select__&is_instance('CWEType')events=('before_delete_entity',)def__call__(self):# final entities can't be deleted, don't care about thatname=self.entity.nameifnameinCORE_TYPES:raisevalidation_error(self.entity,{None:_("can't be deleted")})# delete every entities of this typeifnamenotinETYPE_NAME_MAP:self._cw.execute('DELETE %s X'%name)MemSchemaCWETypeDel(self._cw,etype=name)DropTable(self._cw,table=SQL_PREFIX+name)classAfterDelCWETypeHook(DelCWETypeHook):__regid__='wfcleanup'events=('after_delete_entity',)def__call__(self):# workflow cleanupself._cw.execute('DELETE Workflow X WHERE NOT X workflow_of Y')classAfterAddCWETypeHook(DelCWETypeHook):"""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 """__regid__='syncaddcwetype'events=('after_add_entity',)def__call__(self):entity=self.entityifentity.cw_edited.get('final'):# final entity types don't need a table in the database and are# systematically added by yams at schema initialization time so# there is no need to do further processing. Simply assign its eid.self._cw.vreg.schema[entity.name].eid=entity.eidreturnCWETypeAddOp(self._cw,entity=entity)classBeforeUpdateCWETypeHook(DelCWETypeHook):"""check name change, handle final"""__regid__='syncupdatecwetype'events=('before_update_entity',)def__call__(self):entity=self.entitycheck_valid_changes(self._cw,entity,ro_attrs=('final',))# don't use getattr(entity, attr), we would get the modified value if anyif'name'inentity.cw_edited:oldname,newname=entity.cw_edited.oldnewvalue('name')ifnewname.lower()!=oldname.lower():CWETypeRenameOp(self._cw,oldname=oldname,newname=newname)# CWRType hooks ################################################################classDelCWRTypeHook(SyncSchemaHook):"""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 """__regid__='syncdelcwrtype'__select__=SyncSchemaHook.__select__&is_instance('CWRType')events=('before_delete_entity',)def__call__(self):name=self.entity.nameifnameinCORE_TYPES:raisevalidation_error(self.entity,{None:_("can't be deleted")})# delete relation definitions using this relation typeself._cw.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',{'x':self.entity.eid})self._cw.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s',{'x':self.entity.eid})MemSchemaCWRTypeDel(self._cw,rtype=name)classAfterAddCWRTypeHook(DelCWRTypeHook):"""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 yet this point if a table is necessary """__regid__='syncaddcwrtype'events=('after_add_entity',)def__call__(self):entity=self.entityrtypedef=ybo.RelationType(name=entity.name,description=entity.description,inlined=entity.cw_edited.get('inlined',False),symmetric=entity.cw_edited.get('symmetric',False),eid=entity.eid)MemSchemaCWRTypeAdd(self._cw,rtypedef=rtypedef)classBeforeUpdateCWRTypeHook(DelCWRTypeHook):"""check name change, handle final"""__regid__='syncupdatecwrtype'events=('before_update_entity',)def__call__(self):entity=self.entitycheck_valid_changes(self._cw,entity)newvalues={}forpropin('symmetric','inlined','fulltext_container'):ifpropinentity.cw_edited:old,new=entity.cw_edited.oldnewvalue(prop)ifold!=new:newvalues[prop]=newifnewvalues:rschema=self._cw.vreg.schema.rschema(entity.name)CWRTypeUpdateOp(self._cw,rschema=rschema,entity=entity,values=newvalues)classAfterDelRelationTypeHook(SyncSchemaHook):"""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 """__regid__='syncdelrelationtype'__select__=SyncSchemaHook.__select__&hook.match_rtype('relation_type')events=('after_delete_relation',)def__call__(self):session=self._cwtry:rdef=session.vreg.schema.schema_by_eid(self.eidfrom)exceptKeyError:self.critical('cant get schema rdef associated to %s',self.eidfrom)returnsubjschema,rschema,objschema=rdef.as_triple()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(session.deleted_in_transaction(subjschema.eid)orsession.deleted_in_transaction(objschema.eid)):session.execute('DELETE X %s Y WHERE X is %s, Y is %s'%(rschema,subjschema,objschema))RDefDelOp(session,rdef=rdef)# CWAttribute / CWRelation hooks ###############################################classAfterAddCWAttributeHook(SyncSchemaHook):__regid__='syncaddcwattribute'__select__=SyncSchemaHook.__select__&is_instance('CWAttribute')events=('after_add_entity',)def__call__(self):CWAttributeAddOp(self._cw,entity=self.entity)classAfterAddCWRelationHook(AfterAddCWAttributeHook):__regid__='syncaddcwrelation'__select__=SyncSchemaHook.__select__&is_instance('CWRelation')def__call__(self):CWRelationAddOp(self._cw,entity=self.entity)classAfterUpdateCWRDefHook(SyncSchemaHook):__regid__='syncaddcwattribute'__select__=SyncSchemaHook.__select__&is_instance('CWAttribute','CWRelation')events=('before_update_entity',)def__call__(self):entity=self.entityifself._cw.deleted_in_transaction(entity.eid):returnsubjtype=entity.stype.nameobjtype=entity.otype.nameifsubjtypeinETYPE_NAME_MAPorobjtypeinETYPE_NAME_MAP:returnrschema=self._cw.vreg.schema[entity.rtype.name]# note: do not access schema rdef here, it may be added later by an# operationnewvalues={}forpropinRelationDefinitionSchema.rproperty_defs(objtype):ifprop=='constraints':continueifprop=='order':attr='ordernum'else:attr=propifattrinentity.cw_edited:old,new=entity.cw_edited.oldnewvalue(attr)ifold!=new:newvalues[prop]=newifnewvalues:RDefUpdateOp(self._cw,rschema=rschema,rdefkey=(subjtype,objtype),values=newvalues)# constraints synchronization hooks ############################################classAfterAddCWConstraintHook(SyncSchemaHook):__regid__='syncaddcwconstraint'__select__=SyncSchemaHook.__select__&is_instance('CWConstraint')events=('after_add_entity','after_update_entity')def__call__(self):CWConstraintAddOp(self._cw,entity=self.entity)classAfterAddConstrainedByHook(SyncSchemaHook):__regid__='syncaddconstrainedby'__select__=SyncSchemaHook.__select__&hook.match_rtype('constrained_by')events=('after_add_relation',)def__call__(self):ifself._cw.added_in_transaction(self.eidfrom):# used by get_constraints() which is called in CWAttributeAddOpself._cw.transaction_data.setdefault(self.eidfrom,[]).append(self.eidto)classBeforeDeleteConstrainedByHook(SyncSchemaHook):__regid__='syncdelconstrainedby'__select__=SyncSchemaHook.__select__&hook.match_rtype('constrained_by')events=('before_delete_relation',)def__call__(self):ifself._cw.deleted_in_transaction(self.eidfrom):returnschema=self._cw.vreg.schemaentity=self._cw.entity_from_eid(self.eidto)rdef=schema.schema_by_eid(self.eidfrom)try:cstr=rdef.constraint_by_type(entity.type)exceptIndexError:self._cw.critical('constraint type no more accessible')else:CWConstraintDelOp(self._cw,rdef=rdef,oldcstr=cstr)# unique_together constraints# XXX: use setoperations and before_add_relation here (on constraint_of and relations)classAfterAddCWUniqueTogetherConstraintHook(SyncSchemaHook):__regid__='syncadd_cwuniquetogether_constraint'__select__=SyncSchemaHook.__select__&is_instance('CWUniqueTogetherConstraint')events=('after_add_entity',)def__call__(self):CWUniqueTogetherConstraintAddOp(self._cw,entity=self.entity)classBeforeDeleteConstraintOfHook(SyncSchemaHook):__regid__='syncdelconstraintof'__select__=SyncSchemaHook.__select__&hook.match_rtype('constraint_of')events=('before_delete_relation',)def__call__(self):ifself._cw.deleted_in_transaction(self.eidto):returnschema=self._cw.vreg.schemacstr=self._cw.entity_from_eid(self.eidfrom)entity=schema.schema_by_eid(self.eidto)cols=[r.nameforrincstr.relations]CWUniqueTogetherConstraintDelOp(self._cw,entity=entity,oldcstr=cstr,cols=cols)# permissions synchronization hooks ############################################classAfterAddPermissionHook(SyncSchemaHook):"""added entity/relation *_permission, need to update schema"""__regid__='syncaddperm'__select__=SyncSchemaHook.__select__&hook.match_rtype('read_permission','add_permission','delete_permission','update_permission')events=('after_add_relation',)def__call__(self):action=self.rtype.split('_',1)[0]ifself._cw.describe(self.eidto)[0]=='CWGroup':MemSchemaPermissionAdd(self._cw,action=action,eid=self.eidfrom,group_eid=self.eidto)else:# RQLExpressionexpr=self._cw.entity_from_eid(self.eidto).expressionMemSchemaPermissionAdd(self._cw,action=action,eid=self.eidfrom,expr=expr)classBeforeDelPermissionHook(AfterAddPermissionHook):"""delete entity/relation *_permission, need to update schema skip the operation if the related type is being deleted """__regid__='syncdelperm'events=('before_delete_relation',)def__call__(self):ifself._cw.deleted_in_transaction(self.eidfrom):returnaction=self.rtype.split('_',1)[0]ifself._cw.describe(self.eidto)[0]=='CWGroup':MemSchemaPermissionDel(self._cw,action=action,eid=self.eidfrom,group_eid=self.eidto)else:# RQLExpressionexpr=self._cw.entity_from_eid(self.eidto).expressionMemSchemaPermissionDel(self._cw,action=action,eid=self.eidfrom,expr=expr)classUpdateFTIndexOp(hook.DataOperationMixIn,hook.SingleLastOperation):"""operation to update full text indexation of entity whose schema change We wait after the commit to as the schema in memory is only updated after the commit. """defpostcommit_event(self):session=self.sessionsource=session.repo.system_sourceschema=session.repo.vreg.schemato_reindex=self.get_data()self.info('%i etypes need full text indexed reindexation',len(to_reindex))foretypeinto_reindex:rset=session.execute('Any X WHERE X is %s'%etype)self.info('Reindexing full text index for %i entity of type %s',len(rset),etype)still_fti=list(schema[etype].indexable_attributes())forentityinrset.entities():source.fti_unindex_entities(session,[entity])forcontainerinentity.cw_adapt_to('IFTIndexable').fti_containers():ifstill_ftiorcontainerisnotentity:source.fti_unindex_entities(session,[container])source.fti_index_entities(session,[container])ifto_reindex:# Transaction has already been committedsession.cnxset.commit()# specializes synchronization hooks ############################################classAfterAddSpecializesHook(SyncSchemaHook):__regid__='syncaddspecializes'__select__=SyncSchemaHook.__select__&hook.match_rtype('specializes')events=('after_add_relation',)def__call__(self):MemSchemaSpecializesAdd(self._cw,etypeeid=self.eidfrom,parentetypeeid=self.eidto)classAfterDelSpecializesHook(SyncSchemaHook):__regid__='syncdelspecializes'__select__=SyncSchemaHook.__select__&hook.match_rtype('specializes')events=('after_delete_relation',)def__call__(self):MemSchemaSpecializesDel(self._cw,etypeeid=self.eidfrom,parentetypeeid=self.eidto)