diff options
| author | mike bayer <mike_mp@zzzcomputing.com> | 2018-06-07 10:18:22 -0400 |
|---|---|---|
| committer | Gerrit Code Review <gerrit@ci.zzzcomputing.com> | 2018-06-07 10:18:22 -0400 |
| commit | 2e286dad303a94df092af9e104ed4036cb3c77aa (patch) | |
| tree | e47b3d0b6254063c0b7063bf04d50365411953de /lib/sqlalchemy | |
| parent | 0dd06d666f0b540706138e268ac17c8cb4fae0ec (diff) | |
| parent | 006da86a398f98b899c04bb9e4eee2e9a4cf10d4 (diff) | |
| download | sqlalchemy-2e286dad303a94df092af9e104ed4036cb3c77aa.tar.gz | |
Merge "Iterate options per path for baked cache key"
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategy_options.py | 121 |
2 files changed, 68 insertions, 55 deletions
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 067b6c9f5..74d3079af 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1455,6 +1455,8 @@ class Query(object): # most MapperOptions write to the '_attributes' dictionary, # so copy that as well self._attributes = self._attributes.copy() + if '_unbound_load_dedupes' not in self._attributes: + self._attributes['_unbound_load_dedupes'] = set() opts = tuple(util.flatten_iterator(args)) self._with_options = self._with_options + opts if conditional: diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index 43f571146..f54020fb7 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -83,50 +83,54 @@ class Load(Generative, MapperOption): if key != "loader": continue - endpoint = obj._of_type or obj.path.path[-1] - chopped = self._chop_path(loader_path, path) - - if ( - # means loader_path and path are unrelated, - # this does not need to be part of a cache key - chopped is None - ) or ( - # means no additional path with loader_path + path - # and the endpoint isn't using of_type so isn't modified into - # an alias or other unsafe entity - 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 or ()) + - (tuple([ - (key, obj.local_opts[key]) - for key in sorted(obj.local_opts) - ]) if obj.local_opts else ()) + for local_elem, obj_elem in zip(self.path.path, loader_path): + if local_elem is not obj_elem: + break + else: + endpoint = obj._of_type or obj.path.path[-1] + chopped = self._chop_path(loader_path, path) + + if ( + # means loader_path and path are unrelated, + # this does not need to be part of a cache key + chopped is None + ) or ( + # means no additional path with loader_path + path + # and the endpoint isn't using of_type so isn't modified + # into an alias or other unsafe entity + 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 or ()) + + (tuple([ + (key, obj.local_opts[key]) + for key in sorted(obj.local_opts) + ]) if obj.local_opts else ()) + ) ) - ) if not serialized: return None else: @@ -407,15 +411,19 @@ class _UnboundLoad(Load): 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 + for local_elem, val_elem in zip(self.path, val.path): + if local_elem is not val_elem: + break + else: + 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: @@ -462,10 +470,13 @@ class _UnboundLoad(Load): self.__dict__ = state def _process(self, query, raiseerr): + dedupes = query._attributes['_unbound_load_dedupes'] for val in self._to_bind: - val._bind_loader( - [ent.entity_zero for ent in query._mapper_entities], - query._current_path, query._attributes, raiseerr) + if val not in dedupes: + dedupes.add(val) + 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): |
