]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
restore transfer of mapper.local_table to DML for some cases
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 20 Jul 2024 02:59:35 +0000 (22:59 -0400)
committerMichael Bayer <mike_mp@zzzcomputing.com>
Mon, 29 Jul 2024 13:17:08 +0000 (13:17 +0000)
Fixed regression appearing in 2.0.21 caused by :ticket:`10279` where using
a :func:`_sql.delete` or :func:`_sql.update` against an ORM class that is
the base of an inheritance hierarchy, while also specifying that subclasses
should be loaded polymorphically, would leak the polymorphic joins into the
UPDATE or DELETE statement as well creating incorrect SQL.

This re-introduces logic to set the `.table` of an ORM update or delete
back to `mapper.local_table` that was removed in d18ccdc997185b74 by
:ticket:`10279`; the logic is qualified to only take place for a
statement that's directly against a mapper and not one that's against
an aliased object.

Fixes: #11625
Change-Id: Ia228c99809370733f111925554167e39bcd6be1d

doc/build/changelog/unreleased_20/11625.rst [new file with mode: 0644]
lib/sqlalchemy/orm/bulk_persistence.py
test/orm/dml/test_update_delete_where.py

diff --git a/doc/build/changelog/unreleased_20/11625.rst b/doc/build/changelog/unreleased_20/11625.rst
new file mode 100644 (file)
index 0000000..c32a90a
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: bug, orm, regression
+    :tickets: 11625
+
+    Fixed regression appearing in 2.0.21 caused by :ticket:`10279` where using
+    a :func:`_sql.delete` or :func:`_sql.update` against an ORM class that is
+    the base of an inheritance hierarchy, while also specifying that subclasses
+    should be loaded polymorphically, would leak the polymorphic joins into the
+    UPDATE or DELETE statement as well creating incorrect SQL.
index 37beb0f2bb40c9f9e25a95225c6486f87693492a..b53a8302eacb6e671d3290755e0625cb5ffb1e7e 100644 (file)
@@ -1448,6 +1448,9 @@ class BulkORMUpdate(BulkUDCompileState, UpdateDMLState):
 
         new_stmt = statement._clone()
 
+        if new_stmt.table._annotations["parententity"] is mapper:
+            new_stmt.table = mapper.local_table
+
         # note if the statement has _multi_values, these
         # are passed through to the new statement, which will then raise
         # InvalidRequestError because UPDATE doesn't support multi_values
@@ -1867,6 +1870,9 @@ class BulkORMDelete(BulkUDCompileState, DeleteDMLState):
 
         new_stmt = statement._clone()
 
+        if new_stmt.table._annotations["parententity"] is mapper:
+            new_stmt.table = mapper.local_table
+
         new_crit = cls._adjust_for_extra_criteria(
             self.global_attributes, mapper
         )
index cbf27d018b7770a5e45d7d3f0f0b8fde19f738ac..6e5d29fe97b1ed5221d5be5a540ffa27516952cb 100644 (file)
@@ -36,6 +36,7 @@ from sqlalchemy.sql.dml import Update
 from sqlalchemy.sql.selectable import Select
 from sqlalchemy.testing import assert_raises
 from sqlalchemy.testing import assert_raises_message
+from sqlalchemy.testing import AssertsCompiledSQL
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import expect_raises
 from sqlalchemy.testing import fixtures
@@ -2964,6 +2965,54 @@ class InheritTest(fixtures.DeclarativeMappedTest):
         )
 
 
+class InheritWPolyTest(fixtures.TestBase, AssertsCompiledSQL):
+    __dialect__ = "default"
+
+    @testing.fixture
+    def inherit_fixture(self, decl_base):
+        def go(poly_type):
+
+            class Person(decl_base):
+                __tablename__ = "person"
+                id = Column(Integer, primary_key=True)
+                type = Column(String(50))
+                name = Column(String(50))
+
+                if poly_type.wpoly:
+                    __mapper_args__ = {"with_polymorphic": "*"}
+
+            class Engineer(Person):
+                __tablename__ = "engineer"
+                id = Column(Integer, ForeignKey("person.id"), primary_key=True)
+                engineer_name = Column(String(50))
+
+                if poly_type.inline:
+                    __mapper_args__ = {"polymorphic_load": "inline"}
+
+            return Person, Engineer
+
+        return go
+
+    @testing.variation("poly_type", ["wpoly", "inline", "none"])
+    def test_update_base_only(self, poly_type, inherit_fixture):
+        Person, Engineer = inherit_fixture(poly_type)
+
+        self.assert_compile(
+            update(Person).values(name="n1"), "UPDATE person SET name=:name"
+        )
+
+    @testing.variation("poly_type", ["wpoly", "inline", "none"])
+    def test_delete_base_only(self, poly_type, inherit_fixture):
+        Person, Engineer = inherit_fixture(poly_type)
+
+        self.assert_compile(delete(Person), "DELETE FROM person")
+
+        self.assert_compile(
+            delete(Person).where(Person.id == 7),
+            "DELETE FROM person WHERE person.id = :id_1",
+        )
+
+
 class SingleTablePolymorphicTest(fixtures.DeclarativeMappedTest):
     __backend__ = True