Fixed #33018 -- Fixed annotations with empty queryset.
Thanks Simon Charette for the review and implementation idea.
This commit is contained in:
parent
ad36a198a1
commit
dd1fa3a31b
|
@ -702,7 +702,13 @@ class Func(SQLiteNumericMixin, Expression):
|
||||||
sql_parts = []
|
sql_parts = []
|
||||||
params = []
|
params = []
|
||||||
for arg in self.source_expressions:
|
for arg in self.source_expressions:
|
||||||
|
try:
|
||||||
arg_sql, arg_params = compiler.compile(arg)
|
arg_sql, arg_params = compiler.compile(arg)
|
||||||
|
except EmptyResultSet:
|
||||||
|
empty_result_set_value = getattr(arg, 'empty_result_set_value', NotImplemented)
|
||||||
|
if empty_result_set_value is NotImplemented:
|
||||||
|
raise
|
||||||
|
arg_sql, arg_params = compiler.compile(Value(empty_result_set_value))
|
||||||
sql_parts.append(arg_sql)
|
sql_parts.append(arg_sql)
|
||||||
params.extend(arg_params)
|
params.extend(arg_params)
|
||||||
data = {**self.extra, **extra_context}
|
data = {**self.extra, **extra_context}
|
||||||
|
@ -1114,6 +1120,7 @@ class Subquery(BaseExpression, Combinable):
|
||||||
"""
|
"""
|
||||||
template = '(%(subquery)s)'
|
template = '(%(subquery)s)'
|
||||||
contains_aggregate = False
|
contains_aggregate = False
|
||||||
|
empty_result_set_value = None
|
||||||
|
|
||||||
def __init__(self, queryset, output_field=None, **extra):
|
def __init__(self, queryset, output_field=None, **extra):
|
||||||
# Allow the usage of both QuerySet and sql.Query objects.
|
# Allow the usage of both QuerySet and sql.Query objects.
|
||||||
|
|
|
@ -266,8 +266,12 @@ class SQLCompiler:
|
||||||
try:
|
try:
|
||||||
sql, params = self.compile(col)
|
sql, params = self.compile(col)
|
||||||
except EmptyResultSet:
|
except EmptyResultSet:
|
||||||
|
empty_result_set_value = getattr(col, 'empty_result_set_value', NotImplemented)
|
||||||
|
if empty_result_set_value is NotImplemented:
|
||||||
# Select a predicate that's always False.
|
# Select a predicate that's always False.
|
||||||
sql, params = '0', ()
|
sql, params = '0', ()
|
||||||
|
else:
|
||||||
|
sql, params = self.compile(Value(empty_result_set_value))
|
||||||
else:
|
else:
|
||||||
sql, params = col.select_format(self, sql, params)
|
sql, params = col.select_format(self, sql, params)
|
||||||
ret.append((col, (sql, params), alias))
|
ret.append((col, (sql, params), alias))
|
||||||
|
|
|
@ -143,6 +143,7 @@ class Query(BaseExpression):
|
||||||
"""A single SQL query."""
|
"""A single SQL query."""
|
||||||
|
|
||||||
alias_prefix = 'T'
|
alias_prefix = 'T'
|
||||||
|
empty_result_set_value = None
|
||||||
subq_aliases = frozenset([alias_prefix])
|
subq_aliases = frozenset([alias_prefix])
|
||||||
|
|
||||||
compiler = 'SQLCompiler'
|
compiler = 'SQLCompiler'
|
||||||
|
|
|
@ -210,6 +210,12 @@ class NonAggregateAnnotationTestCase(TestCase):
|
||||||
self.assertEqual(len(books), Book.objects.count())
|
self.assertEqual(len(books), Book.objects.count())
|
||||||
self.assertTrue(all(not book.selected for book in books))
|
self.assertTrue(all(not book.selected for book in books))
|
||||||
|
|
||||||
|
def test_empty_queryset_annotation(self):
|
||||||
|
qs = Author.objects.annotate(
|
||||||
|
empty=Subquery(Author.objects.values('id').none())
|
||||||
|
)
|
||||||
|
self.assertIsNone(qs.first().empty)
|
||||||
|
|
||||||
def test_annotate_with_aggregation(self):
|
def test_annotate_with_aggregation(self):
|
||||||
books = Book.objects.annotate(is_book=Value(1), rating_count=Count('rating'))
|
books = Book.objects.annotate(is_book=Value(1), rating_count=Count('rating'))
|
||||||
for book in books:
|
for book in books:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.db.models import TextField
|
from django.db.models import Subquery, TextField
|
||||||
from django.db.models.functions import Coalesce, Lower
|
from django.db.models.functions import Coalesce, Lower
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -70,3 +70,14 @@ class CoalesceTests(TestCase):
|
||||||
authors, ['John Smith', 'Rhonda'],
|
authors, ['John Smith', 'Rhonda'],
|
||||||
lambda a: a.name
|
lambda a: a.name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_empty_queryset(self):
|
||||||
|
Author.objects.create(name='John Smith')
|
||||||
|
tests = [
|
||||||
|
Author.objects.none(),
|
||||||
|
Subquery(Author.objects.none()),
|
||||||
|
]
|
||||||
|
for empty_query in tests:
|
||||||
|
with self.subTest(empty_query.__class__.__name__):
|
||||||
|
qs = Author.objects.annotate(annotation=Coalesce(empty_query, 42))
|
||||||
|
self.assertEqual(qs.first().annotation, 42)
|
||||||
|
|
Loading…
Reference in New Issue