hooks/integrity.py
changeset 9613 45370ea9f495
parent 9548 be001628edad
child 9680 8fb8f001f4e2
equal deleted inserted replaced
9612:24460d4d64bf 9613:45370ea9f495
    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