summaryrefslogtreecommitdiff
path: root/django/db
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2023-05-21 23:49:05 -0400
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-05-23 06:25:27 +0200
commit2ee01747c32a7275a7a1a5f7862acba7db764921 (patch)
tree7a668fab0cc6996f7491fbe988500d0ee1f81992 /django/db
parent89f10a80d7e681cd0cccf22d932e380f51bd3524 (diff)
Refs #34551 -- Fixed QuerySet.aggregate() crash on precending aggregation reference.
Regression in 1297c0d0d76a708017fe196b61a0ab324df76954. Refs #31679.
Diffstat (limited to 'django/db')
-rw-r--r--django/db/models/aggregates.py9
-rw-r--r--django/db/models/sql/query.py16
2 files changed, 22 insertions, 3 deletions
diff --git a/django/db/models/aggregates.py b/django/db/models/aggregates.py
index e672f0aeb0..a778cd413b 100644
--- a/django/db/models/aggregates.py
+++ b/django/db/models/aggregates.py
@@ -65,7 +65,14 @@ class Aggregate(Func):
c.filter = c.filter and c.filter.resolve_expression(
query, allow_joins, reuse, summarize
)
- if not summarize:
+ if summarize:
+ # Summarized aggregates cannot refer to summarized aggregates.
+ for ref in c.get_refs():
+ if query.annotations[ref].is_summary:
+ raise FieldError(
+ f"Cannot compute {c.name}('{ref}'): '{ref}' is an aggregate"
+ )
+ elif not self.is_summary:
# Call Aggregate.get_source_expressions() to avoid
# returning self.filter and including that in this loop.
expressions = super(Aggregate, c).get_source_expressions()
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 252e5e9fcc..c4b435df2d 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -400,7 +400,10 @@ class Query(BaseExpression):
"""
if not aggregate_exprs:
return {}
- aggregates = {}
+ # Store annotation mask prior to temporarily adding aggregations for
+ # resolving purpose to facilitate their subsequent removal.
+ replacements = {}
+ annotation_select_mask = self.annotation_select_mask
for alias, aggregate_expr in aggregate_exprs.items():
self.check_alias(alias)
aggregate = aggregate_expr.resolve_expression(
@@ -408,7 +411,16 @@ class Query(BaseExpression):
)
if not aggregate.contains_aggregate:
raise TypeError("%s is not an aggregate expression" % alias)
- aggregates[alias] = aggregate
+ # Temporarily add aggregate to annotations to allow remaining
+ # members of `aggregates` to resolve against each others.
+ self.append_annotation_mask([alias])
+ aggregate = aggregate.replace_expressions(replacements)
+ self.annotations[alias] = aggregate
+ replacements[Ref(alias, aggregate)] = aggregate
+ # Stash resolved aggregates now that they have been allowed to resolve
+ # against each other.
+ aggregates = {alias: self.annotations.pop(alias) for alias in aggregate_exprs}
+ self.set_annotation_mask(annotation_select_mask)
# Existing usage of aggregation can be determined by the presence of
# selected aggregates but also by filters against aliased aggregates.
_, having, qualify = self.where.split_having_qualify()