]> 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:12 +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
(cherry picked from commit e82660aba0b9ced0b3c65fd8fc4496e4e371fce0)

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 2ed6a4beaacde576add7e5109d195ebc0ac7fda5..ff85650436e98ddcb6bb2a862cd4c8032f3e1602 100644 (file)
@@ -1446,6 +1446,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
@@ -1865,6 +1868,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