Added support for calling the outconverter when null values are fetched
from the database (#107).
This commit is contained in:
parent
428622760e
commit
1ffaa7fe00
|
@ -371,7 +371,7 @@ Cursor Methods
|
|||
|
||||
|
||||
.. method:: Cursor.var(typ, [size, arraysize, inconverter, outconverter, \
|
||||
typename, encoding_errors, bypass_decode])
|
||||
typename, encoding_errors, bypass_decode, convert_nulls])
|
||||
|
||||
Creates a variable with the specified characteristics. This method was
|
||||
designed for use with PL/SQL in/out variables where the length or type
|
||||
|
@ -442,10 +442,20 @@ Cursor Methods
|
|||
meaning that python-oracledb does not do any decoding. See :ref:`Fetching raw
|
||||
data <fetching-raw-data>` for more information.
|
||||
|
||||
The ``convert_nulls`` parameter, if specified, should be passed a boolean
|
||||
value. Passing the value ``True`` causes the ``outconverter`` to be called
|
||||
when a null value is fetched from the database; otherwise, the
|
||||
``outconverter`` is only called when non-null values are fetched from the
|
||||
database.
|
||||
|
||||
For consistency and compliance with the PEP 8 naming style, the
|
||||
parameter `encodingErrors` was renamed to `encoding_errors`. The old
|
||||
name will continue to work as a keyword parameter for a period of time.
|
||||
|
||||
.. versionchanged:: 1.4
|
||||
|
||||
The ``convert_nulls`` parameter was added.
|
||||
|
||||
.. note::
|
||||
|
||||
The DB API definition does not define this method.
|
||||
|
|
|
@ -50,6 +50,13 @@ Variable Attributes
|
|||
name will continue to work for a period of time.
|
||||
|
||||
|
||||
.. attribute:: Variable.convert_nulls
|
||||
|
||||
This read-only attribute returns whether the :data:`~Variable.outconverter`
|
||||
method is called when null values are fetched from the database.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
.. attribute:: Variable.inconverter
|
||||
|
||||
This read-write attribute specifies the method used to convert data from
|
||||
|
|
|
@ -74,6 +74,11 @@ Thick Mode Changes
|
|||
Common Changes
|
||||
++++++++++++++
|
||||
|
||||
#) Added support for the :attr:`~Variable.outconverter` being called when a
|
||||
null value is fetched from the database and the new parameter
|
||||
``convert_nulls`` to the method :meth:`Cursor.var()` is passed the value
|
||||
``True``
|
||||
(`issue 107 <https://github.com/oracle/python-oracledb/issues/107>`__).
|
||||
#) Replaced fixed 7-tuple for the cursor metadata found in
|
||||
:data:`Cursor.description` with a class which provides additional
|
||||
information such as the database object type and whether the column
|
||||
|
|
|
@ -333,6 +333,7 @@ cdef class BaseVarImpl:
|
|||
readonly bint bypass_decode
|
||||
readonly bint is_array
|
||||
readonly bint nulls_allowed
|
||||
readonly bint convert_nulls
|
||||
public uint32_t num_elements_in_array
|
||||
readonly DbType dbtype
|
||||
readonly BaseDbObjectTypeImpl objtype
|
||||
|
|
|
@ -743,6 +743,7 @@ class Cursor:
|
|||
typename: str=None,
|
||||
encoding_errors: str=None,
|
||||
bypass_decode: bool=False,
|
||||
convert_nulls: bool=False,
|
||||
*,
|
||||
encodingErrors: str=None) -> "Var":
|
||||
"""
|
||||
|
@ -794,6 +795,9 @@ class Cursor:
|
|||
DB_TYPE_VARCHAR, DB_TYPE_CHAR, DB_TYPE_NVARCHAR, DB_TYPE_NCHAR and
|
||||
DB_TYPE_LONG to be returned as bytes instead of str, meaning that
|
||||
oracledb doesn't do any decoding.
|
||||
|
||||
The convert_nulls parameter specifies whether the outconverter should
|
||||
be called when null values are fetched from the database.
|
||||
"""
|
||||
self._verify_open()
|
||||
if typename is not None:
|
||||
|
@ -808,4 +812,5 @@ class Cursor:
|
|||
encoding_errors = encodingErrors
|
||||
return self._impl.create_var(self.connection, typ, size, arraysize,
|
||||
inconverter, outconverter,
|
||||
encoding_errors, bypass_decode)
|
||||
encoding_errors, bypass_decode,
|
||||
convert_nulls=convert_nulls)
|
||||
|
|
|
@ -403,7 +403,8 @@ cdef class BaseCursorImpl:
|
|||
def create_var(self, object conn, object typ, uint32_t size=0,
|
||||
uint32_t num_elements=1, object inconverter=None,
|
||||
object outconverter=None, str encoding_errors=None,
|
||||
bint bypass_decode=False, bint is_array=False):
|
||||
bint bypass_decode=False, bint is_array=False,
|
||||
bint convert_nulls=False):
|
||||
cdef BaseVarImpl var_impl
|
||||
var_impl = self._create_var_impl(conn)
|
||||
var_impl._set_type_info_from_type(typ)
|
||||
|
@ -413,6 +414,7 @@ cdef class BaseCursorImpl:
|
|||
var_impl.outconverter = outconverter
|
||||
var_impl.bypass_decode = bypass_decode
|
||||
var_impl.is_array = is_array
|
||||
var_impl.convert_nulls = convert_nulls
|
||||
var_impl._finalize_init()
|
||||
return PY_TYPE_VAR._from_impl(var_impl)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
|
||||
#
|
||||
# This software is dual-licensed to you under the Universal Permissive License
|
||||
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||
|
@ -276,3 +276,5 @@ cdef class ThickVarImpl(BaseVarImpl):
|
|||
if self.outconverter is not None:
|
||||
value = self.outconverter(value)
|
||||
return value
|
||||
elif self.convert_nulls:
|
||||
return self.outconverter(None)
|
||||
|
|
|
@ -494,7 +494,7 @@ cdef class MessageWithData(Message):
|
|||
num_elements = self.row_index
|
||||
for i in range(num_elements):
|
||||
value = var_impl._values[i]
|
||||
if value is None:
|
||||
if value is None and not var_impl.convert_nulls:
|
||||
continue
|
||||
if isinstance(value, list):
|
||||
for j, element_value in enumerate(value):
|
||||
|
|
|
@ -90,6 +90,14 @@ class Var:
|
|||
"""
|
||||
return self.buffer_size
|
||||
|
||||
@property
|
||||
def convert_nulls(self) -> bool:
|
||||
"""
|
||||
This read-only attribute returns whether null values are converted
|
||||
using the supplied ``outconverter``.
|
||||
"""
|
||||
return self._impl.convert_nulls
|
||||
|
||||
def getvalue(self, pos: int=0) -> Any:
|
||||
"""
|
||||
Return the value at the given position in the variable. For variables
|
||||
|
|
|
@ -433,5 +433,26 @@ class TestCase(test_env.BaseTestCase):
|
|||
self.assertEqual(var.actualElements, 200)
|
||||
self.assertEqual(var.numElements, 200)
|
||||
|
||||
def test_3730_convert_nulls(self):
|
||||
"3730 - test calling of outconverter with null values"
|
||||
def type_handler(cursor, metadata):
|
||||
return cursor.var(metadata.type_code,
|
||||
outconverter=lambda v: f"|{v}|" if v else '',
|
||||
convert_nulls=True, arraysize=cursor.arraysize)
|
||||
self.cursor.outputtypehandler = type_handler
|
||||
self.cursor.execute("""
|
||||
select 'First - A', 'First - B' from dual
|
||||
union all
|
||||
select 'Second - A', null from dual
|
||||
union all
|
||||
select null, 'Third - B' from dual""")
|
||||
rows = self.cursor.fetchall()
|
||||
expected_rows = [
|
||||
('|First - A|', '|First - B|'),
|
||||
('|Second - A|', ''),
|
||||
('', '|Third - B|')
|
||||
]
|
||||
self.assertEqual(rows, expected_rows)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_env.run_test_cases()
|
||||
|
|
Loading…
Reference in New Issue