Set use_mapper_path=True for with_poly subentities

Fixed regression in joined eager loading introduced in 1.3.0b3 via
🎫`4468` where the ability to create a joined option across a
:func:`.with_polymorphic` into a polymorphic subclass using
:meth:`.RelationshipProperty.of_type` and then further along regular mapped
relationships would fail as the polymorphic subclass would not add itself
to the load path in a way that could be located by the loader strategy.  A
tweak has been made to resolve this scenario.

Fixes: #5082
Change-Id: I1c7b8d70ed94436c655e433bf34394b13d384c35
This commit is contained in:
Mike Bayer
2020-01-06 21:06:10 -05:00
parent 84cb9d1f0a
commit 2734439fff
6 changed files with 127 additions and 6 deletions
+12
View File
@@ -0,0 +1,12 @@
.. change::
:tags: orm, bug
:tickets: 5082
Fixed regression in joined eager loading introduced in 1.3.0b3 via
:ticket:`4468` where the ability to create a joined option across a
:func:`.with_polymorphic` into a polymorphic subclass using
:meth:`.RelationshipProperty.of_type` and then further along regular mapped
relationships would fail as the polymorphic subclass would not add itself
to the load path in a way that could be located by the loader strategy. A
tweak has been made to resolve this scenario.
+4 -1
View File
@@ -255,7 +255,10 @@ class PropRegistry(PathRegistry):
and prop.parent in insp.with_polymorphic_mappers
):
subclass_entity = parent[-1]._entity_for_mapper(prop.parent)
parent = parent.parent[subclass_entity]
if subclass_entity._use_mapper_path:
parent = parent.parent[subclass_entity.mapper]
else:
parent = parent.parent[subclass_entity]
self.prop = prop
self.parent = parent
+1
View File
@@ -312,6 +312,7 @@ class Load(HasCacheKey, Generative, MapperOption):
existing = path.entity_path[prop].get(
self.context, "path_with_polymorphic"
)
if not ext_info.is_aliased_class:
ac = orm_util.with_polymorphic(
ext_info.mapper.base_mapper,
+1 -1
View File
@@ -606,7 +606,7 @@ class AliasedInsp(sql_base.HasCacheKey, InspectionAttr):
selectable,
base_alias=self,
adapt_on_names=adapt_on_names,
use_mapper_path=_use_mapper_path,
use_mapper_path=True,
)
setattr(self.entity, poly.class_.__name__, ent)
+100
View File
@@ -1725,6 +1725,106 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest):
)
class JoinedloadWPolyOfTypeContinued(
fixtures.DeclarativeMappedTest, testing.AssertsCompiledSQL
):
"""test for #5082 """
@classmethod
def setup_classes(cls):
Base = cls.DeclarativeBasic
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
class Foo(Base):
__tablename__ = "foos"
__mapper_args__ = {"polymorphic_on": "type"}
id = Column(Integer, primary_key=True)
type = Column(String(10), nullable=False)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship(
"User", backref=backref("foos"), cascade="all"
)
class SubFoo(Foo):
__tablename__ = "foos_sub"
__mapper_args__ = {"polymorphic_identity": "SUB"}
id = Column(Integer, ForeignKey("foos.id"), primary_key=True)
baz = Column(Integer)
bar_id = Column(Integer, ForeignKey("bars.id"))
bar = relationship("Bar")
class Bar(Base):
__tablename__ = "bars"
id = Column(Integer, primary_key=True)
fred_id = Column(Integer, ForeignKey("freds.id"), nullable=False)
fred = relationship("Fred")
class Fred(Base):
__tablename__ = "freds"
id = Column(Integer, primary_key=True)
@classmethod
def insert_data(cls):
User, Fred, Bar, SubFoo = cls.classes("User", "Fred", "Bar", "SubFoo")
user = User(id=1)
fred = Fred(id=1)
bar = Bar(fred=fred)
rectangle = SubFoo(owner=user, baz=10, bar=bar)
s = Session()
s.add_all([user, fred, bar, rectangle])
s.commit()
def test_joined_load(self):
Foo, User, Bar = self.classes("Foo", "User", "Bar")
s = Session()
foo_polymorphic = with_polymorphic(Foo, "*", aliased=True)
foo_load = joinedload(User.foos.of_type(foo_polymorphic))
query = s.query(User).options(
foo_load.joinedload(foo_polymorphic.SubFoo.bar).joinedload(
Bar.fred
)
)
self.assert_compile(
query,
"SELECT users.id AS users_id, anon_1.foos_id AS anon_1_foos_id, "
"anon_1.foos_type AS anon_1_foos_type, anon_1.foos_owner_id "
"AS anon_1_foos_owner_id, freds_1.id AS freds_1_id, bars_1.id "
"AS bars_1_id, bars_1.fred_id AS bars_1_fred_id, "
"anon_1.foos_sub_id AS anon_1_foos_sub_id, "
"anon_1.foos_sub_baz AS anon_1_foos_sub_baz, "
"anon_1.foos_sub_bar_id AS anon_1_foos_sub_bar_id "
"FROM users LEFT OUTER JOIN "
"(SELECT foos.id AS foos_id, foos.type AS foos_type, "
"foos.owner_id AS foos_owner_id, foos_sub.id AS foos_sub_id, "
"foos_sub.baz AS foos_sub_baz, foos_sub.bar_id AS foos_sub_bar_id "
"FROM foos LEFT OUTER JOIN foos_sub ON foos.id = foos_sub.id) "
"AS anon_1 ON users.id = anon_1.foos_owner_id "
"LEFT OUTER JOIN bars AS bars_1 "
"ON bars_1.id = anon_1.foos_sub_bar_id "
"LEFT OUTER JOIN freds AS freds_1 ON freds_1.id = bars_1.fred_id",
)
def go():
user = query.one()
user.foos[0].bar
user.foos[0].bar.fred
self.assert_sql_count(testing.db, go, 1)
class JoinedloadSinglePolysubSingle(
fixtures.DeclarativeMappedTest, testing.AssertsCompiledSQL
):
+9 -4
View File
@@ -935,14 +935,19 @@ class PathRegistryInhTest(_poly_fixtures._Polymorphic):
emapper = inspect(Engineer)
p_poly = with_polymorphic(Person, [Engineer])
e_poly = inspect(p_poly.Engineer)
e_poly = inspect(p_poly.Engineer) # noqa - used by comment below
p_poly_insp = inspect(p_poly)
p1 = PathRegistry.coerce((p_poly_insp, 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))
# polymorphic AliasedClass - as of #5082, for the sub entities that are
# generated for each subclass by with_polymorphic(), use_mapper_path
# is not True so that creating paths from the sub entities, which don't
# by themselves encapsulate the with_polymorphic selectable, states the
# path in terms of that plain entity. previously, this path would be
# (e_poly, emapper.attrs.machines), but a loader strategy would never
# match on "e_poly", it would see "emapper".
eq_(p1.path, (emapper, emapper.attrs.machines))
def test_with_poly_base(self):
Person = _poly_fixtures.Person