@@ -63,6 +63,8 @@ use datafusion_common::tree_node::{Transformed, TreeNode};
63
63
use datafusion_common:: { plan_err, DataFusionError } ;
64
64
use datafusion_physical_expr:: { PhysicalSortExpr , PhysicalSortRequirement } ;
65
65
use datafusion_physical_plan:: repartition:: RepartitionExec ;
66
+ use datafusion_physical_plan:: sorts:: partial_sort:: PartialSortExec ;
67
+ use datafusion_physical_plan:: unbounded_output;
66
68
67
69
use itertools:: izip;
68
70
@@ -185,7 +187,10 @@ impl PhysicalOptimizerRule for EnforceSorting {
185
187
let mut sort_pushdown = SortPushDown :: new_default ( updated_plan. plan ) ;
186
188
assign_initial_requirements ( & mut sort_pushdown) ;
187
189
let adjusted = sort_pushdown. transform_down ( & pushdown_sorts) ?;
188
- Ok ( adjusted. plan )
190
+
191
+ adjusted
192
+ . plan
193
+ . transform_up ( & |plan| Ok ( Transformed :: Yes ( replace_with_partial_sort ( plan) ?) ) )
189
194
}
190
195
191
196
fn name ( & self ) -> & str {
@@ -197,6 +202,42 @@ impl PhysicalOptimizerRule for EnforceSorting {
197
202
}
198
203
}
199
204
205
+ fn replace_with_partial_sort (
206
+ plan : Arc < dyn ExecutionPlan > ,
207
+ ) -> Result < Arc < dyn ExecutionPlan > > {
208
+ let plan_any = plan. as_any ( ) ;
209
+ if let Some ( sort_plan) = plan_any. downcast_ref :: < SortExec > ( ) {
210
+ let child = sort_plan. children ( ) [ 0 ] . clone ( ) ;
211
+ if !unbounded_output ( & child) {
212
+ return Ok ( plan) ;
213
+ }
214
+
215
+ // here we're trying to find the common prefix for sorted columns that is required for the
216
+ // sort and already satisfied by the given ordering
217
+ let child_eq_properties = child. equivalence_properties ( ) ;
218
+ let sort_req = PhysicalSortRequirement :: from_sort_exprs ( sort_plan. expr ( ) ) ;
219
+
220
+ let mut common_prefix_length = 0 ;
221
+ while child_eq_properties
222
+ . ordering_satisfy_requirement ( & sort_req[ 0 ..common_prefix_length + 1 ] )
223
+ {
224
+ common_prefix_length += 1 ;
225
+ }
226
+ if common_prefix_length > 0 {
227
+ return Ok ( Arc :: new (
228
+ PartialSortExec :: new (
229
+ sort_plan. expr ( ) . to_vec ( ) ,
230
+ sort_plan. input ( ) . clone ( ) ,
231
+ common_prefix_length,
232
+ )
233
+ . with_preserve_partitioning ( sort_plan. preserve_partitioning ( ) )
234
+ . with_fetch ( sort_plan. fetch ( ) ) ,
235
+ ) ) ;
236
+ }
237
+ }
238
+ Ok ( plan)
239
+ }
240
+
200
241
/// This function turns plans of the form
201
242
/// ```text
202
243
/// "SortExec: expr=\[a@0 ASC\]",
@@ -2205,4 +2246,101 @@ mod tests {
2205
2246
2206
2247
Ok ( ( ) )
2207
2248
}
2249
+
2250
+ #[ tokio:: test]
2251
+ async fn test_replace_with_partial_sort ( ) -> Result < ( ) > {
2252
+ let schema = create_test_schema3 ( ) ?;
2253
+ let input_sort_exprs = vec ! [ sort_expr( "a" , & schema) ] ;
2254
+ let unbounded_input = stream_exec_ordered ( & schema, input_sort_exprs) ;
2255
+
2256
+ let physical_plan = sort_exec (
2257
+ vec ! [ sort_expr( "a" , & schema) , sort_expr( "c" , & schema) ] ,
2258
+ unbounded_input,
2259
+ ) ;
2260
+
2261
+ let expected_input = [
2262
+ "SortExec: expr=[a@0 ASC,c@2 ASC]" ,
2263
+ " StreamingTableExec: partition_sizes=1, projection=[a, b, c, d, e], infinite_source=true, output_ordering=[a@0 ASC]"
2264
+ ] ;
2265
+ let expected_optimized = [
2266
+ "PartialSortExec: expr=[a@0 ASC,c@2 ASC], common_prefix_length=[1]" ,
2267
+ " StreamingTableExec: partition_sizes=1, projection=[a, b, c, d, e], infinite_source=true, output_ordering=[a@0 ASC]" ,
2268
+ ] ;
2269
+ assert_optimized ! ( expected_input, expected_optimized, physical_plan, true ) ;
2270
+ Ok ( ( ) )
2271
+ }
2272
+
2273
+ #[ tokio:: test]
2274
+ async fn test_replace_with_partial_sort2 ( ) -> Result < ( ) > {
2275
+ let schema = create_test_schema3 ( ) ?;
2276
+ let input_sort_exprs = vec ! [ sort_expr( "a" , & schema) , sort_expr( "c" , & schema) ] ;
2277
+ let unbounded_input = stream_exec_ordered ( & schema, input_sort_exprs) ;
2278
+
2279
+ let physical_plan = sort_exec (
2280
+ vec ! [
2281
+ sort_expr( "a" , & schema) ,
2282
+ sort_expr( "c" , & schema) ,
2283
+ sort_expr( "d" , & schema) ,
2284
+ ] ,
2285
+ unbounded_input,
2286
+ ) ;
2287
+
2288
+ let expected_input = [
2289
+ "SortExec: expr=[a@0 ASC,c@2 ASC,d@3 ASC]" ,
2290
+ " StreamingTableExec: partition_sizes=1, projection=[a, b, c, d, e], infinite_source=true, output_ordering=[a@0 ASC, c@2 ASC]"
2291
+ ] ;
2292
+ // let optimized
2293
+ let expected_optimized = [
2294
+ "PartialSortExec: expr=[a@0 ASC,c@2 ASC,d@3 ASC], common_prefix_length=[2]" ,
2295
+ " StreamingTableExec: partition_sizes=1, projection=[a, b, c, d, e], infinite_source=true, output_ordering=[a@0 ASC, c@2 ASC]" ,
2296
+ ] ;
2297
+ assert_optimized ! ( expected_input, expected_optimized, physical_plan, true ) ;
2298
+ Ok ( ( ) )
2299
+ }
2300
+
2301
+ #[ tokio:: test]
2302
+ async fn test_not_replaced_with_partial_sort_for_bounded_input ( ) -> Result < ( ) > {
2303
+ let schema = create_test_schema3 ( ) ?;
2304
+ let input_sort_exprs = vec ! [ sort_expr( "b" , & schema) , sort_expr( "c" , & schema) ] ;
2305
+ let parquet_input = parquet_exec_sorted ( & schema, input_sort_exprs) ;
2306
+
2307
+ let physical_plan = sort_exec (
2308
+ vec ! [
2309
+ sort_expr( "a" , & schema) ,
2310
+ sort_expr( "b" , & schema) ,
2311
+ sort_expr( "c" , & schema) ,
2312
+ ] ,
2313
+ parquet_input,
2314
+ ) ;
2315
+ let expected_input = [
2316
+ "SortExec: expr=[a@0 ASC,b@1 ASC,c@2 ASC]" ,
2317
+ " ParquetExec: file_groups={1 group: [[x]]}, projection=[a, b, c, d, e], output_ordering=[b@1 ASC, c@2 ASC]"
2318
+ ] ;
2319
+ let expected_no_change = expected_input;
2320
+ assert_optimized ! ( expected_input, expected_no_change, physical_plan, false ) ;
2321
+ Ok ( ( ) )
2322
+ }
2323
+
2324
+ #[ tokio:: test]
2325
+ async fn test_not_replaced_with_partial_sort_for_unbounded_input ( ) -> Result < ( ) > {
2326
+ let schema = create_test_schema3 ( ) ?;
2327
+ let input_sort_exprs = vec ! [ sort_expr( "b" , & schema) , sort_expr( "c" , & schema) ] ;
2328
+ let unbounded_input = stream_exec_ordered ( & schema, input_sort_exprs) ;
2329
+
2330
+ let physical_plan = sort_exec (
2331
+ vec ! [
2332
+ sort_expr( "a" , & schema) ,
2333
+ sort_expr( "b" , & schema) ,
2334
+ sort_expr( "c" , & schema) ,
2335
+ ] ,
2336
+ unbounded_input,
2337
+ ) ;
2338
+ let expected_input = [
2339
+ "SortExec: expr=[a@0 ASC,b@1 ASC,c@2 ASC]" ,
2340
+ " StreamingTableExec: partition_sizes=1, projection=[a, b, c, d, e], infinite_source=true, output_ordering=[b@1 ASC, c@2 ASC]"
2341
+ ] ;
2342
+ let expected_no_change = expected_input;
2343
+ assert_optimized ! ( expected_input, expected_no_change, physical_plan, true ) ;
2344
+ Ok ( ( ) )
2345
+ }
2208
2346
}
0 commit comments