386 |
386 |
387 Holds all connection related data |
387 Holds all connection related data |
388 |
388 |
389 Database connection resources: |
389 Database connection resources: |
390 |
390 |
391 :attr:`running_dbapi_query`, boolean flag telling if the executing query |
391 :attr:`hooks_in_progress`, boolean flag telling if the executing |
392 is coming from a dbapi connection or is a query from within the repository |
392 query is coming from a repoapi connection or is a query from |
|
393 within the repository (e.g. started by hooks) |
393 |
394 |
394 :attr:`cnxset`, the connections set to use to execute queries on sources. |
395 :attr:`cnxset`, the connections set to use to execute queries on sources. |
395 If the transaction is read only, the connection set may be freed between |
396 If the transaction is read only, the connection set may be freed between |
396 actual queries. This allows multiple connections with a reasonably low |
397 actual queries. This allows multiple connections with a reasonably low |
397 connection set pool size. Control mechanism is detailed below. |
398 connection set pool size. Control mechanism is detailed below. |
443 read/write security is currently activated. |
444 read/write security is currently activated. |
444 |
445 |
445 """ |
446 """ |
446 |
447 |
447 is_request = False |
448 is_request = False |
|
449 hooks_in_progress = False |
448 mode = 'read' |
450 mode = 'read' |
449 |
451 |
450 def __init__(self, session, cnxid=None, session_handled=False): |
452 def __init__(self, session, cnxid=None, session_handled=False): |
451 # using super(Connection, self) confuse some test hack |
453 # using super(Connection, self) confuse some test hack |
452 RequestSessionBase.__init__(self, session.vreg) |
454 RequestSessionBase.__init__(self, session.vreg) |
482 |
484 |
483 #: connection set used to execute queries on sources |
485 #: connection set used to execute queries on sources |
484 self._cnxset = None |
486 self._cnxset = None |
485 #: CnxSetTracker used to report cnxset usage |
487 #: CnxSetTracker used to report cnxset usage |
486 self._cnxset_tracker = CnxSetTracker() |
488 self._cnxset_tracker = CnxSetTracker() |
487 #: is this connection from a client or internal to the repo |
|
488 self.running_dbapi_query = True |
|
489 # internal (root) session |
489 # internal (root) session |
490 self.is_internal_session = isinstance(session.user, InternalManager) |
490 self.is_internal_session = isinstance(session.user, InternalManager) |
491 |
491 |
492 #: dict containing arbitrary data cleared at the end of the transaction |
492 #: dict containing arbitrary data cleared at the end of the transaction |
493 self.transaction_data = {} |
493 self.transaction_data = {} |
537 assert self._open # actually already open |
537 assert self._open # actually already open |
538 assert self._cnxset_count == 0 |
538 assert self._cnxset_count == 0 |
539 self.rollback() |
539 self.rollback() |
540 self._open = False |
540 self._open = False |
541 |
541 |
542 |
542 @contextmanager |
|
543 def running_hooks_ops(self): |
|
544 """this context manager should be called whenever hooks or operations |
|
545 are about to be run (but after hook selection) |
|
546 |
|
547 It will help the undo logic record pertinent metadata or some |
|
548 hooks to run (or not) depending on who/what issued the query. |
|
549 """ |
|
550 prevmode = self.hooks_in_progress |
|
551 self.hooks_in_progress = True |
|
552 yield |
|
553 self.hooks_in_progress = prevmode |
543 |
554 |
544 # shared data handling ################################################### |
555 # shared data handling ################################################### |
545 |
556 |
546 @property |
557 @property |
547 def data(self): |
558 def data(self): |
941 return self._read_security |
952 return self._read_security |
942 |
953 |
943 @read_security.setter |
954 @read_security.setter |
944 @_open_only |
955 @_open_only |
945 def read_security(self, activated): |
956 def read_security(self, activated): |
946 oldmode = self._read_security |
|
947 self._read_security = activated |
957 self._read_security = activated |
948 # running_dbapi_query used to detect hooks triggered by a 'dbapi' query |
|
949 # (eg not issued on the session). This is tricky since we the execution |
|
950 # model of a (write) user query is: |
|
951 # |
|
952 # repository.execute (security enabled) |
|
953 # \-> querier.execute |
|
954 # \-> repo.glob_xxx (add/update/delete entity/relation) |
|
955 # \-> deactivate security before calling hooks |
|
956 # \-> WE WANT TO CHECK QUERY NATURE HERE |
|
957 # \-> potentially, other calls to querier.execute |
|
958 # |
|
959 # so we can't rely on simply checking session.read_security, but |
|
960 # recalling the first transition from DEFAULT_SECURITY to something |
|
961 # else (False actually) is not perfect but should be enough |
|
962 # |
|
963 # also reset running_dbapi_query to true when we go back to |
|
964 # DEFAULT_SECURITY |
|
965 self.running_dbapi_query = (oldmode is DEFAULT_SECURITY |
|
966 or activated is DEFAULT_SECURITY) |
|
967 |
958 |
968 # undo support ############################################################ |
959 # undo support ############################################################ |
969 |
960 |
970 @_open_only |
961 @_open_only |
971 def ertype_supports_undo(self, ertype): |
962 def ertype_supports_undo(self, ertype): |
1096 processed = [] |
1087 processed = [] |
1097 self.commit_state = 'precommit' |
1088 self.commit_state = 'precommit' |
1098 if debug: |
1089 if debug: |
1099 print self.commit_state, '*' * 20 |
1090 print self.commit_state, '*' * 20 |
1100 try: |
1091 try: |
1101 while self.pending_operations: |
1092 with self.running_hooks_ops(): |
1102 operation = self.pending_operations.pop(0) |
1093 while self.pending_operations: |
1103 operation.processed = 'precommit' |
1094 operation = self.pending_operations.pop(0) |
1104 processed.append(operation) |
1095 operation.processed = 'precommit' |
1105 if debug: |
1096 processed.append(operation) |
1106 print operation |
1097 if debug: |
1107 operation.handle_event('precommit_event') |
1098 print operation |
|
1099 operation.handle_event('precommit_event') |
1108 self.pending_operations[:] = processed |
1100 self.pending_operations[:] = processed |
1109 self.debug('precommit transaction %s done', self.connectionid) |
1101 self.debug('precommit transaction %s done', self.connectionid) |
1110 except BaseException: |
1102 except BaseException: |
1111 # if error on [pre]commit: |
1103 # if error on [pre]commit: |
1112 # |
1104 # |
1119 # instead of having to implements rollback, revertprecommit |
1111 # instead of having to implements rollback, revertprecommit |
1120 # and revertcommit, that will be enough in mont case. |
1112 # and revertcommit, that will be enough in mont case. |
1121 operation.failed = True |
1113 operation.failed = True |
1122 if debug: |
1114 if debug: |
1123 print self.commit_state, '*' * 20 |
1115 print self.commit_state, '*' * 20 |
1124 for operation in reversed(processed): |
1116 with self.running_hooks_ops(): |
1125 if debug: |
1117 for operation in reversed(processed): |
1126 print operation |
1118 if debug: |
1127 try: |
1119 print operation |
1128 operation.handle_event('revertprecommit_event') |
1120 try: |
1129 except BaseException: |
1121 operation.handle_event('revertprecommit_event') |
1130 self.critical('error while reverting precommit', |
1122 except BaseException: |
1131 exc_info=True) |
1123 self.critical('error while reverting precommit', |
|
1124 exc_info=True) |
1132 # XXX use slice notation since self.pending_operations is a |
1125 # XXX use slice notation since self.pending_operations is a |
1133 # read-only property. |
1126 # read-only property. |
1134 self.pending_operations[:] = processed + self.pending_operations |
1127 self.pending_operations[:] = processed + self.pending_operations |
1135 self.rollback(free_cnxset) |
1128 self.rollback(free_cnxset) |
1136 raise |
1129 raise |
1137 self.cnxset.commit() |
1130 self.cnxset.commit() |
1138 self.commit_state = 'postcommit' |
1131 self.commit_state = 'postcommit' |
1139 if debug: |
1132 if debug: |
1140 print self.commit_state, '*' * 20 |
1133 print self.commit_state, '*' * 20 |
1141 while self.pending_operations: |
1134 with self.running_hooks_ops(): |
1142 operation = self.pending_operations.pop(0) |
1135 while self.pending_operations: |
1143 if debug: |
1136 operation = self.pending_operations.pop(0) |
1144 print operation |
1137 if debug: |
1145 operation.processed = 'postcommit' |
1138 print operation |
1146 try: |
1139 operation.processed = 'postcommit' |
1147 operation.handle_event('postcommit_event') |
1140 try: |
1148 except BaseException: |
1141 operation.handle_event('postcommit_event') |
1149 self.critical('error while postcommit', |
1142 except BaseException: |
1150 exc_info=sys.exc_info()) |
1143 self.critical('error while postcommit', |
|
1144 exc_info=sys.exc_info()) |
1151 self.debug('postcommit transaction %s done', self.connectionid) |
1145 self.debug('postcommit transaction %s done', self.connectionid) |
1152 return self.transaction_uuid(set=False) |
1146 return self.transaction_uuid(set=False) |
1153 finally: |
1147 finally: |
1154 self._session_timestamp.touch() |
1148 self._session_timestamp.touch() |
1155 if free_cnxset: |
1149 if free_cnxset: |