summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_13/5359.rst8
-rw-r--r--lib/sqlalchemy/dialects/mssql/pyodbc.py6
-rw-r--r--test/dialect/mssql/test_engine.py45
3 files changed, 54 insertions, 5 deletions
diff --git a/doc/build/changelog/unreleased_13/5359.rst b/doc/build/changelog/unreleased_13/5359.rst
new file mode 100644
index 000000000..b5f690db8
--- /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 ff164e886..6cf45e7b8 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 0dea2688a..734224ed1 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
@@ -11,6 +12,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
@@ -294,7 +297,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",
@@ -316,7 +319,9 @@ class ParseConnectTest(fixtures.TestBase):
eq_(
dialect.is_disconnect(
- MockProgrammingError("not an error"), None, None
+ MockProgrammingError("Query with abc08007def failed"),
+ None,
+ None,
),
False,
)
@@ -511,3 +516,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)