Skip to content

Commit e4832f2

Browse files
Fixed bug when using asyncio and calling a stored procedure with data
that exceeds 32767 bytes in length (#441).
1 parent 7e8b89c commit e4832f2

File tree

6 files changed

+99
-8
lines changed

6 files changed

+99
-8
lines changed

doc/src/release_notes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ Thin Mode Changes
4242
doesn't support the end of response flag.
4343
#) Fixed hang when using asyncio and a connection is unexpectedly closed by
4444
the database.
45+
#) Fixed bug when using :ref:`asyncio <concurrentprogramming>` and calling a
46+
stored procedure with data that exceeds 32767 bytes in length
47+
(`issue 441 <https://github.com/oracle/python-oracledb/issues/441>`__).
4548
#) Error ``DPY-6002: The distinguished name (DN) on the server certificate
4649
does not match the expected value: "{expected_dn}"`` now shows the expected
4750
value.

src/oracledb/impl/thin/cursor.pyx

Lines changed: 20 additions & 3 deletions
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
@@ -269,13 +269,30 @@ cdef class AsyncThinCursorImpl(BaseThinCursorImpl):
269269
message = self._create_message(FetchMessage, cursor)
270270
await self._conn_impl._protocol._process_single_message(message)
271271

272+
async def _preprocess_execute_async(self, object conn):
273+
"""
274+
Performs the necessary steps required before actually executing the
275+
statement associated with the cursor.
276+
"""
277+
cdef:
278+
ThinVarImpl var_impl
279+
BindInfo bind_info
280+
ssize_t idx
281+
self._preprocess_execute(conn)
282+
for bind_info in self._statement._bind_info_list:
283+
var_impl = bind_info._bind_var_impl
284+
if var_impl._coroutine_indexes is not None:
285+
for idx in var_impl._coroutine_indexes:
286+
var_impl._values[idx] = await var_impl._values[idx]
287+
var_impl._coroutine_indexes = None
288+
272289
async def execute(self, cursor):
273290
cdef:
274291
object conn = cursor.connection
275292
BaseAsyncProtocol protocol
276293
MessageWithData message
277294
protocol = <BaseAsyncProtocol> self._conn_impl._protocol
278-
self._preprocess_execute(conn)
295+
await self._preprocess_execute_async(conn)
279296
message = self._create_message(ExecuteMessage, cursor)
280297
message.num_execs = 1
281298
await protocol._process_single_message(message)
@@ -294,7 +311,7 @@ cdef class AsyncThinCursorImpl(BaseThinCursorImpl):
294311

295312
# set up message to send
296313
protocol = <BaseAsyncProtocol> self._conn_impl._protocol
297-
self._preprocess_execute(cursor.connection)
314+
await self._preprocess_execute_async(cursor.connection)
298315
message = self._create_message(ExecuteMessage, cursor)
299316
message.num_execs = num_execs
300317
message.batcherrors = batcherrors

src/oracledb/impl/thin/var.pyx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
cdef class ThinVarImpl(BaseVarImpl):
3333
cdef:
3434
object _last_raw_value
35+
list _coroutine_indexes
3536

3637
cdef int _bind(self, object conn, BaseCursorImpl cursor_impl,
3738
uint32_t num_execs, object name, uint32_t pos) except -1:
@@ -44,10 +45,11 @@ cdef class ThinVarImpl(BaseVarImpl):
4445
ssize_t idx, num_binds, num_vars
4546
BindInfo bind_info
4647
str normalized_name
47-
object value, lob
48+
bint is_async
49+
object value
4850

4951
# for PL/SQL blocks, if the size of a string or bytes object exceeds
50-
# 32,767 bytes it must be converted to a BLOB/CLOB; and out converter
52+
# 32,767 bytes it must be converted to a BLOB/CLOB; an out converter
5153
# needs to be established as well to return the string in the way that
5254
# the user expects to get it
5355
if stmt._is_plsql and metadata.max_size > 32767:
@@ -67,13 +69,18 @@ cdef class ThinVarImpl(BaseVarImpl):
6769
self.outconverter = converter
6870

6971
# for variables containing LOBs, create temporary LOBs, if needed
72+
is_async = thin_cursor_impl._conn_impl._protocol._transport._is_async
7073
if metadata.dbtype._ora_type_num == ORA_TYPE_NUM_CLOB \
7174
or metadata.dbtype._ora_type_num == ORA_TYPE_NUM_BLOB:
7275
for idx, value in enumerate(self._values):
7376
if value is not None \
7477
and not isinstance(value, (PY_TYPE_LOB,
7578
PY_TYPE_ASYNC_LOB)):
7679
self._values[idx] = conn.createlob(metadata.dbtype, value)
80+
if is_async:
81+
if self._coroutine_indexes is None:
82+
self._coroutine_indexes = []
83+
self._coroutine_indexes.append(idx)
7784

7885
# bind by name
7986
if name is not None:

tests/sql/create_schema.sql

Lines changed: 37 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
@@ -1417,3 +1417,39 @@ create or replace package body &main_user..pkg_SessionCallback as
14171417

14181418
end;
14191419
/
1420+
1421+
create or replace package &main_user..pkg_TestLOBs as
1422+
1423+
procedure GetSize(
1424+
a_BLOB blob,
1425+
a_Size out number
1426+
);
1427+
1428+
procedure GetSize(
1429+
a_CLOB clob,
1430+
a_Size out number
1431+
);
1432+
1433+
end;
1434+
/
1435+
1436+
create or replace package body &main_user..pkg_TestLOBs as
1437+
1438+
procedure GetSize(
1439+
a_BLOB blob,
1440+
a_Size out number
1441+
) is
1442+
begin
1443+
a_Size := dbms_lob.getlength(a_BLOB);
1444+
end;
1445+
1446+
procedure GetSize(
1447+
a_CLOB clob,
1448+
a_Size out number
1449+
) is
1450+
begin
1451+
a_Size := dbms_lob.getlength(a_CLOB);
1452+
end;
1453+
1454+
end;
1455+
/

tests/test_4100_cursor_callproc.py

Lines changed: 15 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
@@ -444,6 +444,20 @@ def test_4124(self):
444444
with self.assertRaisesFullCode("ORA-06550"):
445445
self.cursor.callproc("func_Test2", ("hello", 5, True))
446446

447+
def test_4125(self):
448+
"4125 - test calling a procedure with a string > 32767 characters"
449+
data = "4125" * 16000
450+
size_var = self.cursor.var(int)
451+
self.cursor.callproc("pkg_TestLobs.GetSize", [data, size_var])
452+
self.assertEqual(size_var.getvalue(), len(data))
453+
454+
def test_4126(self):
455+
"4125 - test calling a procedure with raw data > 32767 bytes"
456+
data = b"4126" * 16250
457+
size_var = self.cursor.var(int)
458+
self.cursor.callproc("pkg_TestLobs.GetSize", [data, size_var])
459+
self.assertEqual(size_var.getvalue(), len(data))
460+
447461

448462
if __name__ == "__main__":
449463
test_env.run_test_cases()

tests/test_6200_cursor_callproc_async.py

Lines changed: 15 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
@@ -119,6 +119,20 @@ async def test_6208(self):
119119
"func_Test", oracledb.NUMBER, [], kwargs
120120
)
121121

122+
async def test_6209(self):
123+
"6209 - test calling a procedure with a string > 32767 characters"
124+
data = "6209" * 16000
125+
size_var = self.cursor.var(int)
126+
await self.cursor.callproc("pkg_TestLobs.GetSize", [data, size_var])
127+
self.assertEqual(size_var.getvalue(), len(data))
128+
129+
async def test_6210(self):
130+
"6210 - test calling a procedure with raw data > 32767 bytes"
131+
data = b"6210" * 16250
132+
size_var = self.cursor.var(int)
133+
await self.cursor.callproc("pkg_TestLobs.GetSize", [data, size_var])
134+
self.assertEqual(size_var.getvalue(), len(data))
135+
122136

123137
if __name__ == "__main__":
124138
test_env.run_test_cases()

0 commit comments

Comments
 (0)