Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion django/db/models/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -1703,8 +1703,8 @@ def _combinator_query(self, combinator, *other_qs, all=False):
# Clear limits and ordering so they can be reapplied
clone.query.clear_ordering(force=True)
clone.query.default_ordering = True
self._clear_ordering_in_combined_queries(clone.query, other_qs)
clone.query.clear_limits()
clone.query.combined_queries = (self.query, *(qs.query for qs in other_qs))
clone.query.combinator = combinator
clone.query.combinator_all = all
return clone
Expand Down Expand Up @@ -2335,6 +2335,14 @@ def _check_ordering_first_last_queryset_aggregation(self, method):
f"aggregation. Add an ordering with order_by()."
)

def _clear_ordering_in_combined_queries(self, cloned_query, other_qs):
combined_queries = [self.query]
for qs in other_qs:
query = qs.query.clone()
query.clear_ordering(force=False, clear_default=False)
combined_queries.append(query)
cloned_query.combined_queries = tuple(combined_queries)


class InstanceCheckMeta(type):
def __instancecheck__(self, instance):
Expand Down
13 changes: 4 additions & 9 deletions django/db/models/sql/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,10 @@ def get_combinator_sql(self, combinator, all):
raise DatabaseError(
"LIMIT/OFFSET not allowed in subqueries of compound statements."
)
if compiler.get_order_by():
raise DatabaseError(
"ORDER BY not allowed in subqueries of compound statements."
)
parts = []
empty_compiler = None
for compiler in compilers:
Expand Down Expand Up @@ -636,15 +640,6 @@ def _get_combinator_part_sql(self, compiler):
if selected is not None and compiler.query.selected is None:
compiler.query = compiler.query.clone()
compiler.query.set_values(selected)
if (
(
features.requires_compound_order_by_subquery
and not features.ignores_unnecessary_order_by_in_subqueries
)
or not features.supports_parentheses_in_compound
) and compiler.get_order_by():
compiler.query = compiler.query.clone()
compiler.query.clear_ordering(force=False)
part_sql, part_args = compiler.as_sql(with_col_aliases=True)
if compiler.query.combinator:
# Wrap in a subquery if wrapping in parentheses isn't
Expand Down
5 changes: 5 additions & 0 deletions django/db/models/sql/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -2402,6 +2402,11 @@ def clear_ordering(self, force=False, clear_default=True):
self.extra_order_by = ()
if clear_default:
self.default_ordering = False
# Ordering is cleared on combined queries with clear_default=False
# when union() and analogues are called, so percolate any possible
# clear_default=True.
for query in self.combined_queries:
query.clear_ordering(force=False, clear_default=clear_default)

def set_group_by(self, allow_aliases=True):
"""
Expand Down
31 changes: 27 additions & 4 deletions tests/queries/test_qs_combinators.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,23 @@ def test_union_in_with_ordering(self):
ordered=False,
)

def test_double_union_in_with_default_ordering(self):
e1 = ExtraInfo.objects.create(value=7, info="e1")
Author.objects.bulk_create(
[Author(num=i, name=f"a{i}", extra=e1) for i in range(1, 8)]
)
qs1 = Author.objects.filter(num__lt=2)
qs2 = Author.objects.filter(num__gt=6)
qs3 = Author.objects.filter(num=4)
double_union = qs1.union(qs2).union(qs3)
# Target a column (num) other than the ordering column (name). Before,
# on Postgres: "each UNION query must have the same number of columns".
self.assertQuerySetEqual(
Author.objects.filter(num__in=double_union.values("num")).order_by("-name"),
["a7", "a4", "a1"],
transform=lambda au: au.name,
)

@skipUnlessDBFeature(
"supports_slicing_ordering_in_compound", "allow_sliced_subqueries_with_in"
)
Expand Down Expand Up @@ -608,7 +625,11 @@ def test_count_union_with_select_related(self):
def test_count_union_with_select_related_in_values(self):
e1 = ExtraInfo.objects.create(value=1, info="e1")
a1 = Author.objects.create(name="a1", num=1, extra=e1)
qs = Author.objects.select_related("extra").values("pk", "name", "extra__value")
qs = (
Author.objects.select_related("extra")
.order_by()
.values("pk", "name", "extra__value")
)
self.assertCountEqual(
qs.union(qs), [{"pk": a1.id, "name": "a1", "extra__value": 1}]
)
Expand Down Expand Up @@ -689,9 +710,11 @@ def test_unsupported_ordering_slicing_raises_db_error(self):
msg = "LIMIT/OFFSET not allowed in subqueries of compound statements"
with self.assertRaisesMessage(DatabaseError, msg):
list(qs1.union(qs2[:10]))
# Unioning ordered queries is permitted.
list(qs1.order_by("id").union(qs2))
list(qs1.union(qs2).order_by("id").union(qs3))
msg = "ORDER BY not allowed in subqueries of compound statements."
with self.assertRaisesMessage(DatabaseError, msg):
list(qs1.order_by("id").union(qs2))
with self.assertRaisesMessage(DatabaseError, msg):
list(qs1.union(qs2).order_by("id").union(qs3))

@skipIfDBFeature("supports_select_intersection")
def test_unsupported_intersection_raises_db_error(self):
Expand Down
Loading