Fixed #33992 -- Fixed queryset crash when aggregating over a group containing Exists.

A more in-depth solution is likely to make sure that we always GROUP BY
selected annotations or revisit how we use Query.exists() in the Exists
expression but that requires extra work that isn't suitable for a
backport.

Regression in e5a92d400a.

Thanks Fernando Flores Villaça for the report.
This commit is contained in:
Simon Charette 2022-09-07 22:30:06 -04:00 committed by Mariusz Felisiak
parent a2e580acf6
commit 32536b1324
4 changed files with 27 additions and 1 deletions

View File

@ -1501,6 +1501,14 @@ class Exists(Subquery):
clone.negated = not self.negated
return clone
def get_group_by_cols(self, alias=None):
# self.query only gets limited to a single row in the .exists() call
# from self.as_sql() so deferring to Query.get_group_by_cols() is
# inappropriate.
if alias is None:
return [self]
return super().get_group_by_cols(alias)
def as_sql(self, compiler, connection, template=None, **extra_context):
query = self.query.exists(using=connection.alias)
try:

View File

@ -160,7 +160,10 @@ class SQLCompiler:
expressions = self.collapse_group_by(expressions, having_group_by)
for expr in expressions:
try:
sql, params = self.compile(expr)
except EmptyResultSet:
continue
sql, params = expr.select_format(self, sql, params)
params_hash = make_hashable(params)
if (sql, params_hash) not in seen:

View File

@ -11,3 +11,7 @@ Bugfixes
* Fixed a regression in Django 4.1 that caused a migration crash on PostgreSQL
when adding a model with ``ExclusionConstraint`` (:ticket:`33982`).
* Fixed a regression in Django 4.1 that caused aggregation over a queryset that
contained an ``Exists`` annotation to crash due to too many selected columns
(:ticket:`33992`).

View File

@ -1663,6 +1663,17 @@ class AggregateTestCase(TestCase):
).values_list("publisher_count", flat=True)
self.assertSequenceEqual(books_breakdown, [1] * 6)
def test_aggregation_exists_multivalued_outeref(self):
self.assertCountEqual(
Publisher.objects.annotate(
books_exists=Exists(
Book.objects.filter(publisher=OuterRef("book__publisher"))
),
books_count=Count("book"),
),
Publisher.objects.all(),
)
def test_filter_in_subquery_or_aggregation(self):
"""
Filtering against an aggregate requires the usage of the HAVING clause.