Sfoglia il codice sorgente

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

Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7.

Refs #28477.

Thanks Denis Roldán and Mariusz for the test.
Simon Charette 1 anno fa
parent
commit
e5c844d6f2

+ 1 - 0
django/db/models/expressions.py

@@ -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.

+ 6 - 0
django/db/models/sql/query.py

@@ -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

+ 4 - 0
docs/releases/4.2.2.txt

@@ -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`).

+ 20 - 0
tests/aggregation/tests.py

@@ -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})