summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2023-10-15 21:59:15 -0400
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-10-16 06:15:36 +0200
commit803caec60bed3b282b9f9961860a467160c0c8f1 (patch)
tree051ca6113da9dacd430babe0e7f28933b9dfe7d7
parentcaec4f4a6fc0f0ba60e9d4e4e7f3bfb6f6d492a7 (diff)
[4.2.x] Fixed #34798 -- Fixed QuerySet.aggregate() crash when referencing expressions containing subqueries.
Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7, complements e5c844d6f2a4ac6ae674d741b5f1fa2a688cedf4. Refs #28477, #34551. Thanks Haldun Komsuoglu for the report. Backport of 3b4a571275d967512866012955eb0b3ae486d63c from main
-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(