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] |