[4.0.x] Fixed #33141 -- Renamed Expression.empty_aggregate_value to empty_result_set_value.

Backport of ad36a198a1 from main
This commit is contained in:
David Wobrock 2021-09-24 22:05:02 +02:00 committed by Mariusz Felisiak
parent cebac15931
commit aab76433ed
8 changed files with 22 additions and 21 deletions

View File

@ -36,7 +36,7 @@ class RegrAvgY(StatAggregate):
class RegrCount(StatAggregate): class RegrCount(StatAggregate):
function = 'REGR_COUNT' function = 'REGR_COUNT'
output_field = IntegerField() output_field = IntegerField()
empty_aggregate_value = 0 empty_result_set_value = 0
class RegrIntercept(StatAggregate): class RegrIntercept(StatAggregate):

View File

@ -21,12 +21,12 @@ class Aggregate(Func):
filter_template = '%s FILTER (WHERE %%(filter)s)' filter_template = '%s FILTER (WHERE %%(filter)s)'
window_compatible = True window_compatible = True
allow_distinct = False allow_distinct = False
empty_aggregate_value = None empty_result_set_value = None
def __init__(self, *expressions, distinct=False, filter=None, default=None, **extra): def __init__(self, *expressions, distinct=False, filter=None, default=None, **extra):
if distinct and not self.allow_distinct: if distinct and not self.allow_distinct:
raise TypeError("%s does not allow distinct." % self.__class__.__name__) 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.') raise TypeError(f'{self.__class__.__name__} does not allow default.')
self.distinct = distinct self.distinct = distinct
self.filter = filter self.filter = filter
@ -117,7 +117,7 @@ class Count(Aggregate):
name = 'Count' name = 'Count'
output_field = IntegerField() output_field = IntegerField()
allow_distinct = True allow_distinct = True
empty_aggregate_value = 0 empty_result_set_value = 0
def __init__(self, expression, filter=None, **extra): def __init__(self, expression, filter=None, **extra):
if expression == '*': if expression == '*':

View File

@ -150,10 +150,10 @@ class Combinable:
class BaseExpression: class BaseExpression:
"""Base class for all query expressions.""" """Base class for all query expressions."""
empty_result_set_value = NotImplemented
# aggregate specific fields # aggregate specific fields
is_summary = False is_summary = False
_output_field_resolved_to_none = False _output_field_resolved_to_none = False
empty_aggregate_value = NotImplemented
# Can the expression be used in a WHERE clause? # Can the expression be used in a WHERE clause?
filterable = True filterable = True
# Can the expression can be used as a source expression in Window? # Can the expression can be used as a source expression in Window?
@ -797,7 +797,7 @@ class Value(SQLiteNumericMixin, Expression):
return fields.UUIDField() return fields.UUIDField()
@property @property
def empty_aggregate_value(self): def empty_result_set_value(self):
return self.value return self.value

View File

@ -66,9 +66,9 @@ class Coalesce(Func):
super().__init__(*expressions, **extra) super().__init__(*expressions, **extra)
@property @property
def empty_aggregate_value(self): def empty_result_set_value(self):
for expression in self.get_source_expressions(): 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: if result is NotImplemented or result is not None:
return result return result
return None return None

View File

@ -487,11 +487,11 @@ class Query(BaseExpression):
self.default_cols = False self.default_cols = False
self.extra = {} self.extra = {}
empty_aggregate_result = [ empty_set_result = [
expression.empty_aggregate_value expression.empty_result_set_value
for expression in outer_query.annotation_select.values() 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_ordering(force=True)
outer_query.clear_limits() outer_query.clear_limits()
outer_query.select_for_update = False outer_query.select_for_update = False
@ -499,7 +499,7 @@ class Query(BaseExpression):
compiler = outer_query.get_compiler(using, elide_empty=elide_empty) compiler = outer_query.get_compiler(using, elide_empty=elide_empty)
result = compiler.execute_sql(SINGLE) result = compiler.execute_sql(SINGLE)
if result is None: if result is None:
result = empty_aggregate_result result = empty_set_result
converters = compiler.get_converters(outer_query.annotation_select.values()) converters = compiler.get_converters(outer_query.annotation_select.values())
result = next(compiler.apply_converters((result,), converters)) result = next(compiler.apply_converters((result,), converters))

View File

@ -418,11 +418,11 @@ The ``Aggregate`` API is as follows:
allows passing a ``distinct`` keyword argument. If set to ``False`` allows passing a ``distinct`` keyword argument. If set to ``False``
(default), ``TypeError`` is raised if ``distinct=True`` is passed. (default), ``TypeError`` is raised if ``distinct=True`` is passed.
.. attribute:: empty_aggregate_value .. attribute:: empty_result_set_value
.. versionadded:: 4.0 .. 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 ``None`` since most aggregate functions result in ``NULL`` when applied
to an empty result set. 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 in :class:`~django.db.models.expressions.Window`. Defaults to
``False``. ``False``.
.. attribute:: empty_aggregate_value .. attribute:: empty_result_set_value
.. versionadded:: 4.0 .. versionadded:: 4.0
Tells Django which value should be returned when the expression is used Tells Django which value should be returned when the expression is used
to :meth:`aggregate <django.db.models.query.QuerySet.aggregate>` over to apply a function over an empty result set. Defaults to
an empty result set. Defaults to :py:data:`NotImplemented` which forces :py:data:`NotImplemented` which forces the expression to be computed on
the expression to be computed on the database. the database.
.. method:: resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False) .. method:: resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False)

View File

@ -318,8 +318,9 @@ Models
* :meth:`.QuerySet.bulk_update` now returns the number of objects updated. * :meth:`.QuerySet.bulk_update` now returns the number of objects updated.
* The new :attr:`.Aggregate.empty_aggregate_value` attribute allows specifying * The new :attr:`.Expression.empty_result_set_value` attribute allows
a value to return when the aggregation is used over an empty result set. 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 * The ``skip_locked`` argument of :meth:`.QuerySet.select_for_update()` is now
allowed on MariaDB 10.6+. allowed on MariaDB 10.6+.

View File

@ -1367,7 +1367,7 @@ class AggregateTestCase(TestCase):
'books_count': 0, '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. # executed even if they would return an empty result set.
raw_books_count = Func('book', function='COUNT') raw_books_count = Func('book', function='COUNT')
raw_books_count.contains_aggregate = True raw_books_count.contains_aggregate = True