Commits

Mike Bayer committed 28361fe

- [feature] The before_cursor_execute event
fires off for so-called "_cursor_execute"
events, which are usually special-case
executions of primary-key bound sequences
and default-generation SQL
phrases that invoke separately when RETURNING
is not used with INSERT. [ticket:2459]

Comments (0)

Files changed (9)

     Connection objects, not just Engine
     objects.  [ticket:2511]
 
+  - [feature] The before_cursor_execute event
+    fires off for so-called "_cursor_execute"
+    events, which are usually special-case
+    executions of primary-key bound sequences
+    and default-generation SQL
+    phrases that invoke separately when RETURNING
+    is not used with INSERT.  [ticket:2459]
+
   - [bug] Fixed bug whereby if a database restart
     affected multiple connections, each
     connection would individually invoke a new

lib/sqlalchemy/engine/base.py

         self.__invalid = False
         if _dispatch:
             self.dispatch = _dispatch
+        elif engine._has_events:
+            self.dispatch = self.dispatch._join(engine.dispatch)
         self._has_events = _has_events or engine._has_events
+
         self._echo = self.engine._should_log_info()
         if _execution_options:
             self._execution_options =\
 
         if self._has_events:
             self.dispatch.begin(self)
-            self.engine.dispatch.begin(self)
 
         try:
             self.engine.dialect.do_begin(self.connection)
     def _rollback_impl(self):
         if self._has_events:
             self.dispatch.rollback(self)
-            self.engine.dispatch.rollback(self)
 
         if self._still_open_and_connection_is_valid:
             if self._echo:
     def _commit_impl(self):
         if self._has_events:
             self.dispatch.commit(self)
-            self.engine.dispatch.commit(self)
 
         if self._echo:
             self.engine.logger.info("COMMIT")
     def _savepoint_impl(self, name=None):
         if self._has_events:
             self.dispatch.savepoint(self, name)
-            self.engine.dispatch.savepoint(self, name)
 
         if name is None:
             self.__savepoint_seq += 1
     def _rollback_to_savepoint_impl(self, name, context):
         if self._has_events:
             self.dispatch.rollback_savepoint(self, name, context)
-            self.engine.dispatch.rollback_savepoint(self, name, context)
 
         if self._still_open_and_connection_is_valid:
             self.engine.dialect.do_rollback_to_savepoint(self, name)
     def _release_savepoint_impl(self, name, context):
         if self._has_events:
             self.dispatch.release_savepoint(self, name, context)
-            self.engine.dispatch.release_savepoint(self, name, context)
 
         if self._still_open_and_connection_is_valid:
             self.engine.dialect.do_release_savepoint(self, name)
     def _begin_twophase_impl(self, xid):
         if self._has_events:
             self.dispatch.begin_twophase(self, xid)
-            self.engine.dispatch.begin_twophase(self, xid)
 
         if self._still_open_and_connection_is_valid:
             self.engine.dialect.do_begin_twophase(self, xid)
     def _prepare_twophase_impl(self, xid):
         if self._has_events:
             self.dispatch.prepare_twophase(self, xid)
-            self.engine.dispatch.prepare_twophase(self, xid)
 
         if self._still_open_and_connection_is_valid:
             assert isinstance(self.__transaction, TwoPhaseTransaction)
     def _rollback_twophase_impl(self, xid, is_prepared):
         if self._has_events:
             self.dispatch.rollback_twophase(self, xid, is_prepared)
-            self.engine.dispatch.rollback_twophase(self, xid, is_prepared)
 
         if self._still_open_and_connection_is_valid:
             assert isinstance(self.__transaction, TwoPhaseTransaction)
     def _commit_twophase_impl(self, xid, is_prepared):
         if self._has_events:
             self.dispatch.commit_twophase(self, xid, is_prepared)
-            self.engine.dispatch.commit_twophase(self, xid, is_prepared)
 
         if self._still_open_and_connection_is_valid:
             assert isinstance(self.__transaction, TwoPhaseTransaction)
         """Execute a schema.ColumnDefault object."""
 
         if self._has_events:
-            for fn in chain(
-                        self.dispatch.before_execute,
-                        self.engine.dispatch.before_execute
-                    ):
+            for fn in self.dispatch.before_execute:
                 default, multiparams, params = \
                     fn(self, default, multiparams, params)
 
         if self._has_events:
             self.dispatch.after_execute(self,
                 default, multiparams, params, ret)
-            self.engine.dispatch.after_execute(self,
-                default, multiparams, params, ret)
 
         return ret
 
         """Execute a schema.DDL object."""
 
         if self._has_events:
-            for fn in chain(
-                    self.dispatch.before_execute,
-                    self.engine.dispatch.before_execute
-                    ):
+            for fn in self.dispatch.before_execute:
                 ddl, multiparams, params = \
                     fn(self, ddl, multiparams, params)
 
             compiled
         )
         if self._has_events:
-            self.engine.dispatch.after_execute(self,
+            self.dispatch.after_execute(self,
                 ddl, multiparams, params, ret)
         return ret
 
         """Execute a sql.ClauseElement object."""
 
         if self._has_events:
-            for fn in chain(
-                        self.dispatch.before_execute,
-                        self.engine.dispatch.before_execute
-                    ):
+            for fn in self.dispatch.before_execute:
                 elem, multiparams, params = \
                     fn(self, elem, multiparams, params)
 
         if self._has_events:
             self.dispatch.after_execute(self,
                 elem, multiparams, params, ret)
-            self.engine.dispatch.after_execute(self,
-                elem, multiparams, params, ret)
         return ret
 
     def _execute_compiled(self, compiled, multiparams, params):
         """Execute a sql.Compiled object."""
 
         if self._has_events:
-            for fn in chain(
-                        self.dispatch.before_execute,
-                        self.engine.dispatch.before_execute
-                    ):
+            for fn in self.dispatch.before_execute:
                 compiled, multiparams, params = \
                     fn(self, compiled, multiparams, params)
 
         if self._has_events:
             self.dispatch.after_execute(self,
                 compiled, multiparams, params, ret)
-            self.engine.dispatch.after_execute(self,
-                compiled, multiparams, params, ret)
         return ret
 
     def _execute_text(self, statement, multiparams, params):
         """Execute a string SQL statement."""
 
         if self._has_events:
-            for fn in chain(
-                        self.dispatch.before_execute,
-                        self.engine.dispatch.before_execute
-                    ):
+            for fn in self.dispatch.before_execute:
                 statement, multiparams, params = \
                     fn(self, statement, multiparams, params)
 
         if self._has_events:
             self.dispatch.after_execute(self,
                 statement, multiparams, params, ret)
-            self.engine.dispatch.after_execute(self,
-                statement, multiparams, params, ret)
         return ret
 
     def _execute_context(self, dialect, constructor,
             parameters = parameters[0]
 
         if self._has_events:
-            for fn in chain(
-                        self.dispatch.before_cursor_execute,
-                        self.engine.dispatch.before_cursor_execute
-                    ):
+            for fn in self.dispatch.before_cursor_execute:
                 statement, parameters = \
                             fn(self, cursor, statement, parameters,
                                         context, context.executemany)
                                                 parameters,
                                                 context,
                                                 context.executemany)
-            self.engine.dispatch.after_cursor_execute(self, cursor,
-                                                statement,
-                                                parameters,
-                                                context,
-                                                context.executemany)
 
         if context.compiled:
             context.post_exec()
 
         return result
 
-    def _cursor_execute(self, cursor, statement, parameters):
+    def _cursor_execute(self, cursor, statement, parameters, context=None):
         """Execute a statement + params on the given cursor.
 
         Adds appropriate logging and exception handling.
         terminates at _execute_context().
 
         """
+        if self._has_events:
+            for fn in self.dispatch.before_cursor_execute:
+                statement, parameters = \
+                            fn(self, cursor, statement, parameters,
+                                        context, context.executemany)
+
         if self._echo:
             self.engine.logger.info(statement)
             self.engine.logger.info("%r", parameters)
                                                     parameters,
                                                     context,
                                                     e)
-                    self.engine.dispatch.dbapi_error(self,
-                                                    cursor,
-                                                    statement,
-                                                    parameters,
-                                                    context,
-                                                    e)
                 context.handle_dbapi_exception(e)
 
             is_disconnect = isinstance(e, self.dialect.dbapi.Error) and \

lib/sqlalchemy/engine/default.py

         else:
             default_params = {}
 
-        conn._cursor_execute(self.cursor, stmt, default_params)
+        conn._cursor_execute(self.cursor, stmt, default_params, context=self)
         r = self.cursor.fetchone()[0]
         if type_ is not None:
             # apply type post processors to the result

lib/sqlalchemy/event.py

 """Base event API."""
 
 from . import util, exc
+from itertools import chain
 
 CANCEL = util.symbol('CANCEL')
 NO_RETVAL = util.symbol('NO_RETVAL')
             tgt.dispatch._listen(tgt, identifier, fn, *args, **kw)
             return
     raise exc.InvalidRequestError("No such event '%s' for target '%s'" %
-                                (identifier,target))
+                                (identifier, target))
 
 def listens_for(target, identifier, *args, **kw):
     """Decorate a function as a listener for the given target + identifier.
     """
     for evt_cls in _registrars[identifier]:
         for tgt in evt_cls._accept_with(target):
-            tgt.dispatch._remove(identifier, tgt, fn, *args, **kw)
+            tgt.dispatch._remove(identifier, tgt, fn)
             return
 
 _registrars = util.defaultdict(list)
     def __init__(self, _parent_cls):
         self._parent_cls = _parent_cls
 
+    def _join(self, other):
+        """Create a 'join' of this :class:`._Dispatch` and another.
+
+        This new dispatcher will dispatch events to both
+        :class:`._Dispatch` objects.
+
+        Once constructed, the joined dispatch will respond to new events
+        added to this dispatcher, but may not be aware of events
+        added to the other dispatcher after creation of the join.  This is
+        currently for performance reasons so that both dispatchers need
+        not be "evaluated" fully on each call.
+
+        """
+        if '_joined_dispatch_cls' not in self.__class__.__dict__:
+            cls = type(
+                    "Joined%s" % self.__class__.__name__,
+                    (_JoinedDispatcher, self.__class__), {}
+                )
+            for ls in _event_descriptors(self):
+                setattr(cls, ls.name, _JoinedDispatchDescriptor(ls.name))
+
+            self.__class__._joined_dispatch_cls = cls
+        return self._joined_dispatch_cls(self, other)
+
     def __reduce__(self):
         return _UnpickleDispatch(), (self._parent_cls, )
 
         obj.__dict__[self.__name__] = ret
         return ret
 
+
 class _EmptyListener(object):
     """Serves as a class-level interface to the events
     served by a _DispatchDescriptor, when there are no
         and returns it.
 
         """
-        obj.__dict__[self.name] = result = _ListenerCollection(
-                                        self.parent, obj._parent_cls)
+        result = _ListenerCollection(self.parent, obj._parent_cls)
+        if obj.__dict__[self.name] is self:
+            obj.__dict__[self.name] = result
         return result
 
     def _needs_modify(self, *args, **kw):
     def __iter__(self):
         return iter(self.parent_listeners)
 
-    def __getitem__(self, index):
-        return (self.parent_listeners)[index]
+    def __nonzero__(self):
+        return False
+
+class _CompoundListener(object):
+    _exec_once = False
+
+    def exec_once(self, *args, **kw):
+        """Execute this event, but only if it has not been
+        executed already for this collection."""
+
+        if not self._exec_once:
+            self(*args, **kw)
+            self._exec_once = True
+
+    # I'm not entirely thrilled about the overhead here,
+    # but this allows class-level listeners to be added
+    # at any point.
+    #
+    # In the absense of instance-level listeners,
+    # we stay with the _EmptyListener object when called
+    # at the instance level.
+
+    def __call__(self, *args, **kw):
+        """Execute this event."""
+
+        for fn in self.parent_listeners:
+            fn(*args, **kw)
+        for fn in self.listeners:
+            fn(*args, **kw)
+
+    def __len__(self):
+        return len(self.parent_listeners) + len(self.listeners)
+
+    def __iter__(self):
+        return chain(self.parent_listeners, self.listeners)
 
     def __nonzero__(self):
-        return bool(self.listeners)
+        return bool(self.listeners or self.parent_listeners)
 
 
-class _ListenerCollection(object):
+class _ListenerCollection(_CompoundListener):
     """Instance-level attributes on instances of :class:`._Dispatch`.
 
     Represents a collection of listeners.
 
     """
 
-    _exec_once = False
-
     def __init__(self, parent, target_cls):
         if target_cls not in parent._clslevel:
             parent.update_subclass(target_cls)
         """
         return self
 
-    def exec_once(self, *args, **kw):
-        """Execute this event, but only if it has not been
-        executed already for this collection."""
-
-        if not self._exec_once:
-            self(*args, **kw)
-            self._exec_once = True
-
-    def __call__(self, *args, **kw):
-        """Execute this event."""
-
-        for fn in self.parent_listeners:
-            fn(*args, **kw)
-        for fn in self.listeners:
-            fn(*args, **kw)
-
-    # I'm not entirely thrilled about the overhead here,
-    # but this allows class-level listeners to be added
-    # at any point.
-    #
-    # In the absense of instance-level listeners,
-    # we stay with the _EmptyListener object when called
-    # at the instance level.
-
-    def __len__(self):
-        return len(self.parent_listeners + self.listeners)
-
-    def __iter__(self):
-        return iter(self.parent_listeners + self.listeners)
-
-    def __getitem__(self, index):
-        return (self.parent_listeners + self.listeners)[index]
-
-    def __nonzero__(self):
-        return bool(self.listeners or self.parent_listeners)
-
     def _update(self, other, only_propagate=True):
         """Populate from the listeners in another :class:`_Dispatch`
             object."""
         self.listeners[:] = []
         self.propagate.clear()
 
+
+class _JoinedDispatcher(object):
+    """Represent a connection between two _Dispatch objects."""
+
+    def __init__(self, local, parent):
+        self.local = local
+        self.parent = parent
+        self._parent_cls = local._parent_cls
+
+
+class _JoinedDispatchDescriptor(object):
+    def __init__(self, name):
+        self.name = name
+
+    def __get__(self, obj, cls):
+        if obj is None:
+            return self
+        else:
+            obj.__dict__[self.name] = ret = _JoinedListener(
+                        obj.parent, self.name,
+                        getattr(obj.local, self.name)
+                    )
+            return ret
+
+class _JoinedListener(_CompoundListener):
+    _exec_once = False
+    def __init__(self, parent, name, local):
+        self.parent = parent
+        self.name = name
+        self.local = local
+        self.parent_listeners = self.local
+
+        # fix .listeners for the parent.  This means
+        # new events added to the parent won't be picked
+        # up here.  Alternatively, the listeners can
+        # be via @property to just return getattr(self.parent, self.name)
+        # each time. less performant.
+        self.listeners = list(getattr(self.parent, self.name))
+
+    def for_modify(self, obj):
+        self.local = self.parent_listeners = self.local.for_modify(obj)
+        return self
+
+    def insert(self, obj, target, propagate):
+        self.local.insert(obj, target, propagate)
+
+    def append(self, obj, target, propagate):
+        self.local.append(obj, target, propagate)
+
+    def remove(self, obj, target):
+        self.local.remove(obj, target)
+
+    def clear(self):
+        raise NotImplementedError()
+
+
 class dispatcher(object):
     """Descriptor used by target classes to
     deliver the _Dispatch class at the class level
             return self.dispatch_cls
         obj.__dict__['dispatch'] = disp = self.dispatch_cls(cls)
         return disp
+

lib/sqlalchemy/events.py

     The methods here define the name of an event as well as the names of
     members that are passed to listener functions.
 
-    An event listener can be associated with any :class:`.Connectable`,
-    such as an :class:`.Engine`, e.g.::
+    An event listener can be associated with any :class:`.Connectable`
+    class or instance, such as an :class:`.Engine`, e.g.::
 
         from sqlalchemy import event, create_engine
 
-        def before_execute(conn, clauseelement, multiparams, params):
-            log.info("Received statement: %s" % clauseelement)
+        def before_cursor_execute(conn, cursor, statement, parameters, context,
+                                                        executemany):
+            log.info("Received statement: %s" % statement)
 
         engine = create_engine('postgresql://scott:tiger@localhost/test')
-        event.listen(engine, "before_execute", before_execute)
+        event.listen(engine, "before_cursor_execute", before_cursor_execute)
 
-    Some events allow modifiers to the :func:`.event.listen` function.
+    or with a specific :class:`.Connection`::
+
+        with engine.begin() as conn:
+            @event.listens_for(conn, 'before_cursor_execute')
+            def before_cursor_execute(conn, cursor, statement, parameters,
+                                            context, executemany):
+                log.info("Received statement: %s" % statement)
+
+    The :meth:`.before_execute` and :meth:`.before_cursor_execute`
+    events can also be established with the ``retval=True`` flag, which
+    allows modification of the statement and parameters to be sent
+    to the database.  The :meth:`.before_cursor_execute` event is
+    particularly useful here to add ad-hoc string transformations, such
+    as comments, to all executions::
+
+        from sqlalchemy.engine import Engine
+        from sqlalchemy import event
+
+        @event.listens_for(Engine, "before_cursor_execute", retval=True)
+        def comment_sql_calls(conn, cursor, statement, parameters,
+                                            context, executemany):
+            statement = statement + " -- some comment"
+            return statement, parameters
+
+    .. note:: :class:`.ConnectionEvents` can be established on any
+       combination of :class:`.Engine`, :class:`.Connection`, as well
+       as instances of each of those classes.  Events across all
+       four scopes will fire off for a given instance of
+       :class:`.Connection`.  However, for performance reasons, the
+       :class:`.Connection` object determines at instantiation time
+       whether or not its parent :class:`.Engine` has event listeners
+       established.   Event listeners added to the :class:`.Engine`
+       class or to an instance of :class:`.Engine` *after* the instantiation
+       of a dependent :class:`.Connection` instance will usually
+       *not* be available on that :class:`.Connection` instance.  The newly
+       added listeners will instead take effect for :class:`.Connection`
+       instances created subsequent to those event listeners being
+       established on the parent :class:`.Engine` class or instance.
 
     :param retval=False: Applies to the :meth:`.before_execute` and
       :meth:`.before_cursor_execute` events only.  When True, the
         event.Events._listen(target, identifier, fn)
 
     def before_execute(self, conn, clauseelement, multiparams, params):
-        """Intercept high level execute() events."""
+        """Intercept high level execute() events, receiving uncompiled
+        SQL constructs and other objects prior to rendering into SQL.
+
+        This event is good for debugging SQL compilation issues as well
+        as early manipulation of the parameters being sent to the database,
+        as the parameter lists will be in a consistent format here.
+
+        This event can be optionally established with the ``retval=True``
+        flag.  The ``clauseelement``, ``multiparams``, and ``params``
+        arguments should be returned as a three-tuple in this case::
+
+            @event.listens_for(Engine, "before_execute", retval=True)
+            def before_execute(conn, conn, clauseelement, multiparams, params):
+                # do something with clauseelement, multiparams, params
+                return clauseelement, multiparams, params
+
+        :param conn: :class:`.Connection` object
+        :param clauseelement: SQL expression construct, :class:`.Compiled`
+         instance, or string statement passed to :meth:`.Connection.execute`.
+        :param multiparams: Multiple parameter sets, a list of dictionaries.
+        :param params: Single parameter set, a single dictionary.
+
+        See also:
+
+        :meth:`.before_cursor_execute`
+
+        """
 
     def after_execute(self, conn, clauseelement, multiparams, params, result):
-        """Intercept high level execute() events."""
+        """Intercept high level execute() events after execute.
+
+
+        :param conn: :class:`.Connection` object
+        :param clauseelement: SQL expression construct, :class:`.Compiled`
+         instance, or string statement passed to :meth:`.Connection.execute`.
+        :param multiparams: Multiple parameter sets, a list of dictionaries.
+        :param params: Single parameter set, a single dictionary.
+        :param result: :class:`.ResultProxy` generated by the execution.
+
+        """
 
     def before_cursor_execute(self, conn, cursor, statement,
                         parameters, context, executemany):
-        """Intercept low-level cursor execute() events."""
+        """Intercept low-level cursor execute() events before execution,
+        receiving the string
+        SQL statement and DBAPI-specific parameter list to be invoked
+        against a cursor.
+
+        This event is a good choice for logging as well as late modifications
+        to the SQL string.  It's less ideal for parameter modifications except
+        for those which are specific to a target backend.
+
+        This event can be optionally established with the ``retval=True``
+        flag.  The ``statement`` and ``parameters`` arguments should be
+        returned as a two-tuple in this case::
+
+            @event.listens_for(Engine, "before_cursor_execute", retval=True)
+            def before_cursor_execute(conn, cursor, statement,
+                            parameters, context, executemany):
+                # do something with statement, parameters
+                return statement, parameters
+
+        See the example at :class:`.ConnectionEvents`.
+
+        :param conn: :class:`.Connection` object
+        :param cursor: DBAPI cursor object
+        :param statement: string SQL statement
+        :param parameters: Dictionary, tuple, or list of parameters being
+         passed to the ``execute()`` or ``executemany()`` method of the
+         DBAPI ``cursor``.  In some cases may be ``None``.
+        :param context: :class:`.ExecutionContext` object in use.  May
+         be ``None``.
+        :param executemany: boolean, if ``True``, this is an ``executemany()``
+         call, if ``False``, this is an ``execute()`` call.
+
+        See also:
+
+        :meth:`.before_execute`
+
+        :meth:`.after_cursor_execute`
+
+        """
 
     def after_cursor_execute(self, conn, cursor, statement,
                         parameters, context, executemany):
-        """Intercept low-level cursor execute() events."""
+        """Intercept low-level cursor execute() events after execution.
+
+        :param conn: :class:`.Connection` object
+        :param cursor: DBAPI cursor object.  Will have results pending
+         if the statement was a SELECT, but these should not be consumed
+         as they will be needed by the :class:`.ResultProxy`.
+        :param statement: string SQL statement
+        :param parameters: Dictionary, tuple, or list of parameters being
+         passed to the ``execute()`` or ``executemany()`` method of the
+         DBAPI ``cursor``.  In some cases may be ``None``.
+        :param context: :class:`.ExecutionContext` object in use.  May
+         be ``None``.
+        :param executemany: boolean, if ``True``, this is an ``executemany()``
+         call, if ``False``, this is an ``execute()`` call.
+
+        """
 
     def dbapi_error(self, conn, cursor, statement, parameters,
                         context, exception):
         exception is then wrapped in a SQLAlchemy DBAPI exception
         wrapper and re-thrown.
 
+        :param conn: :class:`.Connection` object
+        :param cursor: DBAPI cursor object
+        :param statement: string SQL statement
+        :param parameters: Dictionary, tuple, or list of parameters being
+         passed to the ``execute()`` or ``executemany()`` method of the
+         DBAPI ``cursor``.  In some cases may be ``None``.
+        :param context: :class:`.ExecutionContext` object in use.  May
+         be ``None``.
+        :param exception: The **unwrapped** exception emitted directly from the
+         DBAPI.  The class here is specific to the DBAPI module in use.
+
         .. versionadded:: 0.7.7
 
         """
 
     def begin(self, conn):
-        """Intercept begin() events."""
+        """Intercept begin() events.
+
+        :param conn: :class:`.Connection` object
+
+        """
 
     def rollback(self, conn):
-        """Intercept rollback() events."""
+        """Intercept rollback() events.
+
+        :param conn: :class:`.Connection` object
+
+        """
 
     def commit(self, conn):
-        """Intercept commit() events."""
+        """Intercept commit() events.
+
+        :param conn: :class:`.Connection` object
+        """
 
     def savepoint(self, conn, name=None):
-        """Intercept savepoint() events."""
+        """Intercept savepoint() events.
+
+        :param conn: :class:`.Connection` object
+        :param name: specified name used for the savepoint.
+
+        """
 
     def rollback_savepoint(self, conn, name, context):
-        """Intercept rollback_savepoint() events."""
+        """Intercept rollback_savepoint() events.
+
+        :param conn: :class:`.Connection` object
+        :param name: specified name used for the savepoint.
+        :param context: :class:`.ExecutionContext` in use.  May be ``None``.
+
+        """
 
     def release_savepoint(self, conn, name, context):
-        """Intercept release_savepoint() events."""
+        """Intercept release_savepoint() events.
+
+        :param conn: :class:`.Connection` object
+        :param name: specified name used for the savepoint.
+        :param context: :class:`.ExecutionContext` in use.  May be ``None``.
+
+        """
 
     def begin_twophase(self, conn, xid):
-        """Intercept begin_twophase() events."""
+        """Intercept begin_twophase() events.
+
+        :param conn: :class:`.Connection` object
+        :param xid: two-phase XID identifier
+
+        """
 
     def prepare_twophase(self, conn, xid):
-        """Intercept prepare_twophase() events."""
+        """Intercept prepare_twophase() events.
+
+        :param conn: :class:`.Connection` object
+        :param xid: two-phase XID identifier
+        """
 
     def rollback_twophase(self, conn, xid, is_prepared):
-        """Intercept rollback_twophase() events."""
+        """Intercept rollback_twophase() events.
+
+        :param conn: :class:`.Connection` object
+        :param xid: two-phase XID identifier
+        :param is_prepared: boolean, indicates if
+         :meth:`.TwoPhaseTransaction.prepare` was called.
+
+        """
 
     def commit_twophase(self, conn, xid, is_prepared):
-        """Intercept commit_twophase() events."""
+        """Intercept commit_twophase() events.
 
+        :param conn: :class:`.Connection` object
+        :param xid: two-phase XID identifier
+        :param is_prepared: boolean, indicates if
+         :meth:`.TwoPhaseTransaction.prepare` was called.
+
+        """
+

test/base/test_events.py

 from sqlalchemy import event, exc, util
 from test.lib import fixtures
 
-class TestEvents(fixtures.TestBase):
+class EventsTest(fixtures.TestBase):
     """Test class- and instance-level event registration."""
 
     def setUp(self):
                 meth
             )
 
-class TestClsLevelListen(fixtures.TestBase):
+class ClsLevelListenTest(fixtures.TestBase):
 
 
     def tearDown(self):
         assert handler2 not in s2.dispatch.event_one
 
 
-class TestAcceptTargets(fixtures.TestBase):
+class AcceptTargetsTest(fixtures.TestBase):
     """Test default target acceptance."""
 
     def setUp(self):
             [listen_two, listen_four]
         )
 
-class TestCustomTargets(fixtures.TestBase):
+class CustomTargetsTest(fixtures.TestBase):
     """Test custom target acceptance."""
 
     def setUp(self):
             listen, "event_one", self.Target
         )
 
-class TestListenOverride(fixtures.TestBase):
+class ListenOverrideTest(fixtures.TestBase):
     """Test custom listen functions which change the listener function signature."""
 
     def setUp(self):
             ]
         )
 
-class TestPropagate(fixtures.TestBase):
+class PropagateTest(fixtures.TestBase):
     def setUp(self):
         class TargetEvents(event.Events):
             def event_one(self, arg):
         t2.dispatch.event_one(t2, 1)
         t2.dispatch.event_two(t2, 2)
         eq_(result, [(t2, 1)])
+
+class JoinTest(fixtures.TestBase):
+    def setUp(self):
+        class TargetEvents(event.Events):
+            def event_one(self, target, arg):
+                pass
+
+        class BaseTarget(object):
+            dispatch = event.dispatcher(TargetEvents)
+
+        class TargetFactory(BaseTarget):
+            def create(self):
+                return TargetElement(self)
+
+        class TargetElement(BaseTarget):
+            def __init__(self, parent):
+                self.dispatch = self.dispatch._join(parent.dispatch)
+
+            def run_event(self, arg):
+                list(self.dispatch.event_one)
+                self.dispatch.event_one(self, arg)
+
+        self.BaseTarget = BaseTarget
+        self.TargetFactory = TargetFactory
+        self.TargetElement = TargetElement
+
+    def tearDown(self):
+        for cls in (self.TargetElement,
+                self.TargetFactory, self.BaseTarget):
+            if 'dispatch' in cls.__dict__:
+                event._remove_dispatcher(cls.__dict__['dispatch'].events)
+
+    def _listener(self):
+        canary = []
+        def listen(target, arg):
+            canary.append((target, arg))
+        return listen, canary
+
+    def test_neither(self):
+        element = self.TargetFactory().create()
+        element.run_event(1)
+        element.run_event(2)
+        element.run_event(3)
+
+    def test_parent_class_only(self):
+        _listener, canary = self._listener()
+
+        event.listen(self.TargetFactory, "event_one", _listener)
+
+        element = self.TargetFactory().create()
+        element.run_event(1)
+        element.run_event(2)
+        element.run_event(3)
+        eq_(
+            canary,
+            [(element, 1), (element, 2), (element, 3)]
+        )
+
+    def test_parent_class_child_class(self):
+        l1, c1 = self._listener()
+        l2, c2 = self._listener()
+
+        event.listen(self.TargetFactory, "event_one", l1)
+        event.listen(self.TargetElement, "event_one", l2)
+
+        element = self.TargetFactory().create()
+        element.run_event(1)
+        element.run_event(2)
+        element.run_event(3)
+        eq_(
+            c1,
+            [(element, 1), (element, 2), (element, 3)]
+        )
+        eq_(
+            c2,
+            [(element, 1), (element, 2), (element, 3)]
+        )
+
+    def test_parent_class_child_instance_apply_after(self):
+        l1, c1 = self._listener()
+        l2, c2 = self._listener()
+
+        event.listen(self.TargetFactory, "event_one", l1)
+        element = self.TargetFactory().create()
+
+        element.run_event(1)
+
+        event.listen(element, "event_one", l2)
+        element.run_event(2)
+        element.run_event(3)
+
+        eq_(
+            c1,
+            [(element, 1), (element, 2), (element, 3)]
+        )
+        eq_(
+            c2,
+            [(element, 2), (element, 3)]
+        )
+
+    def test_parent_class_child_instance_apply_before(self):
+        l1, c1 = self._listener()
+        l2, c2 = self._listener()
+
+        event.listen(self.TargetFactory, "event_one", l1)
+        element = self.TargetFactory().create()
+
+        event.listen(element, "event_one", l2)
+
+        element.run_event(1)
+        element.run_event(2)
+        element.run_event(3)
+
+        eq_(
+            c1,
+            [(element, 1), (element, 2), (element, 3)]
+        )
+        eq_(
+            c2,
+            [(element, 1), (element, 2), (element, 3)]
+        )
+
+    def test_parent_instance_child_class_apply_before(self):
+        l1, c1 = self._listener()
+        l2, c2 = self._listener()
+
+        event.listen(self.TargetElement, "event_one", l2)
+
+        factory = self.TargetFactory()
+        event.listen(factory, "event_one", l1)
+
+        element = factory.create()
+
+        element.run_event(1)
+        element.run_event(2)
+        element.run_event(3)
+
+        eq_(
+            c1,
+            [(element, 1), (element, 2), (element, 3)]
+        )
+        eq_(
+            c2,
+            [(element, 1), (element, 2), (element, 3)]
+        )
+
+    def test_parent_instance_child_class_apply_after(self):
+        l1, c1 = self._listener()
+        l2, c2 = self._listener()
+
+        event.listen(self.TargetElement, "event_one", l2)
+
+        factory = self.TargetFactory()
+        element = factory.create()
+
+        element.run_event(1)
+
+        event.listen(factory, "event_one", l1)
+
+        element.run_event(2)
+        element.run_event(3)
+
+        # c1 gets no events due to _JoinedListener
+        # fixing the "parent" at construction time.
+        # this can be changed to be "live" at the cost
+        # of performance.
+        eq_(
+            c1,
+            []
+            #(element, 2), (element, 3)]
+        )
+        eq_(
+            c2,
+            [(element, 1), (element, 2), (element, 3)]
+        )
+
+    def test_parent_instance_child_instance_apply_before(self):
+        l1, c1 = self._listener()
+        l2, c2 = self._listener()
+        factory = self.TargetFactory()
+
+        event.listen(factory, "event_one", l1)
+
+        element = factory.create()
+        event.listen(element, "event_one", l2)
+
+        element.run_event(1)
+        element.run_event(2)
+        element.run_event(3)
+
+        eq_(
+            c1,
+            [(element, 1), (element, 2), (element, 3)]
+        )
+        eq_(
+            c2,
+            [(element, 1), (element, 2), (element, 3)]
+        )
+
+    def test_parent_events_child_no_events(self):
+        l1, c1 = self._listener()
+        factory = self.TargetFactory()
+
+        event.listen(self.TargetElement, "event_one", l1)
+        element = factory.create()
+
+        element.run_event(1)
+        element.run_event(2)
+        element.run_event(3)
+
+        eq_(
+            c1,
+            [(element, 1), (element, 2), (element, 3)]
+        )

test/engine/test_execute.py

-from test.lib.testing import eq_, assert_raises, assert_raises_message, config
+from test.lib.testing import eq_, assert_raises, assert_raises_message, \
+    config, is_
 import re
 from test.lib.util import picklers
 from sqlalchemy.interfaces import ConnectionProxy
             canary, ['execute', 'cursor_execute']
         )
 
+    @testing.requires.sequences
+    @testing.provide_metadata
+    def test_cursor_execute(self):
+        canary = []
+        def tracker(name):
+            def go(conn, cursor, statement, parameters, context, executemany):
+                canary.append((statement, context))
+            return go
+        engine = engines.testing_engine()
+
+
+        t = Table('t', self.metadata,
+                    Column('x', Integer, Sequence('t_id_seq'), primary_key=True),
+                    implicit_returning=False
+                    )
+        self.metadata.create_all(engine)
+        with engine.begin() as conn:
+            event.listen(conn, 'before_cursor_execute', tracker('cursor_execute'))
+            conn.execute(t.insert())
+        # we see the sequence pre-executed in the first call
+        assert "t_id_seq" in canary[0][0]
+        assert "INSERT" in canary[1][0]
+        # same context
+        is_(
+            canary[0][1], canary[1][1]
+        )
+
     def test_transactional(self):
         canary = []
         def tracker(name):

test/lib/profiles.txt

 test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_postgresql_psycopg2_nocextensions 62
 test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_sqlite_pysqlite_cextensions 62
 test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_sqlite_pysqlite_nocextensions 62
-test.aaa_profiling.test_compiler.CompileTest.test_insert 3.2_postgresql_psycopg2_nocextensions 68
-test.aaa_profiling.test_compiler.CompileTest.test_insert 3.2_sqlite_pysqlite_nocextensions 68
 
 # TEST: test.aaa_profiling.test_compiler.CompileTest.test_select
 
-test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_mysql_mysqldb_cextensions 133
-test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_postgresql_psycopg2_cextensions 133
-test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_postgresql_psycopg2_nocextensions 133
-test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_sqlite_pysqlite_cextensions 133
-test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_sqlite_pysqlite_nocextensions 133
-
-# TEST: test.aaa_profiling.test_compiler.CompileTest.test_select_second_time
-
-test.aaa_profiling.test_compiler.CompileTest.test_select_second_time 2.7_sqlite_pysqlite_nocextensions 133
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.5_sqlite_pysqlite_nocextensions 134
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.6_sqlite_pysqlite_nocextensions 135
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_mysql_mysqldb_cextensions 135
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_mysql_mysqldb_nocextensions 135
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_postgresql_psycopg2_cextensions 135
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_postgresql_psycopg2_nocextensions 135
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_sqlite_pysqlite_cextensions 135
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_sqlite_pysqlite_nocextensions 135
 
 # TEST: test.aaa_profiling.test_compiler.CompileTest.test_update
 
+test.aaa_profiling.test_compiler.CompileTest.test_update 2.5_sqlite_pysqlite_nocextensions 65
+test.aaa_profiling.test_compiler.CompileTest.test_update 2.6_sqlite_pysqlite_nocextensions 65
+test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_mysql_mysqldb_cextensions 65
+test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_mysql_mysqldb_nocextensions 65
+test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_postgresql_psycopg2_cextensions 65
+test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_postgresql_psycopg2_nocextensions 65
 test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_sqlite_pysqlite_cextensions 65
+test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_sqlite_pysqlite_nocextensions 65
 
 # TEST: test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause
 
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.5_sqlite_pysqlite_nocextensions 129
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.6_sqlite_pysqlite_nocextensions 130
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_mysql_mysqldb_cextensions 130
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_mysql_mysqldb_nocextensions 130
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_postgresql_psycopg2_cextensions 130
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_postgresql_psycopg2_nocextensions 130
 test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_sqlite_pysqlite_cextensions 130
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_sqlite_pysqlite_nocextensions 130
 
 # TEST: test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity
 
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_postgresql_psycopg2_nocextensions 17987
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_sqlite_pysqlite_cextensions 17987
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_sqlite_pysqlite_nocextensions 17987
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.2_postgresql_psycopg2_nocextensions 18987
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.2_sqlite_pysqlite_nocextensions 18987
 
 # TEST: test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity
 
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.5_sqlite_pysqlite_nocextensions 116038
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.6_sqlite_pysqlite_nocextensions 114788
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_cextensions 122288
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_nocextensions 125038
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_cextensions 114788
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_nocextensions 117538
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_cextensions 113760
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_nocextensions 116538
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.2_postgresql_psycopg2_nocextensions 121541
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.2_sqlite_pysqlite_nocextensions 121541
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.5_sqlite_pysqlite_nocextensions 116289
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.6_sqlite_pysqlite_nocextensions 116790
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_cextensions 122540
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_nocextensions 125290
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_cextensions 115040
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_nocextensions 117790
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_cextensions 114040
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_nocextensions 116790
 
 # TEST: test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks
 
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.5_sqlite_pysqlite_nocextensions 19770
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.6_sqlite_pysqlite_nocextensions 18941
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_cextensions 19449
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_nocextensions 19709
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_cextensions 18873
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_nocextensions 19096
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_cextensions 18885
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_nocextensions 19117
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.2_postgresql_psycopg2_nocextensions 20217
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.2_sqlite_pysqlite_nocextensions 20274
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.5_sqlite_pysqlite_nocextensions 19852
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.6_sqlite_pysqlite_nocextensions 19217
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_cextensions 19491
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_nocextensions 19781
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_cextensions 18878
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_nocextensions 19168
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_cextensions 18957
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_nocextensions 19217
 
 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_load
 
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.5_sqlite_pysqlite_nocextensions 1132
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.6_sqlite_pysqlite_nocextensions 1113
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_cextensions 1295
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_nocextensions 1320
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_cextensions 1154
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_nocextensions 1179
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_cextensions 1103
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_nocextensions 1128
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.2_postgresql_psycopg2_nocextensions 1202
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.2_sqlite_pysqlite_nocextensions 1165
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.5_sqlite_pysqlite_nocextensions 1178
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.6_sqlite_pysqlite_nocextensions 1174
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_cextensions 1341
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_nocextensions 1366
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_cextensions 1200
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_nocextensions 1225
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_cextensions 1149
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_nocextensions 1174
 
 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_no_load
 
 test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 2.7_postgresql_psycopg2_nocextensions 98,16
 test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 2.7_sqlite_pysqlite_cextensions 98,16
 test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 2.7_sqlite_pysqlite_nocextensions 98,16
-test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 3.2_postgresql_psycopg2_nocextensions 103,17
-test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 3.2_sqlite_pysqlite_nocextensions 103,17
 
 # TEST: test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect
 
 test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 2.7_postgresql_psycopg2_nocextensions 67
 test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 2.7_sqlite_pysqlite_cextensions 67
 test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 2.7_sqlite_pysqlite_nocextensions 67
-test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 3.2_postgresql_psycopg2_nocextensions 55
-test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 3.2_sqlite_pysqlite_nocextensions 55
 
 # TEST: test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect
 
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 2.7_postgresql_psycopg2_nocextensions 29
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 2.7_sqlite_pysqlite_cextensions 29
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 2.7_sqlite_pysqlite_nocextensions 29
-test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 3.2_postgresql_psycopg2_nocextensions 23
-test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 3.2_sqlite_pysqlite_nocextensions 23
 
 # TEST: test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect
 
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 2.7_postgresql_psycopg2_nocextensions 6
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 2.7_sqlite_pysqlite_cextensions 6
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 2.7_sqlite_pysqlite_nocextensions 6
-test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 3.2_postgresql_psycopg2_nocextensions 7
-test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 3.2_sqlite_pysqlite_nocextensions 7
 
 # TEST: test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute
 
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 2.7_postgresql_psycopg2_nocextensions 42
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 2.7_sqlite_pysqlite_cextensions 40
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 2.7_sqlite_pysqlite_nocextensions 42
-test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 3.2_postgresql_psycopg2_nocextensions 40
-test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 3.2_sqlite_pysqlite_nocextensions 40
 
 # TEST: test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute
 
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 2.7_postgresql_psycopg2_nocextensions 65
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 2.7_sqlite_pysqlite_cextensions 63
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 2.7_sqlite_pysqlite_nocextensions 65
-test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 3.2_postgresql_psycopg2_nocextensions 63
-test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 3.2_sqlite_pysqlite_nocextensions 63
 
 # TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile
 
+test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.5_sqlite_pysqlite_nocextensions 14
+test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.6_sqlite_pysqlite_nocextensions 14
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_mysql_mysqldb_cextensions 14
+test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_mysql_mysqldb_nocextensions 14
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_postgresql_psycopg2_cextensions 14
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_postgresql_psycopg2_nocextensions 14
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_sqlite_pysqlite_cextensions 14
 
 # TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_string
 
-test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.5_sqlite_pysqlite_nocextensions 14369
-test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.6_sqlite_pysqlite_nocextensions 14370
-test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_mysql_mysqldb_cextensions 408
-test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_mysql_mysqldb_nocextensions 14428
-test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_postgresql_psycopg2_cextensions 20394
-test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_postgresql_psycopg2_nocextensions 34414
-test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_sqlite_pysqlite_cextensions 350
-test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_sqlite_pysqlite_nocextensions 14370
-test.aaa_profiling.test_resultset.ResultSetTest.test_string 3.2_postgresql_psycopg2_nocextensions 14382
-test.aaa_profiling.test_resultset.ResultSetTest.test_string 3.2_sqlite_pysqlite_nocextensions 14353
+test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.5_sqlite_pysqlite_nocextensions 14413
+test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.6_sqlite_pysqlite_nocextensions 14414
+test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_mysql_mysqldb_cextensions 452
+test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_mysql_mysqldb_nocextensions 14472
+test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_postgresql_psycopg2_cextensions 20438
+test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_postgresql_psycopg2_nocextensions 34458
+test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_sqlite_pysqlite_cextensions 394
+test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_sqlite_pysqlite_nocextensions 14414
 
 # TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_unicode
 
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.5_sqlite_pysqlite_nocextensions 14369
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.6_sqlite_pysqlite_nocextensions 14370
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_mysql_mysqldb_cextensions 408
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_mysql_mysqldb_nocextensions 44428
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_postgresql_psycopg2_cextensions 20394
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_postgresql_psycopg2_nocextensions 34414
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_sqlite_pysqlite_cextensions 350
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_sqlite_pysqlite_nocextensions 14370
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 3.2_postgresql_psycopg2_nocextensions 14382
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 3.2_sqlite_pysqlite_nocextensions 14353
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.5_sqlite_pysqlite_nocextensions 14413
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.6_sqlite_pysqlite_nocextensions 14414
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_mysql_mysqldb_cextensions 452
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_mysql_mysqldb_nocextensions 44472
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_postgresql_psycopg2_cextensions 20438
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_postgresql_psycopg2_nocextensions 34458
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_sqlite_pysqlite_cextensions 394
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_sqlite_pysqlite_nocextensions 14414
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate
 
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_cextensions 4915
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_nocextensions 4959
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 3.2_postgresql_psycopg2_nocextensions 4707
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_cextensions 5044
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_nocextensions 5088
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert
 
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_cextensions 247
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_nocextensions 247
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert 3.2_postgresql_psycopg2_nocextensions 236
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties
 
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_cextensions 3302
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_nocextensions 3526
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_cextensions 3366
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_nocextensions 3590
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions
 
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_cextensions 10062
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_nocextensions 11678
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 3.2_postgresql_psycopg2_nocextensions 10973
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_cextensions 10366
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_nocextensions 11982
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates
 
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_cextensions 998
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_nocextensions 1102
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 3.2_postgresql_psycopg2_nocextensions 1076
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_cextensions 1005
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_nocextensions 1109
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing
 
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_cextensions 1654
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_nocextensions 1697
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 3.2_postgresql_psycopg2_nocextensions 1600
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_cextensions 1736
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_nocextensions 1779
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview
 
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 2.7_postgresql_psycopg2_cextensions 2154
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 2.7_postgresql_psycopg2_nocextensions 2384
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 3.2_postgresql_psycopg2_nocextensions 2365
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 2.7_postgresql_psycopg2_cextensions 2219
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 2.7_postgresql_psycopg2_nocextensions 2449
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate
 
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_cextensions 5842
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_nocextensions 5968
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 3.2_postgresql_psycopg2_nocextensions 5981
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_cextensions 5977
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_nocextensions 6096
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert
 
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_cextensions 391
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_nocextensions 398
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 3.2_postgresql_psycopg2_nocextensions 396
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_cextensions 392
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_nocextensions 399
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties
 
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_cextensions 5846
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_nocextensions 6078
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 3.2_postgresql_psycopg2_nocextensions 6039
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_cextensions 6124
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_nocextensions 6356
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions
 
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_cextensions 17885
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_nocextensions 19316
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 3.2_postgresql_psycopg2_nocextensions 19197
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_cextensions 18140
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_nocextensions 19571
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates
 
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_cextensions 1011
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_nocextensions 1107
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 3.2_postgresql_psycopg2_nocextensions 1082
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_cextensions 1018
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_nocextensions 1114
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing
 
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_cextensions 2555
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_nocextensions 2618
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 3.2_postgresql_psycopg2_nocextensions 2650
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_cextensions 2614
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_nocextensions 2677

test/sql/test_cte.py

             checkpositional=('x', 'y'),
             dialect=dialect
         )
+