summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2022-02-24 14:44:44 +0000
committerGerrit Code Review <gerrit@ci3.zzzcomputing.com>2022-02-24 14:44:44 +0000
commit381894b50ea7e16b4950ecbdda72c0fa45de8d1c (patch)
treecff92f26b61576dd209127a15d8e41886f83a702
parent11333602c0f844e32f12af114f1dfcb160408fcf (diff)
parent8f9e971f10dee0614054671e0c284f0acace2d04 (diff)
downloadsqlalchemy-381894b50ea7e16b4950ecbdda72c0fa45de8d1c.tar.gz
Merge "support cx_Oracle DPI disconnect codes" into main
-rw-r--r--doc/build/changelog/unreleased_14/7748.rst7
-rw-r--r--lib/sqlalchemy/dialects/oracle/cx_oracle.py21
-rw-r--r--test/dialect/oracle/test_dialect.py54
3 files changed, 75 insertions, 7 deletions
diff --git a/doc/build/changelog/unreleased_14/7748.rst b/doc/build/changelog/unreleased_14/7748.rst
new file mode 100644
index 000000000..d9d6bf236
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/7748.rst
@@ -0,0 +1,7 @@
+.. change::
+ :tags: bug, oracle, regression
+ :tickets: 7748
+
+ Added support to parse "DPI" error codes from cx_Oracle exception objects
+ such as ``DPI-1080`` and ``DPI-1010``, both of which now indicate a
+ disconnect scenario as of cx_Oracle 8.3.
diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
index 3f8109a12..a390099ae 100644
--- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py
+++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
@@ -1233,7 +1233,14 @@ class OracleDialect_cx_oracle(OracleDialect):
) and "not connected" in str(e):
return True
- if hasattr(error, "code"):
+ if hasattr(error, "code") and error.code in {
+ 28,
+ 3114,
+ 3113,
+ 3135,
+ 1033,
+ 2396,
+ }:
# ORA-00028: your session has been killed
# ORA-03114: not connected to ORACLE
# ORA-03113: end-of-file on communication channel
@@ -1241,9 +1248,15 @@ class OracleDialect_cx_oracle(OracleDialect):
# ORA-01033: ORACLE initialization or shutdown in progress
# ORA-02396: exceeded maximum idle time, please connect again
# TODO: Others ?
- return error.code in (28, 3114, 3113, 3135, 1033, 2396)
- else:
- return False
+ return True
+
+ if re.match(r"^(?:DPI-1010|DPI-1080)", str(e)):
+ # DPI-1010: not connected
+ # DPI-1080: connection was closed by ORA-3113
+ # TODO: others?
+ return True
+
+ return False
def create_xid(self):
"""create a two-phase transaction ID.
diff --git a/test/dialect/oracle/test_dialect.py b/test/dialect/oracle/test_dialect.py
index 5383ffc0c..e827fa56c 100644
--- a/test/dialect/oracle/test_dialect.py
+++ b/test/dialect/oracle/test_dialect.py
@@ -1,6 +1,7 @@
# coding: utf-8
import re
+from unittest import mock
from unittest.mock import Mock
from sqlalchemy import bindparam
@@ -30,7 +31,6 @@ from sqlalchemy.testing import config
from sqlalchemy.testing import engines
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
-from sqlalchemy.testing import mock
from sqlalchemy.testing.schema import Column
from sqlalchemy.testing.schema import Table
from sqlalchemy.testing.suite import test_select
@@ -56,7 +56,7 @@ class DialectTest(fixtures.TestBase):
exc.InvalidRequestError,
"cx_Oracle version 5.2 and above are supported",
cx_oracle.OracleDialect_cx_oracle,
- dbapi=Mock(),
+ dbapi=mock.Mock(),
)
with mock.patch(
@@ -64,13 +64,61 @@ class DialectTest(fixtures.TestBase):
"_parse_cx_oracle_ver",
lambda self, vers: (5, 3, 1),
):
- cx_oracle.OracleDialect_cx_oracle(dbapi=Mock())
+ cx_oracle.OracleDialect_cx_oracle(dbapi=mock.Mock())
class DialectWBackendTest(fixtures.TestBase):
__backend__ = True
__only_on__ = "oracle"
+ @testing.combinations(
+ (
+ "db is not connected",
+ None,
+ True,
+ ),
+ (
+ "ORA-1234 fake error",
+ 1234,
+ False,
+ ),
+ (
+ "ORA-03114: not connected to ORACLE",
+ 3114,
+ True,
+ ),
+ (
+ "DPI-1010: not connected",
+ None,
+ True,
+ ),
+ (
+ "DPI-1010: make sure we read the code",
+ None,
+ True,
+ ),
+ (
+ "DPI-1080: connection was closed by ORA-3113",
+ None,
+ True,
+ ),
+ (
+ "DPI-1234: some other DPI error",
+ None,
+ False,
+ ),
+ )
+ @testing.only_on("oracle+cx_oracle")
+ def test_is_disconnect(self, message, code, expected):
+
+ dialect = testing.db.dialect
+
+ exception_obj = dialect.dbapi.InterfaceError()
+ exception_obj.args = (Exception(message),)
+ exception_obj.args[0].code = code
+
+ eq_(dialect.is_disconnect(exception_obj, None, None), expected)
+
def test_hypothetical_not_implemented_isolation_level(self):
engine = engines.testing_engine()