]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
normalize execute style for events, 2.0
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 19 Aug 2020 16:08:26 +0000 (12:08 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 20 Aug 2020 14:14:21 +0000 (10:14 -0400)
The _execute_20 and exec_driver_sql methods should wrap
up the parameters so that they represent the single list / single
dictionary style of invocation into the legacy methods.  then
the before_ after_ execute event handlers should be receiving
the parameter dictionary as a single dictionary.   this requires
that we break out distill_params to work differently if event
handlers are present.

additionally, add deprecation warnings for old argument passing
styles.

Change-Id: I97cb4d06adfcc6b889f10d01cc7775925cffb116

12 files changed:
doc/build/core/tutorial.rst
lib/sqlalchemy/cextension/utils.c
lib/sqlalchemy/dialects/postgresql/base.py
lib/sqlalchemy/engine/base.py
lib/sqlalchemy/engine/util.py
lib/sqlalchemy/testing/assertsql.py
test/aaa_profiling/test_resultset.py
test/dialect/postgresql/test_query.py
test/engine/test_deprecations.py
test/engine/test_execute.py
test/engine/test_processors.py
test/profiles.txt

index 3f4d37d0e200169060fbfeed2e82db3e6d806489..8d27dd21d704ccb66b11515191facc691492f63e 100644 (file)
@@ -323,7 +323,7 @@ and use it in the "normal" way:
 .. sourcecode:: pycon+sql
 
     >>> ins = users.insert()
-    >>> conn.execute(ins, id=2, name='wendy', fullname='Wendy Williams')
+    >>> conn.execute(ins, {"id": 2, "name":"wendy", "fullname": "Wendy Williams"})
     {opensql}INSERT INTO users (id, name, fullname) VALUES (?, ?, ?)
     [...] (2, 'wendy', 'Wendy Williams')
     COMMIT
@@ -972,7 +972,7 @@ unchanged.  Below, we create a :func:`_expression.text` object and execute it:
     ...         "AND users.name BETWEEN :x AND :y "
     ...         "AND (addresses.email_address LIKE :e1 "
     ...             "OR addresses.email_address LIKE :e2)")
-    >>> conn.execute(s, x='m', y='z', e1='%@aol.com', e2='%@msn.com').fetchall()
+    >>> conn.execute(s, {"x":"m", "y":"z", "e1":"%@aol.com", "e2":"%@msn.com"}).fetchall()
     {opensql}SELECT users.fullname || ', ' || addresses.email_address AS title
     FROM users, addresses
     WHERE users.id = addresses.user_id AND users.name BETWEEN ? AND ? AND
@@ -1127,7 +1127,7 @@ need to refer to any pre-established :class:`_schema.Table` metadata:
     ...                     "OR addresses.email_address LIKE :y)")
     ...             )
     ...         ).select_from(text('users, addresses'))
-    >>> conn.execute(s, x='%@aol.com', y='%@msn.com').fetchall()
+    >>> conn.execute(s, {"x": "%@aol.com", "y": "%@msn.com"}).fetchall()
     {opensql}SELECT users.fullname || ', ' || addresses.email_address AS title
     FROM users, addresses
     WHERE users.id = addresses.user_id AND users.name BETWEEN 'm' AND 'z'
@@ -1180,7 +1180,7 @@ be quoted:
     ...        )
     ...    ).select_from(table('users')).select_from(table('addresses'))
 
-    >>> conn.execute(s, x='%@aol.com', y='%@msn.com').fetchall()
+    >>> conn.execute(s, {"x":"%@aol.com", "y":"%@msn.com"}).fetchall()
     {opensql}SELECT users.fullname || ? || addresses.email_address AS anon_1
     FROM users, addresses
     WHERE users.id = addresses.user_id
index ab8b39335c215284f39aeeb389d3ba758bd5ce87..c612094dcd1680c22799279bb02dbd9792acaa1e 100644 (file)
@@ -26,12 +26,13 @@ distill_params(PyObject *self, PyObject *args)
        // TODO: pass the Connection in so that there can be a standard
        // method for warning on parameter format
 
-       PyObject *multiparams, *params;
+       PyObject *connection, *multiparams, *params;
        PyObject *enclosing_list, *double_enclosing_list;
        PyObject *zero_element, *zero_element_item;
+    PyObject *tmp;
        Py_ssize_t multiparam_size, zero_element_length;
 
-       if (!PyArg_UnpackTuple(args, "_distill_params", 2, 2, &multiparams, &params)) {
+       if (!PyArg_UnpackTuple(args, "_distill_params", 3, 3, &connection, &multiparams, &params)) {
                return NULL;
        }
 
@@ -47,8 +48,12 @@ distill_params(PyObject *self, PyObject *args)
 
        if (multiparam_size == 0) {
                if (params != Py_None && PyMapping_Size(params) != 0) {
-                       // TODO: this is keyword parameters, emit parameter format
-                       // deprecation warning
+
+                   tmp = PyObject_CallMethod(connection, "_warn_for_legacy_exec_format", "");
+               if (tmp == NULL) {
+                   return NULL;
+               }
+
                        enclosing_list = PyList_New(1);
                        if (enclosing_list == NULL) {
                                return NULL;
@@ -102,6 +107,7 @@ distill_params(PyObject *self, PyObject *args)
                                 * execute(stmt, ("value", "value"))
                                 */
                                Py_XDECREF(zero_element_item);
+
                                enclosing_list = PyList_New(1);
                                if (enclosing_list == NULL) {
                                        return NULL;
@@ -131,6 +137,11 @@ distill_params(PyObject *self, PyObject *args)
                        }
                        return enclosing_list;
                } else {
+                   tmp = PyObject_CallMethod(connection, "_warn_for_legacy_exec_format", "");
+               if (tmp == NULL) {
+                   return NULL;
+               }
+
                        enclosing_list = PyList_New(1);
                        if (enclosing_list ==  NULL) {
                                return NULL;
@@ -157,8 +168,12 @@ distill_params(PyObject *self, PyObject *args)
                }
        }
        else {
-               // TODO: this is multiple positional params, emit parameter format
-               // deprecation warning
+
+           tmp = PyObject_CallMethod(connection, "_warn_for_legacy_exec_format", "");
+        if (tmp == NULL) {
+            return NULL;
+        }
+
                zero_element = PyTuple_GetItem(multiparams, 0);
                if (PyObject_HasAttrString(zero_element, "__iter__") &&
                                !PyObject_HasAttrString(zero_element, "strip")
index 7717a2526bf0b428d383c3b97f01b0b027731966..0c58fa9d7c957d51242b007c788dd8589d20b45f 100644 (file)
@@ -2855,7 +2855,11 @@ class PGDialect(default.DefaultDialect):
                 "JOIN pg_namespace n ON n.oid = c.relnamespace "
                 "WHERE n.nspname = :schema AND c.relkind in ('r', 'p')"
             ).columns(relname=sqltypes.Unicode),
-            schema=schema if schema is not None else self.default_schema_name,
+            dict(
+                schema=schema
+                if schema is not None
+                else self.default_schema_name
+            ),
         )
         return [name for name, in result]
 
@@ -2970,7 +2974,7 @@ class PGDialect(default.DefaultDialect):
             .bindparams(sql.bindparam("table_oid", type_=sqltypes.Integer))
             .columns(attname=sqltypes.Unicode, default=sqltypes.Unicode)
         )
-        c = connection.execute(s, table_oid=table_oid)
+        c = connection.execute(s, dict(table_oid=table_oid))
         rows = c.fetchall()
 
         # dictionary with (name, ) if default search path or (schema, name)
@@ -3212,7 +3216,7 @@ class PGDialect(default.DefaultDialect):
                 ORDER BY k.ord
             """
         t = sql.text(PK_SQL).columns(attname=sqltypes.Unicode)
-        c = connection.execute(t, table_oid=table_oid)
+        c = connection.execute(t, dict(table_oid=table_oid))
         cols = [r[0] for r in c.fetchall()]
 
         PK_CONS_SQL = """
@@ -3222,7 +3226,7 @@ class PGDialect(default.DefaultDialect):
            ORDER BY 1
         """
         t = sql.text(PK_CONS_SQL).columns(conname=sqltypes.Unicode)
-        c = connection.execute(t, table_oid=table_oid)
+        c = connection.execute(t, dict(table_oid=table_oid))
         name = c.scalar()
 
         return {"constrained_columns": cols, "name": name}
@@ -3270,7 +3274,7 @@ class PGDialect(default.DefaultDialect):
         t = sql.text(FK_SQL).columns(
             conname=sqltypes.Unicode, condef=sqltypes.Unicode
         )
-        c = connection.execute(t, table=table_oid)
+        c = connection.execute(t, dict(table=table_oid))
         fkeys = []
         for conname, condef, conschema in c.fetchall():
             m = re.search(FK_REGEX, condef).groups()
@@ -3442,7 +3446,7 @@ class PGDialect(default.DefaultDialect):
         t = sql.text(IDX_SQL).columns(
             relname=sqltypes.Unicode, attname=sqltypes.Unicode
         )
-        c = connection.execute(t, table_oid=table_oid)
+        c = connection.execute(t, dict(table_oid=table_oid))
 
         indexes = defaultdict(lambda: defaultdict(dict))
 
@@ -3584,7 +3588,7 @@ class PGDialect(default.DefaultDialect):
         """
 
         t = sql.text(UNIQUE_SQL).columns(col_name=sqltypes.Unicode)
-        c = connection.execute(t, table_oid=table_oid)
+        c = connection.execute(t, dict(table_oid=table_oid))
 
         uniques = defaultdict(lambda: defaultdict(dict))
         for row in c.fetchall():
@@ -3635,7 +3639,7 @@ class PGDialect(default.DefaultDialect):
                 cons.contype = 'c'
         """
 
-        c = connection.execute(sql.text(CHECK_SQL), table_oid=table_oid)
+        c = connection.execute(sql.text(CHECK_SQL), dict(table_oid=table_oid))
 
         ret = []
         for name, src in c:
index 34bf720b7842d156627cc39a93941897c45316cc..0eaa1fae1157ca1048e48132f19c96d233a87d16 100644 (file)
@@ -935,6 +935,17 @@ class Connection(Connectable):
         if not self.in_transaction():
             self._rollback_impl()
 
+    def _warn_for_legacy_exec_format(self):
+        util.warn_deprecated_20(
+            "The connection.execute() method in "
+            "SQLAlchemy 2.0 will accept parameters as a single "
+            "dictionary or a "
+            "single sequence of dictionaries only. "
+            "Parameters passed as keyword arguments, tuples or positionally "
+            "oriened dictionaries and/or tuples "
+            "will no longer be accepted."
+        )
+
     def close(self):
         """Close this :class:`_engine.Connection`.
 
@@ -1073,14 +1084,13 @@ class Connection(Connectable):
                 "or the Connection.exec_driver_sql() method to invoke a "
                 "driver-level SQL string."
             )
-            distilled_parameters = _distill_params(multiparams, params)
 
             return self._exec_driver_sql(
                 object_,
                 multiparams,
                 params,
-                distilled_parameters,
                 _EMPTY_EXECUTION_OPTS,
+                future=False,
             )
 
         try:
@@ -1113,11 +1123,16 @@ class Connection(Connectable):
             execution_options
         )
 
+        distilled_parameters = _distill_params(self, multiparams, params)
+
         if self._has_events or self.engine._has_events:
-            for fn in self.dispatch.before_execute:
-                default, multiparams, params = fn(
-                    self, default, multiparams, params, execution_options
-                )
+            (
+                distilled_params,
+                event_multiparams,
+                event_params,
+            ) = self._invoke_before_exec_event(
+                default, distilled_parameters, execution_options
+            )
 
         try:
             conn = self._dbapi_connection
@@ -1139,7 +1154,12 @@ class Connection(Connectable):
 
         if self._has_events or self.engine._has_events:
             self.dispatch.after_execute(
-                self, default, multiparams, params, execution_options, ret
+                self,
+                default,
+                event_multiparams,
+                event_params,
+                execution_options,
+                ret,
             )
 
         return ret
@@ -1151,11 +1171,16 @@ class Connection(Connectable):
             self._execution_options, execution_options
         )
 
+        distilled_parameters = _distill_params(self, multiparams, params)
+
         if self._has_events or self.engine._has_events:
-            for fn in self.dispatch.before_execute:
-                ddl, multiparams, params = fn(
-                    self, ddl, multiparams, params, execution_options
-                )
+            (
+                distilled_params,
+                event_multiparams,
+                event_params,
+            ) = self._invoke_before_exec_event(
+                ddl, distilled_parameters, execution_options
+            )
 
         exec_opts = self._execution_options.merge_with(execution_options)
         schema_translate_map = exec_opts.get("schema_translate_map", None)
@@ -1175,10 +1200,43 @@ class Connection(Connectable):
         )
         if self._has_events or self.engine._has_events:
             self.dispatch.after_execute(
-                self, ddl, multiparams, params, execution_options, ret
+                self,
+                ddl,
+                event_multiparams,
+                event_params,
+                execution_options,
+                ret,
             )
         return ret
 
+    def _invoke_before_exec_event(
+        self, elem, distilled_params, execution_options
+    ):
+
+        if len(distilled_params) == 1:
+            event_multiparams, event_params = [], distilled_params[0]
+        else:
+            event_multiparams, event_params = distilled_params, {}
+
+        for fn in self.dispatch.before_execute:
+            elem, event_multiparams, event_params = fn(
+                self, elem, event_multiparams, event_params, execution_options,
+            )
+
+        if event_multiparams:
+            distilled_params = list(event_multiparams)
+            if event_params:
+                raise exc.InvalidRequestError(
+                    "Event handler can't return non-empty multiparams "
+                    "and params at the same time"
+                )
+        elif event_params:
+            distilled_params = [event_params]
+        else:
+            distilled_params = []
+
+        return distilled_params, event_multiparams, event_params
+
     def _execute_clauseelement(
         self, elem, multiparams, params, execution_options
     ):
@@ -1188,14 +1246,18 @@ class Connection(Connectable):
             self._execution_options, execution_options
         )
 
+        distilled_params = _distill_params(self, multiparams, params)
+
         has_events = self._has_events or self.engine._has_events
         if has_events:
-            for fn in self.dispatch.before_execute:
-                elem, multiparams, params = fn(
-                    self, elem, multiparams, params, execution_options
-                )
+            (
+                distilled_params,
+                event_multiparams,
+                event_params,
+            ) = self._invoke_before_exec_event(
+                elem, distilled_params, execution_options
+            )
 
-        distilled_params = _distill_params(multiparams, params)
         if distilled_params:
             # ensure we don't retain a link to the view object for keys()
             # which links to the values, which we don't want to cache
@@ -1237,7 +1299,12 @@ class Connection(Connectable):
         )
         if has_events:
             self.dispatch.after_execute(
-                self, elem, multiparams, params, execution_options, ret
+                self,
+                elem,
+                event_multiparams,
+                event_params,
+                execution_options,
+                ret,
             )
         return ret
 
@@ -1257,49 +1324,58 @@ class Connection(Connectable):
         execution_options = compiled.execution_options.merge_with(
             self._execution_options, execution_options
         )
+        distilled_parameters = _distill_params(self, multiparams, params)
 
         if self._has_events or self.engine._has_events:
-            for fn in self.dispatch.before_execute:
-                compiled, multiparams, params = fn(
-                    self, compiled, multiparams, params, execution_options
-                )
+            (
+                distilled_params,
+                event_multiparams,
+                event_params,
+            ) = self._invoke_before_exec_event(
+                compiled, distilled_parameters, execution_options
+            )
 
         dialect = self.dialect
-        parameters = _distill_params(multiparams, params)
         ret = self._execute_context(
             dialect,
             dialect.execution_ctx_cls._init_compiled,
             compiled,
-            parameters,
+            distilled_parameters,
             execution_options,
             compiled,
-            parameters,
+            distilled_parameters,
             None,
             None,
         )
         if self._has_events or self.engine._has_events:
             self.dispatch.after_execute(
-                self, compiled, multiparams, params, execution_options, ret
+                self,
+                compiled,
+                event_multiparams,
+                event_params,
+                execution_options,
+                ret,
             )
         return ret
 
     def _exec_driver_sql(
-        self,
-        statement,
-        multiparams,
-        params,
-        distilled_parameters,
-        execution_options,
+        self, statement, multiparams, params, execution_options, future
     ):
 
         execution_options = self._execution_options.merge_with(
             execution_options
         )
 
-        if self._has_events or self.engine._has_events:
-            for fn in self.dispatch.before_execute:
-                statement, multiparams, params = fn(
-                    self, statement, multiparams, params, execution_options
+        distilled_parameters = _distill_params(self, multiparams, params)
+
+        if not future:
+            if self._has_events or self.engine._has_events:
+                (
+                    distilled_params,
+                    event_multiparams,
+                    event_params,
+                ) = self._invoke_before_exec_event(
+                    statement, distilled_parameters, execution_options
                 )
 
         dialect = self.dialect
@@ -1312,10 +1388,17 @@ class Connection(Connectable):
             statement,
             distilled_parameters,
         )
-        if self._has_events or self.engine._has_events:
-            self.dispatch.after_execute(
-                self, statement, multiparams, params, execution_options, ret
-            )
+
+        if not future:
+            if self._has_events or self.engine._has_events:
+                self.dispatch.after_execute(
+                    self,
+                    statement,
+                    event_multiparams,
+                    event_params,
+                    execution_options,
+                    ret,
+                )
         return ret
 
     def _execute_20(
@@ -1324,9 +1407,7 @@ class Connection(Connectable):
         parameters=None,
         execution_options=_EMPTY_EXECUTION_OPTS,
     ):
-        multiparams, params, distilled_parameters = _distill_params_20(
-            parameters
-        )
+        args_10style, kwargs_10style = _distill_params_20(parameters)
         try:
             meth = statement._execute_on_connection
         except AttributeError as err:
@@ -1334,7 +1415,7 @@ class Connection(Connectable):
                 exc.ObjectNotExecutableError(statement), replace_context=err
             )
         else:
-            return meth(self, multiparams, params, execution_options)
+            return meth(self, args_10style, kwargs_10style, execution_options)
 
     def exec_driver_sql(
         self, statement, parameters=None, execution_options=None
@@ -1373,22 +1454,28 @@ class Connection(Connectable):
                  (1, 'v1')
              )
 
+         .. note:: The :meth:`_engine.Connection.exec_driver_sql` method does
+             not participate in the
+             :meth:`_events.ConnectionEvents.before_execute` and
+             :meth:`_events.ConnectionEvents.after_execute` events.   To
+             intercept calls to :meth:`_engine.Connection.exec_driver_sql`, use
+             :meth:`_events.ConnectionEvents.before_cursor_execute` and
+             :meth:`_events.ConnectionEvents.after_cursor_execute`.
+
          .. seealso::
 
             :pep:`249`
 
         """
 
-        multiparams, params, distilled_parameters = _distill_params_20(
-            parameters
-        )
+        args_10style, kwargs_10style = _distill_params_20(parameters)
 
         return self._exec_driver_sql(
             statement,
-            multiparams,
-            params,
-            distilled_parameters,
+            args_10style,
+            kwargs_10style,
             execution_options,
+            future=True,
         )
 
     def _execute_context(
index fc0260ae24556f9092cb54abd9f41711d1933da6..c1f6bad77953753fd95913b0e4be8301883e55aa 100644 (file)
@@ -30,10 +30,14 @@ def connection_memoize(key):
     return decorated
 
 
+_no_tuple = ()
+_no_kw = util.immutabledict()
+
+
 def py_fallback():
     # TODO: pass the Connection in so that there can be a standard
     # method for warning on parameter format
-    def _distill_params(multiparams, params):  # noqa
+    def _distill_params(connection, multiparams, params):  # noqa
         r"""Given arguments from the calling form \*multiparams, \**params,
         return a list of bind parameter structures, usually a list of
         dictionaries.
@@ -43,9 +47,12 @@ def py_fallback():
 
         """
 
+        # C version will fail if this assertion is not true.
+        # assert isinstance(multiparams, tuple)
+
         if not multiparams:
             if params:
-                # TODO: parameter format deprecation warning
+                connection._warn_for_legacy_exec_format()
                 return [params]
             else:
                 return []
@@ -61,16 +68,22 @@ def py_fallback():
                     # execute(stmt, [(), (), (), ...])
                     return zero
                 else:
+                    # this is used by exec_driver_sql only, so a deprecation
+                    # warning would already be coming from passing a plain
+                    # textual statement with positional parameters to
+                    # execute().
                     # execute(stmt, ("value", "value"))
+
                     return [zero]
             elif hasattr(zero, "keys"):
                 # execute(stmt, {"key":"value"})
                 return [zero]
             else:
+                connection._warn_for_legacy_exec_format()
                 # execute(stmt, "value")
                 return [[zero]]
         else:
-            # TODO: parameter format deprecation warning
+            connection._warn_for_legacy_exec_format()
             if hasattr(multiparams[0], "__iter__") and not hasattr(
                 multiparams[0], "strip"
             ):
@@ -81,14 +94,55 @@ def py_fallback():
     return locals()
 
 
-_no_tuple = ()
-_no_kw = util.immutabledict()
+def _distill_cursor_params(connection, multiparams, params):
+    """_distill_params without any warnings.  more appropriate for
+    "cursor" params that can include tuple arguments, lists of tuples,
+    etc.
+
+    """
+
+    if not multiparams:
+        if params:
+            return [params]
+        else:
+            return []
+    elif len(multiparams) == 1:
+        zero = multiparams[0]
+        if isinstance(zero, (list, tuple)):
+            if (
+                not zero
+                or hasattr(zero[0], "__iter__")
+                and not hasattr(zero[0], "strip")
+            ):
+                # execute(stmt, [{}, {}, {}, ...])
+                # execute(stmt, [(), (), (), ...])
+                return zero
+            else:
+                # this is used by exec_driver_sql only, so a deprecation
+                # warning would already be coming from passing a plain
+                # textual statement with positional parameters to
+                # execute().
+                # execute(stmt, ("value", "value"))
+
+                return [zero]
+        elif hasattr(zero, "keys"):
+            # execute(stmt, {"key":"value"})
+            return [zero]
+        else:
+            # execute(stmt, "value")
+            return [[zero]]
+    else:
+        if hasattr(multiparams[0], "__iter__") and not hasattr(
+            multiparams[0], "strip"
+        ):
+            return multiparams
+        else:
+            return [multiparams]
 
 
 def _distill_params_20(params):
-    # TODO: this has to be in C
     if params is None:
-        return _no_tuple, _no_kw, []
+        return _no_tuple, _no_kw
     elif isinstance(params, list):
         # collections_abc.MutableSequence): # avoid abc.__instancecheck__
         if params and not isinstance(
@@ -98,15 +152,14 @@ def _distill_params_20(params):
                 "List argument must consist only of tuples or dictionaries"
             )
 
-        # the tuple is needed atm by the C version of _distill_params...
-        return tuple(params), _no_kw, params
+        return (params,), _no_kw
     elif isinstance(
         params,
         (tuple, dict, immutabledict),
         # avoid abc.__instancecheck__
         # (collections_abc.Sequence, collections_abc.Mapping),
     ):
-        return _no_tuple, params, [params]
+        return (params,), _no_kw
     else:
         raise exc.ArgumentError("mapping or sequence expected for parameters")
 
index caf61a80616bfb606180263da4fac6df078c0135..0face5f8f15e14473e0884fda97e30e1b80c12cf 100644 (file)
@@ -13,7 +13,7 @@ from .. import event
 from .. import util
 from ..engine import url
 from ..engine.default import DefaultDialect
-from ..engine.util import _distill_params
+from ..engine.util import _distill_cursor_params
 from ..schema import _DDLCompiles
 
 
@@ -348,7 +348,9 @@ class SQLExecuteObserved(object):
     def __init__(self, context, clauseelement, multiparams, params):
         self.context = context
         self.clauseelement = clauseelement
-        self.parameters = _distill_params(multiparams, params)
+        self.parameters = _distill_cursor_params(
+            context.connection, tuple(multiparams), params
+        )
         self.statements = []
 
     def __repr__(self):
index 119a5ee6a81cc147efa6bc19ffe692527245f04e..aea160c9e42e4c7205889dcc80998d04c3c77cc9 100644 (file)
@@ -98,7 +98,7 @@ class ResultSetTest(fixtures.TestBase, AssertsExecutionResults):
         ) as conn:
             [tuple(row) for row in conn.execute(t2.select()).fetchall()]
 
-    @profiling.function_call_count(variance=0.10)
+    @profiling.function_call_count(variance=0.15)
     def test_raw_string(self):
         stmt = "SELECT %s FROM table1" % (
             ", ".join("field%d" % fnum for fnum in range(NUM_FIELDS))
@@ -106,7 +106,7 @@ class ResultSetTest(fixtures.TestBase, AssertsExecutionResults):
         with testing.db.connect() as conn:
             [tuple(row) for row in conn.exec_driver_sql(stmt).fetchall()]
 
-    @profiling.function_call_count(variance=0.10)
+    @profiling.function_call_count(variance=0.15)
     def test_raw_unicode(self):
         stmt = "SELECT %s FROM table2" % (
             ", ".join("field%d" % fnum for fnum in range(NUM_FIELDS))
index 5ab65f9e34ec4dfe4c84d5f14c3c1cd0e1074322..eb96eaabb1d8d368380dba27e278897d1d826bd8 100644 (file)
@@ -609,8 +609,7 @@ class InsertTest(fixtures.TestBase, AssertsExecutionResults):
                     (exc.IntegrityError, exc.ProgrammingError),
                     conn.execute,
                     table.insert(),
-                    {"data": "d2"},
-                    {"data": "d3"},
+                    [{"data": "d2"}, {"data": "d3"}],
                 )
             with expect_warnings(
                 ".*has no Python-side or server-side default.*"
@@ -628,14 +627,12 @@ class InsertTest(fixtures.TestBase, AssertsExecutionResults):
                     (exc.IntegrityError, exc.ProgrammingError),
                     conn.execute,
                     table.insert(),
-                    {"data": "d2"},
-                    {"data": "d3"},
+                    [{"data": "d2"}, {"data": "d3"}],
                 )
 
             conn.execute(
                 table.insert(),
-                {"id": 31, "data": "d2"},
-                {"id": 32, "data": "d3"},
+                [{"id": 31, "data": "d2"}, {"id": 32, "data": "d3"}],
             )
             conn.execute(table.insert(inline=True), {"id": 33, "data": "d4"})
             eq_(
@@ -668,13 +665,11 @@ class InsertTest(fixtures.TestBase, AssertsExecutionResults):
                     (exc.IntegrityError, exc.ProgrammingError),
                     conn.execute,
                     table.insert(),
-                    {"data": "d2"},
-                    {"data": "d3"},
+                    [{"data": "d2"}, {"data": "d3"}],
                 )
             conn.execute(
                 table.insert(),
-                {"id": 31, "data": "d2"},
-                {"id": 32, "data": "d3"},
+                [{"id": 31, "data": "d2"}, {"id": 32, "data": "d3"}],
             )
             conn.execute(table.insert(inline=True), {"id": 33, "data": "d4"})
             eq_(
index f09f0f1e127e494c4d4b91cad77ceda7292b94d4..62bac312b7a9cf6e738438e23d00cf80335aefc2 100644 (file)
@@ -28,6 +28,7 @@ from sqlalchemy.testing import is_
 from sqlalchemy.testing import is_false
 from sqlalchemy.testing import is_instance_of
 from sqlalchemy.testing import is_true
+from sqlalchemy.testing import mock
 from sqlalchemy.testing.engines import testing_engine
 from sqlalchemy.testing.mock import Mock
 from sqlalchemy.testing.schema import Column
@@ -397,6 +398,25 @@ class DeprecatedEngineFeatureTest(fixtures.TablesTest):
         with _string_deprecation_expect():
             testing.db.execute(select1(testing.db)).scalar()
 
+    def test_execute_plain_string_events(self):
+
+        m1 = Mock()
+        select1_str = select1(testing.db)
+        with _string_deprecation_expect():
+            with testing.db.connect() as conn:
+                event.listen(conn, "before_execute", m1.before_execute)
+                event.listen(conn, "after_execute", m1.after_execute)
+                result = conn.execute(select1_str)
+        eq_(
+            m1.mock_calls,
+            [
+                mock.call.before_execute(mock.ANY, select1_str, [], {}, {}),
+                mock.call.after_execute(
+                    mock.ANY, select1_str, [], {}, {}, result
+                ),
+            ],
+        )
+
     def test_scalar_plain_string(self):
         with _string_deprecation_expect():
             testing.db.scalar(select1(testing.db))
@@ -772,6 +792,76 @@ class RawExecuteTest(fixtures.TablesTest):
             ]
 
 
+class DeprecatedExecParamsTest(fixtures.TablesTest):
+    __backend__ = True
+
+    @classmethod
+    def define_tables(cls, metadata):
+        Table(
+            "users",
+            metadata,
+            Column("user_id", INT, primary_key=True, autoincrement=False),
+            Column("user_name", VARCHAR(20)),
+        )
+
+        Table(
+            "users_autoinc",
+            metadata,
+            Column(
+                "user_id", INT, primary_key=True, test_needs_autoincrement=True
+            ),
+            Column("user_name", VARCHAR(20)),
+        )
+
+    def test_kwargs(self, connection):
+        users = self.tables.users
+
+        with testing.expect_deprecated_20(
+            r"The connection.execute\(\) method in "
+            "SQLAlchemy 2.0 will accept parameters as a single "
+        ):
+            connection.execute(
+                users.insert(), user_id=5, user_name="some name"
+            )
+
+        eq_(connection.execute(select(users)).all(), [(5, "some name")])
+
+    def test_positional_dicts(self, connection):
+        users = self.tables.users
+
+        with testing.expect_deprecated_20(
+            r"The connection.execute\(\) method in "
+            "SQLAlchemy 2.0 will accept parameters as a single "
+        ):
+            connection.execute(
+                users.insert(),
+                {"user_id": 5, "user_name": "some name"},
+                {"user_id": 6, "user_name": "some other name"},
+            )
+
+        eq_(
+            connection.execute(select(users).order_by(users.c.user_id)).all(),
+            [(5, "some name"), (6, "some other name")],
+        )
+
+    def test_single_scalar(self, connection):
+
+        users = self.tables.users_autoinc
+
+        with testing.expect_deprecated_20(
+            r"The connection.execute\(\) method in "
+            "SQLAlchemy 2.0 will accept parameters as a single "
+        ):
+            # TODO: I'm not even sure what this exec format is or how
+            # it worked if at all
+            connection.execute(users.insert(), "some name")
+
+        eq_(
+            connection.execute(select(users).order_by(users.c.user_id)).all(),
+            [(1, None)],
+        )
+
+
 class EngineEventsTest(fixtures.TestBase):
     __requires__ = ("ad_hoc_engines",)
     __backend__ = True
index fd42224ebb5c7db829edb04ee28ea5576581b5df..5b922a97d8cf2403d71fe84e719338456f3ee30d 100644 (file)
@@ -34,6 +34,7 @@ from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import config
 from sqlalchemy.testing import engines
 from sqlalchemy.testing import eq_
+from sqlalchemy.testing import expect_raises_message
 from sqlalchemy.testing import expect_warnings
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import is_
@@ -1420,6 +1421,18 @@ class EngineEventsTest(fixtures.TestBase):
             eq_(canary.be1.call_count, 2)
             eq_(canary.be2.call_count, 2)
 
+    def test_new_exec_driver_sql_no_events(self):
+        m1 = Mock()
+
+        def select1(db):
+            return str(select([1]).compile(dialect=db.dialect))
+
+        with testing.db.connect() as conn:
+            event.listen(conn, "before_execute", m1.before_execute)
+            event.listen(conn, "after_execute", m1.after_execute)
+            conn.exec_driver_sql(select1(testing.db))
+        eq_(m1.mock_calls, [])
+
     def test_add_event_after_connect(self):
         # new feature as of #2978
         canary = Mock()
@@ -1504,6 +1517,83 @@ class EngineEventsTest(fixtures.TestBase):
             [call(conn, ctx.cursor, stmt, ctx.parameters[0], ctx, False)],
         )
 
+    @testing.combinations(
+        (
+            ([{"x": 5, "y": 10}, {"x": 8, "y": 9}],),
+            {},
+            [{"x": 5, "y": 10}, {"x": 8, "y": 9}],
+            {},
+        ),
+        ((), {"z": 10}, [], {"z": 10}, testing.requires.legacy_engine),
+        (({"z": 10},), {}, [], {"z": 10}),
+    )
+    def test_modify_parameters_from_event_one(
+        self, multiparams, params, expected_multiparams, expected_params
+    ):
+        # this is testing both the normalization added to parameters
+        # as of I97cb4d06adfcc6b889f10d01cc7775925cffb116 as well as
+        # that the return value from the event is taken as the new set
+        # of parameters.
+        def before_execute(
+            conn, clauseelement, multiparams, params, execution_options
+        ):
+            eq_(multiparams, expected_multiparams)
+            eq_(params, expected_params)
+            return clauseelement, (), {"q": "15"}
+
+        def after_execute(
+            conn, clauseelement, multiparams, params, result, execution_options
+        ):
+            eq_(multiparams, ())
+            eq_(params, {"q": "15"})
+
+        e1 = testing_engine(config.db_url)
+        event.listen(e1, "before_execute", before_execute, retval=True)
+        event.listen(e1, "after_execute", after_execute)
+
+        with e1.connect() as conn:
+            result = conn.execute(
+                select(bindparam("q", type_=String)), *multiparams, **params
+            )
+            eq_(result.all(), [("15",)])
+
+    @testing.provide_metadata
+    def test_modify_parameters_from_event_two(self, connection):
+        t = Table("t", self.metadata, Column("q", Integer))
+
+        t.create(connection)
+
+        def before_execute(
+            conn, clauseelement, multiparams, params, execution_options
+        ):
+            return clauseelement, [{"q": 15}, {"q": 19}], {}
+
+        event.listen(connection, "before_execute", before_execute, retval=True)
+        connection.execute(t.insert(), {"q": 12})
+        event.remove(connection, "before_execute", before_execute)
+
+        eq_(
+            connection.execute(select(t).order_by(t.c.q)).fetchall(),
+            [(15,), (19,)],
+        )
+
+    def test_modify_parameters_from_event_three(self, connection):
+        def before_execute(
+            conn, clauseelement, multiparams, params, execution_options
+        ):
+            return clauseelement, [{"q": 15}, {"q": 19}], {"q": 7}
+
+        e1 = testing_engine(config.db_url)
+        event.listen(e1, "before_execute", before_execute, retval=True)
+
+        with expect_raises_message(
+            tsa.exc.InvalidRequestError,
+            "Event handler can't return non-empty multiparams "
+            "and params at the same time",
+        ):
+            with e1.connect() as conn:
+                conn.execute(select(literal("1")))
+
     def test_argument_format_execute(self):
         def before_execute(
             conn, clauseelement, multiparams, params, execution_options
@@ -1591,30 +1681,13 @@ class EngineEventsTest(fixtures.TestBase):
                 if ctx:
                     ctx.close()
 
-            if engine._is_future:
-                compiled = [
-                    ("CREATE TABLE t1", {}, None),
-                    (
-                        "INSERT INTO t1 (c1, c2)",
-                        {"c2": "some data", "c1": 5},
-                        None,
-                    ),
-                    ("INSERT INTO t1 (c1, c2)", {"c1": 6}, None),
-                    ("select * from t1", {}, None),
-                    ("DROP TABLE t1", {}, None),
-                ]
-            else:
-                compiled = [
-                    ("CREATE TABLE t1", {}, None),
-                    (
-                        "INSERT INTO t1 (c1, c2)",
-                        {},
-                        ({"c2": "some data", "c1": 5},),
-                    ),
-                    ("INSERT INTO t1 (c1, c2)", {}, ({"c1": 6},)),
-                    ("select * from t1", {}, None),
-                    ("DROP TABLE t1", {}, None),
-                ]
+            compiled = [
+                ("CREATE TABLE t1", {}, None),
+                ("INSERT INTO t1 (c1, c2)", {"c2": "some data", "c1": 5}, (),),
+                ("INSERT INTO t1 (c1, c2)", {"c1": 6}, ()),
+                ("select * from t1", {}, None),
+                ("DROP TABLE t1", {}, None),
+            ]
 
             cursor = [
                 ("CREATE TABLE t1", {}, ()),
index 9bfd8d505d8b51f833da410a0ad27aa20b38328c..3810de06a5c8d304116893f7d4a70bc0151225d9 100644 (file)
@@ -1,6 +1,7 @@
 from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import fixtures
+from sqlalchemy.testing import mock
 
 
 class _BooleanProcessorTest(fixtures.TestBase):
@@ -107,70 +108,77 @@ class CDateProcessorTest(_DateProcessorTest):
 
 class _DistillArgsTest(fixtures.TestBase):
     def test_distill_none(self):
-        eq_(self.module._distill_params(None, None), [])
+        eq_(self.module._distill_params(mock.Mock(), None, None), [])
 
     def test_distill_no_multi_no_param(self):
-        eq_(self.module._distill_params((), {}), [])
+        eq_(self.module._distill_params(mock.Mock(), (), {}), [])
 
     def test_distill_dict_multi_none_param(self):
         eq_(
-            self.module._distill_params(None, {"foo": "bar"}), [{"foo": "bar"}]
+            self.module._distill_params(mock.Mock(), None, {"foo": "bar"}),
+            [{"foo": "bar"}],
         )
 
     def test_distill_dict_multi_empty_param(self):
-        eq_(self.module._distill_params((), {"foo": "bar"}), [{"foo": "bar"}])
+        eq_(
+            self.module._distill_params(mock.Mock(), (), {"foo": "bar"}),
+            [{"foo": "bar"}],
+        )
 
     def test_distill_single_dict(self):
         eq_(
-            self.module._distill_params(({"foo": "bar"},), {}),
+            self.module._distill_params(mock.Mock(), ({"foo": "bar"},), {}),
             [{"foo": "bar"}],
         )
 
     def test_distill_single_list_strings(self):
         eq_(
-            self.module._distill_params((["foo", "bar"],), {}),
+            self.module._distill_params(mock.Mock(), (["foo", "bar"],), {}),
             [["foo", "bar"]],
         )
 
     def test_distill_single_list_tuples(self):
         eq_(
             self.module._distill_params(
-                ([("foo", "bar"), ("bat", "hoho")],), {}
+                mock.Mock(), ([("foo", "bar"), ("bat", "hoho")],), {}
             ),
             [("foo", "bar"), ("bat", "hoho")],
         )
 
     def test_distill_single_list_tuple(self):
         eq_(
-            self.module._distill_params(([("foo", "bar")],), {}),
+            self.module._distill_params(mock.Mock(), ([("foo", "bar")],), {}),
             [("foo", "bar")],
         )
 
     def test_distill_multi_list_tuple(self):
         eq_(
             self.module._distill_params(
-                ([("foo", "bar")], [("bar", "bat")]), {}
+                mock.Mock(), ([("foo", "bar")], [("bar", "bat")]), {}
             ),
             ([("foo", "bar")], [("bar", "bat")]),
         )
 
     def test_distill_multi_strings(self):
-        eq_(self.module._distill_params(("foo", "bar"), {}), [("foo", "bar")])
+        eq_(
+            self.module._distill_params(mock.Mock(), ("foo", "bar"), {}),
+            [("foo", "bar")],
+        )
 
     def test_distill_single_list_dicts(self):
         eq_(
             self.module._distill_params(
-                ([{"foo": "bar"}, {"foo": "hoho"}],), {}
+                mock.Mock(), ([{"foo": "bar"}, {"foo": "hoho"}],), {}
             ),
             [{"foo": "bar"}, {"foo": "hoho"}],
         )
 
     def test_distill_single_string(self):
-        eq_(self.module._distill_params(("arg",), {}), [["arg"]])
+        eq_(self.module._distill_params(mock.Mock(), ("arg",), {}), [["arg"]])
 
     def test_distill_multi_string_tuple(self):
         eq_(
-            self.module._distill_params((("arg", "arg"),), {}),
+            self.module._distill_params(mock.Mock(), (("arg", "arg"),), {}),
             [("arg", "arg")],
         )
 
index f2aaeb07351389b61a4a2c98ee7ef803b54ac7f4..19db8b1486a7658bd92dd954d4379e6537a5ba40 100644 (file)
@@ -1,15 +1,15 @@
 # /home/classic/dev/sqlalchemy/test/profiles.txt
 # This file is written out on a per-environment basis.
-# For each test in aaa_profiling, the corresponding function and
+# For each test in aaa_profiling, the corresponding function and 
 # environment is located within this file.  If it doesn't exist,
 # the test is skipped.
-# If a callcount does exist, it is compared to what we received.
+# If a callcount does exist, it is compared to what we received. 
 # assertions are raised if the counts do not match.
-#
-# To add a new callcount test, apply the function_call_count
-# decorator and re-run the tests using the --write-profiles
+# 
+# To add a new callcount test, apply the function_call_count 
+# decorator and re-run the tests using the --write-profiles 
 # option - this file will be rewritten including the new count.
-#
+# 
 
 # TEST: test.aaa_profiling.test_compiler.CompileTest.test_insert
 
@@ -394,6 +394,7 @@ test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 51
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 49
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 51
+test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 54
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 53
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 53
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 53
@@ -421,6 +422,7 @@ test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute x86_
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 91
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 89
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 91
+test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 92
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 91
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 91
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 91
@@ -448,6 +450,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile x86
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 16
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 16
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 16
+test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 17
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 17
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 17
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 17
@@ -475,6 +478,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_legacy x86_64_
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_legacy x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 13507
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_legacy x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 1458
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_legacy x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 13460
+test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_legacy x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 1547
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_legacy x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 1509
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_legacy x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 13512
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_legacy x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 1516
@@ -502,6 +506,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_6
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 15512
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 2465
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 15467
+test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 2553
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 2517
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 15520
 test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 2524
@@ -529,6 +534,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-0] x86_64
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-0] x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 14
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-0] x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 14
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-0] x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 14
+test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-0] x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 23
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-0] x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 15
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-0] x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 15
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-0] x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 23
@@ -556,6 +562,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 16
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 14
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 16
+test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 23
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 15
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 17
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 23
@@ -583,6 +590,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 16
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 14
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 16
+test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 23
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 15
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 17
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 23
@@ -610,6 +618,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 19
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 17
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 19
+test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 28
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 18
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 20
 test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 28
@@ -637,6 +646,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpy
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 6302
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 251
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 6271
+test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 263
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 256
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 6256
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 288
@@ -647,7 +657,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpy
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.8_oracle_cx_oracle_dbapiunicode_nocextensions 6345
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.8_postgresql_psycopg2_dbapiunicode_cextensions 278
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.8_postgresql_psycopg2_dbapiunicode_nocextensions 6278
-test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.8_sqlite_pysqlite_dbapiunicode_cextensions 245
+test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.8_sqlite_pysqlite_dbapiunicode_cextensions 222
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.8_sqlite_pysqlite_dbapiunicode_nocextensions 6245
 
 # TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode
@@ -664,6 +674,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cp
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 6302
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 251
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 6271
+test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 263
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 256
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 6256
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 288
@@ -674,7 +685,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cp
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.8_oracle_cx_oracle_dbapiunicode_nocextensions 6345
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.8_postgresql_psycopg2_dbapiunicode_cextensions 278
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.8_postgresql_psycopg2_dbapiunicode_nocextensions 6278
-test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.8_sqlite_pysqlite_dbapiunicode_cextensions 245
+test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.8_sqlite_pysqlite_dbapiunicode_cextensions 222
 test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.8_sqlite_pysqlite_dbapiunicode_nocextensions 6245
 
 # TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_string
@@ -691,6 +702,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython
 test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 6505
 test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 458
 test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 6460
+test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 548
 test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 521
 test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 6521
 test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 528
@@ -718,6 +730,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpytho
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 6505
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 458
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 6460
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 548
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 521
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 6521
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 528