From: Mike Bayer Date: Tue, 8 Oct 2024 14:29:34 +0000 (-0400) Subject: re-apply right memo for nested ORMJoin when splicing X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=43b974a34957f22963e7faf44f0798c8179adcfc;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git re-apply right memo for nested ORMJoin when splicing Fixed regression caused by fixes to joined eager loading in :ticket:`11449`, where a particular joinedload case could not be asserted correctly. We now have an example of that case so the assertion has been repaired to allow for it. Fixes: #11965 Change-Id: I2e0a594981534f4aaeff361a2f8cf1a0fba8de8f --- diff --git a/doc/build/changelog/unreleased_20/11965.rst b/doc/build/changelog/unreleased_20/11965.rst new file mode 100644 index 0000000000..1f9294c0d9 --- /dev/null +++ b/doc/build/changelog/unreleased_20/11965.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, orm + :tickets: 11965 + + Fixed regression caused by fixes to joined eager loading in + :ticket:`11449`, where a particular joinedload case could not be asserted + correctly. We now have an example of that case so the assertion has been + repaired to allow for it. + diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 996bdbc1d9..3f947a8d74 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -2694,7 +2694,8 @@ class JoinedLoader(AbstractRelationshipLoader): # lets look at our path we are satisfying and see if we're in the # wrong place. This is specifically for when our entity may # appear more than once in the path, issue #11449 - if detected_existing_path: + # updated in issue #11965. + if detected_existing_path and len(detected_existing_path) > 2: # this assertion is currently based on how this call is made, # where given a join_obj, the call will have these parameters as # entity_inside_join_structure=join_obj._left_memo diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 6d6fc14715..2a1f4bfe4c 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -1946,7 +1946,7 @@ class _ORMJoin(expression.Join): self.onclause, isouter=self.isouter, _left_memo=self._left_memo, - _right_memo=None, + _right_memo=other._left_memo._path_registry, ) return _ORMJoin( diff --git a/test/orm/test_eager_relations.py b/test/orm/test_eager_relations.py index bc3d8f10c2..7e0eca62c6 100644 --- a/test/orm/test_eager_relations.py +++ b/test/orm/test_eager_relations.py @@ -26,6 +26,8 @@ from sqlalchemy.orm import joinedload from sqlalchemy.orm import lazyload from sqlalchemy.orm import Load from sqlalchemy.orm import load_only +from sqlalchemy.orm import Mapped +from sqlalchemy.orm import mapped_column from sqlalchemy.orm import relationship from sqlalchemy.orm import Session from sqlalchemy.orm import undefer @@ -7110,3 +7112,94 @@ class SingletonConstantSubqTest(_fixtures.FixtureTest): ) self.assert_sql_count(testing.db, go, 1) + + +class NestedInnerjoinTestIssue11965( + fixtures.DeclarativeMappedTest, testing.AssertsCompiledSQL +): + """test for issue #11965, regression from #11449""" + + __dialect__ = "default" + + @classmethod + def setup_classes(cls): + Base = cls.DeclarativeBasic + + class Source(Base): + __tablename__ = "source" + id: Mapped[int] = mapped_column(primary_key=True) + + class Day(Base): + __tablename__ = "day" + id: Mapped[int] = mapped_column(primary_key=True) + + class Run(Base): + __tablename__ = "run" + id: Mapped[int] = mapped_column(primary_key=True) + + source_id: Mapped[int] = mapped_column( + ForeignKey(Source.id), nullable=False + ) + source = relationship(Source, lazy="joined", innerjoin=True) + + day = relationship( + Day, + lazy="joined", + innerjoin=True, + ) + day_id: Mapped[int] = mapped_column( + ForeignKey(Day.id), nullable=False + ) + + class Event(Base): + __tablename__ = "event" + + id: Mapped[int] = mapped_column(primary_key=True) + run_id: Mapped[int] = mapped_column( + ForeignKey(Run.id), nullable=False + ) + run = relationship(Run, lazy="joined", innerjoin=True) + + class Room(Base): + __tablename__ = "room" + + id: Mapped[int] = mapped_column(primary_key=True) + event_id: Mapped[int] = mapped_column( + ForeignKey(Event.id), nullable=False + ) + event = relationship(Event, foreign_keys=event_id, lazy="joined") + + @classmethod + def insert_data(cls, connection): + Room, Run, Source, Event, Day = cls.classes( + "Room", "Run", "Source", "Event", "Day" + ) + run = Run(source=Source(), day=Day()) + event = Event(run=run) + room = Room(event=event) + with Session(connection) as session: + session.add(room) + session.commit() + + def test_compile(self): + Room = self.classes.Room + self.assert_compile( + select(Room), + "SELECT room.id, room.event_id, source_1.id AS id_1, " + "day_1.id AS id_2, run_1.id AS id_3, run_1.source_id, " + "run_1.day_id, event_1.id AS id_4, event_1.run_id " + "FROM room LEFT OUTER JOIN " + "(event AS event_1 " + "JOIN run AS run_1 ON run_1.id = event_1.run_id " + "JOIN day AS day_1 ON day_1.id = run_1.day_id " + "JOIN source AS source_1 ON source_1.id = run_1.source_id) " + "ON event_1.id = room.event_id", + ) + + def test_roundtrip(self): + Room = self.classes.Room + session = fixture_session() + rooms = session.scalars(select(Room)).unique().all() + session.close() + # verify eager-loaded correctly + assert rooms[0].event.run.day