diff --git a/django/db/models/query.py b/django/db/models/query.py index 6cdd7681b261..a2068044691e 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -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 @@ -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): diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 764dc46cfc59..bcf28f9ae16d 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -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: @@ -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 diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 45192b78092f..22dd479d67d9 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -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): """ diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 044eb39cf49b..a400ce76de41 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -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" ) @@ -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}] ) @@ -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):