diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-05-25 22:36:44 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-05-25 22:36:44 -0400 |
commit | 903b18828461bb8cb8dca4acc56809b3df2b14d5 (patch) | |
tree | 72a0c86fae3b9a6c9a33bf3ccd39f3c73f143a55 /lib/sqlalchemy/orm/loading.py | |
parent | 6930dfc032c3f9f474e71ab4e021c0ef8384930e (diff) | |
download | sqlalchemy-903b18828461bb8cb8dca4acc56809b3df2b14d5.tar.gz |
Small callcount reductions and refinement for cached queries
baked wasn't using the new one()/first()/one_or_none() methods,
fixed that.
loading._instance_processor() can skip setting up the
quick populators every time because it can cache the getters.
Callcounts have gone below what 1.3 does for the
test_baked_query performance suite, however runtime for
continued inexplicable reasons has not :(. still suspecting
the result tuples but this seems so hard to believe.
Change-Id: Ifbca04834d27350e0fa82cb8512e66112abc8729
Diffstat (limited to 'lib/sqlalchemy/orm/loading.py')
-rw-r--r-- | lib/sqlalchemy/orm/loading.py | 124 |
1 files changed, 80 insertions, 44 deletions
diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py index 616e757a3..102e74b99 100644 --- a/lib/sqlalchemy/orm/loading.py +++ b/lib/sqlalchemy/orm/loading.py @@ -14,8 +14,6 @@ as well as some of the attribute loading strategies. """ from __future__ import absolute_import -import collections - from . import attributes from . import exc as orm_exc from . import path_registry @@ -588,48 +586,86 @@ def _instance_processor( identity_class = mapper._identity_class - populators = collections.defaultdict(list) + compile_state = context.compile_state + + populators = {} props = mapper._prop_set if only_load_props is not None: props = props.intersection(mapper._props[k] for k in only_load_props) - quick_populators = path.get( - context.attributes, "memoized_setups", _none_set - ) + getters = path.get(compile_state.attributes, "getters", None) + if getters is None: + # directives given to us by the ColumnLoader.setup_query() + # methods. Turn these directives into getters against the + # actual result set. + quick_populators = path.get( + context.attributes, "memoized_setups", _none_set + ) + cached_populators = { + "new": [], + "expire": [], + "quick": [], + "todo": [], + "delayed": [], + "existing": [], + "eager": [], + } - for prop in props: - if prop in quick_populators: - # this is an inlined path just for column-based attributes. - col = quick_populators[prop] - if col is _DEFER_FOR_STATE: - populators["new"].append( - (prop.key, prop._deferred_column_loader) - ) - elif col is _SET_DEFERRED_EXPIRED: - # note that in this path, we are no longer - # searching in the result to see if the column might - # be present in some unexpected way. - populators["expire"].append((prop.key, False)) - elif col is _RAISE_FOR_STATE: - populators["new"].append((prop.key, prop._raise_column_loader)) - else: - getter = None - if not getter: - getter = result._getter(col, False) - if getter: - populators["quick"].append((prop.key, getter)) - else: - # fall back to the ColumnProperty itself, which - # will iterate through all of its columns - # to see if one fits - prop.create_row_processor( - context, path, mapper, result, adapter, populators + pk_cols = mapper.primary_key + + if adapter: + pk_cols = [adapter.columns[c] for c in pk_cols] + getters = { + "populators": cached_populators, + "primary_key_getter": result._tuple_getter(pk_cols), + } + + for prop in props: + if prop in quick_populators: + # this is an inlined path just for column-based attributes. + col = quick_populators[prop] + if col is _DEFER_FOR_STATE: + cached_populators["new"].append( + (prop.key, prop._deferred_column_loader) ) - else: - prop.create_row_processor( - context, path, mapper, result, adapter, populators - ) + elif col is _SET_DEFERRED_EXPIRED: + # note that in this path, we are no longer + # searching in the result to see if the column might + # be present in some unexpected way. + cached_populators["expire"].append((prop.key, False)) + elif col is _RAISE_FOR_STATE: + cached_populators["new"].append( + (prop.key, prop._raise_column_loader) + ) + else: + getter = None + if not getter: + getter = result._getter(col, False) + if getter: + cached_populators["quick"].append((prop.key, getter)) + else: + # fall back to the ColumnProperty itself, which + # will iterate through all of its columns + # to see if one fits + prop.create_row_processor( + context, + path, + mapper, + result, + adapter, + cached_populators, + ) + else: + cached_populators["todo"].append(prop) + path.set(compile_state.attributes, "getters", getters) + + cached_populators = getters["populators"] + populators = {k: list(v) for k, v in cached_populators.items()} + for prop in cached_populators["todo"]: + prop.create_row_processor( + context, path, mapper, result, adapter, populators + ) propagated_loader_options = context.propagated_loader_options load_path = ( @@ -707,11 +743,7 @@ def _instance_processor( else: refresh_identity_key = None - pk_cols = mapper.primary_key - - if adapter: - pk_cols = [adapter.columns[c] for c in pk_cols] - tuple_getter = result._tuple_getter(pk_cols) + primary_key_getter = getters["primary_key_getter"] if mapper.allow_partial_pks: is_not_primary_key = _none_set.issuperset @@ -732,7 +764,11 @@ def _instance_processor( else: # look at the row, see if that identity is in the # session, or we have to create a new one - identitykey = (identity_class, tuple_getter(row), identity_token) + identitykey = ( + identity_class, + primary_key_getter(row), + identity_token, + ) instance = session_identity_map.get(identitykey) @@ -875,7 +911,7 @@ def _instance_processor( def ensure_no_pk(row): identitykey = ( identity_class, - tuple_getter(row), + primary_key_getter(row), identity_token, ) if not is_not_primary_key(identitykey[1]): |