Skip to content

Commit 80bbece

Browse files
committed
fix: Make default sort order nulls last
1 parent 22b4e4b commit 80bbece

4 files changed

Lines changed: 106 additions & 33 deletions

File tree

‎python/datafusion/expr.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ def sort_or_default(e: Expr | SortExpr) -> expr_internal.SortExpr:
425425
"""Helper function to return a default Sort if an Expr is provided."""
426426
if isinstance(e, SortExpr):
427427
return e.raw_sort
428-
return SortExpr(e, ascending=True, nulls_first=True).raw_sort
428+
return SortExpr(e, ascending=True, nulls_first=False).raw_sort
429429

430430

431431
def sort_list_to_raw_sort_list(
@@ -893,7 +893,7 @@ def alias(self, name: str, metadata: dict[str, str] | None = None) -> Expr:
893893
"""
894894
return Expr(self.expr.alias(name, metadata))
895895

896-
def sort(self, ascending: bool = True, nulls_first: bool = True) -> SortExpr:
896+
def sort(self, ascending: bool = True, nulls_first: bool = False) -> SortExpr:
897897
"""Creates a sort :py:class:`Expr` from an existing :py:class:`Expr`.
898898
899899
Args:

‎python/datafusion/functions/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ def concat_ws(separator: str, *args: Expr) -> Expr:
808808
return Expr(f.concat_ws(separator, args))
809809

810810

811-
def order_by(expr: Expr, ascending: bool = True, nulls_first: bool = True) -> SortExpr:
811+
def order_by(expr: Expr, ascending: bool = True, nulls_first: bool = False) -> SortExpr:
812812
"""Creates a new sort expression.
813813
814814
Examples:

‎python/tests/test_aggregation.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ def test_bit_and_bool_fns(df, name, expr, result):
363363
"first_value_with_null",
364364
f.first_value(
365365
column("b"),
366-
order_by=[column("b").sort(ascending=True)],
366+
order_by=[column("b").sort(ascending=True, nulls_first=True)],
367367
null_treatment=NullTreatment.RESPECT_NULLS,
368368
),
369369
[None, None],
@@ -372,7 +372,7 @@ def test_bit_and_bool_fns(df, name, expr, result):
372372
"first_value_no_list_order_by",
373373
f.first_value(
374374
column("b"),
375-
order_by=column("b"),
375+
order_by=column("b").sort(nulls_first=True),
376376
null_treatment=NullTreatment.RESPECT_NULLS,
377377
),
378378
[None, None],

‎python/tests/test_dataframe.py‎

Lines changed: 101 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -925,29 +925,41 @@ def test_distinct():
925925
data_test_window_functions = [
926926
(
927927
"row",
928-
f.row_number(order_by=[column("b"), column("a").sort(ascending=False)]),
928+
f.row_number(
929+
order_by=[
930+
f.order_by(column("b"), nulls_first=True),
931+
column("a").sort(ascending=False),
932+
]
933+
),
929934
[4, 2, 3, 5, 7, 1, 6],
930935
),
931936
(
932937
"row_w_params",
933938
f.row_number(
934-
order_by=[column("b"), column("a")],
939+
order_by=[f.order_by(column("b"), nulls_first=True), column("a")],
935940
partition_by=[column("c")],
936941
),
937942
[2, 1, 3, 4, 2, 1, 3],
938943
),
939944
(
940945
"row_w_params_no_lists",
941946
f.row_number(
942-
order_by=column("b"),
947+
order_by=f.order_by(column("b"), nulls_first=True),
943948
partition_by=column("c"),
944949
),
945950
[2, 1, 3, 4, 2, 1, 3],
946951
),
947-
("rank", f.rank(order_by=[column("b")]), [3, 1, 3, 5, 6, 1, 6]),
952+
(
953+
"rank",
954+
f.rank(order_by=[f.order_by(column("b"), nulls_first=True)]),
955+
[3, 1, 3, 5, 6, 1, 6],
956+
),
948957
(
949958
"rank_w_params",
950-
f.rank(order_by=[column("b"), column("a")], partition_by=[column("c")]),
959+
f.rank(
960+
order_by=[f.order_by(column("b"), nulls_first=True), column("a")],
961+
partition_by=[column("c")],
962+
),
951963
[2, 1, 3, 4, 2, 1, 3],
952964
),
953965
(
@@ -957,12 +969,15 @@ def test_distinct():
957969
),
958970
(
959971
"dense_rank",
960-
f.dense_rank(order_by=[column("b")]),
972+
f.dense_rank(order_by=[f.order_by(column("b"), nulls_first=True)]),
961973
[2, 1, 2, 3, 4, 1, 4],
962974
),
963975
(
964976
"dense_rank_w_params",
965-
f.dense_rank(order_by=[column("b"), column("a")], partition_by=[column("c")]),
977+
f.dense_rank(
978+
order_by=[f.order_by(column("b"), nulls_first=True), column("a")],
979+
partition_by=[column("c")],
980+
),
966981
[2, 1, 3, 4, 2, 1, 3],
967982
),
968983
(
@@ -972,14 +987,18 @@ def test_distinct():
972987
),
973988
(
974989
"percent_rank",
975-
f.round(f.percent_rank(order_by=[column("b")]), literal(3)),
990+
f.round(
991+
f.percent_rank(order_by=[f.order_by(column("b"), nulls_first=True)]),
992+
literal(3),
993+
),
976994
[0.333, 0.0, 0.333, 0.667, 0.833, 0.0, 0.833],
977995
),
978996
(
979997
"percent_rank_w_params",
980998
f.round(
981999
f.percent_rank(
982-
order_by=[column("b"), column("a")], partition_by=[column("c")]
1000+
order_by=[f.order_by(column("b"), nulls_first=True), column("a")],
1001+
partition_by=[column("c")],
9831002
),
9841003
literal(3),
9851004
),
@@ -995,14 +1014,18 @@ def test_distinct():
9951014
),
9961015
(
9971016
"cume_dist",
998-
f.round(f.cume_dist(order_by=[column("b")]), literal(3)),
1017+
f.round(
1018+
f.cume_dist(order_by=[f.order_by(column("b"), nulls_first=True)]),
1019+
literal(3),
1020+
),
9991021
[0.571, 0.286, 0.571, 0.714, 1.0, 0.286, 1.0],
10001022
),
10011023
(
10021024
"cume_dist_w_params",
10031025
f.round(
10041026
f.cume_dist(
1005-
order_by=[column("b"), column("a")], partition_by=[column("c")]
1027+
order_by=[f.order_by(column("b"), nulls_first=True), column("a")],
1028+
partition_by=[column("c")],
10061029
),
10071030
literal(3),
10081031
),
@@ -1018,27 +1041,39 @@ def test_distinct():
10181041
),
10191042
(
10201043
"ntile",
1021-
f.ntile(2, order_by=[column("b")]),
1044+
f.ntile(2, order_by=[f.order_by(column("b"), nulls_first=True)]),
10221045
[1, 1, 1, 2, 2, 1, 2],
10231046
),
10241047
(
10251048
"ntile_w_params",
1026-
f.ntile(2, order_by=[column("b"), column("a")], partition_by=[column("c")]),
1049+
f.ntile(
1050+
2,
1051+
order_by=[f.order_by(column("b"), nulls_first=True), column("a")],
1052+
partition_by=[column("c")],
1053+
),
10271054
[1, 1, 2, 2, 1, 1, 2],
10281055
),
10291056
(
10301057
"ntile_w_params_no_lists",
1031-
f.ntile(2, order_by=column("b"), partition_by=column("c")),
1058+
f.ntile(
1059+
2,
1060+
order_by=f.order_by(column("b"), nulls_first=True),
1061+
partition_by=column("c"),
1062+
),
10321063
[1, 1, 2, 2, 1, 1, 2],
10331064
),
1034-
("lead", f.lead(column("b"), order_by=[column("b")]), [7, None, 8, 9, 9, 7, None]),
1065+
(
1066+
"lead",
1067+
f.lead(column("b"), order_by=[f.order_by(column("b"), nulls_first=True)]),
1068+
[7, None, 8, 9, 9, 7, None],
1069+
),
10351070
(
10361071
"lead_w_params",
10371072
f.lead(
10381073
column("b"),
10391074
shift_offset=2,
10401075
default_value=-1,
1041-
order_by=[column("b"), column("a")],
1076+
order_by=[f.order_by(column("b"), nulls_first=True), column("a")],
10421077
partition_by=[column("c")],
10431078
),
10441079
[8, 7, -1, -1, -1, 9, -1],
@@ -1049,19 +1084,23 @@ def test_distinct():
10491084
column("b"),
10501085
shift_offset=2,
10511086
default_value=-1,
1052-
order_by=column("b"),
1087+
order_by=f.order_by(column("b"), nulls_first=True),
10531088
partition_by=column("c"),
10541089
),
10551090
[8, 7, -1, -1, -1, 9, -1],
10561091
),
1057-
("lag", f.lag(column("b"), order_by=[column("b")]), [None, None, 7, 7, 8, None, 9]),
1092+
(
1093+
"lag",
1094+
f.lag(column("b"), order_by=[f.order_by(column("b"), nulls_first=True)]),
1095+
[None, None, 7, 7, 8, None, 9],
1096+
),
10581097
(
10591098
"lag_w_params",
10601099
f.lag(
10611100
column("b"),
10621101
shift_offset=2,
10631102
default_value=-1,
1064-
order_by=[column("b"), column("a")],
1103+
order_by=[f.order_by(column("b"), nulls_first=True), column("a")],
10651104
partition_by=[column("c")],
10661105
),
10671106
[-1, -1, None, 7, -1, -1, None],
@@ -1072,29 +1111,38 @@ def test_distinct():
10721111
column("b"),
10731112
shift_offset=2,
10741113
default_value=-1,
1075-
order_by=column("b"),
1114+
order_by=f.order_by(column("b"), nulls_first=True),
10761115
partition_by=column("c"),
10771116
),
10781117
[-1, -1, None, 7, -1, -1, None],
10791118
),
10801119
(
10811120
"first_value",
10821121
f.first_value(column("a")).over(
1083-
Window(partition_by=[column("c")], order_by=[column("b")])
1122+
Window(
1123+
partition_by=[column("c")],
1124+
order_by=[f.order_by(column("b"), nulls_first=True)],
1125+
)
10841126
),
10851127
[1, 1, 1, 1, 5, 5, 5],
10861128
),
10871129
(
10881130
"first_value_without_list_args",
10891131
f.first_value(column("a")).over(
1090-
Window(partition_by=column("c"), order_by=column("b"))
1132+
Window(
1133+
partition_by=column("c"),
1134+
order_by=f.order_by(column("b"), nulls_first=True),
1135+
)
10911136
),
10921137
[1, 1, 1, 1, 5, 5, 5],
10931138
),
10941139
(
10951140
"first_value_order_by_string",
10961141
f.first_value(column("a")).over(
1097-
Window(partition_by=[column("c")], order_by="b")
1142+
Window(
1143+
partition_by=[column("c")],
1144+
order_by=f.order_by(column("b"), nulls_first=True),
1145+
)
10981146
),
10991147
[1, 1, 1, 1, 5, 5, 5],
11001148
),
@@ -1103,15 +1151,17 @@ def test_distinct():
11031151
f.last_value(column("a")).over(
11041152
Window(
11051153
partition_by=[column("c")],
1106-
order_by=[column("b")],
1154+
order_by=[f.order_by(column("b"), nulls_first=True)],
11071155
window_frame=WindowFrame("rows", None, None),
11081156
)
11091157
),
11101158
[3, 3, 3, 3, 6, 6, 6],
11111159
),
11121160
(
11131161
"3rd_value",
1114-
f.nth_value(column("b"), 3).over(Window(order_by=[column("a")])),
1162+
f.nth_value(column("b"), 3).over(
1163+
Window(order_by=[f.order_by(column("a"), nulls_first=True)])
1164+
),
11151165
[None, None, 7, 7, 7, 7, 7],
11161166
),
11171167
(
@@ -1154,7 +1204,9 @@ def test_rank_partition_by_accepts_string(partitioned_df, partition):
11541204
def test_window_partition_by_accepts_string(partitioned_df, partition):
11551205
"""Window.partition_by accepts string identifiers."""
11561206
expr = f.first_value(column("a")).over(
1157-
Window(partition_by=partition, order_by=column("b"))
1207+
Window(
1208+
partition_by=partition, order_by=f.order_by(column("b"), nulls_first=True)
1209+
)
11581210
)
11591211
df = partitioned_df.select(expr.alias("fv"))
11601212
table = pa.Table.from_batches(df.sort(column("a")).collect())
@@ -1262,9 +1314,9 @@ def _build_array_agg_df(df):
12621314
@pytest.mark.parametrize(
12631315
("builder", "expected"),
12641316
[
1265-
pytest.param(_build_last_value_df, [3, 3, 3, 3, 6, 6, 6], id="last_value"),
1317+
pytest.param(_build_last_value_df, [1, 1, 1, 1, 5, 5, 5], id="last_value"),
12661318
pytest.param(_build_nth_value_df, [None, None, 7, 7, 7, 7, 7], id="nth_value"),
1267-
pytest.param(_build_rank_df, [1, 1, 3, 3, 5, 6, 6], id="rank"),
1319+
pytest.param(_build_rank_df, [1, 1, 3, 4, 4, 6, 6], id="rank"),
12681320
pytest.param(_build_array_agg_df, [[0, 1, 2, 3], [4, 5, 6]], id="array_agg"),
12691321
],
12701322
)
@@ -3881,3 +3933,24 @@ def test_unnest_columns_with_recursions(input_data, recursions, expected_a):
38813933
kwargs["recursions"] = recursions
38823934
result = df.unnest_columns("a", **kwargs).collect()[0]
38833935
assert result.column(0).to_pylist() == expected_a
3936+
3937+
3938+
def test_sort_default_null_behavior():
3939+
ctx = SessionContext()
3940+
ctx.sql("create table t (a int)").collect()
3941+
ctx.sql("insert into t values (3), (null), (1), (null), (4), (2)").collect()
3942+
3943+
# sort
3944+
result_sort = ctx.table("t").sort(column("a")).to_pydict()
3945+
3946+
# sort_by
3947+
result_sort_by = ctx.table("t").sort_by(column("a")).to_pydict()
3948+
3949+
# sql
3950+
result_sql = ctx.sql("select * from t order by a").to_pydict()
3951+
3952+
# order_by function
3953+
result_order_by = ctx.table("t").sort(f.order_by(column("a"))).to_pydict()
3954+
3955+
assert result_sort == result_sort_by == result_sql == result_order_by
3956+
assert result_sort == {"a": [1, 2, 3, 4, None, None]}

0 commit comments

Comments
 (0)