From 5045bf4f4bae769743bc3bef4ec85453caf0fa41 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 7 Jul 2009 17:17:22 +0000 Subject: - Fixed a bug involving contains_eager(), which would apply itself to a secondary (i.e. lazy) load in a particular rare case, producing cartesian products. improved the targeting of query.options() on secondary loads overall [ticket:1461]. --- lib/sqlalchemy/orm/__init__.py | 2 +- lib/sqlalchemy/orm/interfaces.py | 18 +++++++++++++----- lib/sqlalchemy/orm/strategies.py | 11 ++++++++++- 3 files changed, 24 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 7b840a50d..6af8dde9f 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -934,7 +934,7 @@ def contains_eager(*keys, **kwargs): if kwargs: raise exceptions.ArgumentError("Invalid kwargs for contains_eager: %r" % kwargs.keys()) - return (strategies.EagerLazyOption(keys, lazy=False), strategies.LoadEagerFromAliasOption(keys, alias=alias)) + return (strategies.EagerLazyOption(keys, lazy=False, _only_on_lead=True), strategies.LoadEagerFromAliasOption(keys, alias=alias)) @sa_util.accepts_a_list_as_starargs(list_deprecation='pending') def defer(*keys): diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 0ac771305..9a9ebfcab 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -682,13 +682,14 @@ class PropertyOption(MapperOption): searchfor = mapper else: searchfor = _class_to_mapper(mapper).base_mapper - + for ent in query._mapper_entities: if ent.path_entity is searchfor: return ent else: if raiseerr: - raise sa_exc.ArgumentError("Can't find entity %s in Query. Current list: %r" % (searchfor, [str(m.path_entity) for m in query._entities])) + raise sa_exc.ArgumentError("Can't find entity %s in Query. Current list: %r" + % (searchfor, [str(m.path_entity) for m in query._entities])) else: return None @@ -718,8 +719,10 @@ class PropertyOption(MapperOption): entity = None l = [] + # _current_path implies we're in a secondary load + # with an existing path current_path = list(query._current_path) - + if self.mapper: entity = self.__find_entity(query, self.mapper, raiseerr) mapper = entity.mapper @@ -752,7 +755,7 @@ class PropertyOption(MapperOption): if current_path and key == current_path[1]: current_path = current_path[2:] continue - + if prop is None: return [] @@ -764,7 +767,12 @@ class PropertyOption(MapperOption): path_element = mapper = getattr(prop, 'mapper', None) if path_element: path_element = path_element.base_mapper - + + # if current_path tokens remain, then + # we didn't have an exact path match. + if current_path: + return [] + return l class AttributeExtension(object): diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 20cbb8f4d..ebb576a71 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -776,11 +776,16 @@ class EagerLoader(AbstractRelationLoader): log.class_logger(EagerLoader) class EagerLazyOption(StrategizedOption): - def __init__(self, key, lazy=True, chained=False, mapper=None): + def __init__(self, key, lazy=True, chained=False, mapper=None, _only_on_lead=False): super(EagerLazyOption, self).__init__(key, mapper) self.lazy = lazy self.chained = chained + self._only_on_lead = _only_on_lead + def process_query_conditionally(self, query): + if not self._only_on_lead: + StrategizedOption.process_query_conditionally(self, query) + def is_chained(self): return not self.lazy and self.chained @@ -800,6 +805,10 @@ class LoadEagerFromAliasOption(PropertyOption): m, alias, is_aliased_class = mapperutil._entity_info(alias) self.alias = alias + def process_query_conditionally(self, query): + # dont run this option on a secondary load + pass + def process_query_property(self, query, paths): if self.alias: if isinstance(self.alias, basestring): -- cgit v1.2.1