Skip to content

Commit 382e64d

Browse files
Fixed bug that caused oracledb._Error.isrecoverable to always be False.
1 parent 7aebce2 commit 382e64d

File tree

6 files changed

+74
-10
lines changed

6 files changed

+74
-10
lines changed

doc/src/api_manual/module.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4295,8 +4295,10 @@ See :ref:`exception` for usage information.
42954295
.. attribute:: _Error.isrecoverable
42964296

42974297
Boolean attribute representing whether the error is recoverable or not.
4298-
This is False in all cases unless both Oracle Database 12.1 (or later) and
4299-
Oracle Client 12.1 (or later) are being used.
4298+
This requires Oracle Database 12.1 (or later). If python-oracledb Thick
4299+
mode is used, then Oracle Client 12.1 (or later) is also required.
4300+
4301+
See :ref:`tg` for more information.
43004302

43014303
.. _oracledbplugins:
43024304

doc/src/release_notes.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ Thin Mode Changes
3333
#) The thread that closes connection pools on interpreter shutdown is now only
3434
started when the first pool is created and not at module import
3535
(`issue 426 <https://github.com/oracle/python-oracledb/issues/426>`__).
36-
#) Added support for Transaction Guard by adding support to get the value of
37-
:attr:`Connection.ltxid`.
36+
#) Added support for Transaction Guard by adding support to get the values of
37+
:attr:`Connection.ltxid` and :attr:`oracledb._Error.isrecoverable`.
3838
#) Fixed hang when attempting to use pipelining against a database that
3939
doesn't support the end of response flag.
4040
#) Fixed hang when using asyncio and a connection is unexpectedly closed by
@@ -66,6 +66,9 @@ Thin Mode Changes
6666
Thick Mode Changes
6767
++++++++++++++++++
6868

69+
#) Fixed bug that caused :attr:`oracledb._Error.isrecoverable` to always be
70+
`False`.
71+
6972
Common Changes
7073
++++++++++++++
7174

src/oracledb/impl/thin/messages.pyx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,39 @@ cdef class Message:
6565
connection" error is detected, the connection is forced closed
6666
immediately.
6767
"""
68+
cdef bint is_recoverable = False
6869
if self.error_occurred:
70+
if self.error_info.num in (
71+
28, # session has been terminated
72+
31, # session marked for kill
73+
376, # file %s cannot be read at this time
74+
603, # ORACLE server session terminated
75+
1012, # not logged on
76+
1033, # ORACLE initialization or shutdown in progress
77+
1034, # the Oracle instance is not available for use
78+
1089, # immediate shutdown or close in progress
79+
1090, # shutdown in progress
80+
1092, # ORACLE instance terminated
81+
1115, # IO error reading block from file %s (block # %s)
82+
2396, # exceeded maximum idle time
83+
3113, # end-of-file on communication channel
84+
3114, # not connected to ORACLE
85+
3135, # connection lost contact
86+
12153, # TNS:not connected
87+
12514, # Service %s is not registered with the listener
88+
12537, # TNS:connection closed
89+
12547, # TNS:lost contact
90+
12570, # TNS:packet reader failure
91+
12571, # TNS:packet writer failure
92+
12583, # TNS:no reader
93+
12757, # instance does not currently know of requested service
94+
16456, # missing or invalid value
95+
):
96+
is_recoverable = True
6997
error = errors._Error(self.error_info.message,
7098
code=self.error_info.num,
71-
offset=self.error_info.pos)
99+
offset=self.error_info.pos,
100+
isrecoverable=is_recoverable)
72101
if error.is_session_dead:
73102
self.conn_impl._protocol._force_close()
74103
raise error.exc_type(error)

tests/test_1700_error.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -----------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2024, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2025, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -58,6 +58,7 @@ def test_1701(self):
5858
self.assertEqual(error_obj.code, 20101)
5959
self.assertEqual(error_obj.offset, 0)
6060
self.assertIsInstance(error_obj.isrecoverable, bool)
61+
self.assertFalse(error_obj.isrecoverable)
6162
new_error_obj = pickle.loads(pickle.dumps(error_obj))
6263
self.assertIsInstance(new_error_obj, oracledb._Error)
6364
self.assertEqual(new_error_obj.message, error_obj.message)
@@ -198,6 +199,20 @@ def test_1708(self):
198199
self.assertEqual(error_obj.full_code, f"ORA-{code}")
199200
self.assertTrue("Help:" not in error_obj.message)
200201

202+
@unittest.skipIf(test_env.get_is_drcp(), "not supported with DRCP")
203+
def test_1709(self):
204+
"1709 - error from killed connection is deemed recoverable"
205+
admin_conn = test_env.get_admin_connection()
206+
conn = test_env.get_connection()
207+
sid, serial = self.get_sid_serial(conn)
208+
with admin_conn.cursor() as admin_cursor:
209+
sql = f"alter system kill session '{sid},{serial}'"
210+
admin_cursor.execute(sql)
211+
with self.assertRaisesFullCode("DPY-4011") as cm:
212+
with conn.cursor() as cursor:
213+
cursor.execute("select user from dual")
214+
self.assertTrue(cm.error_obj.isrecoverable)
215+
201216

202217
if __name__ == "__main__":
203218
test_env.run_test_cases()

tests/test_6800_error_async.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -----------------------------------------------------------------------------
2-
# Copyright (c) 2023, 2024, Oracle and/or its affiliates.
2+
# Copyright (c) 2023, 2025, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -61,6 +61,7 @@ async def test_6801(self):
6161
self.assertEqual(error_obj.code, 20101)
6262
self.assertEqual(error_obj.offset, 0)
6363
self.assertIsInstance(error_obj.isrecoverable, bool)
64+
self.assertFalse(error_obj.isrecoverable)
6465
new_error_obj = pickle.loads(pickle.dumps(error_obj))
6566
self.assertIsInstance(new_error_obj, oracledb._Error)
6667
self.assertEqual(new_error_obj.message, error_obj.message)
@@ -206,6 +207,20 @@ async def test_6808(self):
206207
self.assertEqual(result.warning.full_code, "DPY-7000")
207208
await self.cursor.execute(f"drop procedure {proc_name}")
208209

210+
@unittest.skipIf(test_env.get_is_drcp(), "not supported with DRCP")
211+
async def test_6809(self):
212+
"6809 - error from killed connection is deemed recoverable"
213+
admin_conn = await test_env.get_admin_connection_async()
214+
conn = await test_env.get_connection_async()
215+
sid, serial = await self.get_sid_serial(conn)
216+
with admin_conn.cursor() as admin_cursor:
217+
sql = f"alter system kill session '{sid},{serial}'"
218+
await admin_cursor.execute(sql)
219+
with self.assertRaisesFullCode("DPY-4011") as cm:
220+
with conn.cursor() as cursor:
221+
await cursor.execute("select user from dual")
222+
self.assertTrue(cm.error_obj.isrecoverable)
223+
209224

210225
if __name__ == "__main__":
211226
test_env.run_test_cases()

tests/test_env.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,11 +552,11 @@ def __exit__(self, exc_type, exc_value, tb):
552552
if not issubclass(exc_type, oracledb.Error):
553553
return False
554554
if issubclass(exc_type, oracledb.Error):
555-
error_obj = exc_value.args[0]
556-
if error_obj.full_code not in self.full_codes:
555+
self.error_obj = exc_value.args[0]
556+
if self.error_obj.full_code not in self.full_codes:
557557
message = (
558558
f"{self.message_fragment} should have been raised but "
559-
f'"{error_obj.full_code}" was raised instead.'
559+
f'"{self.error_obj.full_code}" was raised instead.'
560560
)
561561
raise AssertionError(message)
562562
return True

0 commit comments

Comments
 (0)