@@ -32,10 +32,10 @@ use datafusion_physical_optimizer::pushdown_sort::PushdownSort;
3232use std:: sync:: Arc ;
3333
3434use crate :: physical_optimizer:: test_utils:: {
35- OptimizationTest , coalesce_partitions_exec , parquet_exec , parquet_exec_with_sort ,
36- projection_exec , projection_exec_with_alias , repartition_exec , schema ,
37- simple_projection_exec , sort_exec , sort_exec_with_fetch , sort_expr , sort_expr_named ,
38- test_scan_with_ordering,
35+ OptimizationTest , TestScan , coalesce_partitions_exec , parquet_exec ,
36+ parquet_exec_with_sort , projection_exec , projection_exec_with_alias ,
37+ repartition_exec , schema , simple_projection_exec , sort_exec , sort_exec_with_fetch ,
38+ sort_expr , sort_expr_named , test_scan_with_ordering,
3939} ;
4040
4141#[ test]
@@ -996,3 +996,91 @@ fn test_sort_pushdown_with_test_scan_arbitrary_ordering() {
996996 "
997997 ) ;
998998}
999+
1000+ // ============================================================================
1001+ // EXACT PUSHDOWN TESTS (source guarantees ordering, SortExec removed)
1002+ // ============================================================================
1003+
1004+ #[ test]
1005+ fn test_sort_pushdown_exact_no_fetch_no_limit ( ) {
1006+ // When a source returns Exact (without fetch), the SortExec should be
1007+ // removed entirely with no GlobalLimitExec wrapper.
1008+ let schema = schema ( ) ;
1009+ let a = sort_expr ( "a" , & schema) ;
1010+ let b = sort_expr ( "b" , & schema) ;
1011+ let source =
1012+ Arc :: new ( TestScan :: new ( schema. clone ( ) , vec ! [ ] ) . with_exact_pushdown ( true ) ) ;
1013+
1014+ let ordering = LexOrdering :: new ( vec ! [ a, b. reverse( ) ] ) . unwrap ( ) ;
1015+ let plan = sort_exec ( ordering, source) ;
1016+
1017+ insta:: assert_snapshot!(
1018+ OptimizationTest :: new( plan, PushdownSort :: new( ) , true ) ,
1019+ @r"
1020+ OptimizationTest:
1021+ input:
1022+ - SortExec: expr=[a@0 ASC, b@1 DESC NULLS LAST], preserve_partitioning=[false]
1023+ - TestScan
1024+ output:
1025+ Ok:
1026+ - TestScan: requested_ordering=[a@0 ASC, b@1 DESC NULLS LAST]
1027+ "
1028+ ) ;
1029+ }
1030+
1031+ #[ test]
1032+ fn test_sort_pushdown_exact_preserves_fetch_with_global_limit ( ) {
1033+ // When a source returns Exact but does NOT support with_fetch(),
1034+ // the optimizer must wrap the result with GlobalLimitExec to preserve
1035+ // the LIMIT from the eliminated SortExec.
1036+ let schema = schema ( ) ;
1037+ let a = sort_expr ( "a" , & schema) ;
1038+ let source =
1039+ Arc :: new ( TestScan :: new ( schema. clone ( ) , vec ! [ ] ) . with_exact_pushdown ( true ) ) ;
1040+
1041+ let ordering = LexOrdering :: new ( vec ! [ a] ) . unwrap ( ) ;
1042+ let plan = sort_exec_with_fetch ( ordering, Some ( 10 ) , source) ;
1043+
1044+ insta:: assert_snapshot!(
1045+ OptimizationTest :: new( plan, PushdownSort :: new( ) , true ) ,
1046+ @r"
1047+ OptimizationTest:
1048+ input:
1049+ - SortExec: TopK(fetch=10), expr=[a@0 ASC], preserve_partitioning=[false]
1050+ - TestScan
1051+ output:
1052+ Ok:
1053+ - GlobalLimitExec: skip=0, fetch=10
1054+ - TestScan: requested_ordering=[a@0 ASC]
1055+ "
1056+ ) ;
1057+ }
1058+
1059+ #[ test]
1060+ fn test_sort_pushdown_exact_preserves_fetch_with_source_support ( ) {
1061+ // When a source returns Exact AND supports with_fetch(),
1062+ // the limit should be pushed into the source directly (no GlobalLimitExec).
1063+ let schema = schema ( ) ;
1064+ let a = sort_expr ( "a" , & schema) ;
1065+ let source = Arc :: new (
1066+ TestScan :: new ( schema. clone ( ) , vec ! [ ] )
1067+ . with_exact_pushdown ( true )
1068+ . with_supports_fetch ( true ) ,
1069+ ) ;
1070+
1071+ let ordering = LexOrdering :: new ( vec ! [ a] ) . unwrap ( ) ;
1072+ let plan = sort_exec_with_fetch ( ordering, Some ( 10 ) , source) ;
1073+
1074+ insta:: assert_snapshot!(
1075+ OptimizationTest :: new( plan, PushdownSort :: new( ) , true ) ,
1076+ @r"
1077+ OptimizationTest:
1078+ input:
1079+ - SortExec: TopK(fetch=10), expr=[a@0 ASC], preserve_partitioning=[false]
1080+ - TestScan
1081+ output:
1082+ Ok:
1083+ - TestScan: requested_ordering=[a@0 ASC], fetch=10
1084+ "
1085+ ) ;
1086+ }
0 commit comments