[4.2.x] Fixed #34798 -- Fixed QuerySet.aggregate() crash when referencing expressions containing subqueries.

Regression in 59bea9efd2,
complements e5c844d6f2.

Refs #28477, #34551.

Thanks Haldun Komsuoglu for the report.

Backport of 3b4a571275 from main
This commit is contained in:
Simon Charette 2023-10-15 21:59:15 -04:00 committed by Mariusz Felisiak
parent caec4f4a6f
commit 803caec60b
4 changed files with 32 additions and 2 deletions

View File

@ -255,6 +255,13 @@ class BaseExpression:
for expr in self.get_source_expressions()
)
@cached_property
def contains_subquery(self):
return any(
expr and (getattr(expr, "subquery", False) or expr.contains_subquery)
for expr in self.get_source_expressions()
)
def resolve_expression(
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
):

View File

@ -404,7 +404,7 @@ class Query(BaseExpression):
# members of `aggregates` to resolve against each others.
self.append_annotation_mask([alias])
refs_subquery |= any(
getattr(self.annotations[ref], "subquery", False)
getattr(self.annotations[ref], "contains_subquery", False)
for ref in aggregate.get_refs()
)
refs_window |= any(

View File

@ -9,4 +9,6 @@ Django 4.2.7 fixes several bugs in 4.2.6.
Bugfixes
========
...
* Fixed a regression in Django 4.2 that caused a crash of
``QuerySet.aggregate()`` with aggregates referencing expressions containing
subqueries (:ticket:`34798`).

View File

@ -2260,6 +2260,27 @@ class AggregateAnnotationPruningTests(TestCase):
self.assertEqual(sql.count("select"), 3, "Subquery wrapping required")
self.assertEqual(aggregate, {"sum_total_books": 3})
def test_referenced_composed_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")),
total_books_ref=F("total_books") / 1,
)
.values("pk", "total_books_ref")
.aggregate(
sum_total_books=Sum("total_books_ref"),
)
)
sql = ctx.captured_queries[0]["sql"].lower()
self.assertEqual(sql.count("select"), 3, "Subquery wrapping required")
self.assertEqual(aggregate, {"sum_total_books": 3})
@skipUnlessDBFeature("supports_over_clause")
def test_referenced_window_requires_wrapping(self):
total_books_qs = Book.objects.annotate(