Fixed #28670 -- Added FETCH/OFFSET support on Oracle.

Thanks Tim Graham for the review.
This commit is contained in:
Mariusz Felisiak 2017-10-09 18:07:03 +02:00 committed by GitHub
parent 81d5320db5
commit 0899d583bd
4 changed files with 13 additions and 71 deletions

View File

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

View File

@ -12,7 +12,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
has_select_for_update_of = True has_select_for_update_of = True
select_for_update_of_column = True select_for_update_of_column = True
can_return_id_from_insert = True can_return_id_from_insert = True
allow_sliced_subqueries = False
can_introspect_autofield = True can_introspect_autofield = True
supports_subqueries_in_group_by = False supports_subqueries_in_group_by = False
supports_transactions = True supports_transactions = True

View File

@ -13,8 +13,6 @@ from .utils import BulkInsertMapper, InsertIdVar, Oracle_datetime
class DatabaseOperations(BaseDatabaseOperations): class DatabaseOperations(BaseDatabaseOperations):
compiler_module = "django.db.backends.oracle.compiler"
# Oracle uses NUMBER(11) and NUMBER(19) for integer fields. # Oracle uses NUMBER(11) and NUMBER(19) for integer fields.
integer_field_ranges = { integer_field_ranges = {
'SmallIntegerField': (-99999999999, 99999999999), 'SmallIntegerField': (-99999999999, 99999999999),
@ -233,8 +231,15 @@ END;
else: else:
return "%s" return "%s"
def no_limit_value(self):
return None
def limit_offset_sql(self, low_mark, high_mark): 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): def last_executed_query(self, cursor, sql, params):
# https://cx-oracle.readthedocs.io/en/latest/cursor.html#Cursor.statement # https://cx-oracle.readthedocs.io/en/latest/cursor.html#Cursor.statement

View File

@ -439,6 +439,8 @@ class SQLCompiler:
try: try:
extra_select, order_by, group_by = self.pre_sql_setup() extra_select, order_by, group_by = self.pre_sql_setup()
for_update_part = None 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 combinator = self.query.combinator
features = self.connection.features features = self.connection.features
if combinator: if combinator:
@ -479,7 +481,7 @@ class SQLCompiler:
if self.connection.get_autocommit(): if self.connection.get_autocommit():
raise TransactionManagementError('select_for_update cannot be used outside of a transaction.') 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( raise NotSupportedError(
'LIMIT/OFFSET is not supported with ' 'LIMIT/OFFSET is not supported with '
'select_for_update on this database backend.' 'select_for_update on this database backend.'
@ -531,10 +533,8 @@ class SQLCompiler:
params.extend(o_params) params.extend(o_params)
result.append('ORDER BY %s' % ', '.join(ordering)) result.append('ORDER BY %s' % ', '.join(ordering))
if with_limits: if with_limit_offset:
limit_offset_sql = self.connection.ops.limit_offset_sql(self.query.low_mark, self.query.high_mark) result.append(self.connection.ops.limit_offset_sql(self.query.low_mark, self.query.high_mark))
if limit_offset_sql:
result.append(limit_offset_sql)
if for_update_part and not self.connection.features.for_update_after_from: if for_update_part and not self.connection.features.for_update_after_from:
result.append(for_update_part) result.append(for_update_part)