[3.0.x] Fixed #31584 -- Fixed crash when chaining values()/values_list() after Exists() annotation and aggregation on Oracle.

Oracle requires the EXISTS expression to be wrapped in a CASE WHEN in
the GROUP BY clause.

Regression in efa1908f66.
Backport of 3a941230c8 from master
This commit is contained in:
Mariusz Felisiak 2020-05-14 15:07:08 +02:00 committed by Carlton Gibson
parent 49bbf6570d
commit 92acf1022f
5 changed files with 9 additions and 5 deletions

View File

@ -289,7 +289,8 @@ 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? # Does the backend support boolean expressions in SELECT and GROUP BY
# clauses?
supports_boolean_expr_in_select_clause = True supports_boolean_expr_in_select_clause = True
def __init__(self, connection): def __init__(self, connection):

View File

@ -1108,7 +1108,8 @@ class Exists(Subquery):
def select_format(self, compiler, sql, params): def select_format(self, compiler, sql, params):
# Wrap EXISTS() with a CASE WHEN expression if a database backend # Wrap EXISTS() with a CASE WHEN expression if a database backend
# (e.g. Oracle) doesn't support boolean expression in the SELECT list. # (e.g. Oracle) doesn't support boolean expression in SELECT or GROUP
# BY list.
if not compiler.connection.features.supports_boolean_expr_in_select_clause: if not compiler.connection.features.supports_boolean_expr_in_select_clause:
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

View File

@ -135,6 +135,7 @@ class SQLCompiler:
for expr in expressions: for expr in expressions:
sql, params = self.compile(expr) sql, params = self.compile(expr)
sql, params = expr.select_format(self, sql, params)
params_hash = make_hashable(params) params_hash = make_hashable(params)
if (sql, params_hash) not in seen: if (sql, params_hash) not in seen:
result.append((sql, params)) result.append((sql, params))

View File

@ -18,3 +18,7 @@ Bugfixes
* Fixed a regression in Django 3.0 where aggregates used wrong annotations when * Fixed a regression in Django 3.0 where aggregates used wrong annotations when
a queryset has multiple subqueries annotations (:ticket:`31568`). a queryset has multiple subqueries annotations (:ticket:`31568`).
* Fixed a regression in Django 3.0 where ``QuerySet.values()`` and
``values_list()`` crashed if a queryset contained an aggregation and an
``Exists()`` annotation on Oracle (:ticket:`31584`).

View File

@ -1,9 +1,7 @@
import datetime import datetime
from decimal import Decimal from decimal import Decimal
from unittest import skipIf
from django.core.exceptions import FieldDoesNotExist, FieldError from django.core.exceptions import FieldDoesNotExist, FieldError
from django.db import connection
from django.db.models import ( from django.db.models import (
BooleanField, CharField, Count, DateTimeField, Exists, ExpressionWrapper, BooleanField, CharField, Count, DateTimeField, Exists, ExpressionWrapper,
F, Func, IntegerField, Max, NullBooleanField, OuterRef, Q, Subquery, Sum, F, Func, IntegerField, Max, NullBooleanField, OuterRef, Q, Subquery, Sum,
@ -623,7 +621,6 @@ class NonAggregateAnnotationTestCase(TestCase):
).values('name') ).values('name')
self.assertCountEqual(publisher_books_qs, [{'name': 'Sams'}, {'name': 'Morgan Kaufmann'}]) self.assertCountEqual(publisher_books_qs, [{'name': 'Sams'}, {'name': 'Morgan Kaufmann'}])
@skipIf(connection.vendor == 'oracle', 'See https://code.djangoproject.com/ticket/31584')
def test_annotation_exists_aggregate_values_chaining(self): def test_annotation_exists_aggregate_values_chaining(self):
qs = Book.objects.values('publisher').annotate( qs = Book.objects.values('publisher').annotate(
has_authors=Exists(Book.authors.through.objects.filter(book=OuterRef('pk'))), has_authors=Exists(Book.authors.through.objects.filter(book=OuterRef('pk'))),