Refs #26430 -- Re-introduced empty aggregation optimization.

The introduction of the Expression.empty_aggregate_value interface
allows the compilation stage to enable the EmptyResultSet optimization
if all the aggregates expressions implement it.

This also removes unnecessary RegrCount/Count.convert_value() methods.
Disabling the empty result set aggregation optimization when it wasn't
appropriate prevented None returned for a Count aggregation value.

Thanks Nick Pope for the review.
This commit is contained in:
Simon Charette 2021-05-21 22:32:16 -04:00 committed by Mariusz Felisiak
parent f3112fde98
commit 9f3cce172f
8 changed files with 101 additions and 34 deletions

View file

@ -8,7 +8,7 @@ from django.db.models import (
Avg, Case, Count, DecimalField, DurationField, Exists, F, FloatField,
IntegerField, Max, Min, OuterRef, Subquery, Sum, Value, When,
)
from django.db.models.expressions import RawSQL
from django.db.models.expressions import Func, RawSQL
from django.db.models.functions import Coalesce, Greatest
from django.test import TestCase
from django.test.testcases import skipUnlessDBFeature
@ -1342,33 +1342,63 @@ class AggregateTestCase(TestCase):
('Peter Norvig', 2),
], lambda a: (a.name, a.contact_count), ordered=False)
def test_empty_result_optimization(self):
with self.assertNumQueries(0):
self.assertEqual(
Publisher.objects.none().aggregate(
sum_awards=Sum('num_awards'),
books_count=Count('book'),
), {
'sum_awards': None,
'books_count': 0,
}
)
# Expression without empty_aggregate_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
with self.assertNumQueries(1):
self.assertEqual(
Publisher.objects.none().aggregate(
sum_awards=Sum('num_awards'),
books_count=raw_books_count,
), {
'sum_awards': None,
'books_count': 0,
}
)
def test_coalesced_empty_result_set(self):
self.assertEqual(
Publisher.objects.none().aggregate(
sum_awards=Coalesce(Sum('num_awards'), 0),
)['sum_awards'],
0,
)
with self.assertNumQueries(0):
self.assertEqual(
Publisher.objects.none().aggregate(
sum_awards=Coalesce(Sum('num_awards'), 0),
)['sum_awards'],
0,
)
# Multiple expressions.
self.assertEqual(
Publisher.objects.none().aggregate(
sum_awards=Coalesce(Sum('num_awards'), None, 0),
)['sum_awards'],
0,
)
with self.assertNumQueries(0):
self.assertEqual(
Publisher.objects.none().aggregate(
sum_awards=Coalesce(Sum('num_awards'), None, 0),
)['sum_awards'],
0,
)
# Nested coalesce.
self.assertEqual(
Publisher.objects.none().aggregate(
sum_awards=Coalesce(Coalesce(Sum('num_awards'), None), 0),
)['sum_awards'],
0,
)
with self.assertNumQueries(0):
self.assertEqual(
Publisher.objects.none().aggregate(
sum_awards=Coalesce(Coalesce(Sum('num_awards'), None), 0),
)['sum_awards'],
0,
)
# Expression coalesce.
self.assertIsInstance(
Store.objects.none().aggregate(
latest_opening=Coalesce(
Max('original_opening'), RawSQL('CURRENT_TIMESTAMP', []),
),
)['latest_opening'],
datetime.datetime,
)
with self.assertNumQueries(1):
self.assertIsInstance(
Store.objects.none().aggregate(
latest_opening=Coalesce(
Max('original_opening'), RawSQL('CURRENT_TIMESTAMP', []),
),
)['latest_opening'],
datetime.datetime,
)