Compare commits

...

20 Commits
main ... v1.0.x

Author SHA1 Message Date
Anthony Tuininga 3140a83cd0 Fixed bug that prevented cursors from implicit results sets from being
closed.
2022-07-29 15:26:47 -06:00
Anthony Tuininga d297f8b532 Fixed bug with re-execution of SQL that requires a define, such as
occurs when setting oracledb.defaults.fetch_lobs to the value False
(#41).
2022-07-29 15:24:53 -06:00
Anthony Tuininga 3a9a296cb9 Fixed bug with handling of redirect data returned by some SCAN listeners
(#39).
2022-07-29 15:24:25 -06:00
Anthony Tuininga c6f6b2077f The error "DPY-3015: password verifier type is not supported by
python-oracledb in thin mode" is now raised when the database sends a
password challenge with a verifier type that is not recognized, instead
of "ORA-01017: invalid username/password" (#26).
2022-07-29 15:23:50 -06:00
Anthony Tuininga 0e3b1507eb Correct documentation (#38). 2022-07-29 15:23:21 -06:00
Anthony Tuininga 4c86e20c73 Fixed bug with the deferral of type assignment when creating variables
for executemany() (#35).
2022-07-29 15:22:42 -06:00
Anthony Tuininga 213d535acf Bump version in preparation for release of python-oracledb 1.0.3. 2022-07-29 15:22:11 -06:00
Anthony Tuininga 65bbb201f5 Add missing release notes. 2022-07-18 11:21:31 -06:00
Anthony Tuininga 4b79df832a Tweak release note. 2022-07-15 15:50:50 -06:00
Anthony Tuininga 80e53852ff Thick: fixed the ability to use external authentication with connection
pools.
2022-07-15 11:43:28 -06:00
Anthony Tuininga c3de1a098f Fixed a bug that caused TLS renegotiation to be skipped in some
configurations (#34).
2022-07-15 11:42:36 -06:00
Anthony Tuininga eaa1851f42 Fixed a bug when calling cursor.executemany() with a PL/SQL statement
and a single row of data (#30).
2022-07-15 11:42:04 -06:00
Anthony Tuininga aa1135093f Further documentation improvements. 2022-07-15 11:41:44 -06:00
Anthony Tuininga dd99c40a37 Only add extra arguments when using macOS universal builds. 2022-07-15 11:41:24 -06:00
Anthony Tuininga 2f740ee06a Connecting to a database with national character set `UTF8` is now
supported; an error is now raised only when the first attempt to use
NCHAR, NVARCHAR2 or NCLOB data is made (#16).
2022-07-15 11:41:00 -06:00
Anthony Tuininga 53b041afe6 Correct tables in documentation. 2022-07-15 11:40:33 -06:00
Anthony Tuininga 1eb26ac8f0 Fix links. 2022-07-15 11:40:18 -06:00
Anthony Tuininga a7023bf1e6 Raise a more meaningful exception when attempting to use https_proxy with
protocol tcp.
2022-07-15 11:39:44 -06:00
Anthony Tuininga 52c8c4df4b Bump version to 1.0.2 in preparation for release of patch version. 2022-07-15 11:39:10 -06:00
Anthony Tuininga ed56b22ffd Get the version from the package directly instead of duplicating the
value (and allowing it to potentially be different).
2022-07-15 11:38:05 -06:00
39 changed files with 503 additions and 319 deletions

View File

@ -1,16 +1,18 @@
Sphinx is used to generate the HTML for the python-oracledb documentation.
The generated python-oracledb documentation is at https://python-oracledb.readthedocs.io/
The generated python-oracledb documentation is at
https://python-oracledb.readthedocs.io/
This directory contains the documentation source. It is written using reST
(re-Structured Text). The source files are processed using Sphinx and can be
turned into HTML, PDF or ePub documentation.
If you wish to build documentation yourself, install Sphinx. It is available
on many Linux distributions as a pre-built package. You can also install it
using the Python package manager "pip", for example::
If you wish to build documentation yourself, install Sphinx and the Read the
Docs theme. Sphinx is available on many Linux distributions as a pre-built
package. You can also install Sphinx and the Read the Docs theme using the
Python package manager "pip", for example::
python -m pip install Sphinx
python -m pip install -r requirements.txt
For more information on Sphinx, please visit this page:

View File

@ -1 +1,2 @@
sphinx==4.1.2
sphinx>=4.2.0
sphinx-rtd-theme

View File

@ -0,0 +1,4 @@
/* Added code to display tables without horizontal scrollbars */
.wy-table-responsive table td, .wy-table-responsive table th {
white-space: normal;
}

View File

@ -385,8 +385,10 @@ Cursor Methods
types:
.. list-table-with-summary::
:summary: The first column is the Python Type. The second column is the corresponding Database Type.
:header-rows: 1
:class: wy-table-responsive
:align: center
:summary: The first column is the Python Type. The second column is the corresponding Database Type.
* - Python Type
- Database Type

View File

@ -11,8 +11,7 @@ if applicable. The most recent deprecations are listed first.
.. list-table-with-summary:: Deprecated in python-oracledb 1.0
:header-rows: 1
:widths: 25 75
:width: 100%
:class: wy-table-responsive
:summary: The first column, Name, displays the deprecated API name. The second column, Comments, includes information about when the API was deprecated and what API to use, if applicable.
:name: _deprecations_1
@ -110,8 +109,7 @@ python-oracledb are listed below:
.. list-table-with-summary:: Deprecated in cx_Oracle 8.2
:header-rows: 1
:widths: 25 75
:width: 100%
:class: wy-table-responsive
:summary: The first column, Name, displays the deprecated API name. The second column,
Comments, includes information about when the API was deprecated and what API to use,
if applicable.
@ -209,8 +207,7 @@ python-oracledb are listed below:
.. list-table-with-summary:: Deprecated in cx_Oracle 8.0
:header-rows: 1
:widths: 25 75
:width: 100%
:class: wy-table-responsive
:summary: The first column, Name, displays the deprecated API name. The second column, Comments, includes information about when the API was deprecated and what API to use, if applicable.
:name: _deprecations_8_0
@ -252,8 +249,7 @@ python-oracledb are listed below:
.. list-table-with-summary:: Deprecated in cx_Oracle 7.2
:header-rows: 1
:widths: 25 75
:width: 100%
:class: wy-table-responsive
:summary: The first column, Name, displays the deprecated API name. The second column, Comments, includes information about when the API was deprecated and what API to use, if applicable.
:name: _deprecations_7_2
@ -271,8 +267,7 @@ python-oracledb are listed below:
.. list-table-with-summary:: Deprecated in cx_Oracle 6.4
:header-rows: 1
:widths: 25 75
:width: 100%
:class: wy-table-responsive
:summary: The first column, Name, displays the deprecated API name. The second column, Comments, includes information about when the API was deprecated and what API to use, if applicable.
:name: _deprecations_6_4

View File

@ -1753,8 +1753,6 @@ module.html#session-pool-get-modes>`_ constants that were used in cx_Oracle
of time (defined by the ``wait_timeout`` parameter) for a session to become
available before returning with an error.
This constant is not supported in the python-oracledb Thin mode.
.. note::
This constant deprecates the ``SPOOL_ATTRVAL_TIMEDWAIT`` constant that

View File

@ -40,10 +40,19 @@ author = 'Oracle'
# The default replacements for |version| and |release|, also used in various
# other places throughout the built documents.
#
# The values are acquired from the __version__ constant defined in the module
# itself in order to avoid duplicate values (and therefore one being different
# from the other)
#
# The short X.Y version.
version = '1.0'
global_vars = {}
local_vars = {}
version_file_name = os.path.join("..", "..", "src", "oracledb", "version.py")
with open(version_file_name) as f:
exec(f.read(), global_vars, local_vars)
version = ".".join(local_vars["__version__"].split(".")[:2])
# The full version, including alpha/beta/rc tags.
release = '1.0.0'
release = local_vars["__version__"]
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
@ -75,12 +84,15 @@ pygments_style = 'sphinx'
# The style sheet to use for HTML and HTML Help pages. A file of that name
# must exist either in Sphinx' static/ path, or in one of the custom paths
# given in html_static_path.
html_style = 'default.css'
# html_style = 'default.css'
# The theme to use for readthedocs.
html_theme = 'sphinx_rtd_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['.static']
html_static_path = ['.static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
@ -111,6 +123,10 @@ htmlhelp_basename = 'oracledbdoc'
numfig = True
# Display tables with no horizontal scrollbar
def setup(app):
app.add_css_file('custom.css')
# Options for LaTeX output
# ------------------------

View File

@ -7,6 +7,68 @@ python-oracledb Release Notes
For deprecations, see :ref:`Deprecations <deprecations>`.
oracledb 1.0.3 (August 2022)
----------------------------
Thin Mode Changes
+++++++++++++++++
#) The error `DPY-3015: password verifier type is not supported by
python-oracledb in thin mode` is now raised when
the database sends a password challenge with a verifier type that is not
recognized, instead of `ORA-01017: invalid username/password`
(`issue 26 <https://github.com/oracle/python-oracledb/issues/26>`__).
#) Fixed bug with handling of redirect data returned by some SCAN listeners
(`issue 39 <https://github.com/oracle/python-oracledb/issues/39>`__).
#) Fixed bug with re-execution of SQL that requires a define, such as occurs
when setting `oracledb.defaults.fetch_lobs` to the value `False`
(`issue 41 <https://github.com/oracle/python-oracledb/issues/41>`__).
#) Fixed bug that prevented cursors from implicit results sets from being
closed.
Common Changes
++++++++++++++
#) Fixed bug with the deferral of type assignment when creating variables for
:func:`Cursor.executemany()`
(`issue 35 <https://github.com/oracle/python-oracledb/issues/35>`__).
oracledb 1.0.2 (July 2022)
--------------------------
Thin Mode Changes
+++++++++++++++++
#) Connecting to a database with national character set `UTF8` is now
supported; an error is now raised only when the first attempt to use
NCHAR, NVARCHAR2 or NCLOB data is made
(`issue 16 <https://github.com/oracle/python-oracledb/issues/16>`__).
#) Fixed a bug when calling `cursor.executemany()` with a PL/SQL statement and
a single row of data
(`issue 30 <https://github.com/oracle/python-oracledb/issues/30>`__).
#) When using the connection parameter `https_proxy` while using protocol
`tcp`, a more meaningful exception is now raised:
`DPY-2029: https_proxy requires use of the tcps protocol`.
#) Fixed a bug that caused TLS renegotiation to be skipped in some
configurations, thereby causing the connection to fail to be established
(https://github.com/oracle/python-oracledb/discussions/34).
Thick Mode Changes
++++++++++++++++++
#) Fixed the ability to use external authentication with connection pools.
Common Changes
++++++++++++++
#) The compiler flag ``-arch x86_64`` no longer needs to be explicitly
specified when building from source code on macOS (Intel x86) without
Universal Python binaries.
#) Binary packages have been added for the Linux ARM 64-bit platform.
#) Improved samples and documentation.
oracledb 1.0.1 (June 2022)
--------------------------
@ -47,6 +109,7 @@ Common Changes
``wrapped()``.
#) Improved samples, including adding a Dockerfile that starts a container
with a running database and the samples.
#) A binary package has been added for Python 3.7 on macOS (Intel x86).
#) Improved documentation.

View File

@ -18,6 +18,7 @@ see :ref:`driverdiff` and :ref:`compatibility`.
.. list-table-with-summary:: Features Supported by python-oracledb and cx_Oracle 8.3
:header-rows: 1
:class: wy-table-responsive
:align: center
:summary: The first column displays the Oracle feature. The second column indicates whether the feature is supported in the python-oracledb Thin mode. The third column indicates whether the feature is supported in the python-oracledb Thick mode. The fourth column indicates if the feature is supported in cx_Oracle 8.3.
@ -387,6 +388,7 @@ values.
.. list-table-with-summary:: Oracle Database Data Types Supported
:header-rows: 1
:class: wy-table-responsive
:align: center
:summary: The first column displays the database data type. The second column displays the python-oracledb constant Name. The third column indicates if the type is supported in python-oracledb.

View File

@ -91,6 +91,7 @@ used in :meth:`oracledb.connect()`, :meth:`oracledb.create_pool()`,
.. list-table-with-summary:: Oracle Net Keywords Supported in the python-oracledb Thin Mode
:header-rows: 1
:class: wy-table-responsive
:align: center
:summary: The first column displays the keyword. The second column displays the equivalent oracledb.connect(), oracledb.create_pool(), oracledb.ConnectParams(), or oracledb.PoolParams() parameters. The third column displays the notes.
@ -214,9 +215,6 @@ differs from the python-oracledb Thick mode in the following ways:
``handle`` parameters. The parameters that are ignored in the Thick mode include
``wallet_password``, ``disable_oob``, ``config_dir``, and ``debug_jdwp`` parameters.
* The ``getmode`` value :data:`~oracledb.POOL_GETMODE_TIMEDWAIT` is not
implemented in the python-oracledb Thin mode.
* The python-oracledb Thin mode only suppports :ref:`homogeneous
<connpooltypes>` pools.
@ -313,8 +311,13 @@ Globalization in Thin and Thick Modes
=====================================
All NLS environment variables, and the ``ORA_SDTZ`` and ``ORA_TZFILE``
variables, are ignored by the python-oracledb Thin mode. Use Python's
capabilities instead. See :ref:`globalization`.
environment variables, are ignored by the python-oracledb Thin mode. Use
Python's capabilities instead.
The python-oracledb Thin mode can only use NCHAR, NVARCHAR2, and NCLOB data
when Oracle Database's secondary character set is AL16UTF16.
See :ref:`globalization`.
Tracing in Thin and Thick Modes
===============================

View File

@ -142,7 +142,7 @@ may cause complications if the password contains '@' or '/' characters:
port = 1521
service_name = "orclpdb"
dsn = f'{username}/{userpwd}@{port}:{host}/{service_name}'
dsn = f'{username}/{userpwd}@{host}:{port}/{service_name}'
connection = oracledb.connect(dsn)
Closing Connections

View File

@ -95,7 +95,8 @@ in the examples below:
python-oracledb Thin mode Error::
[Errno 61] Connection refused
DPY-6005: cannot connect to database. Connection failed with "[Errno 61]
Connection refused"
python-oracledb Thick mode Error::

View File

@ -4,48 +4,24 @@
Character Sets and Globalization
********************************
Character Sets
==============
Database Character Set
----------------------
Data fetched from and sent to Oracle Database will be mapped between the
database character set and the "Oracle client" character set of the Oracle
Client libraries used by python-oracledb. If data cannot be correctly mapped between
client and server character sets, then it may be corrupted or queries may fail
with :ref:`"codec can't decode byte" <codecerror>`.
`database character set
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-EA913CC8-C5BA-4FB3-A1B8-882734AF4F43>`__
and the "Oracle client" character set of the Oracle Client libraries used by
python-oracledb. If data cannot be correctly mapped between client and server
character sets, then it may be corrupted or queries may fail with :ref:`"codec
can't decode byte" <codecerror>`.
All database character sets are supported by the python-oracledb Thick mode.
The database performs any required conversion for the python-oracledb Thin
mode.
For the `national character set
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-4E12D991-C286-4F1A-AFC6-F35040A5DE4F>`__
used for NCHAR, NVARCHAR2, and NCLOB data types:
- AL16UTF16 is supported by both the python-oracledb Thin and Thick modes
- UTF8 is not supported by the python-oracledb Thin mode
Python-oracledb Thick mode uses Oracle's National Language Support (NLS) to
assist in globalizing applications, see :ref:`thicklocale`.
.. note::
All NLS environment variables are ignored by the python-oracledb Thin mode.
Also the ``ORA_SDTZ`` and ``ORA_TZFILE`` variables are ignored. See
:ref:`thindatenumber`.
For more information, see the `Database Globalization Support Guide
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=NLSPG>`__.
Setting the Client Character Set
================================
In python-oracledb, the encoding used for all character data is "UTF-8". The
``encoding`` and ``nencoding`` parameters of the :meth:`oracledb.connect`
and :meth:`oracledb.create_pool` methods are ignored.
All database character sets are supported by the python-oracledb.
.. _findingcharset:
Finding the Oracle Database Character Set
=========================================
To find the database character set, execute the query:
.. code-block:: sql
@ -54,8 +30,17 @@ To find the database character set, execute the query:
FROM nls_database_parameters
WHERE parameter = 'NLS_CHARACTERSET';
To find the database 'national character set' used for NCHAR and related types,
execute the query:
Database National Character Set
-------------------------------
For the secondary `national character set
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-4E12D991-C286-4F1A-AFC6-F35040A5DE4F>`__
used for NCHAR, NVARCHAR2, and NCLOB data types:
- AL16UTF16 is supported by both the python-oracledb Thin and Thick modes
- UTF8 is not supported by the python-oracledb Thin mode
To find the database's national character set, execute the query:
.. code-block:: sql
@ -63,27 +48,69 @@ execute the query:
FROM nls_database_parameters
WHERE parameter = 'NLS_NCHAR_CHARACTERSET';
To find the current "client" character set used by python-oracledb, execute the
query:
Setting the Client Character Set
--------------------------------
.. code-block:: sql
In python-oracledb, the encoding used for all character data is "UTF-8". The
``encoding`` and ``nencoding`` parameters of the :meth:`oracledb.connect`
and :meth:`oracledb.create_pool` methods are deprecated and ignored.
SELECT DISTINCT client_charset AS client_charset
FROM v$session_connect_info
WHERE sid = SYS_CONTEXT('USERENV', 'SID');
Setting the Client Locale
=========================
Thick Mode Oracle Database National Language Support (NLS)
----------------------------------------------------------
The python-oracledb Thick mode uses Oracle Database's National Language Support
(NLS) functionality to assist in globalizing applications, for example to
convert numbers and dates to strings in the locale specific format.
You can use the ``NLS_LANG`` environment variable to set the language and
territory used by the Oracle Client libraries. For example, on Linux you could
set::
export NLS_LANG=JAPANESE_JAPAN
The language ("JAPANESE" in this example) specifies conventions such as the
language used for Oracle Database messages, sorting, day names, and month
names. The territory ("JAPAN") specifies conventions such as the default date,
monetary, and numeric formats. If the language is not specified, then the value
defaults to AMERICAN. If the territory is not specified, then the value is
derived from the language value. See `Choosing a Locale with the NLS_LANG
Environment Variable
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-86A29834-AE29-4BA5-8A78-E19C168B690A>`__
If the ``NLS_LANG`` environment variable is set in the application with
``os.environ['NLS_LANG']``, it must be set before any connection pool is
created, or before any standalone connections are created.
Any client character set value in the ``NLS_LANG`` variable, for example
``JAPANESE_JAPAN.JA16SJIS``, is ignored by python-oracledb. See `Setting the
Client Character Set`_.
Other Oracle globalization variables, such as ``NLS_DATE_FORMAT`` can also be
set to change the behavior of python-oracledb Thick, see `Setting NLS Parameters
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&
id=GUID-6475CA50-6476-4559-AD87-35D431276B20>`__.
For more information, see the `Database Globalization Support Guide
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=NLSPG>`__.
.. _thindatenumber:
Locale-aware Number and Date Conversions in python-oracledb Thin Mode
=====================================================================
Thin Mode Locale-aware Number and Date Conversions
--------------------------------------------------
In python-oracledb Thick mode, Oracle NLS routines convert numbers and dates to
strings in the locale specific format. But in the python-oracledb Thin mode,
output type handlers need to be used to perform similar conversions. The
examples below show a simple conversion and also how the Python locale module
can be used. Type handlers like those below can also be used in
python-oracledb Thick mode.
.. note::
All NLS environment variables are ignored by the python-oracledb Thin mode.
Also the ``ORA_SDTZ`` and ``ORA_TZFILE`` variables are ignored.
In the python-oracledb Thin mode, output type handlers need to be used to
perform similar conversions. The examples below show a simple conversion and
also how the Python locale module can be used. Type handlers like those below
can also be used in python-oracledb Thick mode.
To convert numbers:
@ -188,33 +215,3 @@ To convert dates:
for row in cursor:
print(row) # gives 'Mi 15 Dez 19:57:56 2021'
print()
.. _thicklocale:
Setting the Oracle Client Locale in python-oracledb Thick Mode
==============================================================
You can use the ``NLS_LANG`` environment variable to set the language and
territory used by the Oracle Client libraries. For example, on Linux you could
set::
export NLS_LANG=JAPANESE_JAPAN
The language ("JAPANESE" in this example) specifies conventions such as the
language used for Oracle Database messages, sorting, day names, and month
names. The territory ("JAPAN") specifies conventions such as the default date,
monetary, and numeric formats. If the language is not specified, then the value
defaults to AMERICAN. If the territory is not specified, then the value is
derived from the language value. See `Choosing a Locale with the NLS_LANG
Environment Variable
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-86A29834-AE29-4BA5-8A78-E19C168B690A>`__
If the ``NLS_LANG`` environment variable is set in the application with
``os.environ['NLS_LANG']``, it must be set before any connection pool is
created, or before any standalone connections are created.
Other Oracle globalization variables, such as ``NLS_DATE_FORMAT`` can also be
set to change the behavior of python-oracledb Thick, see `Setting NLS Parameters
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&
id=GUID-6475CA50-6476-4559-AD87-35D431276B20>`__.

View File

@ -429,6 +429,7 @@ first connection is established. System environment variables like
.. list-table-with-summary:: Common Oracle environment variables
:header-rows: 1
:class: wy-table-responsive
:widths: 1 2
:summary: The first column displays the Oracle Environment Variable. The second column, Purpose, describes what the environment variableis used for.
:align: left

View File

@ -173,6 +173,7 @@ needed but there is no direct mapping from Python.
.. list-table-with-summary::
:header-rows: 1
:class: wy-table-responsive
:widths: 1 1 1
:summary: The first column is the Python Type or Value. The second column is the equivalent JSON Attribute Type or Value. The third column is the SQL Equivalent syntax.
:align: left
@ -263,6 +264,7 @@ attribute mapping occurs:
.. list-table-with-summary::
:header-rows: 1
:class: wy-table-responsive
:widths: 1 1
:align: left
:summary: The first column is the Database JSON Attribute Type or Value. The second column is the corresponding Python Type or Value mapped.

View File

@ -164,6 +164,7 @@ Python object that is returned by default. Python types can be changed with
.. list-table-with-summary::
:header-rows: 1
:class: wy-table-responsive
:widths: 1 1 1
:align: left
:summary: The first column is the Oracle Database Type. The second column is the oracledb Database Type that is returned in the query metadata. The third column is the type of Python object that is returned by default.

View File

@ -287,6 +287,7 @@ columns:
.. list-table-with-summary:: Sample V$SESSION_CONNECT_INFO column values
:header-rows: 1
:class: wy-table-responsive
:widths: 15 10 10
:name: V$SESSION_CONNECT_INFO
:summary: The first column is the name of V$SESSION_CONNECT_INFO view's column. The second column lists a sample python-oracledb Thick mode value. The third column list a sample python-oracledb Thin mode value.
@ -314,6 +315,7 @@ The following table list sample values for columns with differences in
.. list-table-with-summary:: Sample V$SESSION column values
:header-rows: 1
:class: wy-table-responsive
:widths: 15 10 10
:name: V$SESSION_COLUMN_VALUES
:summary: The first column is the name of the column. The second column lists a sample python-oracledb Thick mode value. The third column lists a sample python-oracledb Thin mode value.

View File

@ -30,7 +30,7 @@
# using the Shapely library for geometry object support.
#
# See http://geopandas.org, https://pandas.pydata.org,
# and https://github.com/Toblerity/Shapely.
# and https://github.com/shapely/shapely.
#
# This example shows how to bring geometries from Oracle Spatial (SDO_GEOMETRY
# data type) into GeoPandas and perform a simple spatial operation. While the
@ -40,7 +40,7 @@
# additional sources such as files and web services.
#
# This script requires GeoPandas and its dependencies (see
# http://geopandas.org/install.html).
# https://geopandas.org/en/stable/getting_started/install.html).
#------------------------------------------------------------------------------
from shapely.wkb import loads

View File

@ -149,7 +149,7 @@
<p>When you have finished this tutorial, we recommend reviewing the <a href="http://python-oracledb.readthedocs.org/en/latest/index.html" >python-oracledb documentation</a>.</p>
<p>The original copy of these instructions that you are reading is <a
href="https://oracle.github.io/python-oracledb/samples/tutorial/Python-and-Oracle-Database-Scripting-for-the-Future.html"
href="https://oracle.github.io/python-oracledb/samples/tutorial/Python-and-Oracle-Database-The-New-Wave-of-Scripting.html"
>here</a>.</p>
<h3><a name="architecture">Python-oracledb Architecture</a></h3>

View File

@ -25,6 +25,7 @@
import os
import platform
import sys
import sysconfig
from setuptools import setup, Extension
# base source directory
@ -61,11 +62,10 @@ thin_depends = [os.path.join(impl_dir, n) \
thin_depends.append(base_pxd)
# if the platform is macOS, add arguments required for cross-compilation for
# both x86_64 and arm64 architectures.
# Use a Universal 2 Python binary to build, or set an archtecture
# before building, e.g. export ARCHFLAGS="-arch x86_64"
# both x86_64 and arm64 architectures if the python interpreter is a
# universal2 version.
extra_compile_args = []
if sys.platform == "darwin":
if sys.platform == "darwin" and "universal2" in sysconfig.get_platform():
if platform.machine() == "x86_64":
target = "arm64-apple-macos"
else:

View File

@ -186,7 +186,6 @@ cdef class ConnectParamsImpl:
cdef str _get_wallet_password(self)
cdef int _parse_connect_string(self, str connect_string) except -1
cdef int _process_connect_descriptor(self, dict args) except -1
cdef int _process_redirect_data(self, str redirect_data) except -1
cdef int _set_new_password(self, str password) except -1
cdef int _set_password(self, str password) except -1
cdef int _set_wallet_password(self, str password) except -1
@ -252,7 +251,6 @@ cdef class BaseCursorImpl:
uint32_t _buffer_index
uint32_t _fetch_array_size
bint _more_rows_to_fetch
bint _is_implicit_cursor
cdef int _bind_values(self, object cursor, object type_handler,
object params, uint32_t num_rows, uint32_t row_num,

View File

@ -264,12 +264,9 @@ class ConnectParams:
"""
@functools.wraps(f)
def wrapped(self):
output = []
for description in self._impl.description_list.descriptions:
for address_list in description.address_lists:
for address in address_list.addresses:
output.append(getattr(address, f.__name__))
return output if len(output) > 1 else output[0]
values = [getattr(a, f.__name__) \
for a in self._impl._get_addresses()]
return values if len(values) > 1 else values[0]
return wrapped
def _description_attr(f):
@ -278,10 +275,9 @@ class ConnectParams:
"""
@functools.wraps(f)
def wrapped(self):
output = []
for description in self._impl.description_list.descriptions:
output.append(getattr(description, f.__name__))
return output if len(output) > 1 else output[0]
values = [getattr(d, f.__name__) \
for d in self._impl.description_list.descriptions]
return values if len(values) > 1 else values[0]
return wrapped
@property

View File

@ -152,6 +152,7 @@ ERR_INVALID_CONNECT_PARAMS = 2025
ERR_INVALID_POOL_CLASS = 2026
ERR_INVALID_POOL_PARAMS = 2027
ERR_EXPECTING_LIST_FOR_ARRAY_VAR = 2028
ERR_HTTPS_PROXY_REQUIRES_TCPS = 2029
# error numbers that result in NotSupportedError
ERR_TIME_NOT_SUPPORTED = 3000
@ -169,6 +170,7 @@ ERR_SERVER_LOGON_TYPE_NOT_SUPPORTED = 3011
ERR_NCHAR_CS_NOT_SUPPORTED = 3012
ERR_UNSUPPORTED_PYTHON_TYPE_FOR_VAR = 3013
ERR_LOB_OF_WRONG_TYPE = 3014
ERR_UNSUPPORTED_VERIFIER_TYPE = 3015
# error numbers that result in DatabaseError
ERR_TNS_ENTRY_NOT_FOUND = 4000
@ -285,6 +287,8 @@ ERR_MESSAGE_FORMATS = {
'to cursor.var()',
ERR_FEATURE_NOT_SUPPORTED:
'{feature} is only supported in python-oracledb {driver_type} mode',
ERR_HTTPS_PROXY_REQUIRES_TCPS:
'https_proxy requires use of the tcps protocol',
ERR_INCONSISTENT_DATATYPES:
'cannot convert from data type {input_type} to {output_type}',
ERR_INCORRECT_VAR_ARRAYSIZE:
@ -431,6 +435,9 @@ ERR_MESSAGE_FORMATS = {
'{db_type_name}',
ERR_UNSUPPORTED_VAR_SET:
'variable of type {db_type_name} does not support being set',
ERR_UNSUPPORTED_VERIFIER_TYPE:
'password verifier type 0x{verifier_type:x} is not supported by '
'python-oracledb in thin mode',
ERR_WALLET_FILE_MISSING:
'cannot connect to database. Wallet file {name} was not found',
ERR_WRONG_ARRAY_DEFINITION:

View File

@ -335,21 +335,6 @@ cdef class ConnectParamsImpl:
address.set_from_args(addr_args)
address_list.addresses.append(address)
cdef int _process_redirect_data(self, str redirect_data) except -1:
"""
Internal method used for parsing the redirect data that is returned
from a listener in order to determine the new host and part that should
be used to connect to the database.
"""
cdef:
dict args = {}
pos = redirect_data.find('\x00')
if pos < 0:
errors._raise_err(errors.ERR_INVALID_REDIRECT_DATA,
data=redirect_data)
_parse_connect_descriptor(redirect_data[:pos], args)
self._process_connect_descriptor(args)
cdef int _set_new_password(self, str password) except -1:
"""
Sets the new password on the instance after first obfuscating it.
@ -402,6 +387,18 @@ cdef class ConnectParamsImpl:
new_params._copy(self)
return new_params
def _get_addresses(self):
"""
Return a list of the stored addresses.
"""
cdef:
AddressList addr_list
Description desc
Address addr
return [addr for desc in self.description_list.descriptions \
for addr_list in desc.address_lists \
for addr in addr_list.addresses]
def get_connect_string(self):
"""
Internal method for getting the connect string. This will either be the

View File

@ -335,7 +335,7 @@ cdef class BaseCursorImpl:
num_rows = len(parameters)
self._reset_bind_vars(num_rows)
for i, params_row in enumerate(parameters):
defer_type_assignment = (i == num_rows - 1)
defer_type_assignment = (i < num_rows - 1)
self._bind_values(cursor, type_handler, params_row, num_rows, i,
defer_type_assignment)

View File

@ -33,6 +33,7 @@
cdef class ThickCursorImpl(BaseCursorImpl):
cdef:
ThickConnImpl _conn_impl
bint _is_implicit_cursor
bint _fixup_ref_cursor
dpiStmtInfo _stmt_info
dpiStmt *_handle

View File

@ -91,6 +91,7 @@ cdef class ThickPoolImpl(BasePoolImpl):
create_params.pingInterval = params.ping_interval
common_params.stmtCacheSize = params.stmtcachesize
common_params.sodaMetadataCache = params.soda_metadata_cache
create_params.externalAuth = params.externalauth
# prepare user, password and DSN for use
if self.username is not None:

View File

@ -324,14 +324,14 @@ cdef class ReadBuffer:
"""
cdef:
ssize_t num_bytes_left, num_bytes_split, max_split_data
uint8_t packet_type, packet_flags
const char_type *source_ptr
uint8_t packet_type
char_type *dest_ptr
# if no bytes are left in the buffer, a new packet needs to be fetched
# before anything else can take place
if self._offset == self._size:
self.receive_packet(&packet_type)
self.receive_packet(&packet_type, &packet_flags)
self.skip_raw_bytes(2) # skip data flags
# if there is enough room in the buffer to satisfy the number of bytes
@ -367,7 +367,7 @@ cdef class ReadBuffer:
while num_bytes > 0:
# acquire new packet
self.receive_packet(&packet_type)
self.receive_packet(&packet_type, &packet_flags)
self.skip_raw_bytes(2) # skip data flags
# copy data into the chunked buffer or split buffer, as appropriate
@ -405,7 +405,8 @@ cdef class ReadBuffer:
errors._raise_err(errors.ERR_UNSUPPORTED_INBAND_NOTIFICATION,
err_num=error_num)
cdef int _receive_packet_helper(self, uint8_t *packet_type) except -1:
cdef int _receive_packet_helper(self, uint8_t *packet_type,
uint8_t *packet_flags) except -1:
"""
Receives a packet and updates the pointers appropriately. Note that
multiple packets may be received if they are small enough or a portion
@ -475,7 +476,8 @@ cdef class ReadBuffer:
if self._caps.protocol_version < TNS_VERSION_MIN_LARGE_SDU:
self.skip_raw_bytes(2) # skip packet checksum
self.read_ub1(packet_type)
self.skip_raw_bytes(3) # skip reserved byte, header checksum
self.read_ub1(packet_flags)
self.skip_raw_bytes(2) # header checksum
# display packet if requested
if DEBUG_PACKETS:
@ -1079,14 +1081,15 @@ cdef class ReadBuffer:
return bytes(output_value).decode()
cdef int receive_packet(self, uint8_t *packet_type) except -1:
cdef int receive_packet(self, uint8_t *packet_type,
uint8_t *packet_flags) except -1:
"""
Calls _receive_packet_helper() and checks the packet type. If a
control packet is received, it is processed and the next packet is
received.
"""
while True:
self._receive_packet_helper(packet_type)
self._receive_packet_helper(packet_type, packet_flags)
if packet_type[0] == TNS_PACKET_TYPE_CONTROL:
self._process_control_packet()
continue

View File

@ -34,6 +34,8 @@ cdef class Capabilities:
cdef:
uint16_t protocol_version
uint8_t ttc_field_version
uint16_t charset_id
uint16_t ncharset_id
bytearray compile_caps
bytearray runtime_caps
bint char_conversion
@ -58,6 +60,15 @@ cdef class Capabilities:
cdef void _adjust_for_server_runtime_caps(self, bytearray server_caps):
pass
cdef int _check_ncharset_id(self) except -1:
"""
Checks that the national character set id is AL16UTF16, which is the
only id that is currently supported.
"""
if self.ncharset_id != TNS_CHARSET_UTF16:
errors._raise_err(errors.ERR_NCHAR_CS_NOT_SUPPORTED,
charset_id=self.ncharset_id)
@cython.boundscheck(False)
cdef void _init_compile_caps(self):
self.ttc_field_version = TNS_CCAP_FIELD_VERSION_MAX

View File

@ -85,9 +85,37 @@ cdef class ThinConnImpl(BaseConnImpl):
elif stmt._cursor_id != 0:
self._add_cursor_to_close(stmt)
cdef object _connect_with_description(self, Description description,
ConnectParamsImpl params,
bint final_desc):
cdef int _connect_with_address(self, Address address,
Description description,
ConnectParamsImpl params,
bint raise_exception) except -1:
"""
Internal method used for connecting with the given description and
address.
"""
try:
self._protocol._connect_phase_one(self, params, description,
address)
except exceptions.DatabaseError:
if raise_exception:
raise
return 0
except (socket.gaierror, ConnectionRefusedError) as e:
if raise_exception:
errors._raise_err(errors.ERR_CONNECTION_FAILED, cause=e,
exception=str(e))
return 0
except Exception as e:
errors._raise_err(errors.ERR_CONNECTION_FAILED, cause=e,
exception=str(e))
self._drcp_enabled = description.server_type == "pooled"
if self._cclass is None:
self._cclass = description.cclass
self._protocol._connect_phase_two(self, description, params)
cdef int _connect_with_description(self, Description description,
ConnectParamsImpl params,
bint final_desc) except -1:
cdef:
bint load_balance = description.load_balance
bint raise_exc = False
@ -126,25 +154,18 @@ cdef class ThinConnImpl(BaseConnImpl):
raise_exc = i == num_attempts - 1 \
and j == num_lists - 1 \
and k == num_addresses - 1
redirect_params = self._connect_with_address(address,
description,
params,
raise_exc)
if redirect_params is not None:
return redirect_params
self._connect_with_address(address, description, params,
raise_exc)
if self._protocol._in_connect:
continue
address_list.lru_index = (idx1 + 1) % num_addresses
description.lru_index = (idx2 + 1) % num_lists
return
return 0
time.sleep(description.retry_delay)
cdef ConnectParamsImpl _connect_with_params(self,
ConnectParamsImpl params):
cdef int _connect_with_params(self, ConnectParamsImpl params) except -1:
"""
Internal method used for connecting with the given parameters. If the
listener requests a redirect, the redirect data is returned so that
this process can be repeated as needed.
Internal method used for connecting with the given parameters.
"""
cdef:
DescriptionList description_list = params.description_list
@ -160,14 +181,10 @@ cdef class ThinConnImpl(BaseConnImpl):
else:
idx = i
description = descriptions[idx]
redirect_params = self._connect_with_description(description,
params,
final_desc)
if redirect_params is not None \
or not self._protocol._in_connect:
self._connect_with_description(description, params, final_desc)
if not self._protocol._in_connect:
description_list.lru_index = (idx + 1) % num_descriptions
break
return redirect_params
cdef Message _create_message(self, type typ):
"""
@ -183,72 +200,6 @@ cdef class ThinConnImpl(BaseConnImpl):
self._pool = None
self._protocol._force_close()
cdef object _connect_with_address(self, Address address,
Description description,
ConnectParamsImpl params,
bint raise_exception):
"""
Creates a socket on which to communicate using the provided parameters.
If a proxy is configured, a connection to the proxy is established and
the target host and port is forwarded to the proxy. The socket is used
to establish a connection with the database. If a redirect is
required, the redirect parameters are returned.
"""
cdef:
bint use_proxy = (address.https_proxy is not None)
double timeout = description.tcp_connect_timeout
if use_proxy:
connect_info = (address.https_proxy, address.https_proxy_port)
else:
connect_info = (address.host, address.port)
try:
sock = socket.create_connection(connect_info, timeout)
if use_proxy:
data = f"CONNECT {address.host}:{address.port} HTTP/1.0\r\n\r\n"
sock.send(data.encode())
reply = sock.recv(1024)
match = re.search('HTTP/1.[01]\\s+(\\d+)\\s+', reply.decode())
if match is None or match.groups()[0] != '200':
errors._raise_err(errors.ERR_PROXY_FAILURE,
response=reply.decode())
if description.expire_time > 0:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
if hasattr(socket, "TCP_KEEPIDLE") \
and hasattr(socket, "TCP_KEEPINTVL") \
and hasattr(socket, "TCP_KEEPCNT"):
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,
description.expire_time * 60)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL,
6)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT,
10)
sock.settimeout(None)
if address.protocol == "tcps":
sock = get_ssl_socket(sock, params, description, address)
self._drcp_enabled = description.server_type == "pooled"
if self._cclass is None:
self._cclass = description.cclass
self._protocol._set_socket(sock)
redirect_params = self._protocol._connect_phase_one(self, params,
description,
address)
if redirect_params is not None:
return redirect_params
except exceptions.DatabaseError:
if raise_exception:
raise
return
except (socket.gaierror, ConnectionRefusedError) as e:
if raise_exception:
errors._raise_err(errors.ERR_CONNECTION_FAILED, cause=e,
exception=str(e))
return
except Exception as e:
errors._raise_err(errors.ERR_CONNECTION_FAILED, cause=e,
exception=str(e))
return
self._protocol._connect_phase_two(self, description, params)
cdef Statement _get_statement(self, str sql, bint cache_statement):
"""
Get a statement from the statement cache, or prepare a new statement
@ -338,14 +289,9 @@ cdef class ThinConnImpl(BaseConnImpl):
self._protocol._process_single_message(message)
def connect(self, ConnectParamsImpl params):
cdef ConnectParamsImpl redirect_params
if params._password is None:
errors._raise_err(errors.ERR_NO_PASSWORD)
while True:
redirect_params = self._connect_with_params(params)
if redirect_params is None:
break
params = redirect_params
self._connect_with_params(params)
self._statement_cache = collections.OrderedDict()
self._statement_cache_size = params.stmtcachesize
self._statement_cache_lock = threading.Lock()

View File

@ -39,6 +39,9 @@ DEF TNS_PACKET_TYPE_MARKER = 12
DEF TNS_PACKET_TYPE_CONTROL = 14
DEF TNS_PACKET_TYPE_REDIRECT = 5
# packet flags
DEF TNS_PACKET_FLAG_TLS_RENEG = 0x08
# marker types
DEF TNS_MARKER_TYPE_BREAK = 1
DEF TNS_MARKER_TYPE_RESET = 2
@ -593,8 +596,9 @@ DEF TNS_RCAP_TTC_ZERO_COPY = 0x01
DEF TNS_RCAP_TTC_32K = 0x04
# verifier types
DEF TNS_VERIFIER_TYPE_SHA1 = 45394
DEF TNS_VERIFIER_TYPE_SSHA1 = 6949
DEF TNS_VERIFIER_TYPE_11G_1 = 0xb152
DEF TNS_VERIFIER_TYPE_11G_2 = 0x1b25
DEF TNS_VERIFIER_TYPE_12C = 0x4815
# other constants
DEF TNS_MAX_SHORT_LENGTH = 252

View File

@ -117,6 +117,14 @@ def get_ssl_socket(sock, ConnectParamsImpl params, Description description,
ssl_context.load_verify_locations(pem_file_name)
ssl_context.load_cert_chain(pem_file_name,
password=params._get_wallet_password())
return perform_tls_negotiation(sock, ssl_context, description, address)
def perform_tls_negotiation(sock, ssl_context, Description description,
Address address):
"""
Peforms TLS negotiation.
"""
if description.ssl_server_dn_match \
and description.ssl_server_cert_dn is None:
sock = ssl_context.wrap_socket(sock, server_hostname=address.host)

View File

@ -46,8 +46,7 @@ cdef class ThinCursorImpl(BaseCursorImpl):
cdef int _close(self, bint in_del) except -1:
if self._statement is not None:
if not self._is_implicit_cursor:
self._conn_impl._return_statement(self._statement)
self._conn_impl._return_statement(self._statement)
self._statement = None
cdef BaseVarImpl _create_var_impl(self, object conn):
@ -148,7 +147,7 @@ cdef class ThinCursorImpl(BaseCursorImpl):
for i in range(num_execs - 1):
message.offset = i + 1
self._conn_impl._protocol._process_single_message(message)
else:
elif num_execs > 1:
message.offset = 1
message.num_execs = num_execs - 1
self._conn_impl._protocol._process_single_message(message)
@ -188,8 +187,7 @@ cdef class ThinCursorImpl(BaseCursorImpl):
def prepare(self, str sql, str tag, bint cache_statement):
self.statement = sql
if self._statement is not None:
if not self._is_implicit_cursor:
self._conn_impl._return_statement(self._statement)
self._conn_impl._return_statement(self._statement)
self._statement = None
self._statement = self._conn_impl._get_statement(sql.strip(),
cache_statement)

View File

@ -51,6 +51,7 @@ cdef class Message:
uint32_t call_status
uint16_t end_to_end_seq_num
uint8_t packet_type
uint8_t packet_flags
bint error_occurred
bint flush_out_binds
bint processed_error
@ -540,6 +541,8 @@ cdef class MessageWithData(Message):
elif ora_type_num == TNS_DATA_TYPE_VARCHAR \
or ora_type_num == TNS_DATA_TYPE_CHAR \
or ora_type_num == TNS_DATA_TYPE_LONG:
if csfrm == TNS_CS_NCHAR:
buf._caps._check_ncharset_id()
column_value = buf.read_str(csfrm)
elif ora_type_num == TNS_DATA_TYPE_RAW \
or ora_type_num == TNS_DATA_TYPE_LONG_RAW:
@ -744,7 +747,6 @@ cdef class MessageWithData(Message):
child_cursor = self._create_cursor_from_describe(buf)
child_cursor_impl = child_cursor._impl
buf.read_ub2(&child_cursor_impl._statement._cursor_id)
child_cursor_impl._is_implicit_cursor = True
self.cursor_impl._implicit_resultsets.append(child_cursor)
cdef int _process_io_vector(self, ReadBuffer buf) except -1:
@ -944,9 +946,10 @@ cdef class MessageWithData(Message):
elif ora_type_num == TNS_DATA_TYPE_VARCHAR \
or ora_type_num == TNS_DATA_TYPE_CHAR \
or ora_type_num == TNS_DATA_TYPE_LONG:
if var_impl.dbtype._csfrm == 1:
if var_impl.dbtype._csfrm == TNS_CS_IMPLICIT:
temp_bytes = (<str> value).encode()
else:
buf._caps._check_ncharset_id()
temp_bytes = (<str> value).encode(TNS_ENCODING_UTF16)
buf.write_bytes_chunked(temp_bytes)
elif ora_type_num == TNS_DATA_TYPE_RAW \
@ -1473,9 +1476,13 @@ cdef class AuthMessage(Message):
if self.purity != 0:
num_pairs += 1
self.auth_mode |= TNS_AUTH_MODE_WITH_PASSWORD
if self.verifier_type == TNS_VERIFIER_TYPE_SHA1 \
or self.verifier_type == TNS_VERIFIER_TYPE_SSHA1:
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:
errors._raise_err(errors.ERR_UNSUPPORTED_VERIFIER_TYPE,
verifier_type=self.verifier_type)
else:
num_pairs += 1
self._generate_verifier(verifier_11g)
@ -1532,7 +1539,8 @@ cdef class ConnectMessage(Message):
bytes connect_string_bytes
Description description
str redirect_data
Address address
str host
int port
cdef int process(self, ReadBuffer buf) except -1:
cdef:
@ -1540,7 +1548,7 @@ cdef class ConnectMessage(Message):
const char_type *redirect_data
if self.packet_type == TNS_PACKET_TYPE_REDIRECT:
buf.read_uint16(&redirect_data_length)
buf.receive_packet(&self.packet_type)
buf.receive_packet(&self.packet_type, &self.packet_flags)
buf.skip_raw_bytes(2) # skip data flags
redirect_data = buf._get_raw(redirect_data_length)
self.redirect_data = \
@ -1564,13 +1572,11 @@ cdef class ConnectMessage(Message):
if error_code_int == TNS_ERR_INVALID_SERVICE_NAME:
errors._raise_err(errors.ERR_INVALID_SERVICE_NAME,
service_name=self.description.service_name,
host=self.address.host,
port=self.address.port)
host=self.host, port=self.port)
elif error_code_int == TNS_ERR_INVALID_SID:
errors._raise_err(errors.ERR_INVALID_SID,
sid=self.description.sid,
host=self.address.host,
port=self.address.port)
host=self.host, port=self.port)
errors._raise_err(errors.ERR_LISTENER_REFUSED_CONNECTION,
error_code=error_code)
@ -1650,13 +1656,18 @@ cdef class ExecuteMessage(MessageWithData):
Runs after the database response has been processed. If the statement
executed requires define and is not a REF cursor (which would already
have performed the define during its execute), then mark the message as
needing to be resent.
needing to be resent. If this is after the second time the message has
been sent, mark the statement as no longer needing a define (since this
only needs to happen once).
"""
MessageWithData._postprocess(self)
cdef Statement stmt = self.cursor_impl._statement
if stmt._requires_define and stmt._sql is not None:
stmt._requires_full_execute = True
self.resend = True
if self.resend:
stmt._requires_define = False
else:
stmt._requires_full_execute = True
self.resend = True
cdef int _write_execute_message(self, WriteBuffer buf) except -1:
"""
@ -1976,6 +1987,7 @@ cdef class LobOpMessage(Message):
buf.write_bytes(self.dest_lob_impl._locator)
if self.operation == TNS_LOB_OP_CREATE_TEMP:
if self.source_lob_impl.dbtype._csfrm == TNS_CS_NCHAR:
buf._caps._check_ncharset_id()
buf.write_ub4(TNS_CHARSET_UTF16)
else:
buf.write_ub4(TNS_CHARSET_UTF8)
@ -2068,9 +2080,10 @@ cdef class ProtocolMessage(Message):
cdef int _process_message(self, ReadBuffer buf,
uint8_t message_type) except -1:
cdef:
uint16_t num_elem, fdo_length, charset_id, ncharset_id
uint16_t num_elem, fdo_length
bytearray server_compile_caps
bytearray server_runtime_caps
Capabilities caps = buf._caps
const char_type *fdo
ssize_t ix
uint8_t c
@ -2080,8 +2093,8 @@ cdef class ProtocolMessage(Message):
buf.read_ub1(&c)
if c == 0:
break
buf.read_uint16(&charset_id, BYTE_ORDER_LSB)
buf._caps.char_conversion = charset_id != TNS_CHARSET_UTF8
buf.read_uint16(&caps.charset_id, BYTE_ORDER_LSB)
buf._caps.char_conversion = caps.charset_id != TNS_CHARSET_UTF8
buf.skip_ub1() # skip server flags
buf.read_uint16(&num_elem, BYTE_ORDER_LSB)
if num_elem > 0: # skip elements
@ -2089,10 +2102,7 @@ cdef class ProtocolMessage(Message):
buf.read_uint16(&fdo_length)
fdo = buf.read_raw_bytes(fdo_length)
ix = 6 + fdo[5] + fdo[6]
ncharset_id = (fdo[ix + 3] << 8) + fdo[ix + 4]
if ncharset_id != TNS_CHARSET_UTF16:
errors._raise_err(errors.ERR_NCHAR_CS_NOT_SUPPORTED,
charset_id=ncharset_id)
caps.ncharset_id = (fdo[ix + 3] << 8) + fdo[ix + 4]
server_compile_caps = bytearray(buf.read_bytes())
server_runtime_caps = bytearray(buf.read_bytes())
if not server_compile_caps[TNS_CCAP_LOGON_TYPES] & TNS_CCAP_O7LOGON:

View File

@ -116,58 +116,79 @@ cdef class Protocol:
self._process_message(message)
self._final_close(self._write_buf)
cdef ConnectParamsImpl _connect_phase_one(self, ThinConnImpl conn_impl,
ConnectParamsImpl params,
Description description,
Address address):
cdef int _connect_phase_one(self,
ThinConnImpl conn_impl,
ConnectParamsImpl params,
Description description,
Address address) except -1:
"""
Method for performing the required steps for establishing a connection
within the scope of a retry. If the listener refuses the connection, a
retry will be performed, if retry_count is set.
"""
cdef:
ConnectMessage connect_message
object ssl_context
str connect_string, host, redirect_data
ConnectMessage connect_message = None
object ssl_context, connect_info
ConnectParamsImpl temp_params
Address temp_address
uint8_t packet_type
str connect_string
int port, pos
# store whether OOB processing is possible or not
self._caps.supports_oob = not params.disable_oob \
and sys.platform != "win32"
# establish initial TCP connection and get initial connect string
host = address.host
port = address.port
self._connect_tcp(params, description, address, host, port)
connect_string = _get_connect_data(address, description)
# send connect message and process response; this may request the
# message to be resent multiple times; if a redirect packet is
# detected, however, the process terminates and is restarted using the
# supplied redirect data
connect_string = _get_connect_data(address, description)
connect_message = conn_impl._create_message(ConnectMessage)
connect_message.address = address
connect_message.description = description
connect_message.connect_string_bytes = connect_string.encode()
connect_message.connect_string_len = \
<uint16_t> len(connect_message.connect_string_bytes)
# detected, a new TCP connection is established first
while True:
# create connection message, if needed
if connect_message is None:
connect_message = conn_impl._create_message(ConnectMessage)
connect_message.host = host
connect_message.port = port
connect_message.description = description
connect_message.connect_string_bytes = connect_string.encode()
connect_message.connect_string_len = \
<uint16_t> len(connect_message.connect_string_bytes)
# process connection message
self._process_message(connect_message)
if connect_message.redirect_data is not None:
params = params.copy()
params._default_description = description
params._default_address = address
params._process_redirect_data(connect_message.redirect_data)
return params
if connect_message.packet_type == TNS_PACKET_TYPE_ACCEPT:
redirect_data = connect_message.redirect_data
pos = redirect_data.find('\x00')
if pos < 0:
errors._raise_err(errors.ERR_INVALID_REDIRECT_DATA,
data=redirect_data)
temp_params = ConnectParamsImpl()
temp_params._parse_connect_string(redirect_data[:pos])
temp_address = temp_params._get_addresses()[0]
host = temp_address.host
port = temp_address.port
connect_string = redirect_data[pos + 1:]
self._connect_tcp(params, description, address, host, port)
connect_message = None
elif connect_message.packet_type == TNS_PACKET_TYPE_ACCEPT:
break
# for TCPS connections, OOB processing is not supported; if a
# wallet is required, the socket must be rewrapped in order to
# be usable; disable the check on the host name, however, as that
# has already been done successfully
# for TCPS connections, OOB processing is not supported; if the
# packet flags indicate that TLS renegotiation is required, this is
# performed now
if address.protocol == "tcps":
self._caps.supports_oob = False
if description.wallet_location is not None:
if connect_message.packet_flags & TNS_PACKET_FLAG_TLS_RENEG:
ssl_context = self._socket.context
ssl_context.check_hostname = False
sock = socket.socket(fileno=self._socket.detach())
sock = ssl_context.wrap_socket(sock)
sock = perform_tls_negotiation(sock, ssl_context,
description, address)
self._set_socket(sock)
cdef int _connect_phase_two(self, ThinConnImpl conn_impl,
@ -208,6 +229,58 @@ cdef class Protocol:
# allows the normal break/reset mechanism to fire
self._in_connect = False
cdef int _connect_tcp(self, ConnectParamsImpl params,
Description description, Address address, str host,
int port) except -1:
"""
Creates a socket on which to communicate using the provided parameters.
If a proxy is configured, a connection to the proxy is established and
the target host and port is forwarded to the proxy.
"""
cdef:
bint use_proxy = (address.https_proxy is not None)
double timeout = description.tcp_connect_timeout
object connect_info, sock, data, reply, m
# establish connection to appropriate host/port
if use_proxy:
if address.protocol != "tcps":
errors._raise_err(errors.ERR_HTTPS_PROXY_REQUIRES_TCPS)
connect_info = (address.https_proxy, address.https_proxy_port)
else:
connect_info = (host, port)
sock = socket.create_connection(connect_info, timeout)
# complete connection through proxy, if applicable
if use_proxy:
data = f"CONNECT {host}:{port} HTTP/1.0\r\n\r\n"
sock.send(data.encode())
reply = sock.recv(1024)
m = re.search('HTTP/1.[01]\\s+(\\d+)\\s+', reply.decode())
if m is None or m.groups()[0] != '200':
errors._raise_err(errors.ERR_PROXY_FAILURE,
response=reply.decode())
# set socket options
if description.expire_time > 0:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
if hasattr(socket, "TCP_KEEPIDLE") \
and hasattr(socket, "TCP_KEEPINTVL") \
and hasattr(socket, "TCP_KEEPCNT"):
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,
description.expire_time * 60)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 6)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 10)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
sock.settimeout(None)
# establish TLS connection, if applicable
if address.protocol == "tcps":
sock = get_ssl_socket(sock, params, description, address)
# save final socket object
self._set_socket(sock)
cdef int _final_close(self, WriteBuffer buf) except -1:
"""
Send the final close packet to the server and close the socket.
@ -306,7 +379,7 @@ cdef class Protocol:
cdef int _receive_packet(self, Message message) except -1:
cdef ReadBuffer buf = self._read_buf
buf.receive_packet(&message.packet_type)
buf.receive_packet(&message.packet_type, &message.packet_flags)
if message.packet_type == TNS_PACKET_TYPE_MARKER:
self._reset(message)
elif message.packet_type == TNS_PACKET_TYPE_REFUSE:
@ -342,7 +415,8 @@ cdef class Protocol:
self._read_buf.read_ub1(&marker_type)
if marker_type == TNS_MARKER_TYPE_RESET:
break
self._read_buf.receive_packet(&message.packet_type)
self._read_buf.receive_packet(&message.packet_type,
&message.packet_flags)
# read error packet; first skip as many marker packets as may be sent
# by the server; if the server doesn't handle out-of-band breaks
@ -350,7 +424,8 @@ cdef class Protocol:
# markers (this addresses both situations without resulting in strange
# errors)
while message.packet_type == TNS_PACKET_TYPE_MARKER:
self._read_buf.receive_packet(&message.packet_type)
self._read_buf.receive_packet(&message.packet_type,
&message.packet_flags)
self._break_in_progress = False
cdef int _send_marker(self, WriteBuffer buf, uint8_t marker_type):

View File

@ -26,7 +26,8 @@
# version.py
#
# Defines the version of the package. This is the only place where this is
# found. The setup.cfg configuration file references this file directly.
# found. The setup.cfg configuration file and the documentation configuration
# file doc/src/conf.py both reference this file directly.
#------------------------------------------------------------------------------
__version__ = "1.0.1"
__version__ = "1.0.3"

View File

@ -286,5 +286,29 @@ class TestCase(test_env.BaseTestCase):
end;""", data)
self.assertEqual(out_bind.values, [5, 8, 17, 24, 6])
def test_4020_executemany_with_plsql_single_row(self):
"4020 - test PL/SQL statement with single row bind"
value = 4020
var = self.cursor.var(int)
data = [[var, value]]
self.cursor.executemany("begin :1 := :2; end;", data)
self.assertEqual(var.values, [value])
def test_4021_defer_type_assignment(self):
"4021 - test deferral of type assignment"
self.cursor.execute("truncate table TestTempTable")
data = [(1, None), (2, 25)]
self.cursor.executemany("""
insert into TestTempTable
(IntCol, NumberCol)
values (:1, :2)""", data)
self.connection.commit()
self.cursor.execute("""
select IntCol, NumberCol
from TestTempTable
order by IntCol""")
fetched_data = self.cursor.fetchall()
self.assertEqual(data, fetched_data)
if __name__ == "__main__":
test_env.run_test_cases()

View File

@ -784,5 +784,23 @@ class TestCase(test_env.BaseTestCase):
self.assertEqual(cursor.rowcount, 1)
self.assertEqual(cursor.rowcount, -1)
def test_4362_change_of_bind_type_with_define(self):
"4362 - changing bind type with define needed"
self.cursor.execute("truncate table TestClobs")
row_for_1 = (1, "Short value 1")
row_for_56 = (56, "Short value 56")
for data in (row_for_1, row_for_56):
self.cursor.execute("""
insert into TestClobs (IntCol, ClobCol)
values (:1, :2)""", data)
sql = "select IntCol, ClobCol from TestClobs where IntCol = :int_col"
with test_env.FetchLobsContextManager(False):
self.cursor.execute(sql, int_col="1")
self.assertEqual(self.cursor.fetchone(), row_for_1)
self.cursor.execute(sql, int_col="56")
self.assertEqual(self.cursor.fetchone(), row_for_56)
self.cursor.execute(sql, int_col=1)
self.assertEqual(self.cursor.fetchone(), row_for_1)
if __name__ == "__main__":
test_env.run_test_cases()

View File

@ -57,12 +57,9 @@ class ConnectParams:
"""
@functools.wraps(f)
def wrapped(self):
output = []
for description in self._impl.description_list.descriptions:
for address_list in description.address_lists:
for address in address_list.addresses:
output.append(getattr(address, f.__name__))
return output if len(output) > 1 else output[0]
values = [getattr(a, f.__name__) \
for a in self._impl._get_addresses()]
return values if len(values) > 1 else values[0]
return wrapped
def _description_attr(f):
@ -71,10 +68,9 @@ class ConnectParams:
"""
@functools.wraps(f)
def wrapped(self):
output = []
for description in self._impl.description_list.descriptions:
output.append(getattr(description, f.__name__))
return output if len(output) > 1 else output[0]
values = [getattr(d, f.__name__) \
for d in self._impl.description_list.descriptions]
return values if len(values) > 1 else values[0]
return wrapped
#{{ properties }}