Skip to content

Commit 1ffaa7f

Browse files
Added support for calling the outconverter when null values are fetched
from the database (#107).
1 parent 4286227 commit 1ffaa7f

File tree

10 files changed

+66
-5
lines changed

10 files changed

+66
-5
lines changed

doc/src/api_manual/cursor.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ Cursor Methods
371371

372372

373373
.. method:: Cursor.var(typ, [size, arraysize, inconverter, outconverter, \
374-
typename, encoding_errors, bypass_decode])
374+
typename, encoding_errors, bypass_decode, convert_nulls])
375375

376376
Creates a variable with the specified characteristics. This method was
377377
designed for use with PL/SQL in/out variables where the length or type
@@ -442,10 +442,20 @@ Cursor Methods
442442
meaning that python-oracledb does not do any decoding. See :ref:`Fetching raw
443443
data <fetching-raw-data>` for more information.
444444

445+
The ``convert_nulls`` parameter, if specified, should be passed a boolean
446+
value. Passing the value ``True`` causes the ``outconverter`` to be called
447+
when a null value is fetched from the database; otherwise, the
448+
``outconverter`` is only called when non-null values are fetched from the
449+
database.
450+
445451
For consistency and compliance with the PEP 8 naming style, the
446452
parameter `encodingErrors` was renamed to `encoding_errors`. The old
447453
name will continue to work as a keyword parameter for a period of time.
448454

455+
.. versionchanged:: 1.4
456+
457+
The ``convert_nulls`` parameter was added.
458+
449459
.. note::
450460

451461
The DB API definition does not define this method.

doc/src/api_manual/variable.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ Variable Attributes
5050
name will continue to work for a period of time.
5151

5252

53+
.. attribute:: Variable.convert_nulls
54+
55+
This read-only attribute returns whether the :data:`~Variable.outconverter`
56+
method is called when null values are fetched from the database.
57+
58+
.. versionadded:: 1.4
59+
5360
.. attribute:: Variable.inconverter
5461

5562
This read-write attribute specifies the method used to convert data from

doc/src/release_notes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ Thick Mode Changes
7474
Common Changes
7575
++++++++++++++
7676

77+
#) Added support for the :attr:`~Variable.outconverter` being called when a
78+
null value is fetched from the database and the new parameter
79+
``convert_nulls`` to the method :meth:`Cursor.var()` is passed the value
80+
``True``
81+
(`issue 107 <https://github.com/oracle/python-oracledb/issues/107>`__).
7782
#) Replaced fixed 7-tuple for the cursor metadata found in
7883
:data:`Cursor.description` with a class which provides additional
7984
information such as the database object type and whether the column

src/oracledb/base_impl.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ cdef class BaseVarImpl:
333333
readonly bint bypass_decode
334334
readonly bint is_array
335335
readonly bint nulls_allowed
336+
readonly bint convert_nulls
336337
public uint32_t num_elements_in_array
337338
readonly DbType dbtype
338339
readonly BaseDbObjectTypeImpl objtype

src/oracledb/cursor.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ def var(self,
743743
typename: str=None,
744744
encoding_errors: str=None,
745745
bypass_decode: bool=False,
746+
convert_nulls: bool=False,
746747
*,
747748
encodingErrors: str=None) -> "Var":
748749
"""
@@ -794,6 +795,9 @@ def var(self,
794795
DB_TYPE_VARCHAR, DB_TYPE_CHAR, DB_TYPE_NVARCHAR, DB_TYPE_NCHAR and
795796
DB_TYPE_LONG to be returned as bytes instead of str, meaning that
796797
oracledb doesn't do any decoding.
798+
799+
The convert_nulls parameter specifies whether the outconverter should
800+
be called when null values are fetched from the database.
797801
"""
798802
self._verify_open()
799803
if typename is not None:
@@ -808,4 +812,5 @@ def var(self,
808812
encoding_errors = encodingErrors
809813
return self._impl.create_var(self.connection, typ, size, arraysize,
810814
inconverter, outconverter,
811-
encoding_errors, bypass_decode)
815+
encoding_errors, bypass_decode,
816+
convert_nulls=convert_nulls)

src/oracledb/impl/base/cursor.pyx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ cdef class BaseCursorImpl:
403403
def create_var(self, object conn, object typ, uint32_t size=0,
404404
uint32_t num_elements=1, object inconverter=None,
405405
object outconverter=None, str encoding_errors=None,
406-
bint bypass_decode=False, bint is_array=False):
406+
bint bypass_decode=False, bint is_array=False,
407+
bint convert_nulls=False):
407408
cdef BaseVarImpl var_impl
408409
var_impl = self._create_var_impl(conn)
409410
var_impl._set_type_info_from_type(typ)
@@ -413,6 +414,7 @@ cdef class BaseCursorImpl:
413414
var_impl.outconverter = outconverter
414415
var_impl.bypass_decode = bypass_decode
415416
var_impl.is_array = is_array
417+
var_impl.convert_nulls = convert_nulls
416418
var_impl._finalize_init()
417419
return PY_TYPE_VAR._from_impl(var_impl)
418420

src/oracledb/impl/thick/var.pyx

Lines changed: 3 additions & 1 deletion
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, 2023, 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
@@ -276,3 +276,5 @@ cdef class ThickVarImpl(BaseVarImpl):
276276
if self.outconverter is not None:
277277
value = self.outconverter(value)
278278
return value
279+
elif self.convert_nulls:
280+
return self.outconverter(None)

src/oracledb/impl/thin/messages.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ cdef class MessageWithData(Message):
494494
num_elements = self.row_index
495495
for i in range(num_elements):
496496
value = var_impl._values[i]
497-
if value is None:
497+
if value is None and not var_impl.convert_nulls:
498498
continue
499499
if isinstance(value, list):
500500
for j, element_value in enumerate(value):

src/oracledb/var.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ def bufferSize(self) -> int:
9090
"""
9191
return self.buffer_size
9292

93+
@property
94+
def convert_nulls(self) -> bool:
95+
"""
96+
This read-only attribute returns whether null values are converted
97+
using the supplied ``outconverter``.
98+
"""
99+
return self._impl.convert_nulls
100+
93101
def getvalue(self, pos: int=0) -> Any:
94102
"""
95103
Return the value at the given position in the variable. For variables

tests/test_3700_var.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,5 +433,26 @@ def test_3729_deprecations(self):
433433
self.assertEqual(var.actualElements, 200)
434434
self.assertEqual(var.numElements, 200)
435435

436+
def test_3730_convert_nulls(self):
437+
"3730 - test calling of outconverter with null values"
438+
def type_handler(cursor, metadata):
439+
return cursor.var(metadata.type_code,
440+
outconverter=lambda v: f"|{v}|" if v else '',
441+
convert_nulls=True, arraysize=cursor.arraysize)
442+
self.cursor.outputtypehandler = type_handler
443+
self.cursor.execute("""
444+
select 'First - A', 'First - B' from dual
445+
union all
446+
select 'Second - A', null from dual
447+
union all
448+
select null, 'Third - B' from dual""")
449+
rows = self.cursor.fetchall()
450+
expected_rows = [
451+
('|First - A|', '|First - B|'),
452+
('|Second - A|', ''),
453+
('', '|Third - B|')
454+
]
455+
self.assertEqual(rows, expected_rows)
456+
436457
if __name__ == "__main__":
437458
test_env.run_test_cases()

0 commit comments

Comments
 (0)