Fixed #34551 -- Fixed QuerySet.aggregate() crash when referencing subqueries.

Regression in 59bea9efd2.

Refs #28477.

Thanks Denis Roldán and Mariusz for the test.
This commit is contained in:
Simon Charette 2023-05-21 23:57:49 -04:00 committed by Mariusz Felisiak
parent 2ee01747c3
commit e5c844d6f2
4 changed files with 31 additions and 0 deletions

View File

@ -1541,6 +1541,7 @@ class Subquery(BaseExpression, Combinable):
template = "(%(subquery)s)"
contains_aggregate = False
empty_result_set_value = None
subquery = True
def __init__(self, queryset, output_field=None, **extra):
# Allow the usage of both QuerySet and sql.Query objects.

View File

@ -402,6 +402,7 @@ class Query(BaseExpression):
return {}
# Store annotation mask prior to temporarily adding aggregations for
# resolving purpose to facilitate their subsequent removal.
refs_subquery = False
replacements = {}
annotation_select_mask = self.annotation_select_mask
for alias, aggregate_expr in aggregate_exprs.items():
@ -414,6 +415,10 @@ class Query(BaseExpression):
# Temporarily add aggregate to annotations to allow remaining
# members of `aggregates` to resolve against each others.
self.append_annotation_mask([alias])
refs_subquery |= any(
getattr(self.annotations[ref], "subquery", False)
for ref in aggregate.get_refs()
)
aggregate = aggregate.replace_expressions(replacements)
self.annotations[alias] = aggregate
replacements[Ref(alias, aggregate)] = aggregate
@ -445,6 +450,7 @@ class Query(BaseExpression):
isinstance(self.group_by, tuple)
or self.is_sliced
or has_existing_aggregation
or refs_subquery
or qualify
or self.distinct
or self.combinator

View File

@ -32,3 +32,7 @@ Bugfixes
* Fixed a regression in Django 4.2 that caused a crash of
``QuerySet.aggregate()`` with expressions referencing other aggregates
(:ticket:`34551`).
* Fixed a regression in Django 4.2 that caused a crash of
``QuerySet.aggregate()`` with aggregates referencing subqueries
(:ticket:`34551`).

View File

@ -2187,3 +2187,23 @@ class AggregateAnnotationPruningTests(TestCase):
mod_count=Count("*")
)
self.assertEqual(queryset.count(), 1)
def test_referenced_subquery_requires_wrapping(self):
total_books_qs = (
Author.book_set.through.objects.values("author")
.filter(author=OuterRef("pk"))
.annotate(total=Count("book"))
)
with self.assertNumQueries(1) as ctx:
aggregate = (
Author.objects.annotate(
total_books=Subquery(total_books_qs.values("total"))
)
.values("pk", "total_books")
.aggregate(
sum_total_books=Sum("total_books"),
)
)
sql = ctx.captured_queries[0]["sql"].lower()
self.assertEqual(sql.count("select"), 3, "Subquery wrapping required")
self.assertEqual(aggregate, {"sum_total_books": 3})