diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2017-04-07 14:18:22 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2017-04-13 14:22:59 -0400 |
| commit | b7644319e85ce38c1a576802317a9058a6aed82d (patch) | |
| tree | 12db3074d79d0c54deb247a7e79424312e183cf3 /lib/sqlalchemy/ext | |
| parent | 755da1797432ee98dd3d1d309026a21529b45f75 (diff) | |
| download | sqlalchemy-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.py | 159 |
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, {}) |
