mirror of
https://github.com/django/django.git
synced 2024-12-22 09:05:43 +00:00
Fixed #33817 -- Added support for python-oracledb and deprecated cx_Oracle.
This commit is contained in:
parent
59f13ce545
commit
9946f0b0d9
@ -1,11 +1,10 @@
|
||||
from cx_Oracle import CLOB
|
||||
|
||||
from django.contrib.gis.db.backends.base.adapter import WKTAdapter
|
||||
from django.contrib.gis.geos import GeometryCollection, Polygon
|
||||
from django.db.backends.oracle.oracledb_any import oracledb
|
||||
|
||||
|
||||
class OracleSpatialAdapter(WKTAdapter):
|
||||
input_size = CLOB
|
||||
input_size = oracledb.CLOB
|
||||
|
||||
def __init__(self, geom):
|
||||
"""
|
||||
|
@ -1,6 +1,5 @@
|
||||
import cx_Oracle
|
||||
|
||||
from django.db.backends.oracle.introspection import DatabaseIntrospection
|
||||
from django.db.backends.oracle.oracledb_any import oracledb
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
@ -12,7 +11,7 @@ class OracleIntrospection(DatabaseIntrospection):
|
||||
def data_types_reverse(self):
|
||||
return {
|
||||
**super().data_types_reverse,
|
||||
cx_Oracle.OBJECT: "GeometryField",
|
||||
oracledb.DB_TYPE_OBJECT: "GeometryField",
|
||||
}
|
||||
|
||||
def get_geometry_type(self, table_name, description):
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""
|
||||
Oracle database backend for Django.
|
||||
|
||||
Requires cx_Oracle: https://oracle.github.io/python-cx_Oracle/
|
||||
Requires oracledb: https://oracle.github.io/python-oracledb/
|
||||
"""
|
||||
import datetime
|
||||
import decimal
|
||||
@ -13,6 +13,7 @@ from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import IntegrityError
|
||||
from django.db.backends.base.base import BaseDatabaseWrapper
|
||||
from django.db.backends.oracle.oracledb_any import oracledb as Database
|
||||
from django.db.backends.utils import debug_transaction
|
||||
from django.utils.asyncio import async_unsafe
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
@ -49,12 +50,8 @@ _setup_environment(
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
import cx_Oracle as Database
|
||||
except ImportError as e:
|
||||
raise ImproperlyConfigured("Error loading cx_Oracle module: %s" % e)
|
||||
|
||||
# Some of these import cx_Oracle, so import them after checking if it's installed.
|
||||
# Some of these import oracledb, so import them after checking if it's
|
||||
# installed.
|
||||
from .client import DatabaseClient # NOQA
|
||||
from .creation import DatabaseCreation # NOQA
|
||||
from .features import DatabaseFeatures # NOQA
|
||||
@ -70,7 +67,7 @@ def wrap_oracle_errors():
|
||||
try:
|
||||
yield
|
||||
except Database.DatabaseError as e:
|
||||
# cx_Oracle raises a cx_Oracle.DatabaseError exception with the
|
||||
# oracledb raises a oracledb.DatabaseError exception with the
|
||||
# following attributes and values:
|
||||
# code = 2091
|
||||
# message = 'ORA-02091: transaction rolled back
|
||||
@ -514,7 +511,7 @@ class FormatStylePlaceholderCursor:
|
||||
return [p.force_bytes for p in params]
|
||||
|
||||
def _fix_for_params(self, query, params, unify_by_values=False):
|
||||
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
|
||||
# oracledb wants no trailing ';' for SQL statements. For PL/SQL, it
|
||||
# it does want a trailing ';' but not a trailing '/'. However, these
|
||||
# characters must be included in the original query in case the query
|
||||
# is being passed to SQL*Plus.
|
||||
|
@ -125,7 +125,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||
},
|
||||
}
|
||||
django_test_expected_failures = {
|
||||
# A bug in Django/cx_Oracle with respect to string handling (#23843).
|
||||
# A bug in Django/oracledb with respect to string handling (#23843).
|
||||
"annotations.tests.NonAggregateAnnotationTestCase.test_custom_functions",
|
||||
"annotations.tests.NonAggregateAnnotationTestCase."
|
||||
"test_custom_functions_can_ref_other_functions",
|
||||
|
@ -1,11 +1,10 @@
|
||||
from collections import namedtuple
|
||||
|
||||
import cx_Oracle
|
||||
|
||||
from django.db import models
|
||||
from django.db.backends.base.introspection import BaseDatabaseIntrospection
|
||||
from django.db.backends.base.introspection import FieldInfo as BaseFieldInfo
|
||||
from django.db.backends.base.introspection import TableInfo as BaseTableInfo
|
||||
from django.db.backends.oracle.oracledb_any import oracledb
|
||||
|
||||
FieldInfo = namedtuple(
|
||||
"FieldInfo", BaseFieldInfo._fields + ("is_autofield", "is_json", "comment")
|
||||
@ -18,22 +17,22 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||
|
||||
# Maps type objects to Django Field types.
|
||||
data_types_reverse = {
|
||||
cx_Oracle.DB_TYPE_DATE: "DateField",
|
||||
cx_Oracle.DB_TYPE_BINARY_DOUBLE: "FloatField",
|
||||
cx_Oracle.DB_TYPE_BLOB: "BinaryField",
|
||||
cx_Oracle.DB_TYPE_CHAR: "CharField",
|
||||
cx_Oracle.DB_TYPE_CLOB: "TextField",
|
||||
cx_Oracle.DB_TYPE_INTERVAL_DS: "DurationField",
|
||||
cx_Oracle.DB_TYPE_NCHAR: "CharField",
|
||||
cx_Oracle.DB_TYPE_NCLOB: "TextField",
|
||||
cx_Oracle.DB_TYPE_NVARCHAR: "CharField",
|
||||
cx_Oracle.DB_TYPE_NUMBER: "DecimalField",
|
||||
cx_Oracle.DB_TYPE_TIMESTAMP: "DateTimeField",
|
||||
cx_Oracle.DB_TYPE_VARCHAR: "CharField",
|
||||
oracledb.DB_TYPE_DATE: "DateField",
|
||||
oracledb.DB_TYPE_BINARY_DOUBLE: "FloatField",
|
||||
oracledb.DB_TYPE_BLOB: "BinaryField",
|
||||
oracledb.DB_TYPE_CHAR: "CharField",
|
||||
oracledb.DB_TYPE_CLOB: "TextField",
|
||||
oracledb.DB_TYPE_INTERVAL_DS: "DurationField",
|
||||
oracledb.DB_TYPE_NCHAR: "CharField",
|
||||
oracledb.DB_TYPE_NCLOB: "TextField",
|
||||
oracledb.DB_TYPE_NVARCHAR: "CharField",
|
||||
oracledb.DB_TYPE_NUMBER: "DecimalField",
|
||||
oracledb.DB_TYPE_TIMESTAMP: "DateTimeField",
|
||||
oracledb.DB_TYPE_VARCHAR: "CharField",
|
||||
}
|
||||
|
||||
def get_field_type(self, data_type, description):
|
||||
if data_type == cx_Oracle.NUMBER:
|
||||
if data_type == oracledb.NUMBER:
|
||||
precision, scale = description[4:6]
|
||||
if scale == 0:
|
||||
if precision > 11:
|
||||
@ -52,7 +51,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||
return "IntegerField"
|
||||
elif scale == -127:
|
||||
return "FloatField"
|
||||
elif data_type == cx_Oracle.NCLOB and description.is_json:
|
||||
elif data_type == oracledb.NCLOB and description.is_json:
|
||||
return "JSONField"
|
||||
|
||||
return super().get_field_type(data_type, description)
|
||||
@ -193,7 +192,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||
is_json,
|
||||
comment,
|
||||
) = field_map[name]
|
||||
name %= {} # cx_Oracle, for some reason, doubles percent signs.
|
||||
name %= {} # oracledb, for some reason, doubles percent signs.
|
||||
description.append(
|
||||
FieldInfo(
|
||||
self.identifier_converter(name),
|
||||
|
@ -247,7 +247,7 @@ END;
|
||||
value = bool(value)
|
||||
return value
|
||||
|
||||
# cx_Oracle always returns datetime.datetime objects for
|
||||
# oracledb always returns datetime.datetime objects for
|
||||
# DATE and TIMESTAMP columns, but Django wants to see a
|
||||
# python datetime.date, .time, or .datetime.
|
||||
|
||||
@ -311,10 +311,10 @@ END;
|
||||
)
|
||||
|
||||
def last_executed_query(self, cursor, sql, params):
|
||||
# https://cx-oracle.readthedocs.io/en/latest/api_manual/cursor.html#Cursor.statement
|
||||
# https://python-oracledb.readthedocs.io/en/latest/api_manual/cursor.html#Cursor.statement
|
||||
# The DB API definition does not define this attribute.
|
||||
statement = cursor.statement
|
||||
# Unlike Psycopg's `query` and MySQLdb`'s `_executed`, cx_Oracle's
|
||||
# Unlike Psycopg's `query` and MySQLdb`'s `_executed`, oracledb's
|
||||
# `statement` doesn't contain the query parameters. Substitute
|
||||
# parameters manually.
|
||||
if params:
|
||||
@ -592,7 +592,7 @@ END;
|
||||
if hasattr(value, "resolve_expression"):
|
||||
return value
|
||||
|
||||
# cx_Oracle doesn't support tz-aware datetimes
|
||||
# oracledb doesn't support tz-aware datetimes
|
||||
if timezone.is_aware(value):
|
||||
if settings.USE_TZ:
|
||||
value = timezone.make_naive(value, self.connection.timezone)
|
||||
|
21
django/db/backends/oracle/oracledb_any.py
Normal file
21
django/db/backends/oracle/oracledb_any.py
Normal file
@ -0,0 +1,21 @@
|
||||
import warnings
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.deprecation import RemovedInDjango60Warning
|
||||
|
||||
try:
|
||||
import oracledb
|
||||
|
||||
is_oracledb = True
|
||||
except ImportError as e:
|
||||
try:
|
||||
import cx_Oracle as oracledb # NOQA
|
||||
|
||||
warnings.warn(
|
||||
"cx_Oracle is deprecated. Use oracledb instead.",
|
||||
RemovedInDjango60Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
is_oracledb = False
|
||||
except ImportError:
|
||||
raise ImproperlyConfigured(f"Error loading oracledb module: {e}")
|
@ -42,7 +42,7 @@ class InsertVar:
|
||||
class Oracle_datetime(datetime.datetime):
|
||||
"""
|
||||
A datetime object, with an additional class attribute
|
||||
to tell cx_Oracle to save the microseconds too.
|
||||
to tell oracledb to save the microseconds too.
|
||||
"""
|
||||
|
||||
input_size = Database.TIMESTAMP
|
||||
|
@ -47,7 +47,7 @@ class CursorWrapper:
|
||||
|
||||
def callproc(self, procname, params=None, kparams=None):
|
||||
# Keyword parameters for callproc aren't supported in PEP 249, but the
|
||||
# database driver may support them (e.g. cx_Oracle).
|
||||
# database driver may support them (e.g. oracledb).
|
||||
if kparams is not None and not self.db.features.supports_callproc_kwargs:
|
||||
raise NotSupportedError(
|
||||
"Keyword parameters for callproc are not supported on this "
|
||||
|
@ -1036,7 +1036,7 @@ class Value(SQLiteNumericMixin, Expression):
|
||||
if hasattr(output_field, "get_placeholder"):
|
||||
return output_field.get_placeholder(val, compiler, connection), [val]
|
||||
if val is None:
|
||||
# cx_Oracle does not always convert None to the appropriate
|
||||
# oracledb does not always convert None to the appropriate
|
||||
# NULL type (like in case expressions using numbers), so we
|
||||
# use a literal SQL NULL
|
||||
return "NULL", []
|
||||
|
@ -38,6 +38,8 @@ details on these changes.
|
||||
* Support for calling ``format_html()`` without passing args or kwargs will be
|
||||
removed.
|
||||
|
||||
* Support for ``cx_Oracle`` will be removed.
|
||||
|
||||
.. _deprecation-removed-in-5.1:
|
||||
|
||||
5.1
|
||||
|
@ -919,11 +919,15 @@ To enable the JSON1 extension you can follow the instruction on
|
||||
Oracle notes
|
||||
============
|
||||
|
||||
Django supports `Oracle Database Server`_ versions 19c and higher. Version 8.3
|
||||
or higher of the `cx_Oracle`_ Python driver is required.
|
||||
Django supports `Oracle Database Server`_ versions 19c and higher. Version
|
||||
1.3.2 or higher of the `oracledb`_ Python driver is required.
|
||||
|
||||
.. deprecated:: 5.0
|
||||
|
||||
Support for ``cx_Oracle`` is deprecated.
|
||||
|
||||
.. _`Oracle Database Server`: https://www.oracle.com/
|
||||
.. _`cx_Oracle`: https://oracle.github.io/python-cx_Oracle/
|
||||
.. _`oracledb`: https://oracle.github.io/python-oracledb/
|
||||
|
||||
In order for the ``python manage.py migrate`` command to work, your Oracle
|
||||
database user must have privileges to run the following commands:
|
||||
|
@ -386,6 +386,10 @@ Models
|
||||
``CHAR(32)`` column. See the migration guide above for more details on
|
||||
:ref:`migrating-uuidfield`.
|
||||
|
||||
* Django now supports `oracledb`_ version 1.3.2 or higher. Support for
|
||||
``cx_Oracle`` is deprecated as of this release and will be removed in Django
|
||||
6.0.
|
||||
|
||||
Pagination
|
||||
~~~~~~~~~~
|
||||
|
||||
@ -606,6 +610,11 @@ Miscellaneous
|
||||
* Support for calling ``format_html()`` without passing args or kwargs will be
|
||||
removed.
|
||||
|
||||
* Support for ``cx_Oracle`` is deprecated in favor of `oracledb`_ 1.3.2+ Python
|
||||
driver.
|
||||
|
||||
.. _`oracledb`: https://oracle.github.io/python-oracledb/
|
||||
|
||||
Features removed in 5.0
|
||||
=======================
|
||||
|
||||
|
@ -90,9 +90,9 @@ database bindings are installed.
|
||||
* If you're using SQLite you might want to read the :ref:`SQLite backend notes
|
||||
<sqlite-notes>`.
|
||||
|
||||
* If you're using Oracle, you'll need a copy of cx_Oracle_, but please
|
||||
read the :ref:`notes for the Oracle backend <oracle-notes>` for details
|
||||
regarding supported versions of both Oracle and ``cx_Oracle``.
|
||||
* If you're using Oracle, you'll need to install oracledb_, but please read the
|
||||
:ref:`notes for the Oracle backend <oracle-notes>` for details regarding
|
||||
supported versions of both Oracle and ``oracledb``.
|
||||
|
||||
* If you're using an unofficial 3rd party backend, please consult the
|
||||
documentation provided for any additional requirements.
|
||||
@ -115,7 +115,7 @@ database queries, Django will need permission to create a test database.
|
||||
.. _psycopg: https://www.psycopg.org/psycopg3/
|
||||
.. _psycopg2: https://www.psycopg.org/
|
||||
.. _SQLite: https://www.sqlite.org/
|
||||
.. _cx_Oracle: https://oracle.github.io/python-cx_Oracle/
|
||||
.. _oracledb: https://oracle.github.io/python-oracledb/
|
||||
.. _Oracle: https://www.oracle.com/
|
||||
|
||||
.. _install-django-code:
|
||||
|
@ -5,7 +5,7 @@ from django.db.backends.oracle.client import DatabaseClient
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
|
||||
@skipUnless(connection.vendor == "oracle", "Requires cx_Oracle to be installed")
|
||||
@skipUnless(connection.vendor == "oracle", "Requires oracledb to be installed")
|
||||
class OracleDbshellTests(SimpleTestCase):
|
||||
def settings_to_cmd_args_env(self, settings_dict, parameters=None, rlwrap=False):
|
||||
if parameters is None:
|
||||
|
@ -536,7 +536,7 @@ class BasicExpressionsTests(TestCase):
|
||||
|
||||
results = list(qs)
|
||||
# Could use Coalesce(subq, Value('')) instead except for the bug in
|
||||
# cx_Oracle mentioned in #23843.
|
||||
# oracledb mentioned in #23843.
|
||||
bob = results[0]
|
||||
if (
|
||||
bob["largest_company"] == ""
|
||||
|
@ -1 +1 @@
|
||||
cx_oracle >= 8.3
|
||||
oracledb >= 1.3.2
|
||||
|
Loading…
Reference in New Issue
Block a user