Skip to content

Commit d7003f2

Browse files
Fixed bug when the fetched data type of a column changes from
oracledb.DB_TYPE_LONG or oracledb.DB_TYPE_LONG_RAW to to a different compatible type (#424).
1 parent f60ea44 commit d7003f2

File tree

3 files changed

+152
-23
lines changed

3 files changed

+152
-23
lines changed

doc/src/release_notes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ Thin Mode Changes
2626
id=GUID-490A0B3B-FEF3-425A-81B0-6FA29D4B8C0E>`__ parameter to TRUE),
2727
python-oracledb no longer attempts to do so
2828
(`issue 419 <https://github.com/oracle/python-oracledb/issues/419>`__).
29+
#) Fixed bug when the fetched data type of a column changes from
30+
:data:`oracledb.DB_TYPE_LONG` or :data:`oracledb.DB_TYPE_LONG_RAW` to
31+
to a different compatible type
32+
(`issue 424 <https://github.com/oracle/python-oracledb/issues/424>`__).
2933
#) Error ``DPY-6002: The distinguished name (DN) on the server certificate
3034
does not match the expected value: "{expected_dn}"`` now shows the expected
3135
value.

src/oracledb/impl/thin/messages.pyx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -370,23 +370,36 @@ cdef class MessageWithData(Message):
370370
previously returned was CHAR/VARCHAR/RAW (or the equivalent long
371371
types), then the server returns the data as LONG (RAW), similarly to
372372
what happens when a define is done to return CLOB/BLOB as string/bytes.
373-
Detect this situation and adjust the fetch type appropriately.
373+
In addition, if the data type returned was previously LONG or LONG RAW,
374+
and the new type is VARCHAR or RAW, the database continues to return
375+
LONG or LONG RAW. Detect these situations and adjust the fetch type
376+
appropriately.
374377
"""
375378
cdef:
376379
OracleMetadata prev_metadata = prev_var_impl._fetch_metadata
377-
uint8_t csfrm = prev_metadata.dbtype._csfrm
378-
uint8_t type_num
379-
if metadata.dbtype._ora_type_num == ORA_TYPE_NUM_CLOB \
380+
uint8_t type_num, csfrm
381+
if metadata.dbtype._ora_type_num == ORA_TYPE_NUM_VARCHAR \
382+
and prev_metadata.dbtype._ora_type_num == ORA_TYPE_NUM_LONG:
383+
type_num = ORA_TYPE_NUM_LONG
384+
csfrm = metadata.dbtype._csfrm
385+
metadata.dbtype = DbType._from_ora_type_and_csfrm(type_num, csfrm)
386+
387+
elif metadata.dbtype._ora_type_num == ORA_TYPE_NUM_RAW \
388+
and prev_metadata.dbtype._ora_type_num == ORA_TYPE_NUM_LONG_RAW:
389+
type_num = ORA_TYPE_NUM_LONG_RAW
390+
metadata.dbtype = DbType._from_ora_type_and_csfrm(type_num, 0)
391+
elif metadata.dbtype._ora_type_num == ORA_TYPE_NUM_CLOB \
380392
and prev_metadata.dbtype._ora_type_num in \
381393
(ORA_TYPE_NUM_CHAR, ORA_TYPE_NUM_VARCHAR,
382394
ORA_TYPE_NUM_LONG):
383395
type_num = ORA_TYPE_NUM_LONG
396+
csfrm = prev_metadata.dbtype._csfrm
384397
metadata.dbtype = DbType._from_ora_type_and_csfrm(type_num, csfrm)
385398
elif metadata.dbtype._ora_type_num == ORA_TYPE_NUM_BLOB \
386399
and prev_metadata.dbtype._ora_type_num in \
387400
(ORA_TYPE_NUM_RAW, ORA_TYPE_NUM_LONG_RAW):
388401
type_num = ORA_TYPE_NUM_LONG_RAW
389-
metadata.dbtype = DbType._from_ora_type_and_csfrm(type_num, csfrm)
402+
metadata.dbtype = DbType._from_ora_type_and_csfrm(type_num, 0)
390403

391404
cdef object _create_cursor_from_describe(self, ReadBuffer buf,
392405
object cursor=None):

tests/test_4600_type_changes.py

Lines changed: 130 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import datetime
3030
import unittest
3131

32+
import oracledb
3233
import test_env
3334

3435

@@ -40,27 +41,33 @@ def __test_type_change(
4041
query_frag_2,
4142
query_value_2,
4243
table_name="dual",
44+
type_handler=None,
4345
):
4446
if test_env.get_is_implicit_pooling():
4547
self.skipTest("sessions can change with implicit pooling")
46-
self.cursor.execute(
47-
f"""
48-
create or replace view TestTypesChanged as
49-
select {query_frag_1} as value
50-
from {table_name}
51-
"""
52-
)
53-
self.cursor.execute("select * from TestTypesChanged")
54-
self.assertEqual(self.cursor.fetchall(), [(query_value_1,)])
55-
self.cursor.execute(
56-
f"""
57-
create or replace view TestTypesChanged as
58-
select {query_frag_2} as value
59-
from dual
60-
"""
61-
)
62-
self.cursor.execute("select * from TestTypesChanged")
63-
self.assertEqual(self.cursor.fetchall(), [(query_value_2,)])
48+
orig_type_handler = self.conn.outputtypehandler
49+
self.conn.outputtypehandler = type_handler
50+
try:
51+
self.cursor.execute(
52+
f"""
53+
create or replace view TestTypesChanged as
54+
select {query_frag_1} as value
55+
from {table_name}
56+
"""
57+
)
58+
self.cursor.execute("select * from TestTypesChanged")
59+
self.assertEqual(self.cursor.fetchall(), [(query_value_1,)])
60+
self.cursor.execute(
61+
f"""
62+
create or replace view TestTypesChanged as
63+
select {query_frag_2} as value
64+
from dual
65+
"""
66+
)
67+
self.cursor.execute("select * from TestTypesChanged")
68+
self.assertEqual(self.cursor.fetchall(), [(query_value_2,)])
69+
finally:
70+
self.conn.outputtypehandler = orig_type_handler
6471

6572
@unittest.skipIf(
6673
not test_env.get_is_thin(),
@@ -273,6 +280,111 @@ def test_4616(self):
273280
datetime.datetime(2022, 1, 5, 0, 0),
274281
)
275282

283+
@unittest.skipIf(
284+
not test_env.get_is_thin(),
285+
"thick mode doesn't support this type change",
286+
)
287+
def test_4617(self):
288+
"4617 - test data type changing from CLOB to VARCHAR"
289+
290+
def type_handler(cursor, metadata):
291+
if metadata.type_code is oracledb.DB_TYPE_CLOB:
292+
return cursor.var(
293+
oracledb.DB_TYPE_VARCHAR,
294+
size=32768,
295+
arraysize=cursor.arraysize,
296+
)
297+
298+
self.__test_type_change(
299+
"to_clob('clob_4617')",
300+
"clob_4617",
301+
"cast('string_4617' as VARCHAR2(15))",
302+
"string_4617",
303+
type_handler=type_handler,
304+
)
305+
306+
@unittest.skipIf(
307+
not test_env.get_is_thin(),
308+
"thick mode doesn't support this type change",
309+
)
310+
def test_4618(self):
311+
"4618 - test data type changing from NCLOB to NVARCHAR"
312+
313+
def type_handler(cursor, metadata):
314+
if metadata.type_code is oracledb.DB_TYPE_NCLOB:
315+
return cursor.var(
316+
oracledb.DB_TYPE_NVARCHAR,
317+
size=32768,
318+
arraysize=cursor.arraysize,
319+
)
320+
321+
self.__test_type_change(
322+
"to_nclob('nclob_4618')",
323+
"nclob_4618",
324+
"cast('nstring_4618' as NVARCHAR2(15))",
325+
"nstring_4618",
326+
type_handler=type_handler,
327+
)
328+
329+
@unittest.skipIf(
330+
not test_env.get_is_thin(),
331+
"thick mode doesn't support this type change",
332+
)
333+
def test_4619(self):
334+
"4619 - test data type changing from CLOB to NVARCHAR"
335+
336+
def type_handler(cursor, metadata):
337+
if metadata.type_code is oracledb.DB_TYPE_CLOB:
338+
return cursor.var(
339+
oracledb.DB_TYPE_NVARCHAR,
340+
size=32768,
341+
arraysize=cursor.arraysize,
342+
)
343+
344+
self.__test_type_change(
345+
"to_clob('clob_4619')",
346+
"clob_4619",
347+
"cast('string_4619' as VARCHAR2(15))",
348+
"string_4619",
349+
type_handler=type_handler,
350+
)
351+
352+
@unittest.skipIf(
353+
not test_env.get_is_thin(),
354+
"thick mode doesn't support this type change",
355+
)
356+
def test_4620(self):
357+
"4620 - test data type changing from BLOB to RAW"
358+
359+
def type_handler(cursor, metadata):
360+
if metadata.type_code is oracledb.DB_TYPE_BLOB:
361+
return cursor.var(
362+
oracledb.DB_TYPE_RAW,
363+
size=32768,
364+
arraysize=cursor.arraysize,
365+
)
366+
367+
self.__test_type_change(
368+
"to_blob(utl_raw.cast_to_raw('blob_4620'))",
369+
b"blob_4620",
370+
"utl_raw.cast_to_raw('string_4620')",
371+
b"string_4620",
372+
type_handler=type_handler,
373+
)
374+
375+
@unittest.skipIf(
376+
not test_env.get_is_thin(),
377+
"thick mode doesn't support this type change",
378+
)
379+
def test_4621(self):
380+
"4621 - test data type changing from NVARCHAR to CLOB"
381+
self.__test_type_change(
382+
"cast('string_4621' as NVARCHAR2(15))",
383+
"string_4621",
384+
"to_clob('clob_4621')",
385+
"clob_4621",
386+
)
387+
276388

277389
if __name__ == "__main__":
278390
test_env.run_test_cases()

0 commit comments

Comments
 (0)