]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed regression in 1.0 where new feature of using "executemany"
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 19 Oct 2015 16:17:37 +0000 (12:17 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 19 Oct 2015 16:17:37 +0000 (12:17 -0400)
for UPDATE statements in the ORM (e.g. :ref:`feature_updatemany`)
would break on Postgresql and other RETURNING backends
when using server-side version generation
schemes, as the server side value is retrieved via RETURNING which
is not supported with executemany.
fixes #3556

doc/build/changelog/changelog_10.rst
lib/sqlalchemy/orm/persistence.py
test/orm/test_versioning.py

index b6a944a1e91f43b3c689199e0339d92d6742a594..3a50b5cb0835ba06c6ff8f78479d4a9f00cbc461 100644 (file)
 .. changelog::
     :version: 1.0.9
 
+    .. change::
+        :tags: bug, orm, postgresql
+        :versions: 1.1.0b1
+        :tickets: 3556
+
+        Fixed regression in 1.0 where new feature of using "executemany"
+        for UPDATE statements in the ORM (e.g. :ref:`feature_updatemany`)
+        would break on Postgresql and other RETURNING backends
+        when using server-side version generation
+        schemes, as the server side value is retrieved via RETURNING which
+        is not supported with executemany.
+
     .. change::
         :tags: feature, ext
         :versions: 1.1.0b1
index d89a93dd3f460d964514022d3a501ba048a9533b..71d62c79b7cebd166fd6db3e44d7abcbade8b247 100644 (file)
@@ -645,7 +645,7 @@ def _emit_update_statements(base_mapper, uowtransaction,
         assert_singlerow = connection.dialect.supports_sane_rowcount
         assert_multirow = assert_singlerow and \
             connection.dialect.supports_sane_multi_rowcount
-        allow_multirow = not needs_version_id or assert_multirow
+        allow_multirow = assert_multirow and not needs_version_id
 
         if hasvalue:
             for state, state_dict, params, mapper, \
index d46799c5aab95bbea5c80109b84b935074927bcf..f42069230f4b641ebca01ad89e025342deb51f80 100644 (file)
@@ -112,6 +112,24 @@ class VersioningTest(fixtures.MappedTest):
         else:
             s1.commit()
 
+    def test_multiple_updates(self):
+        Foo = self.classes.Foo
+
+        s1 = self._fixture()
+        f1 = Foo(value='f1')
+        f2 = Foo(value='f2')
+        s1.add_all((f1, f2))
+        s1.commit()
+
+        f1.value = 'f1rev2'
+        f2.value = 'f2rev2'
+        s1.commit()
+
+        eq_(
+            s1.query(Foo.id, Foo.value, Foo.version_id).order_by(Foo.id).all(),
+            [(f1.id, 'f1rev2', 2), (f2.id, 'f2rev2', 2)]
+        )
+
     @testing.emits_warning_on(
         '+zxjdbc', r'.*does not support (update|delete)d rowcount')
     def test_bump_version(self):
@@ -952,6 +970,76 @@ class ServerVersioningTest(fixtures.MappedTest):
             )
         self.assert_sql_execution(testing.db, sess.flush, *statements)
 
+    def test_multi_update(self):
+        sess = self._fixture()
+
+        f1 = self.classes.Foo(value='f1')
+        f2 = self.classes.Foo(value='f2')
+        f3 = self.classes.Foo(value='f3')
+        sess.add_all([f1, f2, f3])
+        sess.flush()
+
+        f1.value = 'f1a'
+        f2.value = 'f2a'
+        f3.value = 'f3a'
+
+        statements = [
+            # note that the assertsql tests the rule against
+            # "default" - on a "returning" backend, the statement
+            # includes "RETURNING"
+            CompiledSQL(
+                "UPDATE version_table SET version_id=2, value=:value "
+                "WHERE version_table.id = :version_table_id AND "
+                "version_table.version_id = :version_table_version_id",
+                lambda ctx: [
+                    {
+                        "version_table_id": 1,
+                        "version_table_version_id": 1, "value": "f1a"}]
+            ),
+            CompiledSQL(
+                "UPDATE version_table SET version_id=2, value=:value "
+                "WHERE version_table.id = :version_table_id AND "
+                "version_table.version_id = :version_table_version_id",
+                lambda ctx: [
+                    {
+                        "version_table_id": 2,
+                        "version_table_version_id": 1, "value": "f2a"}]
+            ),
+            CompiledSQL(
+                "UPDATE version_table SET version_id=2, value=:value "
+                "WHERE version_table.id = :version_table_id AND "
+                "version_table.version_id = :version_table_version_id",
+                lambda ctx: [
+                    {
+                        "version_table_id": 3,
+                        "version_table_version_id": 1, "value": "f3a"}]
+            )
+        ]
+        if not testing.db.dialect.implicit_returning:
+            # DBs without implicit returning, we must immediately
+            # SELECT for the new version id
+            statements.extend([
+                CompiledSQL(
+                    "SELECT version_table.version_id "
+                    "AS version_table_version_id "
+                    "FROM version_table WHERE version_table.id = :param_1",
+                    lambda ctx: [{"param_1": 1}]
+                ),
+                CompiledSQL(
+                    "SELECT version_table.version_id "
+                    "AS version_table_version_id "
+                    "FROM version_table WHERE version_table.id = :param_1",
+                    lambda ctx: [{"param_1": 2}]
+                ),
+                CompiledSQL(
+                    "SELECT version_table.version_id "
+                    "AS version_table_version_id "
+                    "FROM version_table WHERE version_table.id = :param_1",
+                    lambda ctx: [{"param_1": 3}]
+                )
+            ])
+        self.assert_sql_execution(testing.db, sess.flush, *statements)
+
     def test_delete_col(self):
         sess = self._fixture()