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
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
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
-----------------
.. autoclass:: Result
:members:
-.. autofunction:: bake_lazy_loaders
-
-.. autofunction:: unbake_lazy_loaders
-
-.. autofunction:: baked_lazyload
-
-.. autofunction:: baked_lazyload_all
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)
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:
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()
@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, {})
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.
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 "
"#faq_compiled_cache_threshold"
" for best practices." %
(self,
- self._compiled_cache.size_threshold,
+ lru_cache.size_threshold,
self._compiled_cache_size))
@_memoized_configured_property
_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
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)
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,
: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
@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.
__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)
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*
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:
"""
-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
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):
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 = {}
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(
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
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:
_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):
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()
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)
"""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')``.
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 \
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)
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):
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)
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)
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'."
"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 "
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.
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
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)
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)
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
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
})
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)
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)
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)
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
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
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):
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_(
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)
),
]
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))
+ )
+ )
+
# 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
# 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
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
# 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