From: Mike Bayer Date: Sat, 20 Jul 2024 02:59:35 +0000 (-0400) Subject: restore transfer of mapper.local_table to DML for some cases X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5e77d544893da200d6c770d9bd34c86e33eab293;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git restore transfer of mapper.local_table to DML for some cases 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 --- diff --git a/doc/build/changelog/unreleased_20/11625.rst b/doc/build/changelog/unreleased_20/11625.rst new file mode 100644 index 0000000000..c32a90ad82 --- /dev/null +++ b/doc/build/changelog/unreleased_20/11625.rst @@ -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. diff --git a/lib/sqlalchemy/orm/bulk_persistence.py b/lib/sqlalchemy/orm/bulk_persistence.py index 37beb0f2bb..b53a8302ea 100644 --- a/lib/sqlalchemy/orm/bulk_persistence.py +++ b/lib/sqlalchemy/orm/bulk_persistence.py @@ -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 ) diff --git a/test/orm/dml/test_update_delete_where.py b/test/orm/dml/test_update_delete_where.py index cbf27d018b..6e5d29fe97 100644 --- a/test/orm/dml/test_update_delete_where.py +++ b/test/orm/dml/test_update_delete_where.py @@ -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