Compare commits

...

13 Commits
main ... v1.2.x

Author SHA1 Message Date
Anthony Tuininga ec62806c18 Bump copyright. 2023-01-19 09:58:59 -07:00
Anthony Tuininga 33f4093ad1 Fixed bug when attempting to populate an array variable with too many
elements.
2023-01-18 16:12:13 -07:00
Anthony Tuininga cd70b4474b Some databases return a negative length instead of a 0 length when
returning null boolean values (#119).
2023-01-18 13:55:35 -07:00
Anthony Tuininga 104ec0331b Preparing to release version 1.2.2. 2023-01-18 10:06:04 -07:00
Anthony Tuininga 23af6a748e Fixed bug when using a "select * from table" query and columns are added
to the table (#125).
2023-01-18 10:05:23 -07:00
Anthony Tuininga 7076712234 Update documentation. 2023-01-18 10:05:00 -07:00
Anthony Tuininga ecd8956d42 Fixed bug when getting a record type based on a table (%ROWTYPE) (#123). 2023-01-18 10:04:36 -07:00
Anthony Tuininga d73e43acad Fixed bug when attempting to create bequeath connections to a local
database (#114).
2023-01-18 10:03:45 -07:00
Anthony Tuininga cc52daf97b Any exception raised attempting to find the logged on user for logging
purposes is now ignored (#112).
2023-01-18 10:03:20 -07:00
Anthony Tuininga cebde524d0 Fixed bug when binding OUT a NULL boolean value (#119). 2023-01-18 10:02:55 -07:00
Anthony Tuininga b07a524373 Show how to correctly bind data to insert into NVARCHAR2 columns. 2023-01-18 10:02:37 -07:00
Anthony Tuininga 1d1e17144d Mention DPI-1047 for SEO. Mention unsafe paths. 2023-01-18 10:02:05 -07:00
Anthony Tuininga ace0d52ce7 Bump version. 2023-01-18 10:01:51 -07:00
22 changed files with 361 additions and 146 deletions

View File

@ -2387,7 +2387,7 @@ version of python-oracledb.
.. data:: NCHAR
A synonym for :data:`DB_TYPE_NVARCHAR`.
A synonym for :data:`DB_TYPE_NCHAR`.
.. deprecated:: cx_Oracle 8.0

View File

@ -34,7 +34,7 @@ root_doc = master_doc = 'index'
# General substitutions.
project = 'python-oracledb'
copyright = u'2016, 2022, Oracle and/or its affiliates. All rights reserved. Portions Copyright © 2007-2015, Anthony Tuininga. All rights reserved. Portions Copyright © 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, Canada. All rights reserved'
copyright = u'2016, 2023, Oracle and/or its affiliates. All rights reserved. Portions Copyright © 2007-2015, Anthony Tuininga. All rights reserved. Portions Copyright © 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, Canada. All rights reserved'
author = 'Oracle'
# The default replacements for |version| and |release|, also used in various

View File

@ -10,7 +10,7 @@ License
.. centered:: **LICENSE AGREEMENT FOR python-oracledb**
Copyright |copy| 2016, 2022 Oracle and/or its affiliates.
Copyright |copy| 2016, 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

View File

@ -7,6 +7,37 @@ python-oracledb Release Notes
For deprecations, see :ref:`Deprecations <deprecations>`.
oracledb 1.2.2 (January 2023)
-----------------------------
Thin Mode Changes
+++++++++++++++++
#) Any exception raised while finding the operating system user for database
logging is now ignored (`issue 112
<https://github.com/oracle/python-oracledb/issues/112>`__).
#) Fixed bug when binding OUT a NULL boolean value.
(`issue 119 <https://github.com/oracle/python-oracledb/issues/119>`__).
#) Fixed bug when getting a record type based on a table (%ROWTYPE)
(`issue 123 <https://github.com/oracle/python-oracledb/issues/123>`__).
#) Fixed bug when using a `select * from table` query and columns are added to
the table
(`issue 125 <https://github.com/oracle/python-oracledb/issues/125>`__).
Thick Mode Changes
++++++++++++++++++
#) Fixed bug when attempting to create bequeath connections to a local
database
(`issue 114 <https://github.com/oracle/python-oracledb/issues/114>`__).
Common Changes
++++++++++++++
#) Fixed bug when attempting to populate an array variable with too many
elements.
oracledb 1.2.1 (December 2022)
------------------------------

View File

@ -215,3 +215,22 @@ To convert dates:
for row in cursor:
print(row) # gives 'Mi 15 Dez 19:57:56 2021'
print()
Inserting NVARCHAR2 and NCHAR Data
----------------------------------
To bind NVARCHAR2 data, use :func:`Cursor.setinputsizes()` or create a bind
variable with the correct type by calling :func:`Cursor.var()`. This removes
an internal character set conversion to the standard `Database Character Set`_
that may corrupt data. By binding as :data:`oracledb.DB_TYPE_NVARCHAR`, the
data is inserted directly as the `Database National Character Set`_. For
example, to insert into a table containing two NVARCHAR2 columns:
.. code-block:: python
sql = "insert into mytable values (:1, :2)"
bv = ['data1', 'data2']
cursor.setinputsizes(oracledb.DB_TYPE_NVARCHAR, oracledb.DB_TYPE_NVARCHAR)
cursor.execute(sql, bv)
For NCHAR data, bind as :data:`oracledb.DB_TYPE_NCHAR`.

View File

@ -32,7 +32,8 @@ upgrading a cx_Oracle application to python-oracledb, then refer to
All connections in an application use the same mode.
Once the Thick mode is enabled, you cannot go back to Thin mode.
Once the Thick mode is enabled, you cannot go back to Thin mode except by
restarting the application.
Enabling the python-oracledb Thick mode loads Oracle Client libraries which
handle communication to your database. The Oracle Client libraries need to be
@ -43,9 +44,9 @@ installed separately. See :ref:`installation`.
Architecture of the python-oracledb driver in Thick mode
You can validate the python-oracledb mode by querying the ``CLIENT_DRIVER``
column of ``V$SESSION_CONNECT_INFO`` and verifying the value of the column
begins with ``python-oracledb thk``. See :ref:`vsessconinfo`.
You can validate python-oracledb is running in Thick mode by querying the
``CLIENT_DRIVER`` column of ``V$SESSION_CONNECT_INFO`` and verifying the value
of the column begins with ``python-oracledb thk``. See :ref:`vsessconinfo`.
.. _libinit:
@ -54,26 +55,35 @@ Setting the Oracle Client Library Directory
-------------------------------------------
When :meth:`~oracledb.init_oracle_client()` is called, python-oracledb
dynamically loads Oracle Client libraries using a search heuristic. Only the
first set of libraries found are loaded. The libraries can be:
dynamically loads Oracle Client libraries using a search heuristic. The
libraries can be:
- in an installation of Oracle Instant Client
- or in a full Oracle Client installation
- or in an Oracle Database installation (if Python is running on the same
machine as the database).
The versions of Oracle Client and Oracle Database do not have
to be the same. For certified configurations see Oracle Support's `Doc ID
207303.1
<https://support.oracle.com/epmos/faces/DocumentDisplay?id=207303.1>`__.
See :ref:`installation` for information about installing Oracle Client
libraries.
The versions of Oracle Client libraries and Oracle Database do not have to be
the same. For certified configurations see Oracle Support's `Doc ID 207303.1
<https://support.oracle.com/epmos/faces/DocumentDisplay?id=207303.1>`__.
.. note::
If Oracle Client libraries cannot be loaded then
:meth:`~oracledb.init_oracle_client()` will raise an error ``DPI-1047:
Oracle Client library cannot be loaded``. To resolve this, review the
platform-specific instructions below or see :ref:`runtimetroubleshooting`.
Alternatively remove the call to :meth:`~oracledb.init_oracle_client()` and
use Thin mode. The features supported by Thin mode can be found in
:ref:`driverdiff`.
.. _wininit:
Setting the Oracle Client Directory on Windows
++++++++++++++++++++++++++++++++++++++++++++++
Setting the Oracle Client Library Directory on Windows
++++++++++++++++++++++++++++++++++++++++++++++++++++++
On Windows, python-oracledb Thick mode can be enabled as follows:
@ -121,8 +131,8 @@ On Windows, python-oracledb Thick mode can be enabled as follows:
.. _macinit:
Setting the Oracle Client Directory on macOS
++++++++++++++++++++++++++++++++++++++++++++
Setting the Oracle Client Library Directory on macOS
++++++++++++++++++++++++++++++++++++++++++++++++++++
On macOS, python-oracledb Thick mode can be enabled as follows:
@ -154,14 +164,33 @@ On macOS, python-oracledb Thick mode can be enabled as follows:
'Basic' or 'Basic Light' package, or a symbolic link to the main Oracle
Client library if Instant Client is in a different directory.
You can find the directory containing the Thick mode binary module by
calling the python CLI without specifying a Python script, executing
``import oracledb``, and then typing ``oracledb`` at the prompt. For
example if
``/Users/yourname/Library/3.9.6/lib/python3.9/site-packages/oracledb-1.0.0-py3.9-macosx-11.5-x86_64.egg/oracledb``
contains ``thick_impl.cpython-39-darwin.so``, then you could run ``ln -s
~/Downloads/instantclient_19_8/libclntsh.dylib
~/Library/3.9.6/lib/python3.9/site-packages/oracledb-1.0.0-py3.9-macosx-11.5-x86_64.egg/oracledb/``.
You can find the directory containing the Thick mode binary module by calling
the python CLI without specifying a Python script, executing ``import
oracledb``, and then typing ``oracledb`` at the prompt. For example this
might show
``/Users/yourname/.pyenv/versions/3.9.6/lib/python3.9/site-packages/oracledb/__init__.py``.
After checking that
``/Users/yourname/.pyenv/versions/3.9.6/lib/python3.9/site-packages/oracledb``
contains the binary module ``thick_impl.cpython-39-darwin.so`` you could then
run these commands in a terminal window::
CLIENT_DIR=~/Downloads/instantclient_19_8
DPY_DIR=~/.pyenv/versions/3.9.6/lib/python3.9/site-packages/oracledb
ln -s $CLIENT_DIR/libclntsh.dylib $DPY_DIR
This can be automated in Python with:
.. code-block:: python
CLIENT_DIR = "~/Downloads/instantclient_19_8"
LIB_NAME = "libclntsh.dylib"
import os
import oracledb
target_dir = oracledb.__path__[0]
os.symlink(os.path.join(CLIENT_DIR, LIB_NAME),
os.path.join(target_dir, LIB_NAME))
If python-oracledb does not find the Oracle Client library in that
directory, the directories on the system library search path may be used,
@ -174,8 +203,8 @@ On macOS, python-oracledb Thick mode can be enabled as follows:
.. _linuxinit:
Setting the Oracle Client Directory on Linux and Related Platforms
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Setting the Oracle Client Library Directory on Linux and Related Platforms
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
On Linux and related platforms, python-oracledb Thick mode can be enabled as
follows:
@ -207,16 +236,18 @@ follows:
raised.
Ensure that the Python process has directory and file access permissions for
the Oracle Client libraries. On Linux ensure a ``libclntsh.so`` file exists.
On macOS ensure a ``libclntsh.dylib`` file exists. python-oracledb Thick will
not directly load ``libclntsh.*.XX.1`` files in ``lib_dir`` or from the directory
where the python-oracledb binary module is available. Note that other libraries
used by ``libclntsh*`` are also required.
the Oracle Client libraries. On some platforms OS restrictions may prevent the
opening of Oracle Client libraries installed in unsafe paths, such as from a
user directory. On Linux ensure a ``libclntsh.so`` file exists. On macOS
ensure a ``libclntsh.dylib`` file exists. Python-oracledb Thick mode will not
directly load ``libclntsh.*.XX.1`` files in ``lib_dir`` or from the directory
where the python-oracledb binary module is available. Note that other
libraries used by ``libclntsh*`` are also required.
.. _usinginitoracleclient:
Calling oracledb.init_oracle_client() to Set the Oracle Client Directory
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Example Calling oracledb.init_oracle_client()
+++++++++++++++++++++++++++++++++++++++++++++
Oracle Client Libraries are loaded when :meth:`oracledb.init_oracle_client()`
is called. In some environments, applications can use the ``lib_dir``
@ -225,7 +256,7 @@ Otherwise, the system library search path should contain the relevant library
directory before Python is invoked.
For example, if the Oracle Instant Client Libraries are in
``C:\oracle\instantclient_19_9`` on Windows or
``C:\oracle\instantclient_19_17`` on Windows or
``$HOME/Downloads/instantclient_19_8`` on macOS (Intel x86), then you can use:
.. code-block:: python
@ -238,10 +269,10 @@ For example, if the Oracle Instant Client Libraries are in
if platform.system() == "Darwin" and platform.machine() == "x86_64":
d = os.environ.get("HOME")+"/Downloads/instantclient_19_8")
elif platform.system() == "Windows":
d = r"C:\oracle\instantclient_19_14"
d = r"C:\oracle\instantclient_19_17"
oracledb.init_oracle_client(lib_dir=d)
Note the use of a 'raw' string ``r"..."`` on Windows so that backslashes are
The use of a 'raw' string ``r"..."`` on Windows means that backslashes are
treated as directory separators.
**Note that if you set** ``lib_dir`` **on Linux and related platforms, you must
@ -258,12 +289,16 @@ shown in :ref:`envset`.
**Tracing Oracle Client Libraries Loading**
To trace the loading of Oracle Client libraries, the environment variable
``DPI_DEBUG_LEVEL`` can be set to 64 before starting Python. For example, on
Linux, you might use::
``DPI_DEBUG_LEVEL`` can be set to 64 before starting Python. At a Windows
command prompt, this could be done with::
$ export DPI_DEBUG_LEVEL=64
$ python myapp.py 2> log.txt
set DPI_DEBUG_LEVEL=64
On Linux and macOS, you might use::
export DPI_DEBUG_LEVEL=64
When your python-oracledb application is run, logging output is shown.
.. _optnetfiles:

View File

@ -271,6 +271,10 @@ To use python-oracledb Thick mode with Oracle Instant Client zip files:
cd /opt/oracle
unzip instantclient-basic-linux.x64-21.6.0.0.0.zip
Note OS restrictions may prevent the opening of Oracle Client libraries
installed in unsafe paths, such as from a user directory. You may need to
install under a directory like ``/opt`` or ``/usr/local``.
3. Install the ``libaio`` package with sudo or as the root user. For example::
sudo yum install libaio
@ -827,9 +831,14 @@ used to install into a local directory::
python setup.py install --user
.. _troubleshooting:
Troubleshooting
===============
Installation Troubleshooting
----------------------------
If installation fails:
- An error such as ``not a supported wheel on this platform.`` indicates that
@ -866,6 +875,11 @@ If installation fails:
a subdirectory called "odpi" containing files. If this is missing, review the
section on `Install Using GitHub`_.
.. _runtimetroubleshooting:
Runtime Error Troubleshooting
-----------------------------
If using python-oracledb fails:
- If you have multiple versions of Python installed, ensure that you are
@ -884,14 +898,19 @@ If using python-oracledb fails:
should be the location of your Oracle Client libraries. Do not pass
this parameter on Linux.
- Check that the Python process has permission to open the Oracle Client
libraries. OS restrictions may prevent the opening of libraries installed
in unsafe paths, such as from a user directory. On Linux you may need to
install the Oracle Client libraries under a directory like ``/opt`` or
``/usr/local``.
- Check if Python and your Oracle Client libraries are both 64-bit or
both 32-bit. The ``DPI-1047`` message will tell you whether the 64-bit
or 32-bit Oracle Client is needed for your Python.
- For Thick mode connections, set the environment variable
``DPI_DEBUG_LEVEL`` to 64 and restart python-oracledb. The trace
messages will show how and where python-oracledb is looking for the
Oracle Client libraries.
- Set the environment variable ``DPI_DEBUG_LEVEL`` to 64 and restart
python-oracledb. The trace messages will show how and where
python-oracledb is looking for the Oracle Client libraries.
At a Windows command prompt, this could be done with::
@ -919,9 +938,10 @@ If using python-oracledb fails:
been installed.
- On Linux, check if the ``LD_LIBRARY_PATH`` environment variable contains
the Oracle Client library directory. Or, if you are using Oracle
Instant Client, a preferred alternative is to ensure that a file in the
``/etc/ld.so.conf.d`` directory contains the path to the Instant Client
the Oracle Client library directory. Some environments such as web servers
reset environment variables. If you are using Oracle Instant Client, a
preferred alternative to ``LD_LIBRARY_PATH`` is to ensure that a file in
the ``/etc/ld.so.conf.d`` directory contains the path to the Instant Client
directory, and then run ``ldconfig``.
- If you get the error ``DPY-3010: connections to this database server
@ -931,8 +951,8 @@ If using python-oracledb fails:
:meth:`oracledb.init_oracle_client()` in your code. Alternatively,
upgrade your database.
- If you get the error "``DPI-1072: the Oracle Client library version is
unsupported``", then review the installation requirements. The Thick
- If you get the error ``DPI-1072: the Oracle Client library version is
unsupported``, then review the installation requirements. The Thick
mode of python-oracledb needs Oracle Client libraries 11.2 or later.
Note that version 19 is not supported on Windows 7. Similar steps shown
above for ``DPI-1047`` may help. You may be able to use Thin mode which

View File

@ -248,10 +248,10 @@ Python.
Tuning Fetching from REF CURSORS
--------------------------------
In python-oracledb, REF CURSORS can also be tuned by setting the values of
``arraysize`` and ``prefetchrows``. The ``prefetchrows`` value must be set
before calling the PL/SQL procedure as the REF CURSOR is executed on the
server.
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.
For example:
@ -272,6 +272,8 @@ For example:
.. _roundtrips:
Also see `Avoiding Premature Prefetching`_.
Database Round-trips
====================

View File

@ -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
@ -115,6 +115,7 @@ cdef class AddressList:
bint load_balance
int lru_index
cdef bint _uses_tcps(self)
cdef str build_connect_string(self)
@ -191,7 +192,6 @@ cdef class ConnectParamsImpl:
bytearray _token_obfuscator
bytearray _private_key
bytearray _private_key_obfuscator
bint _has_components
cdef int _check_credentials(self) except -1
cdef int _copy(self, ConnectParamsImpl other_params) except -1

View File

@ -158,5 +158,6 @@ TPC_END_SUSPEND = 0x00100000
# basic configuration constants
DRIVER_NAME = "python-oracledb"
INSTALLATION_URL = "https://python-oracledb.readthedocs.io/en/" \
"latest/user_guide/installation.html"
"/latest/user_guide/initialization.html" \
"#setting-the-oracle-client-library-directory"
ENCODING = "UTF-8"

View File

@ -1,5 +1,5 @@
#------------------------------------------------------------------------------
# Copyright (c) 2022, Oracle and/or its affiliates.
# Copyright (c) 2022, 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
@ -169,8 +169,6 @@ cdef class ConnectParamsImpl:
self._default_address.set_from_args(args)
_set_bool_param(args, "externalauth", &self.externalauth)
self._set_access_token_param(args.get("access_token"))
if args:
self._has_components = True
cdef int _check_credentials(self) except -1:
"""
@ -344,7 +342,6 @@ cdef class ConnectParamsImpl:
# to be a full connect descriptor
if connect_string.startswith("("):
_parse_connect_descriptor(connect_string, args)
self._has_components = True
return self._process_connect_descriptor(args)
# otherwise, see if the connect string is an EasyConnect string
@ -389,9 +386,6 @@ cdef class ConnectParamsImpl:
_parse_connect_descriptor(connect_string, args)
self._process_connect_descriptor(args)
# mark that object has components
self._has_components = True
cdef int _process_connect_descriptor(self, dict args) except -1:
"""
Internal method used for processing the parsed connect descriptor into
@ -538,8 +532,7 @@ cdef class ConnectParamsImpl:
will be a connect string built up from the components supplied when the
object was built.
"""
if self._has_components:
return self.description_list.build_connect_string()
return self.description_list.build_connect_string()
def get_full_user(self):
"""
@ -613,16 +606,18 @@ cdef class Address:
cdef str build_connect_string(self):
"""
Build a connect string from the components.
Build a connect string from the components. If no host is specified,
None is returned (used for bequeath connections).
"""
parts = [f"(PROTOCOL={self.protocol})",
f"(HOST={self.host})",
f"(PORT={self.port})"]
if self.https_proxy is not None:
parts.append(f"(HTTPS_PROXY={self.https_proxy})")
if self.https_proxy_port != 0:
parts.append(f"(HTTPS_PROXY_PORT={self.https_proxy_port})")
return f'(ADDRESS={"".join(parts)})'
if self.host is not None:
parts = [f"(PROTOCOL={self.protocol})",
f"(HOST={self.host})",
f"(PORT={self.port})"]
if self.https_proxy is not None:
parts.append(f"(HTTPS_PROXY={self.https_proxy})")
if self.https_proxy_port != 0:
parts.append(f"(HTTPS_PROXY_PORT={self.https_proxy_port})")
return f'(ADDRESS={"".join(parts)})'
def copy(self):
"""
@ -667,12 +662,25 @@ cdef class AddressList:
def __init__(self):
self.addresses = []
cdef bint _uses_tcps(self):
"""
Returns a boolean indicating if any of the addresses in the address
list use the protocol TCPS.
"""
cdef Address address
for address in self.addresses:
if address.protocol == "tcps":
return True
return False
cdef str build_connect_string(self):
"""
Build a connect string from the components.
"""
cdef Address a
parts = [a.build_connect_string() for a in self.addresses]
if len(parts) == 1:
return parts[0]
return f'(ADDRESS_LIST={"".join(parts)})'
def set_from_args(self, dict args):
@ -713,37 +721,12 @@ cdef class Description:
Build a connect string from the components.
"""
cdef:
str connect_data, security, temp
list parts, address_lists
AddressList a
AddressList address_list
list parts, temp_parts
bint uses_tcps = False
str temp
# build connect data segment
parts = []
if self.service_name is not None:
parts.append(f"(SERVICE_NAME={self.service_name})")
elif self.sid is not None:
parts.append(f"(SID={self.sid})")
if self.server_type is not None:
parts.append(f"(SERVER={self.server_type})")
if self.cclass is not None:
parts.append(f"(POOL_CONNECTION_CLASS={self.cclass})")
if self.purity != 0:
parts.append(f"(POOL_PURITY={self.purity})")
if cid is not None:
parts.append(f"(CID={cid})")
connect_data = f'(CONNECT_DATA={"".join(parts)})'
# build security segment, if applicable
parts = []
if self.ssl_server_dn_match:
parts.append("(SSL_SERVER_DN_MATCH=ON)")
if self.ssl_server_cert_dn is not None:
parts.append(f"(SSL_SERVER_CERT_DN={self.ssl_server_cert_dn})")
if self.wallet_location is not None:
parts.append(f"(MY_WALLET_DIRECTORY={self.wallet_location})")
security = f'(SECURITY={"".join(parts)})'
# build connect string
# build top-level description parts
parts = []
if self.load_balance:
parts.append("(LOAD_BALANCE=ON)")
@ -758,10 +741,48 @@ cdef class Description:
if self.tcp_connect_timeout != DEFAULT_TCP_CONNECT_TIMEOUT:
temp = self._build_duration_str(self.tcp_connect_timeout)
parts.append(f"(TRANSPORT_CONNECT_TIMEOUT={temp})")
address_lists = [a.build_connect_string() for a in self.address_lists]
parts.extend(address_lists)
parts.append(connect_data)
parts.append(security)
# add address lists, but if the address list contains only a single
# entry and that entry does not have a host, the other parts aren't
# relevant anyway!
for address_list in self.address_lists:
temp = address_list.build_connect_string()
if temp is None:
return None
parts.append(temp)
if not uses_tcps:
uses_tcps = address_list._uses_tcps()
# build connect data segment
temp_parts = []
if self.service_name is not None:
temp_parts.append(f"(SERVICE_NAME={self.service_name})")
elif self.sid is not None:
temp_parts.append(f"(SID={self.sid})")
if self.server_type is not None:
temp_parts.append(f"(SERVER={self.server_type})")
if self.cclass is not None:
temp_parts.append(f"(POOL_CONNECTION_CLASS={self.cclass})")
if self.purity != 0:
temp_parts.append(f"(POOL_PURITY={self.purity})")
if cid is not None:
temp_parts.append(f"(CID={cid})")
if temp_parts:
parts.append(f'(CONNECT_DATA={"".join(temp_parts)})')
# build security segment, if applicable
if uses_tcps:
temp_parts = []
if self.ssl_server_dn_match:
temp_parts.append("(SSL_SERVER_DN_MATCH=ON)")
if self.ssl_server_cert_dn is not None:
temp = f"(SSL_SERVER_CERT_DN={self.ssl_server_cert_dn})"
temp_parts.append(temp)
if self.wallet_location is not None:
temp = f"(MY_WALLET_DIRECTORY={self.wallet_location})"
temp_parts.append(temp)
parts.append(f'(SECURITY={"".join(temp_parts)})')
return f'(DESCRIPTION={"".join(parts)})'
def copy(self):

View File

@ -99,7 +99,7 @@ cdef class BaseVarImpl:
if was_set != NULL:
was_set[0] = False
return 0
errors._raise_err(errors.ERR_INCORRECT_VAR_ARRAY_SIZE,
errors._raise_err(errors.ERR_INCORRECT_VAR_ARRAYSIZE,
var_arraysize=self.num_elements,
required_arraysize=num_elements_in_array)

View File

@ -368,13 +368,15 @@ cdef class Buffer:
cdef object read_bool(self):
"""
Read a boolean from the buffer and return True, False or None.
Read a boolean from the buffer and return True, False or None. A zero
length or a negative length (indicated by the value 0x81 in the first
byte) implies a null value.
"""
cdef:
const char_type *ptr
ssize_t num_bytes
self.read_raw_bytes_and_length(&ptr, &num_bytes)
if ptr != NULL:
if ptr != NULL and ptr[0] != 0x81:
return ptr[num_bytes - 1] == 1
cdef object read_bytes(self):

View File

@ -1,5 +1,5 @@
#------------------------------------------------------------------------------
# Copyright (c) 2022, Oracle and/or its affiliates.
# Copyright (c) 2022, 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
@ -719,23 +719,35 @@ cdef class ThinDbObjectTypeCache:
t_SuperTypeOwner varchar2(128);
t_SuperTypeName varchar2(128);
t_SubTypeRefCursor sys_refcursor;
t_Pos pls_integer;
begin
:ret_val := dbms_pickler.get_type_shape(:full_name, :oid,
:version, :tds, t_Instantiable, t_SuperTypeOwner,
t_SuperTypeName, :attrs_rc, t_SubTypeRefCursor);
:package_name := null;
begin
select owner, type_name
into :schema, :name
from all_types
where type_oid = :oid;
exception
when no_data_found then
select owner, package_name, type_name
into :schema, :package_name, :name
from all_plsql_types
where type_oid = :oid;
end;
if substr(:full_name, length(:full_name) - 7) = '%ROWTYPE' then
t_Pos := instr(:full_name, '.');
:schema := substr(:full_name, 1, t_Pos - 1);
:name := substr(:full_name, t_Pos + 1);
else
begin
select owner, type_name
into :schema, :name
from all_types
where type_oid = :oid;
exception
when no_data_found then
begin
select owner, package_name, type_name
into :schema, :package_name, :name
from all_plsql_types
where type_oid = :oid;
exception
when no_data_found then
null;
end;
end;
end if;
end;""")
self.meta_cursor = cursor
@ -939,17 +951,23 @@ cdef class ThinDbObjectTypeCache:
"""
cdef:
ThinDbObjectTypeImpl typ_impl
str full_name
str full_name, name, suffix
while self.partial_types:
typ_impl = self.partial_types.pop()
if self.meta_cursor is None:
self._init_meta_cursor(conn)
suffix = "%ROWTYPE"
if typ_impl.name.endswith(suffix):
name = typ_impl.name[:-len(suffix)]
else:
name = typ_impl.name
suffix = ""
if typ_impl.package_name is not None:
full_name = f'"{typ_impl.schema}".' + \
f'"{typ_impl.package_name}".' + \
f'"{typ_impl.name}"'
f'"{name}"{suffix}'
else:
full_name = f'"{typ_impl.schema}"."{typ_impl.name}"'
full_name = f'"{typ_impl.schema}"."{name}"{suffix}'
self._populate_type_info(full_name, typ_impl)

View File

@ -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
@ -615,7 +615,9 @@ cdef class MessageWithData(Message):
name=var_impl.dbtype.name)
if not self.in_fetch:
buf.read_sb4(&actual_num_bytes)
if actual_num_bytes != 0 and column_value is not None:
if actual_num_bytes < 0 and column_value is False:
column_value = None
elif actual_num_bytes != 0 and column_value is not None:
unit_type = "bytes" if isinstance(column_value, bytes) \
else "characters"
errors._raise_err(errors.ERR_COLUMN_TRUNCATED,
@ -714,7 +716,8 @@ cdef class MessageWithData(Message):
conn = self.cursor.connection
for i in range(cursor_impl._num_columns):
fetch_info = self._process_column_info(buf, cursor_impl)
if prev_fetch_var_impls is not None:
if prev_fetch_var_impls is not None \
and i < len(prev_fetch_var_impls):
self._adjust_fetch_info(prev_fetch_var_impls[i], fetch_info)
cursor_impl._create_fetch_var(conn, self.cursor, type_handler, i,
fetch_info)

View File

@ -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
@ -36,7 +36,10 @@ class ConnectConstants:
self.program_name = sys.executable
self.machine_name = socket.gethostname()
self.pid = str(os.getpid())
self.user_name = getpass.getuser()
try:
self.user_name = getpass.getuser()
except:
self.user_name = ""
self.terminal_name = "unknown"
self.sanitized_program_name = self._sanitize(self.program_name)
self.sanitized_machine_name = self._sanitize(self.machine_name)

View File

@ -30,4 +30,4 @@
# file doc/src/conf.py both reference this file directly.
#------------------------------------------------------------------------------
__version__ = "1.2.1"
__version__ = "1.2.2"

View File

@ -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
@ -968,6 +968,9 @@ create or replace package &main_user..pkg_TestBooleans as
a_Value udt_BooleanList
) return number;
function TestOutValueNull
return boolean;
procedure TestOutArrays (
a_NumElements number,
a_Value out nocopy udt_BooleanList
@ -1011,6 +1014,12 @@ create or replace package body &main_user..pkg_TestBooleans as
return t_Result;
end;
function TestOutValueNull
return boolean is
begin
return null;
end;
procedure TestOutArrays (
a_NumElements number,
a_Value out nocopy udt_BooleanList
@ -1026,6 +1035,10 @@ end;
create or replace package &main_user..pkg_TestBindObject as
subtype udt_RowType is TestTempTable%rowtype;
type udt_CollectionRowType is table of udt_RowType index by binary_integer;
function GetStringRep (
a_Object udt_Object
) return varchar2;

View File

@ -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
@ -89,5 +89,11 @@ class TestCase(test_env.BaseTestCase):
(True,))
self.assertEqual(result, "TRUE")
def test_3108_bind_out_null(self):
"3108 - test binding out a boolean value (None)"
result = self.cursor.callfunc("pkg_TestBooleans.TestOutValueNull",
bool)
self.assertEqual(result, None)
if __name__ == "__main__":
test_env.run_test_cases()

View File

@ -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
@ -539,5 +539,16 @@ class TestCase(test_env.BaseTestCase):
for e in self.cursor.getbatcherrors()]
self.assertEqual(actual_errors, expected_errors)
def test_3228_record_based_on_table_type(self):
"3228 - test %ROWTYPE record type"
type_obj = self.connection.gettype("TESTTEMPTABLE%ROWTYPE")
self.assertEqual(type_obj.attributes[3].name, "NUMBERCOL")
def test_3229_collection_of_records_based_on_table_type(self):
"3229 - test collection of %ROWTYPE record type"
type_name = "PKG_TESTBINDOBJECT.UDT_COLLECTIONROWTYPE"
type_obj = self.connection.gettype(type_name)
self.assertEqual(type_obj.element_type.attributes[3].name, "NUMBERCOL")
if __name__ == "__main__":
test_env.run_test_cases()

View File

@ -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
@ -826,5 +826,30 @@ class TestCase(test_env.BaseTestCase):
from dual""")
self.assertEqual(self.cursor.bindnames(), ["A", "B"])
def test_4365_add_column_to_cached_query(self):
"4365 - test addition of column to cached query"
table_name = "test_4365"
try:
self.cursor.execute(f"drop table {table_name}")
except:
pass
data = ('val 1', 'val 2')
self.cursor.execute(f"create table {table_name} (col1 varchar2(10))")
self.cursor.execute(f"insert into {table_name} values (:1)", [data[0]])
self.connection.commit()
self.cursor.execute(f"select * from {table_name}")
self.assertEqual(self.cursor.fetchall(), [(data[0],)])
self.cursor.execute(f"alter table {table_name} add col2 varchar2(10)")
self.cursor.execute(f"update {table_name} set col2 = :1", [data[1]])
self.connection.commit()
self.cursor.execute(f"select * from {table_name}")
self.assertEqual(self.cursor.fetchall(), [data])
def test_4366_populate_array_var_with_too_many_elements(self):
"4366 - test population of array var with too many elements"
var = self.cursor.arrayvar(int, 3)
self.assertRaisesRegex(oracledb.ProgrammingError, "^DPY-2016:",
var.setvalue, 0, [1, 2, 3, 4])
if __name__ == "__main__":
test_env.run_test_cases()

View File

@ -1,5 +1,5 @@
#------------------------------------------------------------------------------
# Copyright (c) 2021, 2022, Oracle and/or its affiliates.
# Copyright (c) 2021, 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
@ -478,10 +478,9 @@ class TestCase(test_env.BaseTestCase):
tcp_connect_timeout=in_val)
tcp_timeout_val = f"(TRANSPORT_CONNECT_TIMEOUT={out_val})"
connect_string = f"(DESCRIPTION={tcp_timeout_val}" + \
f"(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)" + \
f"(HOST={host})(PORT=1521)))(CONNECT_DATA=" + \
f"(SERVICE_NAME={service_name}))" + \
f"(SECURITY=(SSL_SERVER_DN_MATCH=ON)))"
f"(ADDRESS=(PROTOCOL=tcp)" + \
f"(HOST={host})(PORT=1521))(CONNECT_DATA=" + \
f"(SERVICE_NAME={service_name})))"
self.assertEqual(params.get_connect_string(), connect_string)
def test_4532_multiple_alias_entry_tnsnames(self):
@ -582,9 +581,15 @@ class TestCase(test_env.BaseTestCase):
f"(ADDRESS_LIST=" + \
f"(ADDRESS=(PROTOCOL=tcp)(HOST=host1)(PORT=1521))" + \
f"(ADDRESS=(PROTOCOL=tcp)(HOST=host2)(PORT=1522)))" + \
f"(CONNECT_DATA=(SERVICE_NAME=my_service_35))" + \
f"(SECURITY=(SSL_SERVER_DN_MATCH=ON)))"
f"(CONNECT_DATA=(SERVICE_NAME=my_service_35)))"
self.assertEqual(params.get_connect_string(), connect_string)
def test_4537_no_connect_string(self):
"4537 - test connect parameters which generate no connect string"
params = oracledb.ConnectParams()
self.assertEqual(params.get_connect_string(), None)
params.set(mode=oracledb.SYSDBA)
self.assertEqual(params.get_connect_string(), None)
if __name__ == "__main__":
test_env.run_test_cases()