]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
support cx_Oracle DPI disconnect codes
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 23 Feb 2022 18:43:03 +0000 (13:43 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 24 Feb 2022 13:45:19 +0000 (08:45 -0500)
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.

Fixes: #7748
Change-Id: I4a10d606d512c0d7f9b4653c47ea5734afffb8a5

doc/build/changelog/unreleased_14/7748.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/oracle/cx_oracle.py
test/dialect/oracle/test_dialect.py

diff --git a/doc/build/changelog/unreleased_14/7748.rst b/doc/build/changelog/unreleased_14/7748.rst
new file mode 100644 (file)
index 0000000..d9d6bf2
--- /dev/null
@@ -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.
index 3f8109a1259e279d2e33323e843d14ef8609edb2..a390099aef932c4ebc18f68c513f2eddc82cad45 100644 (file)
@@ -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.
index 5383ffc0c8f83baf1a15326187b3aa19ff0d3824..e827fa56c04b8c9849527be477100b2970bd49b4 100644 (file)
@@ -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()