diff --git a/django/contrib/postgres/aggregates/statistics.py b/django/contrib/postgres/aggregates/statistics.py index c0aae93bd3..2c83b78c0e 100644 --- a/django/contrib/postgres/aggregates/statistics.py +++ b/django/contrib/postgres/aggregates/statistics.py @@ -36,7 +36,7 @@ class RegrAvgY(StatAggregate): class RegrCount(StatAggregate): function = 'REGR_COUNT' output_field = IntegerField() - empty_aggregate_value = 0 + empty_result_set_value = 0 class RegrIntercept(StatAggregate): diff --git a/django/db/models/aggregates.py b/django/db/models/aggregates.py index 1ae4382784..596a161669 100644 --- a/django/db/models/aggregates.py +++ b/django/db/models/aggregates.py @@ -21,12 +21,12 @@ class Aggregate(Func): filter_template = '%s FILTER (WHERE %%(filter)s)' window_compatible = True allow_distinct = False - empty_aggregate_value = None + empty_result_set_value = None def __init__(self, *expressions, distinct=False, filter=None, default=None, **extra): if distinct and not self.allow_distinct: raise TypeError("%s does not allow distinct." % self.__class__.__name__) - if default is not None and self.empty_aggregate_value is not None: + if default is not None and self.empty_result_set_value is not None: raise TypeError(f'{self.__class__.__name__} does not allow default.') self.distinct = distinct self.filter = filter @@ -117,7 +117,7 @@ class Count(Aggregate): name = 'Count' output_field = IntegerField() allow_distinct = True - empty_aggregate_value = 0 + empty_result_set_value = 0 def __init__(self, expression, filter=None, **extra): if expression == '*': diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 9381257bb2..100da26ee6 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -150,10 +150,10 @@ class Combinable: class BaseExpression: """Base class for all query expressions.""" + empty_result_set_value = NotImplemented # aggregate specific fields is_summary = False _output_field_resolved_to_none = False - empty_aggregate_value = NotImplemented # Can the expression be used in a WHERE clause? filterable = True # Can the expression can be used as a source expression in Window? @@ -797,7 +797,7 @@ class Value(SQLiteNumericMixin, Expression): return fields.UUIDField() @property - def empty_aggregate_value(self): + def empty_result_set_value(self): return self.value diff --git a/django/db/models/functions/comparison.py b/django/db/models/functions/comparison.py index 2dac47a0d2..e5882de9c2 100644 --- a/django/db/models/functions/comparison.py +++ b/django/db/models/functions/comparison.py @@ -66,9 +66,9 @@ class Coalesce(Func): super().__init__(*expressions, **extra) @property - def empty_aggregate_value(self): + def empty_result_set_value(self): for expression in self.get_source_expressions(): - result = expression.empty_aggregate_value + result = expression.empty_result_set_value if result is NotImplemented or result is not None: return result return None diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 388c60545f..d1c0c333f5 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -487,11 +487,11 @@ class Query(BaseExpression): self.default_cols = False self.extra = {} - empty_aggregate_result = [ - expression.empty_aggregate_value + empty_set_result = [ + expression.empty_result_set_value for expression in outer_query.annotation_select.values() ] - elide_empty = not any(result is NotImplemented for result in empty_aggregate_result) + elide_empty = not any(result is NotImplemented for result in empty_set_result) outer_query.clear_ordering(force=True) outer_query.clear_limits() outer_query.select_for_update = False @@ -499,7 +499,7 @@ class Query(BaseExpression): compiler = outer_query.get_compiler(using, elide_empty=elide_empty) result = compiler.execute_sql(SINGLE) if result is None: - result = empty_aggregate_result + result = empty_set_result converters = compiler.get_converters(outer_query.annotation_select.values()) result = next(compiler.apply_converters((result,), converters)) diff --git a/docs/ref/models/expressions.txt b/docs/ref/models/expressions.txt index 64a7136e6e..c0ef4a46d0 100644 --- a/docs/ref/models/expressions.txt +++ b/docs/ref/models/expressions.txt @@ -418,11 +418,11 @@ The ``Aggregate`` API is as follows: allows passing a ``distinct`` keyword argument. If set to ``False`` (default), ``TypeError`` is raised if ``distinct=True`` is passed. - .. attribute:: empty_aggregate_value + .. attribute:: empty_result_set_value .. versionadded:: 4.0 - Override :attr:`~django.db.models.Expression.empty_aggregate_value` to + Override :attr:`~django.db.models.Expression.empty_result_set_value` to ``None`` since most aggregate functions result in ``NULL`` when applied to an empty result set. @@ -976,14 +976,14 @@ calling the appropriate methods on the wrapped expression. in :class:`~django.db.models.expressions.Window`. Defaults to ``False``. - .. attribute:: empty_aggregate_value + .. attribute:: empty_result_set_value .. versionadded:: 4.0 Tells Django which value should be returned when the expression is used - to :meth:`aggregate ` over - an empty result set. Defaults to :py:data:`NotImplemented` which forces - the expression to be computed on the database. + to apply a function over an empty result set. Defaults to + :py:data:`NotImplemented` which forces the expression to be computed on + the database. .. method:: resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False) diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index 4bc3fa7678..9089117215 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -318,8 +318,9 @@ Models * :meth:`.QuerySet.bulk_update` now returns the number of objects updated. -* The new :attr:`.Aggregate.empty_aggregate_value` attribute allows specifying - a value to return when the aggregation is used over an empty result set. +* The new :attr:`.Expression.empty_result_set_value` attribute allows + specifying a value to return when the function is used over an empty result + set. * The ``skip_locked`` argument of :meth:`.QuerySet.select_for_update()` is now allowed on MariaDB 10.6+. diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py index 85742dcb9c..72575a526c 100644 --- a/tests/aggregation/tests.py +++ b/tests/aggregation/tests.py @@ -1367,7 +1367,7 @@ class AggregateTestCase(TestCase): 'books_count': 0, } ) - # Expression without empty_aggregate_value forces queries to be + # Expression without empty_result_set_value forces queries to be # executed even if they would return an empty result set. raw_books_count = Func('book', function='COUNT') raw_books_count.contains_aggregate = True