1
0
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:
Jingbei Li 2022-07-12 14:22:40 +08:00 committed by Mariusz Felisiak
parent 59f13ce545
commit 9946f0b0d9
17 changed files with 80 additions and 50 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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}")

View File

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

View File

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

View File

@ -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", []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"] == ""

View File

@ -1 +1 @@
cx_oracle >= 8.3
oracledb >= 1.3.2