From: Mike Bayer Date: Fri, 14 Jul 2023 16:08:24 +0000 (-0400) Subject: test for None plugin_subject X-Git-Tag: rel_2_0_19~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=024931fb1b931b5b71a6fb6726b6dd173a780f79;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git test for None plugin_subject Fixed additional regression caused by :ticket:`9805` where more aggressive propagation of the "ORM" flag on statements could lead to an internal attribute error when embedding an ORM :class:`.Query` construct that nonetheless contained no ORM entities within a Core SQL statement, in this case ORM-enabled UPDATE and DELETE statements. Fixes: #10098 Change-Id: Ieeca9c64c28049e22d558ca47a720dd8bbe1eec9 --- diff --git a/doc/build/changelog/unreleased_20/10098.rst b/doc/build/changelog/unreleased_20/10098.rst new file mode 100644 index 0000000000..a4cd1959ba --- /dev/null +++ b/doc/build/changelog/unreleased_20/10098.rst @@ -0,0 +1,10 @@ +.. change:: + :tags: bug, orm, regression + :tickets: 10098 + + Fixed additional regression caused by :ticket:`9805` where more aggressive + propagation of the "ORM" flag on statements could lead to an internal + attribute error when embedding an ORM :class:`.Query` construct that + nonetheless contained no ORM entities within a Core SQL statement, in this + case ORM-enabled UPDATE and DELETE statements. + diff --git a/lib/sqlalchemy/orm/bulk_persistence.py b/lib/sqlalchemy/orm/bulk_persistence.py index 2181cd296f..063a2cbe48 100644 --- a/lib/sqlalchemy/orm/bulk_persistence.py +++ b/lib/sqlalchemy/orm/bulk_persistence.py @@ -656,9 +656,9 @@ class BulkUDCompileState(ORMDMLState): except KeyError: assert False, "statement had 'orm' plugin but no plugin_subject" else: - bind_arguments["mapper"] = plugin_subject.mapper - - update_options += {"_subject_mapper": plugin_subject.mapper} + if plugin_subject: + bind_arguments["mapper"] = plugin_subject.mapper + update_options += {"_subject_mapper": plugin_subject.mapper} if "parententity" not in statement.table._annotations: update_options += {"_dml_strategy": "core_only"} @@ -1164,9 +1164,9 @@ class BulkORMInsert(ORMDMLState, InsertDMLState): except KeyError: assert False, "statement had 'orm' plugin but no plugin_subject" else: - bind_arguments["mapper"] = plugin_subject.mapper - - insert_options += {"_subject_mapper": plugin_subject.mapper} + if plugin_subject: + bind_arguments["mapper"] = plugin_subject.mapper + insert_options += {"_subject_mapper": plugin_subject.mapper} if not params: if insert_options._dml_strategy == "auto": diff --git a/test/orm/test_session.py b/test/orm/test_session.py index 395603a771..6d599d68ea 100644 --- a/test/orm/test_session.py +++ b/test/orm/test_session.py @@ -11,6 +11,7 @@ from sqlalchemy import ForeignKey from sqlalchemy import insert from sqlalchemy import inspect from sqlalchemy import Integer +from sqlalchemy import literal from sqlalchemy import select from sqlalchemy import Sequence from sqlalchemy import String @@ -2242,6 +2243,58 @@ class NewStyleExecutionTest(_fixtures.FixtureTest): is_true(inspect(u1).detached) is_(inspect(u1).session, None) + @testing.variation("construct", ["select", "update", "delete", "insert"]) + def test_core_sql_w_embedded_orm(self, construct: testing.Variation): + """test #10098""" + user_table = self.tables.users + + sess = fixture_session() + + subq_1 = ( + sess.query(user_table.c.id).where( + user_table.c.id == 10 + ) # note user 10 exists but has no addresses, so + # this is significant for the test here + .scalar_subquery() + ) + + if construct.update: + stmt = ( + user_table.update() + .values(name="xyz") + .where(user_table.c.id.in_(subq_1)) + ) + elif construct.delete: + stmt = user_table.delete().where(user_table.c.id.in_(subq_1)) + elif construct.select: + stmt = select(user_table).where(user_table.c.id.in_(subq_1)) + elif construct.insert: + stmt = insert(user_table).from_select( + ["id", "name"], select(subq_1 + 10, literal("xyz")) + ) + + # force the expected condition for INSERT; can't really get + # this to happen "naturally" + stmt._propagate_attrs = stmt._propagate_attrs.union( + {"compile_state_plugin": "orm", "plugin_subject": None} + ) + + else: + construct.fail() + + # assert that the pre-condition we are specifically testing is + # present. if the implementation changes then this would have + # to change also. + eq_( + stmt._propagate_attrs, + {"compile_state_plugin": "orm", "plugin_subject": None}, + ) + + result = sess.execute(stmt) + + if construct.select: + result.all() + class FlushWarningsTest(fixtures.MappedTest): run_setup_mappers = "each"