summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/db/models/expressions.py7
-rw-r--r--django/db/models/sql/query.py2
-rw-r--r--docs/releases/4.2.7.txt4
-rw-r--r--tests/aggregation/tests.py21
4 files changed, 32 insertions, 2 deletions
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index cc78ac8773..0847eb0931 100644
--- a/django/db/models/expressions.py
+++ b/django/db/models/expressions.py
@@ -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
):
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 27f94175bb..0709c4a45f 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -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(
diff --git a/docs/releases/4.2.7.txt b/docs/releases/4.2.7.txt
index a883a7dff1..c6d513128c 100644
--- a/docs/releases/4.2.7.txt
+++ b/docs/releases/4.2.7.txt
@@ -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`).
diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py
index ad00afdcc1..8d8e46e312 100644
--- a/tests/aggregation/tests.py
+++ b/tests/aggregation/tests.py
@@ -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(