Skip to content

Commit 82175ea

Browse files
authored
Fixed #28293 -- Fixed union(), intersection(), and difference() when combining with an EmptyQuerySet.
Thanks Jon Dufresne for the report and Tim Graham for the review.
1 parent 9dc83c3 commit 82175ea

File tree

4 files changed

+42
-1
lines changed

4 files changed

+42
-1
lines changed

django/db/models/query.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,12 +818,25 @@ def _combinator_query(self, combinator, *other_qs, all=False):
818818
return clone
819819

820820
def union(self, *other_qs, all=False):
821+
# If the query is an EmptyQuerySet, combine all nonempty querysets.
822+
if isinstance(self, EmptyQuerySet):
823+
qs = [q for q in other_qs if not isinstance(q, EmptyQuerySet)]
824+
return qs[0]._combinator_query('union', *qs[1:], all=all) if qs else self
821825
return self._combinator_query('union', *other_qs, all=all)
822826

823827
def intersection(self, *other_qs):
828+
# If any query is an EmptyQuerySet, return it.
829+
if isinstance(self, EmptyQuerySet):
830+
return self
831+
for other in other_qs:
832+
if isinstance(other, EmptyQuerySet):
833+
return other
824834
return self._combinator_query('intersection', *other_qs)
825835

826836
def difference(self, *other_qs):
837+
# If the query is an EmptyQuerySet, return it.
838+
if isinstance(self, EmptyQuerySet):
839+
return self
827840
return self._combinator_query('difference', *other_qs)
828841

829842
def select_for_update(self, nowait=False, skip_locked=False):

django/db/models/sql/compiler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ def get_combinator_sql(self, combinator, all):
390390
features = self.connection.features
391391
compilers = [
392392
query.get_compiler(self.using, self.connection)
393-
for query in self.query.combined_queries
393+
for query in self.query.combined_queries if not query.is_empty()
394394
]
395395
if not features.supports_slicing_ordering_in_compound:
396396
for query, compiler in zip(self.query.combined_queries, compilers):

docs/releases/1.11.3.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ Bugfixes
2929

3030
* Fixed crash in admin's inlines when a model has an inherited non-editable
3131
primary key (:ticket:`27967`).
32+
33+
* Fixed ``QuerySet.union()``, ``intersection()``, and ``difference()`` when
34+
combining with an ``EmptyQuerySet`` (:ticket:`28293`).

tests/queries/test_qs_combinators.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,31 @@ def test_union_distinct(self):
4242
self.assertEqual(len(list(qs1.union(qs2, all=True))), 20)
4343
self.assertEqual(len(list(qs1.union(qs2))), 10)
4444

45+
@skipUnlessDBFeature('supports_select_intersection')
46+
def test_intersection_with_empty_qs(self):
47+
qs1 = Number.objects.all()
48+
qs2 = Number.objects.none()
49+
self.assertEqual(len(qs1.intersection(qs2)), 0)
50+
self.assertEqual(len(qs2.intersection(qs1)), 0)
51+
self.assertEqual(len(qs2.intersection(qs2)), 0)
52+
53+
@skipUnlessDBFeature('supports_select_difference')
54+
def test_difference_with_empty_qs(self):
55+
qs1 = Number.objects.all()
56+
qs2 = Number.objects.none()
57+
self.assertEqual(len(qs1.difference(qs2)), 10)
58+
self.assertEqual(len(qs2.difference(qs1)), 0)
59+
self.assertEqual(len(qs2.difference(qs2)), 0)
60+
61+
def test_union_with_empty_qs(self):
62+
qs1 = Number.objects.all()
63+
qs2 = Number.objects.none()
64+
self.assertEqual(len(qs1.union(qs2)), 10)
65+
self.assertEqual(len(qs2.union(qs1)), 10)
66+
self.assertEqual(len(qs2.union(qs1, qs1, qs1)), 10)
67+
self.assertEqual(len(qs2.union(qs1, qs1, all=True)), 20)
68+
self.assertEqual(len(qs2.union(qs2)), 0)
69+
4570
def test_limits(self):
4671
qs1 = Number.objects.all()
4772
qs2 = Number.objects.all()

0 commit comments

Comments
 (0)