Fixed #33768 -- Fixed ordering compound queries by nulls_first/nulls_last on MySQL.
Columns of the left outer most select statement in a combined query can be referenced by alias just like by index. This removes combined query ordering by column index and avoids an unnecessary usage of RawSQL which causes issues for backends that specialize the treatment of null ordering.
This commit is contained in:
parent
344d31c7e9
commit
c58a8acd41
|
@ -235,6 +235,7 @@ class BaseDatabaseFeatures:
|
||||||
supports_select_difference = True
|
supports_select_difference = True
|
||||||
supports_slicing_ordering_in_compound = False
|
supports_slicing_ordering_in_compound = False
|
||||||
supports_parentheses_in_compound = True
|
supports_parentheses_in_compound = True
|
||||||
|
requires_compound_order_by_subquery = False
|
||||||
|
|
||||||
# Does the database support SQL 2003 FILTER (WHERE ...) in aggregate
|
# Does the database support SQL 2003 FILTER (WHERE ...) in aggregate
|
||||||
# expressions?
|
# expressions?
|
||||||
|
|
|
@ -69,6 +69,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
supports_partial_indexes = False
|
supports_partial_indexes = False
|
||||||
can_rename_index = True
|
can_rename_index = True
|
||||||
supports_slicing_ordering_in_compound = True
|
supports_slicing_ordering_in_compound = True
|
||||||
|
requires_compound_order_by_subquery = True
|
||||||
allows_multiple_constraints_on_same_fields = False
|
allows_multiple_constraints_on_same_fields = False
|
||||||
supports_boolean_expr_in_select_clause = False
|
supports_boolean_expr_in_select_clause = False
|
||||||
supports_comparing_boolean_expr = False
|
supports_comparing_boolean_expr = False
|
||||||
|
|
|
@ -435,21 +435,18 @@ class SQLCompiler:
|
||||||
|
|
||||||
for expr, is_ref in self._order_by_pairs():
|
for expr, is_ref in self._order_by_pairs():
|
||||||
resolved = expr.resolve_expression(self.query, allow_joins=True, reuse=None)
|
resolved = expr.resolve_expression(self.query, allow_joins=True, reuse=None)
|
||||||
if self.query.combinator and self.select:
|
if not is_ref and self.query.combinator and self.select:
|
||||||
src = resolved.expression
|
src = resolved.expression
|
||||||
expr_src = expr.expression
|
expr_src = expr.expression
|
||||||
# Relabel order by columns to raw numbers if this is a combined
|
for sel_expr, _, col_alias in self.select:
|
||||||
# query; necessary since the columns can't be referenced by the
|
if col_alias and not (
|
||||||
# fully qualified name and the simple column names may collide.
|
|
||||||
for idx, (sel_expr, _, col_alias) in enumerate(self.select):
|
|
||||||
if is_ref and col_alias == src.refs:
|
|
||||||
src = src.source
|
|
||||||
elif col_alias and not (
|
|
||||||
isinstance(expr_src, F) and col_alias == expr_src.name
|
isinstance(expr_src, F) and col_alias == expr_src.name
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
if src == sel_expr:
|
if src == sel_expr:
|
||||||
resolved.set_source_expressions([RawSQL("%d" % (idx + 1), ())])
|
resolved.set_source_expressions(
|
||||||
|
[Ref(col_alias if col_alias else src.target.column, src)]
|
||||||
|
)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
if col_alias:
|
if col_alias:
|
||||||
|
@ -853,7 +850,11 @@ class SQLCompiler:
|
||||||
for _, (o_sql, o_params, _) in order_by:
|
for _, (o_sql, o_params, _) in order_by:
|
||||||
ordering.append(o_sql)
|
ordering.append(o_sql)
|
||||||
params.extend(o_params)
|
params.extend(o_params)
|
||||||
result.append("ORDER BY %s" % ", ".join(ordering))
|
order_by_sql = "ORDER BY %s" % ", ".join(ordering)
|
||||||
|
if combinator and features.requires_compound_order_by_subquery:
|
||||||
|
result = ["SELECT * FROM (", *result, ")", order_by_sql]
|
||||||
|
else:
|
||||||
|
result.append(order_by_sql)
|
||||||
|
|
||||||
if with_limit_offset:
|
if with_limit_offset:
|
||||||
result.append(
|
result.append(
|
||||||
|
|
|
@ -61,6 +61,24 @@ class QuerySetSetOperationTests(TestCase):
|
||||||
self.assertSequenceEqual(qs3.none(), [])
|
self.assertSequenceEqual(qs3.none(), [])
|
||||||
self.assertNumbersEqual(qs3, [0, 1, 8, 9], ordered=False)
|
self.assertNumbersEqual(qs3, [0, 1, 8, 9], ordered=False)
|
||||||
|
|
||||||
|
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)
|
||||||
|
qs2 = Number.objects.filter(num__gte=2)
|
||||||
|
qs3 = qs1.union(qs2)
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
qs3.order_by(
|
||||||
|
F("other_num").asc(nulls_first=True),
|
||||||
|
).values_list("other_num", flat=True),
|
||||||
|
[None, 1, 2, 3, 4, 6, 7, 8, 9, 10],
|
||||||
|
)
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
qs3.order_by(
|
||||||
|
F("other_num").asc(nulls_last=True),
|
||||||
|
).values_list("other_num", flat=True),
|
||||||
|
[1, 2, 3, 4, 6, 7, 8, 9, 10, None],
|
||||||
|
)
|
||||||
|
|
||||||
@skipUnlessDBFeature("supports_select_intersection")
|
@skipUnlessDBFeature("supports_select_intersection")
|
||||||
def test_intersection_with_empty_qs(self):
|
def test_intersection_with_empty_qs(self):
|
||||||
qs1 = Number.objects.all()
|
qs1 = Number.objects.all()
|
||||||
|
|
Loading…
Reference in New Issue