29 from cubicweb.schema import (META_RTYPES, WORKFLOW_RTYPES, |
29 from cubicweb.schema import (META_RTYPES, WORKFLOW_RTYPES, |
30 RQLConstraint, RQLUniqueConstraint) |
30 RQLConstraint, RQLUniqueConstraint) |
31 from cubicweb.selectors import is_instance |
31 from cubicweb.selectors import is_instance |
32 from cubicweb.uilib import soup2xhtml |
32 from cubicweb.uilib import soup2xhtml |
33 from cubicweb.server import hook |
33 from cubicweb.server import hook |
34 from cubicweb.server.hook import set_operation |
|
35 |
34 |
36 # special relations that don't have to be checked for integrity, usually |
35 # special relations that don't have to be checked for integrity, usually |
37 # because they are handled internally by hooks (so we trust ourselves) |
36 # because they are handled internally by hooks (so we trust ourselves) |
38 DONT_CHECK_RTYPES_ON_ADD = META_RTYPES | WORKFLOW_RTYPES |
37 DONT_CHECK_RTYPES_ON_ADD = META_RTYPES | WORKFLOW_RTYPES |
39 DONT_CHECK_RTYPES_ON_DEL = META_RTYPES | WORKFLOW_RTYPES |
38 DONT_CHECK_RTYPES_ON_DEL = META_RTYPES | WORKFLOW_RTYPES |
66 _release_unique_cstr_lock(self.session) |
65 _release_unique_cstr_lock(self.session) |
67 def rollback_event(self): |
66 def rollback_event(self): |
68 _release_unique_cstr_lock(self.session) |
67 _release_unique_cstr_lock(self.session) |
69 |
68 |
70 |
69 |
71 class _CheckRequiredRelationOperation(hook.LateOperation): |
70 class _CheckRequiredRelationOperation(hook.DataOperationMixIn, |
72 """checking relation cardinality has to be done after commit in |
71 hook.LateOperation): |
73 case the relation is being replaced |
72 """checking relation cardinality has to be done after commit in case the |
74 """ |
73 relation is being replaced |
|
74 """ |
|
75 containercls = list |
75 role = key = base_rql = None |
76 role = key = base_rql = None |
76 |
77 |
77 def precommit_event(self): |
78 def precommit_event(self): |
78 session =self.session |
79 session = self.session |
79 pendingeids = session.transaction_data.get('pendingeids', ()) |
80 pendingeids = session.transaction_data.get('pendingeids', ()) |
80 pendingrtypes = session.transaction_data.get('pendingrtypes', ()) |
81 pendingrtypes = session.transaction_data.get('pendingrtypes', ()) |
81 # poping key is not optional: if further operation trigger new deletion |
82 # poping key is not optional: if further operation trigger new deletion |
82 # of relation, we'll need a new operation |
83 # of relation, we'll need a new operation |
83 for eid, rtype in session.transaction_data.pop(self.key): |
84 for eid, rtype in self.get_data(): |
84 # recheck pending eids / relation types |
85 # recheck pending eids / relation types |
85 if eid in pendingeids: |
86 if eid in pendingeids: |
86 continue |
87 continue |
87 if rtype in pendingrtypes: |
88 if rtype in pendingrtypes: |
88 continue |
89 continue |
96 |
97 |
97 |
98 |
98 class _CheckSRelationOp(_CheckRequiredRelationOperation): |
99 class _CheckSRelationOp(_CheckRequiredRelationOperation): |
99 """check required subject relation""" |
100 """check required subject relation""" |
100 role = 'subject' |
101 role = 'subject' |
101 key = '_cwisrel' |
|
102 base_rql = 'Any O WHERE S eid %%(x)s, S %s O' |
102 base_rql = 'Any O WHERE S eid %%(x)s, S %s O' |
103 |
103 |
104 class _CheckORelationOp(_CheckRequiredRelationOperation): |
104 class _CheckORelationOp(_CheckRequiredRelationOperation): |
105 """check required object relation""" |
105 """check required object relation""" |
106 role = 'object' |
106 role = 'object' |
107 key = '_cwiorel' |
|
108 base_rql = 'Any S WHERE O eid %%(x)s, S %s O' |
107 base_rql = 'Any S WHERE O eid %%(x)s, S %s O' |
109 |
108 |
110 |
109 |
111 class IntegrityHook(hook.Hook): |
110 class IntegrityHook(hook.Hook): |
112 __abstract__ = True |
111 __abstract__ = True |
129 if rschema.type in DONT_CHECK_RTYPES_ON_ADD: |
128 if rschema.type in DONT_CHECK_RTYPES_ON_ADD: |
130 continue |
129 continue |
131 rdef = rschema.role_rdef(eschema, targetschemas[0], role) |
130 rdef = rschema.role_rdef(eschema, targetschemas[0], role) |
132 if rdef.role_cardinality(role) in '1+': |
131 if rdef.role_cardinality(role) in '1+': |
133 if role == 'subject': |
132 if role == 'subject': |
134 set_operation(self._cw, '_cwisrel', (eid, rschema.type), |
133 op = _CheckSRelationOp.get_instance(self._cw) |
135 _CheckSRelationOp, list) |
|
136 else: |
134 else: |
137 set_operation(self._cw, '_cwiorel', (eid, rschema.type), |
135 op = _CheckORelationOp.get_instance(self._cw) |
138 _CheckORelationOp, list) |
136 op.add_data((eid, rschema.type)) |
139 |
137 |
140 def before_delete_relation(self): |
138 def before_delete_relation(self): |
141 rtype = self.rtype |
139 rtype = self.rtype |
142 if rtype in DONT_CHECK_RTYPES_ON_DEL: |
140 if rtype in DONT_CHECK_RTYPES_ON_DEL: |
143 return |
141 return |
146 pendingrdefs = session.transaction_data.get('pendingrdefs', ()) |
144 pendingrdefs = session.transaction_data.get('pendingrdefs', ()) |
147 if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs: |
145 if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs: |
148 return |
146 return |
149 card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality') |
147 card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality') |
150 if card[0] in '1+' and not session.deleted_in_transaction(eidfrom): |
148 if card[0] in '1+' and not session.deleted_in_transaction(eidfrom): |
151 set_operation(self._cw, '_cwisrel', (eidfrom, rtype), |
149 _CheckSRelationOp.get_instance(self._cw).add_data((eidfrom, rtype)) |
152 _CheckSRelationOp, list) |
|
153 if card[1] in '1+' and not session.deleted_in_transaction(eidto): |
150 if card[1] in '1+' and not session.deleted_in_transaction(eidto): |
154 set_operation(self._cw, '_cwiorel', (eidto, rtype), |
151 _CheckORelationOp.get_instance(self._cw).add_data((eidto, rtype)) |
155 _CheckORelationOp, list) |
152 |
156 |
153 |
157 |
154 class _CheckConstraintsOp(hook.DataOperationMixIn, hook.LateOperation): |
158 class _CheckConstraintsOp(hook.LateOperation): |
|
159 """ check a new relation satisfy its constraints """ |
155 """ check a new relation satisfy its constraints """ |
160 |
156 containercls = list |
161 def precommit_event(self): |
157 def precommit_event(self): |
162 session = self.session |
158 session = self.session |
163 for values in session.transaction_data.pop('check_constraints_op'): |
159 for values in self.get_data(): |
164 eidfrom, rtype, eidto, constraints = values |
160 eidfrom, rtype, eidto, constraints = values |
165 # first check related entities have not been deleted in the same |
161 # first check related entities have not been deleted in the same |
166 # transaction |
162 # transaction |
167 if session.deleted_in_transaction(eidfrom): |
163 if session.deleted_in_transaction(eidfrom): |
168 return |
164 return |
194 def __call__(self): |
190 def __call__(self): |
195 # XXX get only RQL[Unique]Constraints? |
191 # XXX get only RQL[Unique]Constraints? |
196 constraints = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto, |
192 constraints = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto, |
197 'constraints') |
193 'constraints') |
198 if constraints: |
194 if constraints: |
199 hook.set_operation(self._cw, 'check_constraints_op', |
195 _CheckConstraintsOp.get_instance(self._cw).add_data( |
200 (self.eidfrom, self.rtype, self.eidto, tuple(constraints)), |
196 (self.eidfrom, self.rtype, self.eidto, constraints)) |
201 _CheckConstraintsOp, list) |
|
202 |
197 |
203 |
198 |
204 class CheckAttributeConstraintHook(IntegrityHook): |
199 class CheckAttributeConstraintHook(IntegrityHook): |
205 """check the attribute relation satisfy its constraints |
200 """check the attribute relation satisfy its constraints |
206 |
201 |
215 for attr in self.entity.cw_edited: |
210 for attr in self.entity.cw_edited: |
216 if eschema.subjrels[attr].final: |
211 if eschema.subjrels[attr].final: |
217 constraints = [c for c in eschema.rdef(attr).constraints |
212 constraints = [c for c in eschema.rdef(attr).constraints |
218 if isinstance(c, (RQLUniqueConstraint, RQLConstraint))] |
213 if isinstance(c, (RQLUniqueConstraint, RQLConstraint))] |
219 if constraints: |
214 if constraints: |
220 hook.set_operation(self._cw, 'check_constraints_op', |
215 _CheckConstraintsOp.get_instance(self._cw).add_data( |
221 (self.entity.eid, attr, None, tuple(constraints)), |
216 (self.entity.eid, attr, None, constraints)) |
222 _CheckConstraintsOp, list) |
|
223 |
217 |
224 |
218 |
225 class CheckUniqueHook(IntegrityHook): |
219 class CheckUniqueHook(IntegrityHook): |
226 __regid__ = 'checkunique' |
220 __regid__ = 'checkunique' |
227 events = ('before_add_entity', 'before_update_entity') |
221 events = ('before_add_entity', 'before_update_entity') |
295 |
289 |
296 |
290 |
297 # 'active' integrity hooks: you usually don't want to deactivate them, they are |
291 # 'active' integrity hooks: you usually don't want to deactivate them, they are |
298 # not really integrity check, they maintain consistency on changes |
292 # not really integrity check, they maintain consistency on changes |
299 |
293 |
300 class _DelayedDeleteOp(hook.Operation): |
294 class _DelayedDeleteOp(hook.DataOperationMixIn, hook.Operation): |
301 """delete the object of composite relation except if the relation has |
295 """delete the object of composite relation except if the relation has |
302 actually been redirected to another composite |
296 actually been redirected to another composite |
303 """ |
297 """ |
304 key = base_rql = None |
298 base_rql = None |
305 |
299 |
306 def precommit_event(self): |
300 def precommit_event(self): |
307 session = self.session |
301 session = self.session |
308 pendingeids = session.transaction_data.get('pendingeids', ()) |
302 pendingeids = session.transaction_data.get('pendingeids', ()) |
309 neweids = session.transaction_data.get('neweids', ()) |
303 neweids = session.transaction_data.get('neweids', ()) |
310 # poping key is not optional: if further operation trigger new deletion |
304 # poping key is not optional: if further operation trigger new deletion |
311 # of composite relation, we'll need a new operation |
305 # of composite relation, we'll need a new operation |
312 for eid, rtype in session.transaction_data.pop(self.key): |
306 for eid, rtype in self.get_data(): |
313 # don't do anything if the entity is being created or deleted |
307 # don't do anything if the entity is being created or deleted |
314 if not (eid in pendingeids or eid in neweids): |
308 if not (eid in pendingeids or eid in neweids): |
315 etype = session.describe(eid)[0] |
309 etype = session.describe(eid)[0] |
316 session.execute(self.base_rql % (etype, rtype), {'x': eid}) |
310 session.execute(self.base_rql % (etype, rtype), {'x': eid}) |
317 |
311 |
318 class _DelayedDeleteSEntityOp(_DelayedDeleteOp): |
312 class _DelayedDeleteSEntityOp(_DelayedDeleteOp): |
319 """delete orphan subject entity of a composite relation""" |
313 """delete orphan subject entity of a composite relation""" |
320 key = '_cwiscomp' |
|
321 base_rql = 'DELETE %s X WHERE X eid %%(x)s, NOT X %s Y' |
314 base_rql = 'DELETE %s X WHERE X eid %%(x)s, NOT X %s Y' |
322 |
315 |
323 class _DelayedDeleteOEntityOp(_DelayedDeleteOp): |
316 class _DelayedDeleteOEntityOp(_DelayedDeleteOp): |
324 """check required object relation""" |
317 """check required object relation""" |
325 key = '_cwiocomp' |
|
326 base_rql = 'DELETE %s X WHERE X eid %%(x)s, NOT Y %s X' |
318 base_rql = 'DELETE %s X WHERE X eid %%(x)s, NOT Y %s X' |
327 |
319 |
328 |
320 |
329 class DeleteCompositeOrphanHook(hook.Hook): |
321 class DeleteCompositeOrphanHook(hook.Hook): |
330 """delete the composed of a composite relation when this relation is deleted |
322 """delete the composed of a composite relation when this relation is deleted |
341 self._cw.describe(self.eidto)[0]) in pendingrdefs: |
333 self._cw.describe(self.eidto)[0]) in pendingrdefs: |
342 return |
334 return |
343 composite = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto, |
335 composite = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto, |
344 'composite') |
336 'composite') |
345 if composite == 'subject': |
337 if composite == 'subject': |
346 set_operation(self._cw, '_cwiocomp', (self.eidto, self.rtype), |
338 _DelayedDeleteOEntityOp.get_instance(self._cw).add_data( |
347 _DelayedDeleteOEntityOp) |
339 (self.eidto, self.rtype)) |
348 elif composite == 'object': |
340 elif composite == 'object': |
349 set_operation(self._cw, '_cwiscomp', (self.eidfrom, self.rtype), |
341 _DelayedDeleteSEntityOp.get_instance(self._cw).add_data( |
350 _DelayedDeleteSEntityOp) |
342 (self.eidfrom, self.rtype)) |