Compare commits
16 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
99bd32317f | |
![]() |
408f3684bd | |
![]() |
76ad274e05 | |
![]() |
eeda0162d1 | |
![]() |
d480381035 | |
![]() |
cafbf3761d | |
![]() |
132e6c5222 | |
![]() |
10be0c949f | |
![]() |
83d2e98f49 | |
![]() |
aa42ae84ba | |
![]() |
e0f06dbd47 | |
![]() |
5ec3f5cc30 | |
![]() |
9f7a1c3490 | |
![]() |
636be6278c | |
![]() |
09b1b2f719 | |
![]() |
ce8655f572 |
|
@ -7,6 +7,44 @@ python-oracledb Release Notes
|
|||
|
||||
For deprecations, see :ref:`Deprecations <deprecations>`.
|
||||
|
||||
oracledb 1.3.2 (June 2023)
|
||||
--------------------------
|
||||
|
||||
Thin Mode Changes
|
||||
+++++++++++++++++
|
||||
|
||||
#) Fixed bug using :attr:`Cursor.arraysize` for tuning data fetches from REF
|
||||
CURSORS.
|
||||
#) Fixed bug connecting to databases with older 11g password verifiers
|
||||
(`issue 189 <https://github.com/oracle/python-oracledb/issues/189>`__).
|
||||
#) Fixed bugs in the implementation of the statement cache.
|
||||
#) Fixed bug which caused a cursor leak if an error was thrown while
|
||||
processing the execution of a query.
|
||||
#) Eliminated unneeded round trip when using token authentication to connect
|
||||
to the database.
|
||||
#) Fixed bug which could cause a redirect loop with improperly configured
|
||||
listener redirects.
|
||||
#) Fixed bug when executing PL/SQL with a large number of binds.
|
||||
#) Fixed bug when using DRCP with Oracle Database 23c.
|
||||
|
||||
Thick Mode Changes
|
||||
++++++++++++++++++
|
||||
|
||||
#) Fixed bug when using external authentication with a Net Service Name
|
||||
connection string
|
||||
(`issue 178 <https://github.com/oracle/python-oracledb/issues/178>`__).
|
||||
#) Fixed bug when using external authentication with an Easy Connect
|
||||
connection string.
|
||||
|
||||
Common Changes
|
||||
++++++++++++++
|
||||
|
||||
#) When fetching rows from REF CURSORS, the cursor's
|
||||
:attr:`~Cursor.prefetchrows` attribute is now ignored. Use
|
||||
:attr:`Cursor.arraysize` for tuning these fetches. This change allows
|
||||
consistency between Thin and Thick modes.
|
||||
|
||||
|
||||
oracledb 1.3.1 (April 2023)
|
||||
---------------------------
|
||||
|
||||
|
|
|
@ -245,11 +245,10 @@ Avoiding Premature Prefetching
|
|||
|
||||
There are two cases that will benefit from setting ``prefetchrows`` to zero:
|
||||
|
||||
* When passing REF CURSORS into PL/SQL packages. Setting ``prefetchrows`` to 0
|
||||
can stop rows being prematurely (and silently) fetched into the
|
||||
python-oracledb or Oracle Client (in python-oracledb Thick mode) internal
|
||||
buffer, making those rows unavailable to the PL/SQL code that receives the
|
||||
REF CURSOR.
|
||||
* When passing REF CURSORS *into* PL/SQL packages. Setting ``prefetchrows`` to
|
||||
0 can stop rows being prematurely (and silently) fetched into the
|
||||
python-oracledb internal buffer, making those rows unavailable to the PL/SQL
|
||||
code that receives the REF CURSOR.
|
||||
|
||||
* When querying a PL/SQL function that uses PIPE ROW to emit rows at
|
||||
intermittent intervals. By default, several rows needs to be emitted by the
|
||||
|
@ -259,28 +258,34 @@ There are two cases that will benefit from setting ``prefetchrows`` to zero:
|
|||
Tuning Fetching from REF CURSORS
|
||||
--------------------------------
|
||||
|
||||
In python-oracledb, fetching data from REF CURSORS can be tuned by setting the
|
||||
values of ``arraysize`` and ``prefetchrows``. The ``prefetchrows`` value must
|
||||
be set before calling the PL/SQL procedure because the REF CURSOR is executed
|
||||
on the server.
|
||||
The internal buffering and performance of fetching data from REF CURSORS can be
|
||||
tuned by setting the value of ``arraysize`` before rows are fetched from the
|
||||
cursor. The ``prefetchrows`` value is ignored when fetching *from* REF CURSORS.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Set the arraysize and prefetch rows of the REF cursor
|
||||
ref_cursor = connection.cursor()
|
||||
ref_cursor.prefetchrows = 1000
|
||||
ref_cursor.arraysize = 1000
|
||||
|
||||
# Perform the tuned fetch
|
||||
sum_rows = 0
|
||||
cursor.callproc("myrefcursorproc", [ref_cursor])
|
||||
|
||||
ref_cursor.arraysize = 1000
|
||||
print("Sum of IntCol for", num_rows, "rows:")
|
||||
for row in ref_cursor:
|
||||
sum_rows += row[0]
|
||||
print(sum_rows)
|
||||
|
||||
The ``arraysize`` value can also be set before calling the procedure:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ref_cursor = connection.cursor()
|
||||
ref_cursor.arraysize = 1000
|
||||
|
||||
cursor.callproc("myrefcursorproc", [ref_cursor])
|
||||
for row in ref_cursor:
|
||||
. . .
|
||||
|
||||
.. _roundtrips:
|
||||
|
||||
Also see `Avoiding Premature Prefetching`_.
|
||||
|
|
|
@ -412,7 +412,8 @@ ERR_MESSAGE_FORMATS = {
|
|||
ERR_MESSAGE_HAS_NO_PAYLOAD:
|
||||
'message has no payload',
|
||||
ERR_MESSAGE_TYPE_UNKNOWN:
|
||||
'internal error: unknown protocol message type {message_type}',
|
||||
'internal error: unknown protocol message type {message_type} '
|
||||
'at position {position}',
|
||||
ERR_MISSING_BIND_VALUE:
|
||||
'a bind variable replacement value for placeholder ":{name}" was '
|
||||
'not provided',
|
||||
|
|
|
@ -572,6 +572,9 @@ cdef class ConnectParamsImpl:
|
|||
if pos >= 0:
|
||||
user = credentials[:pos] or None
|
||||
password = credentials[pos + 1:] or None
|
||||
elif connect_string is None:
|
||||
connect_string = dsn or None
|
||||
user = password = None
|
||||
else:
|
||||
user = credentials or None
|
||||
password = None
|
||||
|
@ -605,7 +608,7 @@ cdef class ConnectParamsImpl:
|
|||
"""
|
||||
if kwargs:
|
||||
self.set(kwargs)
|
||||
if self.user is None and dsn is not None:
|
||||
if self.user is None and not self.externalauth and dsn is not None:
|
||||
user, password, dsn = self.parse_dsn_with_credentials(dsn)
|
||||
self.set(dict(user=user, password=password))
|
||||
if dsn is not None and thin:
|
||||
|
|
|
@ -199,9 +199,6 @@ cdef class ThickVarImpl(BaseVarImpl):
|
|||
if dpiStmt_addRef(data.value.asStmt) < 0:
|
||||
_raise_from_odpi()
|
||||
cursor_impl._handle = data.value.asStmt
|
||||
if dpiStmt_setPrefetchRows(cursor_impl._handle,
|
||||
cursor_impl.prefetchrows) < 0:
|
||||
_raise_from_odpi()
|
||||
cursor_impl._fixup_ref_cursor = True
|
||||
cursor.statement = None
|
||||
|
||||
|
|
|
@ -237,11 +237,12 @@ cdef class ThinConnImpl(BaseConnImpl):
|
|||
if statement is None:
|
||||
statement = Statement()
|
||||
statement._prepare(sql)
|
||||
if len(self._statement_cache) < self._statement_cache_size \
|
||||
and cache_statement \
|
||||
and not self._drcp_establish_session:
|
||||
self._statement_cache[sql] = statement
|
||||
if cache_statement and not self._drcp_establish_session \
|
||||
and not statement._is_ddl \
|
||||
and self._statement_cache_size > 0:
|
||||
statement._return_to_cache = True
|
||||
self._statement_cache[sql] = statement
|
||||
self._adjust_statement_cache()
|
||||
elif statement._in_use or not cache_statement \
|
||||
or self._drcp_establish_session:
|
||||
if not cache_statement:
|
||||
|
@ -283,8 +284,6 @@ cdef class ThinConnImpl(BaseConnImpl):
|
|||
with self._statement_cache_lock:
|
||||
if statement._return_to_cache:
|
||||
statement._in_use = False
|
||||
self._statement_cache.move_to_end(statement._sql)
|
||||
self._adjust_statement_cache()
|
||||
elif statement._cursor_id != 0:
|
||||
self._add_cursor_to_close(statement)
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ DEF TNS_PACKET_TYPE_CONTROL = 14
|
|||
DEF TNS_PACKET_TYPE_REDIRECT = 5
|
||||
|
||||
# packet flags
|
||||
DEF TNS_PACKET_FLAG_REDIRECT = 0x04
|
||||
DEF TNS_PACKET_FLAG_TLS_RENEG = 0x08
|
||||
|
||||
# data flags
|
||||
|
|
|
@ -104,10 +104,10 @@ cdef class ThinCursorImpl(BaseCursorImpl):
|
|||
cdef MessageWithData message
|
||||
if self._statement._requires_full_execute:
|
||||
message = self._create_message(ExecuteMessage, cursor)
|
||||
message.num_execs = self._fetch_array_size
|
||||
else:
|
||||
message = self._create_message(FetchMessage, cursor)
|
||||
self._conn_impl._protocol._process_single_message(message)
|
||||
self._statement._requires_full_execute = False
|
||||
|
||||
cdef BaseConnImpl _get_conn_impl(self):
|
||||
"""
|
||||
|
|
|
@ -177,7 +177,8 @@ cdef class Message:
|
|||
self._process_server_side_piggyback(buf)
|
||||
else:
|
||||
errors._raise_err(errors.ERR_MESSAGE_TYPE_UNKNOWN,
|
||||
message_type=message_type)
|
||||
message_type=message_type,
|
||||
position=buf._pos - 1)
|
||||
|
||||
cdef int _process_return_parameters(self, ReadBuffer buf) except -1:
|
||||
raise NotImplementedError()
|
||||
|
@ -351,7 +352,6 @@ cdef class MessageWithData(Message):
|
|||
cursor = self.cursor.connection.cursor()
|
||||
cursor_impl = cursor._impl
|
||||
cursor_impl._statement = Statement()
|
||||
cursor_impl._fetch_array_size = cursor.arraysize + cursor.prefetchrows
|
||||
cursor_impl._more_rows_to_fetch = True
|
||||
cursor_impl._statement._is_query = True
|
||||
cursor_impl._statement._requires_full_execute = True
|
||||
|
@ -740,7 +740,8 @@ cdef class MessageWithData(Message):
|
|||
ThinConnImpl conn_impl = self.conn_impl
|
||||
object exc_type
|
||||
Message._process_error_info(self, buf)
|
||||
cursor_impl._statement._cursor_id = self.error_info.cursor_id
|
||||
if self.error_info.cursor_id != 0:
|
||||
cursor_impl._statement._cursor_id = self.error_info.cursor_id
|
||||
if not cursor_impl._statement._is_plsql and not self.in_fetch:
|
||||
cursor_impl.rowcount = self.error_info.rowcount
|
||||
cursor_impl._lastrowid = self.error_info.rowid
|
||||
|
@ -785,13 +786,15 @@ cdef class MessageWithData(Message):
|
|||
It indicates whether binds are IN only, IN/OUT or OUT only.
|
||||
"""
|
||||
cdef:
|
||||
uint16_t i, num_binds, num_bytes, temp16
|
||||
uint16_t i, num_bytes, temp16
|
||||
uint32_t temp32, num_binds
|
||||
BindInfo bind_info
|
||||
buf.skip_ub1() # flag
|
||||
buf.read_ub2(&num_binds) # num requests
|
||||
buf.read_ub4(&self.row_index) # iter num
|
||||
buf.read_ub2(&temp16) # num requests
|
||||
buf.read_ub4(&temp32) # num iters
|
||||
num_binds = temp32 * 256 + temp16
|
||||
buf.skip_ub4() # num iters this time
|
||||
buf.read_ub2(&temp16) # uac buffer length
|
||||
buf.skip_ub2() # uac buffer length
|
||||
buf.read_ub2(&num_bytes) # bit vector for fast fetch
|
||||
if num_bytes > 0:
|
||||
buf.skip_raw_bytes(num_bytes)
|
||||
|
@ -1325,20 +1328,18 @@ cdef class AuthMessage(Message):
|
|||
newpassword_with_salt)
|
||||
self.encoded_newpassword = encrypted_newpassword.hex().upper()
|
||||
|
||||
cdef int _generate_verifier(self, bint verifier_11g) except -1:
|
||||
cdef int _generate_verifier(self) except -1:
|
||||
"""
|
||||
Generate the multi-round verifier.
|
||||
"""
|
||||
cdef bytes jdwp_data
|
||||
cdef:
|
||||
bytes jdwp_data
|
||||
bytearray b
|
||||
ssize_t i
|
||||
|
||||
# create password hash
|
||||
verifier_data = bytes.fromhex(self.session_data['AUTH_VFR_DATA'])
|
||||
if verifier_11g:
|
||||
keylen = 24
|
||||
h = hashlib.sha1(self.password)
|
||||
h.update(verifier_data)
|
||||
password_hash = h.digest() + bytes(4)
|
||||
else:
|
||||
if self.verifier_type == TNS_VERIFIER_TYPE_12C:
|
||||
keylen = 32
|
||||
iterations = int(self.session_data['AUTH_PBKDF2_VGEN_COUNT'])
|
||||
salt = verifier_data + b'AUTH_PBKDF2_SPEEDY_KEY'
|
||||
|
@ -1348,28 +1349,42 @@ cdef class AuthMessage(Message):
|
|||
h.update(password_key)
|
||||
h.update(verifier_data)
|
||||
password_hash = h.digest()[:32]
|
||||
else:
|
||||
keylen = 24
|
||||
h = hashlib.sha1(self.password)
|
||||
h.update(verifier_data)
|
||||
password_hash = h.digest() + bytes(4)
|
||||
|
||||
# decrypt first half of session key
|
||||
encoded_server_key = bytes.fromhex(self.session_data['AUTH_SESSKEY'])
|
||||
session_key_part_a = decrypt_cbc(password_hash, encoded_server_key)
|
||||
|
||||
# generate second half of session key
|
||||
session_key_part_b = secrets.token_bytes(32)
|
||||
session_key_part_b = secrets.token_bytes(len(session_key_part_a))
|
||||
encoded_client_key = encrypt_cbc(password_hash, session_key_part_b)
|
||||
self.session_key = encoded_client_key.hex().upper()[:64]
|
||||
|
||||
# create session key from combo key
|
||||
mixing_salt = bytes.fromhex(self.session_data['AUTH_PBKDF2_CSK_SALT'])
|
||||
iterations = int(self.session_data['AUTH_PBKDF2_SDER_COUNT'])
|
||||
temp_key = session_key_part_b[:keylen] + session_key_part_a[:keylen]
|
||||
combo_key = get_derived_key(temp_key.hex().upper().encode(),
|
||||
mixing_salt, keylen, iterations)
|
||||
# create session key and combo key
|
||||
if len(session_key_part_a) == 48:
|
||||
self.session_key = encoded_client_key.hex().upper()[:96]
|
||||
b = bytearray(24)
|
||||
for i in range(16, 40):
|
||||
b[i - 16] = session_key_part_a[i] ^ session_key_part_b[i]
|
||||
part1 = hashlib.md5(b[:16]).digest()
|
||||
part2 = hashlib.md5(b[16:]).digest()
|
||||
combo_key = (part1 + part2)[:keylen]
|
||||
else:
|
||||
self.session_key = encoded_client_key.hex().upper()[:64]
|
||||
salt = bytes.fromhex(self.session_data['AUTH_PBKDF2_CSK_SALT'])
|
||||
iterations = int(self.session_data['AUTH_PBKDF2_SDER_COUNT'])
|
||||
temp_key = session_key_part_b[:keylen] + session_key_part_a[:keylen]
|
||||
combo_key = get_derived_key(temp_key.hex().upper().encode(), salt,
|
||||
keylen, iterations)
|
||||
|
||||
# retain session key for use by the change password API
|
||||
self.conn_impl._combo_key = combo_key
|
||||
|
||||
# generate speedy key for 12c verifiers
|
||||
if not verifier_11g:
|
||||
if self.verifier_type == TNS_VERIFIER_TYPE_12C:
|
||||
salt = secrets.token_bytes(16)
|
||||
speedy_key = encrypt_cbc(combo_key, salt + password_key)
|
||||
self.speedy_key = speedy_key[:80].hex().upper()
|
||||
|
@ -1537,7 +1552,6 @@ cdef class AuthMessage(Message):
|
|||
cdef int _write_message(self, WriteBuffer buf) except -1:
|
||||
cdef:
|
||||
uint8_t has_user = 1 if self.user_bytes_len > 0 else 0
|
||||
bint verifier_11g = False
|
||||
uint32_t num_pairs
|
||||
|
||||
# perform final determination of data to write
|
||||
|
@ -1557,15 +1571,13 @@ cdef class AuthMessage(Message):
|
|||
else:
|
||||
num_pairs += 2
|
||||
self.auth_mode |= TNS_AUTH_MODE_WITH_PASSWORD
|
||||
if self.verifier_type in (TNS_VERIFIER_TYPE_11G_1,
|
||||
TNS_VERIFIER_TYPE_11G_2):
|
||||
verifier_11g = True
|
||||
elif self.verifier_type != TNS_VERIFIER_TYPE_12C:
|
||||
if self.verifier_type == TNS_VERIFIER_TYPE_12C:
|
||||
num_pairs += 1
|
||||
elif self.verifier_type not in (TNS_VERIFIER_TYPE_11G_1,
|
||||
TNS_VERIFIER_TYPE_11G_2):
|
||||
errors._raise_err(errors.ERR_UNSUPPORTED_VERIFIER_TYPE,
|
||||
verifier_type=self.verifier_type)
|
||||
else:
|
||||
num_pairs += 1
|
||||
self._generate_verifier(verifier_11g)
|
||||
self._generate_verifier()
|
||||
|
||||
# determine which other key/value pairs to write
|
||||
if self.newpassword is not None:
|
||||
|
@ -1613,7 +1625,7 @@ cdef class AuthMessage(Message):
|
|||
self._write_key_value(buf, "AUTH_TOKEN", self.token)
|
||||
elif not self.change_password:
|
||||
self._write_key_value(buf, "AUTH_SESSKEY", self.session_key, 1)
|
||||
if not verifier_11g:
|
||||
if self.verifier_type == TNS_VERIFIER_TYPE_12C:
|
||||
self._write_key_value(buf, "AUTH_PBKDF2_SPEEDY_KEY",
|
||||
self.speedy_key)
|
||||
if self.encoded_password is not None:
|
||||
|
@ -1735,7 +1747,7 @@ cdef class ConnectMessage(Message):
|
|||
if buf._caps.supports_oob:
|
||||
service_options |= TNS_GSO_CAN_RECV_ATTENTION
|
||||
connect_flags_2 |= TNS_CHECK_OOB
|
||||
buf.start_request(TNS_PACKET_TYPE_CONNECT)
|
||||
buf.start_request(TNS_PACKET_TYPE_CONNECT, self.packet_flags)
|
||||
buf.write_uint16(TNS_VERSION_DESIRED)
|
||||
buf.write_uint16(TNS_VERSION_MINIMUM)
|
||||
buf.write_uint16(service_options)
|
||||
|
@ -1843,13 +1855,13 @@ cdef class ExecuteMessage(MessageWithData):
|
|||
if self.parse_only:
|
||||
options |= TNS_EXEC_OPTION_DESCRIBE
|
||||
else:
|
||||
if self.cursor_impl.prefetchrows > 0:
|
||||
options |= TNS_EXEC_OPTION_FETCH
|
||||
if stmt._cursor_id == 0 or stmt._requires_define:
|
||||
num_iters = self.cursor_impl.prefetchrows
|
||||
self.cursor_impl._fetch_array_size = num_iters
|
||||
else:
|
||||
num_iters = self.cursor_impl._fetch_array_size
|
||||
num_iters = self.cursor_impl.arraysize
|
||||
self.cursor_impl._fetch_array_size = num_iters
|
||||
if num_iters > 0:
|
||||
options |= TNS_EXEC_OPTION_FETCH
|
||||
if not stmt._is_plsql and not self.parse_only:
|
||||
options |= TNS_EXEC_OPTION_NOT_PLSQL
|
||||
elif stmt._is_plsql and num_params > 0:
|
||||
|
@ -2226,3 +2238,26 @@ cdef class RollbackMessage(Message):
|
|||
Perform initialization.
|
||||
"""
|
||||
self.function_code = TNS_FUNC_ROLLBACK
|
||||
|
||||
|
||||
@cython.final
|
||||
cdef class SessionReleaseMessage(Message):
|
||||
|
||||
cdef:
|
||||
uint32_t release_mode
|
||||
|
||||
cdef int _initialize_hook(self) except -1:
|
||||
"""
|
||||
Perform initialization.
|
||||
"""
|
||||
self.message_type = TNS_MSG_TYPE_ONEWAY_FN
|
||||
self.function_code = TNS_FUNC_SESSION_RELEASE
|
||||
|
||||
cdef int _write_message(self, WriteBuffer buf) except -1:
|
||||
"""
|
||||
Write the message for a DRCP session release.
|
||||
"""
|
||||
self._write_function_code(buf)
|
||||
buf.write_uint8(0) # pointer (tag name)
|
||||
buf.write_uint8(0) # tag name length
|
||||
buf.write_ub4(self.release_mode) # mode
|
||||
|
|
|
@ -587,6 +587,7 @@ cdef class WriteBuffer(Buffer):
|
|||
|
||||
cdef:
|
||||
uint8_t _packet_type
|
||||
uint8_t _packet_flags
|
||||
Capabilities _caps
|
||||
object _socket
|
||||
uint8_t _seq_num
|
||||
|
@ -610,7 +611,7 @@ cdef class WriteBuffer(Buffer):
|
|||
self.write_uint16(size)
|
||||
self.write_uint16(0)
|
||||
self.write_uint8(self._packet_type)
|
||||
self.write_uint8(0)
|
||||
self.write_uint8(self._packet_flags)
|
||||
self.write_uint16(0)
|
||||
self._pos = size
|
||||
if DEBUG_PACKETS:
|
||||
|
@ -650,7 +651,8 @@ cdef class WriteBuffer(Buffer):
|
|||
"""
|
||||
return self._max_size - PACKET_HEADER_SIZE - 2
|
||||
|
||||
cdef void start_request(self, uint8_t packet_type, uint16_t data_flags=0):
|
||||
cdef void start_request(self, uint8_t packet_type, uint8_t packet_flags=0,
|
||||
uint16_t data_flags=0):
|
||||
"""
|
||||
Indicates that a request from the client is starting. The packet type
|
||||
is retained just in case a request spans multiple packets. The packet
|
||||
|
@ -659,6 +661,7 @@ cdef class WriteBuffer(Buffer):
|
|||
"""
|
||||
self._packet_sent = False
|
||||
self._packet_type = packet_type
|
||||
self._packet_flags = packet_flags
|
||||
self._pos = PACKET_HEADER_SIZE
|
||||
if packet_type == TNS_PACKET_TYPE_DATA:
|
||||
self.write_uint16(data_flags)
|
||||
|
|
|
@ -102,7 +102,7 @@ cdef class Protocol:
|
|||
message = conn_impl._create_message(RollbackMessage)
|
||||
self._process_message(message)
|
||||
if conn_impl._drcp_enabled:
|
||||
self._release_drcp_session(self._write_buf, release_mode)
|
||||
self._release_drcp_session(conn_impl, release_mode)
|
||||
conn_impl._drcp_establish_session = True
|
||||
|
||||
# if the connection is part of a pool, return it to the pool
|
||||
|
@ -133,11 +133,11 @@ cdef class Protocol:
|
|||
"""
|
||||
cdef:
|
||||
ConnectMessage connect_message = None
|
||||
uint8_t packet_type, packet_flags = 0
|
||||
object ssl_context, connect_info
|
||||
ConnectParamsImpl temp_params
|
||||
str host, redirect_data
|
||||
Address temp_address
|
||||
uint8_t packet_type
|
||||
int port, pos
|
||||
|
||||
# store whether OOB processing is possible or not
|
||||
|
@ -163,6 +163,7 @@ cdef class Protocol:
|
|||
connect_message.connect_string_bytes = connect_string.encode()
|
||||
connect_message.connect_string_len = \
|
||||
<uint16_t> len(connect_message.connect_string_bytes)
|
||||
connect_message.packet_flags = packet_flags
|
||||
|
||||
# process connection message
|
||||
self._process_message(connect_message)
|
||||
|
@ -180,6 +181,7 @@ cdef class Protocol:
|
|||
connect_string = redirect_data[pos + 1:]
|
||||
self._connect_tcp(params, description, address, host, port)
|
||||
connect_message = None
|
||||
packet_flags = TNS_PACKET_FLAG_REDIRECT
|
||||
elif connect_message.packet_type == TNS_PACKET_TYPE_ACCEPT:
|
||||
break
|
||||
|
||||
|
@ -226,7 +228,8 @@ cdef class Protocol:
|
|||
auth_message = conn_impl._create_message(AuthMessage)
|
||||
auth_message._set_params(params, description)
|
||||
self._process_message(auth_message)
|
||||
self._process_message(auth_message)
|
||||
if auth_message.resend:
|
||||
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
|
||||
|
@ -292,7 +295,7 @@ cdef class Protocol:
|
|||
"""
|
||||
Send the final close packet to the server and close the socket.
|
||||
"""
|
||||
buf.start_request(TNS_PACKET_TYPE_DATA, TNS_DATA_FLAGS_EOF)
|
||||
buf.start_request(TNS_PACKET_TYPE_DATA, 0, TNS_DATA_FLAGS_EOF)
|
||||
buf.end_request()
|
||||
self._socket.shutdown(socket.SHUT_RDWR)
|
||||
self._socket.close()
|
||||
|
@ -394,20 +397,16 @@ cdef class Protocol:
|
|||
buf.skip_raw_bytes(3)
|
||||
message.error_info.message = buf.read_str(TNS_CS_IMPLICIT)
|
||||
|
||||
cdef int _release_drcp_session(self, WriteBuffer buf,
|
||||
cdef int _release_drcp_session(self, ThinConnImpl conn_impl,
|
||||
uint32_t release_mode) except -1:
|
||||
"""
|
||||
Release the session back to DRCP. Standalone sessions are marked for
|
||||
deauthentication.
|
||||
"""
|
||||
buf.start_request(TNS_PACKET_TYPE_DATA)
|
||||
buf.write_uint8(TNS_MSG_TYPE_ONEWAY_FN)
|
||||
buf.write_uint8(TNS_FUNC_SESSION_RELEASE)
|
||||
buf.write_uint8(0) # seq number
|
||||
buf.write_uint8(0) # pointer (tag name)
|
||||
buf.write_uint8(0) # tag name length
|
||||
buf.write_ub4(release_mode) # mode
|
||||
buf.end_request()
|
||||
cdef SessionReleaseMessage message
|
||||
message = conn_impl._create_message(SessionReleaseMessage)
|
||||
message.release_mode = release_mode
|
||||
message.send(self._write_buf)
|
||||
|
||||
cdef int _reset(self, Message message) except -1:
|
||||
cdef uint8_t marker_type
|
||||
|
|
|
@ -30,4 +30,4 @@
|
|||
# file doc/src/conf.py both reference this file directly.
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
__version__ = "1.3.1"
|
||||
__version__ = "1.3.2"
|
||||
|
|
|
@ -138,7 +138,8 @@ class TestCase(test_env.BaseTestCase):
|
|||
|
||||
def test_1105_bad_connect_string(self):
|
||||
"1105 - connection to database with bad connect string"
|
||||
self.assertRaisesRegex(oracledb.DatabaseError, "^DPY-4001:|ORA-12547:",
|
||||
self.assertRaisesRegex(oracledb.DatabaseError,
|
||||
"^DPY-4000:|^DPY-4026:|^DPY-4027:|ORA-12154:",
|
||||
oracledb.connect, test_env.get_main_user())
|
||||
self.assertRaisesRegex(oracledb.DatabaseError, "^DPY-4000:|^DPY-4001:",
|
||||
oracledb.connect, test_env.get_main_user() + \
|
||||
|
|
|
@ -138,8 +138,8 @@ class TestCase(test_env.BaseTestCase):
|
|||
rows = ref_cursor.fetchall()
|
||||
self.assertEqual(rows, expected_value)
|
||||
|
||||
def test_1306_refcursor_prefetchrows(self):
|
||||
"1306 - test prefetch rows and arraysize using a refcursor"
|
||||
def test_1306_refcursor_round_trips(self):
|
||||
"1306 - test round trips using a REF cursor"
|
||||
self.setup_round_trip_checker()
|
||||
|
||||
# simple DDL only requires a single round trip
|
||||
|
@ -155,14 +155,23 @@ class TestCase(test_env.BaseTestCase):
|
|||
cursor.executemany(sql, data)
|
||||
self.assertRoundTrips(1)
|
||||
|
||||
# create refcursor and execute stored procedure
|
||||
# create REF cursor and execute stored procedure
|
||||
# (array size set before procedure is called)
|
||||
with self.connection.cursor() as cursor:
|
||||
refcursor = self.connection.cursor()
|
||||
refcursor.prefetchrows = 150
|
||||
refcursor.arraysize = 50
|
||||
refcursor.arraysize = 150
|
||||
cursor.callproc("myrefcursorproc", [refcursor])
|
||||
refcursor.fetchall()
|
||||
self.assertRoundTrips(4)
|
||||
self.assertRoundTrips(5)
|
||||
|
||||
# create REF cursor and execute stored procedure
|
||||
# (array size set after procedure is called)
|
||||
with self.connection.cursor() as cursor:
|
||||
refcursor = self.connection.cursor()
|
||||
cursor.callproc("myrefcursorproc", [refcursor])
|
||||
refcursor.arraysize = 145
|
||||
refcursor.fetchall()
|
||||
self.assertRoundTrips(6)
|
||||
|
||||
def test_1307_refcursor_execute_different_sql(self):
|
||||
"1307 - test executing different SQL after getting a REF cursor"
|
||||
|
|
|
@ -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
|
||||
|
@ -310,5 +310,32 @@ class TestCase(test_env.BaseTestCase):
|
|||
fetched_data = self.cursor.fetchall()
|
||||
self.assertEqual(data, fetched_data)
|
||||
|
||||
def test_4022_plsql_large_number_of_binds(self):
|
||||
"4022 - test PL/SQL with a lerge number of binds"
|
||||
parts = []
|
||||
bind_names = []
|
||||
all_bind_values = []
|
||||
out_binds = []
|
||||
for i in range(5):
|
||||
all_bind_values.append([])
|
||||
for i in range(350):
|
||||
n = len(parts) + 1
|
||||
bind_names.extend([f"v_out_{n}_0", f"a_{n}", f"b_{n}", f"c_{n}"])
|
||||
parts.append(f":v_out{n} := :a_{n} + :b_{n} + :c_{n};")
|
||||
out_binds.append(self.cursor.var(int,
|
||||
arraysize=len(all_bind_values)))
|
||||
for j, bind_values in enumerate(all_bind_values):
|
||||
bind_values.extend([out_binds[-1], n * 1 + j, n * 2 + j,
|
||||
n * 3 + j])
|
||||
lf = "\n"
|
||||
sql = f"begin{lf}{lf.join(parts)}{lf}end;"
|
||||
self.cursor.executemany(sql, all_bind_values)
|
||||
init_val = 6
|
||||
for var in out_binds:
|
||||
expected = [init_val, init_val + 3, init_val + 6, init_val + 9,
|
||||
init_val + 12]
|
||||
self.assertEqual(var.values, expected)
|
||||
init_val += 6
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_env.run_test_cases()
|
||||
|
|
|
@ -346,13 +346,15 @@ class TestCase(test_env.BaseTestCase):
|
|||
num_rows = 590
|
||||
with self.connection.cursor() as cursor:
|
||||
cursor.execute("truncate table TestTempTable")
|
||||
self.assertRoundTrips(1)
|
||||
sql = "insert into TestTempTable (IntCol) values (:1)"
|
||||
data = [(n + 1,) for n in range(num_rows)]
|
||||
cursor.executemany(sql, data)
|
||||
cursor.prefetchrows = 300
|
||||
cursor.arraysize = 300
|
||||
self.assertRoundTrips(1)
|
||||
cursor.prefetchrows = 30
|
||||
cursor.arraysize = 100
|
||||
cursor.execute("select IntCol from TestTempTable").fetchall()
|
||||
self.assertRoundTrips(4)
|
||||
self.assertRoundTrips(7)
|
||||
|
||||
def test_4324_bind_names_with_single_line_comments(self):
|
||||
"4324 - test bindnames() with single line comments"
|
||||
|
|
|
@ -619,5 +619,14 @@ class TestCase(test_env.BaseTestCase):
|
|||
self.assertEqual(password, None)
|
||||
self.assertEqual(dsn, None)
|
||||
|
||||
def test_4561_dsn_with_no_credentials(self):
|
||||
"4561 - test parsing a DSN with no credentials"
|
||||
dsn_in = "my_alias_4561"
|
||||
params = oracledb.ConnectParams()
|
||||
user, password, dsn_out = params.parse_dsn_with_credentials(dsn_in)
|
||||
self.assertEqual(user, None)
|
||||
self.assertEqual(password, None)
|
||||
self.assertEqual(dsn_out, dsn_in)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_env.run_test_cases()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue