From: Mike Bayer Date: Fri, 3 Apr 2020 20:00:22 +0000 (-0400) Subject: Key subqueryloaders on the property object, not string key X-Git-Tag: rel_1_3_16~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b83c1d97a3efdabed81019a624da6cf4639aeeda;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Key subqueryloaders on the property object, not string key Fixed bug in :func:`.orm.selectinload` loading option where two or more loaders that represent different relationships with the same string key name as referenced from a single :func:`.orm.with_polymorphic` construct with multiple subclass mappers would fail to invoke each subqueryload separately, instead making use of a single string-based slot that would prevent the other loaders from being invoked. Fixes: #5228 Change-Id: Id0d1db8029ca88c13c0068115fe673adb7a68407 (cherry picked from commit a32c528c541670c0c4469523a3964712d79b7edd) --- diff --git a/doc/build/changelog/unreleased_13/5228.rst b/doc/build/changelog/unreleased_13/5228.rst new file mode 100644 index 0000000000..2f0b6a5675 --- /dev/null +++ b/doc/build/changelog/unreleased_13/5228.rst @@ -0,0 +1,11 @@ +.. change:: + :tags: bug, orm + :tickets: 5228 + + Fixed bug in :func:`.orm.selectinload` loading option where two or more + loaders that represent different relationships with the same string key + name as referenced from a single :func:`.orm.with_polymorphic` construct + with multiple subclass mappers would fail to invoke each subqueryload + separately, instead making use of a single string-based slot that would + prevent the other loaders from being invoked. + diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 3c1de20689..3b97b18462 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -2218,7 +2218,9 @@ class SelectInLoader(AbstractRelationshipLoader, util.MemoizedSlots): if not orm_util._entity_isa(path[-1], self.parent): return - if loading.PostLoad.path_exists(context, selectin_path, self.key): + if loading.PostLoad.path_exists( + context, selectin_path, self.parent_property + ): return path_w_prop = path[self.parent_property] @@ -2246,7 +2248,7 @@ class SelectInLoader(AbstractRelationshipLoader, util.MemoizedSlots): context, selectin_path, self.parent, - self.key, + self.parent_property, self._load_for_path, effective_entity, ) diff --git a/test/orm/test_selectin_relations.py b/test/orm/test_selectin_relations.py index 8bf4054f01..f3e0baa1fb 100644 --- a/test/orm/test_selectin_relations.py +++ b/test/orm/test_selectin_relations.py @@ -28,6 +28,8 @@ from sqlalchemy.testing import is_ from sqlalchemy.testing import is_not_ from sqlalchemy.testing import is_true from sqlalchemy.testing import mock +from sqlalchemy.testing.assertsql import AllOf +from sqlalchemy.testing.assertsql import assert_engine from sqlalchemy.testing.assertsql import CompiledSQL from sqlalchemy.testing.schema import Column from sqlalchemy.testing.schema import Table @@ -3302,3 +3304,110 @@ class M2OWDegradeTest( A(id=5, b=b1), ], ) + + +class SameNamePolymorphicTest(fixtures.DeclarativeMappedTest): + @classmethod + def setup_classes(cls): + Base = cls.DeclarativeBasic + + class GenericParent(Base): + __tablename__ = "generic_parent" + id = Column(Integer, primary_key=True) + type = Column(String(50), nullable=False) + + __mapper_args__ = { + "polymorphic_on": type, + "polymorphic_identity": "generic_parent", + } + + class ParentA(GenericParent): + __tablename__ = "parent_a" + + id = Column( + Integer, ForeignKey("generic_parent.id"), primary_key=True + ) + children = relationship("ChildA", back_populates="parent") + + __mapper_args__ = {"polymorphic_identity": "parent_a"} + + class ParentB(GenericParent): + __tablename__ = "parent_b" + + id = Column( + Integer, ForeignKey("generic_parent.id"), primary_key=True + ) + children = relationship("ChildB", back_populates="parent") + + __mapper_args__ = {"polymorphic_identity": "parent_b"} + + class ChildA(Base): + __tablename__ = "child_a" + id = Column(Integer, primary_key=True) + parent_id = Column( + Integer, ForeignKey("parent_a.id"), nullable=False + ) + parent = relationship("ParentA", back_populates="children") + + class ChildB(Base): + __tablename__ = "child_b" + + id = Column(Integer, primary_key=True) + parent_id = Column( + Integer, ForeignKey("parent_b.id"), nullable=False + ) + parent = relationship("ParentB", back_populates="children") + + @classmethod + def insert_data(cls): + ParentA, ParentB, ChildA, ChildB = cls.classes( + "ParentA", "ParentB", "ChildA", "ChildB" + ) + session = Session() + parent_a = ParentA(id=1) + parent_b = ParentB(id=2) + for i in range(10): + parent_a.children.append(ChildA()) + parent_b.children.append(ChildB()) + session.add_all([parent_a, parent_b]) + + session.commit() + + def test_load_both_wpoly(self): + GenericParent, ParentA, ParentB, ChildA, ChildB = self.classes( + "GenericParent", "ParentA", "ParentB", "ChildA", "ChildB" + ) + session = Session() + + parent_types = with_polymorphic(GenericParent, [ParentA, ParentB]) + + with assert_engine(testing.db) as asserter_: + session.query(parent_types).options( + selectinload(parent_types.ParentA.children), + selectinload(parent_types.ParentB.children), + ).all() + + asserter_.assert_( + CompiledSQL( + "SELECT generic_parent.id AS generic_parent_id, " + "generic_parent.type AS generic_parent_type, " + "parent_a.id AS parent_a_id, parent_b.id AS parent_b_id " + "FROM generic_parent LEFT OUTER JOIN parent_a " + "ON generic_parent.id = parent_a.id LEFT OUTER JOIN parent_b " + "ON generic_parent.id = parent_b.id" + ), + AllOf( + CompiledSQL( + "SELECT child_a.parent_id AS child_a_parent_id, " + "child_a.id AS child_a_id FROM child_a " + "WHERE child_a.parent_id IN ([EXPANDING_primary_keys])", + [{"primary_keys": [1]}], + ), + CompiledSQL( + "SELECT child_b.parent_id AS child_b_parent_id, " + "child_b.id AS child_b_id FROM child_b " + "WHERE child_b.parent_id IN ([EXPANDING_primary_keys])", + [{"primary_keys": [2]}], + ), + ), + )