main commit
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-16 16:30:25 +09:00
parent 91c7e04474
commit 537e7b363f
1146 changed files with 45926 additions and 77196 deletions

View File

@@ -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",
)

View File

@@ -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)

View File

@@ -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

View File

@@ -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{&parameter_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

View File

@@ -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)

View File

@@ -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.

View File

@@ -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)