diff options
| author | Simon Charette <charette.s@gmail.com> | 2023-05-21 23:49:05 -0400 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-05-23 06:25:27 +0200 |
| commit | 2ee01747c32a7275a7a1a5f7862acba7db764921 (patch) | |
| tree | 7a668fab0cc6996f7491fbe988500d0ee1f81992 /django/db | |
| parent | 89f10a80d7e681cd0cccf22d932e380f51bd3524 (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.py | 9 | ||||
| -rw-r--r-- | django/db/models/sql/query.py | 16 |
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() |
