[repo] optimize massive insertion/deletion by using the new set_operation function
Idea is that on massive insertion, cost of handling the list of operation
become non negligeable, so we should minimize the number of operations in
that list.
The set_operation function ease usage of operation associated to data in
session.transaction_data, and we only add the operation when data set isn't
initialized yet, else we simply add data to the set. The operation then
simply process accumulated data.
"""Core hooks: synchronize living session on persistent data changes:organization: Logilab:copyright: 2001-2010 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.schemaimportrole_namefromcubicwebimportUnknownProperty,ValidationError,BadConnectionIdfromcubicweb.selectorsimportimplementsfromcubicweb.serverimporthookdefget_user_sessions(repo,ueid):forsessioninrepo._sessions.values():ifueid==session.user.eid:yieldsessionclassSyncSessionHook(hook.Hook):__abstract__=Truecategory='syncsession'# user/groups synchronisation #################################################class_GroupOperation(hook.Operation):"""base class for group operation"""geid=Nonedef__init__(self,session,*args,**kwargs):"""override to get the group name before actual groups manipulation: we may temporarily loose right access during a commit event, so no query should be emitted while comitting """rql='Any N WHERE G eid %(x)s, G name N'result=session.execute(rql,{'x':kwargs['geid']},'x',build_descr=False)hook.Operation.__init__(self,session,*args,**kwargs)self.group=result[0][0]class_DeleteGroupOp(_GroupOperation):"""synchronize user when a in_group relation has been deleted"""defcommit_event(self):"""the observed connections pool has been commited"""groups=self.cnxuser.groupstry:groups.remove(self.group)exceptKeyError:self.error('user %s not in group %s',self.cnxuser,self.group)returnclass_AddGroupOp(_GroupOperation):"""synchronize user when a in_group relation has been added"""defcommit_event(self):"""the observed connections pool has been commited"""groups=self.cnxuser.groupsifself.groupingroups:self.warning('user %s already in group %s',self.cnxuser,self.group)returngroups.add(self.group)classSyncInGroupHook(SyncSessionHook):__regid__='syncingroup'__select__=SyncSessionHook.__select__&hook.match_rtype('in_group')events=('after_delete_relation','after_add_relation')def__call__(self):ifself.event=='after_delete_relation':opcls=_DeleteGroupOpelse:opcls=_AddGroupOpforsessioninget_user_sessions(self._cw.repo,self.eidfrom):opcls(self._cw,cnxuser=session.user,geid=self.eidto)class_DelUserOp(hook.Operation):"""close associated user's session when it is deleted"""def__init__(self,session,cnxid):self.cnxid=cnxidhook.Operation.__init__(self,session)defcommit_event(self):"""the observed connections pool has been commited"""try:self.session.repo.close(self.cnxid)exceptBadConnectionId:pass# already closedclassCloseDeletedUserSessionsHook(SyncSessionHook):__regid__='closession'__select__=SyncSessionHook.__select__&implements('CWUser')events=('after_delete_entity',)def__call__(self):"""modify user permission, need to update users"""forsessioninget_user_sessions(self._cw.repo,self.entity.eid):_DelUserOp(self._cw,session.id)# CWProperty hooks #############################################################class_DelCWPropertyOp(hook.Operation):"""a user's custom properties has been deleted"""defcommit_event(self):"""the observed connections pool has been commited"""try:delself.cwpropdict[self.key]exceptKeyError:self.error('%s has no associated value',self.key)class_ChangeCWPropertyOp(hook.Operation):"""a user's custom properties has been added/changed"""defcommit_event(self):"""the observed connections pool has been commited"""self.cwpropdict[self.key]=self.valueclass_AddCWPropertyOp(hook.Operation):"""a user's custom properties has been added/changed"""defcommit_event(self):"""the observed connections pool has been commited"""cwprop=self.cwpropifnotcwprop.for_user:self.session.vreg['propertyvalues'][cwprop.pkey]=cwprop.value# if for_user is set, update is handled by a ChangeCWPropertyOp operationclassAddCWPropertyHook(SyncSessionHook):__regid__='addcwprop'__select__=SyncSessionHook.__select__&implements('CWProperty')events=('after_add_entity',)def__call__(self):key,value=self.entity.pkey,self.entity.valuesession=self._cwtry:value=session.vreg.typed_value(key,value)exceptUnknownProperty:qname=role_name('pkey','subject')raiseValidationError(self.entity.eid,{qname:session._('unknown property key')})exceptValueError,ex:qname=role_name('value','subject')raiseValidationError(self.entity.eid,{qname:session._(str(ex))})ifnotsession.user.matching_groups('managers'):session.add_relation(self.entity.eid,'for_user',session.user.eid)else:_AddCWPropertyOp(session,cwprop=self.entity)classUpdateCWPropertyHook(AddCWPropertyHook):__regid__='updatecwprop'events=('after_update_entity',)def__call__(self):entity=self.entityifnot('pkey'inentity.edited_attributesor'value'inentity.edited_attributes):returnkey,value=entity.pkey,entity.valuesession=self._cwtry:value=session.vreg.typed_value(key,value)exceptUnknownProperty:returnexceptValueError,ex:qname=role_name('value','subject')raiseValidationError(entity.eid,{qname:session._(str(ex))})ifentity.for_user:forsession_inget_user_sessions(session.repo,entity.for_user[0].eid):_ChangeCWPropertyOp(session,cwpropdict=session_.user.properties,key=key,value=value)else:# site wide properties_ChangeCWPropertyOp(session,cwpropdict=session.vreg['propertyvalues'],key=key,value=value)classDeleteCWPropertyHook(AddCWPropertyHook):__regid__='delcwprop'events=('before_delete_entity',)def__call__(self):eid=self.entity.eidsession=self._cwforeidfrom,rtype,eidtoinsession.transaction_data.get('pendingrelations',()):ifrtype=='for_user'andeidfrom==self.entity.eid:# if for_user was set, delete has already been handledbreakelse:_DelCWPropertyOp(session,cwpropdict=session.vreg['propertyvalues'],key=self.entity.pkey)classAddForUserRelationHook(SyncSessionHook):__regid__='addcwpropforuser'__select__=SyncSessionHook.__select__&hook.match_rtype('for_user')events=('after_add_relation',)def__call__(self):session=self._cweidfrom=self.eidfromifnotsession.describe(eidfrom)[0]=='CWProperty':returnkey,value=session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',{'x':eidfrom},'x')[0]ifsession.vreg.property_info(key)['sitewide']:qname=role_name('for_user','subject')msg=session._("site-wide property can't be set for user")raiseValidationError(eidfrom,{qname:msg})forsession_inget_user_sessions(session.repo,self.eidto):_ChangeCWPropertyOp(session,cwpropdict=session_.user.properties,key=key,value=value)classDelForUserRelationHook(AddForUserRelationHook):__regid__='delcwpropforuser'events=('after_delete_relation',)def__call__(self):session=self._cwkey=session.execute('Any K WHERE P eid %(x)s, P pkey K',{'x':self.eidfrom},'x')[0][0]session.transaction_data.setdefault('pendingrelations',[]).append((self.eidfrom,self.rtype,self.eidto))forsession_inget_user_sessions(session.repo,self.eidto):_DelCWPropertyOp(session,cwpropdict=session_.user.properties,key=key)