From 9f3cce172f6913c5ac74272fa5fc07f847b4e112 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Fri, 21 May 2021 22:32:16 -0400 Subject: 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. --- tests/aggregation/tests.py | 84 +++++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 27 deletions(-) (limited to 'tests/aggregation') diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py index db24f36a79..f7b2331d34 100644 --- a/tests/aggregation/tests.py +++ b/tests/aggregation/tests.py @@ -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, + ) -- cgit v1.3