]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
include empty intermediary tables in optimized get
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 26 Dec 2021 17:13:19 +0000 (12:13 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 26 Dec 2021 20:01:13 +0000 (15:01 -0500)
Fixed issue in joined-inheritance load of additional attributes
functionality in deep multi-level inheritance where an intermediary table
that contained no columns would not be included in the tables joined,
instead linking those tables to their primary key identifiers. While this
works fine, it nonetheless in 1.4 began producing the cartesian product
compiler warning. The logic has been changed so that these intermediary
tables are included regardless. While this does include additional tables
in the query that are not technically necessary, this only occurs for the
highly unusual case of deep 3+ level inheritance with intermediary tables
that have no non primary key columns, potential performance impact is
therefore expected to be negligible.

Fixes: #7507
Change-Id: Id2073773e97a0853b744b51feeb2bc4437032e51
(cherry picked from commit c1d2fbac4c399b47f4715f7ea2a1147374d2aa43)

doc/build/changelog/unreleased_14/7507.rst [new file with mode: 0644]
lib/sqlalchemy/orm/mapper.py
test/orm/inheritance/test_basic.py

diff --git a/doc/build/changelog/unreleased_14/7507.rst b/doc/build/changelog/unreleased_14/7507.rst
new file mode 100644 (file)
index 0000000..7412c7f
--- /dev/null
@@ -0,0 +1,15 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 7507
+
+    Fixed issue in joined-inheritance load of additional attributes
+    functionality in deep multi-level inheritance where an intermediary table
+    that contained no columns would not be included in the tables joined,
+    instead linking those tables to their primary key identifiers. While this
+    works fine, it nonetheless in 1.4 began producing the cartesian product
+    compiler warning. The logic has been changed so that these intermediary
+    tables are included regardless. While this does include additional tables
+    in the query that are not technically necessary, this only occurs for the
+    highly unusual case of deep 3+ level inheritance with intermediary tables
+    that have no non primary key columns, potential performance impact is
+    therefore expected to be negligible.
index 4de12b88c772121b7b578b3c687a7053bb001e82..9ac18bea664046c29caa50a3894bca88a43856b4 100644 (file)
@@ -3024,23 +3024,28 @@ class Mapper(
 
         allconds = []
 
+        start = False
+
+        # as of #7507, from the lowest base table on upwards,
+        # we include all intermediary tables.
+
+        for mapper in reversed(list(self.iterate_to_root())):
+            if mapper.local_table in tables:
+                start = True
+            elif not isinstance(mapper.local_table, expression.TableClause):
+                return None
+            if start and not mapper.single:
+                allconds.append(mapper.inherit_condition)
+                tables.add(mapper.local_table)
+
+        # only the bottom table needs its criteria to be altered to fit
+        # the primary key ident - the rest of the tables upwards to the
+        # descendant-most class should all be present and joined to each
+        # other.
         try:
-            start = False
-            for mapper in reversed(list(self.iterate_to_root())):
-                if mapper.local_table in tables:
-                    start = True
-                elif not isinstance(
-                    mapper.local_table, expression.TableClause
-                ):
-                    return None
-                if start and not mapper.single:
-                    allconds.append(
-                        visitors.cloned_traverse(
-                            mapper.inherit_condition,
-                            {},
-                            {"binary": visit_binary},
-                        )
-                    )
+            allconds[0] = visitors.cloned_traverse(
+                allconds[0], {}, {"binary": visit_binary}
+            )
         except _OptGetColumnsNotAvailable:
             return None
 
index ac1661fdd16493a5c1f842ba397447d4ea2a565d..446a9d9bd929ae08355070dfde0f6ab8808e4585 100644 (file)
@@ -2696,6 +2696,70 @@ class OptimizedLoadTest(fixtures.MappedTest):
 
             eq_(s1.sub, "s1sub")
 
+    def test_optimized_get_blank_intermediary(self, registry, connection):
+        """test #7507"""
+
+        Base = registry.generate_base()
+
+        class A(Base):
+            __tablename__ = "a"
+
+            id = Column(Integer, primary_key=True)
+            a = Column(String(20), nullable=False)
+            type_ = Column(String(20))
+            __mapper_args__ = {
+                "polymorphic_on": type_,
+                "polymorphic_identity": "a",
+            }
+
+        class B(A):
+            __tablename__ = "b"
+            __mapper_args__ = {"polymorphic_identity": "b"}
+
+            id = Column(Integer, ForeignKey("a.id"), primary_key=True)
+            b = Column(String(20), nullable=False)
+
+        class C(B):
+            __tablename__ = "c"
+            __mapper_args__ = {"polymorphic_identity": "c"}
+
+            id = Column(Integer, ForeignKey("b.id"), primary_key=True)
+
+        class D(C):
+            __tablename__ = "d"
+            __mapper_args__ = {"polymorphic_identity": "d"}
+
+            id = Column(Integer, ForeignKey("c.id"), primary_key=True)
+            c = Column(String(20), nullable=False)
+
+        Base.metadata.create_all(connection)
+
+        session = Session(connection)
+        session.add(D(a="x", b="y", c="z"))
+        session.commit()
+
+        with self.sql_execution_asserter(connection) as asserter:
+            d = session.query(A).one()
+            eq_(d.c, "z")
+        asserter.assert_(
+            CompiledSQL(
+                "SELECT a.id AS a_id, a.a AS a_a, a.type_ AS a_type_ FROM a",
+                [],
+            ),
+            Or(
+                CompiledSQL(
+                    "SELECT d.c AS d_c, b.b AS b_b FROM d, b, c WHERE "
+                    ":param_1 = b.id AND b.id = c.id AND c.id = d.id",
+                    [{"param_1": 1}],
+                ),
+                CompiledSQL(
+                    "SELECT b.b AS b_b, d.c AS d_c FROM b, d, c WHERE "
+                    ":param_1 = b.id AND b.id = c.id AND c.id = d.id",
+                    [{"param_1": 1}],
+                ),
+            ),
+        )
+
     def test_optimized_passes(self):
         """ "test that the 'optimized load' routine doesn't crash when
         a column in the join condition is not available."""