Compare commits
20 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
3140a83cd0 | |
![]() |
d297f8b532 | |
![]() |
3a9a296cb9 | |
![]() |
c6f6b2077f | |
![]() |
0e3b1507eb | |
![]() |
4c86e20c73 | |
![]() |
213d535acf | |
![]() |
65bbb201f5 | |
![]() |
4b79df832a | |
![]() |
80e53852ff | |
![]() |
c3de1a098f | |
![]() |
eaa1851f42 | |
![]() |
aa1135093f | |
![]() |
dd99c40a37 | |
![]() |
2f740ee06a | |
![]() |
53b041afe6 | |
![]() |
1eb26ac8f0 | |
![]() |
a7023bf1e6 | |
![]() |
52c8c4df4b | |
![]() |
ed56b22ffd |
|
@ -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:
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
sphinx==4.1.2
|
||||
sphinx>=4.2.0
|
||||
sphinx-rtd-theme
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
# ------------------------
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
===============================
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::
|
||||
|
||||
|
|
|
@ -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>`__.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
8
setup.py
8
setup.py
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
cdef int _connect_with_address(self, Address address,
|
||||
Description description,
|
||||
ConnectParamsImpl params,
|
||||
bint final_desc):
|
||||
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,
|
||||
self._connect_with_address(address, description, params,
|
||||
raise_exc)
|
||||
if redirect_params is not None:
|
||||
return redirect_params
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -46,7 +46,6 @@ 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._statement = None
|
||||
|
||||
|
@ -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,7 +187,6 @@ 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._statement = None
|
||||
self._statement = self._conn_impl._get_statement(sql.strip(),
|
||||
|
|
|
@ -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,11 +1656,16 @@ 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:
|
||||
if self.resend:
|
||||
stmt._requires_define = False
|
||||
else:
|
||||
stmt._requires_full_execute = True
|
||||
self.resend = True
|
||||
|
||||
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
cdef int _connect_phase_one(self,
|
||||
ThinConnImpl conn_impl,
|
||||
ConnectParamsImpl params,
|
||||
Description description,
|
||||
Address address):
|
||||
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)
|
||||
# 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.address = address
|
||||
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)
|
||||
while True:
|
||||
|
||||
# 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):
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 }}
|
||||
|
|
Loading…
Reference in New Issue