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>`.
|
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)
|
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:
|
There are two cases that will benefit from setting ``prefetchrows`` to zero:
|
||||||
|
|
||||||
* When passing REF CURSORS into PL/SQL packages. Setting ``prefetchrows`` to 0
|
* When passing REF CURSORS *into* PL/SQL packages. Setting ``prefetchrows`` to
|
||||||
can stop rows being prematurely (and silently) fetched into the
|
0 can stop rows being prematurely (and silently) fetched into the
|
||||||
python-oracledb or Oracle Client (in python-oracledb Thick mode) internal
|
python-oracledb internal buffer, making those rows unavailable to the PL/SQL
|
||||||
buffer, making those rows unavailable to the PL/SQL code that receives the
|
code that receives the REF CURSOR.
|
||||||
REF CURSOR.
|
|
||||||
|
|
||||||
* When querying a PL/SQL function that uses PIPE ROW to emit rows at
|
* 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
|
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
|
Tuning Fetching from REF CURSORS
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
In python-oracledb, fetching data from REF CURSORS can be tuned by setting the
|
The internal buffering and performance of fetching data from REF CURSORS can be
|
||||||
values of ``arraysize`` and ``prefetchrows``. The ``prefetchrows`` value must
|
tuned by setting the value of ``arraysize`` before rows are fetched from the
|
||||||
be set before calling the PL/SQL procedure because the REF CURSOR is executed
|
cursor. The ``prefetchrows`` value is ignored when fetching *from* REF CURSORS.
|
||||||
on the server.
|
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# Set the arraysize and prefetch rows of the REF cursor
|
|
||||||
ref_cursor = connection.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])
|
cursor.callproc("myrefcursorproc", [ref_cursor])
|
||||||
|
|
||||||
|
ref_cursor.arraysize = 1000
|
||||||
print("Sum of IntCol for", num_rows, "rows:")
|
print("Sum of IntCol for", num_rows, "rows:")
|
||||||
for row in ref_cursor:
|
for row in ref_cursor:
|
||||||
sum_rows += row[0]
|
sum_rows += row[0]
|
||||||
print(sum_rows)
|
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:
|
.. _roundtrips:
|
||||||
|
|
||||||
Also see `Avoiding Premature Prefetching`_.
|
Also see `Avoiding Premature Prefetching`_.
|
||||||
|
|
|
@ -412,7 +412,8 @@ ERR_MESSAGE_FORMATS = {
|
||||||
ERR_MESSAGE_HAS_NO_PAYLOAD:
|
ERR_MESSAGE_HAS_NO_PAYLOAD:
|
||||||
'message has no payload',
|
'message has no payload',
|
||||||
ERR_MESSAGE_TYPE_UNKNOWN:
|
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:
|
ERR_MISSING_BIND_VALUE:
|
||||||
'a bind variable replacement value for placeholder ":{name}" was '
|
'a bind variable replacement value for placeholder ":{name}" was '
|
||||||
'not provided',
|
'not provided',
|
||||||
|
|
|
@ -572,6 +572,9 @@ cdef class ConnectParamsImpl:
|
||||||
if pos >= 0:
|
if pos >= 0:
|
||||||
user = credentials[:pos] or None
|
user = credentials[:pos] or None
|
||||||
password = credentials[pos + 1:] or None
|
password = credentials[pos + 1:] or None
|
||||||
|
elif connect_string is None:
|
||||||
|
connect_string = dsn or None
|
||||||
|
user = password = None
|
||||||
else:
|
else:
|
||||||
user = credentials or None
|
user = credentials or None
|
||||||
password = None
|
password = None
|
||||||
|
@ -605,7 +608,7 @@ cdef class ConnectParamsImpl:
|
||||||
"""
|
"""
|
||||||
if kwargs:
|
if kwargs:
|
||||||
self.set(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)
|
user, password, dsn = self.parse_dsn_with_credentials(dsn)
|
||||||
self.set(dict(user=user, password=password))
|
self.set(dict(user=user, password=password))
|
||||||
if dsn is not None and thin:
|
if dsn is not None and thin:
|
||||||
|
|
|
@ -199,9 +199,6 @@ cdef class ThickVarImpl(BaseVarImpl):
|
||||||
if dpiStmt_addRef(data.value.asStmt) < 0:
|
if dpiStmt_addRef(data.value.asStmt) < 0:
|
||||||
_raise_from_odpi()
|
_raise_from_odpi()
|
||||||
cursor_impl._handle = data.value.asStmt
|
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_impl._fixup_ref_cursor = True
|
||||||
cursor.statement = None
|
cursor.statement = None
|
||||||
|
|
||||||
|
|
|
@ -237,11 +237,12 @@ cdef class ThinConnImpl(BaseConnImpl):
|
||||||
if statement is None:
|
if statement is None:
|
||||||
statement = Statement()
|
statement = Statement()
|
||||||
statement._prepare(sql)
|
statement._prepare(sql)
|
||||||
if len(self._statement_cache) < self._statement_cache_size \
|
if cache_statement and not self._drcp_establish_session \
|
||||||
and cache_statement \
|
and not statement._is_ddl \
|
||||||
and not self._drcp_establish_session:
|
and self._statement_cache_size > 0:
|
||||||
self._statement_cache[sql] = statement
|
|
||||||
statement._return_to_cache = True
|
statement._return_to_cache = True
|
||||||
|
self._statement_cache[sql] = statement
|
||||||
|
self._adjust_statement_cache()
|
||||||
elif statement._in_use or not cache_statement \
|
elif statement._in_use or not cache_statement \
|
||||||
or self._drcp_establish_session:
|
or self._drcp_establish_session:
|
||||||
if not cache_statement:
|
if not cache_statement:
|
||||||
|
@ -283,8 +284,6 @@ cdef class ThinConnImpl(BaseConnImpl):
|
||||||
with self._statement_cache_lock:
|
with self._statement_cache_lock:
|
||||||
if statement._return_to_cache:
|
if statement._return_to_cache:
|
||||||
statement._in_use = False
|
statement._in_use = False
|
||||||
self._statement_cache.move_to_end(statement._sql)
|
|
||||||
self._adjust_statement_cache()
|
|
||||||
elif statement._cursor_id != 0:
|
elif statement._cursor_id != 0:
|
||||||
self._add_cursor_to_close(statement)
|
self._add_cursor_to_close(statement)
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ DEF TNS_PACKET_TYPE_CONTROL = 14
|
||||||
DEF TNS_PACKET_TYPE_REDIRECT = 5
|
DEF TNS_PACKET_TYPE_REDIRECT = 5
|
||||||
|
|
||||||
# packet flags
|
# packet flags
|
||||||
|
DEF TNS_PACKET_FLAG_REDIRECT = 0x04
|
||||||
DEF TNS_PACKET_FLAG_TLS_RENEG = 0x08
|
DEF TNS_PACKET_FLAG_TLS_RENEG = 0x08
|
||||||
|
|
||||||
# data flags
|
# data flags
|
||||||
|
|
|
@ -104,10 +104,10 @@ cdef class ThinCursorImpl(BaseCursorImpl):
|
||||||
cdef MessageWithData message
|
cdef MessageWithData message
|
||||||
if self._statement._requires_full_execute:
|
if self._statement._requires_full_execute:
|
||||||
message = self._create_message(ExecuteMessage, cursor)
|
message = self._create_message(ExecuteMessage, cursor)
|
||||||
message.num_execs = self._fetch_array_size
|
|
||||||
else:
|
else:
|
||||||
message = self._create_message(FetchMessage, cursor)
|
message = self._create_message(FetchMessage, cursor)
|
||||||
self._conn_impl._protocol._process_single_message(message)
|
self._conn_impl._protocol._process_single_message(message)
|
||||||
|
self._statement._requires_full_execute = False
|
||||||
|
|
||||||
cdef BaseConnImpl _get_conn_impl(self):
|
cdef BaseConnImpl _get_conn_impl(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -177,7 +177,8 @@ cdef class Message:
|
||||||
self._process_server_side_piggyback(buf)
|
self._process_server_side_piggyback(buf)
|
||||||
else:
|
else:
|
||||||
errors._raise_err(errors.ERR_MESSAGE_TYPE_UNKNOWN,
|
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:
|
cdef int _process_return_parameters(self, ReadBuffer buf) except -1:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
@ -351,7 +352,6 @@ cdef class MessageWithData(Message):
|
||||||
cursor = self.cursor.connection.cursor()
|
cursor = self.cursor.connection.cursor()
|
||||||
cursor_impl = cursor._impl
|
cursor_impl = cursor._impl
|
||||||
cursor_impl._statement = Statement()
|
cursor_impl._statement = Statement()
|
||||||
cursor_impl._fetch_array_size = cursor.arraysize + cursor.prefetchrows
|
|
||||||
cursor_impl._more_rows_to_fetch = True
|
cursor_impl._more_rows_to_fetch = True
|
||||||
cursor_impl._statement._is_query = True
|
cursor_impl._statement._is_query = True
|
||||||
cursor_impl._statement._requires_full_execute = True
|
cursor_impl._statement._requires_full_execute = True
|
||||||
|
@ -740,7 +740,8 @@ cdef class MessageWithData(Message):
|
||||||
ThinConnImpl conn_impl = self.conn_impl
|
ThinConnImpl conn_impl = self.conn_impl
|
||||||
object exc_type
|
object exc_type
|
||||||
Message._process_error_info(self, buf)
|
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:
|
if not cursor_impl._statement._is_plsql and not self.in_fetch:
|
||||||
cursor_impl.rowcount = self.error_info.rowcount
|
cursor_impl.rowcount = self.error_info.rowcount
|
||||||
cursor_impl._lastrowid = self.error_info.rowid
|
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.
|
It indicates whether binds are IN only, IN/OUT or OUT only.
|
||||||
"""
|
"""
|
||||||
cdef:
|
cdef:
|
||||||
uint16_t i, num_binds, num_bytes, temp16
|
uint16_t i, num_bytes, temp16
|
||||||
|
uint32_t temp32, num_binds
|
||||||
BindInfo bind_info
|
BindInfo bind_info
|
||||||
buf.skip_ub1() # flag
|
buf.skip_ub1() # flag
|
||||||
buf.read_ub2(&num_binds) # num requests
|
buf.read_ub2(&temp16) # num requests
|
||||||
buf.read_ub4(&self.row_index) # iter num
|
buf.read_ub4(&temp32) # num iters
|
||||||
|
num_binds = temp32 * 256 + temp16
|
||||||
buf.skip_ub4() # num iters this time
|
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
|
buf.read_ub2(&num_bytes) # bit vector for fast fetch
|
||||||
if num_bytes > 0:
|
if num_bytes > 0:
|
||||||
buf.skip_raw_bytes(num_bytes)
|
buf.skip_raw_bytes(num_bytes)
|
||||||
|
@ -1325,20 +1328,18 @@ cdef class AuthMessage(Message):
|
||||||
newpassword_with_salt)
|
newpassword_with_salt)
|
||||||
self.encoded_newpassword = encrypted_newpassword.hex().upper()
|
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.
|
Generate the multi-round verifier.
|
||||||
"""
|
"""
|
||||||
cdef bytes jdwp_data
|
cdef:
|
||||||
|
bytes jdwp_data
|
||||||
|
bytearray b
|
||||||
|
ssize_t i
|
||||||
|
|
||||||
# create password hash
|
# create password hash
|
||||||
verifier_data = bytes.fromhex(self.session_data['AUTH_VFR_DATA'])
|
verifier_data = bytes.fromhex(self.session_data['AUTH_VFR_DATA'])
|
||||||
if verifier_11g:
|
if self.verifier_type == TNS_VERIFIER_TYPE_12C:
|
||||||
keylen = 24
|
|
||||||
h = hashlib.sha1(self.password)
|
|
||||||
h.update(verifier_data)
|
|
||||||
password_hash = h.digest() + bytes(4)
|
|
||||||
else:
|
|
||||||
keylen = 32
|
keylen = 32
|
||||||
iterations = int(self.session_data['AUTH_PBKDF2_VGEN_COUNT'])
|
iterations = int(self.session_data['AUTH_PBKDF2_VGEN_COUNT'])
|
||||||
salt = verifier_data + b'AUTH_PBKDF2_SPEEDY_KEY'
|
salt = verifier_data + b'AUTH_PBKDF2_SPEEDY_KEY'
|
||||||
|
@ -1348,28 +1349,42 @@ cdef class AuthMessage(Message):
|
||||||
h.update(password_key)
|
h.update(password_key)
|
||||||
h.update(verifier_data)
|
h.update(verifier_data)
|
||||||
password_hash = h.digest()[:32]
|
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
|
# decrypt first half of session key
|
||||||
encoded_server_key = bytes.fromhex(self.session_data['AUTH_SESSKEY'])
|
encoded_server_key = bytes.fromhex(self.session_data['AUTH_SESSKEY'])
|
||||||
session_key_part_a = decrypt_cbc(password_hash, encoded_server_key)
|
session_key_part_a = decrypt_cbc(password_hash, encoded_server_key)
|
||||||
|
|
||||||
# generate second half of session 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)
|
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
|
# create session key and combo key
|
||||||
mixing_salt = bytes.fromhex(self.session_data['AUTH_PBKDF2_CSK_SALT'])
|
if len(session_key_part_a) == 48:
|
||||||
iterations = int(self.session_data['AUTH_PBKDF2_SDER_COUNT'])
|
self.session_key = encoded_client_key.hex().upper()[:96]
|
||||||
temp_key = session_key_part_b[:keylen] + session_key_part_a[:keylen]
|
b = bytearray(24)
|
||||||
combo_key = get_derived_key(temp_key.hex().upper().encode(),
|
for i in range(16, 40):
|
||||||
mixing_salt, keylen, iterations)
|
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
|
# retain session key for use by the change password API
|
||||||
self.conn_impl._combo_key = combo_key
|
self.conn_impl._combo_key = combo_key
|
||||||
|
|
||||||
# generate speedy key for 12c verifiers
|
# generate speedy key for 12c verifiers
|
||||||
if not verifier_11g:
|
if self.verifier_type == TNS_VERIFIER_TYPE_12C:
|
||||||
salt = secrets.token_bytes(16)
|
salt = secrets.token_bytes(16)
|
||||||
speedy_key = encrypt_cbc(combo_key, salt + password_key)
|
speedy_key = encrypt_cbc(combo_key, salt + password_key)
|
||||||
self.speedy_key = speedy_key[:80].hex().upper()
|
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 int _write_message(self, WriteBuffer buf) except -1:
|
||||||
cdef:
|
cdef:
|
||||||
uint8_t has_user = 1 if self.user_bytes_len > 0 else 0
|
uint8_t has_user = 1 if self.user_bytes_len > 0 else 0
|
||||||
bint verifier_11g = False
|
|
||||||
uint32_t num_pairs
|
uint32_t num_pairs
|
||||||
|
|
||||||
# perform final determination of data to write
|
# perform final determination of data to write
|
||||||
|
@ -1557,15 +1571,13 @@ cdef class AuthMessage(Message):
|
||||||
else:
|
else:
|
||||||
num_pairs += 2
|
num_pairs += 2
|
||||||
self.auth_mode |= TNS_AUTH_MODE_WITH_PASSWORD
|
self.auth_mode |= TNS_AUTH_MODE_WITH_PASSWORD
|
||||||
if self.verifier_type in (TNS_VERIFIER_TYPE_11G_1,
|
if self.verifier_type == TNS_VERIFIER_TYPE_12C:
|
||||||
TNS_VERIFIER_TYPE_11G_2):
|
num_pairs += 1
|
||||||
verifier_11g = True
|
elif self.verifier_type not in (TNS_VERIFIER_TYPE_11G_1,
|
||||||
elif self.verifier_type != TNS_VERIFIER_TYPE_12C:
|
TNS_VERIFIER_TYPE_11G_2):
|
||||||
errors._raise_err(errors.ERR_UNSUPPORTED_VERIFIER_TYPE,
|
errors._raise_err(errors.ERR_UNSUPPORTED_VERIFIER_TYPE,
|
||||||
verifier_type=self.verifier_type)
|
verifier_type=self.verifier_type)
|
||||||
else:
|
self._generate_verifier()
|
||||||
num_pairs += 1
|
|
||||||
self._generate_verifier(verifier_11g)
|
|
||||||
|
|
||||||
# determine which other key/value pairs to write
|
# determine which other key/value pairs to write
|
||||||
if self.newpassword is not None:
|
if self.newpassword is not None:
|
||||||
|
@ -1613,7 +1625,7 @@ cdef class AuthMessage(Message):
|
||||||
self._write_key_value(buf, "AUTH_TOKEN", self.token)
|
self._write_key_value(buf, "AUTH_TOKEN", self.token)
|
||||||
elif not self.change_password:
|
elif not self.change_password:
|
||||||
self._write_key_value(buf, "AUTH_SESSKEY", self.session_key, 1)
|
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._write_key_value(buf, "AUTH_PBKDF2_SPEEDY_KEY",
|
||||||
self.speedy_key)
|
self.speedy_key)
|
||||||
if self.encoded_password is not None:
|
if self.encoded_password is not None:
|
||||||
|
@ -1735,7 +1747,7 @@ cdef class ConnectMessage(Message):
|
||||||
if buf._caps.supports_oob:
|
if buf._caps.supports_oob:
|
||||||
service_options |= TNS_GSO_CAN_RECV_ATTENTION
|
service_options |= TNS_GSO_CAN_RECV_ATTENTION
|
||||||
connect_flags_2 |= TNS_CHECK_OOB
|
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_DESIRED)
|
||||||
buf.write_uint16(TNS_VERSION_MINIMUM)
|
buf.write_uint16(TNS_VERSION_MINIMUM)
|
||||||
buf.write_uint16(service_options)
|
buf.write_uint16(service_options)
|
||||||
|
@ -1843,13 +1855,13 @@ cdef class ExecuteMessage(MessageWithData):
|
||||||
if self.parse_only:
|
if self.parse_only:
|
||||||
options |= TNS_EXEC_OPTION_DESCRIBE
|
options |= TNS_EXEC_OPTION_DESCRIBE
|
||||||
else:
|
else:
|
||||||
if self.cursor_impl.prefetchrows > 0:
|
|
||||||
options |= TNS_EXEC_OPTION_FETCH
|
|
||||||
if stmt._cursor_id == 0 or stmt._requires_define:
|
if stmt._cursor_id == 0 or stmt._requires_define:
|
||||||
num_iters = self.cursor_impl.prefetchrows
|
num_iters = self.cursor_impl.prefetchrows
|
||||||
self.cursor_impl._fetch_array_size = num_iters
|
|
||||||
else:
|
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:
|
if not stmt._is_plsql and not self.parse_only:
|
||||||
options |= TNS_EXEC_OPTION_NOT_PLSQL
|
options |= TNS_EXEC_OPTION_NOT_PLSQL
|
||||||
elif stmt._is_plsql and num_params > 0:
|
elif stmt._is_plsql and num_params > 0:
|
||||||
|
@ -2226,3 +2238,26 @@ cdef class RollbackMessage(Message):
|
||||||
Perform initialization.
|
Perform initialization.
|
||||||
"""
|
"""
|
||||||
self.function_code = TNS_FUNC_ROLLBACK
|
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:
|
cdef:
|
||||||
uint8_t _packet_type
|
uint8_t _packet_type
|
||||||
|
uint8_t _packet_flags
|
||||||
Capabilities _caps
|
Capabilities _caps
|
||||||
object _socket
|
object _socket
|
||||||
uint8_t _seq_num
|
uint8_t _seq_num
|
||||||
|
@ -610,7 +611,7 @@ cdef class WriteBuffer(Buffer):
|
||||||
self.write_uint16(size)
|
self.write_uint16(size)
|
||||||
self.write_uint16(0)
|
self.write_uint16(0)
|
||||||
self.write_uint8(self._packet_type)
|
self.write_uint8(self._packet_type)
|
||||||
self.write_uint8(0)
|
self.write_uint8(self._packet_flags)
|
||||||
self.write_uint16(0)
|
self.write_uint16(0)
|
||||||
self._pos = size
|
self._pos = size
|
||||||
if DEBUG_PACKETS:
|
if DEBUG_PACKETS:
|
||||||
|
@ -650,7 +651,8 @@ cdef class WriteBuffer(Buffer):
|
||||||
"""
|
"""
|
||||||
return self._max_size - PACKET_HEADER_SIZE - 2
|
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
|
Indicates that a request from the client is starting. The packet type
|
||||||
is retained just in case a request spans multiple packets. The packet
|
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_sent = False
|
||||||
self._packet_type = packet_type
|
self._packet_type = packet_type
|
||||||
|
self._packet_flags = packet_flags
|
||||||
self._pos = PACKET_HEADER_SIZE
|
self._pos = PACKET_HEADER_SIZE
|
||||||
if packet_type == TNS_PACKET_TYPE_DATA:
|
if packet_type == TNS_PACKET_TYPE_DATA:
|
||||||
self.write_uint16(data_flags)
|
self.write_uint16(data_flags)
|
||||||
|
|
|
@ -102,7 +102,7 @@ cdef class Protocol:
|
||||||
message = conn_impl._create_message(RollbackMessage)
|
message = conn_impl._create_message(RollbackMessage)
|
||||||
self._process_message(message)
|
self._process_message(message)
|
||||||
if conn_impl._drcp_enabled:
|
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
|
conn_impl._drcp_establish_session = True
|
||||||
|
|
||||||
# if the connection is part of a pool, return it to the pool
|
# if the connection is part of a pool, return it to the pool
|
||||||
|
@ -133,11 +133,11 @@ cdef class Protocol:
|
||||||
"""
|
"""
|
||||||
cdef:
|
cdef:
|
||||||
ConnectMessage connect_message = None
|
ConnectMessage connect_message = None
|
||||||
|
uint8_t packet_type, packet_flags = 0
|
||||||
object ssl_context, connect_info
|
object ssl_context, connect_info
|
||||||
ConnectParamsImpl temp_params
|
ConnectParamsImpl temp_params
|
||||||
str host, redirect_data
|
str host, redirect_data
|
||||||
Address temp_address
|
Address temp_address
|
||||||
uint8_t packet_type
|
|
||||||
int port, pos
|
int port, pos
|
||||||
|
|
||||||
# store whether OOB processing is possible or not
|
# 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_bytes = connect_string.encode()
|
||||||
connect_message.connect_string_len = \
|
connect_message.connect_string_len = \
|
||||||
<uint16_t> len(connect_message.connect_string_bytes)
|
<uint16_t> len(connect_message.connect_string_bytes)
|
||||||
|
connect_message.packet_flags = packet_flags
|
||||||
|
|
||||||
# process connection message
|
# process connection message
|
||||||
self._process_message(connect_message)
|
self._process_message(connect_message)
|
||||||
|
@ -180,6 +181,7 @@ cdef class Protocol:
|
||||||
connect_string = redirect_data[pos + 1:]
|
connect_string = redirect_data[pos + 1:]
|
||||||
self._connect_tcp(params, description, address, host, port)
|
self._connect_tcp(params, description, address, host, port)
|
||||||
connect_message = None
|
connect_message = None
|
||||||
|
packet_flags = TNS_PACKET_FLAG_REDIRECT
|
||||||
elif connect_message.packet_type == TNS_PACKET_TYPE_ACCEPT:
|
elif connect_message.packet_type == TNS_PACKET_TYPE_ACCEPT:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -226,7 +228,8 @@ cdef class Protocol:
|
||||||
auth_message = conn_impl._create_message(AuthMessage)
|
auth_message = conn_impl._create_message(AuthMessage)
|
||||||
auth_message._set_params(params, description)
|
auth_message._set_params(params, description)
|
||||||
self._process_message(auth_message)
|
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
|
# 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
|
||||||
|
@ -292,7 +295,7 @@ cdef class Protocol:
|
||||||
"""
|
"""
|
||||||
Send the final close packet to the server and close the socket.
|
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()
|
buf.end_request()
|
||||||
self._socket.shutdown(socket.SHUT_RDWR)
|
self._socket.shutdown(socket.SHUT_RDWR)
|
||||||
self._socket.close()
|
self._socket.close()
|
||||||
|
@ -394,20 +397,16 @@ cdef class Protocol:
|
||||||
buf.skip_raw_bytes(3)
|
buf.skip_raw_bytes(3)
|
||||||
message.error_info.message = buf.read_str(TNS_CS_IMPLICIT)
|
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:
|
uint32_t release_mode) except -1:
|
||||||
"""
|
"""
|
||||||
Release the session back to DRCP. Standalone sessions are marked for
|
Release the session back to DRCP. Standalone sessions are marked for
|
||||||
deauthentication.
|
deauthentication.
|
||||||
"""
|
"""
|
||||||
buf.start_request(TNS_PACKET_TYPE_DATA)
|
cdef SessionReleaseMessage message
|
||||||
buf.write_uint8(TNS_MSG_TYPE_ONEWAY_FN)
|
message = conn_impl._create_message(SessionReleaseMessage)
|
||||||
buf.write_uint8(TNS_FUNC_SESSION_RELEASE)
|
message.release_mode = release_mode
|
||||||
buf.write_uint8(0) # seq number
|
message.send(self._write_buf)
|
||||||
buf.write_uint8(0) # pointer (tag name)
|
|
||||||
buf.write_uint8(0) # tag name length
|
|
||||||
buf.write_ub4(release_mode) # mode
|
|
||||||
buf.end_request()
|
|
||||||
|
|
||||||
cdef int _reset(self, Message message) except -1:
|
cdef int _reset(self, Message message) except -1:
|
||||||
cdef uint8_t marker_type
|
cdef uint8_t marker_type
|
||||||
|
|
|
@ -30,4 +30,4 @@
|
||||||
# file doc/src/conf.py both reference this file directly.
|
# 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):
|
def test_1105_bad_connect_string(self):
|
||||||
"1105 - connection to database with bad connect string"
|
"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())
|
oracledb.connect, test_env.get_main_user())
|
||||||
self.assertRaisesRegex(oracledb.DatabaseError, "^DPY-4000:|^DPY-4001:",
|
self.assertRaisesRegex(oracledb.DatabaseError, "^DPY-4000:|^DPY-4001:",
|
||||||
oracledb.connect, test_env.get_main_user() + \
|
oracledb.connect, test_env.get_main_user() + \
|
||||||
|
|
|
@ -138,8 +138,8 @@ class TestCase(test_env.BaseTestCase):
|
||||||
rows = ref_cursor.fetchall()
|
rows = ref_cursor.fetchall()
|
||||||
self.assertEqual(rows, expected_value)
|
self.assertEqual(rows, expected_value)
|
||||||
|
|
||||||
def test_1306_refcursor_prefetchrows(self):
|
def test_1306_refcursor_round_trips(self):
|
||||||
"1306 - test prefetch rows and arraysize using a refcursor"
|
"1306 - test round trips using a REF cursor"
|
||||||
self.setup_round_trip_checker()
|
self.setup_round_trip_checker()
|
||||||
|
|
||||||
# simple DDL only requires a single round trip
|
# simple DDL only requires a single round trip
|
||||||
|
@ -155,14 +155,23 @@ class TestCase(test_env.BaseTestCase):
|
||||||
cursor.executemany(sql, data)
|
cursor.executemany(sql, data)
|
||||||
self.assertRoundTrips(1)
|
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:
|
with self.connection.cursor() as cursor:
|
||||||
refcursor = self.connection.cursor()
|
refcursor = self.connection.cursor()
|
||||||
refcursor.prefetchrows = 150
|
refcursor.arraysize = 150
|
||||||
refcursor.arraysize = 50
|
|
||||||
cursor.callproc("myrefcursorproc", [refcursor])
|
cursor.callproc("myrefcursorproc", [refcursor])
|
||||||
refcursor.fetchall()
|
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):
|
def test_1307_refcursor_execute_different_sql(self):
|
||||||
"1307 - test executing different SQL after getting a REF cursor"
|
"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
|
# This software is dual-licensed to you under the Universal Permissive License
|
||||||
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||||
|
@ -310,5 +310,32 @@ class TestCase(test_env.BaseTestCase):
|
||||||
fetched_data = self.cursor.fetchall()
|
fetched_data = self.cursor.fetchall()
|
||||||
self.assertEqual(data, fetched_data)
|
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__":
|
if __name__ == "__main__":
|
||||||
test_env.run_test_cases()
|
test_env.run_test_cases()
|
||||||
|
|
|
@ -346,13 +346,15 @@ class TestCase(test_env.BaseTestCase):
|
||||||
num_rows = 590
|
num_rows = 590
|
||||||
with self.connection.cursor() as cursor:
|
with self.connection.cursor() as cursor:
|
||||||
cursor.execute("truncate table TestTempTable")
|
cursor.execute("truncate table TestTempTable")
|
||||||
|
self.assertRoundTrips(1)
|
||||||
sql = "insert into TestTempTable (IntCol) values (:1)"
|
sql = "insert into TestTempTable (IntCol) values (:1)"
|
||||||
data = [(n + 1,) for n in range(num_rows)]
|
data = [(n + 1,) for n in range(num_rows)]
|
||||||
cursor.executemany(sql, data)
|
cursor.executemany(sql, data)
|
||||||
cursor.prefetchrows = 300
|
self.assertRoundTrips(1)
|
||||||
cursor.arraysize = 300
|
cursor.prefetchrows = 30
|
||||||
|
cursor.arraysize = 100
|
||||||
cursor.execute("select IntCol from TestTempTable").fetchall()
|
cursor.execute("select IntCol from TestTempTable").fetchall()
|
||||||
self.assertRoundTrips(4)
|
self.assertRoundTrips(7)
|
||||||
|
|
||||||
def test_4324_bind_names_with_single_line_comments(self):
|
def test_4324_bind_names_with_single_line_comments(self):
|
||||||
"4324 - test bindnames() with single line comments"
|
"4324 - test bindnames() with single line comments"
|
||||||
|
|
|
@ -619,5 +619,14 @@ class TestCase(test_env.BaseTestCase):
|
||||||
self.assertEqual(password, None)
|
self.assertEqual(password, None)
|
||||||
self.assertEqual(dsn, 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__":
|
if __name__ == "__main__":
|
||||||
test_env.run_test_cases()
|
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
|
# This software is dual-licensed to you under the Universal Permissive License
|
||||||
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||||
|
|
Loading…
Reference in New Issue