From: Mike Bayer Date: Tue, 28 Jan 2020 17:34:06 +0000 (-0500) Subject: Accommodate for base class when adjusting path for with_polymorphic X-Git-Tag: rel_1_3_14~31^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=635ee0d005039ccb0d8e8b98acbec6a885d7f54a;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Accommodate for base class when adjusting path for with_polymorphic 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) --- diff --git a/doc/build/changelog/unreleased_13/5121.rst b/doc/build/changelog/unreleased_13/5121.rst new file mode 100644 index 0000000000..9803601c2a --- /dev/null +++ b/doc/build/changelog/unreleased_13/5121.rst @@ -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. diff --git a/lib/sqlalchemy/orm/path_registry.py b/lib/sqlalchemy/orm/path_registry.py index f4e16a9f27..a277f48d61 100644 --- a/lib/sqlalchemy/orm/path_registry.py +++ b/lib/sqlalchemy/orm/path_registry.py @@ -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) diff --git a/test/orm/inheritance/test_relationship.py b/test/orm/inheritance/test_relationship.py index b005b6b40f..51eddee719 100644 --- a/test/orm/inheritance/test_relationship.py +++ b/test/orm/inheritance/test_relationship.py @@ -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", ) diff --git a/test/orm/test_utils.py b/test/orm/test_utils.py index bbd28770ca..877fbde69b 100644 --- a/test/orm/test_utils.py +++ b/test/orm/test_utils.py @@ -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)