38 |
38 |
39 _UNIQUE_CONSTRAINTS_LOCK = Lock() |
39 _UNIQUE_CONSTRAINTS_LOCK = Lock() |
40 _UNIQUE_CONSTRAINTS_HOLDER = None |
40 _UNIQUE_CONSTRAINTS_HOLDER = None |
41 |
41 |
42 |
42 |
43 def _acquire_unique_cstr_lock(session): |
43 def _acquire_unique_cstr_lock(cnx): |
44 """acquire the _UNIQUE_CONSTRAINTS_LOCK for the session. |
44 """acquire the _UNIQUE_CONSTRAINTS_LOCK for the cnx. |
45 |
45 |
46 This lock used to avoid potential integrity pb when checking |
46 This lock used to avoid potential integrity pb when checking |
47 RQLUniqueConstraint in two different transactions, as explained in |
47 RQLUniqueConstraint in two different transactions, as explained in |
48 http://intranet.logilab.fr/jpl/ticket/36564 |
48 http://intranet.logilab.fr/jpl/ticket/36564 |
49 """ |
49 """ |
50 if 'uniquecstrholder' in session.transaction_data: |
50 if 'uniquecstrholder' in cnx.transaction_data: |
51 return |
51 return |
52 _UNIQUE_CONSTRAINTS_LOCK.acquire() |
52 _UNIQUE_CONSTRAINTS_LOCK.acquire() |
53 session.transaction_data['uniquecstrholder'] = True |
53 cnx.transaction_data['uniquecstrholder'] = True |
54 # register operation responsible to release the lock on commit/rollback |
54 # register operation responsible to release the lock on commit/rollback |
55 _ReleaseUniqueConstraintsOperation(session) |
55 _ReleaseUniqueConstraintsOperation(cnx) |
56 |
56 |
57 def _release_unique_cstr_lock(session): |
57 def _release_unique_cstr_lock(cnx): |
58 if 'uniquecstrholder' in session.transaction_data: |
58 if 'uniquecstrholder' in cnx.transaction_data: |
59 del session.transaction_data['uniquecstrholder'] |
59 del cnx.transaction_data['uniquecstrholder'] |
60 _UNIQUE_CONSTRAINTS_LOCK.release() |
60 _UNIQUE_CONSTRAINTS_LOCK.release() |
61 |
61 |
62 class _ReleaseUniqueConstraintsOperation(hook.Operation): |
62 class _ReleaseUniqueConstraintsOperation(hook.Operation): |
63 def postcommit_event(self): |
63 def postcommit_event(self): |
64 _release_unique_cstr_lock(self.session) |
64 _release_unique_cstr_lock(self.cnx) |
65 def rollback_event(self): |
65 def rollback_event(self): |
66 _release_unique_cstr_lock(self.session) |
66 _release_unique_cstr_lock(self.cnx) |
67 |
67 |
68 |
68 |
69 class _CheckRequiredRelationOperation(hook.DataOperationMixIn, |
69 class _CheckRequiredRelationOperation(hook.DataOperationMixIn, |
70 hook.LateOperation): |
70 hook.LateOperation): |
71 """checking relation cardinality has to be done after commit in case the |
71 """checking relation cardinality has to be done after commit in case the |
73 """ |
73 """ |
74 containercls = list |
74 containercls = list |
75 role = key = base_rql = None |
75 role = key = base_rql = None |
76 |
76 |
77 def precommit_event(self): |
77 def precommit_event(self): |
78 session = self.session |
78 cnx = self.cnx |
79 pendingeids = session.transaction_data.get('pendingeids', ()) |
79 pendingeids = cnx.transaction_data.get('pendingeids', ()) |
80 pendingrtypes = session.transaction_data.get('pendingrtypes', ()) |
80 pendingrtypes = cnx.transaction_data.get('pendingrtypes', ()) |
81 for eid, rtype in self.get_data(): |
81 for eid, rtype in self.get_data(): |
82 # recheck pending eids / relation types |
82 # recheck pending eids / relation types |
83 if eid in pendingeids: |
83 if eid in pendingeids: |
84 continue |
84 continue |
85 if rtype in pendingrtypes: |
85 if rtype in pendingrtypes: |
86 continue |
86 continue |
87 if not session.execute(self.base_rql % rtype, {'x': eid}): |
87 if not cnx.execute(self.base_rql % rtype, {'x': eid}): |
88 etype = session.entity_metas(eid)['type'] |
88 etype = cnx.entity_metas(eid)['type'] |
89 msg = _('at least one relation %(rtype)s is required on ' |
89 msg = _('at least one relation %(rtype)s is required on ' |
90 '%(etype)s (%(eid)s)') |
90 '%(etype)s (%(eid)s)') |
91 raise validation_error(eid, {(rtype, self.role): msg}, |
91 raise validation_error(eid, {(rtype, self.role): msg}, |
92 {'rtype': rtype, 'etype': etype, 'eid': eid}, |
92 {'rtype': rtype, 'etype': etype, 'eid': eid}, |
93 ['rtype', 'etype']) |
93 ['rtype', 'etype']) |
140 |
140 |
141 def __call__(self): |
141 def __call__(self): |
142 rtype = self.rtype |
142 rtype = self.rtype |
143 if rtype in DONT_CHECK_RTYPES_ON_DEL: |
143 if rtype in DONT_CHECK_RTYPES_ON_DEL: |
144 return |
144 return |
145 session = self._cw |
145 cnx = self._cw |
146 eidfrom, eidto = self.eidfrom, self.eidto |
146 eidfrom, eidto = self.eidfrom, self.eidto |
147 rdef = session.rtype_eids_rdef(rtype, eidfrom, eidto) |
147 rdef = cnx.rtype_eids_rdef(rtype, eidfrom, eidto) |
148 if (rdef.subject, rtype, rdef.object) in session.transaction_data.get('pendingrdefs', ()): |
148 if (rdef.subject, rtype, rdef.object) in cnx.transaction_data.get('pendingrdefs', ()): |
149 return |
149 return |
150 card = rdef.cardinality |
150 card = rdef.cardinality |
151 if card[0] in '1+' and not session.deleted_in_transaction(eidfrom): |
151 if card[0] in '1+' and not cnx.deleted_in_transaction(eidfrom): |
152 _CheckSRelationOp.get_instance(session).add_data((eidfrom, rtype)) |
152 _CheckSRelationOp.get_instance(cnx).add_data((eidfrom, rtype)) |
153 if card[1] in '1+' and not session.deleted_in_transaction(eidto): |
153 if card[1] in '1+' and not cnx.deleted_in_transaction(eidto): |
154 _CheckORelationOp.get_instance(session).add_data((eidto, rtype)) |
154 _CheckORelationOp.get_instance(cnx).add_data((eidto, rtype)) |
155 |
155 |
156 |
156 |
157 class CheckCardinalityHookAfterAddEntity(IntegrityHook): |
157 class CheckCardinalityHookAfterAddEntity(IntegrityHook): |
158 """check cardinalities are satisfied""" |
158 """check cardinalities are satisfied""" |
159 __regid__ = 'checkcard_after_add_entity' |
159 __regid__ = 'checkcard_after_add_entity' |
177 |
177 |
178 class _CheckConstraintsOp(hook.DataOperationMixIn, hook.LateOperation): |
178 class _CheckConstraintsOp(hook.DataOperationMixIn, hook.LateOperation): |
179 """ check a new relation satisfy its constraints """ |
179 """ check a new relation satisfy its constraints """ |
180 containercls = list |
180 containercls = list |
181 def precommit_event(self): |
181 def precommit_event(self): |
182 session = self.session |
182 cnx = self.cnx |
183 for values in self.get_data(): |
183 for values in self.get_data(): |
184 eidfrom, rtype, eidto, constraints = values |
184 eidfrom, rtype, eidto, constraints = values |
185 # first check related entities have not been deleted in the same |
185 # first check related entities have not been deleted in the same |
186 # transaction |
186 # transaction |
187 if session.deleted_in_transaction(eidfrom): |
187 if cnx.deleted_in_transaction(eidfrom): |
188 continue |
188 continue |
189 if session.deleted_in_transaction(eidto): |
189 if cnx.deleted_in_transaction(eidto): |
190 continue |
190 continue |
191 for constraint in constraints: |
191 for constraint in constraints: |
192 # XXX |
192 # XXX |
193 # * lock RQLConstraint as well? |
193 # * lock RQLConstraint as well? |
194 # * use a constraint id to use per constraint lock and avoid |
194 # * use a constraint id to use per constraint lock and avoid |
195 # unnecessary commit serialization ? |
195 # unnecessary commit serialization ? |
196 if isinstance(constraint, RQLUniqueConstraint): |
196 if isinstance(constraint, RQLUniqueConstraint): |
197 _acquire_unique_cstr_lock(session) |
197 _acquire_unique_cstr_lock(cnx) |
198 try: |
198 try: |
199 constraint.repo_check(session, eidfrom, rtype, eidto) |
199 constraint.repo_check(cnx, eidfrom, rtype, eidto) |
200 except NotImplementedError: |
200 except NotImplementedError: |
201 self.critical('can\'t check constraint %s, not supported', |
201 self.critical('can\'t check constraint %s, not supported', |
202 constraint) |
202 constraint) |
203 |
203 |
204 |
204 |