From: Gord Thompson Date: Fri, 29 May 2020 13:20:54 +0000 (-0600) Subject: Fix is_disconnect false positive for mssql+pyodbc X-Git-Tag: rel_1_3_18~16 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2c2bdceab7ce1bf31fffa260caafa0443e8a9c23;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Fix is_disconnect false positive for mssql+pyodbc Fixed an issue where the ``is_disconnect`` function in the SQL Server pyodbc dialect was incorrectly reporting the disconnect state when the exception messsage had a substring that matched a SQL Server ODBC error code. Fixes: #5359 Change-Id: I450c6818405a20f4daee20d58fce2d5ecb33e17f (cherry picked from commit ddff320473dcbd3cc11d577715f96237276bc685) --- diff --git a/doc/build/changelog/unreleased_13/5359.rst b/doc/build/changelog/unreleased_13/5359.rst new file mode 100644 index 0000000000..b5f690db8d --- /dev/null +++ b/doc/build/changelog/unreleased_13/5359.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, mssql + :tickets: 5359 + + Fixed an issue where the ``is_disconnect`` function in the SQL Server + pyodbc dialect was incorrectly reporting the disconnect state when the + exception messsage had a substring that matched a SQL Server ODBC error + code. \ No newline at end of file diff --git a/lib/sqlalchemy/dialects/mssql/pyodbc.py b/lib/sqlalchemy/dialects/mssql/pyodbc.py index 891a3f6e7f..b2571de4f6 100644 --- a/lib/sqlalchemy/dialects/mssql/pyodbc.py +++ b/lib/sqlalchemy/dialects/mssql/pyodbc.py @@ -419,7 +419,8 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect): def is_disconnect(self, e, connection, cursor): if isinstance(e, self.dbapi.Error): - for code in ( + code = e.args[0] + if code in ( "08S01", "01002", "08003", @@ -430,8 +431,7 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect): "HY010", "10054", ): - if code in str(e): - return True + return True return super(MSDialect_pyodbc, self).is_disconnect( e, connection, cursor ) diff --git a/test/dialect/mssql/test_engine.py b/test/dialect/mssql/test_engine.py index 05a01b768b..9bec4cb95e 100644 --- a/test/dialect/mssql/test_engine.py +++ b/test/dialect/mssql/test_engine.py @@ -1,4 +1,5 @@ # -*- encoding: utf-8 + from sqlalchemy import Column from sqlalchemy import engine_from_config from sqlalchemy import event @@ -12,6 +13,8 @@ from sqlalchemy.dialects.mssql import base from sqlalchemy.dialects.mssql import pymssql from sqlalchemy.dialects.mssql import pyodbc from sqlalchemy.engine import url +from sqlalchemy.exc import IntegrityError +from sqlalchemy.testing import assert_raises from sqlalchemy.testing import assert_raises_message from sqlalchemy.testing import assert_warnings from sqlalchemy.testing import engines @@ -321,7 +324,7 @@ class ParseConnectTest(fixtures.TestBase): ) for error in [ - MockDBAPIError("[%s] some pyodbc message" % code) + MockDBAPIError(code, "[%s] some pyodbc message" % code) for code in [ "08S01", "01002", @@ -343,7 +346,9 @@ class ParseConnectTest(fixtures.TestBase): eq_( dialect.is_disconnect( - MockProgrammingError("not an error"), None, None + MockProgrammingError("Query with abc08007def failed"), + None, + None, ), False, ) @@ -526,3 +531,39 @@ class IsolationLevelDetectTest(fixtures.TestBase): dialect.get_isolation_level, connection, ) + + +class InvalidTransactionFalsePositiveTest(fixtures.TablesTest): + __only_on__ = "mssql" + __backend__ = True + + @classmethod + def define_tables(cls, metadata): + Table( + "error_t", + metadata, + Column("error_code", String(50), primary_key=True), + ) + + @classmethod + def insert_data(cls, connection): + connection.execute( + cls.tables.error_t.insert(), [{"error_code": "01002"}], + ) + + def test_invalid_transaction_detection(self, connection): + # issue #5359 + t = self.tables.error_t + + # force duplicate PK error + assert_raises( + IntegrityError, + connection.execute, + t.insert(), + {"error_code": "01002"}, + ) + + # this should not fail with + # "Can't reconnect until invalid transaction is rolled back." + result = connection.execute(t.select()).fetchall() + eq_(len(result), 1)