Fixed #34125 -- Fixed sliced QuerySet.union() crash on a single non-empty queryset.

The bug existed since sliced query union was added but was elevated to
query union slices by moving the .exists() optimization to the compiler
in 3d734c09ff.

Thanks Stefan Hammer for the report.
This commit is contained in:
Simon Charette 2022-10-29 03:21:25 -04:00 committed by GitHub
parent 09397f5cfa
commit c2cc80756b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 31 additions and 1 deletions

View File

@ -531,7 +531,6 @@ class SQLCompiler:
compilers = [
query.get_compiler(self.using, self.connection, self.elide_empty)
for query in self.query.combined_queries
if not query.is_empty()
]
if not features.supports_slicing_ordering_in_compound:
for compiler in compilers:
@ -546,6 +545,11 @@ class SQLCompiler:
elif self.query.is_sliced and combinator == "union":
limit = (self.query.low_mark, self.query.high_mark)
for compiler in compilers:
# A sliced union cannot have its parts elided as some of them
# might be sliced as well and in the event where only a single
# part produces a non-empty resultset it might be impossible to
# generate valid SQL.
compiler.elide_empty = False
if not compiler.query.is_sliced:
compiler.query.set_limits(*limit)
parts = ()

View File

@ -61,6 +61,32 @@ class QuerySetSetOperationTests(TestCase):
self.assertSequenceEqual(qs3.none(), [])
self.assertNumbersEqual(qs3, [0, 1, 8, 9], ordered=False)
def test_union_none_slice(self):
qs1 = Number.objects.filter(num__lte=0)
qs2 = Number.objects.none()
qs3 = qs1.union(qs2)
self.assertNumbersEqual(qs3[:1], [0])
def test_union_empty_filter_slice(self):
qs1 = Number.objects.filter(num__lte=0)
qs2 = Number.objects.filter(pk__in=[])
qs3 = qs1.union(qs2)
self.assertNumbersEqual(qs3[:1], [0])
@skipUnlessDBFeature("supports_slicing_ordering_in_compound")
def test_union_slice_compound_empty(self):
qs1 = Number.objects.filter(num__lte=0)[:1]
qs2 = Number.objects.none()
qs3 = qs1.union(qs2)
self.assertNumbersEqual(qs3[:1], [0])
@skipUnlessDBFeature("supports_slicing_ordering_in_compound")
def test_union_combined_slice_compound_empty(self):
qs1 = Number.objects.filter(num__lte=2)[:3]
qs2 = Number.objects.none()
qs3 = qs1.union(qs2)
self.assertNumbersEqual(qs3.order_by("num")[2:3], [2])
def test_union_order_with_null_first_last(self):
Number.objects.filter(other_num=5).update(other_num=None)
qs1 = Number.objects.filter(num__lte=1)