This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
# dialects/oracle/__init__.py
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# oracle/__init__.py
|
||||
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
# mypy: ignore-errors
|
||||
from types import ModuleType
|
||||
|
||||
|
||||
from . import base # noqa
|
||||
from . import cx_oracle # noqa
|
||||
@@ -32,18 +32,7 @@ from .base import ROWID
|
||||
from .base import TIMESTAMP
|
||||
from .base import VARCHAR
|
||||
from .base import VARCHAR2
|
||||
from .base import VECTOR
|
||||
from .base import VectorIndexConfig
|
||||
from .base import VectorIndexType
|
||||
from .vector import SparseVector
|
||||
from .vector import VectorDistanceType
|
||||
from .vector import VectorStorageFormat
|
||||
from .vector import VectorStorageType
|
||||
|
||||
# Alias oracledb also as oracledb_async
|
||||
oracledb_async = type(
|
||||
"oracledb_async", (ModuleType,), {"dialect": oracledb.dialect_async}
|
||||
)
|
||||
|
||||
base.dialect = dialect = cx_oracle.dialect
|
||||
|
||||
@@ -71,11 +60,4 @@ __all__ = (
|
||||
"NVARCHAR2",
|
||||
"ROWID",
|
||||
"REAL",
|
||||
"VECTOR",
|
||||
"VectorDistanceType",
|
||||
"VectorIndexType",
|
||||
"VectorIndexConfig",
|
||||
"VectorStorageFormat",
|
||||
"VectorStorageType",
|
||||
"SparseVector",
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
||||
# dialects/oracle/cx_oracle.py
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -7,18 +6,13 @@
|
||||
# mypy: ignore-errors
|
||||
|
||||
|
||||
r""".. dialect:: oracle+cx_oracle
|
||||
r"""
|
||||
.. dialect:: oracle+cx_oracle
|
||||
:name: cx-Oracle
|
||||
:dbapi: cx_oracle
|
||||
:connectstring: oracle+cx_oracle://user:pass@hostname:port[/dbname][?service_name=<service>[&key=value&key=value...]]
|
||||
:url: https://oracle.github.io/python-cx_Oracle/
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
cx_Oracle was the original driver for Oracle Database. It was superseded by
|
||||
python-oracledb which should be used instead.
|
||||
|
||||
DSN vs. Hostname connections
|
||||
-----------------------------
|
||||
|
||||
@@ -28,41 +22,27 @@ dialect translates from a series of different URL forms.
|
||||
Hostname Connections with Easy Connect Syntax
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Given a hostname, port and service name of the target database, for example
|
||||
from Oracle Database's Easy Connect syntax then connect in SQLAlchemy using the
|
||||
``service_name`` query string parameter::
|
||||
Given a hostname, port and service name of the target Oracle Database, for
|
||||
example from Oracle's `Easy Connect syntax
|
||||
<https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#easy-connect-syntax-for-connection-strings>`_,
|
||||
then connect in SQLAlchemy using the ``service_name`` query string parameter::
|
||||
|
||||
engine = create_engine(
|
||||
"oracle+cx_oracle://scott:tiger@hostname:port?service_name=myservice&encoding=UTF-8&nencoding=UTF-8"
|
||||
)
|
||||
engine = create_engine("oracle+cx_oracle://scott:tiger@hostname:port/?service_name=myservice&encoding=UTF-8&nencoding=UTF-8")
|
||||
|
||||
Note that the default driver value for encoding and nencoding was changed to
|
||||
“UTF-8” in cx_Oracle 8.0 so these parameters can be omitted when using that
|
||||
version, or later.
|
||||
The `full Easy Connect syntax
|
||||
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-B0437826-43C1-49EC-A94D-B650B6A4A6EE>`_
|
||||
is not supported. Instead, use a ``tnsnames.ora`` file and connect using a
|
||||
DSN.
|
||||
|
||||
To use a full Easy Connect string, pass it as the ``dsn`` key value in a
|
||||
:paramref:`_sa.create_engine.connect_args` dictionary::
|
||||
Connections with tnsnames.ora or Oracle Cloud
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
import cx_Oracle
|
||||
|
||||
e = create_engine(
|
||||
"oracle+cx_oracle://@",
|
||||
connect_args={
|
||||
"user": "scott",
|
||||
"password": "tiger",
|
||||
"dsn": "hostname:port/myservice?transport_connect_timeout=30&expire_time=60",
|
||||
},
|
||||
)
|
||||
|
||||
Connections with tnsnames.ora or to Oracle Autonomous Database
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Alternatively, if no port, database name, or service name is provided, the
|
||||
dialect will use an Oracle Database DSN "connection string". This takes the
|
||||
"hostname" portion of the URL as the data source name. For example, if the
|
||||
``tnsnames.ora`` file contains a TNS Alias of ``myalias`` as below:
|
||||
|
||||
.. sourcecode:: text
|
||||
Alternatively, if no port, database name, or ``service_name`` is provided, the
|
||||
dialect will use an Oracle DSN "connection string". This takes the "hostname"
|
||||
portion of the URL as the data source name. For example, if the
|
||||
``tnsnames.ora`` file contains a `Net Service Name
|
||||
<https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#net-service-names-for-connection-strings>`_
|
||||
of ``myalias`` as below::
|
||||
|
||||
myalias =
|
||||
(DESCRIPTION =
|
||||
@@ -77,22 +57,19 @@ The cx_Oracle dialect connects to this database service when ``myalias`` is the
|
||||
hostname portion of the URL, without specifying a port, database name or
|
||||
``service_name``::
|
||||
|
||||
engine = create_engine("oracle+cx_oracle://scott:tiger@myalias")
|
||||
engine = create_engine("oracle+cx_oracle://scott:tiger@myalias/?encoding=UTF-8&nencoding=UTF-8")
|
||||
|
||||
Users of Oracle Autonomous Database should use this syntax. If the database is
|
||||
configured for mutural TLS ("mTLS"), then you must also configure the cloud
|
||||
Users of Oracle Cloud should use this syntax and also configure the cloud
|
||||
wallet as shown in cx_Oracle documentation `Connecting to Autononmous Databases
|
||||
<https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#autonomousdb>`_.
|
||||
<https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#connecting-to-autononmous-databases>`_.
|
||||
|
||||
SID Connections
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
To use Oracle Database's obsolete System Identifier connection syntax, the SID
|
||||
can be passed in a "database name" portion of the URL::
|
||||
To use Oracle's obsolete SID connection syntax, the SID can be passed in a
|
||||
"database name" portion of the URL as below::
|
||||
|
||||
engine = create_engine(
|
||||
"oracle+cx_oracle://scott:tiger@hostname:port/dbname"
|
||||
)
|
||||
engine = create_engine("oracle+cx_oracle://scott:tiger@hostname:1521/dbname?encoding=UTF-8&nencoding=UTF-8")
|
||||
|
||||
Above, the DSN passed to cx_Oracle is created by ``cx_Oracle.makedsn()`` as
|
||||
follows::
|
||||
@@ -101,23 +78,17 @@ follows::
|
||||
>>> cx_Oracle.makedsn("hostname", 1521, sid="dbname")
|
||||
'(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=hostname)(PORT=1521))(CONNECT_DATA=(SID=dbname)))'
|
||||
|
||||
Note that although the SQLAlchemy syntax ``hostname:port/dbname`` looks like
|
||||
Oracle's Easy Connect syntax it is different. It uses a SID in place of the
|
||||
service name required by Easy Connect. The Easy Connect syntax does not
|
||||
support SIDs.
|
||||
|
||||
Passing cx_Oracle connect arguments
|
||||
-----------------------------------
|
||||
|
||||
Additional connection arguments can usually be passed via the URL query string;
|
||||
particular symbols like ``SYSDBA`` are intercepted and converted to the correct
|
||||
symbol::
|
||||
Additional connection arguments can usually be passed via the URL
|
||||
query string; particular symbols like ``cx_Oracle.SYSDBA`` are intercepted
|
||||
and converted to the correct symbol::
|
||||
|
||||
e = create_engine(
|
||||
"oracle+cx_oracle://user:pass@dsn?encoding=UTF-8&nencoding=UTF-8&mode=SYSDBA&events=true"
|
||||
)
|
||||
"oracle+cx_oracle://user:pass@dsn?encoding=UTF-8&nencoding=UTF-8&mode=SYSDBA&events=true")
|
||||
|
||||
.. versionchanged:: 1.3 the cx_Oracle dialect now accepts all argument names
|
||||
.. versionchanged:: 1.3 the cx_oracle dialect now accepts all argument names
|
||||
within the URL string itself, to be passed to the cx_Oracle DBAPI. As
|
||||
was the case earlier but not correctly documented, the
|
||||
:paramref:`_sa.create_engine.connect_args` parameter also accepts all
|
||||
@@ -128,20 +99,19 @@ string, use the :paramref:`_sa.create_engine.connect_args` dictionary.
|
||||
Any cx_Oracle parameter value and/or constant may be passed, such as::
|
||||
|
||||
import cx_Oracle
|
||||
|
||||
e = create_engine(
|
||||
"oracle+cx_oracle://user:pass@dsn",
|
||||
connect_args={
|
||||
"encoding": "UTF-8",
|
||||
"nencoding": "UTF-8",
|
||||
"mode": cx_Oracle.SYSDBA,
|
||||
"events": True,
|
||||
},
|
||||
"events": True
|
||||
}
|
||||
)
|
||||
|
||||
Note that the default driver value for ``encoding`` and ``nencoding`` was
|
||||
changed to "UTF-8" in cx_Oracle 8.0 so these parameters can be omitted when
|
||||
using that version, or later.
|
||||
Note that the default value for ``encoding`` and ``nencoding`` was changed to
|
||||
"UTF-8" in cx_Oracle 8.0 so these parameters can be omitted when using that
|
||||
version, or later.
|
||||
|
||||
Options consumed by the SQLAlchemy cx_Oracle dialect outside of the driver
|
||||
--------------------------------------------------------------------------
|
||||
@@ -151,19 +121,14 @@ itself. These options are always passed directly to :func:`_sa.create_engine`
|
||||
, such as::
|
||||
|
||||
e = create_engine(
|
||||
"oracle+cx_oracle://user:pass@dsn", coerce_to_decimal=False
|
||||
)
|
||||
"oracle+cx_oracle://user:pass@dsn", coerce_to_decimal=False)
|
||||
|
||||
The parameters accepted by the cx_oracle dialect are as follows:
|
||||
|
||||
* ``arraysize`` - set the cx_oracle.arraysize value on cursors; defaults
|
||||
to ``None``, indicating that the driver default should be used (typically
|
||||
the value is 100). This setting controls how many rows are buffered when
|
||||
fetching rows, and can have a significant effect on performance when
|
||||
modified.
|
||||
|
||||
.. versionchanged:: 2.0.26 - changed the default value from 50 to None,
|
||||
to use the default value of the driver itself.
|
||||
* ``arraysize`` - set the cx_oracle.arraysize value on cursors, defaulted
|
||||
to 50. This setting is significant with cx_Oracle as the contents of LOB
|
||||
objects are only readable within a "live" row (e.g. within a batch of
|
||||
50 rows).
|
||||
|
||||
* ``auto_convert_lobs`` - defaults to True; See :ref:`cx_oracle_lob`.
|
||||
|
||||
@@ -176,16 +141,10 @@ The parameters accepted by the cx_oracle dialect are as follows:
|
||||
Using cx_Oracle SessionPool
|
||||
---------------------------
|
||||
|
||||
The cx_Oracle driver provides its own connection pool implementation that may
|
||||
be used in place of SQLAlchemy's pooling functionality. The driver pool
|
||||
supports Oracle Database features such dead connection detection, connection
|
||||
draining for planned database downtime, support for Oracle Application
|
||||
Continuity and Transparent Application Continuity, and gives support for
|
||||
Database Resident Connection Pooling (DRCP).
|
||||
|
||||
Using the driver pool can be achieved by using the
|
||||
:paramref:`_sa.create_engine.creator` parameter to provide a function that
|
||||
returns a new connection, along with setting
|
||||
The cx_Oracle library provides its own connection pool implementation that may
|
||||
be used in place of SQLAlchemy's pooling functionality. This can be achieved
|
||||
by using the :paramref:`_sa.create_engine.creator` parameter to provide a
|
||||
function that returns a new connection, along with setting
|
||||
:paramref:`_sa.create_engine.pool_class` to ``NullPool`` to disable
|
||||
SQLAlchemy's pooling::
|
||||
|
||||
@@ -194,41 +153,32 @@ SQLAlchemy's pooling::
|
||||
from sqlalchemy.pool import NullPool
|
||||
|
||||
pool = cx_Oracle.SessionPool(
|
||||
user="scott",
|
||||
password="tiger",
|
||||
dsn="orclpdb",
|
||||
min=1,
|
||||
max=4,
|
||||
increment=1,
|
||||
threaded=True,
|
||||
encoding="UTF-8",
|
||||
nencoding="UTF-8",
|
||||
user="scott", password="tiger", dsn="orclpdb",
|
||||
min=2, max=5, increment=1, threaded=True,
|
||||
encoding="UTF-8", nencoding="UTF-8"
|
||||
)
|
||||
|
||||
engine = create_engine(
|
||||
"oracle+cx_oracle://", creator=pool.acquire, poolclass=NullPool
|
||||
)
|
||||
engine = create_engine("oracle+cx_oracle://", creator=pool.acquire, poolclass=NullPool)
|
||||
|
||||
The above engine may then be used normally where cx_Oracle's pool handles
|
||||
connection pooling::
|
||||
|
||||
with engine.connect() as conn:
|
||||
print(conn.scalar("select 1 from dual"))
|
||||
print(conn.scalar("select 1 FROM dual"))
|
||||
|
||||
|
||||
As well as providing a scalable solution for multi-user applications, the
|
||||
cx_Oracle session pool supports some Oracle features such as DRCP and
|
||||
`Application Continuity
|
||||
<https://cx-oracle.readthedocs.io/en/latest/user_guide/ha.html#application-continuity-ac>`_.
|
||||
|
||||
Note that the pool creation parameters ``threaded``, ``encoding`` and
|
||||
``nencoding`` were deprecated in later cx_Oracle releases.
|
||||
|
||||
Using Oracle Database Resident Connection Pooling (DRCP)
|
||||
--------------------------------------------------------
|
||||
|
||||
When using Oracle Database's DRCP, the best practice is to pass a connection
|
||||
class and "purity" when acquiring a connection from the SessionPool. Refer to
|
||||
the `cx_Oracle DRCP documentation
|
||||
When using Oracle's `DRCP
|
||||
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-015CA8C1-2386-4626-855D-CC546DDC1086>`_,
|
||||
the best practice is to pass a connection class and "purity" when acquiring a
|
||||
connection from the SessionPool. Refer to the `cx_Oracle DRCP documentation
|
||||
<https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#database-resident-connection-pooling-drcp>`_.
|
||||
|
||||
This can be achieved by wrapping ``pool.acquire()``::
|
||||
@@ -238,33 +188,21 @@ This can be achieved by wrapping ``pool.acquire()``::
|
||||
from sqlalchemy.pool import NullPool
|
||||
|
||||
pool = cx_Oracle.SessionPool(
|
||||
user="scott",
|
||||
password="tiger",
|
||||
dsn="orclpdb",
|
||||
min=2,
|
||||
max=5,
|
||||
increment=1,
|
||||
threaded=True,
|
||||
encoding="UTF-8",
|
||||
nencoding="UTF-8",
|
||||
user="scott", password="tiger", dsn="orclpdb",
|
||||
min=2, max=5, increment=1, threaded=True,
|
||||
encoding="UTF-8", nencoding="UTF-8"
|
||||
)
|
||||
|
||||
|
||||
def creator():
|
||||
return pool.acquire(
|
||||
cclass="MYCLASS", purity=cx_Oracle.ATTR_PURITY_SELF
|
||||
)
|
||||
return pool.acquire(cclass="MYCLASS", purity=cx_Oracle.ATTR_PURITY_SELF)
|
||||
|
||||
|
||||
engine = create_engine(
|
||||
"oracle+cx_oracle://", creator=creator, poolclass=NullPool
|
||||
)
|
||||
engine = create_engine("oracle+cx_oracle://", creator=creator, poolclass=NullPool)
|
||||
|
||||
The above engine may then be used normally where cx_Oracle handles session
|
||||
pooling and Oracle Database additionally uses DRCP::
|
||||
|
||||
with engine.connect() as conn:
|
||||
print(conn.scalar("select 1 from dual"))
|
||||
print(conn.scalar("select 1 FROM dual"))
|
||||
|
||||
.. _cx_oracle_unicode:
|
||||
|
||||
@@ -272,28 +210,24 @@ Unicode
|
||||
-------
|
||||
|
||||
As is the case for all DBAPIs under Python 3, all strings are inherently
|
||||
Unicode strings. In all cases however, the driver requires an explicit
|
||||
Unicode strings. In all cases however, the driver requires an explicit
|
||||
encoding configuration.
|
||||
|
||||
Ensuring the Correct Client Encoding
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The long accepted standard for establishing client encoding for nearly all
|
||||
Oracle Database related software is via the `NLS_LANG
|
||||
<https://www.oracle.com/database/technologies/faq-nls-lang.html>`_ environment
|
||||
variable. Older versions of cx_Oracle use this environment variable as the
|
||||
source of its encoding configuration. The format of this variable is
|
||||
Territory_Country.CharacterSet; a typical value would be
|
||||
``AMERICAN_AMERICA.AL32UTF8``. cx_Oracle version 8 and later use the character
|
||||
set "UTF-8" by default, and ignore the character set component of NLS_LANG.
|
||||
Oracle related software is via the `NLS_LANG <https://www.oracle.com/database/technologies/faq-nls-lang.html>`_
|
||||
environment variable. cx_Oracle like most other Oracle drivers will use
|
||||
this environment variable as the source of its encoding configuration. The
|
||||
format of this variable is idiosyncratic; a typical value would be
|
||||
``AMERICAN_AMERICA.AL32UTF8``.
|
||||
|
||||
The cx_Oracle driver also supported a programmatic alternative which is to pass
|
||||
the ``encoding`` and ``nencoding`` parameters directly to its ``.connect()``
|
||||
function. These can be present in the URL as follows::
|
||||
The cx_Oracle driver also supports a programmatic alternative which is to
|
||||
pass the ``encoding`` and ``nencoding`` parameters directly to its
|
||||
``.connect()`` function. These can be present in the URL as follows::
|
||||
|
||||
engine = create_engine(
|
||||
"oracle+cx_oracle://scott:tiger@tnsalias?encoding=UTF-8&nencoding=UTF-8"
|
||||
)
|
||||
engine = create_engine("oracle+cx_oracle://scott:tiger@orclpdb/?encoding=UTF-8&nencoding=UTF-8")
|
||||
|
||||
For the meaning of the ``encoding`` and ``nencoding`` parameters, please
|
||||
consult
|
||||
@@ -308,24 +242,25 @@ consult
|
||||
Unicode-specific Column datatypes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The Core expression language handles unicode data by use of the
|
||||
:class:`.Unicode` and :class:`.UnicodeText` datatypes. These types correspond
|
||||
to the VARCHAR2 and CLOB Oracle Database datatypes by default. When using
|
||||
these datatypes with Unicode data, it is expected that the database is
|
||||
configured with a Unicode-aware character set, as well as that the ``NLS_LANG``
|
||||
environment variable is set appropriately (this applies to older versions of
|
||||
cx_Oracle), so that the VARCHAR2 and CLOB datatypes can accommodate the data.
|
||||
The Core expression language handles unicode data by use of the :class:`.Unicode`
|
||||
and :class:`.UnicodeText`
|
||||
datatypes. These types correspond to the VARCHAR2 and CLOB Oracle datatypes by
|
||||
default. When using these datatypes with Unicode data, it is expected that
|
||||
the Oracle database is configured with a Unicode-aware character set, as well
|
||||
as that the ``NLS_LANG`` environment variable is set appropriately, so that
|
||||
the VARCHAR2 and CLOB datatypes can accommodate the data.
|
||||
|
||||
In the case that Oracle Database is not configured with a Unicode character
|
||||
In the case that the Oracle database is not configured with a Unicode character
|
||||
set, the two options are to use the :class:`_types.NCHAR` and
|
||||
:class:`_oracle.NCLOB` datatypes explicitly, or to pass the flag
|
||||
``use_nchar_for_unicode=True`` to :func:`_sa.create_engine`, which will cause
|
||||
the SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` /
|
||||
``use_nchar_for_unicode=True`` to :func:`_sa.create_engine`,
|
||||
which will cause the
|
||||
SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` /
|
||||
:class:`.UnicodeText` datatypes instead of VARCHAR/CLOB.
|
||||
|
||||
.. versionchanged:: 1.3 The :class:`.Unicode` and :class:`.UnicodeText`
|
||||
datatypes now correspond to the ``VARCHAR2`` and ``CLOB`` Oracle Database
|
||||
datatypes unless the ``use_nchar_for_unicode=True`` is passed to the dialect
|
||||
.. versionchanged:: 1.3 The :class:`.Unicode` and :class:`.UnicodeText`
|
||||
datatypes now correspond to the ``VARCHAR2`` and ``CLOB`` Oracle datatypes
|
||||
unless the ``use_nchar_for_unicode=True`` is passed to the dialect
|
||||
when :func:`_sa.create_engine` is called.
|
||||
|
||||
|
||||
@@ -334,7 +269,7 @@ the SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` /
|
||||
Encoding Errors
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
For the unusual case that data in Oracle Database is present with a broken
|
||||
For the unusual case that data in the Oracle database is present with a broken
|
||||
encoding, the dialect accepts a parameter ``encoding_errors`` which will be
|
||||
passed to Unicode decoding functions in order to affect how decoding errors are
|
||||
handled. The value is ultimately consumed by the Python `decode
|
||||
@@ -352,13 +287,13 @@ Fine grained control over cx_Oracle data binding performance with setinputsizes
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
The cx_Oracle DBAPI has a deep and fundamental reliance upon the usage of the
|
||||
DBAPI ``setinputsizes()`` call. The purpose of this call is to establish the
|
||||
DBAPI ``setinputsizes()`` call. The purpose of this call is to establish the
|
||||
datatypes that are bound to a SQL statement for Python values being passed as
|
||||
parameters. While virtually no other DBAPI assigns any use to the
|
||||
``setinputsizes()`` call, the cx_Oracle DBAPI relies upon it heavily in its
|
||||
interactions with the Oracle Database client interface, and in some scenarios
|
||||
it is not possible for SQLAlchemy to know exactly how data should be bound, as
|
||||
some settings can cause profoundly different performance characteristics, while
|
||||
interactions with the Oracle client interface, and in some scenarios it is not
|
||||
possible for SQLAlchemy to know exactly how data should be bound, as some
|
||||
settings can cause profoundly different performance characteristics, while
|
||||
altering the type coercion behavior at the same time.
|
||||
|
||||
Users of the cx_Oracle dialect are **strongly encouraged** to read through
|
||||
@@ -387,16 +322,13 @@ objects which have a ``.key`` and a ``.type`` attribute::
|
||||
|
||||
engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe")
|
||||
|
||||
|
||||
@event.listens_for(engine, "do_setinputsizes")
|
||||
def _log_setinputsizes(inputsizes, cursor, statement, parameters, context):
|
||||
for bindparam, dbapitype in inputsizes.items():
|
||||
log.info(
|
||||
"Bound parameter name: %s SQLAlchemy type: %r DBAPI object: %s",
|
||||
bindparam.key,
|
||||
bindparam.type,
|
||||
dbapitype,
|
||||
)
|
||||
log.info(
|
||||
"Bound parameter name: %s SQLAlchemy type: %r "
|
||||
"DBAPI object: %s",
|
||||
bindparam.key, bindparam.type, dbapitype)
|
||||
|
||||
Example 2 - remove all bindings to CLOB
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -410,28 +342,12 @@ series. This setting can be modified as follows::
|
||||
|
||||
engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe")
|
||||
|
||||
|
||||
@event.listens_for(engine, "do_setinputsizes")
|
||||
def _remove_clob(inputsizes, cursor, statement, parameters, context):
|
||||
for bindparam, dbapitype in list(inputsizes.items()):
|
||||
if dbapitype is CLOB:
|
||||
del inputsizes[bindparam]
|
||||
|
||||
.. _cx_oracle_lob:
|
||||
|
||||
LOB Datatypes
|
||||
--------------
|
||||
|
||||
LOB datatypes refer to the "large object" datatypes such as CLOB, NCLOB and
|
||||
BLOB. Modern versions of cx_Oracle is optimized for these datatypes to be
|
||||
delivered as a single buffer. As such, SQLAlchemy makes use of these newer type
|
||||
handlers by default.
|
||||
|
||||
To disable the use of newer type handlers and deliver LOB objects as classic
|
||||
buffered objects with a ``read()`` method, the parameter
|
||||
``auto_convert_lobs=False`` may be passed to :func:`_sa.create_engine`,
|
||||
which takes place only engine-wide.
|
||||
|
||||
.. _cx_oracle_returning:
|
||||
|
||||
RETURNING Support
|
||||
@@ -440,12 +356,29 @@ RETURNING Support
|
||||
The cx_Oracle dialect implements RETURNING using OUT parameters.
|
||||
The dialect supports RETURNING fully.
|
||||
|
||||
Two Phase Transactions Not Supported
|
||||
------------------------------------
|
||||
.. _cx_oracle_lob:
|
||||
|
||||
Two phase transactions are **not supported** under cx_Oracle due to poor driver
|
||||
support. The newer :ref:`oracledb` dialect however **does** support two phase
|
||||
transactions.
|
||||
LOB Datatypes
|
||||
--------------
|
||||
|
||||
LOB datatypes refer to the "large object" datatypes such as CLOB, NCLOB and
|
||||
BLOB. Modern versions of cx_Oracle and oracledb are optimized for these
|
||||
datatypes to be delivered as a single buffer. As such, SQLAlchemy makes use of
|
||||
these newer type handlers by default.
|
||||
|
||||
To disable the use of newer type handlers and deliver LOB objects as classic
|
||||
buffered objects with a ``read()`` method, the parameter
|
||||
``auto_convert_lobs=False`` may be passed to :func:`_sa.create_engine`,
|
||||
which takes place only engine-wide.
|
||||
|
||||
Two Phase Transactions Not Supported
|
||||
-------------------------------------
|
||||
|
||||
Two phase transactions are **not supported** under cx_Oracle due to poor
|
||||
driver support. As of cx_Oracle 6.0b1, the interface for
|
||||
two phase transactions has been changed to be more of a direct pass-through
|
||||
to the underlying OCI layer with less automation. The additional logic
|
||||
to support this system is not implemented in SQLAlchemy.
|
||||
|
||||
.. _cx_oracle_numeric:
|
||||
|
||||
@@ -456,21 +389,20 @@ SQLAlchemy's numeric types can handle receiving and returning values as Python
|
||||
``Decimal`` objects or float objects. When a :class:`.Numeric` object, or a
|
||||
subclass such as :class:`.Float`, :class:`_oracle.DOUBLE_PRECISION` etc. is in
|
||||
use, the :paramref:`.Numeric.asdecimal` flag determines if values should be
|
||||
coerced to ``Decimal`` upon return, or returned as float objects. To make
|
||||
matters more complicated under Oracle Database, the ``NUMBER`` type can also
|
||||
represent integer values if the "scale" is zero, so the Oracle
|
||||
Database-specific :class:`_oracle.NUMBER` type takes this into account as well.
|
||||
coerced to ``Decimal`` upon return, or returned as float objects. To make
|
||||
matters more complicated under Oracle, Oracle's ``NUMBER`` type can also
|
||||
represent integer values if the "scale" is zero, so the Oracle-specific
|
||||
:class:`_oracle.NUMBER` type takes this into account as well.
|
||||
|
||||
The cx_Oracle dialect makes extensive use of connection- and cursor-level
|
||||
"outputtypehandler" callables in order to coerce numeric values as requested.
|
||||
These callables are specific to the specific flavor of :class:`.Numeric` in
|
||||
use, as well as if no SQLAlchemy typing objects are present. There are
|
||||
observed scenarios where Oracle Database may send incomplete or ambiguous
|
||||
information about the numeric types being returned, such as a query where the
|
||||
numeric types are buried under multiple levels of subquery. The type handlers
|
||||
do their best to make the right decision in all cases, deferring to the
|
||||
underlying cx_Oracle DBAPI for all those cases where the driver can make the
|
||||
best decision.
|
||||
use, as well as if no SQLAlchemy typing objects are present. There are
|
||||
observed scenarios where Oracle may sends incomplete or ambiguous information
|
||||
about the numeric types being returned, such as a query where the numeric types
|
||||
are buried under multiple levels of subquery. The type handlers do their best
|
||||
to make the right decision in all cases, deferring to the underlying cx_Oracle
|
||||
DBAPI for all those cases where the driver can make the best decision.
|
||||
|
||||
When no typing objects are present, as when executing plain SQL strings, a
|
||||
default "outputtypehandler" is present which will generally return numeric
|
||||
@@ -882,8 +814,6 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
|
||||
out_parameters[name] = self.cursor.var(
|
||||
dbtype,
|
||||
# this is fine also in oracledb_async since
|
||||
# the driver will await the read coroutine
|
||||
outconverter=lambda value: value.read(),
|
||||
arraysize=len_params,
|
||||
)
|
||||
@@ -902,9 +832,9 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
)
|
||||
|
||||
for param in self.parameters:
|
||||
param[quoted_bind_names.get(name, name)] = (
|
||||
out_parameters[name]
|
||||
)
|
||||
param[
|
||||
quoted_bind_names.get(name, name)
|
||||
] = out_parameters[name]
|
||||
|
||||
def _generate_cursor_outputtype_handler(self):
|
||||
output_handlers = {}
|
||||
@@ -1100,7 +1030,7 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
self,
|
||||
auto_convert_lobs=True,
|
||||
coerce_to_decimal=True,
|
||||
arraysize=None,
|
||||
arraysize=50,
|
||||
encoding_errors=None,
|
||||
threaded=None,
|
||||
**kwargs,
|
||||
@@ -1234,9 +1164,6 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
with dbapi_connection.cursor() as cursor:
|
||||
cursor.execute(f"ALTER SESSION SET ISOLATION_LEVEL={level}")
|
||||
|
||||
def detect_autocommit_setting(self, dbapi_conn) -> bool:
|
||||
return bool(dbapi_conn.autocommit)
|
||||
|
||||
def _detect_decimal_char(self, connection):
|
||||
# we have the option to change this setting upon connect,
|
||||
# or just look at what it is upon connect and convert.
|
||||
@@ -1356,13 +1283,8 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
cx_Oracle.CLOB,
|
||||
cx_Oracle.NCLOB,
|
||||
):
|
||||
typ = (
|
||||
cx_Oracle.DB_TYPE_VARCHAR
|
||||
if default_type is cx_Oracle.CLOB
|
||||
else cx_Oracle.DB_TYPE_NVARCHAR
|
||||
)
|
||||
return cursor.var(
|
||||
typ,
|
||||
cx_Oracle.DB_TYPE_NVARCHAR,
|
||||
_CX_ORACLE_MAGIC_LOB_SIZE,
|
||||
cursor.arraysize,
|
||||
**dialect._cursor_var_unicode_kwargs,
|
||||
@@ -1493,6 +1415,13 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
return False
|
||||
|
||||
def create_xid(self):
|
||||
"""create a two-phase transaction ID.
|
||||
|
||||
this id will be passed to do_begin_twophase(), do_rollback_twophase(),
|
||||
do_commit_twophase(). its format is unspecified.
|
||||
|
||||
"""
|
||||
|
||||
id_ = random.randint(0, 2**128)
|
||||
return (0x1234, "%032x" % id_, "%032x" % 9)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# dialects/oracle/dictionary.py
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
||||
@@ -1,639 +1,68 @@
|
||||
# dialects/oracle/oracledb.py
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
# mypy: ignore-errors
|
||||
|
||||
r""".. dialect:: oracle+oracledb
|
||||
r"""
|
||||
.. dialect:: oracle+oracledb
|
||||
:name: python-oracledb
|
||||
:dbapi: oracledb
|
||||
:connectstring: oracle+oracledb://user:pass@hostname:port[/dbname][?service_name=<service>[&key=value&key=value...]]
|
||||
:url: https://oracle.github.io/python-oracledb/
|
||||
|
||||
Description
|
||||
-----------
|
||||
python-oracledb is released by Oracle to supersede the cx_Oracle driver.
|
||||
It is fully compatible with cx_Oracle and features both a "thin" client
|
||||
mode that requires no dependencies, as well as a "thick" mode that uses
|
||||
the Oracle Client Interface in the same way as cx_Oracle.
|
||||
|
||||
Python-oracledb is the Oracle Database driver for Python. It features a default
|
||||
"thin" client mode that requires no dependencies, and an optional "thick" mode
|
||||
that uses Oracle Client libraries. It supports SQLAlchemy features including
|
||||
two phase transactions and Asyncio.
|
||||
.. seealso::
|
||||
|
||||
Python-oracle is the renamed, updated cx_Oracle driver. Oracle is no longer
|
||||
doing any releases in the cx_Oracle namespace.
|
||||
|
||||
The SQLAlchemy ``oracledb`` dialect provides both a sync and an async
|
||||
implementation under the same dialect name. The proper version is
|
||||
selected depending on how the engine is created:
|
||||
|
||||
* calling :func:`_sa.create_engine` with ``oracle+oracledb://...`` will
|
||||
automatically select the sync version::
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
sync_engine = create_engine(
|
||||
"oracle+oracledb://scott:tiger@localhost?service_name=FREEPDB1"
|
||||
)
|
||||
|
||||
* calling :func:`_asyncio.create_async_engine` with ``oracle+oracledb://...``
|
||||
will automatically select the async version::
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
asyncio_engine = create_async_engine(
|
||||
"oracle+oracledb://scott:tiger@localhost?service_name=FREEPDB1"
|
||||
)
|
||||
|
||||
The asyncio version of the dialect may also be specified explicitly using the
|
||||
``oracledb_async`` suffix::
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
asyncio_engine = create_async_engine(
|
||||
"oracle+oracledb_async://scott:tiger@localhost?service_name=FREEPDB1"
|
||||
)
|
||||
|
||||
.. versionadded:: 2.0.25 added support for the async version of oracledb.
|
||||
:ref:`cx_oracle` - all of cx_Oracle's notes apply to the oracledb driver
|
||||
as well.
|
||||
|
||||
Thick mode support
|
||||
------------------
|
||||
|
||||
By default, the python-oracledb driver runs in a "thin" mode that does not
|
||||
require Oracle Client libraries to be installed. The driver also supports a
|
||||
"thick" mode that uses Oracle Client libraries to get functionality such as
|
||||
Oracle Application Continuity.
|
||||
By default the ``python-oracledb`` is started in thin mode, that does not
|
||||
require oracle client libraries to be installed in the system. The
|
||||
``python-oracledb`` driver also support a "thick" mode, that behaves
|
||||
similarly to ``cx_oracle`` and requires that Oracle Client Interface (OCI)
|
||||
is installed.
|
||||
|
||||
To enable thick mode, call `oracledb.init_oracle_client()
|
||||
<https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.init_oracle_client>`_
|
||||
explicitly, or pass the parameter ``thick_mode=True`` to
|
||||
:func:`_sa.create_engine`. To pass custom arguments to
|
||||
``init_oracle_client()``, like the ``lib_dir`` path, a dict may be passed, for
|
||||
example::
|
||||
To enable this mode, the user may call ``oracledb.init_oracle_client``
|
||||
manually, or by passing the parameter ``thick_mode=True`` to
|
||||
:func:`_sa.create_engine`. To pass custom arguments to ``init_oracle_client``,
|
||||
like the ``lib_dir`` path, a dict may be passed to this parameter, as in::
|
||||
|
||||
engine = sa.create_engine(
|
||||
"oracle+oracledb://...",
|
||||
thick_mode={
|
||||
"lib_dir": "/path/to/oracle/client/lib",
|
||||
"config_dir": "/path/to/network_config_file_directory",
|
||||
"driver_name": "my-app : 1.0.0",
|
||||
},
|
||||
)
|
||||
|
||||
Note that passing a ``lib_dir`` path should only be done on macOS or
|
||||
Windows. On Linux it does not behave as you might expect.
|
||||
engine = sa.create_engine("oracle+oracledb://...", thick_mode={
|
||||
"lib_dir": "/path/to/oracle/client/lib", "driver_name": "my-app"
|
||||
})
|
||||
|
||||
.. seealso::
|
||||
|
||||
python-oracledb documentation `Enabling python-oracledb Thick mode
|
||||
<https://python-oracledb.readthedocs.io/en/latest/user_guide/initialization.html#enabling-python-oracledb-thick-mode>`_
|
||||
https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.init_oracle_client
|
||||
|
||||
Connecting to Oracle Database
|
||||
-----------------------------
|
||||
|
||||
python-oracledb provides several methods of indicating the target database.
|
||||
The dialect translates from a series of different URL forms.
|
||||
|
||||
Given the hostname, port and service name of the target database, you can
|
||||
connect in SQLAlchemy using the ``service_name`` query string parameter::
|
||||
|
||||
engine = create_engine(
|
||||
"oracle+oracledb://scott:tiger@hostname:port?service_name=myservice"
|
||||
)
|
||||
|
||||
Connecting with Easy Connect strings
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can pass any valid python-oracledb connection string as the ``dsn`` key
|
||||
value in a :paramref:`_sa.create_engine.connect_args` dictionary. See
|
||||
python-oracledb documentation `Oracle Net Services Connection Strings
|
||||
<https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#oracle-net-services-connection-strings>`_.
|
||||
|
||||
For example to use an `Easy Connect string
|
||||
<https://download.oracle.com/ocomdocs/global/Oracle-Net-Easy-Connect-Plus.pdf>`_
|
||||
with a timeout to prevent connection establishment from hanging if the network
|
||||
transport to the database cannot be establishd in 30 seconds, and also setting
|
||||
a keep-alive time of 60 seconds to stop idle network connections from being
|
||||
terminated by a firewall::
|
||||
|
||||
e = create_engine(
|
||||
"oracle+oracledb://@",
|
||||
connect_args={
|
||||
"user": "scott",
|
||||
"password": "tiger",
|
||||
"dsn": "hostname:port/myservice?transport_connect_timeout=30&expire_time=60",
|
||||
},
|
||||
)
|
||||
|
||||
The Easy Connect syntax has been enhanced during the life of Oracle Database.
|
||||
Review the documentation for your database version. The current documentation
|
||||
is at `Understanding the Easy Connect Naming Method
|
||||
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-B0437826-43C1-49EC-A94D-B650B6A4A6EE>`_.
|
||||
|
||||
The general syntax is similar to:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
[[protocol:]//]host[:port][/[service_name]][?parameter_name=value{¶meter_name=value}]
|
||||
|
||||
Note that although the SQLAlchemy URL syntax ``hostname:port/dbname`` looks
|
||||
like Oracle's Easy Connect syntax, it is different. SQLAlchemy's URL requires a
|
||||
system identifier (SID) for the ``dbname`` component::
|
||||
|
||||
engine = create_engine("oracle+oracledb://scott:tiger@hostname:port/sid")
|
||||
|
||||
Easy Connect syntax does not support SIDs. It uses services names, which are
|
||||
the preferred choice for connecting to Oracle Database.
|
||||
|
||||
Passing python-oracledb connect arguments
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Other python-oracledb driver `connection options
|
||||
<https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.connect>`_
|
||||
can be passed in ``connect_args``. For example::
|
||||
|
||||
e = create_engine(
|
||||
"oracle+oracledb://@",
|
||||
connect_args={
|
||||
"user": "scott",
|
||||
"password": "tiger",
|
||||
"dsn": "hostname:port/myservice",
|
||||
"events": True,
|
||||
"mode": oracledb.AUTH_MODE_SYSDBA,
|
||||
},
|
||||
)
|
||||
|
||||
Connecting with tnsnames.ora TNS aliases
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If no port, database name, or service name is provided, the dialect will use an
|
||||
Oracle Database DSN "connection string". This takes the "hostname" portion of
|
||||
the URL as the data source name. For example, if the ``tnsnames.ora`` file
|
||||
contains a `TNS Alias
|
||||
<https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#tns-aliases-for-connection-strings>`_
|
||||
of ``myalias`` as below:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
myalias =
|
||||
(DESCRIPTION =
|
||||
(ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.example.com)(PORT = 1521))
|
||||
(CONNECT_DATA =
|
||||
(SERVER = DEDICATED)
|
||||
(SERVICE_NAME = orclpdb1)
|
||||
)
|
||||
)
|
||||
|
||||
The python-oracledb dialect connects to this database service when ``myalias`` is the
|
||||
hostname portion of the URL, without specifying a port, database name or
|
||||
``service_name``::
|
||||
|
||||
engine = create_engine("oracle+oracledb://scott:tiger@myalias")
|
||||
|
||||
Connecting to Oracle Autonomous Database
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Users of Oracle Autonomous Database should use either use the TNS Alias URL
|
||||
shown above, or pass the TNS Alias as the ``dsn`` key value in a
|
||||
:paramref:`_sa.create_engine.connect_args` dictionary.
|
||||
|
||||
If Oracle Autonomous Database is configured for mutual TLS ("mTLS")
|
||||
connections, then additional configuration is required as shown in `Connecting
|
||||
to Oracle Cloud Autonomous Databases
|
||||
<https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#connecting-to-oracle-cloud-autonomous-databases>`_. In
|
||||
summary, Thick mode users should configure file locations and set the wallet
|
||||
path in ``sqlnet.ora`` appropriately::
|
||||
|
||||
e = create_engine(
|
||||
"oracle+oracledb://@",
|
||||
thick_mode={
|
||||
# directory containing tnsnames.ora and cwallet.so
|
||||
"config_dir": "/opt/oracle/wallet_dir",
|
||||
},
|
||||
connect_args={
|
||||
"user": "scott",
|
||||
"password": "tiger",
|
||||
"dsn": "mydb_high",
|
||||
},
|
||||
)
|
||||
|
||||
Thin mode users of mTLS should pass the appropriate directories and PEM wallet
|
||||
password when creating the engine, similar to::
|
||||
|
||||
e = create_engine(
|
||||
"oracle+oracledb://@",
|
||||
connect_args={
|
||||
"user": "scott",
|
||||
"password": "tiger",
|
||||
"dsn": "mydb_high",
|
||||
"config_dir": "/opt/oracle/wallet_dir", # directory containing tnsnames.ora
|
||||
"wallet_location": "/opt/oracle/wallet_dir", # directory containing ewallet.pem
|
||||
"wallet_password": "top secret", # password for the PEM file
|
||||
},
|
||||
)
|
||||
|
||||
Typically ``config_dir`` and ``wallet_location`` are the same directory, which
|
||||
is where the Oracle Autonomous Database wallet zip file was extracted. Note
|
||||
this directory should be protected.
|
||||
|
||||
Connection Pooling
|
||||
------------------
|
||||
|
||||
Applications with multiple concurrent users should use connection pooling. A
|
||||
minimal sized connection pool is also beneficial for long-running, single-user
|
||||
applications that do not frequently use a connection.
|
||||
|
||||
The python-oracledb driver provides its own connection pool implementation that
|
||||
may be used in place of SQLAlchemy's pooling functionality. The driver pool
|
||||
gives support for high availability features such as dead connection detection,
|
||||
connection draining for planned database downtime, support for Oracle
|
||||
Application Continuity and Transparent Application Continuity, and gives
|
||||
support for `Database Resident Connection Pooling (DRCP)
|
||||
<https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#database-resident-connection-pooling-drcp>`_.
|
||||
|
||||
To take advantage of python-oracledb's pool, use the
|
||||
:paramref:`_sa.create_engine.creator` parameter to provide a function that
|
||||
returns a new connection, along with setting
|
||||
:paramref:`_sa.create_engine.pool_class` to ``NullPool`` to disable
|
||||
SQLAlchemy's pooling::
|
||||
|
||||
import oracledb
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.pool import NullPool
|
||||
|
||||
# Uncomment to use the optional python-oracledb Thick mode.
|
||||
# Review the python-oracledb doc for the appropriate parameters
|
||||
# oracledb.init_oracle_client(<your parameters>)
|
||||
|
||||
pool = oracledb.create_pool(
|
||||
user="scott",
|
||||
password="tiger",
|
||||
dsn="localhost:1521/freepdb1",
|
||||
min=1,
|
||||
max=4,
|
||||
increment=1,
|
||||
)
|
||||
engine = create_engine(
|
||||
"oracle+oracledb://", creator=pool.acquire, poolclass=NullPool
|
||||
)
|
||||
|
||||
The above engine may then be used normally. Internally, python-oracledb handles
|
||||
connection pooling::
|
||||
|
||||
with engine.connect() as conn:
|
||||
print(conn.scalar(text("select 1 from dual")))
|
||||
|
||||
Refer to the python-oracledb documentation for `oracledb.create_pool()
|
||||
<https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.create_pool>`_
|
||||
for the arguments that can be used when creating a connection pool.
|
||||
|
||||
.. _drcp:
|
||||
|
||||
Using Oracle Database Resident Connection Pooling (DRCP)
|
||||
--------------------------------------------------------
|
||||
|
||||
When using Oracle Database's Database Resident Connection Pooling (DRCP), the
|
||||
best practice is to specify a connection class and "purity". Refer to the
|
||||
`python-oracledb documentation on DRCP
|
||||
<https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#database-resident-connection-pooling-drcp>`_.
|
||||
For example::
|
||||
|
||||
import oracledb
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.pool import NullPool
|
||||
|
||||
# Uncomment to use the optional python-oracledb Thick mode.
|
||||
# Review the python-oracledb doc for the appropriate parameters
|
||||
# oracledb.init_oracle_client(<your parameters>)
|
||||
|
||||
pool = oracledb.create_pool(
|
||||
user="scott",
|
||||
password="tiger",
|
||||
dsn="localhost:1521/freepdb1",
|
||||
min=1,
|
||||
max=4,
|
||||
increment=1,
|
||||
cclass="MYCLASS",
|
||||
purity=oracledb.PURITY_SELF,
|
||||
)
|
||||
engine = create_engine(
|
||||
"oracle+oracledb://", creator=pool.acquire, poolclass=NullPool
|
||||
)
|
||||
|
||||
The above engine may then be used normally where python-oracledb handles
|
||||
application connection pooling and Oracle Database additionally uses DRCP::
|
||||
|
||||
with engine.connect() as conn:
|
||||
print(conn.scalar(text("select 1 from dual")))
|
||||
|
||||
If you wish to use different connection classes or purities for different
|
||||
connections, then wrap ``pool.acquire()``::
|
||||
|
||||
import oracledb
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.pool import NullPool
|
||||
|
||||
# Uncomment to use python-oracledb Thick mode.
|
||||
# Review the python-oracledb doc for the appropriate parameters
|
||||
# oracledb.init_oracle_client(<your parameters>)
|
||||
|
||||
pool = oracledb.create_pool(
|
||||
user="scott",
|
||||
password="tiger",
|
||||
dsn="localhost:1521/freepdb1",
|
||||
min=1,
|
||||
max=4,
|
||||
increment=1,
|
||||
cclass="MYCLASS",
|
||||
purity=oracledb.PURITY_SELF,
|
||||
)
|
||||
|
||||
|
||||
def creator():
|
||||
return pool.acquire(cclass="MYOTHERCLASS", purity=oracledb.PURITY_NEW)
|
||||
|
||||
|
||||
engine = create_engine(
|
||||
"oracle+oracledb://", creator=creator, poolclass=NullPool
|
||||
)
|
||||
|
||||
Engine Options consumed by the SQLAlchemy oracledb dialect outside of the driver
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
There are also options that are consumed by the SQLAlchemy oracledb dialect
|
||||
itself. These options are always passed directly to :func:`_sa.create_engine`,
|
||||
such as::
|
||||
|
||||
e = create_engine("oracle+oracledb://user:pass@tnsalias", arraysize=500)
|
||||
|
||||
The parameters accepted by the oracledb dialect are as follows:
|
||||
|
||||
* ``arraysize`` - set the driver cursor.arraysize value. It defaults to
|
||||
``None``, indicating that the driver default value of 100 should be used.
|
||||
This setting controls how many rows are buffered when fetching rows, and can
|
||||
have a significant effect on performance if increased for queries that return
|
||||
large numbers of rows.
|
||||
|
||||
.. versionchanged:: 2.0.26 - changed the default value from 50 to None,
|
||||
to use the default value of the driver itself.
|
||||
|
||||
* ``auto_convert_lobs`` - defaults to True; See :ref:`oracledb_lob`.
|
||||
|
||||
* ``coerce_to_decimal`` - see :ref:`oracledb_numeric` for detail.
|
||||
|
||||
* ``encoding_errors`` - see :ref:`oracledb_unicode_encoding_errors` for detail.
|
||||
|
||||
.. _oracledb_unicode:
|
||||
|
||||
Unicode
|
||||
-------
|
||||
|
||||
As is the case for all DBAPIs under Python 3, all strings are inherently
|
||||
Unicode strings.
|
||||
|
||||
Ensuring the Correct Client Encoding
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In python-oracledb, the encoding used for all character data is "UTF-8".
|
||||
|
||||
Unicode-specific Column datatypes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The Core expression language handles unicode data by use of the
|
||||
:class:`.Unicode` and :class:`.UnicodeText` datatypes. These types correspond
|
||||
to the VARCHAR2 and CLOB Oracle Database datatypes by default. When using
|
||||
these datatypes with Unicode data, it is expected that the database is
|
||||
configured with a Unicode-aware character set so that the VARCHAR2 and CLOB
|
||||
datatypes can accommodate the data.
|
||||
|
||||
In the case that Oracle Database is not configured with a Unicode character
|
||||
set, the two options are to use the :class:`_types.NCHAR` and
|
||||
:class:`_oracle.NCLOB` datatypes explicitly, or to pass the flag
|
||||
``use_nchar_for_unicode=True`` to :func:`_sa.create_engine`, which will cause
|
||||
the SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` /
|
||||
:class:`.UnicodeText` datatypes instead of VARCHAR/CLOB.
|
||||
|
||||
.. versionchanged:: 1.3 The :class:`.Unicode` and :class:`.UnicodeText`
|
||||
datatypes now correspond to the ``VARCHAR2`` and ``CLOB`` Oracle Database
|
||||
datatypes unless the ``use_nchar_for_unicode=True`` is passed to the dialect
|
||||
when :func:`_sa.create_engine` is called.
|
||||
|
||||
|
||||
.. _oracledb_unicode_encoding_errors:
|
||||
|
||||
Encoding Errors
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
For the unusual case that data in Oracle Database is present with a broken
|
||||
encoding, the dialect accepts a parameter ``encoding_errors`` which will be
|
||||
passed to Unicode decoding functions in order to affect how decoding errors are
|
||||
handled. The value is ultimately consumed by the Python `decode
|
||||
<https://docs.python.org/3/library/stdtypes.html#bytes.decode>`_ function, and
|
||||
is passed both via python-oracledb's ``encodingErrors`` parameter consumed by
|
||||
``Cursor.var()``, as well as SQLAlchemy's own decoding function, as the
|
||||
python-oracledb dialect makes use of both under different circumstances.
|
||||
|
||||
.. versionadded:: 1.3.11
|
||||
|
||||
|
||||
.. _oracledb_setinputsizes:
|
||||
|
||||
Fine grained control over python-oracledb data binding with setinputsizes
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
The python-oracle DBAPI has a deep and fundamental reliance upon the usage of
|
||||
the DBAPI ``setinputsizes()`` call. The purpose of this call is to establish
|
||||
the datatypes that are bound to a SQL statement for Python values being passed
|
||||
as parameters. While virtually no other DBAPI assigns any use to the
|
||||
``setinputsizes()`` call, the python-oracledb DBAPI relies upon it heavily in
|
||||
its interactions with the Oracle Database, and in some scenarios it is not
|
||||
possible for SQLAlchemy to know exactly how data should be bound, as some
|
||||
settings can cause profoundly different performance characteristics, while
|
||||
altering the type coercion behavior at the same time.
|
||||
|
||||
Users of the oracledb dialect are **strongly encouraged** to read through
|
||||
python-oracledb's list of built-in datatype symbols at `Database Types
|
||||
<https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#database-types>`_
|
||||
Note that in some cases, significant performance degradation can occur when
|
||||
using these types vs. not.
|
||||
|
||||
On the SQLAlchemy side, the :meth:`.DialectEvents.do_setinputsizes` event can
|
||||
be used both for runtime visibility (e.g. logging) of the setinputsizes step as
|
||||
well as to fully control how ``setinputsizes()`` is used on a per-statement
|
||||
basis.
|
||||
|
||||
.. versionadded:: 1.2.9 Added :meth:`.DialectEvents.setinputsizes`
|
||||
|
||||
|
||||
Example 1 - logging all setinputsizes calls
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The following example illustrates how to log the intermediary values from a
|
||||
SQLAlchemy perspective before they are converted to the raw ``setinputsizes()``
|
||||
parameter dictionary. The keys of the dictionary are :class:`.BindParameter`
|
||||
objects which have a ``.key`` and a ``.type`` attribute::
|
||||
|
||||
from sqlalchemy import create_engine, event
|
||||
|
||||
engine = create_engine(
|
||||
"oracle+oracledb://scott:tiger@localhost:1521?service_name=freepdb1"
|
||||
)
|
||||
|
||||
|
||||
@event.listens_for(engine, "do_setinputsizes")
|
||||
def _log_setinputsizes(inputsizes, cursor, statement, parameters, context):
|
||||
for bindparam, dbapitype in inputsizes.items():
|
||||
log.info(
|
||||
"Bound parameter name: %s SQLAlchemy type: %r DBAPI object: %s",
|
||||
bindparam.key,
|
||||
bindparam.type,
|
||||
dbapitype,
|
||||
)
|
||||
|
||||
Example 2 - remove all bindings to CLOB
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For performance, fetching LOB datatypes from Oracle Database is set by default
|
||||
for the ``Text`` type within SQLAlchemy. This setting can be modified as
|
||||
follows::
|
||||
|
||||
|
||||
from sqlalchemy import create_engine, event
|
||||
from oracledb import CLOB
|
||||
|
||||
engine = create_engine(
|
||||
"oracle+oracledb://scott:tiger@localhost:1521?service_name=freepdb1"
|
||||
)
|
||||
|
||||
|
||||
@event.listens_for(engine, "do_setinputsizes")
|
||||
def _remove_clob(inputsizes, cursor, statement, parameters, context):
|
||||
for bindparam, dbapitype in list(inputsizes.items()):
|
||||
if dbapitype is CLOB:
|
||||
del inputsizes[bindparam]
|
||||
|
||||
.. _oracledb_lob:
|
||||
|
||||
LOB Datatypes
|
||||
--------------
|
||||
|
||||
LOB datatypes refer to the "large object" datatypes such as CLOB, NCLOB and
|
||||
BLOB. Oracle Database can efficiently return these datatypes as a single
|
||||
buffer. SQLAlchemy makes use of type handlers to do this by default.
|
||||
|
||||
To disable the use of the type handlers and deliver LOB objects as classic
|
||||
buffered objects with a ``read()`` method, the parameter
|
||||
``auto_convert_lobs=False`` may be passed to :func:`_sa.create_engine`.
|
||||
|
||||
.. _oracledb_returning:
|
||||
|
||||
RETURNING Support
|
||||
-----------------
|
||||
|
||||
The oracledb dialect implements RETURNING using OUT parameters. The dialect
|
||||
supports RETURNING fully.
|
||||
|
||||
Two Phase Transaction Support
|
||||
-----------------------------
|
||||
|
||||
Two phase transactions are fully supported with python-oracledb. (Thin mode
|
||||
requires python-oracledb 2.3). APIs for two phase transactions are provided at
|
||||
the Core level via :meth:`_engine.Connection.begin_twophase` and
|
||||
:paramref:`_orm.Session.twophase` for transparent ORM use.
|
||||
|
||||
.. versionchanged:: 2.0.32 added support for two phase transactions
|
||||
|
||||
.. _oracledb_numeric:
|
||||
|
||||
Precision Numerics
|
||||
------------------
|
||||
|
||||
SQLAlchemy's numeric types can handle receiving and returning values as Python
|
||||
``Decimal`` objects or float objects. When a :class:`.Numeric` object, or a
|
||||
subclass such as :class:`.Float`, :class:`_oracle.DOUBLE_PRECISION` etc. is in
|
||||
use, the :paramref:`.Numeric.asdecimal` flag determines if values should be
|
||||
coerced to ``Decimal`` upon return, or returned as float objects. To make
|
||||
matters more complicated under Oracle Database, the ``NUMBER`` type can also
|
||||
represent integer values if the "scale" is zero, so the Oracle
|
||||
Database-specific :class:`_oracle.NUMBER` type takes this into account as well.
|
||||
|
||||
The oracledb dialect makes extensive use of connection- and cursor-level
|
||||
"outputtypehandler" callables in order to coerce numeric values as requested.
|
||||
These callables are specific to the specific flavor of :class:`.Numeric` in
|
||||
use, as well as if no SQLAlchemy typing objects are present. There are
|
||||
observed scenarios where Oracle Database may send incomplete or ambiguous
|
||||
information about the numeric types being returned, such as a query where the
|
||||
numeric types are buried under multiple levels of subquery. The type handlers
|
||||
do their best to make the right decision in all cases, deferring to the
|
||||
underlying python-oracledb DBAPI for all those cases where the driver can make
|
||||
the best decision.
|
||||
|
||||
When no typing objects are present, as when executing plain SQL strings, a
|
||||
default "outputtypehandler" is present which will generally return numeric
|
||||
values which specify precision and scale as Python ``Decimal`` objects. To
|
||||
disable this coercion to decimal for performance reasons, pass the flag
|
||||
``coerce_to_decimal=False`` to :func:`_sa.create_engine`::
|
||||
|
||||
engine = create_engine(
|
||||
"oracle+oracledb://scott:tiger@tnsalias", coerce_to_decimal=False
|
||||
)
|
||||
|
||||
The ``coerce_to_decimal`` flag only impacts the results of plain string
|
||||
SQL statements that are not otherwise associated with a :class:`.Numeric`
|
||||
SQLAlchemy type (or a subclass of such).
|
||||
|
||||
.. versionchanged:: 1.2 The numeric handling system for the oracle dialects has
|
||||
been reworked to take advantage of newer driver features as well as better
|
||||
integration of outputtypehandlers.
|
||||
|
||||
.. versionadded:: 2.0.0 added support for the python-oracledb driver.
|
||||
.. versionadded:: 2.0.0 added support for oracledb driver.
|
||||
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from . import cx_oracle as _cx_oracle
|
||||
from .cx_oracle import OracleDialect_cx_oracle as _OracleDialect_cx_oracle
|
||||
from ... import exc
|
||||
from ... import pool
|
||||
from ...connectors.asyncio import AsyncAdapt_dbapi_connection
|
||||
from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
|
||||
from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
|
||||
from ...connectors.asyncio import AsyncAdaptFallback_dbapi_connection
|
||||
from ...engine import default
|
||||
from ...util import asbool
|
||||
from ...util import await_fallback
|
||||
from ...util import await_only
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from oracledb import AsyncConnection
|
||||
from oracledb import AsyncCursor
|
||||
|
||||
|
||||
class OracleExecutionContext_oracledb(
|
||||
_cx_oracle.OracleExecutionContext_cx_oracle
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class OracleDialect_oracledb(_cx_oracle.OracleDialect_cx_oracle):
|
||||
class OracleDialect_oracledb(_OracleDialect_cx_oracle):
|
||||
supports_statement_cache = True
|
||||
execution_ctx_cls = OracleExecutionContext_oracledb
|
||||
|
||||
driver = "oracledb"
|
||||
_min_version = (1,)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
auto_convert_lobs=True,
|
||||
coerce_to_decimal=True,
|
||||
arraysize=None,
|
||||
arraysize=50,
|
||||
encoding_errors=None,
|
||||
thick_mode=None,
|
||||
**kwargs,
|
||||
@@ -662,10 +91,6 @@ class OracleDialect_oracledb(_cx_oracle.OracleDialect_cx_oracle):
|
||||
def is_thin_mode(cls, connection):
|
||||
return connection.connection.dbapi_connection.thin
|
||||
|
||||
@classmethod
|
||||
def get_async_dialect_cls(cls, url):
|
||||
return OracleDialectAsync_oracledb
|
||||
|
||||
def _load_version(self, dbapi_module):
|
||||
version = (0, 0, 0)
|
||||
if dbapi_module is not None:
|
||||
@@ -675,273 +100,10 @@ class OracleDialect_oracledb(_cx_oracle.OracleDialect_cx_oracle):
|
||||
int(x) for x in m.group(1, 2, 3) if x is not None
|
||||
)
|
||||
self.oracledb_ver = version
|
||||
if (
|
||||
self.oracledb_ver > (0, 0, 0)
|
||||
and self.oracledb_ver < self._min_version
|
||||
):
|
||||
if self.oracledb_ver < (1,) and self.oracledb_ver > (0, 0, 0):
|
||||
raise exc.InvalidRequestError(
|
||||
f"oracledb version {self._min_version} and above are supported"
|
||||
"oracledb version 1 and above are supported"
|
||||
)
|
||||
|
||||
def do_begin_twophase(self, connection, xid):
|
||||
conn_xis = connection.connection.xid(*xid)
|
||||
connection.connection.tpc_begin(conn_xis)
|
||||
connection.connection.info["oracledb_xid"] = conn_xis
|
||||
|
||||
def do_prepare_twophase(self, connection, xid):
|
||||
should_commit = connection.connection.tpc_prepare()
|
||||
connection.info["oracledb_should_commit"] = should_commit
|
||||
|
||||
def do_rollback_twophase(
|
||||
self, connection, xid, is_prepared=True, recover=False
|
||||
):
|
||||
if recover:
|
||||
conn_xid = connection.connection.xid(*xid)
|
||||
else:
|
||||
conn_xid = None
|
||||
connection.connection.tpc_rollback(conn_xid)
|
||||
|
||||
def do_commit_twophase(
|
||||
self, connection, xid, is_prepared=True, recover=False
|
||||
):
|
||||
conn_xid = None
|
||||
if not is_prepared:
|
||||
should_commit = connection.connection.tpc_prepare()
|
||||
elif recover:
|
||||
conn_xid = connection.connection.xid(*xid)
|
||||
should_commit = True
|
||||
else:
|
||||
should_commit = connection.info["oracledb_should_commit"]
|
||||
if should_commit:
|
||||
connection.connection.tpc_commit(conn_xid)
|
||||
|
||||
def do_recover_twophase(self, connection):
|
||||
return [
|
||||
# oracledb seems to return bytes
|
||||
(
|
||||
fi,
|
||||
gti.decode() if isinstance(gti, bytes) else gti,
|
||||
bq.decode() if isinstance(bq, bytes) else bq,
|
||||
)
|
||||
for fi, gti, bq in connection.connection.tpc_recover()
|
||||
]
|
||||
|
||||
def _check_max_identifier_length(self, connection):
|
||||
if self.oracledb_ver >= (2, 5):
|
||||
max_len = connection.connection.max_identifier_length
|
||||
if max_len is not None:
|
||||
return max_len
|
||||
return super()._check_max_identifier_length(connection)
|
||||
|
||||
|
||||
class AsyncAdapt_oracledb_cursor(AsyncAdapt_dbapi_cursor):
|
||||
_cursor: AsyncCursor
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def outputtypehandler(self):
|
||||
return self._cursor.outputtypehandler
|
||||
|
||||
@outputtypehandler.setter
|
||||
def outputtypehandler(self, value):
|
||||
self._cursor.outputtypehandler = value
|
||||
|
||||
def var(self, *args, **kwargs):
|
||||
return self._cursor.var(*args, **kwargs)
|
||||
|
||||
def close(self):
|
||||
self._rows.clear()
|
||||
self._cursor.close()
|
||||
|
||||
def setinputsizes(self, *args: Any, **kwargs: Any) -> Any:
|
||||
return self._cursor.setinputsizes(*args, **kwargs)
|
||||
|
||||
def _aenter_cursor(self, cursor: AsyncCursor) -> AsyncCursor:
|
||||
try:
|
||||
return cursor.__enter__()
|
||||
except Exception as error:
|
||||
self._adapt_connection._handle_exception(error)
|
||||
|
||||
async def _execute_async(self, operation, parameters):
|
||||
# override to not use mutex, oracledb already has a mutex
|
||||
|
||||
if parameters is None:
|
||||
result = await self._cursor.execute(operation)
|
||||
else:
|
||||
result = await self._cursor.execute(operation, parameters)
|
||||
|
||||
if self._cursor.description and not self.server_side:
|
||||
self._rows = collections.deque(await self._cursor.fetchall())
|
||||
return result
|
||||
|
||||
async def _executemany_async(
|
||||
self,
|
||||
operation,
|
||||
seq_of_parameters,
|
||||
):
|
||||
# override to not use mutex, oracledb already has a mutex
|
||||
return await self._cursor.executemany(operation, seq_of_parameters)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
|
||||
self.close()
|
||||
|
||||
|
||||
class AsyncAdapt_oracledb_ss_cursor(
|
||||
AsyncAdapt_dbapi_ss_cursor, AsyncAdapt_oracledb_cursor
|
||||
):
|
||||
__slots__ = ()
|
||||
|
||||
def close(self) -> None:
|
||||
if self._cursor is not None:
|
||||
self._cursor.close()
|
||||
self._cursor = None # type: ignore
|
||||
|
||||
|
||||
class AsyncAdapt_oracledb_connection(AsyncAdapt_dbapi_connection):
|
||||
_connection: AsyncConnection
|
||||
__slots__ = ()
|
||||
|
||||
thin = True
|
||||
|
||||
_cursor_cls = AsyncAdapt_oracledb_cursor
|
||||
_ss_cursor_cls = None
|
||||
|
||||
@property
|
||||
def autocommit(self):
|
||||
return self._connection.autocommit
|
||||
|
||||
@autocommit.setter
|
||||
def autocommit(self, value):
|
||||
self._connection.autocommit = value
|
||||
|
||||
@property
|
||||
def outputtypehandler(self):
|
||||
return self._connection.outputtypehandler
|
||||
|
||||
@outputtypehandler.setter
|
||||
def outputtypehandler(self, value):
|
||||
self._connection.outputtypehandler = value
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._connection.version
|
||||
|
||||
@property
|
||||
def stmtcachesize(self):
|
||||
return self._connection.stmtcachesize
|
||||
|
||||
@stmtcachesize.setter
|
||||
def stmtcachesize(self, value):
|
||||
self._connection.stmtcachesize = value
|
||||
|
||||
@property
|
||||
def max_identifier_length(self):
|
||||
return self._connection.max_identifier_length
|
||||
|
||||
def cursor(self):
|
||||
return AsyncAdapt_oracledb_cursor(self)
|
||||
|
||||
def ss_cursor(self):
|
||||
return AsyncAdapt_oracledb_ss_cursor(self)
|
||||
|
||||
def xid(self, *args: Any, **kwargs: Any) -> Any:
|
||||
return self._connection.xid(*args, **kwargs)
|
||||
|
||||
def tpc_begin(self, *args: Any, **kwargs: Any) -> Any:
|
||||
return self.await_(self._connection.tpc_begin(*args, **kwargs))
|
||||
|
||||
def tpc_commit(self, *args: Any, **kwargs: Any) -> Any:
|
||||
return self.await_(self._connection.tpc_commit(*args, **kwargs))
|
||||
|
||||
def tpc_prepare(self, *args: Any, **kwargs: Any) -> Any:
|
||||
return self.await_(self._connection.tpc_prepare(*args, **kwargs))
|
||||
|
||||
def tpc_recover(self, *args: Any, **kwargs: Any) -> Any:
|
||||
return self.await_(self._connection.tpc_recover(*args, **kwargs))
|
||||
|
||||
def tpc_rollback(self, *args: Any, **kwargs: Any) -> Any:
|
||||
return self.await_(self._connection.tpc_rollback(*args, **kwargs))
|
||||
|
||||
|
||||
class AsyncAdaptFallback_oracledb_connection(
|
||||
AsyncAdaptFallback_dbapi_connection, AsyncAdapt_oracledb_connection
|
||||
):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class OracledbAdaptDBAPI:
|
||||
def __init__(self, oracledb) -> None:
|
||||
self.oracledb = oracledb
|
||||
|
||||
for k, v in self.oracledb.__dict__.items():
|
||||
if k != "connect":
|
||||
self.__dict__[k] = v
|
||||
|
||||
def connect(self, *arg, **kw):
|
||||
async_fallback = kw.pop("async_fallback", False)
|
||||
creator_fn = kw.pop("async_creator_fn", self.oracledb.connect_async)
|
||||
|
||||
if asbool(async_fallback):
|
||||
return AsyncAdaptFallback_oracledb_connection(
|
||||
self, await_fallback(creator_fn(*arg, **kw))
|
||||
)
|
||||
|
||||
else:
|
||||
return AsyncAdapt_oracledb_connection(
|
||||
self, await_only(creator_fn(*arg, **kw))
|
||||
)
|
||||
|
||||
|
||||
class OracleExecutionContextAsync_oracledb(OracleExecutionContext_oracledb):
|
||||
# restore default create cursor
|
||||
create_cursor = default.DefaultExecutionContext.create_cursor
|
||||
|
||||
def create_default_cursor(self):
|
||||
# copy of OracleExecutionContext_cx_oracle.create_cursor
|
||||
c = self._dbapi_connection.cursor()
|
||||
if self.dialect.arraysize:
|
||||
c.arraysize = self.dialect.arraysize
|
||||
|
||||
return c
|
||||
|
||||
def create_server_side_cursor(self):
|
||||
c = self._dbapi_connection.ss_cursor()
|
||||
if self.dialect.arraysize:
|
||||
c.arraysize = self.dialect.arraysize
|
||||
|
||||
return c
|
||||
|
||||
|
||||
class OracleDialectAsync_oracledb(OracleDialect_oracledb):
|
||||
is_async = True
|
||||
supports_server_side_cursors = True
|
||||
supports_statement_cache = True
|
||||
execution_ctx_cls = OracleExecutionContextAsync_oracledb
|
||||
|
||||
_min_version = (2,)
|
||||
|
||||
# thick_mode mode is not supported by asyncio, oracledb will raise
|
||||
@classmethod
|
||||
def import_dbapi(cls):
|
||||
import oracledb
|
||||
|
||||
return OracledbAdaptDBAPI(oracledb)
|
||||
|
||||
@classmethod
|
||||
def get_pool_class(cls, url):
|
||||
async_fallback = url.query.get("async_fallback", False)
|
||||
|
||||
if asbool(async_fallback):
|
||||
return pool.FallbackAsyncAdaptedQueuePool
|
||||
else:
|
||||
return pool.AsyncAdaptedQueuePool
|
||||
|
||||
def get_driver_connection(self, connection):
|
||||
return connection._connection
|
||||
|
||||
|
||||
dialect = OracleDialect_oracledb
|
||||
dialect_async = OracleDialectAsync_oracledb
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
# dialects/oracle/provision.py
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
# mypy: ignore-errors
|
||||
|
||||
from ... import create_engine
|
||||
@@ -89,7 +83,7 @@ def _oracle_drop_db(cfg, eng, ident):
|
||||
# cx_Oracle seems to occasionally leak open connections when a large
|
||||
# suite it run, even if we confirm we have zero references to
|
||||
# connection objects.
|
||||
# while there is a "kill session" command in Oracle Database,
|
||||
# while there is a "kill session" command in Oracle,
|
||||
# it unfortunately does not release the connection sufficiently.
|
||||
_ora_drop_ignore(conn, ident)
|
||||
_ora_drop_ignore(conn, "%s_ts1" % ident)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# dialects/oracle/types.py
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -64,18 +63,17 @@ class NUMBER(sqltypes.Numeric, sqltypes.Integer):
|
||||
|
||||
|
||||
class FLOAT(sqltypes.FLOAT):
|
||||
"""Oracle Database FLOAT.
|
||||
"""Oracle FLOAT.
|
||||
|
||||
This is the same as :class:`_sqltypes.FLOAT` except that
|
||||
an Oracle Database -specific :paramref:`_oracle.FLOAT.binary_precision`
|
||||
an Oracle-specific :paramref:`_oracle.FLOAT.binary_precision`
|
||||
parameter is accepted, and
|
||||
the :paramref:`_sqltypes.Float.precision` parameter is not accepted.
|
||||
|
||||
Oracle Database FLOAT types indicate precision in terms of "binary
|
||||
precision", which defaults to 126. For a REAL type, the value is 63. This
|
||||
parameter does not cleanly map to a specific number of decimal places but
|
||||
is roughly equivalent to the desired number of decimal places divided by
|
||||
0.3103.
|
||||
Oracle FLOAT types indicate precision in terms of "binary precision", which
|
||||
defaults to 126. For a REAL type, the value is 63. This parameter does not
|
||||
cleanly map to a specific number of decimal places but is roughly
|
||||
equivalent to the desired number of decimal places divided by 0.3103.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
@@ -92,11 +90,10 @@ class FLOAT(sqltypes.FLOAT):
|
||||
r"""
|
||||
Construct a FLOAT
|
||||
|
||||
:param binary_precision: Oracle Database binary precision value to be
|
||||
rendered in DDL. This may be approximated to the number of decimal
|
||||
characters using the formula "decimal precision = 0.30103 * binary
|
||||
precision". The default value used by Oracle Database for FLOAT /
|
||||
DOUBLE PRECISION is 126.
|
||||
:param binary_precision: Oracle binary precision value to be rendered
|
||||
in DDL. This may be approximated to the number of decimal characters
|
||||
using the formula "decimal precision = 0.30103 * binary precision".
|
||||
The default value used by Oracle for FLOAT / DOUBLE PRECISION is 126.
|
||||
|
||||
:param asdecimal: See :paramref:`_sqltypes.Float.asdecimal`
|
||||
|
||||
@@ -111,36 +108,10 @@ class FLOAT(sqltypes.FLOAT):
|
||||
|
||||
|
||||
class BINARY_DOUBLE(sqltypes.Double):
|
||||
"""Implement the Oracle ``BINARY_DOUBLE`` datatype.
|
||||
|
||||
This datatype differs from the Oracle ``DOUBLE`` datatype in that it
|
||||
delivers a true 8-byte FP value. The datatype may be combined with a
|
||||
generic :class:`.Double` datatype using :meth:`.TypeEngine.with_variant`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`oracle_float_support`
|
||||
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = "BINARY_DOUBLE"
|
||||
|
||||
|
||||
class BINARY_FLOAT(sqltypes.Float):
|
||||
"""Implement the Oracle ``BINARY_FLOAT`` datatype.
|
||||
|
||||
This datatype differs from the Oracle ``FLOAT`` datatype in that it
|
||||
delivers a true 4-byte FP value. The datatype may be combined with a
|
||||
generic :class:`.Float` datatype using :meth:`.TypeEngine.with_variant`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`oracle_float_support`
|
||||
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = "BINARY_FLOAT"
|
||||
|
||||
|
||||
@@ -191,10 +162,10 @@ class _OracleDateLiteralRender:
|
||||
|
||||
|
||||
class DATE(_OracleDateLiteralRender, sqltypes.DateTime):
|
||||
"""Provide the Oracle Database DATE type.
|
||||
"""Provide the oracle DATE type.
|
||||
|
||||
This type has no special Python behavior, except that it subclasses
|
||||
:class:`_types.DateTime`; this is to suit the fact that the Oracle Database
|
||||
:class:`_types.DateTime`; this is to suit the fact that the Oracle
|
||||
``DATE`` type supports a time value.
|
||||
|
||||
"""
|
||||
@@ -274,8 +245,8 @@ class INTERVAL(sqltypes.NativeForEmulated, sqltypes._AbstractInterval):
|
||||
|
||||
|
||||
class TIMESTAMP(sqltypes.TIMESTAMP):
|
||||
"""Oracle Database implementation of ``TIMESTAMP``, which supports
|
||||
additional Oracle Database-specific modes
|
||||
"""Oracle implementation of ``TIMESTAMP``, which supports additional
|
||||
Oracle-specific modes
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
@@ -285,11 +256,10 @@ class TIMESTAMP(sqltypes.TIMESTAMP):
|
||||
"""Construct a new :class:`_oracle.TIMESTAMP`.
|
||||
|
||||
:param timezone: boolean. Indicates that the TIMESTAMP type should
|
||||
use Oracle Database's ``TIMESTAMP WITH TIME ZONE`` datatype.
|
||||
use Oracle's ``TIMESTAMP WITH TIME ZONE`` datatype.
|
||||
|
||||
:param local_timezone: boolean. Indicates that the TIMESTAMP type
|
||||
should use Oracle Database's ``TIMESTAMP WITH LOCAL TIME ZONE``
|
||||
datatype.
|
||||
should use Oracle's ``TIMESTAMP WITH LOCAL TIME ZONE`` datatype.
|
||||
|
||||
|
||||
"""
|
||||
@@ -302,7 +272,7 @@ class TIMESTAMP(sqltypes.TIMESTAMP):
|
||||
|
||||
|
||||
class ROWID(sqltypes.TypeEngine):
|
||||
"""Oracle Database ROWID type.
|
||||
"""Oracle ROWID type.
|
||||
|
||||
When used in a cast() or similar, generates ROWID.
|
||||
|
||||
|
||||
@@ -1,364 +0,0 @@
|
||||
# dialects/oracle/vector.py
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
# mypy: ignore-errors
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import array
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
import sqlalchemy.types as types
|
||||
from sqlalchemy.types import Float
|
||||
|
||||
|
||||
class VectorIndexType(Enum):
|
||||
"""Enum representing different types of VECTOR index structures.
|
||||
|
||||
See :ref:`oracle_vector_datatype` for background.
|
||||
|
||||
.. versionadded:: 2.0.41
|
||||
|
||||
"""
|
||||
|
||||
HNSW = "HNSW"
|
||||
"""
|
||||
The HNSW (Hierarchical Navigable Small World) index type.
|
||||
"""
|
||||
IVF = "IVF"
|
||||
"""
|
||||
The IVF (Inverted File Index) index type
|
||||
"""
|
||||
|
||||
|
||||
class VectorDistanceType(Enum):
|
||||
"""Enum representing different types of vector distance metrics.
|
||||
|
||||
See :ref:`oracle_vector_datatype` for background.
|
||||
|
||||
.. versionadded:: 2.0.41
|
||||
|
||||
"""
|
||||
|
||||
EUCLIDEAN = "EUCLIDEAN"
|
||||
"""Euclidean distance (L2 norm).
|
||||
|
||||
Measures the straight-line distance between two vectors in space.
|
||||
"""
|
||||
DOT = "DOT"
|
||||
"""Dot product similarity.
|
||||
|
||||
Measures the algebraic similarity between two vectors.
|
||||
"""
|
||||
COSINE = "COSINE"
|
||||
"""Cosine similarity.
|
||||
|
||||
Measures the cosine of the angle between two vectors.
|
||||
"""
|
||||
MANHATTAN = "MANHATTAN"
|
||||
"""Manhattan distance (L1 norm).
|
||||
|
||||
Calculates the sum of absolute differences across dimensions.
|
||||
"""
|
||||
|
||||
|
||||
class VectorStorageFormat(Enum):
|
||||
"""Enum representing the data format used to store vector components.
|
||||
|
||||
See :ref:`oracle_vector_datatype` for background.
|
||||
|
||||
.. versionadded:: 2.0.41
|
||||
|
||||
"""
|
||||
|
||||
INT8 = "INT8"
|
||||
"""
|
||||
8-bit integer format.
|
||||
"""
|
||||
BINARY = "BINARY"
|
||||
"""
|
||||
Binary format.
|
||||
"""
|
||||
FLOAT32 = "FLOAT32"
|
||||
"""
|
||||
32-bit floating-point format.
|
||||
"""
|
||||
FLOAT64 = "FLOAT64"
|
||||
"""
|
||||
64-bit floating-point format.
|
||||
"""
|
||||
|
||||
|
||||
class VectorStorageType(Enum):
|
||||
"""Enum representing the vector type,
|
||||
|
||||
See :ref:`oracle_vector_datatype` for background.
|
||||
|
||||
.. versionadded:: 2.0.43
|
||||
|
||||
"""
|
||||
|
||||
SPARSE = "SPARSE"
|
||||
"""
|
||||
A Sparse vector is a vector which has zero value for
|
||||
most of its dimensions.
|
||||
"""
|
||||
DENSE = "DENSE"
|
||||
"""
|
||||
A Dense vector is a vector where most, if not all, elements
|
||||
hold meaningful values.
|
||||
"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class VectorIndexConfig:
|
||||
"""Define the configuration for Oracle VECTOR Index.
|
||||
|
||||
See :ref:`oracle_vector_datatype` for background.
|
||||
|
||||
.. versionadded:: 2.0.41
|
||||
|
||||
:param index_type: Enum value from :class:`.VectorIndexType`
|
||||
Specifies the indexing method. For HNSW, this must be
|
||||
:attr:`.VectorIndexType.HNSW`.
|
||||
|
||||
:param distance: Enum value from :class:`.VectorDistanceType`
|
||||
specifies the metric for calculating distance between VECTORS.
|
||||
|
||||
:param accuracy: interger. Should be in the range 0 to 100
|
||||
Specifies the accuracy of the nearest neighbor search during
|
||||
query execution.
|
||||
|
||||
:param parallel: integer. Specifies degree of parallelism.
|
||||
|
||||
:param hnsw_neighbors: interger. Should be in the range 0 to
|
||||
2048. Specifies the number of nearest neighbors considered
|
||||
during the search. The attribute :attr:`.VectorIndexConfig.hnsw_neighbors`
|
||||
is HNSW index specific.
|
||||
|
||||
:param hnsw_efconstruction: integer. Should be in the range 0
|
||||
to 65535. Controls the trade-off between indexing speed and
|
||||
recall quality during index construction. The attribute
|
||||
:attr:`.VectorIndexConfig.hnsw_efconstruction` is HNSW index
|
||||
specific.
|
||||
|
||||
:param ivf_neighbor_partitions: integer. Should be in the range
|
||||
0 to 10,000,000. Specifies the number of partitions used to
|
||||
divide the dataset. The attribute
|
||||
:attr:`.VectorIndexConfig.ivf_neighbor_partitions` is IVF index
|
||||
specific.
|
||||
|
||||
:param ivf_sample_per_partition: integer. Should be between 1
|
||||
and ``num_vectors / neighbor partitions``. Specifies the
|
||||
number of samples used per partition. The attribute
|
||||
:attr:`.VectorIndexConfig.ivf_sample_per_partition` is IVF index
|
||||
specific.
|
||||
|
||||
:param ivf_min_vectors_per_partition: integer. From 0 (no trimming)
|
||||
to the total number of vectors (results in 1 partition). Specifies
|
||||
the minimum number of vectors per partition. The attribute
|
||||
:attr:`.VectorIndexConfig.ivf_min_vectors_per_partition`
|
||||
is IVF index specific.
|
||||
|
||||
"""
|
||||
|
||||
index_type: VectorIndexType = VectorIndexType.HNSW
|
||||
distance: Optional[VectorDistanceType] = None
|
||||
accuracy: Optional[int] = None
|
||||
hnsw_neighbors: Optional[int] = None
|
||||
hnsw_efconstruction: Optional[int] = None
|
||||
ivf_neighbor_partitions: Optional[int] = None
|
||||
ivf_sample_per_partition: Optional[int] = None
|
||||
ivf_min_vectors_per_partition: Optional[int] = None
|
||||
parallel: Optional[int] = None
|
||||
|
||||
def __post_init__(self):
|
||||
self.index_type = VectorIndexType(self.index_type)
|
||||
for field in [
|
||||
"hnsw_neighbors",
|
||||
"hnsw_efconstruction",
|
||||
"ivf_neighbor_partitions",
|
||||
"ivf_sample_per_partition",
|
||||
"ivf_min_vectors_per_partition",
|
||||
"parallel",
|
||||
"accuracy",
|
||||
]:
|
||||
value = getattr(self, field)
|
||||
if value is not None and not isinstance(value, int):
|
||||
raise TypeError(
|
||||
f"{field} must be an integer if"
|
||||
f"provided, got {type(value).__name__}"
|
||||
)
|
||||
|
||||
|
||||
class SparseVector:
|
||||
"""
|
||||
Lightweight SQLAlchemy-side version of SparseVector.
|
||||
This mimics oracledb.SparseVector.
|
||||
|
||||
.. versionadded:: 2.0.43
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_dimensions: int,
|
||||
indices: Union[list, array.array],
|
||||
values: Union[list, array.array],
|
||||
):
|
||||
if not isinstance(indices, array.array) or indices.typecode != "I":
|
||||
indices = array.array("I", indices)
|
||||
if not isinstance(values, array.array):
|
||||
values = array.array("d", values)
|
||||
if len(indices) != len(values):
|
||||
raise TypeError("indices and values must be of the same length!")
|
||||
|
||||
self.num_dimensions = num_dimensions
|
||||
self.indices = indices
|
||||
self.values = values
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f"SparseVector(num_dimensions={self.num_dimensions}, "
|
||||
f"size={len(self.indices)}, typecode={self.values.typecode})"
|
||||
)
|
||||
|
||||
|
||||
class VECTOR(types.TypeEngine):
|
||||
"""Oracle VECTOR datatype.
|
||||
|
||||
For complete background on using this type, see
|
||||
:ref:`oracle_vector_datatype`.
|
||||
|
||||
.. versionadded:: 2.0.41
|
||||
|
||||
"""
|
||||
|
||||
cache_ok = True
|
||||
__visit_name__ = "VECTOR"
|
||||
|
||||
_typecode_map = {
|
||||
VectorStorageFormat.INT8: "b", # Signed int
|
||||
VectorStorageFormat.BINARY: "B", # Unsigned int
|
||||
VectorStorageFormat.FLOAT32: "f", # Float
|
||||
VectorStorageFormat.FLOAT64: "d", # Double
|
||||
}
|
||||
|
||||
def __init__(self, dim=None, storage_format=None, storage_type=None):
|
||||
"""Construct a VECTOR.
|
||||
|
||||
:param dim: integer. The dimension of the VECTOR datatype. This
|
||||
should be an integer value.
|
||||
|
||||
:param storage_format: VectorStorageFormat. The VECTOR storage
|
||||
type format. This should be Enum values form
|
||||
:class:`.VectorStorageFormat` INT8, BINARY, FLOAT32, or FLOAT64.
|
||||
|
||||
:param storage_type: VectorStorageType. The Vector storage type. This
|
||||
should be Enum values from :class:`.VectorStorageType` SPARSE or
|
||||
DENSE.
|
||||
|
||||
"""
|
||||
|
||||
if dim is not None and not isinstance(dim, int):
|
||||
raise TypeError("dim must be an interger")
|
||||
if storage_format is not None and not isinstance(
|
||||
storage_format, VectorStorageFormat
|
||||
):
|
||||
raise TypeError(
|
||||
"storage_format must be an enum of type VectorStorageFormat"
|
||||
)
|
||||
if storage_type is not None and not isinstance(
|
||||
storage_type, VectorStorageType
|
||||
):
|
||||
raise TypeError(
|
||||
"storage_type must be an enum of type VectorStorageType"
|
||||
)
|
||||
|
||||
self.dim = dim
|
||||
self.storage_format = storage_format
|
||||
self.storage_type = storage_type
|
||||
|
||||
def _cached_bind_processor(self, dialect):
|
||||
"""
|
||||
Converts a Python-side SparseVector instance into an
|
||||
oracledb.SparseVectormor a compatible array format before
|
||||
binding it to the database.
|
||||
"""
|
||||
|
||||
def process(value):
|
||||
if value is None or isinstance(value, array.array):
|
||||
return value
|
||||
|
||||
# Convert list to a array.array
|
||||
elif isinstance(value, list):
|
||||
typecode = self._array_typecode(self.storage_format)
|
||||
value = array.array(typecode, value)
|
||||
return value
|
||||
|
||||
# Convert SqlAlchemy SparseVector to oracledb SparseVector object
|
||||
elif isinstance(value, SparseVector):
|
||||
return dialect.dbapi.SparseVector(
|
||||
value.num_dimensions,
|
||||
value.indices,
|
||||
value.values,
|
||||
)
|
||||
|
||||
else:
|
||||
raise TypeError(
|
||||
"""
|
||||
Invalid input for VECTOR: expected a list, an array.array,
|
||||
or a SparseVector object.
|
||||
"""
|
||||
)
|
||||
|
||||
return process
|
||||
|
||||
def _cached_result_processor(self, dialect, coltype):
|
||||
"""
|
||||
Converts database-returned values into Python-native representations.
|
||||
If the value is an oracledb.SparseVector, it is converted into the
|
||||
SQLAlchemy-side SparseVector class.
|
||||
If the value is a array.array, it is converted to a plain Python list.
|
||||
|
||||
"""
|
||||
|
||||
def process(value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
elif isinstance(value, array.array):
|
||||
return list(value)
|
||||
|
||||
# Convert Oracledb SparseVector to SqlAlchemy SparseVector object
|
||||
elif isinstance(value, dialect.dbapi.SparseVector):
|
||||
return SparseVector(
|
||||
num_dimensions=value.num_dimensions,
|
||||
indices=value.indices,
|
||||
values=value.values,
|
||||
)
|
||||
|
||||
return process
|
||||
|
||||
def _array_typecode(self, typecode):
|
||||
"""
|
||||
Map storage format to array typecode.
|
||||
"""
|
||||
return self._typecode_map.get(typecode, "d")
|
||||
|
||||
class comparator_factory(types.TypeEngine.Comparator):
|
||||
def l2_distance(self, other):
|
||||
return self.op("<->", return_type=Float)(other)
|
||||
|
||||
def inner_product(self, other):
|
||||
return self.op("<#>", return_type=Float)(other)
|
||||
|
||||
def cosine_distance(self, other):
|
||||
return self.op("<=>", return_type=Float)(other)
|
||||
Reference in New Issue
Block a user