Fixed #34717 -- Fixed QuerySet.aggregate() crash when referencing window functions.

Regression in 59bea9efd2.

Refs #28477.

Thanks younes-chaoui for the report.
This commit is contained in:
Simon Charette 2023-07-17 12:51:54 -04:00 committed by Mariusz Felisiak
parent f8c43aca46
commit 68912e4f6f
3 changed files with 30 additions and 1 deletions

View File

@ -403,6 +403,7 @@ class Query(BaseExpression):
# Store annotation mask prior to temporarily adding aggregations for # Store annotation mask prior to temporarily adding aggregations for
# resolving purpose to facilitate their subsequent removal. # resolving purpose to facilitate their subsequent removal.
refs_subquery = False refs_subquery = False
refs_window = False
replacements = {} replacements = {}
annotation_select_mask = self.annotation_select_mask annotation_select_mask = self.annotation_select_mask
for alias, aggregate_expr in aggregate_exprs.items(): for alias, aggregate_expr in aggregate_exprs.items():
@ -419,6 +420,10 @@ class Query(BaseExpression):
getattr(self.annotations[ref], "subquery", False) getattr(self.annotations[ref], "subquery", False)
for ref in aggregate.get_refs() for ref in aggregate.get_refs()
) )
refs_window |= any(
getattr(self.annotations[ref], "contains_over_clause", True)
for ref in aggregate.get_refs()
)
aggregate = aggregate.replace_expressions(replacements) aggregate = aggregate.replace_expressions(replacements)
self.annotations[alias] = aggregate self.annotations[alias] = aggregate
replacements[Ref(alias, aggregate)] = aggregate replacements[Ref(alias, aggregate)] = aggregate
@ -451,6 +456,7 @@ class Query(BaseExpression):
or self.is_sliced or self.is_sliced
or has_existing_aggregation or has_existing_aggregation
or refs_subquery or refs_subquery
or refs_window
or qualify or qualify
or self.distinct or self.distinct
or self.combinator or self.combinator

View File

@ -9,4 +9,6 @@ Django 4.2.4 fixes several bugs in 4.2.3.
Bugfixes Bugfixes
======== ========
* ... * Fixed a regression in Django 4.2 that caused a crash of
``QuerySet.aggregate()`` with aggregates referencing window functions
(:ticket:`34717`).

View File

@ -28,6 +28,7 @@ from django.db.models import (
Value, Value,
Variance, Variance,
When, When,
Window,
) )
from django.db.models.expressions import Func, RawSQL from django.db.models.expressions import Func, RawSQL
from django.db.models.functions import ( from django.db.models.functions import (
@ -2207,3 +2208,23 @@ class AggregateAnnotationPruningTests(TestCase):
sql = ctx.captured_queries[0]["sql"].lower() sql = ctx.captured_queries[0]["sql"].lower()
self.assertEqual(sql.count("select"), 3, "Subquery wrapping required") self.assertEqual(sql.count("select"), 3, "Subquery wrapping required")
self.assertEqual(aggregate, {"sum_total_books": 3}) self.assertEqual(aggregate, {"sum_total_books": 3})
@skipUnlessDBFeature("supports_over_clause")
def test_referenced_window_requires_wrapping(self):
total_books_qs = Book.objects.annotate(
avg_publisher_pages=Coalesce(
Window(Avg("pages"), partition_by=F("publisher")),
0.0,
)
)
with self.assertNumQueries(1) as ctx:
aggregate = total_books_qs.aggregate(
sum_avg_publisher_pages=Sum("avg_publisher_pages"),
books_count=Count("id"),
)
sql = ctx.captured_queries[0]["sql"].lower()
self.assertEqual(sql.count("select"), 2, "Subquery wrapping required")
self.assertEqual(
aggregate,
{"sum_avg_publisher_pages": 1100.0, "books_count": 2},
)