summaryrefslogtreecommitdiff
path: root/django/db
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2018-09-26 20:18:48 +0200
committerGitHub <noreply@github.com>2018-09-26 20:18:48 +0200
commit024abe5b82d95ee60cb18a77ebf841ad715467fa (patch)
tree58375f803fde96f2badcd21e6af717fe0f063df0 /django/db
parent51da347c32f5af1e8de4be77c15d6a35b58d4059 (diff)
Fixed #29630 -- Fixed crash of sliced queries with multiple columns with the same name on Oracle 12.1.
Regression in 0899d583bdb140910698d00d17f5f1abc8774b07. Thanks Tim Graham for the review and Jani Tiainen for help.
Diffstat (limited to 'django/db')
-rw-r--r--django/db/backends/oracle/compiler.py60
-rw-r--r--django/db/backends/oracle/features.py9
-rw-r--r--django/db/backends/oracle/operations.py7
3 files changed, 76 insertions, 0 deletions
diff --git a/django/db/backends/oracle/compiler.py b/django/db/backends/oracle/compiler.py
new file mode 100644
index 0000000000..819f241f80
--- /dev/null
+++ b/django/db/backends/oracle/compiler.py
@@ -0,0 +1,60 @@
+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 restriction in Oracle 12.1 and emulate LIMIT and OFFSET with
+ a subquery.
+
+ If 'with_limits' is False, any limit/offset information is not included
+ in the query.
+ """
+ # Whether the query must be constructed using limit/offset.
+ 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 81eb03f2b5..c44b5041e7 100644
--- a/django/db/backends/oracle/features.py
+++ b/django/db/backends/oracle/features.py
@@ -1,5 +1,6 @@
from django.db.backends.base.features import BaseDatabaseFeatures
from django.db.utils import InterfaceError
+from django.utils.functional import cached_property
class DatabaseFeatures(BaseDatabaseFeatures):
@@ -55,3 +56,11 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_over_clause = True
supports_ignore_conflicts = False
max_query_params = 2**16 - 1
+
+ @cached_property
+ def has_fetch_offset_support(self):
+ return self.connection.oracle_version >= (12, 2)
+
+ @cached_property
+ def allow_sliced_subqueries_with_in(self):
+ return self.has_fetch_offset_support
diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py
index 9cfee5897d..dcbcb5e47f 100644
--- a/django/db/backends/oracle/operations.py
+++ b/django/db/backends/oracle/operations.py
@@ -8,6 +8,7 @@ from django.db.backends.utils import strip_quotes, truncate_name
from django.db.utils import DatabaseError
from django.utils import timezone
from django.utils.encoding import force_bytes
+from django.utils.functional import cached_property
from .base import Database
from .utils import BulkInsertMapper, InsertIdVar, Oracle_datetime
@@ -579,3 +580,9 @@ END;
if fields:
return self.connection.features.max_query_params // len(fields)
return len(objs)
+
+ @cached_property
+ def compiler_module(self):
+ if self.connection.features.has_fetch_offset_support:
+ return super().compiler_module
+ return 'django.db.backends.oracle.compiler'