From c72001644fa794b82fa88a7d2ecc20197b01b6f2 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Tue, 12 Dec 2023 16:07:31 +0000 Subject: [PATCH] Updated DatabaseFeatures.bare_select_suffix on Oracle 23c. https://docs.oracle.com/en/database/oracle/oracle-database/23/nfcoa/application-development.html#GUID-4EB70EB9-4EE3-4FE2-99C4-86F7AAC60F12 --- django/db/backends/oracle/features.py | 8 ++++++-- django/db/backends/oracle/operations.py | 7 +++++-- django/db/models/functions/text.py | 5 +++-- tests/backends/oracle/tests.py | 3 ++- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py index 05632bb10e51..002e03e2b534 100644 --- a/django/db/backends/oracle/features.py +++ b/django/db/backends/oracle/features.py @@ -37,7 +37,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): requires_literal_defaults = True supports_default_keyword_in_bulk_insert = False closed_cursor_error_class = InterfaceError - bare_select_suffix = " FROM DUAL" # Select for update with limit can be achieved on Oracle, but not with the # current backend. supports_select_for_update_with_limit = False @@ -159,9 +158,10 @@ def test_collations(self): @cached_property def supports_collation_on_charfield(self): + sql = "SELECT CAST('a' AS VARCHAR2(4001))" + self.bare_select_suffix with self.connection.cursor() as cursor: try: - cursor.execute("SELECT CAST('a' AS VARCHAR2(4001)) FROM dual") + cursor.execute(sql) except DatabaseError as e: if e.args[0].code == 910: return False @@ -183,3 +183,7 @@ def supports_boolean_expr_in_select_clause(self): @cached_property def supports_aggregation_over_interval_types(self): return self.connection.oracle_version >= (23,) + + @cached_property + def bare_select_suffix(self): + return "" if self.connection.oracle_version >= (23,) else " FROM DUAL" diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index 11043a9b51ba..9e8172b80a10 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -54,7 +54,7 @@ class DatabaseOperations(BaseDatabaseOperations): SELECT NVL(last_number - cache_size, 0) INTO seq_value FROM user_sequences WHERE sequence_name = seq_name; WHILE table_value > seq_value LOOP - EXECUTE IMMEDIATE 'SELECT "'||seq_name||'".nextval FROM DUAL' + EXECUTE IMMEDIATE 'SELECT "'||seq_name||'".nextval%(suffix)s' INTO seq_value; END LOOP; END; @@ -527,6 +527,7 @@ def sequence_reset_by_name_sql(self, style, sequences): "column": column, "table_name": strip_quotes(table), "column_name": strip_quotes(column), + "suffix": self.connection.features.bare_select_suffix, } sql.append(query) return sql @@ -550,6 +551,7 @@ def sequence_reset_sql(self, style, model_list): "column": column, "table_name": strip_quotes(table), "column_name": strip_quotes(column), + "suffix": self.connection.features.bare_select_suffix, } ) # Only one AutoField is allowed per model, so don't @@ -683,7 +685,8 @@ def bulk_insert_sql(self, fields, placeholder_rows): if not query: placeholder = "%s col_%s" % (placeholder, i) select.append(placeholder) - query.append("SELECT %s FROM DUAL" % ", ".join(select)) + suffix = self.connection.features.bare_select_suffix + query.append(f"SELECT %s{suffix}" % ", ".join(select)) # Bulk insert to tables with Oracle identity columns causes Oracle to # add sequence.nextval to it. Sequence.nextval cannot be used with the # UNION operator. To prevent incorrect SQL, move UNION to a subquery. diff --git a/django/db/models/functions/text.py b/django/db/models/functions/text.py index 500fbea19403..392061880c72 100644 --- a/django/db/models/functions/text.py +++ b/django/db/models/functions/text.py @@ -261,13 +261,14 @@ class Reverse(Transform): def as_oracle(self, compiler, connection, **extra_context): # REVERSE in Oracle is undocumented and doesn't support multi-byte # strings. Use a special subquery instead. + suffix = connection.features.bare_select_suffix sql, params = super().as_sql( compiler, connection, template=( "(SELECT LISTAGG(s) WITHIN GROUP (ORDER BY n DESC) FROM " - "(SELECT LEVEL n, SUBSTR(%(expressions)s, LEVEL, 1) s " - "FROM DUAL CONNECT BY LEVEL <= LENGTH(%(expressions)s)) " + f"(SELECT LEVEL n, SUBSTR(%(expressions)s, LEVEL, 1) s{suffix} " + "CONNECT BY LEVEL <= LENGTH(%(expressions)s)) " "GROUP BY %(expressions)s)" ), **extra_context, diff --git a/tests/backends/oracle/tests.py b/tests/backends/oracle/tests.py index 1ad98a88cf21..a4aa26cd2edd 100644 --- a/tests/backends/oracle/tests.py +++ b/tests/backends/oracle/tests.py @@ -43,8 +43,9 @@ def test_order_of_nls_parameters(self): An 'almost right' datetime works with configured NLS parameters (#18465). """ + suffix = connection.features.bare_select_suffix with connection.cursor() as cursor: - query = "select 1 from dual where '1936-12-29 00:00' < sysdate" + query = f"SELECT 1{suffix} WHERE '1936-12-29 00:00' < SYSDATE" # The query succeeds without errors - pre #18465 this # wasn't the case. cursor.execute(query)