diff --git a/django/contrib/gis/db/backends/oracle/adapter.py b/django/contrib/gis/db/backends/oracle/adapter.py index 0bf11648d9..bd52b1bf21 100644 --- a/django/contrib/gis/db/backends/oracle/adapter.py +++ b/django/contrib/gis/db/backends/oracle/adapter.py @@ -1,6 +1,7 @@ +import oracledb + 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): diff --git a/django/contrib/gis/db/backends/oracle/introspection.py b/django/contrib/gis/db/backends/oracle/introspection.py index bf299b12ff..8e1a5e7a8c 100644 --- a/django/contrib/gis/db/backends/oracle/introspection.py +++ b/django/contrib/gis/db/backends/oracle/introspection.py @@ -1,5 +1,6 @@ +import oracledb + from django.db.backends.oracle.introspection import DatabaseIntrospection -from django.db.backends.oracle.oracledb_any import oracledb from django.utils.functional import cached_property diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 3b37c38f97..9fd82dd705 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -14,7 +14,6 @@ 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 is_oracledb from django.db.backends.utils import debug_transaction from django.utils.asyncio import async_unsafe from django.utils.encoding import force_bytes, force_str @@ -22,7 +21,7 @@ from django.utils.functional import cached_property from django.utils.version import get_version_tuple try: - from django.db.backends.oracle.oracledb_any import oracledb as Database + import oracledb as Database except ImportError as e: raise ImproperlyConfigured(f"Error loading oracledb module: {e}") @@ -286,11 +285,6 @@ class DatabaseWrapper(BaseDatabaseWrapper): return self.oracle_version def get_connection_params(self): - # Pooling feature is only supported for oracledb. - if self.is_pool and not is_oracledb: - raise ImproperlyConfigured( - "Pooling isn't supported by cx_Oracle. Use python-oracledb instead." - ) conn_params = self.settings_dict["OPTIONS"].copy() if "use_returning_into" in conn_params: del conn_params["use_returning_into"] diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py index ad9ab8da55..bf47577ed6 100644 --- a/django/db/backends/oracle/features.py +++ b/django/db/backends/oracle/features.py @@ -1,6 +1,5 @@ from django.db import DatabaseError, InterfaceError from django.db.backends.base.features import BaseDatabaseFeatures -from django.db.backends.oracle.oracledb_any import is_oracledb from django.utils.functional import cached_property @@ -158,16 +157,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): }, } ) - if is_oracledb and self.connection.oracledb_version >= (2, 1, 2): - skips.update( - { - "python-oracledb 2.1.2+ no longer hides 'ORA-1403: no data found' " - "exceptions raised in database triggers.": { - "backends.oracle.tests.TransactionalTests." - "test_hidden_no_data_found_exception" - }, - }, - ) return skips @cached_property diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py index 07c2d9bded..393e6c66d7 100644 --- a/django/db/backends/oracle/introspection.py +++ b/django/db/backends/oracle/introspection.py @@ -1,10 +1,11 @@ from collections import namedtuple +import oracledb + 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") diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index 79c6da994e..aa67f28a79 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -3,7 +3,7 @@ import uuid from functools import lru_cache from django.conf import settings -from django.db import DatabaseError, NotSupportedError +from django.db import NotSupportedError from django.db.backends.base.operations import BaseDatabaseOperations from django.db.backends.utils import split_tzname_delta, strip_quotes, truncate_name from django.db.models import AutoField, Exists, ExpressionWrapper, Lookup @@ -295,15 +295,6 @@ END; columns = [] for param in returning_params: value = param.get_value() - # Can be removed when cx_Oracle is no longer supported and - # python-oracle 2.1.2 becomes the minimum supported version. - if value == []: - raise DatabaseError( - "The database did not return a new row id. Probably " - '"ORA-1403: no data found" was raised internally but was ' - "hidden by the Oracle OCI library (see " - "https://code.djangoproject.com/ticket/28859)." - ) columns.append(value[0]) return tuple(columns) diff --git a/django/db/backends/oracle/oracledb_any.py b/django/db/backends/oracle/oracledb_any.py deleted file mode 100644 index 22a9b78be8..0000000000 --- a/django/db/backends/oracle/oracledb_any.py +++ /dev/null @@ -1,20 +0,0 @@ -import warnings - -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 e from None diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index b3c429d8a2..217be19728 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -990,10 +990,6 @@ Oracle notes Django supports `Oracle Database Server`_ versions 19c and higher. Version 2.3.0 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/ .. _`oracledb`: https://oracle.github.io/python-oracledb/ diff --git a/docs/releases/6.0.txt b/docs/releases/6.0.txt index d985e14679..8708e4495a 100644 --- a/docs/releases/6.0.txt +++ b/docs/releases/6.0.txt @@ -281,6 +281,8 @@ to remove usage of these features. * The ``ForeignObject.get_reverse_joining_columns()`` method is be removed. +* Support for ``cx_Oracle`` is removed. + See :ref:`deprecated-features-5.1` for details on these changes, including how to remove usage of these features. diff --git a/tests/backends/oracle/tests.py b/tests/backends/oracle/tests.py index 0c4dbce8ba..aa5555fa2e 100644 --- a/tests/backends/oracle/tests.py +++ b/tests/backends/oracle/tests.py @@ -3,16 +3,11 @@ import unittest from unittest import mock from django.core.exceptions import ImproperlyConfigured -from django.db import DatabaseError, NotSupportedError, ProgrammingError, connection +from django.db import NotSupportedError, ProgrammingError, connection from django.db.models import BooleanField from django.test import TestCase, TransactionTestCase -from ..models import Square, VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ - -try: - from django.db.backends.oracle.oracledb_any import is_oracledb -except ImportError: - is_oracledb = False +from ..models import VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ def no_pool_connection(alias=None): @@ -85,7 +80,6 @@ class Tests(TestCase): connection.check_database_version_supported() self.assertTrue(mocked_get_database_version.called) - @unittest.skipUnless(is_oracledb, "Pool specific tests") def test_pool_set_to_true(self): new_connection = no_pool_connection(alias="default_pool") new_connection.settings_dict["OPTIONS"]["pool"] = True @@ -94,7 +88,6 @@ class Tests(TestCase): finally: new_connection.close_pool() - @unittest.skipUnless(is_oracledb, "Pool specific tests") def test_pool_reuse(self): new_connection = no_pool_connection(alias="default_pool") new_connection.settings_dict["OPTIONS"]["pool"] = { @@ -128,7 +121,6 @@ class Tests(TestCase): conn.close() new_connection.close_pool() - @unittest.skipUnless(is_oracledb, "Pool specific tests") def test_cannot_open_new_connection_in_atomic_block(self): new_connection = no_pool_connection(alias="default_pool") new_connection.settings_dict["OPTIONS"]["pool"] = True @@ -138,7 +130,6 @@ class Tests(TestCase): with self.assertRaisesMessage(ProgrammingError, msg): new_connection.ensure_connection() - @unittest.skipUnless(is_oracledb, "Pool specific tests") def test_pooling_not_support_persistent_connections(self): new_connection = no_pool_connection(alias="default_pool") new_connection.settings_dict["OPTIONS"]["pool"] = True @@ -147,48 +138,11 @@ class Tests(TestCase): with self.assertRaisesMessage(ImproperlyConfigured, msg): new_connection.pool - @unittest.skipIf(is_oracledb, "cx_oracle specific tests") - def test_cx_Oracle_not_support_pooling(self): - new_connection = no_pool_connection() - new_connection.settings_dict["OPTIONS"]["pool"] = True - msg = "Pooling isn't supported by cx_Oracle. Use python-oracledb instead." - with self.assertRaisesMessage(ImproperlyConfigured, msg): - new_connection.connect() - @unittest.skipUnless(connection.vendor == "oracle", "Oracle tests") class TransactionalTests(TransactionTestCase): available_apps = ["backends"] - def test_hidden_no_data_found_exception(self): - # "ORA-1403: no data found" exception is hidden by Oracle OCI library - # when an INSERT statement is used with a RETURNING clause (see #28859). - with connection.cursor() as cursor: - # Create trigger that raises "ORA-1403: no data found". - cursor.execute( - """ - CREATE OR REPLACE TRIGGER "TRG_NO_DATA_FOUND" - AFTER INSERT ON "BACKENDS_SQUARE" - FOR EACH ROW - BEGIN - RAISE NO_DATA_FOUND; - END; - """ - ) - try: - with self.assertRaisesMessage( - DatabaseError, - ( - 'The database did not return a new row id. Probably "ORA-1403: no ' - 'data found" was raised internally but was hidden by the Oracle ' - "OCI library (see https://code.djangoproject.com/ticket/28859)." - ), - ): - Square.objects.create(root=2, square=4) - finally: - with connection.cursor() as cursor: - cursor.execute('DROP TRIGGER "TRG_NO_DATA_FOUND"') - def test_password_with_at_sign(self): from django.db.backends.oracle.base import Database