diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index c91a1174f8..18e1ccffee 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -62,7 +62,7 @@ from .features import DatabaseFeatures # NOQA isort:skip from .introspection import DatabaseIntrospection # NOQA isort:skip from .operations import DatabaseOperations # NOQA isort:skip from .schema import DatabaseSchemaEditor # NOQA isort:skip -from .utils import Oracle_datetime, convert_unicode # NOQA isort:skip +from .utils import Oracle_datetime # NOQA isort:skip class _UninitializedOperatorsDescriptor(object): @@ -208,8 +208,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): return conn_params def get_new_connection(self, conn_params): - conn_string = convert_unicode(self._connect_string()) - return Database.connect(conn_string, **conn_params) + return Database.connect(self._connect_string(), **conn_params) def init_connection_state(self): cursor = self.create_cursor() @@ -246,13 +245,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): self.operators = self._standard_operators self.pattern_ops = self._standard_pattern_ops cursor.close() - - try: - self.connection.stmtcachesize = 20 - except AttributeError: - # Django docs specify cx_Oracle version 4.3.1 or higher, but - # stmtcachesize is available only in 4.3.2 and up. - pass + self.connection.stmtcachesize = 20 # Ensure all changes are preserved even when AUTOCOMMIT is False. if not self.get_autocommit(): self.commit() @@ -265,7 +258,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): try: return self.connection.commit() except Database.DatabaseError as e: - # cx_Oracle 5.0.4 raises a cx_Oracle.DatabaseError exception + # cx_Oracle raises a cx_Oracle.DatabaseError exception # with the following attributes and values: # code = 2091 # message = 'ORA-02091: transaction rolled back @@ -363,7 +356,7 @@ class OracleParam(object): else: # To transmit to the database, we need Unicode if supported # To get size right, we must consider bytes. - self.force_bytes = convert_unicode(param, cursor.charset, strings_only) + self.force_bytes = force_text(param, cursor.charset, strings_only) if isinstance(self.force_bytes, six.string_types): # We could optimize by only converting up to 4000 bytes here string_size = len(force_bytes(param, cursor.charset, strings_only)) @@ -459,11 +452,11 @@ class FormatStylePlaceholderCursor(object): query = query[:-1] if params is None: params = [] - query = convert_unicode(query, self.charset) + query = query elif hasattr(params, 'keys'): # Handle params as dict args = {k: ":%s" % k for k in params.keys()} - query = convert_unicode(query % args, self.charset) + query = query % args elif unify_by_values and len(params) > 0: # Handle params as a dict with unified query parameters by their # values. It can be used only in single query execute() because @@ -480,23 +473,17 @@ class FormatStylePlaceholderCursor(object): params_dict = {param: ':arg%d' % i for i, param in enumerate(set(params))} args = [params_dict[param] for param in params] params = dict(zip(params_dict.values(), list(zip(*params_dict.keys()))[0])) - query = convert_unicode(query % tuple(args), self.charset) + query = query % tuple(args) else: # Handle params as sequence args = [(':arg%d' % i) for i in range(len(params))] - query = convert_unicode(query % tuple(args), self.charset) - return query, self._format_params(params) + query = query % tuple(args) + return force_text(query, self.charset), self._format_params(params) def execute(self, query, params=None): query, params = self._fix_for_params(query, params, unify_by_values=True) self._guess_input_sizes([params]) - try: - return self.cursor.execute(query, self._param_generator(params)) - except Database.DatabaseError as e: - # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. - if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, Database.IntegrityError): - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) - raise + return self.cursor.execute(query, self._param_generator(params)) def executemany(self, query, params=None): if not params: @@ -509,13 +496,7 @@ class FormatStylePlaceholderCursor(object): # more than once, we can't make it lazy by using a generator formatted = [firstparams] + [self._format_params(p) for p in params_iter] self._guess_input_sizes(formatted) - try: - return self.cursor.executemany(query, [self._param_generator(p) for p in formatted]) - except Database.DatabaseError as e: - # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. - if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, Database.IntegrityError): - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) - raise + return self.cursor.executemany(query, [self._param_generator(p) for p in formatted]) def fetchone(self): row = self.cursor.fetchone() diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index b800339809..d55c003383 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -11,7 +11,7 @@ from django.utils import six, timezone from django.utils.encoding import force_bytes, force_text from .base import Database -from .utils import InsertIdVar, Oracle_datetime, convert_unicode +from .utils import InsertIdVar, Oracle_datetime class DatabaseOperations(BaseDatabaseOperations): @@ -310,10 +310,10 @@ WHEN (new.%(col_name)s IS NULL) return "RETURNING %s INTO %%s", (InsertIdVar(),) def savepoint_create_sql(self, sid): - return convert_unicode("SAVEPOINT " + self.quote_name(sid)) + return "SAVEPOINT " + self.quote_name(sid) def savepoint_rollback_sql(self, sid): - return convert_unicode("ROLLBACK TO SAVEPOINT " + self.quote_name(sid)) + return "ROLLBACK TO SAVEPOINT " + self.quote_name(sid) def sql_flush(self, style, tables, sequences, allow_cascade=False): # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', diff --git a/django/db/backends/oracle/utils.py b/django/db/backends/oracle/utils.py index c63bf39c1c..e89a9b70cb 100644 --- a/django/db/backends/oracle/utils.py +++ b/django/db/backends/oracle/utils.py @@ -1,18 +1,7 @@ import datetime -from django.utils.encoding import force_bytes, force_text - from .base import Database -# Check whether cx_Oracle was compiled with the WITH_UNICODE option if cx_Oracle is pre-5.1. This will -# also be True for cx_Oracle 5.1 and in Python 3.0. See #19606 -if int(Database.version.split('.', 1)[0]) >= 5 and \ - (int(Database.version.split('.', 2)[1]) >= 1 or - not hasattr(Database, 'UNICODE')): - convert_unicode = force_text -else: - convert_unicode = force_bytes - class InsertIdVar(object): """ diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 65368ee27e..51feceea04 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -731,17 +731,7 @@ Oracle notes ============ Django supports `Oracle Database Server`_ versions 11.2 and higher. Version -4.3.1 or higher of the `cx_Oracle`_ Python driver is required, although we -recommend version 5.1.3 or later as these versions support Python 3. - -Note that due to a Unicode-corruption bug in ``cx_Oracle`` 5.0, that -version of the driver should **not** be used with Django; -``cx_Oracle`` 5.0.1 resolved this issue, so if you'd like to use a -more recent ``cx_Oracle``, use version 5.0.1. - -``cx_Oracle`` 5.0.1 or greater can optionally be compiled with the -``WITH_UNICODE`` environment variable. This is recommended but not -required. +5.2 or higher of the `cx_Oracle`_ Python driver is required. .. _`Oracle Database Server`: http://www.oracle.com/ .. _`cx_Oracle`: http://cx-oracle.sourceforge.net/ diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index a895f40c5f..33429a21b5 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -698,6 +698,8 @@ Miscellaneous leaves ``request.POST`` immutable. If you're modifying that ``QueryDict``, you must now first copy it, e.g. ``request.POST.copy()``. +* Support for ``cx_Oracle`` < 5.2 is removed. + .. _deprecated-features-1.11: Features deprecated in 1.11 diff --git a/tests/backends/tests.py b/tests/backends/tests.py index f27b22d8af..e17679a4ba 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -86,11 +86,8 @@ class OracleTests(unittest.TestCase): def test_dbms_session(self): # If the backend is Oracle, test that we can call a standard # stored procedure through our cursor wrapper. - from django.db.backends.oracle.base import convert_unicode - with connection.cursor() as cursor: - cursor.callproc(convert_unicode('DBMS_SESSION.SET_IDENTIFIER'), - [convert_unicode('_django_testing!')]) + cursor.callproc('DBMS_SESSION.SET_IDENTIFIER', ['_django_testing!']) def test_cursor_var(self): # If the backend is Oracle, test that we can pass cursor variables