Compare commits
17 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
9d84126e66 | |
![]() |
91e22e2147 | |
![]() |
1191748b88 | |
![]() |
9a0b18f010 | |
![]() |
4d3f461d02 | |
![]() |
3ea70a4ff8 | |
![]() |
659db4468a | |
![]() |
de2792fac6 | |
![]() |
8df2972a75 | |
![]() |
d633f860d9 | |
![]() |
1c0ab31c30 | |
![]() |
cc1e3ea41b | |
![]() |
2757a45f45 | |
![]() |
cbd2fe7c26 | |
![]() |
4ca0c8060a | |
![]() |
c1c4ef8c99 | |
![]() |
ec700ab526 |
|
@ -1195,7 +1195,7 @@ Oracledb Methods
|
|||
The ``user``, ``password``, and ``dsn`` parameters are the same as for
|
||||
:meth:`oracledb.connect_async()`.
|
||||
|
||||
The ``pool_class`` parameter is expected to be a
|
||||
The ``pool_class`` parameter is expected to be an
|
||||
:ref:`AsyncConnectionPool Object <asyncconnpoolobj>` or a subclass of
|
||||
AsyncConnectionPool.
|
||||
|
||||
|
|
|
@ -7,6 +7,44 @@ python-oracledb Release Notes
|
|||
|
||||
For deprecations, see :ref:`Deprecations <deprecations>`.
|
||||
|
||||
oracledb 2.0.1 (TBD)
|
||||
--------------------
|
||||
|
||||
Thin Mode Changes
|
||||
+++++++++++++++++
|
||||
|
||||
#) Added support for using alternative event loop implementations like uvloop
|
||||
(`issue 276 <https://github.com/oracle/python-oracledb/issues/276>`__).
|
||||
#) Added support for the asynchronous context manager protocol on the
|
||||
AsyncCursor class as a convenience.
|
||||
#) Fixed regression when connecting to a database using listener redirects
|
||||
with either a connection pool or using asyncio
|
||||
(`issue 275 <https://github.com/oracle/python-oracledb/issues/275>`__).
|
||||
#) Fixed bug with intermittent hang on some versions of Oracle Database when
|
||||
using asyncio and the database raises an error and output variables are
|
||||
present
|
||||
(`issue 278 <https://github.com/oracle/python-oracledb/issues/278>`__).
|
||||
#) Fixed bug when fetch variables contain output converters and a query is
|
||||
re-executed
|
||||
(`issue 271 <https://github.com/oracle/python-oracledb/issues/271>`__).
|
||||
#) Internal change to ensure that pools are closed gracefully when the main
|
||||
thread terminates.
|
||||
#) Internal change to slightly improve performance of LOB reads and writes.
|
||||
#) Corrected typing declaration for :meth:`oracledb.connect_async()`.
|
||||
|
||||
Common Changes
|
||||
++++++++++++++
|
||||
|
||||
#) Fixed regression which prevented a null value from being set on DbObject
|
||||
attributes or used as elements of collections
|
||||
(`issue 273 <https://github.com/oracle/python-oracledb/issues/273>`__).
|
||||
#) Fixed regression from cx_Oracle which ignored the value of the
|
||||
``encoding_errors`` parameter when creating variables by calling the method
|
||||
:meth:`Cursor.var()`
|
||||
(`issue 279 <https://github.com/oracle/python-oracledb/issues/279>`__).
|
||||
#) Bumped minimum requirement of Cython to 3.0.
|
||||
|
||||
|
||||
oracledb 2.0.0 (December 2023)
|
||||
------------------------------
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ classifiers =
|
|||
[options]
|
||||
zip_safe = false
|
||||
python_requires = >=3.7
|
||||
setup_requires = cython
|
||||
setup_requires = cython>=3.0
|
||||
install_requires = cryptography>=3.2.1
|
||||
test_suite = tests
|
||||
packages = find:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -342,6 +342,7 @@ cdef class BaseVarImpl:
|
|||
readonly uint32_t size
|
||||
readonly uint32_t buffer_size
|
||||
readonly bint bypass_decode
|
||||
readonly str encoding_errors
|
||||
readonly bint is_array
|
||||
readonly bint nulls_allowed
|
||||
readonly bint convert_nulls
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -1747,7 +1747,7 @@ def connect_async(
|
|||
ssl_context: Any = None,
|
||||
sdu: int = 8192,
|
||||
handle: int = 0,
|
||||
) -> Connection:
|
||||
) -> AsyncConnection:
|
||||
"""
|
||||
Factory function which creates a connection to the database and returns it.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2021, 2024, 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
|
||||
|
@ -911,6 +911,15 @@ class Cursor(BaseCursor):
|
|||
class AsyncCursor(BaseCursor):
|
||||
__module__ = MODULE_NAME
|
||||
|
||||
async def __aenter__(self):
|
||||
self._verify_open()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *exc_info):
|
||||
self._verify_open()
|
||||
self._impl.close(in_del=True)
|
||||
self._impl = None
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
|
|
|
@ -419,6 +419,7 @@ cdef class BaseCursorImpl:
|
|||
var_impl.inconverter = inconverter
|
||||
var_impl.outconverter = outconverter
|
||||
var_impl.bypass_decode = bypass_decode
|
||||
var_impl.encoding_errors = encoding_errors
|
||||
var_impl.is_array = is_array
|
||||
var_impl.convert_nulls = convert_nulls
|
||||
var_impl._finalize_init()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -37,7 +37,7 @@ cdef class BaseDbObjectImpl:
|
|||
Checks to see if the maximum size has been violated.
|
||||
"""
|
||||
violated[0] = False
|
||||
if max_size > 0:
|
||||
if max_size > 0 and value is not None:
|
||||
if isinstance(value, str):
|
||||
actual_size[0] = len((<str> value).encode())
|
||||
else:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -29,7 +29,8 @@
|
|||
# thick_impl.pyx).
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
cdef void _callback_handler(void* context, dpiSubscrMessage* message) with gil:
|
||||
cdef int _callback_handler(void* context,
|
||||
dpiSubscrMessage* message) except -1 with gil:
|
||||
cdef:
|
||||
object subscr = <object> context
|
||||
ThickSubscrImpl subscr_impl
|
||||
|
@ -156,7 +157,7 @@ cdef class ThickSubscrImpl(BaseSubscrImpl):
|
|||
params.name = name_buf.ptr
|
||||
params.nameLength = name_buf.length
|
||||
if self.callback is not None:
|
||||
params.callback = _callback_handler
|
||||
params.callback = <dpiSubscrCallback> _callback_handler
|
||||
params.callbackContext = <void*> subscr
|
||||
params.ipAddress = ip_address_buf.ptr
|
||||
params.ipAddressLength = ip_address_buf.length
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -261,7 +261,8 @@ cdef object _convert_to_python(ThickConnImpl conn_impl, DbType dbtype,
|
|||
ThickDbObjectTypeImpl obj_type_impl,
|
||||
dpiDataBuffer *dbvalue,
|
||||
int preferred_num_type=NUM_TYPE_FLOAT,
|
||||
bint bypass_decode=False):
|
||||
bint bypass_decode=False,
|
||||
const char* encoding_errors=NULL):
|
||||
cdef:
|
||||
uint32_t oracle_type = dbtype.num
|
||||
ThickDbObjectImpl obj_impl
|
||||
|
@ -282,7 +283,7 @@ cdef object _convert_to_python(ThickConnImpl conn_impl, DbType dbtype,
|
|||
or oracle_type == DPI_ORACLE_TYPE_LONG_NVARCHAR \
|
||||
or oracle_type == DPI_ORACLE_TYPE_XMLTYPE:
|
||||
as_bytes = &dbvalue.asBytes
|
||||
return as_bytes.ptr[:as_bytes.length].decode()
|
||||
return as_bytes.ptr[:as_bytes.length].decode("utf-8", encoding_errors)
|
||||
elif oracle_type == DPI_ORACLE_TYPE_NUMBER:
|
||||
as_bytes = &dbvalue.asBytes
|
||||
if preferred_num_type == NUM_TYPE_INT \
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -264,15 +264,21 @@ cdef class ThickVarImpl(BaseVarImpl):
|
|||
Transforms a single element from the value supplied by ODPI-C to its
|
||||
equivalent Python value.
|
||||
"""
|
||||
cdef object value
|
||||
cdef:
|
||||
const char *encoding_errors = NULL
|
||||
bytes encoding_errors_bytes
|
||||
object value
|
||||
data = &data[pos]
|
||||
if not data.isNull:
|
||||
if self._native_type_num == DPI_NATIVE_TYPE_STMT:
|
||||
return self._get_cursor_value(&data.value)
|
||||
if self.encoding_errors is not None:
|
||||
encoding_errors_bytes = self.encoding_errors.encode()
|
||||
encoding_errors = encoding_errors_bytes
|
||||
value = _convert_to_python(self._conn_impl, self.dbtype,
|
||||
self.objtype, &data.value,
|
||||
self._preferred_num_type,
|
||||
self.bypass_decode)
|
||||
self.bypass_decode, encoding_errors)
|
||||
if self.outconverter is not None:
|
||||
value = self.outconverter(value)
|
||||
return value
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -765,7 +765,7 @@ cdef class Buffer:
|
|||
self._pos = end_pos + 1
|
||||
return self._data[start_pos:self._pos]
|
||||
|
||||
cdef object read_str(self, int csfrm):
|
||||
cdef object read_str(self, int csfrm, const char* encoding_errors=NULL):
|
||||
"""
|
||||
Reads bytes from the buffer and decodes them into a string following
|
||||
the supplied character set form.
|
||||
|
@ -776,8 +776,9 @@ cdef class Buffer:
|
|||
self.read_raw_bytes_and_length(&ptr, &num_bytes)
|
||||
if ptr != NULL:
|
||||
if csfrm == TNS_CS_IMPLICIT:
|
||||
return ptr[:num_bytes].decode()
|
||||
return ptr[:num_bytes].decode(TNS_ENCODING_UTF16)
|
||||
return ptr[:num_bytes].decode(TNS_ENCODING_UTF8,
|
||||
encoding_errors)
|
||||
return ptr[:num_bytes].decode(TNS_ENCODING_UTF16, encoding_errors)
|
||||
|
||||
cdef int read_ub1(self, uint8_t *value) except -1:
|
||||
"""
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -676,8 +676,8 @@ cdef enum:
|
|||
TNS_ENCODING_CONV_LENGTH = 0x02
|
||||
|
||||
# character set strings
|
||||
cdef str TNS_ENCODING_UTF8 = "UTF-8"
|
||||
cdef str TNS_ENCODING_UTF16 = "UTF-16BE"
|
||||
cdef const char* TNS_ENCODING_UTF8 = "UTF-8"
|
||||
cdef const char* TNS_ENCODING_UTF16 = "UTF-16BE"
|
||||
|
||||
# compile time capability indices
|
||||
cdef enum:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -152,10 +152,10 @@ cdef class BaseThinLobImpl(BaseLobImpl):
|
|||
raise TypeError(
|
||||
"only strings can be written to CLOBs and NCLOBS"
|
||||
)
|
||||
message.data = value.encode(self._get_encoding())
|
||||
message.data = (<str> value).encode(self._get_encoding())
|
||||
return message
|
||||
|
||||
cdef str _get_encoding(self):
|
||||
cdef const char* _get_encoding(self):
|
||||
"""
|
||||
Return the encoding used by the LOB.
|
||||
"""
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -496,6 +496,8 @@ cdef class MessageWithData(Message):
|
|||
ThinVarImpl var_impl, uint32_t pos):
|
||||
cdef:
|
||||
uint8_t num_bytes, ora_type_num, csfrm
|
||||
const char* encoding_errors = NULL
|
||||
bytes encoding_errors_bytes
|
||||
ThinDbObjectTypeImpl typ_impl
|
||||
BaseThinCursorImpl cursor_impl
|
||||
object column_value = None
|
||||
|
@ -525,7 +527,10 @@ cdef class MessageWithData(Message):
|
|||
or ora_type_num == TNS_DATA_TYPE_LONG:
|
||||
if csfrm == TNS_CS_NCHAR:
|
||||
buf._caps._check_ncharset_id()
|
||||
column_value = buf.read_str(csfrm)
|
||||
if var_impl.encoding_errors is not None:
|
||||
encoding_errors_bytes = var_impl.encoding_errors.encode()
|
||||
encoding_errors = encoding_errors_bytes
|
||||
column_value = buf.read_str(csfrm, encoding_errors)
|
||||
elif ora_type_num == TNS_DATA_TYPE_RAW \
|
||||
or ora_type_num == TNS_DATA_TYPE_LONG_RAW:
|
||||
column_value = buf.read_bytes()
|
||||
|
@ -761,6 +766,7 @@ cdef class MessageWithData(Message):
|
|||
if self.error_info.num == TNS_ERR_NO_DATA_FOUND:
|
||||
self.error_info.num = 0
|
||||
cursor_impl._more_rows_to_fetch = False
|
||||
cursor_impl._last_row_index = 0
|
||||
self.error_occurred = False
|
||||
elif self.error_info.num == TNS_ERR_ARRAY_DML_ERRORS:
|
||||
self.error_info.num = 0
|
||||
|
@ -1798,7 +1804,8 @@ cdef class CommitMessage(Message):
|
|||
cdef class ConnectMessage(Message):
|
||||
cdef:
|
||||
bytes connect_string_bytes
|
||||
uint16_t connect_string_len
|
||||
uint16_t connect_string_len, redirect_data_len
|
||||
bint read_redirect_data_len
|
||||
ConnectionCookie cookie
|
||||
Description description
|
||||
uint8_t packet_flags
|
||||
|
@ -1808,15 +1815,19 @@ cdef class ConnectMessage(Message):
|
|||
|
||||
cdef int process(self, ReadBuffer buf) except -1:
|
||||
cdef:
|
||||
uint16_t redirect_data_length, protocol_version, protocol_options
|
||||
uint16_t protocol_version, protocol_options
|
||||
const char_type *redirect_data
|
||||
bytes db_uuid
|
||||
if buf._current_packet.packet_type == TNS_PACKET_TYPE_REDIRECT:
|
||||
buf.read_uint16(&redirect_data_length)
|
||||
if not self.read_redirect_data_len:
|
||||
buf.read_uint16(&self.redirect_data_len)
|
||||
self.read_redirect_data_len = True
|
||||
buf.wait_for_packets_sync()
|
||||
redirect_data = buf._get_raw(redirect_data_length)
|
||||
self.redirect_data = \
|
||||
redirect_data[:redirect_data_length].decode()
|
||||
redirect_data = buf._get_raw(self.redirect_data_len)
|
||||
if self.redirect_data_len > 0:
|
||||
self.redirect_data = \
|
||||
redirect_data[:self.redirect_data_len].decode()
|
||||
self.read_redirect_data_len = False
|
||||
elif buf._current_packet.packet_type == TNS_PACKET_TYPE_ACCEPT:
|
||||
buf.read_uint16(&protocol_version)
|
||||
buf.read_uint16(&protocol_options)
|
||||
|
@ -2207,9 +2218,9 @@ cdef class LobOpMessage(Message):
|
|||
cdef int _process_message(self, ReadBuffer buf,
|
||||
uint8_t message_type) except -1:
|
||||
cdef:
|
||||
const char* encoding
|
||||
const char_type *ptr
|
||||
ssize_t num_bytes
|
||||
str encoding
|
||||
if message_type == TNS_MSG_TYPE_LOB_DATA:
|
||||
buf.read_raw_bytes_and_length(&ptr, &num_bytes)
|
||||
if self.source_lob_impl.dbtype._ora_type_num == TNS_DATA_TYPE_BLOB:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -442,8 +442,7 @@ cdef class ThinPoolImpl(BaseThinPoolImpl):
|
|||
super().__init__(dsn, params)
|
||||
self._condition = threading.Condition()
|
||||
self._bg_task_condition = threading.Condition()
|
||||
self._bg_task = threading.Thread(target=self._bg_task_func,
|
||||
daemon=True)
|
||||
self._bg_task = threading.Thread(target=self._bg_task_func)
|
||||
self._bg_task.start()
|
||||
|
||||
def _bg_task_func(self):
|
||||
|
@ -459,6 +458,9 @@ cdef class ThinPoolImpl(BaseThinPoolImpl):
|
|||
list conn_impls_to_drop
|
||||
bint wait
|
||||
|
||||
# add to the list of pools that require closing
|
||||
pools_to_close.add(self)
|
||||
|
||||
# create connections and close connections as needed
|
||||
while True:
|
||||
conn_impls_to_drop = []
|
||||
|
@ -499,6 +501,9 @@ cdef class ThinPoolImpl(BaseThinPoolImpl):
|
|||
with self._bg_task_condition:
|
||||
self._bg_task_condition.wait()
|
||||
|
||||
# remove from the list of pools that require closing
|
||||
pools_to_close.remove(self)
|
||||
|
||||
cdef ThinConnImpl _create_conn_impl(self, ConnectParamsImpl params=None):
|
||||
"""
|
||||
Create a single connection using the pool's information. This
|
||||
|
@ -638,11 +643,14 @@ cdef class ThinPoolImpl(BaseThinPoolImpl):
|
|||
|
||||
def close(self, bint force):
|
||||
"""
|
||||
Internal method for closing the pool.
|
||||
Internal method for closing the pool. Note that the thread to destroy
|
||||
pools gracefully may have already run, so if the close has already
|
||||
happened, nothing more needs to be done!
|
||||
"""
|
||||
with self._condition:
|
||||
self._close_helper(force)
|
||||
self._bg_task.join()
|
||||
if self in pools_to_close:
|
||||
with self._condition:
|
||||
self._close_helper(force)
|
||||
self._bg_task.join()
|
||||
|
||||
def drop(self, ThinConnImpl conn_impl):
|
||||
"""
|
||||
|
@ -883,3 +891,13 @@ cdef class AsyncThinPoolImpl(BaseThinPoolImpl):
|
|||
self._busy_conn_impls.remove(conn_impl)
|
||||
self._drop_conn_impl(conn_impl)
|
||||
self._condition.notify()
|
||||
|
||||
# keep track of which pools need to be closed and ensure that they are closed
|
||||
# gracefully when the main thread finishes its work
|
||||
pools_to_close = set()
|
||||
def close_pools_gracefully():
|
||||
cdef ThinPoolImpl pool_impl
|
||||
threading.main_thread().join() # wait for main thread to finish
|
||||
for pool_impl in list(pools_to_close):
|
||||
pool_impl.close(True)
|
||||
threading.Thread(target=close_pools_gracefully).start()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -304,8 +304,12 @@ cdef class Protocol(BaseProtocol):
|
|||
self._process_message(auth_message)
|
||||
|
||||
# mark protocol to indicate that connect is no longer in progress; this
|
||||
# allows the normal break/reset mechanism to fire
|
||||
# allows the normal break/reset mechanism to fire; also mark the
|
||||
# session as not needing to be closed since for listener redirects
|
||||
# the packet may indicate EOF for the initial connection that is
|
||||
# established
|
||||
conn_impl.warning = auth_message.warning
|
||||
self._read_buf._session_needs_to_be_closed = False
|
||||
self._in_connect = False
|
||||
|
||||
cdef int _connect_tcp(self, ConnectParamsImpl params,
|
||||
|
@ -384,7 +388,7 @@ cdef class Protocol(BaseProtocol):
|
|||
self._write_buf.start_request(TNS_PACKET_TYPE_DATA)
|
||||
self._write_buf.write_uint8(TNS_MSG_TYPE_FLUSH_OUT_BINDS)
|
||||
self._write_buf.end_request()
|
||||
self._reset(message)
|
||||
self._receive_packet(message)
|
||||
message.process(self._read_buf)
|
||||
if self._break_in_progress:
|
||||
try:
|
||||
|
@ -456,9 +460,6 @@ cdef class Protocol(BaseProtocol):
|
|||
cdef int _reset(self, Message message) except -1:
|
||||
cdef uint8_t marker_type, packet_type
|
||||
|
||||
# send reset marker
|
||||
self._send_marker(self._write_buf, TNS_MARKER_TYPE_RESET)
|
||||
|
||||
# read and discard all packets until a marker packet is received
|
||||
while True:
|
||||
packet_type = self._read_buf._current_packet.packet_type
|
||||
|
@ -469,11 +470,12 @@ cdef class Protocol(BaseProtocol):
|
|||
break
|
||||
self._read_buf.wait_for_packets_sync()
|
||||
|
||||
# read error packet; first skip as many marker packets as may be sent
|
||||
# by the server; if the server doesn't handle out-of-band breaks
|
||||
# properly, some quit immediately and others send multiple reset
|
||||
# markers (this addresses both situations without resulting in strange
|
||||
# errors)
|
||||
# send reset marker and then read error packet; first skip as many
|
||||
# marker packets as may be sent by the server; if the server doesn't
|
||||
# handle out-of-band breaks properly, some quit immediately and others
|
||||
# send multiple reset markers (this addresses both situations without
|
||||
# resulting in strange errors)
|
||||
self._send_marker(self._write_buf, TNS_MARKER_TYPE_RESET)
|
||||
while packet_type == TNS_PACKET_TYPE_MARKER:
|
||||
self._read_buf.wait_for_packets_sync()
|
||||
packet_type = self._read_buf._current_packet.packet_type
|
||||
|
@ -485,6 +487,7 @@ cdef class BaseAsyncProtocol(BaseProtocol):
|
|||
def __init__(self):
|
||||
BaseProtocol.__init__(self)
|
||||
self._request_lock = asyncio.Lock()
|
||||
self._transport._is_async = True
|
||||
|
||||
async def _close(self, AsyncThinConnImpl conn_impl):
|
||||
"""
|
||||
|
@ -766,7 +769,7 @@ cdef class BaseAsyncProtocol(BaseProtocol):
|
|||
self._write_buf.start_request(TNS_PACKET_TYPE_DATA)
|
||||
self._write_buf.write_uint8(TNS_MSG_TYPE_FLUSH_OUT_BINDS)
|
||||
self._write_buf.end_request()
|
||||
await self._reset()
|
||||
await self._receive_packet(message)
|
||||
message.process(self._read_buf)
|
||||
if self._break_in_progress:
|
||||
try:
|
||||
|
@ -850,9 +853,6 @@ cdef class BaseAsyncProtocol(BaseProtocol):
|
|||
async def _reset(self):
|
||||
cdef uint8_t marker_type, packet_type
|
||||
|
||||
# send reset marker
|
||||
self._send_marker(self._write_buf, TNS_MARKER_TYPE_RESET)
|
||||
|
||||
# read and discard all packets until a marker packet is received
|
||||
while True:
|
||||
packet_type = self._read_buf._current_packet.packet_type
|
||||
|
@ -863,11 +863,13 @@ cdef class BaseAsyncProtocol(BaseProtocol):
|
|||
break
|
||||
await self._read_buf.wait_for_packets_async()
|
||||
|
||||
# read error packet; first skip as many marker packets as may be sent
|
||||
# by the server; if the server doesn't handle out-of-band breaks
|
||||
# properly, some quit immediately and others send multiple reset
|
||||
# markers (this addresses both situations without resulting in strange
|
||||
# errors)
|
||||
|
||||
# send reset marker and then read error packet; first skip as many
|
||||
# marker packets as may be sent by the server; if the server doesn't
|
||||
# handle out-of-band breaks properly, some quit immediately and others
|
||||
# send multiple reset markers (this addresses both situations without
|
||||
# resulting in strange errors)
|
||||
self._send_marker(self._write_buf, TNS_MARKER_TYPE_RESET)
|
||||
while packet_type == TNS_PACKET_TYPE_MARKER:
|
||||
await self._read_buf.wait_for_packets_async()
|
||||
packet_type = self._read_buf._current_packet.packet_type
|
||||
|
@ -876,9 +878,12 @@ cdef class BaseAsyncProtocol(BaseProtocol):
|
|||
def connection_lost(self, exc):
|
||||
"""
|
||||
Called when a connection has been lost. The presence of an exception
|
||||
indicates an abornmal loss of the connection.
|
||||
indicates an abornmal loss of the connection. If in the process of
|
||||
establishing a connection, losing the connection is ignored since this
|
||||
can happen normally when a listener redirect is encountered.
|
||||
"""
|
||||
self._transport = None
|
||||
if not self._in_connect:
|
||||
self._transport = None
|
||||
|
||||
def data_received(self, data):
|
||||
"""
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# Copyright (c) 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2023, 2024, 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
|
||||
|
@ -44,22 +44,6 @@ cdef class Transport:
|
|||
bint _full_packet_size
|
||||
bint _is_async
|
||||
|
||||
cdef int _get_data(self, ReadBuffer buf, object obj,
|
||||
ssize_t bytes_requested, ssize_t *bytes_read) except -1:
|
||||
"""
|
||||
Simple function that performs a socket read while verifying that the
|
||||
server has not reset the connection. If it has, the dead connection
|
||||
error is raised instead.
|
||||
"""
|
||||
try:
|
||||
bytes_read[0] = self._transport.recv_into(obj, bytes_requested)
|
||||
except ConnectionResetError as e:
|
||||
errors._raise_err(errors.ERR_CONNECTION_CLOSED, str(e), cause=e)
|
||||
if bytes_read[0] == 0:
|
||||
self.disconnect()
|
||||
buf._transport = None
|
||||
errors._raise_err(errors.ERR_CONNECTION_CLOSED)
|
||||
|
||||
cdef str _get_debugging_header(self, str operation):
|
||||
"""
|
||||
Returns the header line used for debugging packets.
|
||||
|
@ -284,7 +268,6 @@ cdef class Transport:
|
|||
Sets the transport from a socket.
|
||||
"""
|
||||
cdef object sock
|
||||
self._is_async = isinstance(transport, asyncio.Transport)
|
||||
if self._is_async:
|
||||
sock = transport.get_extra_info('socket')
|
||||
else:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2021, 2024, 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
|
||||
|
@ -30,4 +30,4 @@
|
|||
# file doc/src/conf.py both reference this file directly.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
__version__ = "2.0.0"
|
||||
__version__ = "2.0.1"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -771,6 +771,12 @@ class TestCase(test_env.BaseTestCase):
|
|||
"C" * 101,
|
||||
)
|
||||
|
||||
def test_2335_validate_string_attr_null(self):
|
||||
"2335 - test validating a string attribute with null value"
|
||||
typ = self.conn.gettype("UDT_OBJECT")
|
||||
obj = typ.newobject()
|
||||
obj.STRINGVALUE = None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_env.run_test_cases()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2021, 2024, 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
|
||||
|
@ -683,6 +683,44 @@ class TestCase(test_env.BaseTestCase):
|
|||
cursor.execute("select 1 from dual")
|
||||
self.assertEqual(cursor.fetchall(), [("1",)])
|
||||
|
||||
def test_3676_reexecute_no_rows(self):
|
||||
"3676 - re-execute query with second fetch returning no rows"
|
||||
|
||||
self.cursor.execute("truncate table TestTempTable")
|
||||
data = [(i + 1,) for i in range(5)]
|
||||
self.cursor.executemany(
|
||||
"insert into TestTempTable (IntCol) values (:1)", data
|
||||
)
|
||||
self.conn.commit()
|
||||
|
||||
def type_handler_1(cursor, metadata):
|
||||
return cursor.var(
|
||||
str,
|
||||
arraysize=cursor.arraysize,
|
||||
outconverter=lambda x: f"_{x}_",
|
||||
)
|
||||
|
||||
def type_handler_2(cursor, metadata):
|
||||
return cursor.var(
|
||||
str,
|
||||
arraysize=cursor.arraysize,
|
||||
outconverter=lambda x: f"={x}=",
|
||||
)
|
||||
|
||||
self.cursor.outputtypehandler = type_handler_1
|
||||
self.cursor.arraysize = 6
|
||||
self.cursor.prefetchrows = 6
|
||||
sql = "select IntCol from TestTempTable where rownum <= :1"
|
||||
self.cursor.execute(sql, [6])
|
||||
expected_value = [(f"_{x}_",) for x, in data]
|
||||
self.assertEqual(self.cursor.fetchall(), expected_value)
|
||||
|
||||
self.cursor.outputtypehandler = type_handler_2
|
||||
self.cursor.prefetchrows = 2
|
||||
self.cursor.arraysize = 2
|
||||
self.cursor.execute(sql, [0])
|
||||
self.assertEqual(self.cursor.fetchall(), [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_env.run_test_cases()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2021, 2024, 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
|
||||
|
@ -281,6 +281,26 @@ class TestCase(test_env.BaseTestCase):
|
|||
self.cursor.execute("select * from TestJson")
|
||||
self.assertEqual(self.cursor.fetchall(), data_to_insert)
|
||||
|
||||
def test_3807(self):
|
||||
"3807 - output type handler for encoding errors"
|
||||
|
||||
def output_type_handler(cursor, metadata):
|
||||
if metadata.type_code is oracledb.DB_TYPE_VARCHAR:
|
||||
return cursor.var(
|
||||
metadata.type_code,
|
||||
arraysize=cursor.arraysize,
|
||||
encoding_errors="replace",
|
||||
)
|
||||
|
||||
self.cursor.outputtypehandler = output_type_handler
|
||||
self.cursor.execute(
|
||||
"select utl_raw.cast_to_varchar2('41ab42cd43ef') from dual"
|
||||
)
|
||||
(result,) = self.cursor.fetchone()
|
||||
rc = chr(0xFFFD)
|
||||
expected_result = f"A{rc}B{rc}C{rc}"
|
||||
self.assertEqual(result, expected_result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_env.run_test_cases()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2023, 2024, 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
|
||||
|
@ -583,6 +583,16 @@ class TestCase(test_env.BaseAsyncTestCase):
|
|||
(fetch_info,) = self.cursor.description
|
||||
self.assertEqual(fetch_info[1:3], (oracledb.DB_TYPE_NUMBER, 10))
|
||||
|
||||
async def test_5434_async_context_manager(self):
|
||||
"5434 - test async context manager"
|
||||
expected_value = test_env.get_main_user().upper()
|
||||
with self.conn.cursor() as cursor:
|
||||
await cursor.execute("select user from dual")
|
||||
self.assertEqual(await cursor.fetchone(), (expected_value,))
|
||||
async with self.conn.cursor() as cursor:
|
||||
await cursor.execute("select user from dual")
|
||||
self.assertEqual(await cursor.fetchone(), (expected_value,))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_env.run_test_cases()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2020, 2024, 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
|
||||
|
@ -1515,7 +1515,7 @@ def connect_async(
|
|||
conn_class: Type[AsyncConnection] = AsyncConnection,
|
||||
params: ConnectParams = None,
|
||||
# {{ args_with_defaults }}
|
||||
) -> Connection:
|
||||
) -> AsyncConnection:
|
||||
"""
|
||||
Factory function which creates a connection to the database and returns it.
|
||||
|
||||
|
|
Loading…
Reference in New Issue