]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
test for None plugin_subject
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 14 Jul 2023 16:08:24 +0000 (12:08 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 14 Jul 2023 16:19:40 +0000 (12:19 -0400)
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

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

diff --git a/doc/build/changelog/unreleased_20/10098.rst b/doc/build/changelog/unreleased_20/10098.rst
new file mode 100644 (file)
index 0000000..a4cd195
--- /dev/null
@@ -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.
+
index 2181cd296f846b6c8e518798d72e14acdaa01131..063a2cbe481df8cec6ddace8ebf33ddf0b72ed7c 100644 (file)
@@ -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":
index 395603a771533282478fb13e8fc38a6eb6988b04..6d599d68eaf5cbd5dc999b0594a66d71fbef34d6 100644 (file)
@@ -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"