From 0899d583bdb140910698d00d17f5f1abc8774b07 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 9 Oct 2017 18:07:03 +0200 Subject: [PATCH] Fixed #28670 -- Added FETCH/OFFSET support on Oracle. Thanks Tim Graham for the review. --- django/db/backends/oracle/compiler.py | 62 ------------------------- django/db/backends/oracle/features.py | 1 - django/db/backends/oracle/operations.py | 11 +++-- django/db/models/sql/compiler.py | 10 ++-- 4 files changed, 13 insertions(+), 71 deletions(-) delete mode 100644 django/db/backends/oracle/compiler.py diff --git a/django/db/backends/oracle/compiler.py b/django/db/backends/oracle/compiler.py deleted file mode 100644 index b568e59e9e..0000000000 --- a/django/db/backends/oracle/compiler.py +++ /dev/null @@ -1,62 +0,0 @@ -from django.db import NotSupportedError -from django.db.models.sql import compiler - - -class SQLCompiler(compiler.SQLCompiler): - def as_sql(self, with_limits=True, with_col_aliases=False): - """ - Create the SQL for this query. Return the SQL string and list - of parameters. This is overridden from the original Query class - to handle the additional SQL Oracle requires to emulate LIMIT - and OFFSET. - - If 'with_limits' is False, any limit/offset information is not - included in the query. - """ - # The `do_offset` flag indicates whether we need to construct - # the SQL needed to use limit/offset with Oracle. - do_offset = with_limits and (self.query.high_mark is not None or self.query.low_mark) - if not do_offset: - sql, params = super().as_sql(with_limits=False, with_col_aliases=with_col_aliases) - elif not self.connection.features.supports_select_for_update_with_limit and self.query.select_for_update: - raise NotSupportedError( - 'LIMIT/OFFSET is not supported with select_for_update on this ' - 'database backend.' - ) - else: - sql, params = super().as_sql(with_limits=False, with_col_aliases=True) - # Wrap the base query in an outer SELECT * with boundaries on - # the "_RN" column. This is the canonical way to emulate LIMIT - # and OFFSET on Oracle. - high_where = '' - if self.query.high_mark is not None: - high_where = 'WHERE ROWNUM <= %d' % (self.query.high_mark,) - - if self.query.low_mark: - sql = ( - 'SELECT * FROM (SELECT "_SUB".*, ROWNUM AS "_RN" FROM (%s) ' - '"_SUB" %s) WHERE "_RN" > %d' % (sql, high_where, self.query.low_mark) - ) - else: - # Simplify the query to support subqueries if there's no offset. - sql = ( - 'SELECT * FROM (SELECT "_SUB".* FROM (%s) "_SUB" %s)' % (sql, high_where) - ) - - return sql, params - - -class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler): - pass - - -class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler): - pass - - -class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler): - pass - - -class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler): - pass diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py index 71421f0df8..cb2fa7d558 100644 --- a/django/db/backends/oracle/features.py +++ b/django/db/backends/oracle/features.py @@ -12,7 +12,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): has_select_for_update_of = True select_for_update_of_column = True can_return_id_from_insert = True - allow_sliced_subqueries = False can_introspect_autofield = True supports_subqueries_in_group_by = False supports_transactions = True diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index 5c0f6accae..fa29a8925a 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -13,8 +13,6 @@ from .utils import BulkInsertMapper, InsertIdVar, Oracle_datetime class DatabaseOperations(BaseDatabaseOperations): - compiler_module = "django.db.backends.oracle.compiler" - # Oracle uses NUMBER(11) and NUMBER(19) for integer fields. integer_field_ranges = { 'SmallIntegerField': (-99999999999, 99999999999), @@ -233,8 +231,15 @@ END; else: return "%s" + def no_limit_value(self): + return None + def limit_offset_sql(self, low_mark, high_mark): - return '' + fetch, offset = self._get_limit_offset_params(low_mark, high_mark) + return '%s%s' % ( + (' OFFSET %d ROWS' % offset) if offset else '', + (' FETCH FIRST %d ROWS ONLY' % fetch) if fetch else '', + ) def last_executed_query(self, cursor, sql, params): # https://cx-oracle.readthedocs.io/en/latest/cursor.html#Cursor.statement diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 078a8477af..93ba60638a 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -439,6 +439,8 @@ class SQLCompiler: try: extra_select, order_by, group_by = self.pre_sql_setup() for_update_part = None + # Is a LIMIT/OFFSET clause needed? + with_limit_offset = with_limits and (self.query.high_mark is not None or self.query.low_mark) combinator = self.query.combinator features = self.connection.features if combinator: @@ -479,7 +481,7 @@ class SQLCompiler: if self.connection.get_autocommit(): raise TransactionManagementError('select_for_update cannot be used outside of a transaction.') - if with_limits and not self.connection.features.supports_select_for_update_with_limit: + if with_limit_offset and not self.connection.features.supports_select_for_update_with_limit: raise NotSupportedError( 'LIMIT/OFFSET is not supported with ' 'select_for_update on this database backend.' @@ -531,10 +533,8 @@ class SQLCompiler: params.extend(o_params) result.append('ORDER BY %s' % ', '.join(ordering)) - if with_limits: - limit_offset_sql = self.connection.ops.limit_offset_sql(self.query.low_mark, self.query.high_mark) - if limit_offset_sql: - result.append(limit_offset_sql) + if with_limit_offset: + result.append(self.connection.ops.limit_offset_sql(self.query.low_mark, self.query.high_mark)) if for_update_part and not self.connection.features.for_update_after_from: result.append(for_update_part)