]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Use baked lazyloading by default
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 7 Apr 2017 18:18:22 +0000 (14:18 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 13 Apr 2017 18:22:59 +0000 (14:22 -0400)
The ``lazy="select"`` loader strategy now makes used of the
:class:`.BakedQuery` query caching system in all cases.  This
removes most overhead of generating a :class:`.Query` object and
running it into a :func:`.select` and then string SQL statement from
the process of lazy-loading related collections and objects.  The
"baked" lazy loader has also been improved such that it can now
cache in most cases where query load options are used.

Change-Id: Ic96792fffaa045ae9aa0a4657d6d29235d3efb85
Fixes: #3954
16 files changed:
doc/build/changelog/changelog_12.rst
doc/build/changelog/migration_12.rst
doc/build/orm/extensions/baked.rst
lib/sqlalchemy/ext/baked.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/relationships.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/strategy_options.py
lib/sqlalchemy/orm/util.py
lib/sqlalchemy/util/_collections.py
test/aaa_profiling/test_memusage.py
test/ext/test_baked.py
test/orm/test_options.py
test/profiles.txt

index 3b611d147630b4fc97f7634ba20fd2fa2951c3d0..7b47149f8abfcd3d65de9022adb0b0cb51990068 100644 (file)
         that the loader options will match to an aliased or non-aliased
         entity more specifically if those options include entity information.
 
+    .. change:: 3954
+        :tags: feature, orm
+        :tickets: 3954
+
+        The ``lazy="select"`` loader strategy now makes used of the
+        :class:`.BakedQuery` query caching system in all cases.  This
+        removes most overhead of generating a :class:`.Query` object and
+        running it into a :func:`.select` and then string SQL statement from
+        the process of lazy-loading related collections and objects.  The
+        "baked" lazy loader has also been improved such that it can now
+        cache in most cases where query load options are used.
+
+        .. seealso::
+
+            :ref:`change_3954`
+
     .. change:: 3740
         :tags: bug, sql
         :tickets: 3740
index e36e0af0877230b5d500974099b6c025b3283aba..c673eaa2e91c6bccff34f5cfa3a81dba3baf4196 100644 (file)
@@ -34,6 +34,42 @@ SQLAlchemy is currently tested on versions 3.5 and 3.6.
 New Features and Improvements - ORM
 ===================================
 
+.. _change_3954:
+
+"Baked" loading now the default for lazy loads
+----------------------------------------------
+
+The :mod:`sqlalchemy.ext.baked` extension, first introduced in the 1.0 series,
+allows for the construction of a so-called :class:`.BakedQuery` object,
+which is an object that generates a :class:`.Query` object in conjunction
+with a cache key representing the structure of the query; this cache key
+is then linked to the resulting string SQL statement so that subsequent use
+of another :class:`.BakedQuery` with the same structure will bypass all the
+overhead of building the :class:`.Query` object, building the core
+:func:`.select` object within, as well as the compilation of the :func:`.select`
+into a string, cutting out well the majority of function call overhead normally
+associated with constructing and emitting an ORM :class:`.Query` object.
+
+The :class:`.BakedQuery` is now used by default by the ORM when it generates
+a "lazy" query for the lazy load of a :func:`.relationship` construct, e.g.
+that of the default ``lazy="select"`` relationship loader strategy.  This
+will allow for a significant reduction in function calls within the scope
+of an application's use of lazy load queries to load collections and related
+objects.   Previously, this feature was available
+in 1.0 and 1.1 through the use of a global API method or by using the
+``baked_select`` strategy, it's now the only implementation for this behavior.
+The feature has also been improved such that the caching can still take place
+for objects that have additional loader options in effect subsequent
+to the lazy load.
+
+The caching behavior can be disabled on a per-relationship basis using the
+:paramref:`.relationship.bake_queries` flag, which is available for
+very unusual cases, such as a relationship that uses a custom
+:class:`.Query` implementation that's not compatible with caching.
+
+
+:ticket:`3954`
+
 .. _change_3229:
 
 Support for bulk updates of hybrids, composites
index 0fb8da5dfec50f599282313ca88dd9299ffd9023..0dceedc4a1ee2716e11adaad263c878951a7aaed 100644 (file)
@@ -335,37 +335,22 @@ with conditionals needed to be addressed, leading to the final approach.
 Lazy Loading Integration
 ------------------------
 
-The baked query can be integrated with SQLAlchemy's lazy loader feature
-transparently.   A future release of SQLAlchemy may enable this by default,
-as its use within lazy loading is completely transparent.    For now,
-to enable baked lazyloading for all lazyloaders systemwide, call upon
-the :func:`.bake_lazy_loaders` function.   This will impact all relationships
-that use the ``lazy='select'`` strategy as well as all use of the :func:`.lazyload`
-per-query strategy.
+The baked query system is integrated into SQLAlchemy's lazy loader feature
+as used by :func:`.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.
 
-"Baked" lazy loading may be enabled on a per-:func:`.relationship` basis
-using the ``baked_select`` loader strategy::
-
-    class MyClass(Base):
-        # ...
-
-        widgets = relationship("Widget", lazy="baked_select")
-
-The ``baked_select`` strategy is available once any part of the application
-has imported the ``sqlalchemy.ext.baked`` module.   The "bakery" used by
-this feature is local to the mapper for ``MyClass``.
-
-For per-query use, the :func:`.baked_lazyload` strategy may be used,
-which works like any other loader option.
+.. versionchanged:: 1.2  "baked" queries are now the foundation of the
+   lazy-loader feature of :func:`.relationship`.
 
 Opting out with the bake_queries flag
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 The :func:`.relationship` construct includes a flag
 :paramref:`.relationship.bake_queries` which when set to False will cause
-that relationship to opt out of the baked query system, when the
-application-wide :func:`.bake_lazy_loaders` function has been called to enable
-baked query loaders by default.
+that relationship to opt out of caching queries.
 
 API Documentation
 -----------------
@@ -378,10 +363,3 @@ API Documentation
 .. autoclass:: Result
     :members:
 
-.. autofunction:: bake_lazy_loaders
-
-.. autofunction:: unbake_lazy_loaders
-
-.. autofunction:: baked_lazyload
-
-.. autofunction:: baked_lazyload_all
index 68bd468b97e90ebd3f5d1705c4375e006b9df686..249f5db4e915172e6e4807902533fc495ca4e020 100644 (file)
@@ -41,10 +41,10 @@ class BakedQuery(object):
         self._bakery = bakery
 
     @classmethod
-    def bakery(cls, size=200):
+    def bakery(cls, size=200, _size_alert=None):
         """Construct a new bakery."""
 
-        _bakery = util.LRUCache(size)
+        _bakery = util.LRUCache(size, size_alert=_size_alert)
 
         def call(initial_fn, *args):
             return cls(_bakery, initial_fn, args)
@@ -134,6 +134,35 @@ class BakedQuery(object):
         self._spoiled = True
         return self
 
+    def _add_lazyload_options(self, options, effective_path):
+        """Used by per-state lazy loaders to add options to the
+        "lazy load" query from a parent query.
+
+        Creates a cache key based on given load path and query options;
+        if a repeatable cache key cannot be generated, the query is
+        "spoiled" so that it won't use caching.
+
+        """
+
+        key = ()
+
+        if effective_path.path[0].is_aliased_class:
+            # paths that are against an AliasedClass are unsafe to cache
+            # with since the AliasedClass is an ad-hoc object.
+            self.spoil()
+        else:
+            for opt in options:
+                cache_key = opt._generate_cache_key(effective_path)
+                if cache_key is False:
+                    self.spoil()
+                elif cache_key is not None:
+                    key += cache_key
+        self.add_criteria(
+            lambda q: q._with_current_path(effective_path).
+            _conditional_options(*options),
+            effective_path.path, key
+        )
+
     def _retrieve_baked_query(self, session):
         query = self._bakery.get(self._cache_key, None)
         if query is None:
@@ -412,125 +441,31 @@ class Result(object):
             return None
 
 
+@util.deprecated(
+    "1.2", "Baked lazy loading is now the default implementation.")
 def bake_lazy_loaders():
     """Enable the use of baked queries for all lazyloaders systemwide.
 
-    This operation should be safe for all lazy loaders, and will reduce
-    Python overhead for these operations.
+    The "baked" implementation of lazy loading is now the sole implementation
+    for the base lazy loader; this method has no effect except for a warning.
 
     """
-    BakedLazyLoader._strategy_keys[:] = []
-
-    properties.RelationshipProperty.strategy_for(
-        lazy="select")(BakedLazyLoader)
-    properties.RelationshipProperty.strategy_for(
-        lazy=True)(BakedLazyLoader)
-    properties.RelationshipProperty.strategy_for(
-        lazy="baked_select")(BakedLazyLoader)
-
-    strategies.LazyLoader._strategy_keys[:] = BakedLazyLoader._strategy_keys[:]
+    pass
 
 
+@util.deprecated(
+    "1.2", "Baked lazy loading is now the default implementation.")
 def unbake_lazy_loaders():
     """Disable the use of baked queries for all lazyloaders systemwide.
 
-    This operation reverts the changes produced by :func:`.bake_lazy_loaders`.
+    This method now raises NotImplmentedError() as the "baked" implementation
+    is the only lazy load implementation.  The
+    :paramref:`.relationship.bake_queries` flag may be used to disable
+    the caching of queries on a per-relationship basis.
 
     """
-    strategies.LazyLoader._strategy_keys[:] = []
-    BakedLazyLoader._strategy_keys[:] = []
-
-    properties.RelationshipProperty.strategy_for(
-        lazy="select")(strategies.LazyLoader)
-    properties.RelationshipProperty.strategy_for(
-        lazy=True)(strategies.LazyLoader)
-    properties.RelationshipProperty.strategy_for(
-        lazy="baked_select")(BakedLazyLoader)
-    assert strategies.LazyLoader._strategy_keys
-
-
-@sqla_log.class_logger
-@properties.RelationshipProperty.strategy_for(lazy="baked_select")
-class BakedLazyLoader(strategies.LazyLoader):
-
-    def _emit_lazyload(self, session, state, ident_key, passive):
-        q = BakedQuery(
-            self.mapper._compiled_cache,
-            lambda session: session.query(self.mapper))
-        q.add_criteria(
-            lambda q: q._adapt_all_clauses()._with_invoke_all_eagers(False),
-            self.parent_property)
-
-        if not self.parent_property.bake_queries:
-            q.spoil(full=True)
-
-        if self.parent_property.secondary is not None:
-            q.add_criteria(
-                lambda q:
-                q.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))
-
-        if state.load_options:
-            q.spoil()
-            args = state.load_path[self.parent_property]
-            q.add_criteria(
-                lambda q:
-                q._with_current_path(args), args)
-            q.add_criteria(
-                lambda q: q._conditional_options(*state.load_options))
-
-        if self.use_get:
-            return q(session)._load_on_ident(
-                session.query(self.mapper), ident_key)
-
-        if self.parent_property.order_by:
-            q.add_criteria(
-                lambda q:
-                q.order_by(*util.to_list(self.parent_property.order_by)))
-
-        for rev in self.parent_property._reverse_property:
-            # reverse props that are MANYTOONE are loading *this*
-            # object from get(), so don't need to eager out to those.
-            if rev.direction is interfaces.MANYTOONE and \
-                rev._use_get and \
-                    not isinstance(rev.strategy, strategies.LazyLoader):
-
-                q.add_criteria(
-                    lambda q:
-                    q.options(
-                        strategy_options.Load.for_existing_path(
-                            q._current_path[rev.parent]
-                        ).baked_lazyload(rev.key)
-                    )
-                )
-
-        lazy_clause, params = self._generate_lazy_clause(state, passive)
-
-        if pending:
-            if orm_util._none_set.intersection(params.values()):
-                return None
-
-        q.add_criteria(lambda q: q.filter(lazy_clause))
-        result = q(session).params(**params).all()
-        if self.uselist:
-            return result
-        else:
-            l = len(result)
-            if l:
-                if l > 1:
-                    util.warn(
-                        "Multiple rows returned with "
-                        "uselist=False for lazily-loaded attribute '%s' "
-                        % self.parent_property)
-
-                return result[0]
-            else:
-                return None
+    raise NotImplementedError(
+        "Baked lazy loading is now the default implementation")
 
 
 @strategy_options.loader_option()
@@ -543,12 +478,18 @@ def baked_lazyload(loadopt, attr):
 
 
 @baked_lazyload._add_unbound_fn
+@util.deprecated(
+    "1.2", "Baked lazy loading is now the default "
+    "implementation for lazy loading.")
 def baked_lazyload(*keys):
     return strategy_options._UnboundLoad._from_keys(
         strategy_options._UnboundLoad.baked_lazyload, keys, False, {})
 
 
 @baked_lazyload._add_unbound_all_fn
+@util.deprecated(
+    "1.2", "Baked lazy loading is now the default "
+    "implementation for lazy loading.")
 def baked_lazyload_all(*keys):
     return strategy_options._UnboundLoad._from_keys(
         strategy_options._UnboundLoad.baked_lazyload, keys, True, {})
index 1b14acefb93563643c596b386af511ca98c15186..84b5f6cc7a3c3ade07ae4697eec5e1136b3ba15d 100644 (file)
@@ -593,6 +593,28 @@ class MapperOption(object):
 
         self.process_query(query)
 
+    def _generate_cache_key(self, path):
+        """Used by the baked loader to see if this option can be cached.
+
+        A given MapperOption that returns a cache key must return a key
+        that uniquely identifies the complete state of this option, which
+        will match any other MapperOption that itself retains the identical
+        state.  This includes path options, flags, etc.
+
+        If the MapperOption does not apply to the given path and would
+        not affect query results on such a path, it should return None.
+
+        if the MapperOption **does** apply to the give path, however cannot
+        produce a safe cache key, it should return False; this will cancel
+        caching of the result.   An unsafe cache key is one that includes
+        an ad-hoc user object, typically an AliasedClass object.  As these
+        are usually created per-query, they don't work as cache keys.
+
+
+        """
+
+        return None
+
 
 class LoaderStrategy(object):
     """Describe the loading behavior of a StrategizedProperty object.
index 28a5c5b9bf75c4243906e372f4252b98e34f2002..3fdba44825ecfdf05135f897e463a84c9b25a615 100644 (file)
@@ -2721,7 +2721,7 @@ class Mapper(InspectionAttr):
         return util.LRUCache(self._compiled_cache_size,
                              size_alert=self._alert_lru_cache_limit)
 
-    def _alert_lru_cache_limit(self):
+    def _alert_lru_cache_limit(self, lru_cache):
         util.warn(
             "Compiled statement cache for mapper %s is "
             "reaching its size threshold of %d, based on _compiled_cache_size "
@@ -2730,7 +2730,7 @@ class Mapper(InspectionAttr):
             "#faq_compiled_cache_threshold"
             " for best practices." %
             (self,
-             self._compiled_cache.size_threshold,
+             lru_cache.size_threshold,
              self._compiled_cache_size))
 
     @_memoized_configured_property
index bbfa393f8c0fbcca749274021877498aacfddbf1..272ef77fb80b4b5ba5135160de14e5bfc1818e7a 100644 (file)
@@ -29,7 +29,8 @@ from .base import _entity_descriptor, _is_aliased_class, \
     _is_mapped_class, _orm_columns, _generative, InspectionAttr
 from .path_registry import PathRegistry
 from .util import (
-    AliasedClass, ORMAdapter, join as orm_join, with_parent, aliased
+    AliasedClass, ORMAdapter, join as orm_join, with_parent, aliased,
+    _entity_corresponds_to
 )
 from .. import sql, util, log, exc as sa_exc, inspect, inspection
 from ..sql.expression import _interpret_as_from
@@ -3641,18 +3642,7 @@ class _MapperEntity(_QueryEntity):
         return self.entity_zero
 
     def corresponds_to(self, entity):
-        if entity.is_aliased_class:
-            if self.is_aliased_class:
-                if entity._base_alias is self.entity_zero._base_alias:
-                    return True
-            return False
-        elif self.is_aliased_class:
-            if self.entity_zero._use_mapper_path:
-                return entity in self._with_polymorphic
-            else:
-                return entity is self.entity_zero
-
-        return entity.common_parent(self.entity_zero)
+        return _entity_corresponds_to(self.entity_zero, entity)
 
     def adapt_to_selectable(self, query, sel):
         query._entities.append(self)
index 3b83f10cdccb1b0d2d4a4debde38adc5024d5405..1005e7eeb40d254c86398c5e402466b7ac10de23 100644 (file)
@@ -102,7 +102,7 @@ class RelationshipProperty(StrategizedProperty):
                  back_populates=None,
                  post_update=False,
                  cascade=False, extension=None,
-                 viewonly=False, lazy=True,
+                 viewonly=False, lazy="select",
                  collection_class=None, passive_deletes=False,
                  passive_updates=True, remote_side=None,
                  enable_typechecks=True, join_depth=None,
@@ -277,22 +277,13 @@ class RelationshipProperty(StrategizedProperty):
 
         :param bake_queries=True:
           Use the :class:`.BakedQuery` cache to cache the construction of SQL
-          used in lazy loads, when the :func:`.bake_lazy_loaders` function has
-          first been called.  Defaults to True and is intended to provide an
-          "opt out" flag per-relationship when the baked query cache system is
-          in use.
-
-          .. warning::
-
-              This flag **only** has an effect when the application-wide
-              :func:`.bake_lazy_loaders` function has been called.   It
-              defaults to True so is an "opt out" flag.
-
-          Setting this flag to False when baked queries are otherwise in
-          use might be to reduce
-          ORM memory use for this :func:`.relationship`, or to work around
-          unresolved stability issues observed within the baked query
-          cache system.
+          used in lazy loads.  True by default.   Set to False if the
+          join condition of the relationship has unusual features that
+          might not respond well to statement caching.
+
+          .. versionchanged:: 1.2
+             "Baked" loading is the default implementation for the "select",
+             a.k.a. "lazy" loading strategy for relationships.
 
           .. versionadded:: 1.0.0
 
index c70994e8f0f11427f76d33ea8ae40f0f1658c242..fdcb54953ae0bc481b0f8c7db51c70f34a825a4a 100644 (file)
@@ -371,6 +371,7 @@ class NoLoader(AbstractRelationshipLoader):
 @properties.RelationshipProperty.strategy_for(lazy="select")
 @properties.RelationshipProperty.strategy_for(lazy="raise")
 @properties.RelationshipProperty.strategy_for(lazy="raise_on_sql")
+@properties.RelationshipProperty.strategy_for(lazy="baked_select")
 class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
     """Provide loading behavior for a :class:`.RelationshipProperty`
     with "lazy=True", that is loads when first accessed.
@@ -380,7 +381,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
     __slots__ = (
         '_lazywhere', '_rev_lazywhere', 'use_get', '_bind_to_col',
         '_equated_columns', '_rev_bind_to_col', '_rev_equated_columns',
-        '_simple_lazy_clause', '_raise_always', '_raise_on_sql')
+        '_simple_lazy_clause', '_raise_always', '_raise_on_sql',
+        '_bakery')
 
     def __init__(self, parent, strategy_key):
         super(LazyLoader, self).__init__(parent, strategy_key)
@@ -575,35 +577,90 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             for pk in self.mapper.primary_key
         ]
 
-    @util.dependencies("sqlalchemy.orm.strategy_options")
+    @util.dependencies("sqlalchemy.ext.baked")
+    def _memoized_attr__bakery(self, baked):
+        return baked.bakery(size=50, _size_alert=self._alert_lru_cache_limit)
+
+    def _alert_lru_cache_limit(self, lru_cache):
+        util.warn(
+            "Compiled statement cache for lazy loader on attribute %s is "
+            "reaching its size threshold of %d.  Consider setting "
+            "bake_queries=False for this relationship.  Please refer to "
+            "http://docs.sqlalchemy.org/en/latest/faq/performance.html"
+            "#faq_compiled_cache_threshold"
+            " for best practices." %
+            (self.parent_property,
+             lru_cache.size_threshold))
+
+    @util.dependencies(
+        "sqlalchemy.orm.strategy_options")
     def _emit_lazyload(
             self, strategy_options, session, state, ident_key, passive):
 
-        q = session.query(self.mapper)._adapt_all_clauses()
-        if self.parent_property.secondary is not None:
-            q = q.select_from(self.mapper, self.parent_property.secondary)
+        # 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.
+
+        # 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.mapper), self)
+
+        q.add_criteria(
+            lambda q: q._adapt_all_clauses()._with_invoke_all_eagers(False),
+            self.parent_property)
+
+        if not self.parent_property.bake_queries:
+            q.spoil(full=True)
 
-        q = q._with_invoke_all_eagers(False)
+        if self.parent_property.secondary is not None:
+            q.add_criteria(
+                lambda q:
+                q.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 = q.autoflush(False)
-
-        if state.load_path:
-            q = q._with_current_path(state.load_path[self.parent_property])
+            q.add_criteria(lambda q: q.autoflush(False))
 
         if state.load_options:
-            q = q._conditional_options(*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
+            )
 
         if self.use_get:
             if self._raise_on_sql:
                 self._invoke_raise_load(state, passive, "raise_on_sql")
-            return loading.load_on_ident(q, ident_key)
+            return q(session)._load_on_ident(
+                session.query(self.mapper), ident_key)
 
         if self.parent_property.order_by:
-            q = q.order_by(*util.to_list(self.parent_property.order_by))
+            q.add_criteria(
+                lambda q:
+                q.order_by(*util.to_list(self.parent_property.order_by)))
 
         for rev in self.parent_property._reverse_property:
             # reverse props that are MANYTOONE are loading *this*
@@ -611,28 +668,31 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             if rev.direction is interfaces.MANYTOONE and \
                 rev._use_get and \
                     not isinstance(rev.strategy, LazyLoader):
-                q = q.options(
-                    strategy_options.Load.for_existing_path(
-                        q._current_path[rev.parent]
-                    ).lazyload(rev.key)
+
+                q.add_criteria(
+                    lambda q:
+                    q.options(
+                        strategy_options.Load.for_existing_path(
+                            q._current_path[rev.parent]
+                        ).lazyload(rev.key)
+                    )
                 )
 
-        lazy_clause, params = self._generate_lazy_clause(
-            state, passive=passive)
+        lazy_clause, params = self._generate_lazy_clause(state, passive)
 
         if pending:
             if util.has_intersection(
                     orm_util._none_set, params.values()):
                 return None
+
         elif util.has_intersection(orm_util._never_set, params.values()):
             return None
 
         if self._raise_on_sql:
             self._invoke_raise_load(state, passive, "raise_on_sql")
 
-        q = q.filter(lazy_clause).params(params)
-
-        result = q.all()
+        q.add_criteria(lambda q: q.filter(lazy_clause))
+        result = q(session).params(**params).all()
         if self.uselist:
             return result
         else:
index 60ed2be3040ac32be3efb0ab363c977c6ff0eebf..e67159b0dcb107808b98a28adbc6da214939df2b 100644 (file)
@@ -8,7 +8,8 @@
 
 """
 
-from .interfaces import MapperOption, PropComparator
+from .interfaces import MapperOption, PropComparator, MapperProperty
+from .attributes import QueryableAttribute
 from .. import util
 from ..sql.base import _generative, Generative
 from .. import exc as sa_exc, inspect
@@ -82,8 +83,9 @@ class Load(Generative, MapperOption):
         self.path = insp._path_registry
         # note that this .context is shared among all descendant
         # Load objects
-        self.context = {}
+        self.context = util.OrderedDict()
         self.local_opts = {}
+        self._of_type = None
 
     @classmethod
     def for_existing_path(cls, path):
@@ -91,8 +93,57 @@ class Load(Generative, MapperOption):
         load.path = path
         load.context = {}
         load.local_opts = {}
+        load._of_type = None
         return load
 
+    def _generate_cache_key(self, path):
+        if path.path[0].is_aliased_class:
+            return False
+
+        serialized = []
+        for (key, loader_path), obj in self.context.items():
+            if key != "loader":
+                continue
+
+            endpoint = obj._of_type or obj.path.path[-1]
+            chopped = self._chop_path(loader_path, path)
+            if not chopped and not obj._of_type:
+                continue
+
+            serialized_path = []
+
+            for token in chopped:
+                if isinstance(token, util.string_types):
+                    serialized_path.append(token)
+                elif token.is_aliased_class:
+                    return False
+                elif token.is_property:
+                    serialized_path.append(token.key)
+                else:
+                    assert token.is_mapper
+                    serialized_path.append(token.class_)
+
+            if not serialized_path or endpoint != serialized_path[-1]:
+                if endpoint.is_mapper:
+                    serialized_path.append(endpoint.class_)
+                elif endpoint.is_aliased_class:
+                    return False
+
+            serialized.append(
+                (
+                    tuple(serialized_path) +
+                    obj.strategy +
+                    (tuple([
+                        (key, obj.local_opts[key])
+                        for key in sorted(obj.local_opts)
+                    ]) if obj.local_opts else ())
+                )
+            )
+        if not serialized:
+            return None
+        else:
+            return tuple(serialized)
+
     def _generate(self):
         cloned = super(Load, self)._generate()
         cloned.local_opts = {}
@@ -119,6 +170,7 @@ class Load(Generative, MapperOption):
             query._attributes.update(self.context)
 
     def _generate_path(self, path, attr, wildcard_key, raiseerr=True):
+        self._of_type = None
         if raiseerr and not path.has_entity:
             if isinstance(path, TokenRegistry):
                 raise sa_exc.ArgumentError(
@@ -137,7 +189,9 @@ class Load(Generative, MapperOption):
                     self.propagate_to_loaders = False
                 if wildcard_key:
                     attr = "%s:%s" % (wildcard_key, attr)
-                return path.token(attr)
+                path = path.token(attr)
+                self.path = path
+                return path
 
             try:
                 # use getattr on the class to work around
@@ -171,7 +225,6 @@ class Load(Generative, MapperOption):
                 ac = attr._of_type
                 ext_info = inspect(ac)
 
-                path_element = ext_info.mapper
                 existing = path.entity_path[prop].get(
                     self.context, "path_with_polymorphic")
                 if not ext_info.is_aliased_class:
@@ -182,12 +235,25 @@ class Load(Generative, MapperOption):
                         _existing_alias=existing)
                 path.entity_path[prop].set(
                     self.context, "path_with_polymorphic", inspect(ac))
-                path = path[prop][path_element]
+
+                # the path here will go into the context dictionary and
+                # needs to match up to how the class graph is traversed.
+                # so we can't put an AliasedInsp in the path here, needs
+                # to be the base mapper.
+                path = path[prop][ext_info.mapper]
+
+                # but, we need to know what the original of_type()
+                # argument is for cache key purposes.  so....store that too.
+                # it might be better for "path" to really represent,
+                # "the path", but trying to keep the impact of the cache
+                # key feature localized for now
+                self._of_type = ext_info
             else:
                 path = path[prop]
 
         if path.has_entity:
             path = path.entity_path
+        self.path = path
         return path
 
     def __str__(self):
@@ -205,7 +271,7 @@ class Load(Generative, MapperOption):
 
         self.propagate_to_loaders = propagate_to_loaders
         # if the path is a wildcard, this will set propagate_to_loaders=False
-        self.path = self._generate_path(self.path, attr, "relationship")
+        self._generate_path(self.path, attr, "relationship")
         self.strategy = strategy
         if strategy is not None:
             self._set_path_strategy()
@@ -215,10 +281,9 @@ class Load(Generative, MapperOption):
         strategy = self._coerce_strat(strategy)
 
         for attr in attrs:
-            path = self._generate_path(self.path, attr, "column")
             cloned = self._generate()
             cloned.strategy = strategy
-            cloned.path = path
+            cloned._generate_path(self.path, attr, "column")
             cloned.propagate_to_loaders = True
             if opts:
                 cloned.local_opts.update(opts)
@@ -285,7 +350,7 @@ class _UnboundLoad(Load):
     """Represent a loader option that isn't tied to a root entity.
 
     The loader option will produce an entity-linked :class:`.Load`
-    object when it is passed :meth:`.Query.options`.
+    object when it is passed :metfh:`.Query.options`.
 
     This provides compatibility with the traditional system
     of freestanding options, e.g. ``joinedload('x.y.z')``.
@@ -294,13 +359,30 @@ class _UnboundLoad(Load):
 
     def __init__(self):
         self.path = ()
-        self._to_bind = set()
+        self._to_bind = []
         self.local_opts = {}
 
     _is_chain_link = False
 
+    def _generate_cache_key(self, path):
+        serialized = ()
+        for val in self._to_bind:
+            opt = val._bind_loader(
+                [path.path[0]],
+                None, None, False)
+            if opt:
+                c_key = opt._generate_cache_key(path)
+                if c_key is False:
+                    return False
+                elif c_key:
+                    serialized += c_key
+        if not serialized:
+            return None
+        else:
+            return serialized
+
     def _set_path_strategy(self):
-        self._to_bind.add(self)
+        self._to_bind.append(self)
 
     def _generate_path(self, path, attr, wildcard_key):
         if wildcard_key and isinstance(attr, util.string_types) and \
@@ -308,25 +390,28 @@ class _UnboundLoad(Load):
             if attr == _DEFAULT_TOKEN:
                 self.propagate_to_loaders = False
             attr = "%s:%s" % (wildcard_key, attr)
-
-        return path + (attr, )
+        path = path + (attr, )
+        self.path = path
+        return path
 
     def __getstate__(self):
         d = self.__dict__.copy()
-        d['path'] = ret = []
-        for token in util.to_list(self.path):
-            if isinstance(token, PropComparator):
-                ret.append((token._parentmapper.class_, token.key))
-            else:
-                ret.append(token)
+        d['path'] = self._serialize_path(self.path)
         return d
 
     def __setstate__(self, state):
         ret = []
         for key in state['path']:
             if isinstance(key, tuple):
-                cls, propkey = key
-                ret.append(getattr(cls, propkey))
+                if len(key) == 2:
+                    # support legacy
+                    cls, propkey = key
+                else:
+                    cls, propkey, of_type = key
+                prop = getattr(cls, propkey)
+                if of_type:
+                    prop = prop.of_type(prop)
+                ret.append(prop)
             else:
                 ret.append(key)
         state['path'] = tuple(ret)
@@ -334,7 +419,9 @@ class _UnboundLoad(Load):
 
     def _process(self, query, raiseerr):
         for val in self._to_bind:
-            val._bind_loader(query, query._attributes, raiseerr)
+            val._bind_loader(
+                [ent.entity_zero for ent in query._mapper_entities],
+                query._current_path, query._attributes, raiseerr)
 
     @classmethod
     def _from_keys(cls, meth, keys, chained, kw):
@@ -384,26 +471,80 @@ class _UnboundLoad(Load):
 
         return to_chop[i:]
 
-    def _bind_loader(self, query, context, raiseerr):
+    def _serialize_path(self, path, reject_aliased_class=False):
+        ret = []
+        for token in path:
+            if isinstance(token, QueryableAttribute):
+                if reject_aliased_class and (
+                    (token._of_type and
+                        inspect(token._of_type).is_aliased_class)
+                    or
+                    inspect(token.parent).is_aliased_class
+                ):
+                    return False
+                ret.append(
+                    (token._parentmapper.class_, token.key, token._of_type))
+            elif isinstance(token, PropComparator):
+                ret.append((token._parentmapper.class_, token.key, None))
+            else:
+                ret.append(token)
+        return ret
+
+    def _bind_loader(self, entities, current_path, context, raiseerr):
+        """Convert from an _UnboundLoad() object into a Load() object.
+
+        The _UnboundLoad() uses an informal "path" and does not necessarily
+        refer to a lead entity as it may use string tokens.   The Load()
+        OTOH refers to a complete path.   This method reconciles from a
+        given Query into a Load.
+
+        Example::
+
+
+            query = session.query(User).options(
+                joinedload("orders").joinedload("items"))
+
+        The above options will be an _UnboundLoad object along the lines
+        of (note this is not the exact API of _UnboundLoad)::
+
+            _UnboundLoad(
+                _to_bind=[
+                    _UnboundLoad(["orders"], {"lazy": "joined"}),
+                    _UnboundLoad(["orders", "items"], {"lazy": "joined"}),
+                ]
+            )
+
+        After this method, we get something more like this (again this is
+        not exact API)::
+
+            Load(
+                User,
+                (User, User.orders.property))
+            Load(
+                User,
+                (User, User.orders.property, Order, Order.items.property))
+
+        """
         start_path = self.path
         # _current_path implies we're in a
         # secondary load with an existing path
 
-        current_path = query._current_path
         if current_path:
             start_path = self._chop_path(start_path, current_path)
 
         if not start_path:
             return None
 
+        # look at the first token and try to locate within the Query
+        # what entity we are referring towards.
         token = start_path[0]
 
         if isinstance(token, util.string_types):
-            entity = self._find_entity_basestring(query, token, raiseerr)
+            entity = self._find_entity_basestring(entities, token, raiseerr)
         elif isinstance(token, PropComparator):
             prop = token.property
             entity = self._find_entity_prop_comparator(
-                query,
+                entities,
                 prop.key,
                 token._parententity,
                 raiseerr)
@@ -416,20 +557,26 @@ class _UnboundLoad(Load):
         if not entity:
             return
 
-        path_element = entity.entity_zero
+        path_element = entity
 
         # transfer our entity-less state into a Load() object
-        # with a real entity path.
+        # with a real entity path.  Start with the lead entity
+        # we just located, then go through the rest of our path
+        # tokens and populate into the Load().
         loader = Load(path_element)
-        loader.context = context
+
+        if context is not None:
+            loader.context = context
+        else:
+            context = loader.context
+
         loader.strategy = self.strategy
         loader.is_opts_only = self.is_opts_only
 
         path = loader.path
         for token in start_path:
-            loader.path = path = loader._generate_path(
-                loader.path, token, None, raiseerr)
-            if path is None:
+            if not loader._generate_path(
+                    loader.path, token, None, raiseerr):
                 return
 
         loader.local_opts.update(self.local_opts)
@@ -455,17 +602,19 @@ class _UnboundLoad(Load):
                 replace=not self._is_chain_link,
                 merge_opts=self.is_opts_only)
 
-    def _find_entity_prop_comparator(self, query, token, mapper, raiseerr):
+        return loader
+
+    def _find_entity_prop_comparator(self, entities, token, mapper, raiseerr):
         if _is_aliased_class(mapper):
             searchfor = mapper
         else:
             searchfor = _class_to_mapper(mapper)
-        for ent in query._mapper_entities:
-            if ent.corresponds_to(searchfor):
+        for ent in entities:
+            if orm_util._entity_corresponds_to(ent, searchfor):
                 return ent
         else:
             if raiseerr:
-                if not list(query._mapper_entities):
+                if not list(entities):
                     raise sa_exc.ArgumentError(
                         "Query has only expression-based entities - "
                         "can't find property named '%s'."
@@ -477,14 +626,14 @@ class _UnboundLoad(Load):
                         "specified in this Query.  Note the full path "
                         "from root (%s) to target entity must be specified."
                         % (token, ",".join(str(x) for
-                                           x in query._mapper_entities))
+                                           x in entities))
                     )
             else:
                 return None
 
-    def _find_entity_basestring(self, query, token, raiseerr):
+    def _find_entity_basestring(self, entities, token, raiseerr):
         if token.endswith(':' + _WILDCARD_TOKEN):
-            if len(list(query._mapper_entities)) != 1:
+            if len(list(entities)) != 1:
                 if raiseerr:
                     raise sa_exc.ArgumentError(
                         "Wildcard loader can only be used with exactly "
@@ -493,7 +642,7 @@ class _UnboundLoad(Load):
         elif token.endswith(_DEFAULT_TOKEN):
             raiseerr = False
 
-        for ent in query._mapper_entities:
+        for ent in entities:
             # return only the first _MapperEntity when searching
             # based on string prop name.   Ideally object
             # attributes are used to specify more exactly.
index 73b0be99ce7ab65ee7b20995cc18020efe84cdd1..2e61661edaaac1ca8551f2885825bd92b2d73245 100644 (file)
@@ -1039,6 +1039,19 @@ def was_deleted(object):
     state = attributes.instance_state(object)
     return state.was_deleted
 
+def _entity_corresponds_to(given, entity):
+    if entity.is_aliased_class:
+        if given.is_aliased_class:
+            if entity._base_alias is given._base_alias:
+                return True
+        return False
+    elif given.is_aliased_class:
+        if given._use_mapper_path:
+            return entity in given.with_polymorphic_mappers
+        else:
+            return entity is given
+
+    return entity.common_parent(given)
 
 def randomize_unitofwork():
     """Use random-ordering sets within the unit of work in order
index d94af5f62a44294af02f15b0cb9f8fcdf40d3cac..32d9c6190aae24d667a12fac6b8495e9f250c4a7 100644 (file)
@@ -925,7 +925,7 @@ class LRUCache(dict):
             while len(self) > self.capacity + self.capacity * self.threshold:
                 if size_alert:
                     size_alert = False
-                    self.size_alert()
+                    self.size_alert(self)
                 by_counter = sorted(dict.values(self),
                                     key=operator.itemgetter(2),
                                     reverse=True)
index e038d5c387f223f51dd68d29228f0c74cb4480ef..4a378d77dddc45d7d5eadbdd6ce26a91c928a533 100644 (file)
@@ -263,7 +263,8 @@ class MemUsageWBackendTest(EnsureZeroed):
             del sessmaker
         go()
 
-    @testing.emits_warning("Compiled statement cache for.*")
+    @testing.emits_warning("Compiled statement cache for mapper.*")
+    @testing.emits_warning("Compiled statement cache for lazy loader.*")
     @testing.crashes('sqlite', ':memory: connection not suitable here')
     def test_orm_many_engines(self):
         metadata = MetaData(self.engine)
index a5eb31495056b7fd8725ac7c40839153d780ab70..263a1bb6c367f3f1527ab5cf6d052f59e84215cd 100644 (file)
@@ -1,10 +1,12 @@
 from sqlalchemy.orm import Session, subqueryload, \
-    mapper, relationship, lazyload, clear_mappers, backref
+    mapper, relationship, lazyload, clear_mappers, backref, aliased, \
+    Load, defaultload
 from sqlalchemy.testing import eq_, is_, is_not_
 from sqlalchemy.testing import assert_raises, assert_raises_message
 from sqlalchemy import testing
+from sqlalchemy import inspect
 from test.orm import _fixtures
-from sqlalchemy.ext.baked import BakedQuery, baked_lazyload, BakedLazyLoader
+from sqlalchemy.ext.baked import BakedQuery
 from sqlalchemy.ext import baked
 from sqlalchemy import bindparam, func, literal_column
 from sqlalchemy.orm import exc as orm_exc
@@ -719,6 +721,22 @@ class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest):
         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 _m2o_fixture(self):
         User = self.classes.User
         Address = self.classes.Address
@@ -729,124 +747,81 @@ class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest):
         })
         return User, Address
 
-    def test_strategy_lookup(self):
-        """test that the lazy loader strategies aren't getting mixed up
-        with BakedLazyLoader as a subclass.
-
-        """
-        User, Address = self._o2m_fixture()
-
-        ll = User.addresses.property._get_strategy((('lazy', 'select'),))
-        assert not isinstance(ll, BakedLazyLoader)
-        eq_(ll._strategy_keys, [(('lazy', 'select'),), (('lazy', True),)])
-
-        ll = User.addresses.property._get_strategy((('lazy', True),))
-        assert not isinstance(ll, BakedLazyLoader)
-        eq_(ll._strategy_keys, [(('lazy', 'select'),), (('lazy', True),)])
-
-        bl = User.addresses.property._get_strategy((('lazy', 'baked_select'),))
-        assert isinstance(bl, BakedLazyLoader)
-        eq_(bl._strategy_keys, [(('lazy', 'baked_select'),)])
-
-    def test_invocation_per_state(self):
-        """test that BakedLazyLoader is getting invoked with the
-        baked_lazyload() loader.
-
-        """
-        User, Address = self._o2m_fixture()
-
-        sess = Session()
-        q = sess.query(User)
-
-        with mock.patch.object(BakedLazyLoader, "_emit_lazyload") as el:
-            u1 = q.first()
-            u1.addresses
-            # not invoked
-            eq_(el.mock_calls, [])
-
-        sess = Session()
-        q = sess.query(User).options(baked_lazyload(User.addresses))
-        with mock.patch.object(BakedLazyLoader, "_emit_lazyload") as el:
-            u1 = q.first()
-            u1.addresses
-            # invoked
-            is_(
-                el.mock_calls[0][1][1],
-                u1._sa_instance_state
-            )
-
-    def test_invocation_per_mapper(self):
-        """test that BakedLazyLoader is getting invoked with the
-        "baked_select" lazy setting.
-
-        """
-        User, Address = self._o2m_fixture(lazy="baked_select")
+    def test_unsafe_unbound_option_cancels_bake(self):
+        User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined")
 
-        sess = Session()
-        q = sess.query(User).options(lazyload(User.addresses))
-
-        with mock.patch.object(BakedLazyLoader, "_emit_lazyload") as el:
-            u1 = q.first()
-            u1.addresses
-            # not invoked
-            eq_(el.mock_calls, [])
-
-        sess = Session()
-        q = sess.query(User)
-        with mock.patch.object(BakedLazyLoader, "_emit_lazyload") as el:
-            u1 = q.first()
-            u1.addresses
-            # invoked
-            is_(
-                el.mock_calls[0][1][1],
-                u1._sa_instance_state
-            )
-
-    def test_systemwide_loaders_loadable_via_lazyloader(self):
-        from sqlalchemy.orm import configure_mappers
-
-        baked.bake_lazy_loaders()
-        try:
-            User, Address = self._o2m_fixture(lazy='joined')
-
-            configure_mappers()
-
-            is_(
-                User.addresses.property.
-                _get_strategy((('lazy', 'select'), )).__class__,
-                BakedLazyLoader
-            )
-        finally:
-            baked.unbake_lazy_loaders()
-
-    def test_invocation_systemwide_loaders(self):
-        baked.bake_lazy_loaders()
-        try:
-            User, Address = self._o2m_fixture()
+        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()
-            q = sess.query(User).options(lazyload(User.addresses))
-            with mock.patch.object(BakedLazyLoader, "_emit_lazyload") as el:
-                u1 = q.first()
-                u1.addresses
-                # invoked
-                is_(
-                    el.mock_calls[0][1][1],
-                    u1._sa_instance_state
-                )
-        finally:
-            baked.unbake_lazy_loaders()
-
-        clear_mappers()
-        User, Address = self._o2m_fixture()
-        sess = Session()
-        q = sess.query(User).options(lazyload(User.addresses))
+            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, 1)
+
+    def test_unsafe_bound_option_cancels_bake(self):
+        User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined")
+
+        class SubDingaling(Dingaling):
+            pass
+        mapper(SubDingaling, None, inherits=Dingaling)
 
-        with mock.patch.object(BakedLazyLoader, "_emit_lazyload") as el:
-            u1 = q.first()
-            u1.addresses
-            # not invoked
-            eq_(el.mock_calls, [])
+        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, 1)
+
+    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)
@@ -855,37 +830,33 @@ class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest):
         self._test_baked_lazy_loading_relationship_flag(False)
 
     def _test_baked_lazy_loading_relationship_flag(self, flag):
-        baked.bake_lazy_loaders()
-        try:
-            User, Address = self._o2m_fixture(bake_queries=flag)
+        User, Address = self._o2m_fixture(bake_queries=flag)
 
-            sess = Session()
-            u1 = sess.query(User).first()
+        sess = Session()
+        u1 = sess.query(User).first()
 
-            from sqlalchemy.orm import Query
+        from sqlalchemy.orm import Query
 
-            canary = mock.Mock()
+        canary = mock.Mock()
 
-            # I would think Mock can do this but apparently
-            # it cannot (wrap / autospec don't work together)
-            real_compile_context = Query._compile_context
+        # I would think Mock can do this but apparently
+        # it cannot (wrap / autospec don't work together)
+        real_compile_context = Query._compile_context
 
-            def _my_compile_context(*arg, **kw):
-                if arg[0].column_descriptions[0]['entity'] is Address:
-                    canary()
-                return real_compile_context(*arg, **kw)
+        def _my_compile_context(*arg, **kw):
+            if arg[0].column_descriptions[0]['entity'] is Address:
+                canary()
+            return real_compile_context(*arg, **kw)
 
-            with mock.patch.object(
-                Query,
-                "_compile_context",
-                _my_compile_context
-            ):
-                u1.addresses
+        with mock.patch.object(
+            Query,
+            "_compile_context",
+            _my_compile_context
+        ):
+            u1.addresses
 
-                sess.expire(u1)
-                u1.addresses
-        finally:
-            baked.unbake_lazy_loaders()
+            sess.expire(u1)
+            u1.addresses
 
         if flag:
             eq_(canary.call_count, 1)
@@ -907,7 +878,7 @@ class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest):
             lambda s: s.query(User))
 
         if set_option:
-            base_bq += lambda q: q.options(baked_lazyload(User.addresses))
+            base_bq += lambda q: q.options(lazyload(User.addresses))
 
         base_bq += lambda q: q.order_by(User.id)
 
@@ -965,7 +936,7 @@ class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest):
         base_bq = self.bakery(
             lambda s: s.query(Address))
 
-        base_bq += lambda q: q.options(baked_lazyload(Address.user))
+        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
index b7c574e2a5ae8f7dbc19d9055e6ae00a2d5e3d2e..e29a63e08d6fbf4fe2ef43c46a15576a41840226 100644 (file)
@@ -2,7 +2,7 @@ from sqlalchemy import inspect
 from sqlalchemy.orm import attributes, mapper, relationship, backref, \
     configure_mappers, create_session, synonym, Session, class_mapper, \
     aliased, column_property, joinedload_all, joinedload, Query,\
-    util as orm_util, Load, defer
+    util as orm_util, Load, defer, defaultload
 from sqlalchemy.orm.query import QueryContext
 from sqlalchemy.orm import strategy_options
 import sqlalchemy as sa
@@ -20,6 +20,11 @@ class QueryTest(_fixtures.FixtureTest):
     def setup_mappers(cls):
         cls._setup_stock_mapping()
 
+        class SubItem(cls.classes.Item):
+            pass
+
+        mapper(SubItem, None, inherits=cls.classes.Item)
+
 
 class PathTest(object):
     def _make_path(self, path):
@@ -42,7 +47,9 @@ class PathTest(object):
         attr = {}
 
         for val in opt._to_bind:
-            val._bind_loader(q, attr, False)
+            val._bind_loader(
+                [ent.entity_zero for ent in q._mapper_entities],
+                q._current_path, attr, False)
 
         assert_paths = [k[1] for k in attr]
         eq_(
@@ -868,7 +875,9 @@ class LocalOptsTest(PathTest, QueryTest):
         for opt in opts:
             if isinstance(opt, strategy_options._UnboundLoad):
                 for tb in opt._to_bind:
-                    tb._bind_loader(query, attr, False)
+                    tb._bind_loader(
+                        [ent.entity_zero for ent in query._mapper_entities],
+                        query._current_path, attr, False)
             else:
                 attr.update(opt.context)
 
@@ -951,3 +960,387 @@ class LocalOptsTest(PathTest, QueryTest):
             ),
         ]
         self._assert_attrs(opts, {"foo": "bar", "bat": "hoho"})
+
+
+class CacheKeyTest(PathTest, QueryTest):
+
+    run_create_tables = False
+    run_inserts = None
+    run_deletes = None
+
+    def test_unbound_cache_key_included_safe(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "orders"])
+
+        opt = joinedload(User.orders).joinedload(Order.items)
+        eq_(
+            opt._generate_cache_key(query_path),
+            (
+                ((Order, 'items', Item, ('lazy', 'joined')),)
+            )
+        )
+
+    def test_bound_cache_key_included_safe(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "orders"])
+
+        opt = Load(User).joinedload(User.orders).joinedload(Order.items)
+        eq_(
+            opt._generate_cache_key(query_path),
+            (
+                ((Order, 'items', Item, ('lazy', 'joined')),)
+            )
+        )
+
+    def test_unbound_cache_key_excluded_on_other(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry(
+            [User, "addresses"])
+
+        opt = joinedload(User.orders).joinedload(Order.items)
+        eq_(
+            opt._generate_cache_key(query_path),
+            None
+        )
+
+    def test_bound_cache_key_excluded_on_other(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry(
+            [User, "addresses"])
+
+        opt = Load(User).joinedload(User.orders).joinedload(Order.items)
+        eq_(
+            opt._generate_cache_key(query_path),
+            None
+        )
+
+    def test_unbound_cache_key_excluded_on_aliased(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        # query of:
+        #
+        # query(User).options(
+        #       joinedload(aliased(User).orders).joinedload(Order.items))
+        #
+        # we are lazy loading Order objects from User.orders
+        # the path excludes our option so cache key should
+        # be None
+
+        query_path = self._make_path_registry(
+            [User, "orders"])
+
+        opt = joinedload(aliased(User).orders).joinedload(Order.items)
+        eq_(
+            opt._generate_cache_key(query_path),
+            None
+        )
+
+    def test_unbound_cache_key_included_of_type_safe(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "orders"])
+
+        opt = joinedload(User.orders).joinedload(Order.items.of_type(SubItem))
+        eq_(
+            opt._generate_cache_key(query_path),
+            (
+                (Order, 'items', SubItem, ('lazy', 'joined')),
+            )
+        )
+
+    def test_bound_cache_key_included_of_type_safe(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "orders"])
+
+        opt = Load(User).joinedload(User.orders).\
+            joinedload(Order.items.of_type(SubItem))
+
+        eq_(
+            opt._generate_cache_key(query_path),
+            (
+                (Order, 'items', SubItem, ('lazy', 'joined')),
+            )
+        )
+
+    def test_unbound_cache_key_included_unsafe_option_one(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "orders"])
+
+        opt = joinedload(User.orders).\
+            joinedload(Order.items.of_type(aliased(SubItem)))
+        eq_(
+            opt._generate_cache_key(query_path),
+            False
+        )
+
+    def test_unbound_cache_key_included_unsafe_option_two(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "orders", Order])
+
+        opt = joinedload(User.orders).\
+            joinedload(Order.items.of_type(aliased(SubItem)))
+        eq_(
+            opt._generate_cache_key(query_path),
+            False
+        )
+
+    def test_unbound_cache_key_included_unsafe_option_three(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "orders", Order, "items"])
+
+        opt = joinedload(User.orders).\
+            joinedload(Order.items.of_type(aliased(SubItem)))
+        eq_(
+            opt._generate_cache_key(query_path),
+            False
+        )
+
+    def test_unbound_cache_key_included_unsafe_query(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        au = aliased(User)
+        query_path = self._make_path_registry([inspect(au), "orders"])
+
+        opt = joinedload(au.orders).\
+            joinedload(Order.items)
+        eq_(
+            opt._generate_cache_key(query_path),
+            False
+        )
+
+    def test_unbound_cache_key_included_safe_w_deferred(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "addresses"])
+
+        opt = joinedload(User.addresses).\
+            defer(Address.email_address).defer(Address.user_id)
+        eq_(
+            opt._generate_cache_key(query_path),
+            (
+                (
+                    Address, "email_address",
+                    ('deferred', True),
+                    ('instrument', True)
+                ),
+                (
+                    Address, "user_id",
+                    ('deferred', True),
+                    ('instrument', True)
+                ),
+            )
+        )
+
+    def test_bound_cache_key_included_safe_w_deferred(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "addresses"])
+
+        opt = Load(User).joinedload(User.addresses).\
+            defer(Address.email_address).defer(Address.user_id)
+        eq_(
+            opt._generate_cache_key(query_path),
+            (
+                (
+                    Address, "email_address",
+                    ('deferred', True),
+                    ('instrument', True)
+                ),
+                (
+                    Address, "user_id",
+                    ('deferred', True),
+                    ('instrument', True)
+                ),
+            )
+        )
+
+    def test_unbound_cache_key_included_safe_w_option(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        opt = defaultload("orders").joinedload(
+            "items", innerjoin=True).defer("description")
+        query_path = self._make_path_registry([User, "orders"])
+
+        eq_(
+            opt._generate_cache_key(query_path),
+            (
+                (Order, 'items', Item,
+                 ('lazy', 'joined'), ('innerjoin', True)),
+                (Order, 'items', Item, 'description',
+                 ('deferred', True), ('instrument', True))
+            )
+        )
+
+    def test_bound_cache_key_excluded_on_aliased(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry(
+            [User, "orders"])
+
+        au = aliased(User)
+        opt = Load(au).joinedload(au.orders).joinedload(Order.items)
+        eq_(
+            opt._generate_cache_key(query_path),
+            None
+        )
+
+    def test_bound_cache_key_included_unsafe_option_one(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "orders"])
+
+        opt = Load(User).joinedload(User.orders).\
+            joinedload(Order.items.of_type(aliased(SubItem)))
+        eq_(
+            opt._generate_cache_key(query_path),
+            False
+        )
+
+    def test_bound_cache_key_included_unsafe_option_two(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "orders", Order])
+
+        opt = Load(User).joinedload(User.orders).\
+            joinedload(Order.items.of_type(aliased(SubItem)))
+        eq_(
+            opt._generate_cache_key(query_path),
+            False
+        )
+
+    def test_bound_cache_key_included_unsafe_option_three(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "orders", Order, "items"])
+
+        opt = Load(User).joinedload(User.orders).\
+            joinedload(Order.items.of_type(aliased(SubItem)))
+        eq_(
+            opt._generate_cache_key(query_path),
+            False
+        )
+
+    def test_bound_cache_key_included_unsafe_query(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        au = aliased(User)
+        query_path = self._make_path_registry([inspect(au), "orders"])
+
+        opt = Load(au).joinedload(au.orders).\
+            joinedload(Order.items)
+        eq_(
+            opt._generate_cache_key(query_path),
+            False
+        )
+
+
+    def test_bound_cache_key_included_safe_w_option(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        opt = Load(User).defaultload("orders").joinedload(
+            "items", innerjoin=True).defer("description")
+        query_path = self._make_path_registry([User, "orders"])
+
+        eq_(
+            opt._generate_cache_key(query_path),
+            (
+                (Order, 'items', Item,
+                 ('lazy', 'joined'), ('innerjoin', True)),
+                (Order, 'items', Item, 'description',
+                 ('deferred', True), ('instrument', True))
+            )
+        )
+
+    def test_unbound_cache_key_included_safe_w_loadonly_strs(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "addresses"])
+
+        opt = defaultload(User.addresses).load_only("id", "email_address")
+        eq_(
+            opt._generate_cache_key(query_path),
+
+            (
+                (Address, 'id',
+                 ('deferred', False), ('instrument', True)),
+                (Address, 'email_address',
+                 ('deferred', False), ('instrument', True)),
+                (Address, 'column:*',
+                 ('deferred', True), ('instrument', True),
+                 ('undefer_pks', True))
+            )
+        )
+
+    def test_unbound_cache_key_included_safe_w_loadonly_props(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "addresses"])
+
+        opt = defaultload(User.addresses).load_only(
+            Address.id, Address.email_address)
+        eq_(
+            opt._generate_cache_key(query_path),
+
+            (
+                (Address, 'id',
+                 ('deferred', False), ('instrument', True)),
+                (Address, 'email_address',
+                 ('deferred', False), ('instrument', True)),
+                (Address, 'column:*',
+                 ('deferred', True), ('instrument', True),
+                 ('undefer_pks', True))
+            )
+        )
+
+    def test_bound_cache_key_included_safe_w_loadonly(self):
+        User, Address, Order, Item, SubItem = self.classes(
+            'User', 'Address', 'Order', 'Item', 'SubItem')
+
+        query_path = self._make_path_registry([User, "addresses"])
+
+        opt = Load(User).defaultload(User.addresses).\
+            load_only("id", "email_address")
+        eq_(
+            opt._generate_cache_key(query_path),
+
+            (
+                (Address, 'id',
+                 ('deferred', False), ('instrument', True)),
+                (Address, 'email_address',
+                 ('deferred', False), ('instrument', True)),
+                (Address, 'column:*',
+                 ('deferred', True), ('instrument', True),
+                 ('undefer_pks', True))
+            )
+        )
+
index 720c26b80deaef32f7dccd9fe408d02337b1f344..8af5f9b1b461a9bbfb204863aba8317f8e1a631c 100644 (file)
@@ -211,60 +211,60 @@ test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.5_sqlite_pysqlite_d
 
 # TEST: test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols
 
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_mysql_mysqldb_dbapiunicode_cextensions 23231
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_mysql_mysqldb_dbapiunicode_nocextensions 26236
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_postgresql_psycopg2_dbapiunicode_cextensions 23198
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 26203
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_cextensions 23175
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 26180
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_mysql_mysqldb_dbapiunicode_cextensions 23237
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_mysql_mysqldb_dbapiunicode_nocextensions 26242
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_postgresql_psycopg2_dbapiunicode_cextensions 23204
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 26209
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_cextensions 23181
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 26186
 test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.4_mysql_mysqldb_dbapiunicode_cextensions 24260
 test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.4_mysql_mysqldb_dbapiunicode_nocextensions 27267
 test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.4_postgresql_psycopg2_dbapiunicode_cextensions 24225
 test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.4_postgresql_psycopg2_dbapiunicode_nocextensions 27232
 test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.4_sqlite_pysqlite_dbapiunicode_cextensions 24211
 test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.4_sqlite_pysqlite_dbapiunicode_nocextensions 27218
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_mysql_mysqldb_dbapiunicode_cextensions 24253
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_mysql_mysqldb_dbapiunicode_nocextensions 27260
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_postgresql_psycopg2_dbapiunicode_cextensions 24222
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 27229
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_sqlite_pysqlite_dbapiunicode_cextensions 24204
-test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 27211
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_mysql_mysqldb_dbapiunicode_cextensions 24259
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_mysql_mysqldb_dbapiunicode_nocextensions 27266
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_postgresql_psycopg2_dbapiunicode_cextensions 24228
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 27235
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_sqlite_pysqlite_dbapiunicode_cextensions 24210
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 27217
 
 # TEST: test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query
 
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_mysql_mysqldb_dbapiunicode_cextensions 406489
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_mysql_mysqldb_dbapiunicode_nocextensions 406475
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_postgresql_psycopg2_dbapiunicode_cextensions 406489
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 406468
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_cextensions 406482
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 406482
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_mysql_mysqldb_dbapiunicode_cextensions 413452
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_mysql_mysqldb_dbapiunicode_nocextensions 413431
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_postgresql_psycopg2_dbapiunicode_cextensions 413445
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 413438
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_cextensions 413438
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 413438
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.4_mysql_mysqldb_dbapiunicode_cextensions 532398
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.4_mysql_mysqldb_dbapiunicode_nocextensions 532398
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.4_postgresql_psycopg2_dbapiunicode_cextensions 532398
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.4_postgresql_psycopg2_dbapiunicode_nocextensions 532398
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.4_sqlite_pysqlite_dbapiunicode_cextensions 532398
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.4_sqlite_pysqlite_dbapiunicode_nocextensions 532398
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_mysql_mysqldb_dbapiunicode_cextensions 434497
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_mysql_mysqldb_dbapiunicode_nocextensions 434504
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_postgresql_psycopg2_dbapiunicode_cextensions 434504
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 434490
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_sqlite_pysqlite_dbapiunicode_cextensions 434497
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 434504
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_mysql_mysqldb_dbapiunicode_cextensions 440781
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_mysql_mysqldb_dbapiunicode_nocextensions 440774
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_postgresql_psycopg2_dbapiunicode_cextensions 440774
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 440781
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_sqlite_pysqlite_dbapiunicode_cextensions 440788
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 440781
 
 # TEST: test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results
 
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_mysql_mysqldb_dbapiunicode_cextensions 567666
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_mysql_mysqldb_dbapiunicode_nocextensions 580466
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_postgresql_psycopg2_dbapiunicode_cextensions 578324
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 591124
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_sqlite_pysqlite_dbapiunicode_cextensions 557938
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 570738
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_mysql_mysqldb_dbapiunicode_cextensions 570849
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_mysql_mysqldb_dbapiunicode_nocextensions 583849
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_postgresql_psycopg2_dbapiunicode_cextensions 598107
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 611107
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_sqlite_pysqlite_dbapiunicode_cextensions 562621
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 575621
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_mysql_mysqldb_dbapiunicode_cextensions 567786
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_mysql_mysqldb_dbapiunicode_nocextensions 580586
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_postgresql_psycopg2_dbapiunicode_cextensions 578444
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 591244
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_sqlite_pysqlite_dbapiunicode_cextensions 558058
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 570760
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_mysql_mysqldb_dbapiunicode_cextensions 570897
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_mysql_mysqldb_dbapiunicode_nocextensions 583897
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_postgresql_psycopg2_dbapiunicode_cextensions 598155
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 611155
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_sqlite_pysqlite_dbapiunicode_cextensions 562571
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 575669
 
 # TEST: test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity
 
@@ -289,66 +289,66 @@ 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_mysql_mysqldb_dbapiunicode_cextensions 137052
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_dbapiunicode_nocextensions 139302
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_dbapiunicode_cextensions 127552
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 129802
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_cextensions 125302
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 127552
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_dbapiunicode_cextensions 94694
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_dbapiunicode_nocextensions 96493
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_dbapiunicode_cextensions 85692
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 87444
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_cextensions 83193
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 84945
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.4_mysql_mysqldb_dbapiunicode_cextensions 143106
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.4_mysql_mysqldb_dbapiunicode_nocextensions 145856
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.4_postgresql_psycopg2_dbapiunicode_cextensions 133056
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.4_postgresql_psycopg2_dbapiunicode_nocextensions 135806
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.4_sqlite_pysqlite_dbapiunicode_cextensions 131306
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.4_sqlite_pysqlite_dbapiunicode_nocextensions 134056
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_mysql_mysqldb_dbapiunicode_cextensions 143054
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_mysql_mysqldb_dbapiunicode_nocextensions 145854
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_postgresql_psycopg2_dbapiunicode_cextensions 134604
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 137304
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_sqlite_pysqlite_dbapiunicode_cextensions 131304
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 134054
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_mysql_mysqldb_dbapiunicode_cextensions 97011
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_mysql_mysqldb_dbapiunicode_nocextensions 98765
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_postgresql_psycopg2_dbapiunicode_cextensions 89009
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 90763
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_sqlite_pysqlite_dbapiunicode_cextensions 85510
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 87264
 
 # 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_mysql_mysqldb_dbapiunicode_cextensions 19549
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_dbapiunicode_nocextensions 19797
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_dbapiunicode_cextensions 19028
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 19264
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_cextensions 18904
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 19122
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_dbapiunicode_cextensions 18601
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_dbapiunicode_nocextensions 18872
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_dbapiunicode_cextensions 18080
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 18257
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_cextensions 17950
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 18126
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.4_mysql_mysqldb_dbapiunicode_cextensions 20210
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.4_mysql_mysqldb_dbapiunicode_nocextensions 20428
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.4_postgresql_psycopg2_dbapiunicode_cextensions 19528
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.4_postgresql_psycopg2_dbapiunicode_nocextensions 19834
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.4_sqlite_pysqlite_dbapiunicode_cextensions 19494
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.4_sqlite_pysqlite_dbapiunicode_nocextensions 19700
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_mysql_mysqldb_dbapiunicode_cextensions 20152
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_mysql_mysqldb_dbapiunicode_nocextensions 20465
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_postgresql_psycopg2_dbapiunicode_cextensions 19641
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 19921
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_sqlite_pysqlite_dbapiunicode_cextensions 19412
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 19687
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_mysql_mysqldb_dbapiunicode_cextensions 19155
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_mysql_mysqldb_dbapiunicode_nocextensions 19399
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_postgresql_psycopg2_dbapiunicode_cextensions 18573
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 18805
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_sqlite_pysqlite_dbapiunicode_cextensions 18382
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 18627
 
 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_load
 
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_dbapiunicode_cextensions 1446
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_dbapiunicode_nocextensions 1465
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_dbapiunicode_cextensions 1330
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 1349
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1213
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1231
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_dbapiunicode_cextensions 1190
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_dbapiunicode_nocextensions 1207
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_dbapiunicode_cextensions 1083
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 1100
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_cextensions 958
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 975
 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.4_mysql_mysqldb_dbapiunicode_cextensions 1494
 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.4_mysql_mysqldb_dbapiunicode_nocextensions 1517
 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.4_postgresql_psycopg2_dbapiunicode_cextensions 1365
 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.4_postgresql_psycopg2_dbapiunicode_nocextensions 1388
 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.4_sqlite_pysqlite_dbapiunicode_cextensions 1257
 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.4_sqlite_pysqlite_dbapiunicode_nocextensions 1280
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_mysql_mysqldb_dbapiunicode_cextensions 1495
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_mysql_mysqldb_dbapiunicode_nocextensions 1519
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_postgresql_psycopg2_dbapiunicode_cextensions 1375
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 1398
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_sqlite_pysqlite_dbapiunicode_cextensions 1256
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 1280
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_mysql_mysqldb_dbapiunicode_cextensions 1226
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_mysql_mysqldb_dbapiunicode_nocextensions 1245
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_postgresql_psycopg2_dbapiunicode_cextensions 1114
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 1133
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_sqlite_pysqlite_dbapiunicode_cextensions 988
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 1007
 
 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_no_load
 
@@ -390,28 +390,28 @@ test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.5_mysql_mysqldb_dbapiuni
 test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.5_postgresql_psycopg2_dbapiunicode_cextensions 6324
 test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 6894
 test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.5_sqlite_pysqlite_dbapiunicode_cextensions 6096
-test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 6756
+test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 6666
 
 # TEST: test.aaa_profiling.test_orm.SessionTest.test_expire_lots
 
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_mysql_mysqldb_dbapiunicode_cextensions 1147
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_mysql_mysqldb_dbapiunicode_nocextensions 1152
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_postgresql_psycopg2_dbapiunicode_cextensions 1148
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 1134
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1155
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1152
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_mysql_mysqldb_dbapiunicode_cextensions 1138
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_mysql_mysqldb_dbapiunicode_nocextensions 1156
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_postgresql_psycopg2_dbapiunicode_cextensions 1147
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 1140
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1154
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1168
 test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.4_mysql_mysqldb_dbapiunicode_cextensions 1256
 test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.4_mysql_mysqldb_dbapiunicode_nocextensions 1252
 test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.4_postgresql_psycopg2_dbapiunicode_cextensions 1235
 test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.4_postgresql_psycopg2_dbapiunicode_nocextensions 1278
 test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.4_sqlite_pysqlite_dbapiunicode_cextensions 1258
 test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.4_sqlite_pysqlite_dbapiunicode_nocextensions 1252
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_mysql_mysqldb_dbapiunicode_cextensions 1264
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_mysql_mysqldb_dbapiunicode_nocextensions 1254
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_postgresql_psycopg2_dbapiunicode_cextensions 1250
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 1265
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_sqlite_pysqlite_dbapiunicode_cextensions 1235
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 1242
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_mysql_mysqldb_dbapiunicode_cextensions 1302
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_mysql_mysqldb_dbapiunicode_nocextensions 1258
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_postgresql_psycopg2_dbapiunicode_cextensions 1257
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 1259
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_sqlite_pysqlite_dbapiunicode_cextensions 1286
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.5_sqlite_pysqlite_dbapiunicode_nocextensions 1251
 
 # TEST: test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect
 
@@ -623,8 +623,8 @@ test.aaa_profiling.test_zoomark.ZooMarkTest.test_invocation 3.5_postgresql_psyco
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation
 
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 2.7_postgresql_psycopg2_dbapiunicode_cextensions 6462,410,6777,17745,1170,2657
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 6552,415,6897,18814,1276,2692
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 6558,415,6897,18814,1276,2692
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 3.4_postgresql_psycopg2_dbapiunicode_cextensions 6430,401,6925,18147,1169,2705
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 3.4_postgresql_psycopg2_dbapiunicode_nocextensions 6520,406,7061,19258,1271,2746
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 3.5_postgresql_psycopg2_dbapiunicode_cextensions 6430,401,6909,18147,1169,2705
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 3.5_postgresql_psycopg2_dbapiunicode_cextensions 6435,401,6909,18147,1169,2705
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 3.5_postgresql_psycopg2_dbapiunicode_nocextensions 6527,406,7045,19258,1271,2746