hooks/integrity.py
changeset 9548 be001628edad
parent 9469 032825bbacab
child 9613 45370ea9f495
equal deleted inserted replaced
9547:43aace16a953 9548:be001628edad
     1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     3 #
     3 #
     4 # This file is part of CubicWeb.
     4 # This file is part of CubicWeb.
     5 #
     5 #
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
    22 __docformat__ = "restructuredtext en"
    22 __docformat__ = "restructuredtext en"
    23 _ = unicode
    23 _ = unicode
    24 
    24 
    25 from threading import Lock
    25 from threading import Lock
    26 
    26 
    27 from cubicweb import validation_error
    27 from cubicweb import validation_error, neg_role
    28 from cubicweb.schema import (META_RTYPES, WORKFLOW_RTYPES,
    28 from cubicweb.schema import (META_RTYPES, WORKFLOW_RTYPES,
    29                              RQLConstraint, RQLUniqueConstraint)
    29                              RQLConstraint, RQLUniqueConstraint)
    30 from cubicweb.predicates import is_instance
    30 from cubicweb.predicates import is_instance, composite_etype
    31 from cubicweb.uilib import soup2xhtml
    31 from cubicweb.uilib import soup2xhtml
    32 from cubicweb.server import hook
    32 from cubicweb.server import hook
    33 
    33 
    34 # special relations that don't have to be checked for integrity, usually
    34 # special relations that don't have to be checked for integrity, usually
    35 # because they are handled internally by hooks (so we trust ourselves)
    35 # because they are handled internally by hooks (so we trust ourselves)
   307         login = self.entity.cw_edited.get('login')
   307         login = self.entity.cw_edited.get('login')
   308         if login:
   308         if login:
   309             self.entity.cw_edited['login'] = login.strip()
   309             self.entity.cw_edited['login'] = login.strip()
   310 
   310 
   311 
   311 
   312 # 'active' integrity hooks: you usually don't want to deactivate them, they are
       
   313 # not really integrity check, they maintain consistency on changes
       
   314 
       
   315 class _DelayedDeleteOp(hook.DataOperationMixIn, hook.Operation):
       
   316     """delete the object of composite relation except if the relation has
       
   317     actually been redirected to another composite
       
   318     """
       
   319     base_rql = None
       
   320 
       
   321     def precommit_event(self):
       
   322         session = self.session
       
   323         pendingeids = session.transaction_data.get('pendingeids', ())
       
   324         eids_by_etype_rtype = {}
       
   325         for eid, rtype in self.get_data():
       
   326             # don't do anything if the entity is being deleted
       
   327             if eid not in pendingeids:
       
   328                 etype = session.entity_metas(eid)['type']
       
   329                 key = (etype, rtype)
       
   330                 if key not in eids_by_etype_rtype:
       
   331                     eids_by_etype_rtype[key] = [str(eid)]
       
   332                 else:
       
   333                     eids_by_etype_rtype[key].append(str(eid))
       
   334         for (etype, rtype), eids in eids_by_etype_rtype.iteritems():
       
   335             # quite unexpectedly, not deleting too many entities at a time in
       
   336             # this operation benefits to the exec speed (possibly on the RQL
       
   337             # parsing side)
       
   338             start = 0
       
   339             incr = 500
       
   340             while start < len(eids):
       
   341                 session.execute(self.base_rql % (etype, ','.join(eids[start:start+incr]), rtype))
       
   342                 start += incr
       
   343 
       
   344 class _DelayedDeleteSEntityOp(_DelayedDeleteOp):
       
   345     """delete orphan subject entity of a composite relation"""
       
   346     base_rql = 'DELETE %s X WHERE X eid IN (%s), NOT X %s Y'
       
   347 
       
   348 class _DelayedDeleteOEntityOp(_DelayedDeleteOp):
       
   349     """check required object relation"""
       
   350     base_rql = 'DELETE %s X WHERE X eid IN (%s), NOT Y %s X'
       
   351 
       
   352 
       
   353 class DeleteCompositeOrphanHook(hook.Hook):
   312 class DeleteCompositeOrphanHook(hook.Hook):
   354     """delete the composed of a composite relation when this relation is deleted
   313     """Delete the composed of a composite relation when the composite is
       
   314     deleted (this is similar to the cascading ON DELETE CASCADE
       
   315     semantics of sql).
   355     """
   316     """
   356     __regid__ = 'deletecomposite'
   317     __regid__ = 'deletecomposite'
   357     events = ('before_delete_relation',)
   318     __select__ = hook.Hook.__select__ & composite_etype()
       
   319     events = ('before_delete_entity',)
   358     category = 'activeintegrity'
   320     category = 'activeintegrity'
   359 
   321 
   360     def __call__(self):
   322     def __call__(self):
   361         # if the relation is being delete, don't delete composite's components
   323         eid = self.entity.eid
   362         # automatically
   324         for rdef, role in self.entity.e_schema.composite_rdef_roles:
   363         session = self._cw
   325             rtype = rdef.rtype.type
   364         rtype = self.rtype
   326             target = getattr(rdef, neg_role(role))
   365         rdef = session.rtype_eids_rdef(rtype, self.eidfrom, self.eidto)
   327             expr = ('C %s X' % rtype) if role == 'subject' else ('X %s C' % rtype)
   366         if (rdef.subject, rtype, rdef.object) in session.transaction_data.get('pendingrdefs', ()):
   328             self._cw.execute('DELETE %s X WHERE C eid %%(c)s, %s' % (target, expr),
   367             return
   329                              {'c': eid})
   368         composite = rdef.composite
   330 
   369         if composite == 'subject':
       
   370             _DelayedDeleteOEntityOp.get_instance(self._cw).add_data(
       
   371                 (self.eidto, rtype))
       
   372         elif composite == 'object':
       
   373             _DelayedDeleteSEntityOp.get_instance(self._cw).add_data(
       
   374                 (self.eidfrom, rtype))
       
   375 
   331 
   376 def registration_callback(vreg):
   332 def registration_callback(vreg):
   377     vreg.register_all(globals().values(), __name__)
   333     vreg.register_all(globals().values(), __name__)
   378     symmetric_rtypes = [rschema.type for rschema in vreg.schema.relations()
   334     symmetric_rtypes = [rschema.type for rschema in vreg.schema.relations()
   379                         if rschema.symmetric]
   335                         if rschema.symmetric]