]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Reduce import time overhead
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 2 Nov 2020 22:37:12 +0000 (17:37 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 3 Nov 2020 18:56:02 +0000 (13:56 -0500)
* Fix subclass traversals to not run classes multiple times

* switch compiler visitor to use an attrgetter, to avoid
  an eval() at startup time

* don't pre-generate traversal functions, there's lots of these
  which are expensive to generate at once and most applications
  won't use them all; have it generate them on first use instead

* Some ideas about removing asyncio imports, they don't seem to
  be too signficant, apply some more simplicity to the overall
  "greenlet fallback" situation

Fixes: #5681
Change-Id: Ib564ddaddb374787ce3e11ff48026e99ed570933

13 files changed:
lib/sqlalchemy/engine/create.py
lib/sqlalchemy/event/attr.py
lib/sqlalchemy/sql/__init__.py
lib/sqlalchemy/sql/annotation.py
lib/sqlalchemy/sql/traversals.py
lib/sqlalchemy/sql/visitors.py
lib/sqlalchemy/util/__init__.py
lib/sqlalchemy/util/_concurrency_py3k.py
lib/sqlalchemy/util/_preloaded.py
lib/sqlalchemy/util/concurrency.py
lib/sqlalchemy/util/langhelpers.py
test/aaa_profiling/test_orm.py
test/profiles.txt

index 365a72a96138bd6f8702a865939b3d33843cfa72..4d84687ea323d08ab1457f89287db7bd5cb9c071 100644 (file)
@@ -92,7 +92,7 @@ def create_engine(url, **kwargs):
 
         :ref:`connections_toplevel`
 
-    :param case_sensitive=True: if False, result column names
+    :param case_sensitive: if False, result column names
        will match in a case-insensitive fashion, that is,
        ``row['SomeColumn']``.
 
index baa3cd28a6d2435fa9b967d39772849a138e0cd8..122221d4074a51d8e6ef8475b4756c310430b708 100644 (file)
@@ -127,10 +127,8 @@ class _ClsLevelDispatch(RefCollection):
             raise exc.InvalidRequestError(
                 "Can't assign an event directly to the %s class" % target
             )
-        stack = [target]
-        while stack:
-            cls = stack.pop(0)
-            stack.extend(cls.__subclasses__())
+
+        for cls in util.walk_subclasses(target):
             if cls is not target and cls not in self._clslevel:
                 self.update_subclass(cls)
             else:
@@ -148,10 +146,7 @@ class _ClsLevelDispatch(RefCollection):
             raise exc.InvalidRequestError(
                 "Can't assign an event directly to the %s class" % target
             )
-        stack = [target]
-        while stack:
-            cls = stack.pop(0)
-            stack.extend(cls.__subclasses__())
+        for cls in util.walk_subclasses(target):
             if cls is not target and cls not in self._clslevel:
                 self.update_subclass(cls)
             else:
@@ -178,10 +173,7 @@ class _ClsLevelDispatch(RefCollection):
 
     def remove(self, event_key):
         target = event_key.dispatch_target
-        stack = [target]
-        while stack:
-            cls = stack.pop(0)
-            stack.extend(cls.__subclasses__())
+        for cls in util.walk_subclasses(target):
             if cls in self._clslevel:
                 self._clslevel[cls].remove(event_key._listen_fn)
         registry._removed_from_collection(event_key, self)
index 8cfd20054e4a3d3e36dca5c8698bd506218a27a5..645189e76e68edb329824f0a551de4af4fa90d8c 100644 (file)
@@ -55,10 +55,10 @@ from .expression import literal_column  # noqa
 from .expression import modifier  # noqa
 from .expression import not_  # noqa
 from .expression import null  # noqa
-from .expression import nullsfirst  # noqa; deprecated 1.4; see #5435
-from .expression import nullslast  # noqa; deprecated 1.4; see #5435
 from .expression import nulls_first  # noqa
 from .expression import nulls_last  # noqa
+from .expression import nullsfirst  # noqa
+from .expression import nullslast  # noqa
 from .expression import or_  # noqa
 from .expression import outerjoin  # noqa
 from .expression import outparam  # noqa
@@ -106,7 +106,8 @@ def __go(lcls):
     from .elements import AnnotatedColumnElement
     from .elements import ClauseList  # noqa
     from .selectable import AnnotatedFromClause  # noqa
-    from .traversals import _preconfigure_traversals
+
+    # from .traversals import _preconfigure_traversals
 
     from . import base
     from . import coercions
@@ -133,7 +134,9 @@ def __go(lcls):
     _prepare_annotations(FromClause, AnnotatedFromClause)
     _prepare_annotations(ClauseList, Annotated)
 
-    _preconfigure_traversals(ClauseElement)
+    # this is expensive at import time; elements that are used can create
+    # their traversals on demand
+    # _preconfigure_traversals(ClauseElement)
 
     _sa_util.preloaded.import_prefix("sqlalchemy.sql")
 
index 8a0d6ec280ce656b823edba97ec01503d3b73129..94d37573ce9da38be55270c5072ced05bb0a64f3 100644 (file)
@@ -354,9 +354,5 @@ def _new_annotation_type(cls, base_cls):
 
 
 def _prepare_annotations(target_hierarchy, base_cls):
-    stack = [target_hierarchy]
-    while stack:
-        cls = stack.pop()
-        stack.extend(cls.__subclasses__())
-
+    for cls in util.walk_subclasses(target_hierarchy):
         _new_annotation_type(cls, base_cls)
index cb38df6afa2fb29a65fd22ee807bd5cf6fcdff08..a24d010cddc31aa18cdad006ecdf6963d567df62 100644 (file)
@@ -33,12 +33,7 @@ def compare(obj1, obj2, **kw):
 
 
 def _preconfigure_traversals(target_hierarchy):
-
-    stack = [target_hierarchy]
-    while stack:
-        cls = stack.pop()
-        stack.extend(cls.__subclasses__())
-
+    for cls in util.walk_subclasses(target_hierarchy):
         if hasattr(cls, "_traverse_internals"):
             cls._generate_cache_attrs()
             _copy_internals.generate_dispatch(
index 27499b5f7103026b161535ce9f659f5d02cf89af..43b7cb4afe307a5567cf3ce4032168823fc2a8a9 100644 (file)
@@ -24,6 +24,7 @@ http://techspot.zzzeek.org/2008/01/23/expression-transformations/ .
 """
 
 from collections import deque
+import operator
 
 from .. import exc
 from .. import util
@@ -63,26 +64,24 @@ def _generate_compiler_dispatch(cls):
             % cls.__name__
         )
 
-    code = (
-        "def _compiler_dispatch(self, visitor, **kw):\n"
-        "    try:\n"
-        "        meth = visitor.visit_%(name)s\n"
-        "    except AttributeError as err:\n"
-        "        util.raise_(\n"
-        "            exc.UnsupportedCompilationError(visitor, cls), \n"
-        "            replace_context=err)\n"
-        "    else:\n"
-        "        return meth(self, **kw)\n"
-    ) % {"name": visit_name}
-
-    _compiler_dispatch = langhelpers._exec_code_in_env(
-        code, {"exc": exc, "cls": cls, "util": util}, "_compiler_dispatch"
-    )
+    name = "visit_%s" % visit_name
+    getter = operator.attrgetter(name)
+
+    def _compiler_dispatch(self, visitor, **kw):
+        """Look for an attribute named "visit_<visit_name>" on the
+        visitor, and call it with the same kw params.
 
-    _compiler_dispatch.__doc__ = """Look for an attribute named "visit_"
-        + self.__visit_name__ on the visitor, and call it with the same
-        kw params.
         """
+        try:
+            meth = getter(visitor)
+        except AttributeError as err:
+            util.raise_(
+                exc.UnsupportedCompilationError(visitor, cls),
+                replace_context=err,
+            )
+        else:
+            return meth(self, **kw)
+
     cls._compiler_dispatch = (
         cls._original_compiler_dispatch
     ) = _compiler_dispatch
index 00066cbed8fedb7c4c71fd5b3c8705542711351d..7c5257b875d2d776fbfac77995bbead8225c8450 100644 (file)
@@ -157,6 +157,7 @@ from .langhelpers import set_creation_order  # noqa
 from .langhelpers import string_or_unprintable  # noqa
 from .langhelpers import symbol  # noqa
 from .langhelpers import unbound_method_to_callable  # noqa
+from .langhelpers import walk_subclasses  # noqa
 from .langhelpers import warn  # noqa
 from .langhelpers import warn_exception  # noqa
 from .langhelpers import warn_limited  # noqa
index ee3abe5fa9962a563a6f1035beb6a23c0192b33a..5d11bf92c16e1ad5806b715cf72a9637d73404b1 100644 (file)
@@ -4,110 +4,103 @@ from typing import Any
 from typing import Callable
 from typing import Coroutine
 
+import greenlet
+
 from .. import exc
 
-try:
-    import greenlet
 
-    # implementation based on snaury gist at
-    # https://gist.github.com/snaury/202bf4f22c41ca34e56297bae5f33fef
-    # Issue for context: https://github.com/python-greenlet/greenlet/issues/173
+# implementation based on snaury gist at
+# https://gist.github.com/snaury/202bf4f22c41ca34e56297bae5f33fef
+# Issue for context: https://github.com/python-greenlet/greenlet/issues/173
+
+
+class _AsyncIoGreenlet(greenlet.greenlet):
+    def __init__(self, fn, driver):
+        greenlet.greenlet.__init__(self, fn, driver)
+        self.driver = driver
+
+
+def await_only(awaitable: Coroutine) -> Any:
+    """Awaits an async function in a sync method.
+
+    The sync method must be insice a :func:`greenlet_spawn` context.
+    :func:`await_` calls cannot be nested.
 
-    class _AsyncIoGreenlet(greenlet.greenlet):
-        def __init__(self, fn, driver):
-            greenlet.greenlet.__init__(self, fn, driver)
-            self.driver = driver
+    :param awaitable: The coroutine to call.
 
-    def await_only(awaitable: Coroutine) -> Any:
-        """Awaits an async function in a sync method.
+    """
+    # this is called in the context greenlet while running fn
+    current = greenlet.getcurrent()
+    if not isinstance(current, _AsyncIoGreenlet):
+        raise exc.InvalidRequestError(
+            "greenlet_spawn has not been called; can't call await_() here."
+        )
 
-        The sync method must be insice a :func:`greenlet_spawn` context.
-        :func:`await_` calls cannot be nested.
+    # returns the control to the driver greenlet passing it
+    # a coroutine to run. Once the awaitable is done, the driver greenlet
+    # switches back to this greenlet with the result of awaitable that is
+    # then returned to the caller (or raised as error)
+    return current.driver.switch(awaitable)
 
-        :param awaitable: The coroutine to call.
 
-        """
-        # this is called in the context greenlet while running fn
-        current = greenlet.getcurrent()
-        if not isinstance(current, _AsyncIoGreenlet):
+def await_fallback(awaitable: Coroutine) -> Any:
+    """Awaits an async function in a sync method.
+
+    The sync method must be insice a :func:`greenlet_spawn` context.
+    :func:`await_` calls cannot be nested.
+
+    :param awaitable: The coroutine to call.
+
+    """
+
+    # this is called in the context greenlet while running fn
+    current = greenlet.getcurrent()
+    if not isinstance(current, _AsyncIoGreenlet):
+        loop = asyncio.get_event_loop()
+        if loop.is_running():
             raise exc.InvalidRequestError(
-                "greenlet_spawn has not been called; can't call await_() here."
+                "greenlet_spawn has not been called and asyncio event "
+                "loop is already running; can't call await_() here."
             )
-
-        # returns the control to the driver greenlet passing it
-        # a coroutine to run. Once the awaitable is done, the driver greenlet
-        # switches back to this greenlet with the result of awaitable that is
-        # then returned to the caller (or raised as error)
-        return current.driver.switch(awaitable)
-
-    def await_fallback(awaitable: Coroutine) -> Any:
-        """Awaits an async function in a sync method.
-
-        The sync method must be insice a :func:`greenlet_spawn` context.
-        :func:`await_` calls cannot be nested.
-
-        :param awaitable: The coroutine to call.
-
-        """
-        # this is called in the context greenlet while running fn
-        current = greenlet.getcurrent()
-        if not isinstance(current, _AsyncIoGreenlet):
-            loop = asyncio.get_event_loop()
-            if loop.is_running():
-                raise exc.InvalidRequestError(
-                    "greenlet_spawn has not been called and asyncio event "
-                    "loop is already running; can't call await_() here."
-                )
-            return loop.run_until_complete(awaitable)
-
-        return current.driver.switch(awaitable)
-
-    async def greenlet_spawn(fn: Callable, *args, **kwargs) -> Any:
-        """Runs a sync function ``fn`` in a new greenlet.
-
-        The sync function can then use :func:`await_` to wait for async
-        functions.
-
-        :param fn: The sync callable to call.
-        :param \\*args: Positional arguments to pass to the ``fn`` callable.
-        :param \\*\\*kwargs: Keyword arguments to pass to the ``fn`` callable.
-        """
-        context = _AsyncIoGreenlet(fn, greenlet.getcurrent())
-        # runs the function synchronously in gl greenlet. If the execution
-        # is interrupted by await_, context is not dead and result is a
-        # coroutine to wait. If the context is dead the function has
-        # returned, and its result can be returned.
-        try:
-            result = context.switch(*args, **kwargs)
-            while not context.dead:
-                try:
-                    # wait for a coroutine from await_ and then return its
-                    # result back to it.
-                    value = await result
-                except Exception:
-                    # this allows an exception to be raised within
-                    # the moderated greenlet so that it can continue
-                    # its expected flow.
-                    result = context.throw(*sys.exc_info())
-                else:
-                    result = context.switch(value)
-        finally:
-            # clean up to avoid cycle resolution by gc
-            del context.driver
-        return result
-
-
-except ImportError:  # pragma: no cover
-    greenlet = None
-
-    def await_fallback(awaitable):
-        return asyncio.get_event_loop().run_until_complete(awaitable)
-
-    def await_only(awaitable):
-        raise ValueError("Greenlet is required to use this function")
-
-    async def greenlet_spawn(fn, *args, **kw):
-        raise ValueError("Greenlet is required to use this function")
+        return loop.run_until_complete(awaitable)
+
+    return current.driver.switch(awaitable)
+
+
+async def greenlet_spawn(fn: Callable, *args, **kwargs) -> Any:
+    """Runs a sync function ``fn`` in a new greenlet.
+
+    The sync function can then use :func:`await_` to wait for async
+    functions.
+
+    :param fn: The sync callable to call.
+    :param \\*args: Positional arguments to pass to the ``fn`` callable.
+    :param \\*\\*kwargs: Keyword arguments to pass to the ``fn`` callable.
+    """
+
+    context = _AsyncIoGreenlet(fn, greenlet.getcurrent())
+    # runs the function synchronously in gl greenlet. If the execution
+    # is interrupted by await_, context is not dead and result is a
+    # coroutine to wait. If the context is dead the function has
+    # returned, and its result can be returned.
+    try:
+        result = context.switch(*args, **kwargs)
+        while not context.dead:
+            try:
+                # wait for a coroutine from await_ and then return its
+                # result back to it.
+                value = await result
+            except Exception:
+                # this allows an exception to be raised within
+                # the moderated greenlet so that it can continue
+                # its expected flow.
+                result = context.throw(*sys.exc_info())
+            else:
+                result = context.switch(value)
+    finally:
+        # clean up to avoid cycle resolution by gc
+        del context.driver
+    return result
 
 
 class AsyncAdaptedLock:
index 1a833a963c0c5f35efff85d86d3b0bc86fb2591e..e267c008c1e1d44d1234ba8eb0cdf924a5f499dd 100644 (file)
@@ -35,8 +35,9 @@ class _ModuleRegistry:
     name. Example: ``sqlalchemy.sql.util`` becomes ``preloaded.sql_util``.
     """
 
-    def __init__(self, prefix="sqlalchemy"):
+    def __init__(self, prefix="sqlalchemy."):
         self.module_registry = set()
+        self.prefix = prefix
 
     def preload_module(self, *deps):
         """Adds the specified modules to the list to load.
@@ -52,8 +53,13 @@ class _ModuleRegistry:
         specified path.
         """
         for module in self.module_registry:
-            key = module.split("sqlalchemy.")[-1].replace(".", "_")
-            if module.startswith(path) and key not in self.__dict__:
+            if self.prefix:
+                key = module.split(self.prefix)[-1].replace(".", "_")
+            else:
+                key = module
+            if (
+                not path or module.startswith(path)
+            ) and key not in self.__dict__:
                 compat.import_(module, globals(), locals())
                 self.__dict__[key] = sys.modules[module]
 
index e0883aa6835fc67a6d0eae6a67518527d67b91bf..f78c0971c786dfb56a04ffd722ef5527e3d530e9 100644 (file)
@@ -1,25 +1,40 @@
 from . import compat
 
+have_greenlet = False
 
 if compat.py3k:
-    import asyncio
-    from ._concurrency_py3k import await_only
-    from ._concurrency_py3k import await_fallback
-    from ._concurrency_py3k import greenlet
-    from ._concurrency_py3k import greenlet_spawn
-    from ._concurrency_py3k import AsyncAdaptedLock
-else:
-    asyncio = None
-    greenlet = None
-
-    def await_only(thing):
+    try:
+        import greenlet  # noqa F401
+    except ImportError:
+        pass
+    else:
+        have_greenlet = True
+        from ._concurrency_py3k import await_only
+        from ._concurrency_py3k import await_fallback
+        from ._concurrency_py3k import greenlet_spawn
+        from ._concurrency_py3k import AsyncAdaptedLock
+        from ._concurrency_py3k import asyncio  # noqa F401
+
+if not have_greenlet:
+
+    asyncio = None  # noqa F811
+
+    def _not_implemented():
+        if not compat.py3k:
+            raise ValueError("Cannot use this function in py2.")
+        else:
+            raise ValueError(
+                "the greenlet library is required to use this function."
+            )
+
+    def await_only(thing):  # noqa F811
         return thing
 
-    def await_fallback(thing):
+    def await_fallback(thing):  # noqa F81
         return thing
 
-    def greenlet_spawn(fn, *args, **kw):
-        raise ValueError("Cannot use this function in py2.")
+    def greenlet_spawn(fn, *args, **kw):  # noqa F81
+        _not_implemented()
 
-    def AsyncAdaptedLock(*args, **kw):
-        raise ValueError("Cannot use this function in py2.")
+    def AsyncAdaptedLock(*args, **kw):  # noqa F81
+        _not_implemented()
index 4289db81295eeed22ac8510957955e683dc46e3b..ce3a2849936d55d005e81c500c7db91515a51300 100644 (file)
@@ -10,6 +10,7 @@ modules, classes, hierarchies, attributes, functions, and methods.
 
 """
 
+import collections
 from functools import update_wrapper
 import hashlib
 import inspect
@@ -83,6 +84,20 @@ class safe_reraise(object):
             compat.raise_(value, with_traceback=traceback)
 
 
+def walk_subclasses(cls):
+    seen = set()
+
+    stack = [cls]
+    while stack:
+        cls = stack.pop()
+        if cls in seen:
+            continue
+        else:
+            seen.add(cls)
+        stack.extend(cls.__subclasses__())
+        yield cls
+
+
 def string_or_unprintable(element):
     if isinstance(element, compat.string_types):
         return element
@@ -1782,15 +1797,22 @@ def inject_docstring_text(doctext, injecttext, pos):
     return "\n".join(lines)
 
 
+_param_reg = re.compile(r"(\s+):param (.+?):")
+
+
 def inject_param_text(doctext, inject_params):
-    doclines = doctext.splitlines()
+    doclines = collections.deque(doctext.splitlines())
     lines = []
 
+    # TODO: this is not working for params like ":param case_sensitive=True:"
+
     to_inject = None
     while doclines:
-        line = doclines.pop(0)
+        line = doclines.popleft()
+
+        m = _param_reg.match(line)
+
         if to_inject is None:
-            m = re.match(r"(\s+):param (.+?):", line)
             if m:
                 param = m.group(2).lstrip("*")
                 if param in inject_params:
@@ -1805,23 +1827,16 @@ def inject_param_text(doctext, inject_params):
                             indent = " " * len(m2.group(1))
 
                     to_inject = indent + inject_params[param]
-        elif line.lstrip().startswith(":param "):
-            lines.append("\n")
-            lines.append(to_inject)
-            lines.append("\n")
+        elif m:
+            lines.extend(["\n", to_inject, "\n"])
             to_inject = None
         elif not line.rstrip():
-            lines.append(line)
-            lines.append(to_inject)
-            lines.append("\n")
+            lines.extend([line, to_inject, "\n"])
             to_inject = None
         elif line.endswith("::"):
             # TODO: this still wont cover if the code example itself has blank
             # lines in it, need to detect those via indentation.
-            lines.append(line)
-            lines.append(
-                doclines.pop(0)
-            )  # the blank line following a code example
+            lines.extend([line, doclines.popleft()])
             continue
         lines.append(line)
 
index 4bc2af93d3c7ed71141270788950a5f3b6f9d831..1bbfd36586c8f56c74ab280519719e66fa47b71f 100644 (file)
@@ -864,7 +864,7 @@ class JoinedEagerLoadTest(NoCache, fixtures.MappedTest):
 
         from sqlalchemy.orm.context import ORMCompileState
 
-        @profiling.function_call_count()
+        @profiling.function_call_count(warmup=1)
         def go():
             for i in range(100):
                 # NOTE: this test was broken in
index 4a21de4273b82640955509368e9eb57af11c81f2..08cdf911b9cbfae4732c18ad2dc5dbccfefbc353 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
 
@@ -153,34 +153,10 @@ test.aaa_profiling.test_compiler.CompileTest.test_update x86_64_linux_cpython_3.
 
 # TEST: test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause
 
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_mysqldb_dbapiunicode_cextensions 159
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_mysqldb_dbapiunicode_nocextensions 159
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_pymysql_dbapiunicode_cextensions 159
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_pymysql_dbapiunicode_nocextensions 159
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mssql_pyodbc_dbapiunicode_cextensions 159
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mssql_pyodbc_dbapiunicode_nocextensions 159
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mysql_mysqldb_dbapiunicode_cextensions 152
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mysql_mysqldb_dbapiunicode_nocextensions 152
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mysql_pymysql_dbapiunicode_cextensions 152
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mysql_pymysql_dbapiunicode_nocextensions 152
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_oracle_cx_oracle_dbapiunicode_cextensions 157
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_oracle_cx_oracle_dbapiunicode_nocextensions 159
 test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_cextensions 159
 test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 159
 test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 159
 test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 159
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 165
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_nocextensions 165
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_pymysql_dbapiunicode_cextensions 165
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_pymysql_dbapiunicode_nocextensions 165
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 165
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 165
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 158
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_nocextensions 158
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mysql_pymysql_dbapiunicode_cextensions 158
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mysql_pymysql_dbapiunicode_nocextensions 158
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_oracle_cx_oracle_dbapiunicode_cextensions 163
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_oracle_cx_oracle_dbapiunicode_nocextensions 163
 test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_postgresql_psycopg2_dbapiunicode_cextensions 165
 test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_postgresql_psycopg2_dbapiunicode_nocextensions 165
 test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_sqlite_pysqlite_dbapiunicode_cextensions 165