]> 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:46:42 +0000 (08:46 -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
(cherry picked from commit 8f9e971f10dee0614054671e0c284f0acace2d04)

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 104b88bc0de51b035a2f69e42bdf3df69e56bf4b..4c89ed7355d27edb0d9ba56526e784eb94cc5012 100644 (file)
@@ -1318,7 +1318,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
@@ -1326,9 +1333,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 acabfc8bb80b91d3705991fb556ef5c2dd52b82d..d65a6d2b53aa110b62471a95b49bf81ebfceddef 100644 (file)
@@ -58,7 +58,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(
@@ -66,13 +66,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()