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, \
|
.. 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
|
Creates a variable with the specified characteristics. This method was
|
||||||
designed for use with PL/SQL in/out variables where the length or type
|
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
|
meaning that python-oracledb does not do any decoding. See :ref:`Fetching raw
|
||||||
data <fetching-raw-data>` for more information.
|
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
|
For consistency and compliance with the PEP 8 naming style, the
|
||||||
parameter `encodingErrors` was renamed to `encoding_errors`. The old
|
parameter `encodingErrors` was renamed to `encoding_errors`. The old
|
||||||
name will continue to work as a keyword parameter for a period of time.
|
name will continue to work as a keyword parameter for a period of time.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.4
|
||||||
|
|
||||||
|
The ``convert_nulls`` parameter was added.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The DB API definition does not define this method.
|
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.
|
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
|
.. attribute:: Variable.inconverter
|
||||||
|
|
||||||
This read-write attribute specifies the method used to convert data from
|
This read-write attribute specifies the method used to convert data from
|
||||||
|
|
|
@ -74,6 +74,11 @@ Thick Mode Changes
|
||||||
Common 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
|
#) Replaced fixed 7-tuple for the cursor metadata found in
|
||||||
:data:`Cursor.description` with a class which provides additional
|
:data:`Cursor.description` with a class which provides additional
|
||||||
information such as the database object type and whether the column
|
information such as the database object type and whether the column
|
||||||
|
|
|
@ -333,6 +333,7 @@ cdef class BaseVarImpl:
|
||||||
readonly bint bypass_decode
|
readonly bint bypass_decode
|
||||||
readonly bint is_array
|
readonly bint is_array
|
||||||
readonly bint nulls_allowed
|
readonly bint nulls_allowed
|
||||||
|
readonly bint convert_nulls
|
||||||
public uint32_t num_elements_in_array
|
public uint32_t num_elements_in_array
|
||||||
readonly DbType dbtype
|
readonly DbType dbtype
|
||||||
readonly BaseDbObjectTypeImpl objtype
|
readonly BaseDbObjectTypeImpl objtype
|
||||||
|
|
|
@ -743,6 +743,7 @@ class Cursor:
|
||||||
typename: str=None,
|
typename: str=None,
|
||||||
encoding_errors: str=None,
|
encoding_errors: str=None,
|
||||||
bypass_decode: bool=False,
|
bypass_decode: bool=False,
|
||||||
|
convert_nulls: bool=False,
|
||||||
*,
|
*,
|
||||||
encodingErrors: str=None) -> "Var":
|
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_VARCHAR, DB_TYPE_CHAR, DB_TYPE_NVARCHAR, DB_TYPE_NCHAR and
|
||||||
DB_TYPE_LONG to be returned as bytes instead of str, meaning that
|
DB_TYPE_LONG to be returned as bytes instead of str, meaning that
|
||||||
oracledb doesn't do any decoding.
|
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()
|
self._verify_open()
|
||||||
if typename is not None:
|
if typename is not None:
|
||||||
|
@ -808,4 +812,5 @@ class Cursor:
|
||||||
encoding_errors = encodingErrors
|
encoding_errors = encodingErrors
|
||||||
return self._impl.create_var(self.connection, typ, size, arraysize,
|
return self._impl.create_var(self.connection, typ, size, arraysize,
|
||||||
inconverter, outconverter,
|
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,
|
def create_var(self, object conn, object typ, uint32_t size=0,
|
||||||
uint32_t num_elements=1, object inconverter=None,
|
uint32_t num_elements=1, object inconverter=None,
|
||||||
object outconverter=None, str encoding_errors=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
|
cdef BaseVarImpl var_impl
|
||||||
var_impl = self._create_var_impl(conn)
|
var_impl = self._create_var_impl(conn)
|
||||||
var_impl._set_type_info_from_type(typ)
|
var_impl._set_type_info_from_type(typ)
|
||||||
|
@ -413,6 +414,7 @@ cdef class BaseCursorImpl:
|
||||||
var_impl.outconverter = outconverter
|
var_impl.outconverter = outconverter
|
||||||
var_impl.bypass_decode = bypass_decode
|
var_impl.bypass_decode = bypass_decode
|
||||||
var_impl.is_array = is_array
|
var_impl.is_array = is_array
|
||||||
|
var_impl.convert_nulls = convert_nulls
|
||||||
var_impl._finalize_init()
|
var_impl._finalize_init()
|
||||||
return PY_TYPE_VAR._from_impl(var_impl)
|
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
|
# 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
|
# (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:
|
if self.outconverter is not None:
|
||||||
value = self.outconverter(value)
|
value = self.outconverter(value)
|
||||||
return value
|
return value
|
||||||
|
elif self.convert_nulls:
|
||||||
|
return self.outconverter(None)
|
||||||
|
|
|
@ -494,7 +494,7 @@ cdef class MessageWithData(Message):
|
||||||
num_elements = self.row_index
|
num_elements = self.row_index
|
||||||
for i in range(num_elements):
|
for i in range(num_elements):
|
||||||
value = var_impl._values[i]
|
value = var_impl._values[i]
|
||||||
if value is None:
|
if value is None and not var_impl.convert_nulls:
|
||||||
continue
|
continue
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
for j, element_value in enumerate(value):
|
for j, element_value in enumerate(value):
|
||||||
|
|
|
@ -90,6 +90,14 @@ class Var:
|
||||||
"""
|
"""
|
||||||
return self.buffer_size
|
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:
|
def getvalue(self, pos: int=0) -> Any:
|
||||||
"""
|
"""
|
||||||
Return the value at the given position in the variable. For variables
|
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.actualElements, 200)
|
||||||
self.assertEqual(var.numElements, 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__":
|
if __name__ == "__main__":
|
||||||
test_env.run_test_cases()
|
test_env.run_test_cases()
|
||||||
|
|
Loading…
Reference in New Issue