summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-10-19 12:17:37 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2015-10-19 12:17:37 -0400
commit1e0afd584c12ff624259f03d8a6ab5f892c2c0cf (patch)
tree25305e2c7bc6ae62e19297d26994503fd2b99582
parent322f8f18f51985c75b78b425a3a768b2529410ed (diff)
downloadsqlalchemy-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.rst12
-rw-r--r--lib/sqlalchemy/orm/persistence.py2
-rw-r--r--test/orm/test_versioning.py88
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()