diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2018-05-28 13:03:14 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2018-06-04 12:30:05 -0400 |
| commit | a574b409296ef793cec8e1d00f1f7be48f15325e (patch) | |
| tree | f79b0471f31abe95f5d73d71c35125a4bbdb210c /lib/sqlalchemy | |
| parent | 59ef300206c9973a7867098f4f6bc22985580617 (diff) | |
| download | sqlalchemy-a574b409296ef793cec8e1d00f1f7be48f15325e.tar.gz | |
Add Query.lazy_load_from attribute for sharding
Added new attribute :attr:`.Query.lazy_loaded_from` which is populated
with an :class:`.InstanceState` that is using this :class:`.Query` in
order to lazy load a relationship. The rationale for this is that
it serves as a hint for the horizontal sharding feature to use, such that
the identity token of the state can be used as the default identity token
to use for the query within id_chooser().
Also repaired an issue in the :meth:`.Result.with_post_criteria`
method added in I899808734458e25a023142c2c5bb37cbed869479
for :ticket:`4128` where the "unbake subquery loaders" version was calling
the post crtieria functions given the :class:`.Result` as the argument
rather than applying them to the :class:`.Query`.
Change-Id: I3c0919ce7fd151b80fe2f9b5f99f60df31c2d73d
Fixes: #4243
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/ext/baked.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/ext/horizontal_shard.py | 5 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 28 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 15 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/assertions.py | 15 |
5 files changed, 57 insertions, 8 deletions
diff --git a/lib/sqlalchemy/ext/baked.py b/lib/sqlalchemy/ext/baked.py index f4d71f410..79457e86e 100644 --- a/lib/sqlalchemy/ext/baked.py +++ b/lib/sqlalchemy/ext/baked.py @@ -253,7 +253,7 @@ class BakedQuery(object): bk._cache_key = cache_key q = bk.for_session(session) for fn in post_criteria: - q = fn(q) + q = q.with_post_criteria(fn) context.attributes[k] = q.params(**params) diff --git a/lib/sqlalchemy/ext/horizontal_shard.py b/lib/sqlalchemy/ext/horizontal_shard.py index c7770d195..6ef4c5612 100644 --- a/lib/sqlalchemy/ext/horizontal_shard.py +++ b/lib/sqlalchemy/ext/horizontal_shard.py @@ -65,7 +65,8 @@ class ShardedQuery(Query): return iter(partial) def _identity_lookup( - self, mapper, primary_key_identity, identity_token=None, **kw): + self, mapper, primary_key_identity, identity_token=None, + lazy_loaded_from=None, **kw): """override the default Query._identity_lookup method so that we search for a given non-token primary key identity across all possible identity tokens (e.g. shard ids). @@ -79,6 +80,8 @@ class ShardedQuery(Query): ) else: q = self.session.query(mapper) + if lazy_loaded_from: + q = q._set_lazyload_from(lazy_loaded_from) for shard_id in self.id_chooser(q, primary_key_identity): obj = super(ShardedQuery, self)._identity_lookup( mapper, primary_key_identity, identity_token=shard_id, **kw diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 56e42a702..e7efc172a 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -112,6 +112,18 @@ class Query(object): _current_path = _path_registry _has_mapper_entities = False + lazy_loaded_from = None + """An :class:`.InstanceState` that is using this :class:`.Query` for a + lazy load operation. + + This can be used for extensions like the horizontal sharding extension + as well as event handlers and custom mapper options to determine + when a query is being used to lazy load a relationship on an object. + + .. versionadded:: 1.2.9 + + """ + def __init__(self, entities, session=None): """Construct a :class:`.Query` directly. @@ -261,6 +273,10 @@ class Query(object): ] @_generative() + def _set_lazyload_from(self, state): + self.lazy_loaded_from = state + + @_generative() def _adapt_all_clauses(self): self._orm_only_adapt = False @@ -887,8 +903,8 @@ class Query(object): ident, loading.load_on_pk_identity) def _identity_lookup(self, mapper, primary_key_identity, - identity_token=None, - passive=attributes.PASSIVE_OFF): + identity_token=None, passive=attributes.PASSIVE_OFF, + lazy_loaded_from=None): """Locate an object in the identity map. Given a primary key identity, constructs an identity key and then @@ -913,6 +929,14 @@ class Query(object): :func:`.loading.get_from_identity`, which impacts the behavior if the object is found; the object may be validated and/or unexpired if the flag allows for SQL to be emitted. + :param lazy_loaded_from: an :class:`.InstanceState` that is + specifically asking for this identity as a related identity. Used + for sharding schemes where there is a correspondence between an object + and a related object being lazy-loaded (or otherwise + relationship-loaded). + + .. versionadded:: 1.2.9 + :return: None if the object is not found in the identity map, *or* if the object was unexpired and found to have been deleted. if passive flags disallow SQL and the object is expired, returns diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 93288c3d6..d7597d3b2 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -617,7 +617,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): # does this, including how it decides what the correct # identity_token would be for this identity. instance = session.query()._identity_lookup( - self.mapper, primary_key_identity, passive=passive + self.mapper, primary_key_identity, passive=passive, + lazy_loaded_from=state ) if instance is not None: @@ -715,8 +716,12 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): if self.use_get: if self._raise_on_sql: self._invoke_raise_load(state, passive, "raise_on_sql") - return q(session)._load_on_pk_identity( - session.query(self.mapper), primary_key_identity) + + return q(session).\ + with_post_criteria(lambda q: q._set_lazyload_from(state)).\ + _load_on_pk_identity( + session.query(self.mapper), + primary_key_identity) if self.parent_property.order_by: q.add_criteria( @@ -761,7 +766,9 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): q._params = params return q - result = q(session).with_post_criteria(set_default_params).all() + result = q(session).\ + with_post_criteria(lambda q: q._set_lazyload_from(state)).\ + with_post_criteria(set_default_params).all() if self.uselist: return result else: diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py index 69d43c92f..e42376921 100644 --- a/lib/sqlalchemy/testing/assertions.py +++ b/lib/sqlalchemy/testing/assertions.py @@ -518,6 +518,21 @@ class AssertsExecutionResults(object): self.assert_sql_execution( db, callable_, assertsql.CountStatements(count)) + def assert_multiple_sql_count(self, dbs, callable_, counts): + recs = [ + (self.sql_execution_asserter(db), db, count) + for (db, count) in zip(dbs, counts) + ] + asserters = [] + for ctx, db, count in recs: + asserters.append(ctx.__enter__()) + try: + return callable_() + finally: + for asserter, (ctx, db, count) in zip(asserters, recs): + ctx.__exit__(None, None, None) + asserter.assert_(assertsql.CountStatements(count)) + @contextlib.contextmanager def assert_execution(self, db, *rules): with self.sql_execution_asserter(db) as asserter: |
