]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Convert lazy loader, selectinload, load_on_ident to lambda statements
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 5 Aug 2020 21:58:48 +0000 (17:58 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 5 Aug 2020 22:53:17 +0000 (18:53 -0400)
Building on newly robust lambdas in
I29a513c98917b1d503abfdd61e6b6e8800851aa8,
convert key loading off of the "baked" system so that baked
is no longer used by the ORM.

Change-Id: I3abfb45dd6e50f84f29d39434caa0b550ce27864

23 files changed:
doc/build/orm/extensions/baked.rst
lib/sqlalchemy/ext/baked.py
lib/sqlalchemy/ext/horizontal_shard.py
lib/sqlalchemy/orm/context.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/loading.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/persistence.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/session.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/strategy_options.py
lib/sqlalchemy/orm/util.py
lib/sqlalchemy/sql/base.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/dml.py
lib/sqlalchemy/sql/lambdas.py
test/aaa_profiling/test_orm.py
test/ext/test_baked.py
test/orm/inheritance/test_polymorphic_rel.py
test/orm/inheritance/test_relationship.py
test/orm/test_lazy_relations.py
test/profiles.txt

index 9ff3432392ada17b594b49f9ca7b51cb2787a246..e8651dbaaa0901064f4bbc8dd44dc3148bc5cc9b 100644 (file)
@@ -24,14 +24,8 @@ the caching of the SQL calls and result sets themselves is available in
    caching system that removes the need for the :class:`.BakedQuery` system.
    Caching is now transparently active for all Core and ORM queries with no
    action taken by the user, using the system described at :ref:`sql_caching`.
-   For background on using lambda-style construction for cacheable Core and ORM
-   SQL constructs, which is now an optional technique to provide additional
-   performance gains, see the section :ref:`engine_lambda_caching`.
 
 
-
-.. versionadded:: 1.0.0
-
 .. note::
 
     The :mod:`sqlalchemy.ext.baked` extension is **not for beginners**.  Using
@@ -450,9 +444,7 @@ causing all baked queries to not use the cache when used against that
 Like all session flags, it is also accepted by factory objects like
 :class:`.sessionmaker` and methods like :meth:`.sessionmaker.configure`.
 
-The immediate rationale for this flag is to reduce memory use in the case
-that the query baking used by relationship loaders and other loaders
-is not desirable.   It also can be used in the case that an application
+The immediate rationale for this flag is so that an application
 which is seeing issues potentially due to cache key conflicts from user-defined
 baked queries or other baked query issues can turn the behavior off, in
 order to identify or eliminate baked queries as the cause of an issue.
@@ -462,26 +454,10 @@ order to identify or eliminate baked queries as the cause of an issue.
 Lazy Loading Integration
 ------------------------
 
-The baked query system is integrated into SQLAlchemy's lazy loader feature
-as used by :func:`_orm.relationship`, and will cache queries for most lazy
-load conditions.   A small subset of
-"lazy loads" may not be cached; these involve query options in conjunction with ad-hoc
-:obj:`.aliased` structures that cannot produce a repeatable cache
-key.
-
-.. versionchanged:: 1.2  "baked" queries are now the foundation of the
-   lazy-loader feature of :func:`_orm.relationship`.
-
-Opting out with the bake_queries flag
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The :func:`_orm.relationship` construct includes a flag
-:paramref:`_orm.relationship.bake_queries` which when set to False will cause
-that relationship to opt out of caching queries.  Additionally, the
-:paramref:`.Session.enable_baked_queries` setting can be used to disable
-all "baked query" use.   These flags can be useful to conserve memory,
-when memory conservation is more important than performance for a particular
-relationship or for the application overall.
+.. versionchanged:: 1.4 As of SQLAlchemy 1.4, the "baked query" system is no
+   longer part of the relationship loading system.
+   The :ref:`native caching <sql_caching>` system is used instead.
+
 
 API Documentation
 -----------------
index e642a83d5c4301b8d67446afec5c0ba5f2d1f6e6..97c825f02afe3798b2b8dd263479f48bc723771b 100644 (file)
@@ -200,6 +200,12 @@ class BakedQuery(object):
                 if ck is None:
                     self.spoil(full=True)
                 else:
+                    assert not ck[1], (
+                        "loader options with variable bound parameters "
+                        "not supported with baked queries.  Please "
+                        "use new-style select() statements for cached "
+                        "ORM queries."
+                    )
                     key += ck[0]
 
         self.add_criteria(
@@ -395,7 +401,7 @@ class Result(object):
         for fn in self._post_criteria:
             q = fn(q)
 
-        params = q.load_options._params
+        params = q._params
         execution_options = dict(q._execution_options)
         execution_options.update(
             {
index 786d00597af36bc6afc998f9ae57adfe3cfe70e3..53545826bce9d84755fa432ed9de9817b1a2e717 100644 (file)
@@ -207,13 +207,10 @@ class ShardedSession(Session):
 
 
 def execute_and_instances(orm_context):
-    params = orm_context.parameters
 
     if orm_context.is_select:
         load_options = active_options = orm_context.load_options
         update_options = None
-        if params is None:
-            params = active_options._params
 
     else:
         load_options = None
index 55a6b4cd2132454d6c7c230b5ea4dc51bee24bfb..96725e55b1ac5ceb9ba0b02e2fe27e4d4dd4469e 100644 (file)
@@ -46,6 +46,7 @@ class QueryContext(object):
     __slots__ = (
         "compile_state",
         "query",
+        "params",
         "load_options",
         "bind_arguments",
         "execution_options",
@@ -83,6 +84,7 @@ class QueryContext(object):
         self,
         compile_state,
         statement,
+        params,
         session,
         load_options,
         execution_options=None,
@@ -96,6 +98,7 @@ class QueryContext(object):
         self.session = session
         self.loaders_require_buffering = False
         self.loaders_require_uniquing = False
+        self.params = params
 
         self.propagated_loader_options = {
             o for o in statement._with_options if o.propagate_to_loaders
@@ -239,7 +242,13 @@ class ORMCompileState(CompileState):
 
     @classmethod
     def orm_setup_cursor_result(
-        cls, session, statement, execution_options, bind_arguments, result
+        cls,
+        session,
+        statement,
+        params,
+        execution_options,
+        bind_arguments,
+        result,
     ):
         execution_context = result.context
         compile_state = execution_context.compiled.compile_state
@@ -256,6 +265,7 @@ class ORMCompileState(CompileState):
         querycontext = QueryContext(
             compile_state,
             statement,
+            params,
             session,
             load_options,
             execution_options,
@@ -711,7 +721,9 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
                 and "entity_namespace" in element._annotations
             ):
                 for elem in _select_iterables(
-                    element._annotations["entity_namespace"].columns
+                    element._annotations[
+                        "entity_namespace"
+                    ]._all_column_expressions
                 ):
                     yield elem
             else:
index e569c06031db70d0c8365d74686c7c1a8bd8f78f..4cf820ae3250c37f3c33eff45d63545d32aec52a 100644 (file)
@@ -38,6 +38,7 @@ from .. import util
 from ..sql import operators
 from ..sql import roles
 from ..sql import visitors
+from ..sql.base import ExecutableOption
 from ..sql.traversals import HasCacheKey
 
 if util.TYPE_CHECKING:
@@ -675,7 +676,7 @@ class StrategizedProperty(MapperProperty):
         )
 
 
-class ORMOption(HasCacheKey):
+class ORMOption(ExecutableOption):
     """Base class for option objects that are passed to ORM queries.
 
     These options may be consumed by :meth:`.Query.options`,
index 8d1ae2e69664eccd479a351964697f721a08b883..60139315661c0928117d6c78db5b4e5a02c39cfe 100644 (file)
@@ -187,6 +187,7 @@ def merge_frozen_result(session, statement, frozen_result, load=True):
 @util.preload_module("sqlalchemy.orm.context")
 def merge_result(query, iterator, load=True):
     """Merge a result into this :class:`.Query` object's Session."""
+
     querycontext = util.preloaded.orm_context
 
     session = query.session
@@ -298,7 +299,8 @@ def load_on_ident(
     with_for_update=None,
     only_load_props=None,
     no_autoflush=False,
-    bind_arguments=util.immutabledict(),
+    bind_arguments=util.EMPTY_DICT,
+    execution_options=util.EMPTY_DICT,
 ):
     """Load the given identity key from the database."""
     if key is not None:
@@ -318,6 +320,7 @@ def load_on_ident(
         identity_token=identity_token,
         no_autoflush=no_autoflush,
         bind_arguments=bind_arguments,
+        execution_options=execution_options,
     )
 
 
@@ -331,7 +334,8 @@ def load_on_pk_identity(
     only_load_props=None,
     identity_token=None,
     no_autoflush=False,
-    bind_arguments=util.immutabledict(),
+    bind_arguments=util.EMPTY_DICT,
+    execution_options=util.EMPTY_DICT,
 ):
 
     """Load the given primary key identity from the database."""
@@ -339,21 +343,18 @@ def load_on_pk_identity(
     query = statement
     q = query._clone()
 
+    is_lambda = q._is_lambda_element
+
     # TODO: fix these imports ....
     from .context import QueryContext, ORMCompileState
 
     if load_options is None:
         load_options = QueryContext.default_load_options
 
-    compile_options = ORMCompileState.default_compile_options.safe_merge(
-        q._compile_options
-    )
+    compile_options = ORMCompileState.default_compile_options
 
     if primary_key_identity is not None:
-        # mapper = query._only_full_mapper_zero("load_on_pk_identity")
-
-        # TODO: error checking?
-        mapper = query._raw_columns[0]._annotations["parententity"]
+        mapper = query._propagate_attrs["plugin_subject"]
 
         (_get_clause, _get_params) = mapper._get_clause
 
@@ -379,10 +380,16 @@ def load_on_pk_identity(
                     "release."
                 )
 
-        # TODO: can mapper._get_clause be pre-adapted?
-        q._where_criteria = (
-            sql_util._deep_annotate(_get_clause, {"_orm_adapt": True}),
-        )
+        if is_lambda:
+            q = q.add_criteria(
+                lambda q: q.where(
+                    sql_util._deep_annotate(_get_clause, {"_orm_adapt": True})
+                ),
+            )
+        else:
+            q._where_criteria = (
+                sql_util._deep_annotate(_get_clause, {"_orm_adapt": True}),
+            )
 
         params = dict(
             [
@@ -392,44 +399,71 @@ def load_on_pk_identity(
                 )
             ]
         )
+    else:
+        params = None
 
-        load_options += {"_params": params}
+    if is_lambda:
+        if with_for_update is not None or refresh_state or only_load_props:
+            raise NotImplementedError(
+                "refresh operation not supported with lambda statement"
+            )
 
-    if with_for_update is not None:
-        version_check = True
-        q._for_update_arg = with_for_update
-    elif query._for_update_arg is not None:
-        version_check = True
-        q._for_update_arg = query._for_update_arg
-    else:
         version_check = False
 
-    if refresh_state and refresh_state.load_options:
-        compile_options += {"_current_path": refresh_state.load_path.parent}
-        q = q.options(*refresh_state.load_options)
-
-    # TODO: most of the compile_options that are not legacy only involve this
-    # function, so try to see if handling of them can mostly be local to here
+        _, load_options = _set_get_options(
+            compile_options,
+            load_options,
+            populate_existing=bool(refresh_state),
+            version_check=version_check,
+            only_load_props=only_load_props,
+            refresh_state=refresh_state,
+            identity_token=identity_token,
+        )
 
-    q._compile_options, load_options = _set_get_options(
-        compile_options,
-        load_options,
-        populate_existing=bool(refresh_state),
-        version_check=version_check,
-        only_load_props=only_load_props,
-        refresh_state=refresh_state,
-        identity_token=identity_token,
-    )
-    q._order_by = None
+        if no_autoflush:
+            load_options += {"_autoflush": False}
+    else:
+        if with_for_update is not None:
+            version_check = True
+            q._for_update_arg = with_for_update
+        elif query._for_update_arg is not None:
+            version_check = True
+            q._for_update_arg = query._for_update_arg
+        else:
+            version_check = False
+
+        if refresh_state and refresh_state.load_options:
+            compile_options += {
+                "_current_path": refresh_state.load_path.parent
+            }
+            q = q.options(*refresh_state.load_options)
+
+        # TODO: most of the compile_options that are not legacy only involve
+        # this function, so try to see if handling of them can mostly be local
+        # to here
+
+        q._compile_options, load_options = _set_get_options(
+            compile_options,
+            load_options,
+            populate_existing=bool(refresh_state),
+            version_check=version_check,
+            only_load_props=only_load_props,
+            refresh_state=refresh_state,
+            identity_token=identity_token,
+        )
+        q._order_by = None
 
-    if no_autoflush:
-        load_options += {"_autoflush": False}
+        if no_autoflush:
+            load_options += {"_autoflush": False}
 
+    execution_options = util.EMPTY_DICT.merge_with(
+        execution_options, {"_sa_orm_load_options": load_options}
+    )
     result = (
         session.execute(
             q,
-            params=load_options._params,
-            execution_options={"_sa_orm_load_options": load_options},
+            params=params,
+            execution_options=execution_options,
             bind_arguments=bind_arguments,
             future=True,
         )
index 2b04f1cc7ab91c052944437d62cb33b89c7aa889..6d22c620502d39ef51795d89d2c493adda3fdd0b 100644 (file)
@@ -719,9 +719,8 @@ class Mapper(
         """
         return self
 
-    _cache_key_traversal = [
-        ("mapper", visitors.ExtendedInternalTraversal.dp_plain_obj),
-    ]
+    def _gen_cache_key(self, anon_map, bindparams):
+        return (self,)
 
     @property
     def entity(self):
@@ -2315,6 +2314,17 @@ class Mapper(
             )
         )
 
+    @property
+    def _all_column_expressions(self):
+        poly_properties = self._polymorphic_properties
+        adapter = self._polymorphic_adapter
+
+        return [
+            adapter.columns[prop.columns[0]] if adapter else prop.columns[0]
+            for prop in poly_properties
+            if isinstance(prop, properties.ColumnProperty)
+        ]
+
     def _columns_plus_keys(self, polymorphic_mappers=()):
         if polymorphic_mappers:
             poly_properties = self._iterate_polymorphic_properties(
index a78af92b9ad5d2cd767b23122c2bff8b5d4b6cfb..7c254c61bd9a0305b620d76371cafc7253177797 100644 (file)
@@ -1832,7 +1832,13 @@ class BulkUDCompileState(CompileState):
 
     @classmethod
     def orm_setup_cursor_result(
-        cls, session, statement, execution_options, bind_arguments, result
+        cls,
+        session,
+        statement,
+        params,
+        execution_options,
+        bind_arguments,
+        result,
     ):
 
         # this stage of the execution is called after the
index b8226dfc06b7b54d0692fcc8d5dfaa650f5a2297..d60c03bdcb1640225ddef50c3f95667c7e41c106 100644 (file)
@@ -125,6 +125,8 @@ class Query(
 
     load_options = QueryContext.default_load_options
 
+    _params = util.EMPTY_DICT
+
     # local Query builder state, not needed for
     # compilation or execution
     _aliased_generation = None
@@ -366,10 +368,8 @@ class Query(
         else:
             stmt = self._compile_state(for_statement=True).statement
 
-        if self.load_options._params:
-            # this is the search and replace thing.  this is kind of nuts
-            # to be doing here.
-            stmt = stmt.params(self.load_options._params)
+        if self._params:
+            stmt = stmt.params(self._params)
 
         return stmt
 
@@ -431,11 +431,7 @@ class Query(
         return stmt
 
     def subquery(
-        self,
-        name=None,
-        with_labels=False,
-        reduce_columns=False,
-        _legacy_core_statement=False,
+        self, name=None, with_labels=False, reduce_columns=False,
     ):
         """Return the full SELECT statement represented by
         this :class:`_query.Query`, embedded within an
@@ -463,10 +459,7 @@ class Query(
         if with_labels:
             q = q.with_labels()
 
-        if _legacy_core_statement:
-            q = q._compile_state(for_statement=True).statement
-        else:
-            q = q.statement
+        q = q.statement
 
         if reduce_columns:
             q = q.reduce_columns()
@@ -994,6 +987,13 @@ class Query(
         after rollback or commit handles object state automatically.
         This method is not intended for general use.
 
+        .. versionadded:: 1.4
+
+            The :meth:`.populate_existing` method is equivalent to passing the
+            ``populate_existing=True`` option to the
+            :meth:`_orm.Query.execution_options` method.
+
+
         """
         self.load_options += {"_populate_existing": True}
 
@@ -1298,16 +1298,11 @@ class Query(
             self.with_labels()
             .enable_eagerloads(False)
             .correlate(None)
-            .subquery(_legacy_core_statement=True)
+            .subquery()
             ._anonymous_fromclause()
         )
 
-        parententity = self._raw_columns[0]._annotations.get("parententity")
-        if parententity:
-            ac = aliased(parententity.mapper, alias=fromclause)
-            q = self._from_selectable(ac)
-        else:
-            q = self._from_selectable(fromclause)
+        q = self._from_selectable(fromclause)
 
         if entities:
             q._set_entities(entities)
@@ -1503,12 +1498,29 @@ class Query(
     def execution_options(self, **kwargs):
         """ Set non-SQL options which take effect during execution.
 
-        The options are the same as those accepted by
-        :meth:`_engine.Connection.execution_options`.
+        Options allowed here include all of those accepted by
+        :meth:`_engine.Connection.execution_options`, as well as a series
+        of ORM specific options:
+
+        ``populate_existing=True`` - equivalent to using
+        :meth:`_orm.Query.populate_existing`
+
+        ``autoflush=True|False`` - equivalent to using
+        :meth:`_orm.Query.autoflush`
+
+        ``yield_per=<value>`` - equivalent to using
+        :meth:`_orm.Query.yield_per`
 
         Note that the ``stream_results`` execution option is enabled
         automatically if the :meth:`~sqlalchemy.orm.query.Query.yield_per()`
-        method is used.
+        method or execution option is used.
+
+        The execution options may also be specified on a per execution basis
+        when using :term:`2.0 style` queries via the
+        :paramref:`_orm.Session.execution_options` parameter.
+
+        .. versionadded:: 1.4 - added ORM options to
+           :meth:`_orm.Query.execution_options`
 
         .. seealso::
 
@@ -1579,8 +1591,7 @@ class Query(
                 "params() takes zero or one positional argument, "
                 "which is a dictionary."
             )
-        params = self.load_options._params.union(kwargs)
-        self.load_options += {"_params": params}
+        self._params = self._params.union(kwargs)
 
     def where(self, *criterion):
         """A synonym for :meth:`.Query.filter`.
@@ -2694,7 +2705,8 @@ class Query(
 
     def _iter(self):
         # new style execution.
-        params = self.load_options._params
+        params = self._params
+
         statement = self._statement_20()
         result = self.session.execute(
             statement,
@@ -2789,6 +2801,7 @@ class Query(
             context = QueryContext(
                 compile_state,
                 compile_state.statement,
+                self._params,
                 self.session,
                 self.load_options,
             )
@@ -2984,7 +2997,7 @@ class Query(
         delete_._where_criteria = self._where_criteria
         result = self.session.execute(
             delete_,
-            self.load_options._params,
+            self._params,
             execution_options={"synchronize_session": synchronize_session},
             future=True,
         )
@@ -3060,7 +3073,7 @@ class Query(
         upd._where_criteria = self._where_criteria
         result = self.session.execute(
             upd,
-            self.load_options._params,
+            self._params,
             execution_options={"synchronize_session": synchronize_session},
             future=True,
         )
@@ -3104,6 +3117,7 @@ class Query(
         context = QueryContext(
             compile_state,
             compile_state.statement,
+            self._params,
             self.session,
             self.load_options,
         )
index 25aedd52d17d98a583f63e9774fa3f6600a75188..339c57bdcede1960b5ee0917b5a111b597870f90 100644 (file)
@@ -36,7 +36,6 @@ from ..inspection import inspect
 from ..sql import coercions
 from ..sql import dml
 from ..sql import roles
-from ..sql import selectable
 from ..sql import visitors
 from ..sql.base import CompileState
 
@@ -235,17 +234,22 @@ class ORMExecuteState(util.MemoizedSlots):
     @property
     def is_select(self):
         """return True if this is a SELECT operation."""
-        return isinstance(self.statement, selectable.Select)
+        return self.statement.is_select
+
+    @property
+    def is_insert(self):
+        """return True if this is an INSERT operation."""
+        return self.statement.is_dml and self.statement.is_insert
 
     @property
     def is_update(self):
         """return True if this is an UPDATE operation."""
-        return isinstance(self.statement, dml.Update)
+        return self.statement.is_dml and self.statement.is_update
 
     @property
     def is_delete(self):
         """return True if this is a DELETE operation."""
-        return isinstance(self.statement, dml.Delete)
+        return self.statement.is_dml and self.statement.is_delete
 
     @property
     def _is_crud(self):
@@ -1622,7 +1626,12 @@ class Session(_SessionClassMethods):
 
         if compile_state_cls:
             result = compile_state_cls.orm_setup_cursor_result(
-                self, statement, execution_options, bind_arguments, result
+                self,
+                statement,
+                params,
+                execution_options,
+                bind_arguments,
+                result,
             )
 
         return result
index db82f0b743d67eae611770111ed5544796a3fd1a..44f303feed970add24c5cc93753ff9da7d6ab38c 100644 (file)
@@ -26,6 +26,7 @@ from .base import _RAISE_FOR_STATE
 from .base import _SET_DEFERRED_EXPIRED
 from .context import _column_descriptions
 from .context import ORMCompileState
+from .context import QueryContext
 from .interfaces import LoaderStrategy
 from .interfaces import StrategizedProperty
 from .session import _state_session
@@ -34,7 +35,6 @@ from .util import _none_set
 from .util import aliased
 from .. import event
 from .. import exc as sa_exc
-from .. import future
 from .. import inspect
 from .. import log
 from .. import sql
@@ -487,7 +487,7 @@ class DeferredColumnLoader(LoaderStrategy):
         if (
             loading.load_on_ident(
                 session,
-                future.select(localparent).apply_labels(),
+                sql.select(localparent).apply_labels(),
                 state.key,
                 only_load_props=group,
                 refresh_state=state,
@@ -620,7 +620,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         "_simple_lazy_clause",
         "_raise_always",
         "_raise_on_sql",
-        "_bakery",
+        "_query_cache",
     )
 
     def __init__(self, parent, strategy_key):
@@ -881,83 +881,68 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             for pk in self.mapper.primary_key
         ]
 
-    @util.preload_module("sqlalchemy.ext.baked")
-    def _memoized_attr__bakery(self):
-        return util.preloaded.ext_baked.bakery(size=50)
+    def _memoized_attr__query_cache(self):
+        return util.LRUCache(30)
 
     @util.preload_module("sqlalchemy.orm.strategy_options")
     def _emit_lazyload(self, session, state, primary_key_identity, passive):
-        # emit lazy load now using BakedQuery, to cut way down on the overhead
-        # of generating queries.
-        # there are two big things we are trying to guard against here:
-        #
-        # 1. two different lazy loads that need to have a different result,
-        #    being cached on the same key.  The results between two lazy loads
-        #    can be different due to the options passed to the query, which
-        #    take effect for descendant objects.  Therefore we have to make
-        #    sure paths and load options generate good cache keys, and if they
-        #    don't, we don't cache.
-        # 2. a lazy load that gets cached on a key that includes some
-        #    "throwaway" object, like a per-query AliasedClass, meaning
-        #    the cache key will never be seen again and the cache itself
-        #    will fill up.   (the cache is an LRU cache, so while we won't
-        #    run out of memory, it will perform terribly when it's full.  A
-        #    warning is emitted if this occurs.)   We must prevent the
-        #    generation of a cache key that is including a throwaway object
-        #    in the key.
-
         strategy_options = util.preloaded.orm_strategy_options
 
-        # note that "lazy='select'" and "lazy=True" make two separate
-        # lazy loaders.   Currently the LRU cache is local to the LazyLoader,
-        # however add ourselves to the initial cache key just to future
-        # proof in case it moves
-        q = self._bakery(lambda session: session.query(self.entity), self)
-
-        q.add_criteria(
-            lambda q: q._with_invoke_all_eagers(False), self.parent_property,
+        stmt = sql.lambda_stmt(
+            lambda: sql.select(self.entity)
+            .apply_labels()
+            ._set_compile_options(ORMCompileState.default_compile_options),
+            global_track_bound_values=False,
+            lambda_cache=self._query_cache,
+            track_on=(self,),
         )
 
         if not self.parent_property.bake_queries:
-            q.spoil(full=True)
+            stmt = stmt.spoil()
+
+        load_options = QueryContext.default_load_options
+
+        load_options += {
+            "_invoke_all_eagers": False,
+            "_lazy_loaded_from": state,
+        }
 
         if self.parent_property.secondary is not None:
-            q.add_criteria(
-                lambda q: q.select_from(
-                    self.mapper, self.parent_property.secondary
-                )
+            stmt += lambda stmt: stmt.select_from(
+                self.mapper, self.parent_property.secondary
             )
 
         pending = not state.key
 
         # don't autoflush on pending
         if pending or passive & attributes.NO_AUTOFLUSH:
-            q.add_criteria(lambda q: q.autoflush(False))
+            stmt += lambda stmt: stmt.execution_options(autoflush=False)
 
         if state.load_options:
-            # here, if any of the options cannot return a cache key,
-            # the BakedQuery "spoils" and caching will not occur.  a path
-            # that features Cls.attribute.of_type(some_alias) will cancel
-            # caching, for example, since "some_alias" is user-defined and
-            # is usually a throwaway object.
+
             effective_path = state.load_path[self.parent_property]
 
-            q._add_lazyload_options(state.load_options, effective_path)
+            opts = list(state.load_options)
+
+            stmt += lambda stmt: stmt.options(*opts)
+            stmt += lambda stmt: stmt._update_compile_options(
+                {"_current_path": effective_path}
+            )
 
         if self.use_get:
             if self._raise_on_sql:
                 self._invoke_raise_load(state, passive, "raise_on_sql")
 
-            return (
-                q(session)
-                .with_post_criteria(lambda q: q._set_lazyload_from(state))
-                ._load_on_pk_identity(
-                    session, session.query(self.mapper), primary_key_identity
-                )
+            return loading.load_on_pk_identity(
+                session,
+                stmt,
+                primary_key_identity,
+                load_options=load_options,
+                execution_options={"compiled_cache": self._query_cache},
             )
 
         if self._order_by:
-            q.add_criteria(lambda q: q.order_by(*self._order_by))
+            stmt += lambda stmt: stmt.order_by(*self._order_by)
 
         def _lazyload_reverse(compile_context):
             for rev in self.parent_property._reverse_property:
@@ -974,13 +959,18 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
                         ]
                     ).lazyload(rev.key).process_compile_state(compile_context)
 
-        q.add_criteria(
-            lambda q: q._add_context_option(
-                _lazyload_reverse, self.parent_property
-            )
+        stmt += lambda stmt: stmt._add_context_option(
+            _lazyload_reverse, self.parent_property
         )
 
         lazy_clause, params = self._generate_lazy_clause(state, passive)
+
+        execution_options = {
+            "_sa_orm_load_options": load_options,
+        }
+        if not self.parent_property.bake_queries:
+            execution_options["compiled_cache"] = None
+
         if self.key in state.dict:
             return attributes.ATTR_WAS_SET
 
@@ -994,21 +984,16 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         if self._raise_on_sql:
             self._invoke_raise_load(state, passive, "raise_on_sql")
 
-        q.add_criteria(lambda q: q.filter(lazy_clause))
-
-        # set parameters in the query such that we don't overwrite
-        # parameters that are already set within it
-        def set_default_params(q):
-            params.update(q.load_options._params)
-            q.load_options += {"_params": params}
-            return q
+        stmt = stmt.add_criteria(
+            lambda stmt: stmt.where(lazy_clause), enable_tracking=False
+        )
 
-        result = (
-            q(session)
-            .with_post_criteria(lambda q: q._set_lazyload_from(state))
-            .with_post_criteria(set_default_params)
-            .all()
+        result = session.execute(
+            stmt, params, future=True, execution_options=execution_options
         )
+
+        result = result.unique().scalars().all()
+
         if self.uselist:
             return result
         else:
@@ -1409,6 +1394,7 @@ class SubqueryLoader(PostLoader):
             "session",
             "execution_options",
             "load_options",
+            "params",
             "subq",
             "_data",
         )
@@ -1419,6 +1405,7 @@ class SubqueryLoader(PostLoader):
             self.session = context.session
             self.execution_options = context.execution_options
             self.load_options = context.load_options
+            self.params = context.params or {}
             self.subq = subq
             self._data = None
 
@@ -1443,7 +1430,7 @@ class SubqueryLoader(PostLoader):
             # to work with baked query, the parameters may have been
             # updated since this query was created, so take these into account
 
-            rows = list(q.params(self.load_options._params))
+            rows = list(q.params(self.params))
             for k, v in itertools.groupby(rows, lambda x: x[1:]):
                 self._data[k].extend(vv[0] for vv in v)
 
@@ -1519,19 +1506,16 @@ class SubqueryLoader(PostLoader):
             orig_query, "orm"
         )
 
-        # this would create the full blown compile state, which we don't
-        # need
-        # orig_compile_state = compile_state_cls.create_for_statement(
-        # orig_query, None)
-
         if orig_query._is_lambda_element:
-            util.warn(
-                'subqueryloader for "%s" must invoke lambda callable at %r in '
-                "order to produce a new query, decreasing the efficiency "
-                "of caching for this statement.  Consider using "
-                "selectinload() for more effective full-lambda caching"
-                % (self, orig_query)
-            )
+            if context.load_options._lazy_loaded_from is None:
+                util.warn(
+                    'subqueryloader for "%s" must invoke lambda callable '
+                    "at %r in "
+                    "order to produce a new query, decreasing the efficiency "
+                    "of caching for this statement.  Consider using "
+                    "selectinload() for more effective full-lambda caching"
+                    % (self, orig_query)
+                )
             orig_query = orig_query._resolved
 
         # this is the more "quick" version, however it's not clear how
@@ -2112,7 +2096,7 @@ class JoinedLoader(AbstractRelationshipLoader):
         else:
             # all other cases are innerjoin=='nested' approach
             eagerjoin = self._splice_nested_inner_join(
-                path, towrap, clauses, onclause
+                path, towrap, clauses, onclause,
             )
 
         compile_state.eager_joins[query_entity_key] = eagerjoin
@@ -2153,7 +2137,7 @@ class JoinedLoader(AbstractRelationshipLoader):
             assert isinstance(join_obj, orm_util._ORMJoin)
         elif isinstance(join_obj, sql.selectable.FromGrouping):
             return self._splice_nested_inner_join(
-                path, join_obj.element, clauses, onclause, splicing
+                path, join_obj.element, clauses, onclause, splicing,
             )
         elif not isinstance(join_obj, orm_util._ORMJoin):
             if path[-2] is splicing:
@@ -2170,12 +2154,12 @@ class JoinedLoader(AbstractRelationshipLoader):
                 return None
 
         target_join = self._splice_nested_inner_join(
-            path, join_obj.right, clauses, onclause, join_obj._right_memo
+            path, join_obj.right, clauses, onclause, join_obj._right_memo,
         )
         if target_join is None:
             right_splice = False
             target_join = self._splice_nested_inner_join(
-                path, join_obj.left, clauses, onclause, join_obj._left_memo
+                path, join_obj.left, clauses, onclause, join_obj._left_memo,
             )
             if target_join is None:
                 # should only return None when recursively called,
@@ -2401,7 +2385,7 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
         "_parent_alias",
         "_query_info",
         "_fallback_query_info",
-        "_bakery",
+        "_query_cache",
     )
 
     query_info = collections.namedtuple(
@@ -2504,9 +2488,8 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
             (("lazy", "select"),)
         ).init_class_attribute(mapper)
 
-    @util.preload_module("sqlalchemy.ext.baked")
-    def _memoized_attr__bakery(self):
-        return util.preloaded.ext_baked.bakery(size=50)
+    def _memoized_attr__query_cache(self):
+        return util.LRUCache(30)
 
     def create_row_processor(
         self,
@@ -2564,9 +2547,8 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
         with_poly_entity = path_w_prop.get(
             context.attributes, "path_with_polymorphic", None
         )
-
         if with_poly_entity is not None:
-            effective_entity = with_poly_entity
+            effective_entity = inspect(with_poly_entity)
         else:
             effective_entity = self.entity
 
@@ -2645,32 +2627,37 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
             # we need to adapt our "pk_cols" and "in_expr" to that
             # entity.   in non-"omit join" mode, these are against the
             # parent entity and do not need adaption.
-            insp = inspect(effective_entity)
-            if insp.is_aliased_class:
-                pk_cols = [insp._adapt_element(col) for col in pk_cols]
-                in_expr = insp._adapt_element(in_expr)
-                pk_cols = [insp._adapt_element(col) for col in pk_cols]
-
-        q = self._bakery(
-            lambda session: session.query(
+            if effective_entity.is_aliased_class:
+                pk_cols = [
+                    effective_entity._adapt_element(col) for col in pk_cols
+                ]
+                in_expr = effective_entity._adapt_element(in_expr)
+
+        q = sql.lambda_stmt(
+            lambda: sql.select(
                 orm_util.Bundle("pk", *pk_cols), effective_entity
-            ),
-            self,
+            ).apply_labels(),
+            lambda_cache=self._query_cache,
+            global_track_bound_values=False,
+            track_on=(self, effective_entity,) + tuple(pk_cols),
         )
 
+        if not self.parent_property.bake_queries:
+            q = q.spoil()
+
         if not query_info.load_with_join:
             # the Bundle we have in the "omit_join" case is against raw, non
             # annotated columns, so to ensure the Query knows its primary
             # entity, we add it explicitly.  If we made the Bundle against
             # annotated columns, we hit a performance issue in this specific
             # case, which is detailed in issue #4347.
-            q.add_criteria(lambda q: q.select_from(effective_entity))
+            q = q.add_criteria(lambda q: q.select_from(effective_entity))
         else:
             # in the non-omit_join case, the Bundle is against the annotated/
             # mapped column of the parent entity, but the #4347 issue does not
             # occur in this case.
             pa = self._parent_alias
-            q.add_criteria(
+            q = q.add_criteria(
                 lambda q: q.select_from(pa).join(
                     getattr(pa, self.parent_property.key).of_type(
                         effective_entity
@@ -2678,18 +2665,9 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
                 )
             )
 
-        if query_info.load_only_child:
-            q.add_criteria(
-                lambda q: q.filter(
-                    in_expr.in_(sql.bindparam("primary_keys", expanding=True))
-                )
-            )
-        else:
-            q.add_criteria(
-                lambda q: q.filter(
-                    in_expr.in_(sql.bindparam("primary_keys", expanding=True))
-                )
-            )
+        q = q.add_criteria(
+            lambda q: q.filter(in_expr.in_(sql.bindparam("primary_keys")))
+        )
 
         # a test which exercises what these comments talk about is
         # test_selectin_relations.py -> test_twolevel_selectin_w_polymorphic
@@ -2715,31 +2693,39 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
         # that query will be in terms of the effective entity we were just
         # handed.
         #
-        # But now the selectinload/ baked query we are running is *also*
+        # But now the selectinload query we are running is *also*
         # cached.  What if it's cached and running from some previous iteration
         # of that AliasedInsp?  Well in that case it will also use the previous
-        # iteration of the loader options.   If the baked query expires and
+        # iteration of the loader options.   If the query expires and
         # gets generated again, it will be handed the current effective_entity
         # and the current _with_options, again in terms of whatever
         # compile_state.select_statement happens to be right now, so the
         # query will still be internally consistent and loader callables
         # will be correctly invoked.
 
-        q._add_lazyload_options(
-            orig_query._with_options, path[self.parent_property]
+        effective_path = path[self.parent_property]
+
+        options = orig_query._with_options
+        q = q.add_criteria(
+            lambda q: q.options(*options)._update_compile_options(
+                {"_current_path": effective_path}
+            )
         )
 
         if context.populate_existing:
-            q.add_criteria(lambda q: q.populate_existing())
+            q = q.add_criteria(
+                lambda q: q.execution_options(populate_existing=True)
+            )
 
         if self.parent_property.order_by:
             if not query_info.load_with_join:
                 eager_order_by = self.parent_property.order_by
-                if insp.is_aliased_class:
+                if effective_entity.is_aliased_class:
                     eager_order_by = [
-                        insp._adapt_element(elem) for elem in eager_order_by
+                        effective_entity._adapt_element(elem)
+                        for elem in eager_order_by
                     ]
-                q.add_criteria(lambda q: q.order_by(*eager_order_by))
+                q = q.add_criteria(lambda q: q.order_by(*eager_order_by))
             else:
 
                 def _setup_outermost_orderby(compile_context):
@@ -2747,7 +2733,7 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
                         util.to_list(self.parent_property.order_by)
                     )
 
-                q.add_criteria(
+                q = q.add_criteria(
                     lambda q: q._add_context_option(
                         _setup_outermost_orderby, self.parent_property
                     )
@@ -2770,11 +2756,16 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
             our_keys = our_keys[self._chunksize :]
             data = {
                 k: v
-                for k, v in q(context.session).params(
-                    primary_keys=[
-                        key[0] if query_info.zero_idx else key for key in chunk
-                    ]
-                )
+                for k, v in context.session.execute(
+                    q,
+                    params={
+                        "primary_keys": [
+                            key[0] if query_info.zero_idx else key
+                            for key in chunk
+                        ]
+                    },
+                    future=True,
+                ).unique()
             }
 
             for key in chunk:
@@ -2817,7 +2808,9 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
 
             data = collections.defaultdict(list)
             for k, v in itertools.groupby(
-                q(context.session).params(primary_keys=primary_keys),
+                context.session.execute(
+                    q, params={"primary_keys": primary_keys}, future=True
+                ).unique(),
                 lambda x: x[0],
             ):
                 data[k].extend(vv[1] for vv in v)
index ffaa93404f1418ad0a52424e46bd9d9c35783bc8..b405153b92d067f60787ad3cf9fba23f9ded3e79 100644 (file)
@@ -352,7 +352,6 @@ class Load(Generative, LoaderOption):
         self, attr, strategy, propagate_to_loaders=True
     ):
         strategy = self._coerce_strat(strategy)
-
         self.propagate_to_loaders = propagate_to_loaders
         cloned = self._clone_for_bind_strategy(attr, strategy, "relationship")
         self.path = cloned.path
@@ -577,6 +576,7 @@ class _UnboundLoad(Load):
         if attr:
             path = path + (attr,)
         self.path = path
+
         return path
 
     def __getstate__(self):
index 68ffa2393daecf5edf5abf94b79b5d879794be86..71ee295974797033dc5a2e0c0f511fd58575dcb3 100644 (file)
@@ -806,7 +806,7 @@ class AliasedInsp(
         return {}
 
     @util.memoized_property
-    def columns(self):
+    def _all_column_expressions(self):
         if self._is_with_polymorphic:
             cols_plus_keys = self.mapper._columns_plus_keys(
                 [ent.mapper for ent in self._with_polymorphic_entities]
index 36a8151d33604cef727da80bb0e89d14a1521c53..dc280469119f585337e2baaf3ccef12159c68db3 100644 (file)
@@ -16,6 +16,7 @@ import re
 
 from . import roles
 from .traversals import HasCacheKey  # noqa
+from .traversals import HasCopyInternals  # noqa
 from .traversals import MemoizedHasCacheKey  # noqa
 from .visitors import ClauseVisitor
 from .visitors import ExtendedInternalTraversal
@@ -699,6 +700,20 @@ class CacheableOptions(Options, HasCacheKey):
         return HasCacheKey._generate_cache_key_for_object(self)
 
 
+class ExecutableOption(HasCopyInternals, HasCacheKey):
+    _annotations = util.EMPTY_DICT
+
+    __visit_name__ = "executable_option"
+
+    def _clone(self):
+        """Create a shallow copy of this ExecutableOption.
+
+        """
+        c = self.__class__.__new__(self.__class__)
+        c.__dict__ = dict(self.__dict__)
+        return c
+
+
 class Executable(Generative):
     """Mark a :class:`_expression.ClauseElement` as supporting execution.
 
@@ -715,11 +730,18 @@ class Executable(Generative):
     _with_context_options = ()
 
     _executable_traverse_internals = [
-        ("_with_options", ExtendedInternalTraversal.dp_has_cache_key_list),
+        ("_with_options", InternalTraversal.dp_executable_options),
         ("_with_context_options", ExtendedInternalTraversal.dp_plain_obj),
         ("_propagate_attrs", ExtendedInternalTraversal.dp_propagate_attrs),
     ]
 
+    is_select = False
+    is_update = False
+    is_insert = False
+    is_text = False
+    is_delete = False
+    is_dml = False
+
     @property
     def _effective_plugin_target(self):
         return self.__visit_name__
@@ -758,6 +780,22 @@ class Executable(Generative):
             coercions.expect(roles.HasCacheKeyRole, opt) for opt in options
         )
 
+    @_generative
+    def _set_compile_options(self, compile_options):
+        """Assign the compile options to a new value.
+
+        :param compile_options: appropriate CacheableOptions structure
+
+        """
+
+        self._compile_options = compile_options
+
+    @_generative
+    def _update_compile_options(self, options):
+        """update the _compile_options with new keys."""
+
+        self._compile_options += options
+
     @_generative
     def _add_context_option(self, callable_, cache_args):
         """Add a context option to this statement.
index a8bd1de33723cf4dec5c4acc2fddd2b626166ae0..ac4055bdf17ecc8c082d47fb486ae6fbacb670a0 100644 (file)
@@ -353,6 +353,7 @@ class FromLinter(collections.namedtuple("FromLinter", ["froms", "edges"])):
                 message = template.format(
                     froms=froms_str, start=self.froms[start_with]
                 )
+
                 util.warn(message)
 
 
@@ -713,7 +714,10 @@ class SQLCompiler(Compiled):
         self.cache_key = cache_key
 
         if cache_key:
-            self._cache_key_bind_match = {b: b for b in cache_key[1]}
+            self._cache_key_bind_match = ckbm = {
+                b.key: b for b in cache_key[1]
+            }
+            ckbm.update({b: b for b in cache_key[1]})
 
         # compile INSERT/UPDATE defaults/sequences to expect executemany
         # style execution, which may mean no pre-execute of defaults,
@@ -2166,7 +2170,10 @@ class SQLCompiler(Compiled):
         # key was been generated.
         ckbm = self._cache_key_bind_match
         if ckbm:
-            ckbm.update({bp: bindparam for bp in bindparam._cloned_set})
+            for bp in bindparam._cloned_set:
+                if bp.key in ckbm:
+                    cb = ckbm[bp.key]
+                    ckbm[cb] = bindparam
 
         if bindparam.isoutparam:
             self.has_out_parameters = True
@@ -2186,6 +2193,7 @@ class SQLCompiler(Compiled):
             expanding=bindparam.expanding,
             **kwargs
         )
+
         if bindparam.expanding:
             ret = "(%s)" % ret
         return ret
index 81baa9f5218e6122e4460bf241a741aa0fe55f40..21476c1f91e8e928cfef25ba0d30df20c7193a97 100644 (file)
@@ -194,6 +194,8 @@ class UpdateBase(
     _hints = util.immutabledict()
     named_with_column = False
 
+    is_dml = True
+
     @classmethod
     def _constructor_20_deprecations(cls, fn_name, clsname, names):
 
@@ -774,6 +776,8 @@ class Insert(ValuesBase):
     select = None
     include_insert_from_select_defaults = False
 
+    is_insert = True
+
     _traverse_internals = (
         [
             ("table", InternalTraversal.dp_clauseelement),
@@ -1002,6 +1006,8 @@ class Update(DMLWhereBase, ValuesBase):
 
     __visit_name__ = "update"
 
+    is_update = True
+
     _traverse_internals = (
         [
             ("table", InternalTraversal.dp_clauseelement),
@@ -1247,6 +1253,8 @@ class Delete(DMLWhereBase, UpdateBase):
 
     __visit_name__ = "delete"
 
+    is_delete = True
+
     _traverse_internals = (
         [
             ("table", InternalTraversal.dp_clauseelement),
index 3270039026075553be31d2488ed99f02b5a07786..7d52f97ee8abe8c679f97d4b6f227126e82200cd 100644 (file)
@@ -1,5 +1,5 @@
 # sql/lambdas.py
-# Copyright (C) 2005-2019 the SQLAlchemy authors and contributors
+# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors
 # <see AUTHORS file>
 #
 # This module is part of SQLAlchemy and is released under
index 850159323c89d22f425a976f2f0ffa915548621d..fb1edd38fcbe98800eed3ea4bc7c292f2202eedc 100644 (file)
@@ -620,10 +620,13 @@ class QueryTest(NoCache, fixtures.MappedTest):
 
 
 class SelectInEagerLoadTest(NoCache, fixtures.MappedTest):
-    """basic test for selectin() loading, which uses a baked query.
+    """basic test for selectin() loading, which uses a lambda query.
 
-    if the baked query starts spoiling due to some bug in cache keys,
-    this callcount blows up.
+    For the previous "baked query" version of this, statement caching
+    was still taking effect as the selectinloader used its own baked
+    query cache.  in 1.4 we align the loader caches with the global
+    "cache_size" (tenatitively) so the callcount has gone up to accommodate
+    for 3x the compilations.
 
     """
 
@@ -888,7 +891,7 @@ class JoinedEagerLoadTest(NoCache, fixtures.MappedTest):
 
                 r.context.compiled.compile_state = compile_state
                 obj = ORMCompileState.orm_setup_cursor_result(
-                    sess, compile_state.statement, exec_opts, {}, r,
+                    sess, compile_state.statement, {}, exec_opts, {}, r,
                 )
                 list(obj)
                 sess.close()
index 791b04b57b908a59eca0ef1f64f18d5ad71a1d96..6279dcf55e1e6684a4e4c214828b75adf480f30a 100644 (file)
@@ -5,15 +5,9 @@ from sqlalchemy import bindparam
 from sqlalchemy import event
 from sqlalchemy import exc as sa_exc
 from sqlalchemy import func
-from sqlalchemy import literal_column
 from sqlalchemy import testing
 from sqlalchemy.ext import baked
-from sqlalchemy.orm import aliased
-from sqlalchemy.orm import backref
-from sqlalchemy.orm import defaultload
 from sqlalchemy.orm import exc as orm_exc
-from sqlalchemy.orm import lazyload
-from sqlalchemy.orm import Load
 from sqlalchemy.orm import mapper
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import Session
@@ -24,7 +18,6 @@ from sqlalchemy.testing import eq_
 from sqlalchemy.testing import is_
 from sqlalchemy.testing import is_not_
 from sqlalchemy.testing import mock
-from sqlalchemy.testing.assertsql import CompiledSQL
 from test.orm import _fixtures
 
 
@@ -971,654 +964,6 @@ class ResultTest(BakedTest):
         self.assert_sql_count(testing.db, go, 2)
 
 
-class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest):
-    run_setup_mappers = "each"
-
-    @testing.fixture
-    def modify_query_fixture(self):
-        def set_event(bake_ok):
-
-            event.listen(
-                Query,
-                "before_compile",
-                _modify_query,
-                retval=True,
-                bake_ok=bake_ok,
-            )
-            return m1
-
-        m1 = mock.Mock()
-
-        def _modify_query(query):
-            m1(query.column_descriptions[0]["entity"])
-            query = query.enable_assertions(False).filter(
-                literal_column("1") == 1
-            )
-            return query
-
-        yield set_event
-        event.remove(Query, "before_compile", _modify_query)
-
-    def _o2m_fixture(self, lazy="select", **kw):
-        User = self.classes.User
-        Address = self.classes.Address
-
-        mapper(
-            User,
-            self.tables.users,
-            properties={
-                "addresses": relationship(
-                    Address,
-                    order_by=self.tables.addresses.c.id,
-                    lazy=lazy,
-                    **kw
-                )
-            },
-        )
-        mapper(Address, self.tables.addresses)
-        return User, Address
-
-    def _o2m_twolevel_fixture(self, lazy="select", **kw):
-        User = self.classes.User
-        Address = self.classes.Address
-        Dingaling = self.classes.Dingaling
-
-        mapper(
-            User,
-            self.tables.users,
-            properties={
-                "addresses": relationship(
-                    Address,
-                    order_by=self.tables.addresses.c.id,
-                    lazy=lazy,
-                    **kw
-                )
-            },
-        )
-        mapper(
-            Address,
-            self.tables.addresses,
-            properties={"dingalings": relationship(Dingaling, lazy=lazy)},
-        )
-        mapper(Dingaling, self.tables.dingalings)
-        return User, Address, Dingaling
-
-    def _o2m_threelevel_fixture(self, lazy="select", **kw):
-        Order = self.classes.Order
-        User = self.classes.User
-        Address = self.classes.Address
-        Dingaling = self.classes.Dingaling
-
-        mapper(
-            Order, self.tables.orders, properties={"user": relationship(User)}
-        )
-
-        mapper(
-            User,
-            self.tables.users,
-            properties={
-                "addresses": relationship(
-                    Address,
-                    order_by=self.tables.addresses.c.id,
-                    lazy=lazy,
-                    **kw
-                )
-            },
-        )
-        mapper(
-            Address,
-            self.tables.addresses,
-            properties={"dingalings": relationship(Dingaling, lazy=lazy)},
-        )
-        mapper(Dingaling, self.tables.dingalings)
-        return Order, User, Address, Dingaling
-
-    def _m2o_fixture(self):
-        User = self.classes.User
-        Address = self.classes.Address
-
-        mapper(User, self.tables.users)
-        mapper(
-            Address,
-            self.tables.addresses,
-            properties={"user": relationship(User)},
-        )
-        return User, Address
-
-    def test_same_lazyload_multiple_paths(self):
-        """this is an initial test that requires the presence of
-        the ResultMetaData._adapt_to_context method.
-
-        Both queries emit a lazyload against User.addresses that then
-        includes a subqueryload against Address.dingalings.
-
-        The subqueryload produces an adapted query that has anonymized
-        columns.  for these to be locatable in the result, the columns in
-        the adapted query have to match up to the result map, which is not
-        the case if the statement is pulled from the cache.
-
-        The query is done in two different ways so that one lazyload will not
-        have a state.load_path on User, and the other one will. this means
-        there will be two different baked queries that both produce the same
-        Core SELECT statement. Using logical cache keys in Core, rather than
-        based on identity, means two different BakedContext objects will use
-        the same Core context.   For this reason, at result fetch time the
-        ResultMetaData._adapt_to_context step is required so that the ORM can
-        still locate the columns.
-
-        This will all be done differently with direct ORM results, however this
-        same issue that ResultMetaData has to be adapted to the live statement
-        will exist indepndently of baked queries.  More statement-level caching
-        tests need to be added which test with complex statements that include
-        anonymous columns.
-
-        """
-        Order, User, Address, Dingaling = self._o2m_threelevel_fixture()
-
-        sess = Session()
-        from sqlalchemy.orm import joinedload
-
-        o1 = (
-            sess.query(Order)
-            .options(
-                joinedload(Order.user)
-                .lazyload(User.addresses)
-                .subqueryload(Address.dingalings)
-            )
-            .filter(Order.id == 2)
-            .first()
-        )
-        assert o1.user.addresses[0].dingalings
-
-        sess.expire_all()
-
-        u1 = (
-            sess.query(User)
-            .options(lazyload(User.addresses).subqueryload(Address.dingalings))
-            .filter(User.id == 9)
-            .first()
-        )
-        assert u1.addresses[0].dingalings
-
-    def test_no_cache_for_event(self, modify_query_fixture):
-
-        m1 = modify_query_fixture(False)
-
-        User, Address = self._o2m_fixture()
-
-        sess = Session()
-        u1 = sess.query(User).filter(User.id == 7).first()
-
-        u1.addresses
-
-        eq_(m1.mock_calls, [mock.call(User), mock.call(Address)])
-
-        sess.expire(u1, ["addresses"])
-
-        u1.addresses
-        eq_(
-            m1.mock_calls,
-            [mock.call(User), mock.call(Address), mock.call(Address)],
-        )
-
-    def test_cache_ok_for_event(self, modify_query_fixture):
-
-        m1 = modify_query_fixture(True)
-
-        User, Address = self._o2m_fixture()
-
-        sess = Session()
-        u1 = sess.query(User).filter(User.id == 7).first()
-
-        u1.addresses
-
-        eq_(m1.mock_calls, [mock.call(User), mock.call(Address)])
-
-        sess.expire(u1, ["addresses"])
-
-        u1.addresses
-        eq_(m1.mock_calls, [mock.call(User), mock.call(Address)])
-
-    def test_aliased_unbound_are_now_safe_to_cache(self):
-        User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined")
-
-        class SubDingaling(Dingaling):
-            pass
-
-        mapper(SubDingaling, None, inherits=Dingaling)
-
-        lru = Address.dingalings.property._lazy_strategy._bakery(
-            lambda q: None
-        )._bakery
-        l1 = len(lru)
-        for i in range(5):
-            sess = Session()
-            u1 = (
-                sess.query(User)
-                .options(
-                    defaultload(User.addresses).lazyload(
-                        Address.dingalings.of_type(aliased(SubDingaling))
-                    )
-                )
-                .first()
-            )
-            for ad in u1.addresses:
-                ad.dingalings
-        l2 = len(lru)
-        eq_(l1, 0)
-        eq_(l2, 2)
-
-    def test_aliased_bound_are_now_safe_to_cache(self):
-        User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined")
-
-        class SubDingaling(Dingaling):
-            pass
-
-        mapper(SubDingaling, None, inherits=Dingaling)
-
-        lru = Address.dingalings.property._lazy_strategy._bakery(
-            lambda q: None
-        )._bakery
-        l1 = len(lru)
-        for i in range(5):
-            sess = Session()
-            u1 = (
-                sess.query(User)
-                .options(
-                    Load(User)
-                    .defaultload(User.addresses)
-                    .lazyload(
-                        Address.dingalings.of_type(aliased(SubDingaling))
-                    )
-                )
-                .first()
-            )
-            for ad in u1.addresses:
-                ad.dingalings
-        l2 = len(lru)
-        eq_(l1, 0)
-        eq_(l2, 2)
-
-    def test_safe_unbound_option_allows_bake(self):
-        User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined")
-
-        lru = Address.dingalings.property._lazy_strategy._bakery(
-            lambda q: None
-        )._bakery
-        l1 = len(lru)
-        for i in range(5):
-            sess = Session()
-            u1 = (
-                sess.query(User)
-                .options(
-                    defaultload(User.addresses).lazyload(Address.dingalings)
-                )
-                .first()
-            )
-            for ad in u1.addresses:
-                ad.dingalings
-        l2 = len(lru)
-        eq_(l1, 0)
-        eq_(l2, 2)
-
-    def test_safe_bound_option_allows_bake(self):
-        User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined")
-
-        lru = Address.dingalings.property._lazy_strategy._bakery(
-            lambda q: None
-        )._bakery
-        l1 = len(lru)
-        for i in range(5):
-            sess = Session()
-            u1 = (
-                sess.query(User)
-                .options(
-                    Load(User)
-                    .defaultload(User.addresses)
-                    .lazyload(Address.dingalings)
-                )
-                .first()
-            )
-            for ad in u1.addresses:
-                ad.dingalings
-        l2 = len(lru)
-        eq_(l1, 0)
-        eq_(l2, 2)
-
-    def test_baked_lazy_loading_relationship_flag_true(self):
-        self._test_baked_lazy_loading_relationship_flag(True)
-
-    def test_baked_lazy_loading_relationship_flag_false(self):
-        self._test_baked_lazy_loading_relationship_flag(False)
-
-    def _test_baked_lazy_loading_relationship_flag(self, flag):
-        User, Address = self._o2m_fixture(bake_queries=flag)
-        from sqlalchemy import inspect
-        from sqlalchemy.orm.interfaces import UserDefinedOption
-
-        address_mapper = inspect(Address)
-        sess = Session(testing.db)
-
-        # there's no event in the compile process either at the ORM
-        # or core level and it is not easy to patch.  the option object
-        # is the one thing that will get carried into the lazyload from the
-        # outside and invoked on a per-compile basis
-
-        class MockOpt(UserDefinedOption):
-            _is_compile_state = True
-            propagate_to_loaders = True
-            _is_legacy_option = True
-
-            def _gen_cache_key(self, *args):
-                return ("hi",)
-
-            def _generate_path_cache_key(self, *args):
-                return ("hi",)
-
-            def _generate_cache_key(self, *args):
-                return (("hi",), [])
-
-            _mock = mock.Mock()
-
-            def process_query(self, *args):
-                self._mock.process_query(*args)
-
-            def process_query_conditionally(self, *args):
-                self._mock.process_query_conditionally(*args)
-
-            def process_compile_state(self, *args):
-                self._mock.process_compile_state(*args)
-
-            def orm_execute(self):
-                self._mock.orm_execute()
-
-            @property
-            def mock_calls(self):
-                return self._mock.mock_calls
-
-        mock_opt = MockOpt()
-
-        u1 = sess.query(User).options(mock_opt).first()
-
-        @event.listens_for(sess, "do_orm_execute")
-        def _my_compile_state(context):
-            if (
-                context.statement._raw_columns[0]._annotations["parententity"]
-                is address_mapper
-            ):
-                mock_opt.orm_execute()
-
-        u1.addresses
-
-        sess.expire(u1)
-        u1.addresses
-
-        if flag:
-            eq_(
-                mock_opt.mock_calls,
-                [
-                    mock.call.process_query(mock.ANY),
-                    mock.call.process_compile_state(mock.ANY),  # query.first()
-                    mock.call.process_query_conditionally(mock.ANY),
-                    mock.call.orm_execute(),  # lazyload addresses
-                    mock.call.process_compile_state(mock.ANY),  # emit lazyload
-                    mock.call.process_compile_state(
-                        mock.ANY
-                    ),  # load scalar attributes for user
-                    # lazyload addresses, no call to process_compile_state
-                    mock.call.orm_execute(),
-                ],
-            )
-        else:
-            eq_(
-                mock_opt.mock_calls,
-                [
-                    mock.call.process_query(mock.ANY),
-                    mock.call.process_compile_state(mock.ANY),  # query.first()
-                    mock.call.process_query_conditionally(mock.ANY),
-                    mock.call.orm_execute(),  # lazyload addresses
-                    mock.call.process_compile_state(mock.ANY),  # emit_lazyload
-                    mock.call.process_compile_state(
-                        mock.ANY
-                    ),  # load_scalar_attributes for user
-                    mock.call.process_query_conditionally(mock.ANY),
-                    mock.call.orm_execute(),  # lazyload addresses
-                    mock.call.process_compile_state(
-                        mock.ANY
-                    ),  # emit_lazyload, here the query was not cached
-                ],
-            )
-
-    def test_baked_lazy_loading_option_o2m(self):
-        User, Address = self._o2m_fixture()
-        self._test_baked_lazy_loading(set_option=True)
-
-    def test_baked_lazy_loading_mapped_o2m(self):
-        User, Address = self._o2m_fixture(lazy="baked_select")
-        self._test_baked_lazy_loading(set_option=False)
-
-    def _test_baked_lazy_loading(self, set_option):
-        User, Address = self.classes.User, self.classes.Address
-
-        base_bq = self.bakery(lambda s: s.query(User))
-
-        if set_option:
-            base_bq += lambda q: q.options(lazyload(User.addresses))
-
-        base_bq += lambda q: q.order_by(User.id)
-
-        assert_result = self.static.user_address_result
-
-        for i in range(4):
-            for cond1, cond2 in itertools.product(
-                *[(False, True) for j in range(2)]
-            ):
-
-                bq = base_bq._clone()
-                sess = Session()
-
-                if cond1:
-                    bq += lambda q: q.filter(User.name == "jack")
-                else:
-                    bq += lambda q: q.filter(User.name.like("%ed%"))
-
-                if cond2:
-                    ct = func.count(Address.id).label("count")
-                    subq = (
-                        sess.query(ct, Address.user_id)
-                        .group_by(Address.user_id)
-                        .having(ct > 2)
-                        .subquery()
-                    )
-
-                    bq += lambda q: q.join(subq)
-
-                if cond2:
-                    if cond1:
-
-                        def go():
-                            result = bq(sess).all()
-                            eq_([], result)
-
-                        self.assert_sql_count(testing.db, go, 1)
-                    else:
-
-                        def go():
-                            result = bq(sess).all()
-                            eq_(assert_result[1:2], result)
-
-                        self.assert_sql_count(testing.db, go, 2)
-                else:
-                    if cond1:
-
-                        def go():
-                            result = bq(sess).all()
-                            eq_(assert_result[0:1], result)
-
-                        self.assert_sql_count(testing.db, go, 2)
-                    else:
-
-                        def go():
-                            result = bq(sess).all()
-                            eq_(assert_result[1:3], result)
-
-                        self.assert_sql_count(testing.db, go, 3)
-
-                sess.close()
-
-    def test_baked_lazy_loading_m2o(self):
-        User, Address = self._m2o_fixture()
-
-        base_bq = self.bakery(lambda s: s.query(Address))
-
-        base_bq += lambda q: q.options(lazyload(Address.user))
-        base_bq += lambda q: q.order_by(Address.id)
-
-        assert_result = self.static.address_user_result
-
-        for i in range(4):
-            for cond1 in (False, True):
-                bq = base_bq._clone()
-
-                sess = Session()
-
-                if cond1:
-                    bq += lambda q: q.filter(
-                        Address.email_address == "jack@bean.com"
-                    )
-                else:
-                    bq += lambda q: q.filter(
-                        Address.email_address.like("ed@%")
-                    )
-
-                if cond1:
-
-                    def go():
-                        result = bq(sess).all()
-                        eq_(assert_result[0:1], result)
-
-                    self.assert_sql_count(testing.db, go, 2)
-                else:
-
-                    def go():
-                        result = bq(sess).all()
-                        eq_(assert_result[1:4], result)
-
-                    self.assert_sql_count(testing.db, go, 2)
-
-                sess.close()
-
-    def test_useget_cancels_eager(self):
-        """test that a one to many lazyload cancels the unnecessary
-        eager many-to-one join on the other side."""
-
-        User = self.classes.User
-        Address = self.classes.Address
-
-        mapper(User, self.tables.users)
-        mapper(
-            Address,
-            self.tables.addresses,
-            properties={
-                "user": relationship(
-                    User,
-                    lazy="joined",
-                    backref=backref("addresses", lazy="baked_select"),
-                )
-            },
-        )
-
-        sess = Session()
-        u1 = sess.query(User).filter(User.id == 8).one()
-
-        def go():
-            eq_(u1.addresses[0].user, u1)
-
-        self.assert_sql_execution(
-            testing.db,
-            go,
-            CompiledSQL(
-                "SELECT addresses.id AS addresses_id, addresses.user_id AS "
-                "addresses_user_id, addresses.email_address AS "
-                "addresses_email_address FROM addresses WHERE :param_1 = "
-                "addresses.user_id",
-                {"param_1": 8},
-            ),
-        )
-
-    def test_useget_cancels_eager_propagated_present(self):
-        """test that a one to many lazyload cancels the unnecessary
-        eager many-to-one join on the other side, even when a propagated
-        option is present."""
-
-        User = self.classes.User
-        Address = self.classes.Address
-
-        mapper(User, self.tables.users)
-        mapper(
-            Address,
-            self.tables.addresses,
-            properties={
-                "user": relationship(
-                    User,
-                    lazy="joined",
-                    backref=backref("addresses", lazy="baked_select"),
-                )
-            },
-        )
-
-        from sqlalchemy.orm.interfaces import MapperOption
-
-        class MyBogusOption(MapperOption):
-            propagate_to_loaders = True
-
-        sess = Session()
-        u1 = (
-            sess.query(User)
-            .options(MyBogusOption())
-            .filter(User.id == 8)
-            .one()
-        )
-
-        def go():
-            eq_(u1.addresses[0].user, u1)
-
-        self.assert_sql_execution(
-            testing.db,
-            go,
-            CompiledSQL(
-                "SELECT addresses.id AS addresses_id, addresses.user_id AS "
-                "addresses_user_id, addresses.email_address AS "
-                "addresses_email_address FROM addresses WHERE :param_1 = "
-                "addresses.user_id",
-                {"param_1": 8},
-            ),
-        )
-
-    def test_simple_lazy_clause_no_race_on_generate(self):
-        User, Address = self._o2m_fixture()
-
-        (
-            expr1,
-            paramdict1,
-        ) = User.addresses.property._lazy_strategy._simple_lazy_clause
-
-        # delete the attr, as though a concurrent thread is also generating it
-        del User.addresses.property._lazy_strategy._simple_lazy_clause
-        (
-            expr2,
-            paramdict2,
-        ) = User.addresses.property._lazy_strategy._simple_lazy_clause
-
-        eq_(paramdict1, paramdict2)
-
-    # additional tests:
-    # 1. m2m w lazyload
-    # 2. o2m lazyload where m2o backrefs have an eager load, test
-    # that eager load is canceled out
-    # 3. uselist = False, uselist=False assertion
-
-
 # assert that the integration style illustrated in the dogpile.cache
 # example works w/ baked
 class CustomIntegrationTest(testing.AssertsCompiledSQL, BakedTest):
index d8214465a49006aa1805634fc29e4998edc560b0..e33e95cc0339c8552c9ed41fb8e905599902848e 100644 (file)
@@ -1525,6 +1525,7 @@ class _PolymorphicTestBase(object):
         )
 
     def test_self_referential_two(self):
+
         sess = create_session()
         palias = aliased(Person)
         expected = [(m1, e1), (m1, e2), (m1, b1)]
@@ -1540,13 +1541,62 @@ class _PolymorphicTestBase(object):
             expected,
         )
 
+    def test_self_referential_two_point_five(self):
+        """Using two aliases, the above case works.
+        """
+        sess = create_session()
+        palias = aliased(Person)
+        palias2 = aliased(Person)
+
+        expected = [(m1, e1), (m1, e2), (m1, b1)]
+
+        eq_(
+            sess.query(palias, palias2)
+            .filter(palias.company_id == palias2.company_id)
+            .filter(palias.name == "dogbert")
+            .filter(palias.person_id > palias2.person_id)
+            .from_self()
+            .order_by(palias.person_id, palias2.person_id)
+            .all(),
+            expected,
+        )
+
     def test_self_referential_two_future(self):
+        # TODO: this is the SECOND test *EVER* of an aliased class of
+        # an aliased class.
+        sess = create_session(testing.db, future=True)
+        expected = [(m1, e1), (m1, e2), (m1, b1)]
+
+        # not aliasing the first class
+        p1 = Person
+        p2 = aliased(Person)
+        stmt = (
+            select(p1, p2)
+            .filter(p1.company_id == p2.company_id)
+            .filter(p1.name == "dogbert")
+            .filter(p1.person_id > p2.person_id)
+        )
+
+        subq = stmt.subquery()
+
+        pa1 = aliased(p1, subq)
+        pa2 = aliased(p2, subq)
+
+        stmt2 = select(pa1, pa2).order_by(pa1.person_id, pa2.person_id)
+
+        eq_(
+            sess.execute(stmt2).unique().all(), expected,
+        )
+
+    def test_self_referential_two_point_five_future(self):
+
         # TODO: this is the first test *EVER* of an aliased class of
         # an aliased class.  we should add many more tests for this.
         # new case added in Id810f485c5f7ed971529489b84694e02a3356d6d
         sess = create_session(testing.db, future=True)
         expected = [(m1, e1), (m1, e2), (m1, b1)]
 
+        # aliasing the first class
         p1 = aliased(Person)
         p2 = aliased(Person)
         stmt = (
@@ -1560,10 +1610,10 @@ class _PolymorphicTestBase(object):
         pa1 = aliased(p1, subq)
         pa2 = aliased(p2, subq)
 
-        stmt = select(pa1, pa2).order_by(pa1.person_id, pa2.person_id)
+        stmt2 = select(pa1, pa2).order_by(pa1.person_id, pa2.person_id)
 
         eq_(
-            sess.execute(stmt).unique().all(), expected,
+            sess.execute(stmt2).unique().all(), expected,
         )
 
     def test_nesting_queries(self):
index cb9eca479964e1b6a8588b69141c4790b70991de..c7d2042b310ee822cbff5726abc4e8e92fc3c81c 100644 (file)
@@ -1729,10 +1729,15 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest):
         Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes()
 
         s = Session()
+
+        # as of from_self() changing in
+        # I3abfb45dd6e50f84f29d39434caa0b550ce27864,
+        # this query is coming out instead which is equivalent, but not
+        # totally sure where this happens
+
         self.assert_compile(
             s.query(Sub2).from_self().join(Sub2.ep1).join(Sub2.ep2),
             "SELECT anon_1.sub2_id AS anon_1_sub2_id, "
-            "anon_1.base2_id AS anon_1_base2_id, "
             "anon_1.base2_base1_id AS anon_1_base2_base1_id, "
             "anon_1.base2_data AS anon_1_base2_data, "
             "anon_1.sub2_subdata AS anon_1_sub2_subdata "
@@ -1740,14 +1745,19 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest):
             "base2.base1_id AS base2_base1_id, base2.data AS base2_data, "
             "sub2.subdata AS sub2_subdata "
             "FROM base2 JOIN sub2 ON base2.id = sub2.id) AS anon_1 "
-            "JOIN ep1 ON anon_1.base2_id = ep1.base2_id "
-            "JOIN ep2 ON anon_1.base2_id = ep2.base2_id",
+            "JOIN ep1 ON anon_1.sub2_id = ep1.base2_id "
+            "JOIN ep2 ON anon_1.sub2_id = ep2.base2_id",
         )
 
     def test_seven(self):
         Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes()
 
         s = Session()
+
+        # as of from_self() changing in
+        # I3abfb45dd6e50f84f29d39434caa0b550ce27864,
+        # this query is coming out instead which is equivalent, but not
+        # totally sure where this happens
         self.assert_compile(
             # adding Sub2 to the entities list helps it,
             # otherwise the joins for Sub2.ep1/ep2 don't have columns
@@ -1761,7 +1771,6 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest):
             "SELECT anon_1.parent_id AS anon_1_parent_id, "
             "anon_1.parent_data AS anon_1_parent_data, "
             "anon_1.sub2_id AS anon_1_sub2_id, "
-            "anon_1.base2_id AS anon_1_base2_id, "
             "anon_1.base2_base1_id AS anon_1_base2_base1_id, "
             "anon_1.base2_data AS anon_1_base2_data, "
             "anon_1.sub2_subdata AS anon_1_sub2_subdata "
@@ -1775,8 +1784,8 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest):
             "ON parent.id = sub1.parent_id JOIN "
             "(base2 JOIN sub2 ON base2.id = sub2.id) "
             "ON base1.id = base2.base1_id) AS anon_1 "
-            "JOIN ep1 ON anon_1.base2_id = ep1.base2_id "
-            "JOIN ep2 ON anon_1.base2_id = ep2.base2_id",
+            "JOIN ep1 ON anon_1.sub2_id = ep1.base2_id "
+            "JOIN ep2 ON anon_1.sub2_id = ep2.base2_id",
         )
 
 
index b2477cba70de70daf6e09d84f25b3b1ed72944d1..c1cc85261f1f5109ad8bc13597cbb652c835c645 100644 (file)
@@ -22,13 +22,11 @@ from sqlalchemy.orm import exc as orm_exc
 from sqlalchemy.orm import mapper
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import Session
-from sqlalchemy.orm.interfaces import MapperOption
 from sqlalchemy.testing import assert_raises
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import is_false
 from sqlalchemy.testing import is_true
-from sqlalchemy.testing import mock
 from sqlalchemy.testing.assertsql import CompiledSQL
 from sqlalchemy.testing.schema import Column
 from sqlalchemy.testing.schema import Table
@@ -405,73 +403,6 @@ class LazyTest(_fixtures.FixtureTest):
         fred = s.query(User).filter_by(name="fred").one()
         eq_(fred.addresses, [])  # fred is missing
 
-    def test_custom_bind(self):
-        Address, addresses, users, User = (
-            self.classes.Address,
-            self.tables.addresses,
-            self.tables.users,
-            self.classes.User,
-        )
-
-        mapper(
-            User,
-            users,
-            properties=dict(
-                addresses=relationship(
-                    mapper(Address, addresses),
-                    lazy="select",
-                    primaryjoin=and_(
-                        users.c.id == addresses.c.user_id,
-                        users.c.name == bindparam("name"),
-                    ),
-                )
-            ),
-        )
-
-        canary = mock.Mock()
-
-        class MyOption(MapperOption):
-            propagate_to_loaders = True
-
-            def __init__(self, crit):
-                self.crit = crit
-
-            def process_query_conditionally(self, query):
-                """process query during a lazyload"""
-                canary()
-                query.params.non_generative(query, dict(name=self.crit))
-
-        s = Session()
-        ed = s.query(User).options(MyOption("ed")).filter_by(name="ed").one()
-        eq_(
-            ed.addresses,
-            [
-                Address(id=2, user_id=8),
-                Address(id=3, user_id=8),
-                Address(id=4, user_id=8),
-            ],
-        )
-        eq_(canary.mock_calls, [mock.call()])
-
-        fred = (
-            s.query(User).options(MyOption("ed")).filter_by(name="fred").one()
-        )
-        eq_(fred.addresses, [])  # fred is missing
-        eq_(canary.mock_calls, [mock.call(), mock.call()])
-
-        # the lazy query was not cached; the option is re-applied to the
-        # Fred object due to populate_existing()
-        fred = (
-            s.query(User)
-            .populate_existing()
-            .options(MyOption("fred"))
-            .filter_by(name="fred")
-            .one()
-        )
-        eq_(fred.addresses, [Address(id=5, user_id=9)])  # fred is there
-
-        eq_(canary.mock_calls, [mock.call(), mock.call(), mock.call()])
-
     def test_one_to_many_scalar(self):
         Address, addresses, users, User = (
             self.classes.Address,
@@ -1398,8 +1329,8 @@ class O2MWOSideFixedTest(fixtures.MappedTest):
             go,
             CompiledSQL(
                 "SELECT person.id AS person_id, person.city_id AS "
-                "person_city_id FROM person "
-                "WHERE person.city_id = :param_1 AND :param_2 = 0",
+                "person_city_id FROM person WHERE person.city_id = :param_1 "
+                "AND :param_2 = 0",
                 {"param_1": 2, "param_2": 1},
             ),
         )
@@ -1585,9 +1516,9 @@ class TypeCoerceTest(fixtures.MappedTest, testing.AssertsExecutionResults):
 
         asserter.assert_(
             CompiledSQL(
-                "SELECT pets.id AS pets_id, pets.person_id "
-                "AS pets_person_id FROM pets "
-                "WHERE pets.person_id = CAST(:param_1 AS INTEGER)",
+                "SELECT pets.id AS pets_id, pets.person_id AS "
+                "pets_person_id FROM pets WHERE pets.person_id = "
+                "CAST(:param_1 AS INTEGER)",
                 [{"param_1": 5}],
             )
         )
index ffff55d0ec651f84a90dfa64afa9f0f7661c106b..b849e7602a93dc46b4bc35ac3c1ef4f4759b28b1 100644 (file)
@@ -165,66 +165,66 @@ test.aaa_profiling.test_misc.EnumTest.test_create_enum_from_pep_435_w_expensive_
 
 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation
 
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 2.7_sqlite_pysqlite_dbapiunicode_cextensions 45105
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 55905
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 3.8_sqlite_pysqlite_dbapiunicode_cextensions 49105
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 60705
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 2.7_sqlite_pysqlite_dbapiunicode_cextensions 46105
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 57205
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 3.8_sqlite_pysqlite_dbapiunicode_cextensions 50105
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 62005
 
 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation
 
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 2.7_sqlite_pysqlite_dbapiunicode_cextensions 44005
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 54805
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 3.8_sqlite_pysqlite_dbapiunicode_cextensions 48005
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 59605
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 2.7_sqlite_pysqlite_dbapiunicode_cextensions 45005
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 56105
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 3.8_sqlite_pysqlite_dbapiunicode_cextensions 49005
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 60905
 
 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations
 
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 43105
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 51405
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 46405
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 55505
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 44105
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 52705
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 47405
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 56805
 
 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations
 
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 42305
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 50605
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 45605
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 54705
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 43305
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 51905
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 46605
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 56005
 
 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle
 
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 2.7_sqlite_pysqlite_dbapiunicode_cextensions 41705
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 45005
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 3.8_sqlite_pysqlite_dbapiunicode_cextensions 44405
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 48505
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 2.7_sqlite_pysqlite_dbapiunicode_cextensions 42705
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 46305
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 3.8_sqlite_pysqlite_dbapiunicode_cextensions 45405
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 49805
 
 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations
 
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 43105
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 51405
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 46405
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 55505
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 44105
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 52705
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 47405
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 56805
 
 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations
 
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 42305
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 50605
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 45605
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 54705
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 43305
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 51905
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 46605
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 56005
 
 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations
 
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 27205
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 29405
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 30105
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 32505
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 28205
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 30705
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 31105
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 33805
 
 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations
 
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 26405
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 28605
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 29305
-test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 31705
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 27405
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 29905
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 30305
+test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 33005
 
 # TEST: test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set
 
@@ -256,59 +256,59 @@ test.aaa_profiling.test_orm.BranchedOptionTest.test_query_opts_unbound_branching
 
 # TEST: test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline
 
-test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_sqlite_pysqlite_dbapiunicode_cextensions 15150
-test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 26159
-test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.8_sqlite_pysqlite_dbapiunicode_cextensions 15188
-test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 27200
+test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_sqlite_pysqlite_dbapiunicode_cextensions 15158
+test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 26170
+test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.8_sqlite_pysqlite_dbapiunicode_cextensions 15196
+test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 27211
 
 # TEST: test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols
 
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_cextensions 21294
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 26303
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.8_sqlite_pysqlite_dbapiunicode_cextensions 21339
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 27351
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_cextensions 21312
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 26324
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.8_sqlite_pysqlite_dbapiunicode_cextensions 21357
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 27372
 
 # TEST: test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased
 
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 2.7_sqlite_pysqlite_dbapiunicode_cextensions 9703
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 9853
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 3.8_sqlite_pysqlite_dbapiunicode_cextensions 10154
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 10304
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 2.7_sqlite_pysqlite_dbapiunicode_cextensions 9853
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 10003
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 3.8_sqlite_pysqlite_dbapiunicode_cextensions 10304
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 10454
 
 # TEST: test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain
 
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_sqlite_pysqlite_dbapiunicode_cextensions 3953
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 4103
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 3.8_sqlite_pysqlite_dbapiunicode_cextensions 3954
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 4104
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_sqlite_pysqlite_dbapiunicode_cextensions 4053
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 4203
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 3.8_sqlite_pysqlite_dbapiunicode_cextensions 4054
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 4204
 
 # TEST: test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d
 
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 2.7_sqlite_pysqlite_dbapiunicode_cextensions 93738
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 94088
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 3.8_sqlite_pysqlite_dbapiunicode_cextensions 101554
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 101704
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 2.7_sqlite_pysqlite_dbapiunicode_cextensions 94638
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 94788
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 3.8_sqlite_pysqlite_dbapiunicode_cextensions 102254
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 102204
 
 # TEST: test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased
 
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 2.7_sqlite_pysqlite_dbapiunicode_cextensions 91788
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 92138
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 3.8_sqlite_pysqlite_dbapiunicode_cextensions 99919
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 100069
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 2.7_sqlite_pysqlite_dbapiunicode_cextensions 92688
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 92838
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 3.8_sqlite_pysqlite_dbapiunicode_cextensions 100604
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 100569
 
 # TEST: test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query
 
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_cextensions 434915
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 436762
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.8_sqlite_pysqlite_dbapiunicode_cextensions 465676
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 467518
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_cextensions 438105
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 439952
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.8_sqlite_pysqlite_dbapiunicode_cextensions 468876
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 470718
 
 # TEST: test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results
 
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_sqlite_pysqlite_dbapiunicode_cextensions 391100
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 405907
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.8_sqlite_pysqlite_dbapiunicode_cextensions 395113
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 412627
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 406507
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.8_sqlite_pysqlite_dbapiunicode_cextensions 396913
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 412027
 
 # TEST: test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity
 
@@ -319,24 +319,24 @@ test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_
 
 # 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.7_sqlite_pysqlite_dbapiunicode_cextensions 77560
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 79780
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.8_sqlite_pysqlite_dbapiunicode_cextensions 80098
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 83365
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_cextensions 80113
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 82391
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.8_sqlite_pysqlite_dbapiunicode_cextensions 83854
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 87383
 
 # 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.7_sqlite_pysqlite_dbapiunicode_cextensions 18701
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 19147
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.8_sqlite_pysqlite_dbapiunicode_cextensions 19670
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 20154
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_cextensions 20145
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 20583
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.8_sqlite_pysqlite_dbapiunicode_cextensions 21187
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 21723
 
 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_load
 
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1035
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1062
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.8_sqlite_pysqlite_dbapiunicode_cextensions 1079
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 1113
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1313
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1345
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.8_sqlite_pysqlite_dbapiunicode_cextensions 1384
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 1425
 
 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_no_load
 
@@ -347,23 +347,23 @@ test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 3.8_sqlite_pysqlite_dba
 
 # TEST: test.aaa_profiling.test_orm.QueryTest.test_query_cols
 
-test.aaa_profiling.test_orm.QueryTest.test_query_cols 2.7_sqlite_pysqlite_dbapiunicode_cextensions 5306
-test.aaa_profiling.test_orm.QueryTest.test_query_cols 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 6026
-test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.8_sqlite_pysqlite_dbapiunicode_cextensions 5674
-test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 6414
+test.aaa_profiling.test_orm.QueryTest.test_query_cols 2.7_sqlite_pysqlite_dbapiunicode_cextensions 5397
+test.aaa_profiling.test_orm.QueryTest.test_query_cols 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 6147
+test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.8_sqlite_pysqlite_dbapiunicode_cextensions 5765
+test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 6535
 
 # TEST: test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results
 
-test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_cextensions 152251
-test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 168676
-test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_cextensions 159629
-test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 177451
+test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_cextensions 232364
+test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 248890
+test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_cextensions 244874
+test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 262595
 
 # TEST: test.aaa_profiling.test_orm.SessionTest.test_expire_lots
 
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1135
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1150
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.8_sqlite_pysqlite_dbapiunicode_cextensions 1241
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1141
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1156
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.8_sqlite_pysqlite_dbapiunicode_cextensions 1259
 test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 1256
 
 # TEST: test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect