@@ -33,9 +33,9 @@ use std::sync::Arc;
3333
3434use crate :: physical_optimizer:: test_utils:: {
3535 OptimizationTest , coalesce_batches_exec, 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,
36+ parquet_exec_with_sort, parquet_exec_with_sort_exact_reverse , projection_exec ,
37+ projection_exec_with_alias , repartition_exec, schema, simple_projection_exec,
38+ sort_exec , sort_exec_with_fetch , sort_expr, sort_expr_named, test_scan_with_ordering,
3939} ;
4040
4141#[ test]
@@ -1038,3 +1038,119 @@ fn test_sort_pushdown_with_test_scan_arbitrary_ordering() {
10381038 "
10391039 ) ;
10401040}
1041+
1042+ // ============================================================================
1043+ // EXACT REVERSE SCAN TESTS
1044+ // ============================================================================
1045+ // These tests verify behavior when exact_reverse is enabled on ParquetSource.
1046+ // With exact reverse, the Sort operator is removed entirely and fetch is pushed
1047+ // down to the scan.
1048+
1049+ #[ test]
1050+ fn test_exact_reverse_removes_sort ( ) {
1051+ // With exact_reverse=true, Sort should be removed entirely
1052+ let schema = schema ( ) ;
1053+ let a = sort_expr ( "a" , & schema) ;
1054+ let source_ordering = LexOrdering :: new ( vec ! [ a. clone( ) ] ) . unwrap ( ) ;
1055+ let source =
1056+ parquet_exec_with_sort_exact_reverse ( schema. clone ( ) , vec ! [ source_ordering] ) ;
1057+
1058+ let desc_ordering = LexOrdering :: new ( vec ! [ a. reverse( ) ] ) . unwrap ( ) ;
1059+ let plan = sort_exec ( desc_ordering, source) ;
1060+
1061+ insta:: assert_snapshot!(
1062+ OptimizationTest :: new( plan, PushdownSort :: new( ) , true ) ,
1063+ @r"
1064+ OptimizationTest:
1065+ input:
1066+ - SortExec: expr=[a@0 DESC NULLS LAST], preserve_partitioning=[false]
1067+ - DataSourceExec: file_groups={1 group: [[x]]}, projection=[a, b, c, d, e], output_ordering=[a@0 ASC], file_type=parquet
1068+ output:
1069+ Ok:
1070+ - DataSourceExec: file_groups={1 group: [[x]]}, projection=[a, b, c, d, e], output_ordering=[a@0 ASC], file_type=parquet, scan_direction=Reversed
1071+ "
1072+ ) ;
1073+ }
1074+
1075+ #[ test]
1076+ fn test_exact_reverse_with_fetch_pushes_limit ( ) {
1077+ // With exact_reverse=true, Sort with fetch should be removed and fetch
1078+ // pushed down to the scan
1079+ let schema = schema ( ) ;
1080+ let a = sort_expr ( "a" , & schema) ;
1081+ let source_ordering = LexOrdering :: new ( vec ! [ a. clone( ) ] ) . unwrap ( ) ;
1082+ let source =
1083+ parquet_exec_with_sort_exact_reverse ( schema. clone ( ) , vec ! [ source_ordering] ) ;
1084+
1085+ let desc_ordering = LexOrdering :: new ( vec ! [ a. reverse( ) ] ) . unwrap ( ) ;
1086+ let plan = sort_exec_with_fetch ( desc_ordering, Some ( 10 ) , source) ;
1087+
1088+ insta:: assert_snapshot!(
1089+ OptimizationTest :: new( plan, PushdownSort :: new( ) , true ) ,
1090+ @r"
1091+ OptimizationTest:
1092+ input:
1093+ - SortExec: TopK(fetch=10), expr=[a@0 DESC NULLS LAST], preserve_partitioning=[false]
1094+ - DataSourceExec: file_groups={1 group: [[x]]}, projection=[a, b, c, d, e], output_ordering=[a@0 ASC], file_type=parquet
1095+ output:
1096+ Ok:
1097+ - DataSourceExec: file_groups={1 group: [[x]]}, projection=[a, b, c, d, e], limit=10, output_ordering=[a@0 ASC], file_type=parquet, scan_direction=Reversed
1098+ "
1099+ ) ;
1100+ }
1101+
1102+ #[ test]
1103+ fn test_exact_reverse_through_projection_with_fetch ( ) {
1104+ // Exact reverse with fetch pushes through projection
1105+ let schema = schema ( ) ;
1106+ let a = sort_expr ( "a" , & schema) ;
1107+ let source_ordering = LexOrdering :: new ( vec ! [ a. clone( ) ] ) . unwrap ( ) ;
1108+ let source =
1109+ parquet_exec_with_sort_exact_reverse ( schema. clone ( ) , vec ! [ source_ordering] ) ;
1110+
1111+ let projection = simple_projection_exec ( source, vec ! [ 0 , 1 ] ) ;
1112+
1113+ let desc_ordering = LexOrdering :: new ( vec ! [ a. reverse( ) ] ) . unwrap ( ) ;
1114+ let plan = sort_exec_with_fetch ( desc_ordering, Some ( 5 ) , projection) ;
1115+
1116+ insta:: assert_snapshot!(
1117+ OptimizationTest :: new( plan, PushdownSort :: new( ) , true ) ,
1118+ @r"
1119+ OptimizationTest:
1120+ input:
1121+ - SortExec: TopK(fetch=5), expr=[a@0 DESC NULLS LAST], preserve_partitioning=[false]
1122+ - ProjectionExec: expr=[a@0 as a, b@1 as b]
1123+ - DataSourceExec: file_groups={1 group: [[x]]}, projection=[a, b, c, d, e], output_ordering=[a@0 ASC], file_type=parquet
1124+ output:
1125+ Ok:
1126+ - ProjectionExec: expr=[a@0 as a, b@1 as b]
1127+ - DataSourceExec: file_groups={1 group: [[x]]}, projection=[a, b, c, d, e], limit=5, output_ordering=[a@0 ASC], file_type=parquet, scan_direction=Reversed
1128+ "
1129+ ) ;
1130+ }
1131+
1132+ #[ test]
1133+ fn test_exact_reverse_without_fetch_no_limit ( ) {
1134+ // Exact reverse without fetch: Sort removed, no limit on scan
1135+ let schema = schema ( ) ;
1136+ let a = sort_expr ( "a" , & schema) ;
1137+ let source_ordering = LexOrdering :: new ( vec ! [ a. clone( ) ] ) . unwrap ( ) ;
1138+ let source =
1139+ parquet_exec_with_sort_exact_reverse ( schema. clone ( ) , vec ! [ source_ordering] ) ;
1140+
1141+ let desc_ordering = LexOrdering :: new ( vec ! [ a. reverse( ) ] ) . unwrap ( ) ;
1142+ let plan = sort_exec ( desc_ordering, source) ; // no fetch
1143+
1144+ insta:: assert_snapshot!(
1145+ OptimizationTest :: new( plan, PushdownSort :: new( ) , true ) ,
1146+ @r"
1147+ OptimizationTest:
1148+ input:
1149+ - SortExec: expr=[a@0 DESC NULLS LAST], preserve_partitioning=[false]
1150+ - DataSourceExec: file_groups={1 group: [[x]]}, projection=[a, b, c, d, e], output_ordering=[a@0 ASC], file_type=parquet
1151+ output:
1152+ Ok:
1153+ - DataSourceExec: file_groups={1 group: [[x]]}, projection=[a, b, c, d, e], output_ordering=[a@0 ASC], file_type=parquet, scan_direction=Reversed
1154+ "
1155+ ) ;
1156+ }
0 commit comments