From 0e1d950fbb44878f6972a0e5730f69f98caf468e Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 12 May 2026 17:00:34 -0400 Subject: [PATCH 1/3] Fixed #37097 -- Made Query.clear_ordering() clear ordering on combined queries also. Thanks Shai Berger for the report. Regression in 087bb9e8f3478d53f12b1737af865992af17c5f2. (That commit drove more traffic into an error that would have been reachable only with an explicit order_by() after each union().) Co-authored-by: Simon Charette Co-authored-by: siddus --- django/db/models/sql/query.py | 2 ++ tests/queries/test_qs_combinators.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 45192b78092f..fd5ef10c0724 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -2402,6 +2402,8 @@ def clear_ordering(self, force=False, clear_default=True): self.extra_order_by = () if clear_default: self.default_ordering = False + 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..374c6796dbd6 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" ) From 6bbe2656e1e89a03d1ae8f2cea7bdd2a027f0665 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 13 May 2026 10:31:39 -0400 Subject: [PATCH 2/3] Refs #36938 -- Reverted "Refs #36938 -- Tolerated unnecessary ordering in compound queries on SQLite." This mostly reverts 2314cdf1ff860058a6579bb9f9bac1253fc9ab43, but keeps the removal of some test skips. --- django/db/models/sql/compiler.py | 4 ++++ tests/queries/test_qs_combinators.py | 14 ++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 764dc46cfc59..6442ed7ecbd3 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: diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 374c6796dbd6..a400ce76de41 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -625,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}] ) @@ -706,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): From fbe902d4a4b8b9dcb371509b25ba8feff3852e64 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 13 May 2026 10:31:27 -0400 Subject: [PATCH 3/3] Refs #37097 -- Removed compilation-time order clearing on combined queries on Oracle. Thanks Simon Charette, JaeHyuck Sa, and Shai Berger for reviews. --- django/db/models/query.py | 10 +++++++++- django/db/models/sql/compiler.py | 9 --------- django/db/models/sql/query.py | 3 +++ 3 files changed, 12 insertions(+), 10 deletions(-) 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 6442ed7ecbd3..bcf28f9ae16d 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -640,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 fd5ef10c0724..22dd479d67d9 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -2402,6 +2402,9 @@ 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)