server/session.py
brancholdstable
changeset 8123 a4e667270dd4
parent 7969 d43569aaf5d6
child 7970 83075d897943
--- a/server/session.py	Mon Sep 26 18:37:23 2011 +0200
+++ b/server/session.py	Fri Dec 09 12:08:27 2011 +0100
@@ -61,6 +61,18 @@
         description.append(term.get_type(solution, args))
     return description
 
+def selection_idx_type(i, rqlst, args):
+    """try to return type of term at index `i` of the rqlst's selection"""
+    for select in rqlst.children:
+        term = select.selection[i]
+        for solution in select.solutions:
+            try:
+                ttype = term.get_type(solution, args)
+                if ttype is not None:
+                    return ttype
+            except CoercionError:
+                return None
+
 @objectify_selector
 def is_user_session(cls, req, **kwargs):
     """repository side only selector returning 1 if the session is a regular
@@ -125,21 +137,13 @@
         self.categories = categories
 
     def __enter__(self):
-        self.oldmode = self.session.set_hooks_mode(self.mode)
-        if self.mode is self.session.HOOKS_DENY_ALL:
-            self.changes = self.session.enable_hook_categories(*self.categories)
-        else:
-            self.changes = self.session.disable_hook_categories(*self.categories)
+        self.oldmode, self.changes = self.session.init_hooks_mode_categories(
+            self.mode, self.categories)
 
     def __exit__(self, exctype, exc, traceback):
-        if self.changes:
-            if self.mode is self.session.HOOKS_DENY_ALL:
-                self.session.disable_hook_categories(*self.changes)
-            else:
-                self.session.enable_hook_categories(*self.changes)
-        self.session.set_hooks_mode(self.oldmode)
+        self.session.reset_hooks_mode_categories(self.oldmode, self.mode, self.changes)
 
-INDENT = ''
+
 class security_enabled(object):
     """context manager to control security w/ session.execute, since by
     default security is disabled on queries executed on the repository
@@ -151,33 +155,90 @@
         self.write = write
 
     def __enter__(self):
-#        global INDENT
-        if self.read is not None:
-            self.oldread = self.session.set_read_security(self.read)
-#            print INDENT + 'read', self.read, self.oldread
-        if self.write is not None:
-            self.oldwrite = self.session.set_write_security(self.write)
-#            print INDENT + 'write', self.write, self.oldwrite
-#        INDENT += '  '
+        self.oldread, self.oldwrite = self.session.init_security(
+            self.read, self.write)
 
     def __exit__(self, exctype, exc, traceback):
-#        global INDENT
-#        INDENT = INDENT[:-2]
-        if self.read is not None:
-            self.session.set_read_security(self.oldread)
-#            print INDENT + 'reset read to', self.oldread
-        if self.write is not None:
-            self.session.set_write_security(self.oldwrite)
-#            print INDENT + 'reset write to', self.oldwrite
+        self.session.reset_security(self.oldread, self.oldwrite)
 
 
 class TransactionData(object):
     def __init__(self, txid):
         self.transactionid = txid
+        self.ctx_count = 0
+
 
 class Session(RequestSessionBase):
-    """tie session id, user, connections pool and other session data all
-    together
+    """Repository usersession, tie a session id, user, connections set and
+    other session data all together.
+
+    About session storage / transactions
+    ------------------------------------
+
+    Here is a description of internal session attributes. Besides :attr:`data`
+    and :attr:`transaction_data`, you should not have to use attributes
+    described here but higher level APIs.
+
+      :attr:`data` is a dictionary containing shared data, used to communicate
+      extra information between the client and the repository
+
+      :attr:`_tx_data` is a dictionary of :class:`TransactionData` instance, one
+      for each running transaction. The key is the transaction id. By default
+      the transaction id is the thread name but it can be otherwise (per dbapi
+      cursor for instance, or per thread name *from another process*).
+
+      :attr:`__threaddata` is a thread local storage whose `txdata` attribute
+      refers to the proper instance of :class:`TransactionData` according to the
+      transaction.
+
+      :attr:`_threads_in_transaction` is a set of (thread, connections set)
+      referencing threads that currently hold a connections set for the session.
+
+    You should not have to use neither :attr:`_txdata` nor :attr:`__threaddata`,
+    simply access transaction data transparently through the :attr:`_threaddata`
+    property. Also, you usually don't have to access it directly since current
+    transaction's data may be accessed/modified through properties / methods:
+
+      :attr:`transaction_data`, similarly to :attr:`data`, is a dictionary
+      containing some shared data that should be cleared at the end of the
+      transaction. Hooks and operations may put arbitrary data in there, and
+      this may also be used as a communication channel between the client and
+      the repository.
+
+      :attr:`cnxset`, the connections set to use to execute queries on sources.
+      During a transaction, the connection set may be freed so that is may be
+      used by another session as long as no writing is done. This means we can
+      have multiple sessions with a reasonably low connections set pool size.
+
+      :attr:`mode`, string telling the connections set handling mode, may be one
+      of 'read' (connections set may be freed), 'write' (some write was done in
+      the connections set, it can't be freed before end of the transaction),
+      'transaction' (we want to keep the connections set during all the
+      transaction, with or without writing)
+
+      :attr:`pending_operations`, ordered list of operations to be processed on
+      commit/rollback
+
+      :attr:`commit_state`, describing the transaction commit state, may be one
+      of None (not yet committing), 'precommit' (calling precommit event on
+      operations), 'postcommit' (calling postcommit event on operations),
+      'uncommitable' (some :exc:`ValidationError` or :exc:`Unauthorized` error
+      has been raised during the transaction and so it must be rollbacked).
+
+      :attr:`read_security` and :attr:`write_security`, boolean flags telling if
+      read/write security is currently activated.
+
+      :attr:`hooks_mode`, may be either `HOOKS_ALLOW_ALL` or `HOOKS_DENY_ALL`.
+
+      :attr:`enabled_hook_categories`, when :attr:`hooks_mode` is
+      `HOOKS_DENY_ALL`, this set contains hooks categories that are enabled.
+
+      :attr:`disabled_hook_categories`, when :attr:`hooks_mode` is
+      `HOOKS_ALLOW_ALL`, this set contains hooks categories that are disabled.
+
+
+      :attr:`running_dbapi_query`, boolean flag telling if the executing query
+      is coming from a dbapi connection or is a query from within the repository
     """
     is_internal_session = False
 
@@ -246,7 +307,10 @@
         """return a fake request/session using specified user"""
         session = Session(user, self.repo)
         threaddata = session._threaddata
-        threaddata.pool = self.pool
+        threaddata.cnxset = self.cnxset
+        # we attributed a connections set, need to update ctx_count else it will be freed
+        # while undesired
+        threaddata.ctx_count = 1
         # share pending_operations, else operation added in the hi-jacked
         # session such as SendMailOp won't ever be processed
         threaddata.pending_operations = self.pending_operations
@@ -388,14 +452,14 @@
         """return a sql cursor on the system database"""
         if sql.split(None, 1)[0].upper() != 'SELECT':
             self.mode = 'write'
-        source = self.pool.source('system')
+        source = self.cnxset.source('system')
         try:
             return source.doexec(self, sql, args, rollback=rollback_on_failure)
         except (source.OperationalError, source.InterfaceError):
             if not rollback_on_failure:
                 raise
             source.warning("trying to reconnect")
-            self.pool.reconnect(source)
+            self.cnxset.reconnect(source)
             return source.doexec(self, sql, args, rollback=rollback_on_failure)
 
     def set_language(self, language):
@@ -446,6 +510,29 @@
     def security_enabled(self, read=False, write=False):
         return security_enabled(self, read=read, write=write)
 
+    def init_security(self, read, write):
+        if read is None:
+            oldread = None
+        else:
+            oldread = self.set_read_security(read)
+        if write is None:
+            oldwrite = None
+        else:
+            oldwrite = self.set_write_security(write)
+        self._threaddata.ctx_count += 1
+        return oldread, oldwrite
+
+    def reset_security(self, read, write):
+        txstore = self._threaddata
+        txstore.ctx_count -= 1
+        if txstore.ctx_count == 0:
+            self._clear_thread_storage(txstore)
+        else:
+            if read is not None:
+                self.set_read_security(read)
+            if write is not None:
+                self.set_write_security(write)
+
     @property
     def read_security(self):
         """return a boolean telling if read security is activated or not"""
@@ -498,7 +585,7 @@
             return self.DEFAULT_SECURITY
         try:
             return txstore.write_security
-        except:
+        except AttributeError:
             txstore.write_security = self.DEFAULT_SECURITY
             return txstore.write_security
 
@@ -546,6 +633,30 @@
         self._threaddata.hooks_mode = mode
         return oldmode
 
+    def init_hooks_mode_categories(self, mode, categories):
+        oldmode = self.set_hooks_mode(mode)
+        if mode is self.HOOKS_DENY_ALL:
+            changes = self.enable_hook_categories(*categories)
+        else:
+            changes = self.disable_hook_categories(*categories)
+        self._threaddata.ctx_count += 1
+        return oldmode, changes
+
+    def reset_hooks_mode_categories(self, oldmode, mode, categories):
+        txstore = self._threaddata
+        txstore.ctx_count -= 1
+        if txstore.ctx_count == 0:
+            self._clear_thread_storage(txstore)
+        else:
+            try:
+                if categories:
+                    if mode is self.HOOKS_DENY_ALL:
+                        return self.disable_hook_categories(*categories)
+                    else:
+                        return self.enable_hook_categories(*categories)
+            finally:
+                self.set_hooks_mode(oldmode)
+
     @property
     def disabled_hook_categories(self):
         try:
@@ -569,17 +680,18 @@
         - on HOOKS_ALLOW_ALL mode, ensure those categories are disabled
         """
         changes = set()
+        self.pruned_hooks_cache.clear()
         if self.hooks_mode is self.HOOKS_DENY_ALL:
-            enablecats = self.enabled_hook_categories
+            enabledcats = self.enabled_hook_categories
             for category in categories:
-                if category in enablecats:
-                    enablecats.remove(category)
+                if category in enabledcats:
+                    enabledcats.remove(category)
                     changes.add(category)
         else:
-            disablecats = self.disabled_hook_categories
+            disabledcats = self.disabled_hook_categories
             for category in categories:
-                if category not in disablecats:
-                    disablecats.add(category)
+                if category not in disabledcats:
+                    disabledcats.add(category)
                     changes.add(category)
         return tuple(changes)
 
@@ -590,17 +702,18 @@
         - on HOOKS_ALLOW_ALL mode, ensure those categories are not disabled
         """
         changes = set()
+        self.pruned_hooks_cache.clear()
         if self.hooks_mode is self.HOOKS_DENY_ALL:
-            enablecats = self.enabled_hook_categories
+            enabledcats = self.enabled_hook_categories
             for category in categories:
-                if category not in enablecats:
-                    enablecats.add(category)
+                if category not in enabledcats:
+                    enabledcats.add(category)
                     changes.add(category)
         else:
-            disablecats = self.disabled_hook_categories
+            disabledcats = self.disabled_hook_categories
             for category in categories:
-                if category in self.disabled_hook_categories:
-                    disablecats.remove(category)
+                if category in disabledcats:
+                    disabledcats.remove(category)
                     changes.add(category)
         return tuple(changes)
 
@@ -620,19 +733,19 @@
 
     # connection management ###################################################
 
-    def keep_pool_mode(self, mode):
-        """set pool_mode, e.g. how the session will keep its pool:
+    def keep_cnxset_mode(self, mode):
+        """set `mode`, e.g. how the session will keep its connections set:
 
-        * if mode == 'write', the pool is freed after each ready query, but kept
-          until the transaction's end (eg commit or rollback) when a write query
-          is detected (eg INSERT/SET/DELETE queries)
+        * if mode == 'write', the connections set is freed after each ready
+          query, but kept until the transaction's end (eg commit or rollback)
+          when a write query is detected (eg INSERT/SET/DELETE queries)
 
-        * if mode == 'transaction', the pool is only freed after the
+        * if mode == 'transaction', the connections set is only freed after the
           transaction's end
 
-        notice that a repository has a limited set of pools, and a session has to
-        wait for a free pool to run any rql query (unless it already has a pool
-        set).
+        notice that a repository has a limited set of connections sets, and a
+        session has to wait for a free connections set to run any rql query
+        (unless it already has one set).
         """
         assert mode in ('transaction', 'write')
         if mode == 'transaction':
@@ -655,56 +768,58 @@
     commit_state = property(get_commit_state, set_commit_state)
 
     @property
-    def pool(self):
-        """connections pool, set according to transaction mode for each query"""
+    def cnxset(self):
+        """connections set, set according to transaction mode for each query"""
         if self._closed:
-            self.reset_pool(True)
-            raise Exception('try to access pool on a closed session')
-        return getattr(self._threaddata, 'pool', None)
+            self.free_cnxset(True)
+            raise Exception('try to access connections set on a closed session %s' % self.id)
+        return getattr(self._threaddata, 'cnxset', None)
 
-    def set_pool(self):
-        """the session need a pool to execute some queries"""
+    def set_cnxset(self):
+        """the session need a connections set to execute some queries"""
         with self._closed_lock:
             if self._closed:
-                self.reset_pool(True)
-                raise Exception('try to set pool on a closed session')
-            if self.pool is None:
-                # get pool first to avoid race-condition
-                self._threaddata.pool = pool = self.repo._get_pool()
+                self.free_cnxset(True)
+                raise Exception('try to set connections set on a closed session %s' % self.id)
+            if self.cnxset is None:
+                # get connections set first to avoid race-condition
+                self._threaddata.cnxset = cnxset = self.repo._get_cnxset()
+                self._threaddata.ctx_count += 1
                 try:
-                    pool.pool_set()
-                except:
-                    self._threaddata.pool = None
-                    self.repo._free_pool(pool)
+                    cnxset.cnxset_set()
+                except Exception:
+                    self._threaddata.cnxset = None
+                    self.repo._free_cnxset(cnxset)
                     raise
                 self._threads_in_transaction.add(
-                    (threading.currentThread(), pool) )
-            return self._threaddata.pool
+                    (threading.currentThread(), cnxset) )
+            return self._threaddata.cnxset
 
-    def _free_thread_pool(self, thread, pool, force_close=False):
+    def _free_thread_cnxset(self, thread, cnxset, force_close=False):
         try:
-            self._threads_in_transaction.remove( (thread, pool) )
+            self._threads_in_transaction.remove( (thread, cnxset) )
         except KeyError:
-            # race condition on pool freeing (freed by commit or rollback vs
+            # race condition on cnxset freeing (freed by commit or rollback vs
             # close)
             pass
         else:
             if force_close:
-                pool.reconnect()
+                cnxset.reconnect()
             else:
-                pool.pool_reset()
-            # free pool once everything is done to avoid race-condition
-            self.repo._free_pool(pool)
+                cnxset.cnxset_freed()
+            # free cnxset once everything is done to avoid race-condition
+            self.repo._free_cnxset(cnxset)
 
-    def reset_pool(self, ignoremode=False):
-        """the session is no longer using its pool, at least for some time"""
-        # pool may be none if no operation has been done since last commit
+    def free_cnxset(self, ignoremode=False):
+        """the session is no longer using its connections set, at least for some time"""
+        # cnxset may be none if no operation has been done since last commit
         # or rollback
-        pool = getattr(self._threaddata, 'pool', None)
-        if pool is not None and (ignoremode or self.mode == 'read'):
+        cnxset = getattr(self._threaddata, 'cnxset', None)
+        if cnxset is not None and (ignoremode or self.mode == 'read'):
             # even in read mode, we must release the current transaction
-            self._free_thread_pool(threading.currentThread(), pool)
-            del self._threaddata.pool
+            self._free_thread_cnxset(threading.currentThread(), cnxset)
+            del self._threaddata.cnxset
+            self._threaddata.ctx_count -= 1
 
     def _touch(self):
         """update latest session usage timestamp and reset mode to read"""
@@ -770,9 +885,13 @@
     def source_defs(self):
         return self.repo.source_defs()
 
-    def describe(self, eid):
+    def describe(self, eid, asdict=False):
         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
-        return self.repo.type_and_source_from_eid(eid, self)
+        metas = self.repo.type_and_source_from_eid(eid, self)
+        if asdict:
+            return dict(zip(('type', 'source', 'extid', 'asource'), metas)) 
+       # XXX :-1 for cw compat, use asdict=True for full information
+        return metas[:-1]
 
     # db-api like interface ###################################################
 
@@ -793,9 +912,9 @@
         rset.req = self
         return rset
 
-    def _clear_thread_data(self, reset_pool=True):
-        """remove everything from the thread local storage, except pool
-        which is explicitly removed by reset_pool, and mode which is set anyway
+    def _clear_thread_data(self, free_cnxset=True):
+        """remove everything from the thread local storage, except connections set
+        which is explicitly removed by free_cnxset, and mode which is set anyway
         by _touch
         """
         try:
@@ -803,23 +922,38 @@
         except AttributeError:
             pass
         else:
-            if reset_pool:
-                self._tx_data.pop(txstore.transactionid, None)
-                try:
-                    del self.__threaddata.txdata
-                except AttributeError:
-                    pass
+            if free_cnxset:
+                self.free_cnxset()
+                if txstore.ctx_count == 0:
+                    self._clear_thread_storage(txstore)
+                else:
+                    self._clear_tx_storage(txstore)
             else:
-                for name in ('commit_state', 'transaction_data',
-                             'pending_operations', '_rewriter'):
-                    try:
-                        delattr(txstore, name)
-                    except AttributeError:
-                        continue
+                self._clear_tx_storage(txstore)
+
+    def _clear_thread_storage(self, txstore):
+        self._tx_data.pop(txstore.transactionid, None)
+        try:
+            del self.__threaddata.txdata
+        except AttributeError:
+            pass
 
-    def commit(self, reset_pool=True):
+    def _clear_tx_storage(self, txstore):
+        for name in ('commit_state', 'transaction_data',
+                     'pending_operations', '_rewriter',
+                     'pruned_hooks_cache'):
+            try:
+                delattr(txstore, name)
+            except AttributeError:
+                continue
+
+    def commit(self, free_cnxset=True, reset_pool=None):
         """commit the current session's transaction"""
-        if self.pool is None:
+        if reset_pool is not None:
+            warn('[3.13] use free_cnxset argument instead for reset_pool',
+                 DeprecationWarning, stacklevel=2)
+            free_cnxset = reset_pool
+        if self.cnxset is None:
             assert not self.pending_operations
             self._clear_thread_data()
             self._touch()
@@ -847,7 +981,7 @@
                         operation.handle_event('precommit_event')
                     self.pending_operations[:] = processed
                     self.debug('precommit session %s done', self.id)
-                except:
+                except BaseException:
                     # if error on [pre]commit:
                     #
                     # * set .failed = True on the operation causing the failure
@@ -862,37 +996,41 @@
                     for operation in reversed(processed):
                         try:
                             operation.handle_event('revertprecommit_event')
-                        except:
+                        except BaseException:
                             self.critical('error while reverting precommit',
                                           exc_info=True)
                     # XXX use slice notation since self.pending_operations is a
                     # read-only property.
                     self.pending_operations[:] = processed + self.pending_operations
-                    self.rollback(reset_pool)
+                    self.rollback(free_cnxset)
                     raise
-                self.pool.commit()
+                self.cnxset.commit()
                 self.commit_state = 'postcommit'
                 while self.pending_operations:
                     operation = self.pending_operations.pop(0)
                     operation.processed = 'postcommit'
                     try:
                         operation.handle_event('postcommit_event')
-                    except:
+                    except BaseException:
                         self.critical('error while postcommit',
                                       exc_info=sys.exc_info())
                 self.debug('postcommit session %s done', self.id)
                 return self.transaction_uuid(set=False)
         finally:
             self._touch()
-            if reset_pool:
-                self.reset_pool(ignoremode=True)
-            self._clear_thread_data(reset_pool)
+            if free_cnxset:
+                self.free_cnxset(ignoremode=True)
+            self._clear_thread_data(free_cnxset)
 
-    def rollback(self, reset_pool=True):
+    def rollback(self, free_cnxset=True, reset_pool=None):
         """rollback the current session's transaction"""
-        # don't use self.pool, rollback may be called with _closed == True
-        pool = getattr(self._threaddata, 'pool', None)
-        if pool is None:
+        if reset_pool is not None:
+            warn('[3.13] use free_cnxset argument instead for reset_pool',
+                 DeprecationWarning, stacklevel=2)
+            free_cnxset = reset_pool
+        # don't use self.cnxset, rollback may be called with _closed == True
+        cnxset = getattr(self._threaddata, 'cnxset', None)
+        if cnxset is None:
             self._clear_thread_data()
             self._touch()
             self.debug('rollback session %s done (no db activity)', self.id)
@@ -904,23 +1042,23 @@
                     try:
                         operation = self.pending_operations.pop(0)
                         operation.handle_event('rollback_event')
-                    except:
+                    except BaseException:
                         self.critical('rollback error', exc_info=sys.exc_info())
                         continue
-                pool.rollback()
+                cnxset.rollback()
                 self.debug('rollback for session %s done', self.id)
         finally:
             self._touch()
-            if reset_pool:
-                self.reset_pool(ignoremode=True)
-            self._clear_thread_data(reset_pool)
+            if free_cnxset:
+                self.free_cnxset(ignoremode=True)
+            self._clear_thread_data(free_cnxset)
 
     def close(self):
-        """do not close pool on session close, since they are shared now"""
+        """do not close connections set on session close, since they are shared now"""
         with self._closed_lock:
             self._closed = True
         # copy since _threads_in_transaction maybe modified while waiting
-        for thread, pool in self._threads_in_transaction.copy():
+        for thread, cnxset in self._threads_in_transaction.copy():
             if thread is threading.currentThread():
                 continue
             self.info('waiting for thread %s', thread)
@@ -930,12 +1068,12 @@
             for i in xrange(10):
                 thread.join(1)
                 if not (thread.isAlive() and
-                        (thread, pool) in self._threads_in_transaction):
+                        (thread, cnxset) in self._threads_in_transaction):
                     break
             else:
                 self.error('thread %s still alive after 10 seconds, will close '
                            'session anyway', thread)
-                self._free_thread_pool(thread, pool, force_close=True)
+                self._free_thread_cnxset(thread, cnxset, force_close=True)
         self.rollback()
         del self.__threaddata
         del self._tx_data
@@ -962,9 +1100,16 @@
             self._threaddata.pending_operations = []
             return self._threaddata.pending_operations
 
+    @property
+    def pruned_hooks_cache(self):
+        try:
+            return self._threaddata.pruned_hooks_cache
+        except AttributeError:
+            self._threaddata.pruned_hooks_cache = {}
+            return self._threaddata.pruned_hooks_cache
+
     def add_operation(self, operation, index=None):
-        """add an observer"""
-        assert self.commit_state != 'commit'
+        """add an operation"""
         if index is None:
             self.pending_operations.append(operation)
         else:
@@ -1023,20 +1168,13 @@
         unstables = rqlst.get_variable_indices()
         basedescr = []
         todetermine = []
-        sampleselect = rqlst.children[0]
-        samplesols = sampleselect.solutions[0]
-        for i, term in enumerate(sampleselect.selection):
-            try:
-                ttype = term.get_type(samplesols, args)
-            except CoercionError:
+        for i in xrange(len(rqlst.children[0].selection)):
+            ttype = selection_idx_type(i, rqlst, args)
+            if ttype is None or ttype == 'Any':
                 ttype = None
                 isfinal = True
             else:
-                if ttype is None or ttype == 'Any':
-                    ttype = None
-                    isfinal = True
-                else:
-                    isfinal = ttype in BASE_TYPES
+                isfinal = ttype in BASE_TYPES
             if ttype is None or i in unstables:
                 basedescr.append(None)
                 todetermine.append( (i, isfinal) )
@@ -1049,7 +1187,8 @@
     def _build_descr(self, result, basedescription, todetermine):
         description = []
         etype_from_eid = self.describe
-        for row in result:
+        todel = []
+        for i, row in enumerate(result):
             row_descr = basedescription[:]
             for index, isfinal in todetermine:
                 value = row[index]
@@ -1063,10 +1202,14 @@
                     try:
                         row_descr[index] = etype_from_eid(value)[0]
                     except UnknownEid:
-                        self.critical('wrong eid %s in repository, you should '
-                                      'db-check the database' % value)
-                        row_descr[index] = row[index] = None
-            description.append(tuple(row_descr))
+                        self.error('wrong eid %s in repository, you should '
+                                   'db-check the database' % value)
+                        todel.append(i)
+                        break
+            else:
+                description.append(tuple(row_descr))
+        for i in reversed(todel):
+            del result[i]
         return description
 
     # deprecated ###############################################################
@@ -1075,6 +1218,18 @@
     def schema_rproperty(self, rtype, eidfrom, eidto, rprop):
         return getattr(self.rtype_eids_rdef(rtype, eidfrom, eidto), rprop)
 
+    @property
+    @deprecated("[3.13] use .cnxset attribute instead of .pool")
+    def pool(self):
+        return self.cnxset
+
+    @deprecated("[3.13] use .set_cnxset() method instead of .set_pool()")
+    def set_pool(self):
+        return self.set_cnxset()
+
+    @deprecated("[3.13] use .free_cnxset() method instead of .reset_pool()")
+    def reset_pool(self):
+        return self.free_cnxset()
 
     @deprecated("[3.7] execute is now unsafe by default in hooks/operation. You"
                 " can also control security with the security_enabled context "
@@ -1133,20 +1288,21 @@
     is_internal_session = True
     running_dbapi_query = False
 
-    def __init__(self, repo, cnxprops=None):
+    def __init__(self, repo, cnxprops=None, safe=False):
         super(InternalSession, self).__init__(InternalManager(), repo, cnxprops,
                                               _id='internal')
         self.user._cw = self # XXX remove when "vreg = user._cw.vreg" hack in entity.py is gone
         self.cnxtype = 'inmemory'
-        self.disable_hook_categories('integrity')
+        if not safe:
+            self.disable_hook_categories('integrity')
 
     @property
-    def pool(self):
-        """connections pool, set according to transaction mode for each query"""
+    def cnxset(self):
+        """connections set, set according to transaction mode for each query"""
         if self.repo.shutting_down:
-            self.reset_pool(True)
+            self.free_cnxset(True)
             raise ShuttingDown('repository is shutting down')
-        return getattr(self._threaddata, 'pool', None)
+        return getattr(self._threaddata, 'cnxset', None)
 
 
 class InternalManager(object):