From 197ffa2be2cadce3df8bfb0799b3c80158250286 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 5 Jan 2016 10:25:36 -0500 Subject: - Fixed 1.0 regression where the eager fetch of cursor.rowcount was no longer called for an UPDATE or DELETE statement emitted via plain text or via the :func:`.text` construct, affecting those drivers that erase cursor.rowcount once the cursor is closed such as SQL Server ODBC and Firebird drivers. fixes #3622 --- doc/build/changelog/changelog_10.rst | 16 ++++++++++++++++ lib/sqlalchemy/engine/base.py | 2 +- lib/sqlalchemy/engine/default.py | 3 +++ lib/sqlalchemy/sql/compiler.py | 6 ++++++ test/sql/test_rowcount.py | 17 +++++++++++++++++ 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 25592a3b1..782af7320 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -15,6 +15,22 @@ .. include:: changelog_07.rst :start-line: 5 +.. changelog:: + :version: 1.0.12 + :released: + + .. change:: + :tags: bug, mssql, firebird + :versions: 1.1.0b1 + :tickets: 3622 + + Fixed 1.0 regression where the eager fetch of cursor.rowcount was + no longer called for an UPDATE or DELETE statement emitted via plain + text or via the :func:`.text` construct, affecting those drivers + that erase cursor.rowcount once the cursor is closed such as SQL + Server ODBC and Firebird drivers. + + .. changelog:: :version: 1.0.11 :released: December 22, 2015 diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index eaa435d45..31e253eed 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -1155,7 +1155,7 @@ class Connection(Connectable): if context.compiled: context.post_exec() - if context.is_crud: + if context.is_crud or context.is_text: result = context._setup_crud_result_proxy() else: result = context.get_result_proxy() diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 9a7b80bfd..87278c2be 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -467,6 +467,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext): isupdate = False isdelete = False is_crud = False + is_text = False isddl = False executemany = False compiled = None @@ -543,6 +544,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext): self.isinsert = compiled.isinsert self.isupdate = compiled.isupdate self.isdelete = compiled.isdelete + self.is_text = compiled.isplaintext if not parameters: self.compiled_parameters = [compiled.construct_params()] @@ -622,6 +624,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext): self.root_connection = connection self._dbapi_connection = dbapi_connection self.dialect = connection.dialect + self.is_text = True # plain text statement self.execution_options = connection._execution_options diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 6766c99b7..2ca549267 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -286,6 +286,7 @@ class _CompileLabel(visitors.Visitable): def self_group(self, **kw): return self + class SQLCompiler(Compiled): """Default implementation of Compiled. @@ -305,6 +306,8 @@ class SQLCompiler(Compiled): INSERT/UPDATE/DELETE """ + isplaintext = False + returning = None """holds the "returning" collection of columns if the statement is CRUD and defines returning columns @@ -688,6 +691,9 @@ class SQLCompiler(Compiled): else: return self.bindparam_string(name, **kw) + if not self.stack: + self.isplaintext = True + # un-escape any \:params return BIND_PARAMS_ESC.sub( lambda m: m.group(1), diff --git a/test/sql/test_rowcount.py b/test/sql/test_rowcount.py index 46e10e192..110f3639f 100644 --- a/test/sql/test_rowcount.py +++ b/test/sql/test_rowcount.py @@ -1,6 +1,7 @@ from sqlalchemy import * from sqlalchemy.testing import fixtures, AssertsExecutionResults from sqlalchemy import testing +from sqlalchemy.testing import eq_ class FoundRowsTest(fixtures.TestBase, AssertsExecutionResults): @@ -65,6 +66,22 @@ class FoundRowsTest(fixtures.TestBase, AssertsExecutionResults): print("expecting 3, dialect reports %s" % r.rowcount) assert r.rowcount == 3 + def test_raw_sql_rowcount(self): + # test issue #3622, make sure eager rowcount is called for text + with testing.db.connect() as conn: + result = conn.execute( + "update employees set department='Z' where department='C'") + eq_(result.rowcount, 3) + + def test_text_rowcount(self): + # test issue #3622, make sure eager rowcount is called for text + with testing.db.connect() as conn: + result = conn.execute( + text( + "update employees set department='Z' " + "where department='C'")) + eq_(result.rowcount, 3) + def test_delete_rowcount(self): # WHERE matches 3, 3 rows deleted department = employees_table.c.department -- cgit v1.2.1