Refs #25367 -- Moved Oracle Exists() handling to contextual methods.
Oracle requires the EXISTS expression to be wrapped in a CASE WHEN in the following cases. 1. When part of a SELECT clause. 2. When part of a ORDER BY clause. 3. When compared against another expression in the WHERE clause. This commit moves the systematic CASE WHEN wrapping of Exists.as_oracle to contextual .select_format, Lookup.as_oracle, and OrderBy.as_oracle methods in order to avoid unnecessary wrapping.
This commit is contained in:
parent
fff5186d32
commit
efa1908f66
|
@ -288,6 +288,9 @@ class BaseDatabaseFeatures:
|
||||||
# field(s)?
|
# field(s)?
|
||||||
allows_multiple_constraints_on_same_fields = True
|
allows_multiple_constraints_on_same_fields = True
|
||||||
|
|
||||||
|
# Does the backend support boolean expressions in the SELECT clause?
|
||||||
|
supports_boolean_expr_in_select_clause = True
|
||||||
|
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|
||||||
|
|
|
@ -58,3 +58,4 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
supports_partial_indexes = False
|
supports_partial_indexes = False
|
||||||
supports_slicing_ordering_in_compound = True
|
supports_slicing_ordering_in_compound = True
|
||||||
allows_multiple_constraints_on_same_fields = False
|
allows_multiple_constraints_on_same_fields = False
|
||||||
|
supports_boolean_expr_in_select_clause = False
|
||||||
|
|
|
@ -1075,11 +1075,10 @@ class Exists(Subquery):
|
||||||
sql = 'NOT {}'.format(sql)
|
sql = 'NOT {}'.format(sql)
|
||||||
return sql, params
|
return sql, params
|
||||||
|
|
||||||
def as_oracle(self, compiler, connection, template=None, **extra_context):
|
def select_format(self, compiler, sql, params):
|
||||||
# Oracle doesn't allow EXISTS() in the SELECT list, so wrap it with a
|
# Wrap EXISTS() with a CASE WHEN expression if a database backend
|
||||||
# CASE WHEN expression. Change the template since the When expression
|
# (e.g. Oracle) doesn't support boolean expression in the SELECT list.
|
||||||
# requires a left hand side (column) to compare against.
|
if not compiler.connection.features.supports_boolean_expr_in_select_clause:
|
||||||
sql, params = self.as_sql(compiler, connection, template, **extra_context)
|
|
||||||
sql = 'CASE WHEN {} THEN 1 ELSE 0 END'.format(sql)
|
sql = 'CASE WHEN {} THEN 1 ELSE 0 END'.format(sql)
|
||||||
return sql, params
|
return sql, params
|
||||||
|
|
||||||
|
@ -1140,6 +1139,19 @@ class OrderBy(BaseExpression):
|
||||||
template = 'IF(ISNULL(%(expression)s),0,1), %(expression)s %(ordering)s '
|
template = 'IF(ISNULL(%(expression)s),0,1), %(expression)s %(ordering)s '
|
||||||
return self.as_sql(compiler, connection, template=template)
|
return self.as_sql(compiler, connection, template=template)
|
||||||
|
|
||||||
|
def as_oracle(self, compiler, connection):
|
||||||
|
# Oracle doesn't allow ORDER BY EXISTS() unless it's wrapped in
|
||||||
|
# a CASE WHEN.
|
||||||
|
if isinstance(self.expression, Exists):
|
||||||
|
copy = self.copy()
|
||||||
|
# XXX: Use Case(When(self.lhs)) once support for boolean
|
||||||
|
# expressions is added to When.
|
||||||
|
exists_sql, params = compiler.compile(self.expression)
|
||||||
|
case_sql = 'CASE WHEN %s THEN 1 ELSE 0 END' % exists_sql
|
||||||
|
copy.expression = RawSQL(case_sql, params)
|
||||||
|
return copy.as_sql(compiler, connection)
|
||||||
|
return self.as_sql(compiler, connection)
|
||||||
|
|
||||||
def get_group_by_cols(self, alias=None):
|
def get_group_by_cols(self, alias=None):
|
||||||
cols = []
|
cols = []
|
||||||
for source in self.get_source_expressions():
|
for source in self.get_source_expressions():
|
||||||
|
|
|
@ -3,7 +3,7 @@ import math
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
from django.core.exceptions import EmptyResultSet
|
from django.core.exceptions import EmptyResultSet
|
||||||
from django.db.models.expressions import Func, Value
|
from django.db.models.expressions import Exists, Func, RawSQL, Value
|
||||||
from django.db.models.fields import DateTimeField, Field, IntegerField
|
from django.db.models.fields import DateTimeField, Field, IntegerField
|
||||||
from django.db.models.query_utils import RegisterLookupMixin
|
from django.db.models.query_utils import RegisterLookupMixin
|
||||||
from django.utils.datastructures import OrderedSet
|
from django.utils.datastructures import OrderedSet
|
||||||
|
@ -112,6 +112,23 @@ class Lookup:
|
||||||
def as_sql(self, compiler, connection):
|
def as_sql(self, compiler, connection):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def as_oracle(self, compiler, connection):
|
||||||
|
# Oracle doesn't allow EXISTS() to be compared to another expression
|
||||||
|
# unless it's wrapped in a CASE WHEN.
|
||||||
|
wrapped = False
|
||||||
|
exprs = []
|
||||||
|
for expr in (self.lhs, self.rhs):
|
||||||
|
if isinstance(expr, Exists):
|
||||||
|
# XXX: Use Case(When(self.lhs)) once support for boolean
|
||||||
|
# expressions is added to When.
|
||||||
|
sql, params = compiler.compile(expr)
|
||||||
|
sql = 'CASE WHEN %s THEN 1 ELSE 0 END' % sql
|
||||||
|
expr = RawSQL(sql, params)
|
||||||
|
wrapped = True
|
||||||
|
exprs.append(expr)
|
||||||
|
lookup = type(self)(*exprs) if wrapped else self
|
||||||
|
return lookup.as_sql(compiler, connection)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def contains_aggregate(self):
|
def contains_aggregate(self):
|
||||||
return self.lhs.contains_aggregate or getattr(self.rhs, 'contains_aggregate', False)
|
return self.lhs.contains_aggregate or getattr(self.rhs, 'contains_aggregate', False)
|
||||||
|
|
Loading…
Reference in New Issue