From: Mike Bayer Date: Wed, 23 Feb 2022 18:43:03 +0000 (-0500) Subject: support cx_Oracle DPI disconnect codes X-Git-Tag: rel_2_0_0b1~467^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8f9e971f10dee0614054671e0c284f0acace2d04;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git support cx_Oracle DPI disconnect codes 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 --- diff --git a/doc/build/changelog/unreleased_14/7748.rst b/doc/build/changelog/unreleased_14/7748.rst new file mode 100644 index 0000000000..d9d6bf2361 --- /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 3f8109a125..a390099aef 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 5383ffc0c8..e827fa56c0 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()