]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Set use_mapper_path=True for with_poly subentities
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 7 Jan 2020 02:06:10 +0000 (21:06 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 7 Jan 2020 15:50:09 +0000 (10:50 -0500)
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.

Fixes: #5082
Change-Id: I1c7b8d70ed94436c655e433bf34394b13d384c35
(cherry picked from commit 2734439fff953a7bb8aecdedb5f851441b5122e9)

doc/build/changelog/unreleased_13/5082.rst [new file with mode: 0644]
lib/sqlalchemy/orm/path_registry.py
lib/sqlalchemy/orm/strategy_options.py
lib/sqlalchemy/orm/util.py
test/orm/inheritance/test_relationship.py
test/orm/test_utils.py

diff --git a/doc/build/changelog/unreleased_13/5082.rst b/doc/build/changelog/unreleased_13/5082.rst
new file mode 100644 (file)
index 0000000..000f55a
--- /dev/null
@@ -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.
+
index 300fdafee0d76c8f7567b73994c0fbfa7d226e03..1215511a1dbd9764c628a683362d1a4dcaf027ec 100644 (file)
@@ -247,7 +247,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
index f898b546c9b721dc4eb2495e0bb10554a41ea148..d9c12ac21e61057c587a00c4e7f9726a158df223 100644 (file)
@@ -287,6 +287,7 @@ class Load(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,
index 08af18d18b1b02691b332da991020ede78638517..ed5d113c8f3d65312f26b0cfde3ef64aa7b6ce01 100644 (file)
@@ -603,7 +603,7 @@ class AliasedInsp(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)
index 3c121e06beeff78786e020a19b9b0f0242d95918..da7136fc98e53777303e4cd4d595b94e392c8d61 100644 (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
 ):
index b82e2e3f76e1776147a6039ef5bcee5c5972f10e..2b4eadaf0ef8e0014ae113afe93230a2cc9b114f 100644 (file)
@@ -918,14 +918,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