summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2017-04-07 14:18:22 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2017-04-13 14:22:59 -0400
commitb7644319e85ce38c1a576802317a9058a6aed82d (patch)
tree12db3074d79d0c54deb247a7e79424312e183cf3 /lib/sqlalchemy/ext
parent755da1797432ee98dd3d1d309026a21529b45f75 (diff)
downloadsqlalchemy-b7644319e85ce38c1a576802317a9058a6aed82d.tar.gz
Use baked lazyloading by default
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
Diffstat (limited to 'lib/sqlalchemy/ext')
-rw-r--r--lib/sqlalchemy/ext/baked.py159
1 files changed, 50 insertions, 109 deletions
diff --git a/lib/sqlalchemy/ext/baked.py b/lib/sqlalchemy/ext/baked.py
index 68bd468b9..249f5db4e 100644
--- a/lib/sqlalchemy/ext/baked.py
+++ b/lib/sqlalchemy/ext/baked.py
@@ -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, {})