Skip to content

Commit 3ea70a4

Browse files
Fixed regression from cx_Oracle which ignored the value of the
"encoding_errors" parameter when creating variables by calling the method Cursor.var() (#279).
1 parent 659db44 commit 3ea70a4

File tree

8 files changed

+52
-13
lines changed

8 files changed

+52
-13
lines changed

doc/src/release_notes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ Common Changes
3232
#) Fixed regression which prevented a null value from being set on DbObject
3333
attributes or used as elements of collections
3434
(`issue 273 <https://github.com/oracle/python-oracledb/issues/273>`__).
35+
#) Fixed regression from cx_Oracle which ignored the value of the
36+
``encoding_errors`` parameter when creating variables by calling the method
37+
:meth:`Cursor.var()`
38+
(`issue 279 <https://github.com/oracle/python-oracledb/issues/279>`__).
3539
#) Corrected typing declarations.
3640
#) Bumped minimum requirement of Cython to 3.0.
3741

src/oracledb/base_impl.pxd

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2024, 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
@@ -342,6 +342,7 @@ cdef class BaseVarImpl:
342342
readonly uint32_t size
343343
readonly uint32_t buffer_size
344344
readonly bint bypass_decode
345+
readonly str encoding_errors
345346
readonly bint is_array
346347
readonly bint nulls_allowed
347348
readonly bint convert_nulls

src/oracledb/impl/base/cursor.pyx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ cdef class BaseCursorImpl:
419419
var_impl.inconverter = inconverter
420420
var_impl.outconverter = outconverter
421421
var_impl.bypass_decode = bypass_decode
422+
var_impl.encoding_errors = encoding_errors
422423
var_impl.is_array = is_array
423424
var_impl.convert_nulls = convert_nulls
424425
var_impl._finalize_init()

src/oracledb/impl/thick/utils.pyx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2024, 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
@@ -261,7 +261,8 @@ cdef object _convert_to_python(ThickConnImpl conn_impl, DbType dbtype,
261261
ThickDbObjectTypeImpl obj_type_impl,
262262
dpiDataBuffer *dbvalue,
263263
int preferred_num_type=NUM_TYPE_FLOAT,
264-
bint bypass_decode=False):
264+
bint bypass_decode=False,
265+
const char* encoding_errors=NULL):
265266
cdef:
266267
uint32_t oracle_type = dbtype.num
267268
ThickDbObjectImpl obj_impl
@@ -282,7 +283,7 @@ cdef object _convert_to_python(ThickConnImpl conn_impl, DbType dbtype,
282283
or oracle_type == DPI_ORACLE_TYPE_LONG_NVARCHAR \
283284
or oracle_type == DPI_ORACLE_TYPE_XMLTYPE:
284285
as_bytes = &dbvalue.asBytes
285-
return as_bytes.ptr[:as_bytes.length].decode()
286+
return as_bytes.ptr[:as_bytes.length].decode("utf-8", encoding_errors)
286287
elif oracle_type == DPI_ORACLE_TYPE_NUMBER:
287288
as_bytes = &dbvalue.asBytes
288289
if preferred_num_type == NUM_TYPE_INT \

src/oracledb/impl/thick/var.pyx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2024, 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
@@ -264,15 +264,21 @@ cdef class ThickVarImpl(BaseVarImpl):
264264
Transforms a single element from the value supplied by ODPI-C to its
265265
equivalent Python value.
266266
"""
267-
cdef object value
267+
cdef:
268+
const char *encoding_errors = NULL
269+
bytes encoding_errors_bytes
270+
object value
268271
data = &data[pos]
269272
if not data.isNull:
270273
if self._native_type_num == DPI_NATIVE_TYPE_STMT:
271274
return self._get_cursor_value(&data.value)
275+
if self.encoding_errors is not None:
276+
encoding_errors_bytes = self.encoding_errors.encode()
277+
encoding_errors = encoding_errors_bytes
272278
value = _convert_to_python(self._conn_impl, self.dbtype,
273279
self.objtype, &data.value,
274280
self._preferred_num_type,
275-
self.bypass_decode)
281+
self.bypass_decode, encoding_errors)
276282
if self.outconverter is not None:
277283
value = self.outconverter(value)
278284
return value

src/oracledb/impl/thin/buffer.pyx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2024, 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
@@ -765,7 +765,7 @@ cdef class Buffer:
765765
self._pos = end_pos + 1
766766
return self._data[start_pos:self._pos]
767767

768-
cdef object read_str(self, int csfrm):
768+
cdef object read_str(self, int csfrm, const char* encoding_errors=NULL):
769769
"""
770770
Reads bytes from the buffer and decodes them into a string following
771771
the supplied character set form.
@@ -776,8 +776,9 @@ cdef class Buffer:
776776
self.read_raw_bytes_and_length(&ptr, &num_bytes)
777777
if ptr != NULL:
778778
if csfrm == TNS_CS_IMPLICIT:
779-
return ptr[:num_bytes].decode()
780-
return ptr[:num_bytes].decode(TNS_ENCODING_UTF16)
779+
return ptr[:num_bytes].decode(TNS_ENCODING_UTF8,
780+
encoding_errors)
781+
return ptr[:num_bytes].decode(TNS_ENCODING_UTF16, encoding_errors)
781782

782783
cdef int read_ub1(self, uint8_t *value) except -1:
783784
"""

src/oracledb/impl/thin/messages.pyx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,8 @@ cdef class MessageWithData(Message):
496496
ThinVarImpl var_impl, uint32_t pos):
497497
cdef:
498498
uint8_t num_bytes, ora_type_num, csfrm
499+
const char* encoding_errors = NULL
500+
bytes encoding_errors_bytes
499501
ThinDbObjectTypeImpl typ_impl
500502
BaseThinCursorImpl cursor_impl
501503
object column_value = None
@@ -525,7 +527,10 @@ cdef class MessageWithData(Message):
525527
or ora_type_num == TNS_DATA_TYPE_LONG:
526528
if csfrm == TNS_CS_NCHAR:
527529
buf._caps._check_ncharset_id()
528-
column_value = buf.read_str(csfrm)
530+
if var_impl.encoding_errors is not None:
531+
encoding_errors_bytes = var_impl.encoding_errors.encode()
532+
encoding_errors = encoding_errors_bytes
533+
column_value = buf.read_str(csfrm, encoding_errors)
529534
elif ora_type_num == TNS_DATA_TYPE_RAW \
530535
or ora_type_num == TNS_DATA_TYPE_LONG_RAW:
531536
column_value = buf.read_bytes()

tests/test_3800_typehandler.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -----------------------------------------------------------------------------
2-
# Copyright (c) 2021, 2023, Oracle and/or its affiliates.
2+
# Copyright (c) 2021, 2024, 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
@@ -281,6 +281,26 @@ def output_type_handler(cursor, metadata):
281281
self.cursor.execute("select * from TestJson")
282282
self.assertEqual(self.cursor.fetchall(), data_to_insert)
283283

284+
def test_3807(self):
285+
"3807 - output type handler for encoding errors"
286+
287+
def output_type_handler(cursor, metadata):
288+
if metadata.type_code is oracledb.DB_TYPE_VARCHAR:
289+
return cursor.var(
290+
metadata.type_code,
291+
arraysize=cursor.arraysize,
292+
encoding_errors="replace",
293+
)
294+
295+
self.cursor.outputtypehandler = output_type_handler
296+
self.cursor.execute(
297+
"select utl_raw.cast_to_varchar2('41ab42cd43ef') from dual"
298+
)
299+
(result,) = self.cursor.fetchone()
300+
rc = chr(0xFFFD)
301+
expected_result = f"A{rc}B{rc}C{rc}"
302+
self.assertEqual(result, expected_result)
303+
284304

285305
if __name__ == "__main__":
286306
test_env.run_test_cases()

0 commit comments

Comments
 (0)