]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
fix test for same mapper to use "isa"
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 30 Oct 2022 00:08:25 +0000 (20:08 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 30 Oct 2022 13:43:40 +0000 (09:43 -0400)
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)

doc/build/changelog/unreleased_14/8738.rst [new file with mode: 0644]
lib/sqlalchemy/orm/strategies.py
test/orm/inheritance/test_relationship.py

diff --git a/doc/build/changelog/unreleased_14/8738.rst b/doc/build/changelog/unreleased_14/8738.rst
new file mode 100644 (file)
index 0000000..2372c25
--- /dev/null
@@ -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.
+
index a014b2f411578f5ca6a155604ba1561a6e1bb4ee..2b09421411783b236bfe1ed399e9c0c15ce28259 100644 (file)
@@ -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=<from object> 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"
index 0b1967f51916970bac282290f4af8c201237fa50..c4e20fefce167d897ec971ad4c32478eb4143dfe 100644 (file)
@@ -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"
+            "",
+        )