]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Accommodate for base class when adjusting path for with_polymorphic
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 28 Jan 2020 17:34:06 +0000 (12:34 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 28 Jan 2020 17:47:44 +0000 (12:47 -0500)
Fixed an additional regression in the same area as that of :ticket:`5080`
introduced in 1.3.0b3 via :ticket:`4468` where the ability to create a
joined option across a :func:`.with_polymorphic` into a relationship
against the base class of that with_polymorphic, and then further into
regular mapped relationships would fail as the base class component would
not add itself to the load path in a way that could be located by the
loader strategy. The changes applied in :ticket:`5080` have been further
refined to also accommodate this scenario.

Fixes: #5121
Change-Id: I9753dbbcb7b7640c995ad983a6d04b36fa18cf54
(cherry picked from commit c9406e58a4fd2de4bb2fd53530e04cc9fc786cbf)

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

diff --git a/doc/build/changelog/unreleased_13/5121.rst b/doc/build/changelog/unreleased_13/5121.rst
new file mode 100644 (file)
index 0000000..9803601
--- /dev/null
@@ -0,0 +1,12 @@
+.. change::
+    :tags: orm, bug
+    :tickets: 5121
+
+    Fixed an additional regression in the same area as that of :ticket:`5080`
+    introduced in 1.3.0b3 via :ticket:`4468` where the ability to create a
+    joined option across a :func:`.with_polymorphic` into a relationship
+    against the base class of that with_polymorphic, and then further into
+    regular mapped relationships would fail as the base class component would
+    not add itself to the load path in a way that could be located by the
+    loader strategy. The changes applied in :ticket:`5080` have been further
+    refined to also accommodate this scenario.
index f4e16a9f27206d8c79490dccc78a27d6ed6d23f3..a277f48d61b5e9b9b41706a057ceea205a0589c5 100644 (file)
@@ -265,7 +265,6 @@ class PropRegistry(PathRegistry):
         elif (
             insp.is_aliased_class
             and insp.with_polymorphic_mappers
-            and prop.parent is not insp.mapper
             and prop.parent in insp.with_polymorphic_mappers
         ):
             subclass_entity = parent[-1]._entity_for_mapper(prop.parent)
index b005b6b40f51f71e6adc09d72866354239c15b09..51eddee7194d037765425b30fc8d38c84a021c30 100644 (file)
@@ -1738,6 +1738,7 @@ class JoinedloadWPolyOfTypeContinued(
             __tablename__ = "users"
 
             id = Column(Integer, primary_key=True)
+            foos = relationship("Foo", back_populates="owner")
 
         class Foo(Base):
             __tablename__ = "foos"
@@ -1746,9 +1747,9 @@ class JoinedloadWPolyOfTypeContinued(
             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"
-            )
+            owner = relationship("User", back_populates="foos")
+            bar_id = Column(ForeignKey("bars.id"))
+            bar = relationship("Bar")
 
         class SubFoo(Foo):
             __tablename__ = "foos_sub"
@@ -1756,8 +1757,8 @@ class JoinedloadWPolyOfTypeContinued(
 
             id = Column(Integer, ForeignKey("foos.id"), primary_key=True)
             baz = Column(Integer)
-            bar_id = Column(Integer, ForeignKey("bars.id"))
-            bar = relationship("Bar")
+            sub_bar_id = Column(Integer, ForeignKey("sub_bars.id"))
+            sub_bar = relationship("SubBar")
 
         class Bar(Base):
             __tablename__ = "bars"
@@ -1766,6 +1767,13 @@ class JoinedloadWPolyOfTypeContinued(
             fred_id = Column(Integer, ForeignKey("freds.id"), nullable=False)
             fred = relationship("Fred")
 
+        class SubBar(Base):
+            __tablename__ = "sub_bars"
+
+            id = Column(Integer, primary_key=True)
+            fred_id = Column(Integer, ForeignKey("freds.id"), nullable=False)
+            fred = relationship("Fred")
+
         class Fred(Base):
             __tablename__ = "freds"
 
@@ -1773,18 +1781,21 @@ class JoinedloadWPolyOfTypeContinued(
 
     @classmethod
     def insert_data(cls):
-        User, Fred, Bar, SubFoo = cls.classes("User", "Fred", "Bar", "SubFoo")
+        User, Fred, SubBar, Bar, SubFoo = cls.classes(
+            "User", "Fred", "SubBar", "Bar", "SubFoo"
+        )
         user = User(id=1)
         fred = Fred(id=1)
         bar = Bar(fred=fred)
-        rectangle = SubFoo(owner=user, baz=10, bar=bar)
+        sub_bar = SubBar(fred=fred)
+        rectangle = SubFoo(owner=user, baz=10, bar=bar, sub_bar=sub_bar)
 
         s = Session()
-        s.add_all([user, fred, bar, rectangle])
+        s.add_all([user, fred, bar, sub_bar, rectangle])
         s.commit()
 
-    def test_joined_load(self):
-        Foo, User, Bar = self.classes("Foo", "User", "Bar")
+    def test_joined_load_lastlink_subclass(self):
+        Foo, User, SubBar = self.classes("Foo", "User", "SubBar")
 
         s = Session()
 
@@ -1792,8 +1803,8 @@ class JoinedloadWPolyOfTypeContinued(
 
         foo_load = joinedload(User.foos.of_type(foo_polymorphic))
         query = s.query(User).options(
-            foo_load.joinedload(foo_polymorphic.SubFoo.bar).joinedload(
-                Bar.fred
+            foo_load.joinedload(foo_polymorphic.SubFoo.sub_bar).joinedload(
+                SubBar.fred
             )
         )
 
@@ -1801,19 +1812,68 @@ class JoinedloadWPolyOfTypeContinued(
             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, "
+            "AS anon_1_foos_owner_id, "
+            "anon_1.foos_bar_id AS anon_1_foos_bar_id, "
+            "freds_1.id AS freds_1_id, sub_bars_1.id "
+            "AS sub_bars_1_id, sub_bars_1.fred_id AS sub_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 "
+            "anon_1.foos_sub_sub_bar_id AS anon_1_foos_sub_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 "
+            "foos.owner_id AS foos_owner_id, foos.bar_id AS foos_bar_id, "
+            "foos_sub.id AS foos_sub_id, "
+            "foos_sub.baz AS foos_sub_baz, "
+            "foos_sub.sub_bar_id AS foos_sub_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 sub_bars AS sub_bars_1 "
+            "ON sub_bars_1.id = anon_1.foos_sub_sub_bar_id "
+            "LEFT OUTER JOIN freds AS freds_1 "
+            "ON freds_1.id = sub_bars_1.fred_id",
+        )
+
+        def go():
+            user = query.one()
+            user.foos[0].sub_bar
+            user.foos[0].sub_bar.fred
+
+        self.assert_sql_count(testing.db, go, 1)
+
+    def test_joined_load_lastlink_baseclass(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.bar).joinedload(Bar.fred)
+        )
+
+        self.assert_compile(
+            query,
+            "SELECT users.id AS users_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_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, anon_1.foos_bar_id AS anon_1_foos_bar_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_sub_bar_id AS anon_1_foos_sub_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.bar_id AS foos_bar_id, "
+            "foos_sub.id AS "
+            "foos_sub_id, foos_sub.baz AS foos_sub_baz, "
+            "foos_sub.sub_bar_id AS "
+            "foos_sub_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_bar_id "
             "LEFT OUTER JOIN freds AS freds_1 ON freds_1.id = bars_1.fred_id",
         )
 
index bbd28770ca1761e882043ce6ab8c6924a8a38015..877fbde69bf0074a8adbb630a5f8e58b60bab43d 100644 (file)
@@ -994,6 +994,45 @@ class PathRegistryInhTest(_poly_fixtures._Polymorphic):
             ),
         )
 
+    def test_with_poly_base_two(self):
+        Company = _poly_fixtures.Company
+        Person = _poly_fixtures.Person
+        Engineer = _poly_fixtures.Engineer
+        cmapper = inspect(Company)
+        pmapper = inspect(Person)
+
+        p_poly = with_polymorphic(Person, [Engineer])
+        e_poly_insp = inspect(p_poly.Engineer)  # noqa - used by comment below
+        p_poly_insp = inspect(p_poly)
+
+        p1 = PathRegistry.coerce(
+            (
+                cmapper,
+                cmapper.attrs.employees,
+                p_poly_insp,
+                pmapper.attrs.paperwork,
+            )
+        )
+
+        eq_(
+            p1.path,
+            (
+                cmapper,
+                cmapper.attrs.employees,
+                p_poly_insp,
+                pmapper.attrs.paperwork,
+            ),
+        )
+        eq_(
+            p1.natural_path,
+            (
+                cmapper,
+                cmapper.attrs.employees,
+                pmapper,
+                pmapper.attrs.paperwork,
+            ),
+        )
+
     def test_nonpoly_oftype_aliased_subclass_onroot(self):
         Engineer = _poly_fixtures.Engineer
         eng_alias = aliased(Engineer)
@@ -1077,7 +1116,7 @@ class PathRegistryInhTest(_poly_fixtures._Polymorphic):
             ),
         )
 
-    def test_with_poly_base(self):
+    def test_with_poly_base_one(self):
         Person = _poly_fixtures.Person
         Engineer = _poly_fixtures.Engineer
         pmapper = inspect(Person)