From: Mike Bayer Date: Sun, 30 Oct 2022 00:08:25 +0000 (-0400) Subject: fix test for same mapper to use "isa" X-Git-Tag: rel_1_4_43~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d8c4c34aafdca6e62061944566ccc26864551a00;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git fix test for same mapper to use "isa" Fixed issue in joined eager loading where an assertion fail would occur with a particular combination of outer/inner joined eager loads in conjunction with an inherited subclass mapper as the middle target. Fixes: #8738 Change-Id: I4909e7518302cbb82046e0425abbbdc8eb1c0146 (cherry picked from commit 99e7afb4b2d82baff80f5d1fe1b2d1b21cbbec09) --- diff --git a/doc/build/changelog/unreleased_14/8738.rst b/doc/build/changelog/unreleased_14/8738.rst new file mode 100644 index 0000000000..2372c25afe --- /dev/null +++ b/doc/build/changelog/unreleased_14/8738.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, orm + :tickets: 8731 + + Fixed issue in joined eager loading where an assertion fail would occur + with a particular combination of outer/inner joined eager loads in + conjunction with an inherited subclass mapper as the middle target. + diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index a014b2f411..2b09421411 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -2386,6 +2386,11 @@ class JoinedLoader(AbstractRelationshipLoader): self, path, join_obj, clauses, onclause, extra_criteria, splicing=False ): + # recursive fn to splice a nested join into an existing one. + # splicing=False means this is the outermost call, and it + # should return a value. splicing= is the recursive + # form, where it can return None to indicate the end of the recursion + if splicing is False: # first call is always handed a join object # from the outside @@ -2400,7 +2405,7 @@ class JoinedLoader(AbstractRelationshipLoader): splicing, ) elif not isinstance(join_obj, orm_util._ORMJoin): - if path[-2] is splicing: + if path[-2].isa(splicing): return orm_util._ORMJoin( join_obj, clauses.aliased_class, @@ -2411,7 +2416,6 @@ class JoinedLoader(AbstractRelationshipLoader): _extra_criteria=extra_criteria, ) else: - # only here if splicing == True return None target_join = self._splice_nested_inner_join( @@ -2434,7 +2438,7 @@ class JoinedLoader(AbstractRelationshipLoader): ) if target_join is None: # should only return None when recursively called, - # e.g. splicing==True + # e.g. splicing refers to a from obj assert ( splicing is not False ), "assertion failed attempting to produce joined eager loads" diff --git a/test/orm/inheritance/test_relationship.py b/test/orm/inheritance/test_relationship.py index 0b1967f519..c4e20fefce 100644 --- a/test/orm/inheritance/test_relationship.py +++ b/test/orm/inheritance/test_relationship.py @@ -3015,3 +3015,111 @@ class M2ODontLoadSiblingTest(fixtures.DeclarativeMappedTest): is_(obj.child2, None) is_(obj.parent, c1) + + +class JoinedLoadSpliceFromJoinedTest( + testing.AssertsCompiledSQL, fixtures.DeclarativeMappedTest +): + """test #8378""" + + __dialect__ = "default" + run_create_tables = None + + @classmethod + def setup_classes(cls): + Base = cls.DeclarativeBasic + + class Root(Base): + __tablename__ = "root" + + id = Column(Integer, primary_key=True) + root_elements = relationship("BaseModel") + + class BaseModel(Base): + __tablename__ = "base_model" + + id = Column(Integer, primary_key=True) + root_id = Column(Integer, ForeignKey("root.id"), nullable=False) + type = Column(String, nullable=False) + __mapper_args__ = {"polymorphic_on": type} + + class SubModel(BaseModel): + elements = relationship("SubModelElement") + __mapper_args__ = {"polymorphic_identity": "sub_model"} + + class SubModelElement(Base): + __tablename__ = "sub_model_element" + + id = Column(Integer, primary_key=True) + model_id = Column(ForeignKey("base_model.id"), nullable=False) + + def test_oj_ij(self): + Root, SubModel = self.classes("Root", "SubModel") + + s = Session() + query = s.query(Root) + query = query.options( + joinedload(Root.root_elements.of_type(SubModel)).joinedload( + SubModel.elements, innerjoin=True + ) + ) + self.assert_compile( + query, + "SELECT root.id AS root_id, base_model_1.id AS base_model_1_id, " + "base_model_1.root_id AS base_model_1_root_id, " + "base_model_1.type AS base_model_1_type, " + "sub_model_element_1.id AS sub_model_element_1_id, " + "sub_model_element_1.model_id AS sub_model_element_1_model_id " + "FROM root LEFT OUTER JOIN (base_model AS base_model_1 " + "JOIN sub_model_element AS sub_model_element_1 " + "ON base_model_1.id = sub_model_element_1.model_id) " + "ON root.id = base_model_1.root_id", + ) + + def test_ij_oj(self): + Root, SubModel = self.classes("Root", "SubModel") + + s = Session() + query = s.query(Root) + query = query.options( + joinedload( + Root.root_elements.of_type(SubModel), innerjoin=True + ).joinedload(SubModel.elements) + ) + self.assert_compile( + query, + "SELECT root.id AS root_id, base_model_1.id AS base_model_1_id, " + "base_model_1.root_id AS base_model_1_root_id, " + "base_model_1.type AS base_model_1_type, " + "sub_model_element_1.id AS sub_model_element_1_id, " + "sub_model_element_1.model_id AS sub_model_element_1_model_id " + "FROM root JOIN base_model AS base_model_1 " + "ON root.id = base_model_1.root_id " + "LEFT OUTER JOIN sub_model_element AS sub_model_element_1 " + "ON base_model_1.id = sub_model_element_1.model_id" + "", + ) + + def test_ij_ij(self): + Root, SubModel = self.classes("Root", "SubModel") + + s = Session() + query = s.query(Root) + query = query.options( + joinedload( + Root.root_elements.of_type(SubModel), innerjoin=True + ).joinedload(SubModel.elements, innerjoin=True) + ) + self.assert_compile( + query, + "SELECT root.id AS root_id, base_model_1.id AS base_model_1_id, " + "base_model_1.root_id AS base_model_1_root_id, " + "base_model_1.type AS base_model_1_type, " + "sub_model_element_1.id AS sub_model_element_1_id, " + "sub_model_element_1.model_id AS sub_model_element_1_model_id " + "FROM root JOIN base_model AS base_model_1 " + "ON root.id = base_model_1.root_id " + "JOIN sub_model_element AS sub_model_element_1 " + "ON base_model_1.id = sub_model_element_1.model_id" + "", + )