Compare commits

...

17 Commits
v2.0.0 ... main

Author SHA1 Message Date
Anthony Tuininga 9d84126e66 Internal change to ensure that pools are closed gracefully when the main
thread terminates.
2024-01-05 16:36:50 -07:00
Anthony Tuininga 91e22e2147 Allow for the situation where the second part of the redirect packet
does not arrive before the first part has been parsed (#275).
2024-01-05 15:19:23 -07:00
Anthony Tuininga 1191748b88 Tweaked release note based on feedback. 2024-01-05 13:45:03 -07:00
Anthony Tuininga 9a0b18f010 Removed dead code. 2024-01-05 13:44:33 -07:00
Anthony Tuininga 4d3f461d02 Fixed regression when connecting to a database using listener redirects
with either a connection pool or using asyncio (#275).
2024-01-05 13:42:30 -07:00
Anthony Tuininga 3ea70a4ff8 Fixed regression from cx_Oracle which ignored the value of the
"encoding_errors" parameter when creating variables by calling the
method Cursor.var() (#279).
2024-01-04 15:44:05 -07:00
Anthony Tuininga 659db4468a Internal change to slightly improve performance of LOB reads and writes. 2024-01-04 15:43:43 -07:00
Anthony Tuininga de2792fac6 Bumped minimum requirement of Cython to 3.0. 2024-01-04 15:43:21 -07:00
Anthony Tuininga 8df2972a75 Squash compilation warning. 2024-01-04 15:42:56 -07:00
Anthony Tuininga d633f860d9 Make it clearer in the code that we are waiting for the server to
respond, not initiating the reset that is about to occur!
2024-01-04 10:12:16 -07:00
Anthony Tuininga 1c0ab31c30 Added support for the asynchronous context manager protocol on the
AsyncCursor class as a convenience.
2024-01-03 14:24:58 -07:00
Anthony Tuininga cc1e3ea41b 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 (#278).
2024-01-03 13:23:07 -07:00
Anthony Tuininga 2757a45f45 Grammar typo. 2024-01-02 21:20:22 -07:00
Anthony Tuininga cbd2fe7c26 Fixed bug when fetch variables contain output converters and a query is
re-executed (#271).
2024-01-02 21:19:02 -07:00
Anthony Tuininga 4ca0c8060a Fixed regression which prevented a null value from being set on DbObject
attributes or used as elements of collections (#273).
2024-01-02 15:03:19 -07:00
Anthony Tuininga c1c4ef8c99 Added support for using alternative event loop implementations like
uvloop (#276).
2024-01-02 10:19:08 -07:00
Anthony Tuininga ec700ab526 Corrected typing declarations. 2024-01-02 08:37:09 -07:00
24 changed files with 240 additions and 91 deletions

View File

@ -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.

View File

@ -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)
------------------------------

View File

@ -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:

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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

View File

@ -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 \

View File

@ -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

View File

@ -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:
"""

View File

@ -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:

View File

@ -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.
"""

View File

@ -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:

View File

@ -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()

View File

@ -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):
"""

View File

@ -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:

View File

@ -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"

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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.