From: Mike Bayer Date: Mon, 11 Jan 2010 20:26:34 +0000 (+0000) Subject: have paths represented as their actual mapper, not the base mapper, allowing X-Git-Tag: rel_0_6beta1~80 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7fedc9298ace22df69d030c4d97169528f09f662;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git have paths represented as their actual mapper, not the base mapper, allowing more information for custom mapper opts to see what's going on. add a new _reduce_path() function to apply to the path as stored in dictionaries, adds a slight cost overhead. --- diff --git a/examples/beaker_caching/meta.py b/examples/beaker_caching/meta.py index 4103baa325..25fa1d4cf1 100644 --- a/examples/beaker_caching/meta.py +++ b/examples/beaker_caching/meta.py @@ -170,7 +170,7 @@ class FromCache(MapperOption): """ if self.cls_ is not None and query._current_path: mapper, key = query._current_path[-2:] - if issubclass(self.cls_, mapper.class_) and key == self.propname: + if self.cls_ is mapper.class_ and key == self.propname: self._set_query_cache(query) def process_query(self, query): diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index fa76704205..d8ba9ea96d 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -642,7 +642,7 @@ class StrategizedProperty(MapperProperty): """ def __get_context_strategy(self, context, path): - cls = context.attributes.get(("loaderstrategy", path), None) + cls = context.attributes.get(("loaderstrategy", _reduce_path(path)), None) if cls: try: return self.__all_strategies[cls] @@ -762,11 +762,13 @@ class PropertyOption(MapperOption): if _is_aliased_class(mapper): searchfor = mapper + isa = False else: - searchfor = _class_to_mapper(mapper).base_mapper - + searchfor = _class_to_mapper(mapper) + isa = True + for ent in query._mapper_entities: - if ent.path_entity is searchfor: + if searchfor is ent.path_entity or (isa and searchfor.common_parent(ent.path_entity)): return ent else: if raiseerr: @@ -852,7 +854,7 @@ class PropertyOption(MapperOption): path_element = mapper = getattr(prop, 'mapper', None) if path_element: - path_element = path_element.base_mapper + path_element = path_element # if current_path tokens remain, then @@ -911,15 +913,34 @@ class StrategizedOption(PropertyOption): return False def process_query_property(self, query, paths, mappers): + # __get_context_strategy may receive the path in terms of + # a base mapper - e.g. options(eagerload_all(Company.employees, Engineer.machines)) + # in the polymorphic tests leads to "(Person, 'machines')" in + # the path due to the mechanics of how the eager strategy builds + # up the path if self.is_chained(): for path in paths: - query._attributes[("loaderstrategy", path)] = self.get_strategy_class() + query._attributes[("loaderstrategy", _reduce_path(path))] = \ + self.get_strategy_class() else: - query._attributes[("loaderstrategy", paths[-1])] = self.get_strategy_class() + query._attributes[("loaderstrategy", _reduce_path(paths[-1]))] = \ + self.get_strategy_class() def get_strategy_class(self): raise NotImplementedError() +def _reduce_path(path): + """Convert a (mapper, path) path to use base mappers. + + This is used to allow more open ended selection of loader strategies, i.e. + Mapper -> prop1 -> Subclass -> prop2, where Subclass is a sub-mapper + of the mapper referened by Mapper.prop1. + + """ + return tuple([i % 2 != 0 and + path[i] or + getattr(path[i], 'base_mapper', path[i]) + for i in xrange(len(path))]) class LoaderStrategy(object): """Describe the loading behavior of a StrategizedProperty object. diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 5b2fbb5240..c90c163e96 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1967,7 +1967,7 @@ class _MapperEntity(_QueryEntity): if is_aliased_class: self.path_entity = self.entity = self.entity_zero = entity else: - self.path_entity = mapper.base_mapper + self.path_entity = mapper self.entity = self.entity_zero = mapper def set_with_polymorphic(self, query, cls_or_mappers, selectable, discriminator): @@ -1989,7 +1989,7 @@ class _MapperEntity(_QueryEntity): if _is_aliased_class(entity): return entity is self.path_entity else: - return entity.base_mapper is self.path_entity + return entity.isa(self.path_entity) def adapt_to_selectable(self, query, sel): query._entities.append(self) @@ -2151,7 +2151,7 @@ class _ColumnEntity(_QueryEntity): return entity is self.entity_zero else: return not _is_aliased_class(self.entity_zero) and \ - entity.base_mapper.common_parent(self.entity_zero) + entity.common_parent(self.entity_zero) def _resolve_expr_against_query_aliases(self, query, expr, context): return query._adapt_clause(expr, False, True) diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index a3cc06b910..0970f38362 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -628,16 +628,20 @@ class EagerLoader(AbstractRelationLoader): return path = path + (self.key,) - + + reduced_path = interfaces._reduce_path(path) + # check for user-defined eager alias - if ("user_defined_eager_row_processor", path) in context.attributes: - clauses = context.attributes[("user_defined_eager_row_processor", path)] + if ("user_defined_eager_row_processor", reduced_path) in context.attributes: + clauses = context.attributes[("user_defined_eager_row_processor", reduced_path)] adapter = entity._get_entity_clauses(context.query, context) if adapter and clauses: - context.attributes[("user_defined_eager_row_processor", path)] = clauses = clauses.wrap(adapter) + context.attributes[("user_defined_eager_row_processor", reduced_path)] = \ + clauses = clauses.wrap(adapter) elif adapter: - context.attributes[("user_defined_eager_row_processor", path)] = clauses = adapter + context.attributes[("user_defined_eager_row_processor", reduced_path)] = \ + clauses = adapter add_to_collection = context.primary_columns @@ -645,12 +649,12 @@ class EagerLoader(AbstractRelationLoader): # check for join_depth or basic recursion, # if the current path was not explicitly stated as # a desired "loaderstrategy" (i.e. via query.options()) - if ("loaderstrategy", path) not in context.attributes: + if ("loaderstrategy", reduced_path) not in context.attributes: if self.join_depth: if len(path) / 2 > self.join_depth: return else: - if self.mapper.base_mapper in path: + if self.mapper.base_mapper in reduced_path: return clauses = mapperutil.ORMAdapter(mapperutil.AliasedClass(self.mapper), @@ -664,13 +668,13 @@ class EagerLoader(AbstractRelationLoader): ) add_to_collection = context.secondary_columns - context.attributes[("eager_row_processor", path)] = clauses + context.attributes[("eager_row_processor", reduced_path)] = clauses for value in self.mapper._iterate_polymorphic_properties(): value.setup( context, entity, - path + (self.mapper.base_mapper,), + path + (self.mapper,), clauses, parentmapper=self.mapper, column_collection=add_to_collection) @@ -757,16 +761,17 @@ class EagerLoader(AbstractRelationLoader): def _create_eager_adapter(self, context, row, adapter, path): - if ("user_defined_eager_row_processor", path) in context.attributes: - decorator = context.attributes[("user_defined_eager_row_processor", path)] + reduced_path = interfaces._reduce_path(path) + if ("user_defined_eager_row_processor", reduced_path) in context.attributes: + decorator = context.attributes[("user_defined_eager_row_processor", reduced_path)] # user defined eagerloads are part of the "primary" portion of the load. # the adapters applied to the Query should be honored. if context.adapter and decorator: decorator = decorator.wrap(context.adapter) elif context.adapter: decorator = context.adapter - elif ("eager_row_processor", path) in context.attributes: - decorator = context.attributes[("eager_row_processor", path)] + elif ("eager_row_processor", reduced_path) in context.attributes: + decorator = context.attributes[("eager_row_processor", reduced_path)] else: if self._should_log_debug: self.logger.debug("Could not locate aliased clauses for key: " + str(path)) @@ -788,7 +793,7 @@ class EagerLoader(AbstractRelationLoader): if eager_adapter is not False: key = self.key - _instance = self.mapper._instance_processor(context, path + (self.mapper.base_mapper,), eager_adapter) + _instance = self.mapper._instance_processor(context, path + (self.mapper,), eager_adapter) if not self.uselist: def execute(state, dict_, row, isnew, **flags): @@ -892,13 +897,18 @@ class LoadEagerFromAliasOption(PropertyOption): (root_mapper, propname) = paths[-1][-2:] prop = mapper.get_property(propname, resolve_synonyms=True) self.alias = prop.target.alias(self.alias) - query._attributes[("user_defined_eager_row_processor", paths[-1])] = sql_util.ColumnAdapter(self.alias) + query._attributes[ + ("user_defined_eager_row_processor", + interfaces._reduce_path(paths[-1])) + ] = sql_util.ColumnAdapter(self.alias) else: (root_mapper, propname) = paths[-1][-2:] mapper = mappers[-1] prop = mapper.get_property(propname, resolve_synonyms=True) adapter = query._polymorphic_adapters.get(prop.mapper, None) - query._attributes[("user_defined_eager_row_processor", paths[-1])] = adapter + query._attributes[ + ("user_defined_eager_row_processor", + interfaces._reduce_path(paths[-1]))] = adapter class _SingleParentValidator(interfaces.AttributeExtension): def __init__(self, prop):