diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-10-19 12:17:37 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-10-19 12:17:37 -0400 |
commit | 1e0afd584c12ff624259f03d8a6ab5f892c2c0cf (patch) | |
tree | 25305e2c7bc6ae62e19297d26994503fd2b99582 | |
parent | 322f8f18f51985c75b78b425a3a768b2529410ed (diff) | |
download | sqlalchemy-1e0afd584c12ff624259f03d8a6ab5f892c2c0cf.tar.gz |
- 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.
fixes #3556
-rw-r--r-- | doc/build/changelog/changelog_10.rst | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/persistence.py | 2 | ||||
-rw-r--r-- | test/orm/test_versioning.py | 88 |
3 files changed, 101 insertions, 1 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index b6a944a1e..3a50b5cb0 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -19,6 +19,18 @@ :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 :tickets: 3551 diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py index d89a93dd3..71d62c79b 100644 --- a/lib/sqlalchemy/orm/persistence.py +++ b/lib/sqlalchemy/orm/persistence.py @@ -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, \ diff --git a/test/orm/test_versioning.py b/test/orm/test_versioning.py index d46799c5a..f42069230 100644 --- a/test/orm/test_versioning.py +++ b/test/orm/test_versioning.py @@ -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() |