]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- refactor of pathing mechanics, to address #2614, #2617
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 2 Dec 2012 01:12:23 +0000 (20:12 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 2 Dec 2012 01:12:23 +0000 (20:12 -0500)
- paths now store Mapper + MapperProperty now instead of string key,
so that the parent mapper for the property is known, supports same-named
properties on multiple subclasses
- the Mapper within the path is now always relevant to the property
to the right of it.  PathRegistry does the translation now, instead
of having all the outside users of PathRegistry worry about it,
to produce a path that is much more consistent.  Paths are now
consistent with mappings in all cases.  Special logic to get at
"with_polymorphic" structures and such added also.
- AliasedClass now has two modes, "use_mapper_path" and regular;
"use_mapper_path" is for all those situations where we put an AliasedClass
in for a plain class internally, and want it to "path" with the
plain mapper.
- The AliasedInsp is now the first class "entity" for an AliasedClass,
and is passed around internally and used as attr._parententity
and such.  it is the AliasedClass analogue for Mapper.

19 files changed:
doc/build/changelog/changelog_08.rst
lib/sqlalchemy/ext/serializer.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/loading.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/util.py
test/aaa_profiling/test_memusage.py
test/orm/inheritance/test_polymorphic_rel.py
test/orm/inheritance/test_relationship.py
test/orm/inheritance/test_single.py
test/orm/test_eager_relations.py
test/orm/test_inspect.py
test/orm/test_joins.py
test/orm/test_mapper.py
test/orm/test_query.py
test/orm/test_subquery_relations.py
test/orm/test_utils.py

index 4d36cf3b49c5a99b5b99fd49e245081e79da0c53..38aba26d87cb985e79590d8333053f52d3344f53 100644 (file)
@@ -8,7 +8,7 @@
 
     .. change::
         :tags: firebird, bug
-        :ticket: 2622
+        :tickets: 2622
 
       Added missing import for "fdb" to the experimental
       "firebird+fdb" dialect.
 
     .. change::
         :tags: orm, bug
-        :ticket: 2614
-
-      Added a new exception to detect the case where two
-      subclasses are being loaded using with_polymorphic(),
-      each subclass contains a relationship attribute of the same
-      name, and eager loading is being applied to one or both.
-      This is an ongoing bug which can't be immediately fixed,
-      so since the results are usually wrong in any case it raises an
-      exception for now.   0.7 has the same issue, so an exception
-      raise here probably means the code was returning the wrong
-      data in 0.7.
+        :tickets: 2614
+
+      A second overhaul of aliasing/internal pathing mechanics
+      now allows two subclasses to have different relationships
+      of the same name, supported with subquery or joined eager
+      loading on both simultaneously when a full polymorphic
+      load is used.
+
+    .. change::
+        :tags: orm, bug
+        :tickets: 2617
+
+      Fixed bug whereby a multi-hop subqueryload within
+      a particular with_polymorphic load would produce a KeyError.
+      Takes advantage of the same internal pathing overhaul
+      as :ticket:`2614`.
 
     .. change::
         :tags: sql, bug
index 3ed41f48a39a2f9adf42353ab7b9ef9fedde4378..c129b0dcc709f34f5ed8d16853a2bcb4a60c0588 100644 (file)
@@ -54,6 +54,7 @@ needed for:
 from ..orm import class_mapper
 from ..orm.session import Session
 from ..orm.mapper import Mapper
+from ..orm.interfaces import MapperProperty
 from ..orm.attributes import QueryableAttribute
 from .. import Table, Column
 from ..engine import Engine
@@ -90,6 +91,9 @@ def Serializer(*args, **kw):
             id = "attribute:" + key + ":" + b64encode(pickle.dumps(cls))
         elif isinstance(obj, Mapper) and not obj.non_primary:
             id = "mapper:" + b64encode(pickle.dumps(obj.class_))
+        elif isinstance(obj, MapperProperty) and not obj.parent.non_primary:
+            id = "mapperprop:" + b64encode(pickle.dumps(obj.parent.class_)) + \
+                                    ":" + obj.key
         elif isinstance(obj, Table):
             id = "table:" + str(obj)
         elif isinstance(obj, Column) and isinstance(obj.table, Table):
@@ -134,6 +138,10 @@ def Deserializer(file, metadata=None, scoped_session=None, engine=None):
             elif type_ == "mapper":
                 cls = pickle.loads(b64decode(args))
                 return class_mapper(cls)
+            elif type_ == "mapperprop":
+                mapper, keyname = args.split(':')
+                cls = pickle.loads(b64decode(args))
+                return class_mapper(cls).attrs[keyname]
             elif type_ == "table":
                 return metadata.tables[args]
             elif type_ == "column":
index c91746da0e93180bb094d818c7c225885ea84c99..55a980b2e744bb0975c31619fe4084b72733ac5c 100644 (file)
@@ -219,6 +219,10 @@ class MapperProperty(_MappedAttribute, _InspectionAttr):
 
         return operator(self.comparator, value)
 
+    def __repr__(self):
+        return '<%s at 0x%x; %s>' % (
+            self.__class__.__name__,
+            id(self), self.key)
 
 class PropComparator(operators.ColumnOperators):
     """Defines boolean, comparison, and other operators for
@@ -413,21 +417,18 @@ class StrategizedProperty(MapperProperty):
             return None
 
     def _get_context_strategy(self, context, path):
-        # this is essentially performance inlining.
-        key = ('loaderstrategy', path.reduced_path + (self.key,))
-        cls = None
-        if key in context.attributes:
-            cls = context.attributes[key]
-        else:
+        strategy_cls = path._inlined_get_for(self, context, 'loaderstrategy')
+
+        if not strategy_cls:
             wc_key = self._wildcard_path
             if wc_key and wc_key in context.attributes:
-                cls = context.attributes[wc_key]
+                strategy_cls = context.attributes[wc_key]
 
-        if cls:
+        if strategy_cls:
             try:
-                return self._strategies[cls]
+                return self._strategies[strategy_cls]
             except KeyError:
-                return self.__init_strategy(cls)
+                return self.__init_strategy(strategy_cls)
         return self.strategy
 
     def _get_strategy(self, cls):
@@ -528,10 +529,8 @@ class PropertyOption(MapperOption):
     def _find_entity_prop_comparator(self, query, token, mapper, raiseerr):
         if orm_util._is_aliased_class(mapper):
             searchfor = mapper
-            isa = False
         else:
             searchfor = orm_util._class_to_mapper(mapper)
-            isa = True
         for ent in query._mapper_entities:
             if ent.corresponds_to(searchfor):
                 return ent
@@ -600,7 +599,7 @@ class PropertyOption(MapperOption):
                 # exhaust current_path before
                 # matching tokens to entities
                 if current_path:
-                    if current_path[1] == token:
+                    if current_path[1].key == token:
                         current_path = current_path[2:]
                         continue
                     else:
@@ -634,7 +633,7 @@ class PropertyOption(MapperOption):
                 # matching tokens to entities
                 if current_path:
                     if current_path[0:2] == \
-                            [token._parententity, prop.key]:
+                            [token._parententity, prop]:
                         current_path = current_path[2:]
                         continue
                     else:
@@ -648,6 +647,7 @@ class PropertyOption(MapperOption):
                                             raiseerr)
                     if not entity:
                         return no_result
+
                     path_element = entity.entity_zero
                     mapper = entity.mapper
             else:
@@ -659,7 +659,7 @@ class PropertyOption(MapperOption):
                 raise sa_exc.ArgumentError("Attribute '%s' does not "
                             "link from element '%s'" % (token, path_element))
 
-            path = path[path_element][prop.key]
+            path = path[path_element][prop]
 
             paths.append(path)
 
@@ -670,7 +670,8 @@ class PropertyOption(MapperOption):
                 if not ext_info.is_aliased_class:
                     ac = orm_util.with_polymorphic(
                                 ext_info.mapper.base_mapper,
-                                ext_info.mapper, aliased=True)
+                                ext_info.mapper, aliased=True,
+                                _use_mapper_path=True)
                     ext_info = inspect(ac)
                 path.set(query, "path_with_polymorphic", ext_info)
             else:
index a5d156a1fb8e5b368502b2fd9d44f10c7a29dc17..4bd80c38801458a6c631ab3e1f530bd9465dba2e 100644 (file)
@@ -271,6 +271,7 @@ def instance_processor(mapper, context, path, adapter,
     new_populators = []
     existing_populators = []
     eager_populators = []
+
     load_path = context.query._current_path + path \
                 if context.query._current_path.path \
                 else path
@@ -504,9 +505,12 @@ def _populators(mapper, context, path, row, adapter,
     delayed_populators = []
     pops = (new_populators, existing_populators, delayed_populators,
                         eager_populators)
+
     for prop in mapper._props.itervalues():
+
         for i, pop in enumerate(prop.create_row_processor(
-                                    context, path,
+                                    context,
+                                    path,
                                     mapper, row, adapter)):
             if pop is not None:
                 pops[i].append((prop.key, pop))
@@ -529,17 +533,10 @@ def _configure_subclass_mapper(mapper, context, path, adapter):
         if sub_mapper is mapper:
             return None
 
-        # replace the tip of the path info with the subclass mapper
-        # being used, that way accurate "load_path" info is available
-        # for options invoked during deferred loads, e.g.
-        # query(Person).options(defer(Engineer.machines, Machine.name)).
-        # for AliasedClass paths, disregard this step (new in 0.8).
         return instance_processor(
                             sub_mapper,
                             context,
-                            path.parent[sub_mapper]
-                                if not path.is_aliased_class
-                                else path,
+                            path,
                             adapter,
                             polymorphic_from=mapper)
     return configure_subclass_mapper
index b89163340c4327d0bb909d00ec1d1aa34c4de2c5..626105b5e14c8fb018aef0696668b82ec552e433 100644 (file)
@@ -472,7 +472,7 @@ class Mapper(_InspectionAttr):
     dispatch = event.dispatcher(events.MapperEvents)
 
     @util.memoized_property
-    def _sa_path_registry(self):
+    def _path_registry(self):
         return PathRegistry.per_mapper(self)
 
     def _configure_inheritance(self):
@@ -1403,7 +1403,7 @@ class Mapper(_InspectionAttr):
         if _new_mappers:
             configure_mappers()
         if not self.with_polymorphic:
-            return [self]
+            return []
         return self._mappers_from_spec(*self.with_polymorphic)
 
     @_memoized_configured_property
@@ -1458,10 +1458,10 @@ class Mapper(_InspectionAttr):
         return list(self._iterate_polymorphic_properties(
             self._with_polymorphic_mappers))
 
+
     def _iterate_polymorphic_properties(self, mappers=None):
         """Return an iterator of MapperProperty objects which will render into
         a SELECT."""
-
         if mappers is None:
             mappers = self._with_polymorphic_mappers
 
index ca334e2735537d1aa4962dd35f7d9bd4dff90e4a..d6847177f0f0dd570b7912522d00b0c934723074 100644 (file)
@@ -157,7 +157,7 @@ class Query(object):
                 ent.setup_entity(*d[entity])
 
     def _mapper_loads_polymorphically_with(self, mapper, adapter):
-        for m2 in mapper._with_polymorphic_mappers:
+        for m2 in mapper._with_polymorphic_mappers or [mapper]:
             self._polymorphic_adapters[m2] = adapter
             for m in m2.iterate_to_root():
                 self._polymorphic_adapters[m.local_table] = adapter
@@ -2744,17 +2744,24 @@ class _MapperEntity(_QueryEntity):
         self._with_polymorphic = ext_info.with_polymorphic_mappers
         self._polymorphic_discriminator = \
                 ext_info.polymorphic_on
+        self.entity_zero = ext_info
         if ext_info.is_aliased_class:
-            self.entity_zero = ext_info.entity
-            self._label_name = self.entity_zero._sa_label_name
+            self._label_name = self.entity_zero.name
         else:
-            self.entity_zero = self.mapper
             self._label_name = self.mapper.class_.__name__
-        self.path = self.entity_zero._sa_path_registry
+        self.path = self.entity_zero._path_registry
 
     def set_with_polymorphic(self, query, cls_or_mappers,
                                 selectable, polymorphic_on):
+        """Receive an update from a call to query.with_polymorphic().
+
+        Note the newer style of using a free standing with_polymporphic()
+        construct doesn't make use of this method.
+
+
+        """
         if self.is_aliased_class:
+            # TODO: invalidrequest ?
             raise NotImplementedError(
                         "Can't use with_polymorphic() against "
                         "an Aliased object"
@@ -2785,13 +2792,18 @@ class _MapperEntity(_QueryEntity):
         return self.entity_zero
 
     def corresponds_to(self, entity):
-        entity_info = inspect(entity)
-        if entity_info.is_aliased_class or self.is_aliased_class:
-            return entity is self.entity_zero \
-                or \
-                entity in self._with_polymorphic
-        else:
-            return entity.common_parent(self.entity_zero)
+        if entity.is_aliased_class:
+            if self.is_aliased_class:
+                if entity._base_alias is self.entity_zero._base_alias:
+                    return True
+            return False
+        elif self.is_aliased_class:
+            if self.entity_zero._use_mapper_path:
+                return entity in self._with_polymorphic
+            else:
+                return entity is self.entity_zero
+
+        return entity.common_parent(self.entity_zero)
 
     def adapt_to_selectable(self, query, sel):
         query._entities.append(self)
@@ -3008,6 +3020,7 @@ class _ColumnEntity(_QueryEntity):
         if self.entity_zero is None:
             return False
         elif _is_aliased_class(entity):
+            # TODO: polymorphic subclasses ?
             return entity is self.entity_zero
         else:
             return not _is_aliased_class(self.entity_zero) and \
index 586ec4b4e9e01903d30f49234fee345634bf17fc..05c7ef37bd19b985d58f6a4ab816eb07edf10bcd 100644 (file)
@@ -303,16 +303,6 @@ class AbstractRelationshipLoader(LoaderStrategy):
         self.uselist = self.parent_property.uselist
 
 
-    def _warn_existing_path(self):
-        raise sa_exc.InvalidRequestError(
-            "Eager loading cannot currently function correctly when two or "
-            "more "
-            "same-named attributes associated with multiple polymorphic "
-            "classes "
-            "of the same base are present.   Encountered more than one "
-            r"eager path for attribute '%s' on mapper '%s'." %
-            (self.key, self.parent.base_mapper, ))
-
 
 class NoLoader(AbstractRelationshipLoader):
     """Provide loading behavior for a :class:`.RelationshipProperty`
@@ -564,7 +554,7 @@ class LazyLoader(AbstractRelationshipLoader):
             q = q.autoflush(False)
 
         if state.load_path:
-            q = q._with_current_path(state.load_path[self.key])
+            q = q._with_current_path(state.load_path[self.parent_property])
 
         if state.load_options:
             q = q._conditional_options(*state.load_options)
@@ -694,7 +684,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
         if not context.query._enable_eagerloads:
             return
 
-        path = path[self.key]
+        path = path[self.parent_property]
 
         # build up a path indicating the path from the leftmost
         # entity to the thing we're subquery loading.
@@ -757,22 +747,20 @@ class SubqueryLoader(AbstractRelationshipLoader):
 
         # add new query to attributes to be picked up
         # by create_row_processor
-        existing = path.replace(context, "subquery", q)
-        if existing:
-            self._warn_existing_path()
+        path.set(context, "subquery", q)
 
     def _get_leftmost(self, subq_path):
         subq_path = subq_path.path
         subq_mapper = orm_util._class_to_mapper(subq_path[0])
 
         # determine attributes of the leftmost mapper
-        if self.parent.isa(subq_mapper) and self.key == subq_path[1]:
+        if self.parent.isa(subq_mapper) and self.parent_property is subq_path[1]:
             leftmost_mapper, leftmost_prop = \
                                     self.parent, self.parent_property
         else:
             leftmost_mapper, leftmost_prop = \
                                     subq_mapper, \
-                                    subq_mapper._props[subq_path[1]]
+                                    subq_path[1]
 
         leftmost_cols = leftmost_prop.local_columns
 
@@ -805,23 +793,35 @@ class SubqueryLoader(AbstractRelationshipLoader):
         # the original query now becomes a subquery
         # which we'll join onto.
         embed_q = q.with_labels().subquery()
-        left_alias = orm_util.AliasedClass(leftmost_mapper, embed_q)
+        left_alias = orm_util.AliasedClass(leftmost_mapper, embed_q,
+                            use_mapper_path=True)
         return left_alias
 
     def _prep_for_joins(self, left_alias, subq_path):
-        subq_path = subq_path.path
-
         # figure out what's being joined.  a.k.a. the fun part
-        to_join = [
-                    (subq_path[i], subq_path[i + 1])
-                    for i in xrange(0, len(subq_path), 2)
-                ]
+        to_join = []
+        pairs = list(subq_path.pairs())
+
+        for i, (mapper, prop) in enumerate(pairs):
+            if i > 0:
+                # look at the previous mapper in the chain -
+                # if it is as or more specific than this prop's
+                # mapper, use that instead.
+                # note we have an assumption here that
+                # the non-first element is always going to be a mapper,
+                # not an AliasedClass
+
+                prev_mapper = pairs[i - 1][1].mapper
+                to_append = prev_mapper if prev_mapper.isa(mapper) else mapper
+            else:
+                to_append = mapper
+
+            to_join.append((to_append, prop.key))
 
         # determine the immediate parent class we are joining from,
         # which needs to be aliased.
-
         if len(to_join) > 1:
-            info = inspect(subq_path[-2])
+            info = inspect(to_join[-1][0])
 
         if len(to_join) < 2:
             # in the case of a one level eager load, this is the
@@ -833,11 +833,13 @@ class SubqueryLoader(AbstractRelationshipLoader):
             # in the vast majority of cases, and [ticket:2014]
             # illustrates a case where sub_path[-2] is a subclass
             # of self.parent
-            parent_alias = orm_util.AliasedClass(subq_path[-2])
+            parent_alias = orm_util.AliasedClass(to_join[-1][0],
+                                use_mapper_path=True)
         else:
             # if of_type() were used leading to this relationship,
             # self.parent is more specific than subq_path[-2]
-            parent_alias = orm_util.AliasedClass(self.parent)
+            parent_alias = orm_util.AliasedClass(self.parent,
+                                use_mapper_path=True)
 
         local_cols = self.parent_property.local_columns
 
@@ -916,9 +918,10 @@ class SubqueryLoader(AbstractRelationshipLoader):
                         "population - eager loading cannot be applied." %
                         self)
 
-        path = path[self.key]
+        path = path[self.parent_property]
 
         subq = path.get(context, 'subquery')
+
         if subq is None:
             return None, None, None
 
@@ -1000,7 +1003,7 @@ class JoinedLoader(AbstractRelationshipLoader):
         if not context.query._enable_eagerloads:
             return
 
-        path = path[self.key]
+        path = path[self.parent_property]
 
         with_polymorphic = None
 
@@ -1040,6 +1043,7 @@ class JoinedLoader(AbstractRelationshipLoader):
             with_polymorphic = None
 
         path = path[self.mapper]
+
         for value in self.mapper._iterate_polymorphic_properties(
                                 mappers=with_polymorphic):
             value.setup(
@@ -1079,7 +1083,8 @@ class JoinedLoader(AbstractRelationshipLoader):
         if with_poly_info:
             to_adapt = with_poly_info.entity
         else:
-            to_adapt = orm_util.AliasedClass(self.mapper)
+            to_adapt = orm_util.AliasedClass(self.mapper,
+                                use_mapper_path=True)
         clauses = orm_util.ORMAdapter(
                     to_adapt,
                     equivalents=self.mapper._equivalent_columns,
@@ -1104,9 +1109,8 @@ class JoinedLoader(AbstractRelationshipLoader):
         )
 
         add_to_collection = context.secondary_columns
-        existing = path.replace(context, "eager_row_processor", clauses)
-        if existing:
-            self._warn_existing_path()
+        path.set(context, "eager_row_processor", clauses)
+
         return clauses, adapter, add_to_collection, allow_innerjoin
 
     def _create_eager_join(self, context, entity,
@@ -1154,7 +1158,8 @@ class JoinedLoader(AbstractRelationshipLoader):
                 onclause = getattr(
                                 orm_util.AliasedClass(
                                         self.parent,
-                                        adapter.selectable
+                                        adapter.selectable,
+                                        use_mapper_path=True
                                 ),
                                 self.key, self.parent_property
                             )
@@ -1238,7 +1243,7 @@ class JoinedLoader(AbstractRelationshipLoader):
                         "population - eager loading cannot be applied." %
                         self)
 
-        our_path = path[self.key]
+        our_path = path[self.parent_property]
 
         eager_adapter = self._create_eager_adapter(
                                                 context,
@@ -1391,15 +1396,13 @@ class LoadEagerFromAliasOption(PropertyOption):
     def process_query_property(self, query, paths):
         if self.chained:
             for path in paths[0:-1]:
-                (root_mapper, propname) = path.path[-2:]
-                prop = root_mapper._props[propname]
+                (root_mapper, prop) = path.path[-2:]
                 adapter = query._polymorphic_adapters.get(prop.mapper, None)
                 path.setdefault(query,
                             "user_defined_eager_row_processor",
                             adapter)
 
-        root_mapper, propname = paths[-1].path[-2:]
-        prop = root_mapper._props[propname]
+        root_mapper, prop = paths[-1].path[-2:]
         if self.alias is not None:
             if isinstance(self.alias, basestring):
                 self.alias = prop.target.alias(self.alias)
index e5e725138cb200d173f5f84c7a72eb9693f2c3b1..04838cb64d35b2785b4e2573bbc8974acb2ca6b6 100644 (file)
@@ -245,6 +245,8 @@ class ORMAdapter(sql_util.ColumnAdapter):
         else:
             return None
 
+def _unreduce_path(path):
+    return PathRegistry.deserialize(path)
 
 class PathRegistry(object):
     """Represent query load paths and registry functions.
@@ -277,19 +279,13 @@ class PathRegistry(object):
             self.path == other.path
 
     def set(self, reg, key, value):
-        reg._attributes[(key, self.reduced_path)] = value
-
-    def replace(self, reg, key, value):
-        path_key = (key, self.reduced_path)
-        existing = reg._attributes.get(path_key, None)
-        reg._attributes[path_key] = value
-        return existing
+        reg._attributes[(key, self.path)] = value
 
     def setdefault(self, reg, key, value):
-        reg._attributes.setdefault((key, self.reduced_path), value)
+        reg._attributes.setdefault((key, self.path), value)
 
     def get(self, reg, key, value=None):
-        key = (key, self.reduced_path)
+        key = (key, self.path)
         if key in reg._attributes:
             return reg._attributes[key]
         else:
@@ -302,17 +298,25 @@ class PathRegistry(object):
     def length(self):
         return len(self.path)
 
+    def pairs(self):
+        path = self.path
+        for i in xrange(0, len(path), 2):
+            yield path[i], path[i + 1]
+
     def contains_mapper(self, mapper):
         return mapper in self.path
 
     def contains(self, reg, key):
-        return (key, self.reduced_path) in reg._attributes
+        return (key, self.path) in reg._attributes
+
+    def __reduce__(self):
+        return _unreduce_path, (self.serialize(), )
 
     def serialize(self):
         path = self.path
         return zip(
             [m.class_ for m in [path[i] for i in range(0, len(path), 2)]],
-            [path[i] for i in range(1, len(path), 2)] + [None]
+            [path[i].key for i in range(1, len(path), 2)] + [None]
         )
 
     @classmethod
@@ -320,7 +324,10 @@ class PathRegistry(object):
         if path is None:
             return None
 
-        p = tuple(chain(*[(class_mapper(mcls), key) for mcls, key in path]))
+        p = tuple(chain(*[(class_mapper(mcls),
+                            class_mapper(mcls).attrs[key]
+                                if key is not None else None)
+                            for mcls, key in path]))
         if p and p[-1] is None:
             p = p[0:-1]
         return cls.coerce(p)
@@ -337,7 +344,7 @@ class PathRegistry(object):
 
     @classmethod
     def token(cls, token):
-        return KeyRegistry(cls.root, token)
+        return TokenRegistry(cls.root, token)
 
     def __add__(self, other):
         return util.reduce(
@@ -354,19 +361,36 @@ class RootRegistry(PathRegistry):
 
     """
     path = ()
-    reduced_path = ()
 
-    def __getitem__(self, mapper):
-        return mapper._sa_path_registry
+    def __getitem__(self, entity):
+        return entity._path_registry
 PathRegistry.root = RootRegistry()
 
+class TokenRegistry(PathRegistry):
+    def __init__(self, parent, token):
+        self.token = token
+        self.parent = parent
+        self.path = parent.path + (token,)
 
-class KeyRegistry(PathRegistry):
-    def __init__(self, parent, key):
-        self.key = key
+    def __getitem__(self, entity):
+        raise NotImplementedError()
+
+class PropRegistry(PathRegistry):
+    def __init__(self, parent, prop):
+        # restate this path in terms of the
+        # given MapperProperty's parent.
+        insp = inspection.inspect(parent[-1])
+        if not insp.is_aliased_class or insp._use_mapper_path:
+            parent = parent.parent[prop.parent]
+        elif insp.is_aliased_class and insp.with_polymorphic_mappers:
+            if prop.parent is not insp.mapper and \
+                prop.parent in insp.with_polymorphic_mappers:
+                subclass_entity = parent[-1]._entity_for_mapper(prop.parent)
+                parent = parent.parent[subclass_entity]
+
+        self.prop = prop
         self.parent = parent
-        self.path = parent.path + (key,)
-        self.reduced_path = parent.reduced_path + (key,)
+        self.path = parent.path + (prop,)
 
     def __getitem__(self, entity):
         if isinstance(entity, (int, slice)):
@@ -381,15 +405,11 @@ class EntityRegistry(PathRegistry, dict):
     is_aliased_class = False
 
     def __init__(self, parent, entity):
-        self.key = reduced_key = entity
+        self.key = entity
         self.parent = parent
-        if hasattr(entity, 'base_mapper'):
-            reduced_key = entity.base_mapper
-        else:
-            self.is_aliased_class = True
+        self.is_aliased_class = entity.is_aliased_class
 
         self.path = parent.path + (entity,)
-        self.reduced_path = parent.reduced_path + (reduced_key,)
 
     def __nonzero__(self):
         return True
@@ -400,8 +420,26 @@ class EntityRegistry(PathRegistry, dict):
         else:
             return dict.__getitem__(self, entity)
 
+    def _inlined_get_for(self, prop, context, key):
+        """an inlined version of:
+
+        cls = path[mapperproperty].get(context, key)
+
+        Skips the isinstance() check in __getitem__
+        and the extra method call for get().
+        Used by StrategizedProperty for its
+        very frequent lookup.
+
+        """
+        path = dict.__getitem__(self, prop)
+        path_key = (key, path.path)
+        if path_key in context._attributes:
+            return context._attributes[path_key]
+        else:
+            return None
+
     def __missing__(self, key):
-        self[key] = item = KeyRegistry(self, key)
+        self[key] = item = PropRegistry(self, key)
         return item
 
 
@@ -448,8 +486,11 @@ class AliasedClass(object):
     def __init__(self, cls, alias=None,
                             name=None,
                             adapt_on_names=False,
+                            #  TODO: None for default here?
                             with_polymorphic_mappers=(),
-                            with_polymorphic_discriminator=None):
+                            with_polymorphic_discriminator=None,
+                            base_alias=None,
+                            use_mapper_path=False):
         mapper = _class_to_mapper(cls)
         if alias is None:
             alias = mapper._with_polymorphic_selectable.alias(name=name)
@@ -458,11 +499,19 @@ class AliasedClass(object):
             mapper,
             alias,
             name,
-            with_polymorphic_mappers,
+            with_polymorphic_mappers
+                if with_polymorphic_mappers
+                else mapper.with_polymorphic_mappers,
             with_polymorphic_discriminator
+                if with_polymorphic_discriminator is not None
+                else mapper.polymorphic_on,
+            base_alias,
+            use_mapper_path
         )
+
         self._setup(self._aliased_insp, adapt_on_names)
 
+
     def _setup(self, aliased_insp, adapt_on_names):
         self.__adapt_on_names = adapt_on_names
         mapper = aliased_insp.mapper
@@ -473,18 +522,13 @@ class AliasedClass(object):
                             equivalents=mapper._equivalent_columns,
                             adapt_on_names=self.__adapt_on_names)
         for poly in aliased_insp.with_polymorphic_mappers:
-            setattr(self, poly.class_.__name__,
-                    AliasedClass(poly.class_, alias))
+            if poly is not mapper:
+                setattr(self, poly.class_.__name__,
+                    AliasedClass(poly.class_, alias, base_alias=self,
+                            use_mapper_path=self._aliased_insp._use_mapper_path))
 
-        # used to assign a name to the RowTuple object
-        # returned by Query.
-        self._sa_label_name = aliased_insp.name
         self.__name__ = 'AliasedClass_%s' % self.__target.__name__
 
-    @util.memoized_property
-    def _sa_path_registry(self):
-        return PathRegistry.per_mapper(self)
-
     def __getstate__(self):
         return {
             'mapper': self._aliased_insp.mapper,
@@ -494,7 +538,9 @@ class AliasedClass(object):
             'with_polymorphic_mappers':
                 self._aliased_insp.with_polymorphic_mappers,
             'with_polymorphic_discriminator':
-                self._aliased_insp.polymorphic_on
+                self._aliased_insp.polymorphic_on,
+            'base_alias': self._aliased_insp._base_alias.entity,
+            'use_mapper_path': self._aliased_insp._use_mapper_path
         }
 
     def __setstate__(self, state):
@@ -503,8 +549,10 @@ class AliasedClass(object):
             state['mapper'],
             state['alias'],
             state['name'],
-            state.get('with_polymorphic_mappers'),
-            state.get('with_polymorphic_discriminator')
+            state['with_polymorphic_mappers'],
+            state['with_polymorphic_discriminator'],
+            state['base_alias'],
+            state['use_mapper_path']
         )
         self._setup(self._aliased_insp, state['adapt_on_names'])
 
@@ -521,7 +569,7 @@ class AliasedClass(object):
         queryattr = attributes.QueryableAttribute(
                                 self, key,
                                 impl=existing.impl,
-                                parententity=self,
+                                parententity=self._aliased_insp,
                                 comparator=comparator)
         setattr(self, key, queryattr)
         return queryattr
@@ -558,17 +606,7 @@ class AliasedClass(object):
             id(self), self.__target.__name__)
 
 
-AliasedInsp = util.namedtuple("AliasedInsp", [
-        "entity",
-        "mapper",
-        "selectable",
-        "name",
-        "with_polymorphic_mappers",
-        "polymorphic_on"
-    ])
-
-
-class AliasedInsp(_InspectionAttr, AliasedInsp):
+class AliasedInsp(_InspectionAttr):
     """Provide an inspection interface for an
     :class:`.AliasedClass` object.
 
@@ -604,6 +642,22 @@ class AliasedInsp(_InspectionAttr, AliasedInsp):
 
     """
 
+    def __init__(self, entity, mapper, selectable, name,
+                    with_polymorphic_mappers, polymorphic_on,
+                    _base_alias, _use_mapper_path):
+        self.entity = entity
+        self.mapper = mapper
+        self.selectable = selectable
+        self.name = name
+        self.with_polymorphic_mappers = with_polymorphic_mappers
+        self.polymorphic_on = polymorphic_on
+
+        # a little dance to get serialization to work
+        self._base_alias = _base_alias._aliased_insp if _base_alias \
+                            and _base_alias is not entity else self
+        self._use_mapper_path = _use_mapper_path
+
+
     is_aliased_class = True
     "always returns True"
 
@@ -613,8 +667,29 @@ class AliasedInsp(_InspectionAttr, AliasedInsp):
         :class:`.AliasedInsp`."""
         return self.mapper.class_
 
+    @util.memoized_property
+    def _path_registry(self):
+        if self._use_mapper_path:
+            return self.mapper._path_registry
+        else:
+            return PathRegistry.per_mapper(self)
+
+    def _entity_for_mapper(self, mapper):
+        self_poly = self.with_polymorphic_mappers
+        if mapper in self_poly:
+            return getattr(self.entity, mapper.class_.__name__)._aliased_insp
+        elif mapper.isa(self.mapper):
+            return self
+        else:
+            assert False, "mapper %s doesn't correspond to %s" % (mapper, self)
+
+    def __repr__(self):
+        return '<AliasedInsp at 0x%x; %s>' % (
+            id(self), self.class_.__name__)
+
 
 inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
+inspection._inspects(AliasedInsp)(lambda target: target)
 
 
 def aliased(element, alias=None, name=None, adapt_on_names=False):
@@ -699,7 +774,7 @@ def aliased(element, alias=None, name=None, adapt_on_names=False):
 
 def with_polymorphic(base, classes, selectable=False,
                         polymorphic_on=None, aliased=False,
-                        innerjoin=False):
+                        innerjoin=False, _use_mapper_path=False):
     """Produce an :class:`.AliasedClass` construct which specifies
     columns for descendant mappers of the given base.
 
@@ -758,7 +833,8 @@ def with_polymorphic(base, classes, selectable=False,
     return AliasedClass(base,
                 selectable,
                 with_polymorphic_mappers=mappers,
-                with_polymorphic_discriminator=polymorphic_on)
+                with_polymorphic_discriminator=polymorphic_on,
+                use_mapper_path=_use_mapper_path)
 
 
 def _orm_annotate(element, exclude=None):
@@ -1109,6 +1185,7 @@ def _entity_descriptor(entity, key):
         description = entity
         entity = insp.c
     elif insp.is_aliased_class:
+        entity = insp.entity
         description = entity
     elif hasattr(insp, "mapper"):
         description = entity = insp.mapper.class_
index fca0635d244d7bdc9eb13a2c09b459664a5f76ca..54c7d7ecc3ba8412ee35ffe7f4087486337316a1 100644 (file)
@@ -426,12 +426,14 @@ class MemUsageTest(EnsureZeroed):
         metadata = MetaData()
         a = Table("a", metadata,
             Column('id', Integer, primary_key=True),
+            Column('foo', Integer),
+            Column('bar', Integer)
         )
         m1 = mapper(A, a)
         @profile_memory()
         def go():
-            ma = aliased(A)
-            m1._sa_path_registry['foo'][ma]['bar']
+            ma = sa.inspect(aliased(A))
+            m1._path_registry[m1.attrs.foo][ma][m1.attrs.bar]
         go()
         clear_mappers()
 
index 8bbde972ddaef895523fd44e3b6aba330a6116ca..e22848912f3450bcf2ed1f0d81835048c41fe227 100644 (file)
@@ -496,41 +496,10 @@ class _PolymorphicTestBase(object):
                 .all(),
             expected)
 
-    # TODO: this fails due to the change
-    # in _configure_subclass_mapper.  however we might not
-    # need it anymore.
-    def test_polymorphic_option(self):
-        """
-        Test that polymorphic loading sets state.load_path with its
-        actual mapper on a subclass, and not the superclass mapper.
-
-        This only works for non-aliased mappers.
-        """
-        paths = []
-        class MyOption(interfaces.MapperOption):
-            propagate_to_loaders = True
-            def process_query_conditionally(self, query):
-                paths.append(query._current_path.path)
-
-        sess = create_session()
-        names = ['dilbert', 'pointy haired boss']
-        dilbert, boss = (
-            sess.query(Person)
-                .options(MyOption())
-                .filter(Person.name.in_(names))
-                .order_by(Person.name).all())
-
-        dilbert.machines
-        boss.paperwork
-
-        eq_(paths,
-            [(class_mapper(Engineer), 'machines'),
-            (class_mapper(Boss), 'paperwork')])
 
     def test_subclass_option_pathing(self):
         from sqlalchemy.orm import defer
         sess = create_session()
-        names = ['dilbert', 'pointy haired boss']
         dilbert = sess.query(Person).\
                 options(defer(Engineer.machines, Machine.name)).\
                 filter(Person.name == 'dilbert').first()
@@ -963,7 +932,7 @@ class _PolymorphicTestBase(object):
                 .filter(palias.name.in_(['dilbert', 'wally'])).all(),
             [e1, e2])
 
-    def test_self_referential(self):
+    def test_self_referential_one(self):
         sess = create_session()
         palias = aliased(Person)
         expected = [(m1, e1), (m1, e2), (m1, b1)]
@@ -975,6 +944,11 @@ class _PolymorphicTestBase(object):
                 .order_by(Person.person_id, palias.person_id).all(),
             expected)
 
+    def test_self_referential_two(self):
+        sess = create_session()
+        palias = aliased(Person)
+        expected = [(m1, e1), (m1, e2), (m1, b1)]
+
         eq_(sess.query(Person, palias)
                 .filter(Person.company_id == palias.company_id)
                 .filter(Person.name == 'dogbert')
index a630e2f3de346200789dfe4cbf94e68d7bf25b3d..509d540efbd2a0d0efc190116b7416860c816fdd 100644 (file)
@@ -1,6 +1,6 @@
 from sqlalchemy.orm import create_session, relationship, mapper, \
     contains_eager, joinedload, subqueryload, subqueryload_all,\
-    Session, aliased
+    Session, aliased, with_polymorphic
 
 from sqlalchemy import Integer, String, ForeignKey
 from sqlalchemy.engine import default
@@ -717,15 +717,29 @@ class EagerToSubclassTest(fixtures.MappedTest):
 
     def test_subq_through_related(self):
         Parent = self.classes.Parent
-        Sub = self.classes.Sub
+        Base = self.classes.Base
         sess = Session()
+
         def go():
             eq_(sess.query(Parent)
-                    .options(subqueryload_all(Parent.children, Sub.related))
+                    .options(subqueryload_all(Parent.children, Base.related))
                     .order_by(Parent.data).all(),
                 [p1, p2])
         self.assert_sql_count(testing.db, go, 3)
 
+    def test_subq_through_related_aliased(self):
+        Parent = self.classes.Parent
+        Base = self.classes.Base
+        pa = aliased(Parent)
+        sess = Session()
+
+        def go():
+            eq_(sess.query(pa)
+                    .options(subqueryload_all(pa.children, Base.related))
+                    .order_by(pa.data).all(),
+                [p1, p2])
+        self.assert_sql_count(testing.db, go, 3)
+
 class SubClassEagerToSubClassTest(fixtures.MappedTest):
     """Test joinedloads from subclass to subclass mappers"""
 
@@ -876,3 +890,226 @@ class SubClassEagerToSubClassTest(fixtures.MappedTest):
                 [p1, p2])
         self.assert_sql_count(testing.db, go, 2)
 
+class SameNamedPropTwoPolymorphicSubClassesTest(fixtures.MappedTest):
+    """test pathing when two subclasses contain a different property
+    for the same name, and polymorphic loading is used.
+
+    #2614
+
+    """
+    run_setup_classes = 'once'
+    run_setup_mappers = 'once'
+    run_inserts = 'once'
+    run_deletes = None
+
+    @classmethod
+    def define_tables(cls, metadata):
+        Table('a', metadata,
+            Column('id', Integer, primary_key=True,
+                    test_needs_autoincrement=True),
+            Column('type', String(10))
+        )
+        Table('b', metadata,
+            Column('id', Integer, ForeignKey('a.id'), primary_key=True)
+        )
+        Table('btod', metadata,
+            Column('bid', Integer, ForeignKey('b.id'), nullable=False),
+            Column('did', Integer, ForeignKey('d.id'), nullable=False)
+        )
+        Table('c', metadata,
+            Column('id', Integer, ForeignKey('a.id'), primary_key=True)
+        )
+        Table('ctod', metadata,
+            Column('cid', Integer, ForeignKey('c.id'), nullable=False),
+            Column('did', Integer, ForeignKey('d.id'), nullable=False)
+        )
+        Table('d', metadata,
+            Column('id', Integer, primary_key=True,
+                        test_needs_autoincrement=True)
+        )
+
+    @classmethod
+    def setup_classes(cls):
+        class A(cls.Comparable):
+            pass
+        class B(A):
+            pass
+        class C(A):
+            pass
+        class D(cls.Comparable):
+            pass
+
+    @classmethod
+    def setup_mappers(cls):
+        A = cls.classes.A
+        B = cls.classes.B
+        C = cls.classes.C
+        D = cls.classes.D
+
+        mapper(A, cls.tables.a, polymorphic_on=cls.tables.a.c.type)
+        mapper(B, cls.tables.b, inherits=A, polymorphic_identity='b',
+                    properties={
+                        'related': relationship(D, secondary=cls.tables.btod)
+                    })
+        mapper(C, cls.tables.c, inherits=A, polymorphic_identity='c',
+                    properties={
+                        'related': relationship(D, secondary=cls.tables.ctod)
+                    })
+        mapper(D, cls.tables.d)
+
+
+    @classmethod
+    def insert_data(cls):
+        B = cls.classes.B
+        C = cls.classes.C
+        D = cls.classes.D
+
+        session = Session()
+
+        d = D()
+        session.add_all([
+            B(related=[d]),
+            C(related=[d])
+        ])
+        session.commit()
+
+    def test_free_w_poly_subquery(self):
+        A = self.classes.A
+        B = self.classes.B
+        C = self.classes.C
+        D = self.classes.D
+
+        session = Session()
+        d = session.query(D).one()
+        a_poly = with_polymorphic(A, [B, C])
+        def go():
+            for a in session.query(a_poly).\
+                options(
+                        subqueryload(a_poly.B.related),
+                        subqueryload(a_poly.C.related)):
+                eq_(a.related, [d])
+        self.assert_sql_count(testing.db, go, 3)
+
+    def test_fixed_w_poly_subquery(self):
+        A = self.classes.A
+        B = self.classes.B
+        C = self.classes.C
+        D = self.classes.D
+
+        session = Session()
+        d = session.query(D).one()
+        def go():
+            for a in session.query(A).with_polymorphic([B, C]).\
+                options(subqueryload(B.related), subqueryload(C.related)):
+                eq_(a.related, [d])
+        self.assert_sql_count(testing.db, go, 3)
+
+    def test_free_w_poly_joined(self):
+        A = self.classes.A
+        B = self.classes.B
+        C = self.classes.C
+        D = self.classes.D
+
+        session = Session()
+        d = session.query(D).one()
+        a_poly = with_polymorphic(A, [B, C])
+        def go():
+            for a in session.query(a_poly).\
+                options(
+                        joinedload(a_poly.B.related),
+                        joinedload(a_poly.C.related)):
+                eq_(a.related, [d])
+        self.assert_sql_count(testing.db, go, 1)
+
+    def test_fixed_w_poly_joined(self):
+        A = self.classes.A
+        B = self.classes.B
+        C = self.classes.C
+        D = self.classes.D
+
+        session = Session()
+        d = session.query(D).one()
+        def go():
+            for a in session.query(A).with_polymorphic([B, C]).\
+                options(joinedload(B.related), joinedload(C.related)):
+                eq_(a.related, [d])
+        self.assert_sql_count(testing.db, go, 1)
+
+
+class SubClassToSubClassFromParentTest(fixtures.MappedTest):
+    """test #2617
+
+    """
+    run_setup_classes = 'once'
+    run_setup_mappers = 'once'
+    run_inserts = 'once'
+    run_deletes = None
+
+    @classmethod
+    def define_tables(cls, metadata):
+        Table('z', metadata,
+            Column('id', Integer, primary_key=True,
+                        test_needs_autoincrement=True)
+        )
+        Table('a', metadata,
+            Column('id', Integer, primary_key=True,
+                    test_needs_autoincrement=True),
+            Column('type', String(10)),
+            Column('z_id', Integer, ForeignKey('z.id'))
+        )
+        Table('b', metadata,
+            Column('id', Integer, ForeignKey('a.id'), primary_key=True)
+        )
+        Table('d', metadata,
+            Column('id', Integer, ForeignKey('a.id'), primary_key=True),
+            Column('b_id', Integer, ForeignKey('b.id'))
+        )
+
+    @classmethod
+    def setup_classes(cls):
+        class Z(cls.Comparable):
+            pass
+        class A(cls.Comparable):
+            pass
+        class B(A):
+            pass
+        class D(A):
+            pass
+
+    @classmethod
+    def setup_mappers(cls):
+        Z = cls.classes.Z
+        A = cls.classes.A
+        B = cls.classes.B
+        D = cls.classes.D
+
+        mapper(Z, cls.tables.z)
+        mapper(A, cls.tables.a, polymorphic_on=cls.tables.a.c.type,
+                    with_polymorphic='*',
+                    properties={
+                        'zs': relationship(Z, lazy="subquery")
+                    })
+        mapper(B, cls.tables.b, inherits=A, polymorphic_identity='b',
+                    properties={
+                        'related': relationship(D, lazy="subquery",
+                            primaryjoin=cls.tables.d.c.b_id ==
+                                                cls.tables.b.c.id)
+                    })
+        mapper(D, cls.tables.d, inherits=A, polymorphic_identity='d')
+
+
+    @classmethod
+    def insert_data(cls):
+        B = cls.classes.B
+
+        session = Session()
+        session.add(B())
+        session.commit()
+
+    def test_2617(self):
+        A = self.classes.A
+        session = Session()
+        def go():
+            a1 = session.query(A).first()
+            eq_(a1.related, [])
+        self.assert_sql_count(testing.db, go, 3)
index 08a693b92f1bb779b4d0ba8b281cea160b24a09b..de6e55e95653247b8546d78ddab4b01b6e1e6747 100644 (file)
@@ -278,7 +278,8 @@ class RelationshipFromSingleTest(testing.AssertsCompiledSQL, fixtures.MappedTest
 
         sess = create_session()
         context = sess.query(Manager).options(subqueryload('stuff'))._compile_context()
-        subq = context.attributes[('subquery', (class_mapper(Employee), 'stuff'))]
+        subq = context.attributes[('subquery',
+                (class_mapper(Manager), class_mapper(Manager).attrs.stuff))]
 
         self.assert_compile(subq,
                             'SELECT employee_stuff.id AS '
index ae3853e75fde2de3860737d5181bc227bb310106..2c59491b1768f15ff1c8d5732a3fa99fb774fdc2 100644 (file)
@@ -311,6 +311,8 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
         })
         mapper(Keyword, keywords)
 
+
+
         for opt, count in [
             ((
                 joinedload(User.orders, Order.items),
@@ -2662,128 +2664,3 @@ class CyclicalInheritingEagerTestTwo(fixtures.DeclarativeMappedTest,
         assert len(list(session)) == 3
 
 
-
-class WarnFor2614TestBase(object):
-
-    @classmethod
-    def define_tables(cls, metadata):
-        Table('a', metadata,
-            Column('id', Integer, primary_key=True),
-            Column('type', String(50)),
-        )
-        Table('b', metadata,
-            Column('id', Integer, ForeignKey('a.id'), primary_key=True),
-        )
-        Table('c', metadata,
-            Column('id', Integer, ForeignKey('a.id'), primary_key=True),
-        )
-        Table('d', metadata,
-            Column('id', Integer, primary_key=True),
-            Column('bid', Integer, ForeignKey('b.id')),
-            Column('cid', Integer, ForeignKey('c.id')),
-        )
-
-    def _mapping(self, lazy_b=True, lazy_c=True):
-        class A(object):
-            pass
-
-        class B(A):
-            pass
-
-        class C(A):
-            pass
-
-        class D(object):
-            pass
-
-        mapper(A, self.tables.a, polymorphic_on=self.tables.a.c.type)
-        mapper(B, self.tables.b, inherits=A, polymorphic_identity='b',
-                    properties={
-                        'ds': relationship(D, lazy=lazy_b)
-                    })
-        mapper(C, self.tables.c, inherits=A, polymorphic_identity='c',
-                    properties={
-                        'ds': relationship(D, lazy=lazy_c)
-                    })
-        mapper(D, self.tables.d)
-
-
-        return  A, B, C, D
-
-    def _assert_raises(self, fn):
-        assert_raises_message(
-            sa.exc.InvalidRequestError,
-            "Eager loading cannot currently function correctly when two or more "
-            r"same\-named attributes associated with multiple polymorphic classes "
-            "of the same base are present.   Encountered more than one "
-            r"eager path for attribute 'ds' on mapper 'Mapper\|A\|a'.",
-            fn
-        )
-
-    def test_poly_both_eager(self):
-        A, B, C, D = self._mapping(lazy_b=self.eager_name,
-                                    lazy_c=self.eager_name)
-
-        session = Session()
-        self._assert_raises(
-            session.query(A).with_polymorphic('*').all
-        )
-
-    def test_poly_one_eager(self):
-        A, B, C, D = self._mapping(lazy_b=self.eager_name, lazy_c=True)
-
-        session = Session()
-        session.query(A).with_polymorphic('*').all()
-
-    def test_poly_both_option(self):
-        A, B, C, D = self._mapping()
-
-        session = Session()
-        self._assert_raises(
-            session.query(A).with_polymorphic('*').options(
-                        self.eager_option(B.ds), self.eager_option(C.ds)).all
-        )
-
-    def test_poly_one_option_bs(self):
-        A, B, C, D = self._mapping()
-
-        session = Session()
-
-        # sucks, can't even do eager() on just one of them, as B.ds
-        # hits for both
-        self._assert_raises(
-            session.query(A).with_polymorphic('*').\
-                options(self.eager_option(B.ds)).all
-        )
-
-    def test_poly_one_option_cs(self):
-        A, B, C, D = self._mapping()
-
-        session = Session()
-
-        # sucks, can't even do eager() on just one of them, as B.ds
-        # hits for both
-        self._assert_raises(
-            session.query(A).with_polymorphic('*').\
-                options(self.eager_option(C.ds)).all
-        )
-
-    def test_single_poly_one_option_bs(self):
-        A, B, C, D = self._mapping()
-
-        session = Session()
-
-        session.query(A).with_polymorphic(B).\
-            options(self.eager_option(B.ds)).all()
-
-    def test_lazy_True(self):
-        A, B, C, D = self._mapping()
-
-        session = Session()
-        session.query(A).with_polymorphic('*').all()
-
-class WarnFor2614Test(WarnFor2614TestBase, fixtures.MappedTest):
-    eager_name = "joined"
-
-    def eager_option(self, arg):
-        return joinedload(arg)
index fc2d2181fe52ba0fd82e433d42a1166d4f23ea0f..fdf675183a6d8920d7cdf846a1f2c6398a790b7d 100644 (file)
@@ -113,7 +113,7 @@ class TestORMInspection(_fixtures.FixtureTest):
     def test_with_polymorphic(self):
         User = self.classes.User
         insp = inspect(User)
-        eq_(insp.with_polymorphic_mappers, [insp])
+        eq_(insp.with_polymorphic_mappers, [])
 
     def test_col_property(self):
         User = self.classes.User
@@ -198,7 +198,7 @@ class TestORMInspection(_fixtures.FixtureTest):
         is_(prop._parentmapper, class_mapper(User))
         is_(prop.mapper, class_mapper(Address))
 
-        is_(prop._parententity, ua)
+        is_(prop._parententity, inspect(ua))
 
     def test_insp_column_prop(self):
         User = self.classes.User
@@ -222,7 +222,7 @@ class TestORMInspection(_fixtures.FixtureTest):
 
         assert not hasattr(prop, "mapper")
 
-        is_(prop._parententity, ua)
+        is_(prop._parententity, inspect(ua))
 
     def test_rel_accessors(self):
         User = self.classes.User
index 5ef436b0ff56a88b9681b1e5574012fa630269f2..8fd38a680ead2768c18c1baaab28be3820b0708c 100644 (file)
@@ -1922,7 +1922,7 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
         )
 
 
-    def test_multiple_explicit_entities(self):
+    def test_multiple_explicit_entities_one(self):
         Node = self.classes.Node
 
         sess = create_session()
@@ -1938,6 +1938,13 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
             (Node(data='n122'), Node(data='n12'), Node(data='n1'))
         )
 
+    def test_multiple_explicit_entities_two(self):
+        Node = self.classes.Node
+
+        sess = create_session()
+
+        parent = aliased(Node)
+        grandparent = aliased(Node)
         eq_(
             sess.query(Node, parent, grandparent).\
                 join(parent, Node.parent).\
@@ -1947,6 +1954,13 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
             (Node(data='n122'), Node(data='n12'), Node(data='n1'))
         )
 
+    def test_multiple_explicit_entities_three(self):
+        Node = self.classes.Node
+
+        sess = create_session()
+
+        parent = aliased(Node)
+        grandparent = aliased(Node)
         # same, change order around
         eq_(
             sess.query(parent, grandparent, Node).\
@@ -1957,6 +1971,13 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
             (Node(data='n12'), Node(data='n1'), Node(data='n122'))
         )
 
+    def test_multiple_explicit_entities_four(self):
+        Node = self.classes.Node
+
+        sess = create_session()
+
+        parent = aliased(Node)
+        grandparent = aliased(Node)
         eq_(
             sess.query(Node, parent, grandparent).\
                 join(parent, Node.parent).\
@@ -1967,6 +1988,13 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
             (Node(data='n122'), Node(data='n12'), Node(data='n1'))
         )
 
+    def test_multiple_explicit_entities_five(self):
+        Node = self.classes.Node
+
+        sess = create_session()
+
+        parent = aliased(Node)
+        grandparent = aliased(Node)
         eq_(
             sess.query(Node, parent, grandparent).\
                 join(parent, Node.parent).\
index 1d7e0228cd77c22b00c1808d2c18878d06c333aa..8c5b9cd849d13f5082445d2ca36508e8663b2173 100644 (file)
@@ -3278,4 +3278,3 @@ class MagicNamesTest(fixtures.MappedTest):
                   reserved: maps.c.state})
 
 
-
index 9bf728de228fdba04697e0786650817ea98fd1d3..9aad19579d1c998d8102f75d41919a95540fe3e5 100644 (file)
@@ -2,6 +2,7 @@ import operator
 from sqlalchemy import MetaData, null, exists, text, union, literal, \
     literal_column, func, between, Unicode, desc, and_, bindparam, \
     select, distinct, or_, collate
+from sqlalchemy import inspect
 from sqlalchemy import exc as sa_exc, util
 from sqlalchemy.sql import compiler, table, column
 from sqlalchemy.sql import expression
@@ -2309,6 +2310,9 @@ class OptionsTest(QueryTest):
             if i % 2 == 0:
                 if isinstance(item, type):
                     item = class_mapper(item)
+            else:
+                if isinstance(item, basestring):
+                    item = inspect(r[-1]).mapper.attrs[item]
             r.append(item)
         return tuple(r)
 
@@ -2351,7 +2355,8 @@ class OptionsTest(QueryTest):
         q = sess.query(User)
         opt = self._option_fixture('email_address', 'id')
         q = sess.query(Address)._with_current_path(
-                orm_util.PathRegistry.coerce([class_mapper(User), 'addresses'])
+                orm_util.PathRegistry.coerce([inspect(User),
+                        inspect(User).attrs.addresses])
             )
         self._assert_path_result(opt, q, [])
 
@@ -2462,13 +2467,13 @@ class OptionsTest(QueryTest):
         class SubAddr(Address):
             pass
         mapper(SubAddr, inherits=Address, properties={
-            'flub':relationship(Dingaling)
+            'flub': relationship(Dingaling)
         })
 
         q = sess.query(Address)
         opt = self._option_fixture(SubAddr.flub)
 
-        self._assert_path_result(opt, q, [(Address, 'flub')])
+        self._assert_path_result(opt, q, [(SubAddr, 'flub')])
 
     def test_from_subclass_to_subclass_attr(self):
         Dingaling, Address = self.classes.Dingaling, self.classes.Address
@@ -2477,7 +2482,7 @@ class OptionsTest(QueryTest):
         class SubAddr(Address):
             pass
         mapper(SubAddr, inherits=Address, properties={
-            'flub':relationship(Dingaling)
+            'flub': relationship(Dingaling)
         })
 
         q = sess.query(SubAddr)
@@ -2492,13 +2497,14 @@ class OptionsTest(QueryTest):
         class SubAddr(Address):
             pass
         mapper(SubAddr, inherits=Address, properties={
-            'flub':relationship(Dingaling)
+            'flub': relationship(Dingaling)
         })
 
         q = sess.query(Address)
         opt = self._option_fixture(SubAddr.user)
 
-        self._assert_path_result(opt, q, [(Address, 'user')])
+        self._assert_path_result(opt, q,
+                [(Address, inspect(Address).attrs.user)])
 
     def test_of_type(self):
         User, Address = self.classes.User, self.classes.Address
@@ -2511,9 +2517,11 @@ class OptionsTest(QueryTest):
         q = sess.query(User)
         opt = self._option_fixture(User.addresses.of_type(SubAddr), SubAddr.user)
 
+        u_mapper = inspect(User)
+        a_mapper = inspect(Address)
         self._assert_path_result(opt, q, [
-            (User, 'addresses'),
-            (User, 'addresses', SubAddr, 'user')
+            (u_mapper, u_mapper.attrs.addresses),
+            (u_mapper, u_mapper.attrs.addresses, a_mapper, a_mapper.attrs.user)
         ])
 
     def test_of_type_plus_level(self):
@@ -2525,15 +2533,17 @@ class OptionsTest(QueryTest):
         class SubAddr(Address):
             pass
         mapper(SubAddr, inherits=Address, properties={
-            'flub':relationship(Dingaling)
+            'flub': relationship(Dingaling)
         })
 
         q = sess.query(User)
         opt = self._option_fixture(User.addresses.of_type(SubAddr), SubAddr.flub)
 
+        u_mapper = inspect(User)
+        sa_mapper = inspect(SubAddr)
         self._assert_path_result(opt, q, [
-            (User, 'addresses'),
-            (User, 'addresses', SubAddr, 'flub')
+            (u_mapper, u_mapper.attrs.addresses),
+            (u_mapper, u_mapper.attrs.addresses, sa_mapper, sa_mapper.attrs.flub)
         ])
 
     def test_aliased_single(self):
@@ -2543,7 +2553,7 @@ class OptionsTest(QueryTest):
         ualias = aliased(User)
         q = sess.query(ualias)
         opt = self._option_fixture(ualias.addresses)
-        self._assert_path_result(opt, q, [(ualias, 'addresses')])
+        self._assert_path_result(opt, q, [(inspect(ualias), 'addresses')])
 
     def test_with_current_aliased_single(self):
         User, Address = self.classes.User, self.classes.Address
@@ -2554,7 +2564,7 @@ class OptionsTest(QueryTest):
                         self._make_path_registry([Address, 'user'])
                 )
         opt = self._option_fixture(Address.user, ualias.addresses)
-        self._assert_path_result(opt, q, [(ualias, 'addresses')])
+        self._assert_path_result(opt, q, [(inspect(ualias), 'addresses')])
 
     def test_with_current_aliased_single_nonmatching_option(self):
         User, Address = self.classes.User, self.classes.Address
@@ -2828,8 +2838,8 @@ class OptionsNoPropTest(_fixtures.FixtureTest):
                     cls.tables.addresses, cls.classes.Address,
                     cls.tables.orders, cls.classes.Order)
         mapper(User, users, properties={
-            'addresses':relationship(Address),
-            'orders':relationship(Order)
+            'addresses': relationship(Address),
+            'orders': relationship(Order)
         })
         mapper(Address, addresses)
         mapper(Order, orders)
@@ -2839,7 +2849,7 @@ class OptionsNoPropTest(_fixtures.FixtureTest):
                                 cls.classes.Keyword,
                                 cls.classes.Item)
         mapper(Keyword, keywords, properties={
-            "keywords":column_property(keywords.c.name + "some keyword")
+            "keywords": column_property(keywords.c.name + "some keyword")
         })
         mapper(Item, items,
                properties=dict(keywords=relationship(Keyword,
@@ -2850,7 +2860,7 @@ class OptionsNoPropTest(_fixtures.FixtureTest):
 
         q = create_session().query(*entity_list).\
                             options(joinedload(option))
-        key = ('loaderstrategy', (class_mapper(Item), 'keywords'))
+        key = ('loaderstrategy', (inspect(Item), inspect(Item).attrs.keywords))
         assert key in q._attributes
 
     def _assert_eager_with_entity_exception(self, entity_list, options,
@@ -2865,3 +2875,4 @@ class OptionsNoPropTest(_fixtures.FixtureTest):
         assert_raises_message(sa.exc.ArgumentError, message,
                               create_session().query(column).options,
                               joinedload(eager_option))
+
index 000f4abaf896ddceb15bb3a89b997aa651278e21..a4cc830ee6ab627416bb3ee04b72916affbb9b6a 100644 (file)
@@ -1226,24 +1226,24 @@ class InheritanceToRelatedTest(fixtures.MappedTest):
     @classmethod
     def fixtures(cls):
         return dict(
-            foo = [
+            foo=[
                 ('id', 'type', 'related_id'),
                 (1, 'bar', 1),
                 (2, 'bar', 2),
                 (3, 'baz', 1),
                 (4, 'baz', 2),
             ],
-            bar = [
+            bar=[
                 ('id', ),
                 (1,),
                 (2,)
             ],
-            baz = [
+            baz=[
                 ('id', ),
                 (3,),
                 (4,)
             ],
-            related = [
+            related=[
                 ('id', ),
                 (1,),
                 (2,)
@@ -1252,7 +1252,7 @@ class InheritanceToRelatedTest(fixtures.MappedTest):
     @classmethod
     def setup_mappers(cls):
         mapper(cls.classes.Foo, cls.tables.foo, properties={
-            'related':relationship(cls.classes.Related)
+            'related': relationship(cls.classes.Related)
         }, polymorphic_on=cls.tables.foo.c.type)
         mapper(cls.classes.Bar, cls.tables.bar, polymorphic_identity='bar',
                     inherits=cls.classes.Foo)
@@ -1260,22 +1260,43 @@ class InheritanceToRelatedTest(fixtures.MappedTest):
                     inherits=cls.classes.Foo)
         mapper(cls.classes.Related, cls.tables.related)
 
-    def test_caches_query_per_base(self):
+    def test_caches_query_per_base_subq(self):
         Foo, Bar, Baz, Related = self.classes.Foo, self.classes.Bar, \
                         self.classes.Baz, self.classes.Related
         s = Session(testing.db)
         def go():
             eq_(
-                s.query(Foo).with_polymorphic([Bar, Baz]).order_by(Foo.id).options(subqueryload(Foo.related)).all(),
+                s.query(Foo).with_polymorphic([Bar, Baz]).\
+                            order_by(Foo.id).\
+                            options(subqueryload(Foo.related)).all(),
                 [
-                    Bar(id=1,related=Related(id=1)),
-                    Bar(id=2,related=Related(id=2)),
-                    Baz(id=3,related=Related(id=1)),
-                    Baz(id=4,related=Related(id=2))
+                    Bar(id=1, related=Related(id=1)),
+                    Bar(id=2, related=Related(id=2)),
+                    Baz(id=3, related=Related(id=1)),
+                    Baz(id=4, related=Related(id=2))
                 ]
             )
         self.assert_sql_count(testing.db, go, 2)
 
+    def test_caches_query_per_base_joined(self):
+        # technically this should be in test_eager_relations
+        Foo, Bar, Baz, Related = self.classes.Foo, self.classes.Bar, \
+                        self.classes.Baz, self.classes.Related
+        s = Session(testing.db)
+        def go():
+            eq_(
+                s.query(Foo).with_polymorphic([Bar, Baz]).\
+                            order_by(Foo.id).\
+                            options(joinedload(Foo.related)).all(),
+                [
+                    Bar(id=1, related=Related(id=1)),
+                    Bar(id=2, related=Related(id=2)),
+                    Baz(id=3, related=Related(id=1)),
+                    Baz(id=4, related=Related(id=2))
+                ]
+            )
+        self.assert_sql_count(testing.db, go, 1)
+
 class CyclicalInheritingEagerTestOne(fixtures.MappedTest):
 
     @classmethod
@@ -1344,14 +1365,13 @@ class CyclicalInheritingEagerTestTwo(fixtures.DeclarativeMappedTest,
 
     def test_from_subclass(self):
         Director = self.classes.Director
-        PersistentObject = self.classes.PersistentObject
-
 
         s = create_session()
 
         ctx = s.query(Director).options(subqueryload('*'))._compile_context()
 
-        q = ctx.attributes[('subquery', (inspect(PersistentObject), 'movies'))]
+        q = ctx.attributes[('subquery',
+                        (inspect(Director), inspect(Director).attrs.movies))]
         self.assert_compile(q,
             "SELECT anon_1.movie_id AS anon_1_movie_id, "
             "anon_1.persistent_id AS anon_1_persistent_id, "
@@ -1384,10 +1404,3 @@ class CyclicalInheritingEagerTestTwo(fixtures.DeclarativeMappedTest,
         d = session.query(Director).options(subqueryload('*')).first()
         assert len(list(session)) == 3
 
-from . import test_eager_relations
-
-class WarnFor2614Test(test_eager_relations.WarnFor2614TestBase, fixtures.MappedTest):
-    eager_name = "subquery"
-
-    def eager_option(self, arg):
-        return subqueryload(arg)
index afe8fb5c1f25ca230e5fd49bb64ae7ceb606e830..b2853a8b83401cac85546a06ed8f756f4d91b723 100644 (file)
@@ -4,7 +4,7 @@ from sqlalchemy import Column
 from sqlalchemy import Integer
 from sqlalchemy import MetaData
 from sqlalchemy import Table
-from sqlalchemy.orm import aliased
+from sqlalchemy.orm import aliased, with_polymorphic
 from sqlalchemy.orm import mapper, create_session
 from sqlalchemy.testing import fixtures
 from test.orm import _fixtures
@@ -228,6 +228,7 @@ class IdentityKeyTest(_fixtures.FixtureTest):
         key = util.identity_key(User, row=row)
         eq_(key, (User, (1,)))
 
+
 class PathRegistryTest(_fixtures.FixtureTest):
     run_setup_mappers = 'once'
     run_inserts = None
@@ -242,7 +243,7 @@ class PathRegistryTest(_fixtures.FixtureTest):
         umapper = inspect(self.classes.User)
         is_(
             util.RootRegistry()[umapper],
-            umapper._sa_path_registry
+            umapper._path_registry
         )
         eq_(
             util.RootRegistry()[umapper],
@@ -255,9 +256,10 @@ class PathRegistryTest(_fixtures.FixtureTest):
         path = PathRegistry.coerce((umapper,))
 
         eq_(
-            path['addresses'][amapper]['email_address'],
-            PathRegistry.coerce((umapper, 'addresses',
-                                amapper, 'email_address'))
+            path[umapper.attrs.addresses][amapper]
+                [amapper.attrs.email_address],
+            PathRegistry.coerce((umapper, umapper.attrs.addresses,
+                                amapper, amapper.attrs.email_address))
         )
 
     def test_entity_boolean(self):
@@ -267,47 +269,48 @@ class PathRegistryTest(_fixtures.FixtureTest):
 
     def test_key_boolean(self):
         umapper = inspect(self.classes.User)
-        path = PathRegistry.coerce((umapper, 'addresses'))
+        path = PathRegistry.coerce((umapper, umapper.attrs.addresses))
         is_(bool(path), True)
 
     def test_aliased_class(self):
         User = self.classes.User
         ua = aliased(User)
-        path = PathRegistry.coerce((ua, 'addresses'))
+        ua_insp = inspect(ua)
+        path = PathRegistry.coerce((ua_insp, ua_insp.mapper.attrs.addresses))
         assert path.parent.is_aliased_class
 
     def test_indexed_entity(self):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
-        path = PathRegistry.coerce((umapper, 'addresses',
-                                amapper, 'email_address'))
+        path = PathRegistry.coerce((umapper, umapper.attrs.addresses,
+                                amapper, amapper.attrs.email_address))
         is_(path[0], umapper)
         is_(path[2], amapper)
 
     def test_indexed_key(self):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
-        path = PathRegistry.coerce((umapper, 'addresses',
-                                amapper, 'email_address'))
-        eq_(path[1], 'addresses')
-        eq_(path[3], 'email_address')
+        path = PathRegistry.coerce((umapper, umapper.attrs.addresses,
+                                amapper, amapper.attrs.email_address))
+        eq_(path[1], umapper.attrs.addresses)
+        eq_(path[3], amapper.attrs.email_address)
 
     def test_slice(self):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
-        path = PathRegistry.coerce((umapper, 'addresses',
-                                amapper, 'email_address'))
-        eq_(path[1:3], ('addresses', amapper))
+        path = PathRegistry.coerce((umapper, umapper.attrs.addresses,
+                                amapper, amapper.attrs.email_address))
+        eq_(path[1:3], (umapper.attrs.addresses, amapper))
 
     def test_addition(self):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
-        p1 = PathRegistry.coerce((umapper, 'addresses'))
-        p2 = PathRegistry.coerce((amapper, 'email_address'))
+        p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
+        p2 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
         eq_(
             p1 + p2,
-            PathRegistry.coerce((umapper, 'addresses',
-                                amapper, 'email_address'))
+            PathRegistry.coerce((umapper, umapper.attrs.addresses,
+                                amapper, amapper.attrs.email_address))
         )
 
     def test_length(self):
@@ -315,10 +318,10 @@ class PathRegistryTest(_fixtures.FixtureTest):
         amapper = inspect(self.classes.Address)
         pneg1 = PathRegistry.coerce(())
         p0 = PathRegistry.coerce((umapper,))
-        p1 = PathRegistry.coerce((umapper, 'addresses'))
-        p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
-        p3 = PathRegistry.coerce((umapper, 'addresses',
-                                amapper, 'email_address'))
+        p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
+        p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
+        p3 = PathRegistry.coerce((umapper, umapper.attrs.addresses,
+                                amapper, amapper.attrs.email_address))
 
         eq_(len(pneg1), 0)
         eq_(len(p0), 1)
@@ -334,14 +337,17 @@ class PathRegistryTest(_fixtures.FixtureTest):
     def test_eq(self):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
-        p1 = PathRegistry.coerce((umapper, 'addresses'))
-        p2 = PathRegistry.coerce((umapper, 'addresses'))
-        p3 = PathRegistry.coerce((umapper, 'other'))
-        p4 = PathRegistry.coerce((amapper, 'addresses'))
-        p5 = PathRegistry.coerce((umapper, 'addresses', amapper))
-        p6 = PathRegistry.coerce((amapper, 'user', umapper, 'addresses'))
-        p7 = PathRegistry.coerce((amapper, 'user', umapper, 'addresses',
-                                amapper, 'email_address'))
+        u_alias = inspect(aliased(self.classes.User))
+        p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
+        p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
+        p3 = PathRegistry.coerce((umapper, umapper.attrs.name))
+        p4 = PathRegistry.coerce((u_alias, umapper.attrs.addresses))
+        p5 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
+        p6 = PathRegistry.coerce((amapper, amapper.attrs.user, umapper,
+                                umapper.attrs.addresses))
+        p7 = PathRegistry.coerce((amapper, amapper.attrs.user, umapper,
+                                umapper.attrs.addresses,
+                                amapper, amapper.attrs.email_address))
 
         is_(p1 == p2, True)
         is_(p1 == p3, False)
@@ -358,7 +364,7 @@ class PathRegistryTest(_fixtures.FixtureTest):
     def test_contains_mapper(self):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
-        p1 = PathRegistry.coerce((umapper, 'addresses'))
+        p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
         assert p1.contains_mapper(umapper)
         assert not p1.contains_mapper(amapper)
 
@@ -373,18 +379,18 @@ class PathRegistryTest(_fixtures.FixtureTest):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
 
-        p1 = PathRegistry.coerce((umapper, 'addresses'))
-        p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
-        p3 = PathRegistry.coerce((amapper, 'email_address'))
+        p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
+        p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
+        p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
 
         eq_(
-            p1.path, (umapper, 'addresses')
+            p1.path, (umapper, umapper.attrs.addresses)
         )
         eq_(
-            p2.path, (umapper, 'addresses', amapper)
+            p2.path, (umapper, umapper.attrs.addresses, amapper)
         )
         eq_(
-            p3.path, (amapper, 'email_address')
+            p3.path, (amapper, amapper.attrs.email_address)
         )
 
     def test_registry_set(self):
@@ -392,9 +398,9 @@ class PathRegistryTest(_fixtures.FixtureTest):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
 
-        p1 = PathRegistry.coerce((umapper, 'addresses'))
-        p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
-        p3 = PathRegistry.coerce((amapper, 'email_address'))
+        p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
+        p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
+        p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
 
         p1.set(reg, "p1key", "p1value")
         p2.set(reg, "p2key", "p2value")
@@ -413,9 +419,9 @@ class PathRegistryTest(_fixtures.FixtureTest):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
 
-        p1 = PathRegistry.coerce((umapper, 'addresses'))
-        p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
-        p3 = PathRegistry.coerce((amapper, 'email_address'))
+        p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
+        p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
+        p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
         reg.update(
             {
                 ('p1key', p1.path): 'p1value',
@@ -435,9 +441,9 @@ class PathRegistryTest(_fixtures.FixtureTest):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
 
-        p1 = PathRegistry.coerce((umapper, 'addresses'))
-        p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
-        p3 = PathRegistry.coerce((amapper, 'email_address'))
+        p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
+        p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
+        p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
         reg.update(
             {
                 ('p1key', p1.path): 'p1value',
@@ -455,8 +461,8 @@ class PathRegistryTest(_fixtures.FixtureTest):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
 
-        p1 = PathRegistry.coerce((umapper, 'addresses'))
-        p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
+        p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
+        p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
         reg.update(
             {
                 ('p1key', p1.path): 'p1value',
@@ -481,10 +487,10 @@ class PathRegistryTest(_fixtures.FixtureTest):
         umapper = inspect(self.classes.User)
         amapper = inspect(self.classes.Address)
 
-        p1 = PathRegistry.coerce((umapper, 'addresses', amapper,
-                            'email_address'))
-        p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
-        p3 = PathRegistry.coerce((umapper, 'addresses'))
+        p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper,
+                            amapper.attrs.email_address))
+        p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
+        p3 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
         eq_(
             p1.serialize(),
             [(User, "addresses"), (Address, "email_address")]
@@ -505,10 +511,10 @@ class PathRegistryTest(_fixtures.FixtureTest):
         amapper = inspect(self.classes.Address)
 
 
-        p1 = PathRegistry.coerce((umapper, 'addresses', amapper,
-                            'email_address'))
-        p2 = PathRegistry.coerce((umapper, 'addresses', amapper))
-        p3 = PathRegistry.coerce((umapper, 'addresses'))
+        p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper,
+                            amapper.attrs.email_address))
+        p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
+        p3 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
 
         eq_(
             PathRegistry.deserialize([(User, "addresses"),
@@ -523,3 +529,135 @@ class PathRegistryTest(_fixtures.FixtureTest):
             PathRegistry.deserialize([(User, "addresses")]),
             p3
         )
+
+from .inheritance import _poly_fixtures
+class PathRegistryInhTest(_poly_fixtures._Polymorphic):
+    run_setup_mappers = 'once'
+    run_inserts = None
+    run_deletes = None
+
+    def test_plain(self):
+        Person = _poly_fixtures.Person
+        Engineer = _poly_fixtures.Engineer
+        pmapper = inspect(Person)
+        emapper = inspect(Engineer)
+
+        p1 = PathRegistry.coerce((pmapper, emapper.attrs.machines))
+
+        # given a mapper and an attribute on a subclass,
+        # the path converts what you get to be against that subclass
+        eq_(
+            p1.path,
+            (emapper, emapper.attrs.machines)
+        )
+
+    def test_plain_compound(self):
+        Company = _poly_fixtures.Company
+        Person = _poly_fixtures.Person
+        Engineer = _poly_fixtures.Engineer
+        cmapper = inspect(Company)
+        pmapper = inspect(Person)
+        emapper = inspect(Engineer)
+
+        p1 = PathRegistry.coerce((cmapper, cmapper.attrs.employees,
+                        pmapper, emapper.attrs.machines))
+
+        # given a mapper and an attribute on a subclass,
+        # the path converts what you get to be against that subclass
+        eq_(
+            p1.path,
+            (cmapper, cmapper.attrs.employees, emapper, emapper.attrs.machines)
+        )
+
+    def test_plain_aliased(self):
+        Person = _poly_fixtures.Person
+        Engineer = _poly_fixtures.Engineer
+        emapper = inspect(Engineer)
+
+        p_alias = aliased(Person)
+        p_alias = inspect(p_alias)
+
+        p1 = PathRegistry.coerce((p_alias, emapper.attrs.machines))
+        # plain AliasedClass - the path keeps that AliasedClass directly
+        # as is in the path
+        eq_(
+            p1.path,
+            (p_alias, emapper.attrs.machines)
+        )
+
+    def test_plain_aliased_compound(self):
+        Company = _poly_fixtures.Company
+        Person = _poly_fixtures.Person
+        Engineer = _poly_fixtures.Engineer
+        cmapper = inspect(Company)
+        emapper = inspect(Engineer)
+
+        c_alias = aliased(Company)
+        p_alias = aliased(Person)
+
+        c_alias = inspect(c_alias)
+        p_alias = inspect(p_alias)
+
+        p1 = PathRegistry.coerce((c_alias, cmapper.attrs.employees,
+                    p_alias, emapper.attrs.machines))
+        # plain AliasedClass - the path keeps that AliasedClass directly
+        # as is in the path
+        eq_(
+            p1.path,
+            (c_alias, cmapper.attrs.employees, p_alias, emapper.attrs.machines)
+        )
+
+    def test_with_poly_sub(self):
+        Person = _poly_fixtures.Person
+        Engineer = _poly_fixtures.Engineer
+        emapper = inspect(Engineer)
+
+        p_poly = with_polymorphic(Person, [Engineer])
+        e_poly = inspect(p_poly.Engineer)
+        p_poly = inspect(p_poly)
+
+        p1 = PathRegistry.coerce((p_poly, emapper.attrs.machines))
+
+        # polymorphic AliasedClass - the path uses _entity_for_mapper()
+        # to get the most specific sub-entity
+        eq_(
+            p1.path,
+            (e_poly, emapper.attrs.machines)
+        )
+
+    def test_with_poly_base(self):
+        Person = _poly_fixtures.Person
+        Engineer = _poly_fixtures.Engineer
+        pmapper = inspect(Person)
+        emapper = inspect(Engineer)
+
+        p_poly = with_polymorphic(Person, [Engineer])
+        p_poly = inspect(p_poly)
+
+        # "name" is actually on Person, not Engineer
+        p1 = PathRegistry.coerce((p_poly, emapper.attrs.name))
+
+        # polymorphic AliasedClass - because "name" is on Person,
+        # we get Person, not Engineer
+        eq_(
+            p1.path,
+            (p_poly, pmapper.attrs.name)
+        )
+
+    def test_with_poly_use_mapper(self):
+        Person = _poly_fixtures.Person
+        Engineer = _poly_fixtures.Engineer
+        emapper = inspect(Engineer)
+
+        p_poly = with_polymorphic(Person, [Engineer], _use_mapper_path=True)
+        p_poly = inspect(p_poly)
+
+        p1 = PathRegistry.coerce((p_poly, emapper.attrs.machines))
+
+        # polymorphic AliasedClass with the "use_mapper_path" flag -
+        # the AliasedClass acts just like the base mapper
+        eq_(
+            p1.path,
+            (emapper, emapper.attrs.machines)
+        )
+