@@ -23,19 +23,22 @@ use arrow::datatypes::{self as arrow_schema, SchemaRef};
2323use async_trait:: async_trait;
2424use datafusion:: catalog:: { Session , TableProvider } ;
2525use datafusion:: common:: Result ;
26- use datafusion:: common:: tree_node:: TreeNodeRecursion ;
26+ use datafusion:: common:: config:: ConfigOptions ;
27+ use datafusion:: common:: tree_node:: { Transformed , TreeNode , TreeNodeRecursion } ;
2728use datafusion:: execution:: { SendableRecordBatchStream , TaskContext } ;
2829use datafusion:: logical_expr:: { Expr , Operator , TableType } ;
2930use datafusion:: physical_expr:: expressions:: { binary, col, lit, negative} ;
3031use datafusion:: physical_expr:: projection:: ProjectionExprs ;
3132use datafusion:: physical_expr:: { PhysicalExpr , PhysicalSortExpr } ;
33+ use datafusion:: physical_optimizer:: PhysicalOptimizerRule ;
3234use datafusion:: physical_plan:: filter:: FilterExec ;
3335use datafusion:: physical_plan:: limit:: LocalLimitExec ;
3436use datafusion:: physical_plan:: projection:: ProjectionExec ;
37+ use datafusion:: physical_plan:: sorts:: sort:: SortExec ;
3538use datafusion:: physical_plan:: test:: TestMemoryExec ;
3639use datafusion:: physical_plan:: {
37- ChildrenPropertiesMode , DisplayAs , DisplayFormatType , ExecutionPlan , PlanProperties ,
38- ReplaceChildrenOptions ,
40+ ChildrenPropertiesMode , DisplayAs , DisplayFormatType , ExecutionPlan ,
41+ ExecutionPlanProperties , PlanProperties , ReplaceChildrenOptions ,
3942} ;
4043use datafusion:: prelude:: SessionContext ;
4144
@@ -79,18 +82,65 @@ pub(super) fn register_custom_sort_pushdown(ctx: &SessionContext) -> Result<()>
7982
8083 let batch = record_batch ! ( ( "a" , Int32 , [ 1 , 2 , 3 ] ) , ( "b" , Int32 , [ 30 , 20 , 10 ] ) ) ?;
8184 let schema = batch. schema ( ) ;
82- let ordering = [ PhysicalSortExpr :: new_default ( col ( "a" , & schema) ?) ] . into ( ) ;
85+ let ordering =
86+ [ PhysicalSortExpr :: new_default ( col ( "a" , & schema) ?) . nulls_last ( ) ] . into ( ) ;
8387 let input = Arc :: new (
8488 TestMemoryExec :: try_new ( & [ vec ! [ batch] ] , Arc :: clone ( & schema) , None ) ?
8589 . try_with_sort_information ( vec ! [ ordering] ) ?,
8690 ) ;
87- ctx. register_table (
88- "custom_limited_input" ,
89- Arc :: new ( CustomExec ( Arc :: new ( LocalLimitExec :: new ( input, 2 ) ) ) ) ,
90- ) ?;
91+ ctx. register_table ( "custom_ordered_input" , Arc :: new ( CustomExec ( input) ) ) ?;
9192 Ok ( ( ) )
9293}
9394
95+ /// Exposes the SQL fixture's ordered selection as a custom limit before sort pushdown.
96+ #[ derive( Debug ) ]
97+ pub ( super ) struct CustomLimitRule ;
98+
99+ impl PhysicalOptimizerRule for CustomLimitRule {
100+ fn optimize (
101+ & self ,
102+ plan : Arc < dyn ExecutionPlan > ,
103+ _: & ConfigOptions ,
104+ ) -> Result < Arc < dyn ExecutionPlan > > {
105+ plan. transform_up ( |plan| {
106+ let Some ( sort) = plan. downcast_ref :: < SortExec > ( ) else {
107+ return Ok ( Transformed :: no ( plan) ) ;
108+ } ;
109+ let Some ( fetch) = sort. fetch ( ) else {
110+ return Ok ( Transformed :: no ( plan) ) ;
111+ } ;
112+ let input = sort. input ( ) ;
113+ let source = input
114+ . downcast_ref :: < ProjectionExec > ( )
115+ . map_or ( input, |projection| projection. input ( ) ) ;
116+ let Some ( source) = source. downcast_ref :: < CustomExec > ( ) else {
117+ return Ok ( Transformed :: no ( plan) ) ;
118+ } ;
119+ // Only the dedicated ordered source is a wrapped memory scan.
120+ if !source. 0 . is :: < TestMemoryExec > ( )
121+ || input. output_partitioning ( ) . partition_count ( ) != 1
122+ || !input
123+ . equivalence_properties ( )
124+ . ordering_satisfy ( sort. expr ( ) . clone ( ) ) ?
125+ {
126+ return Ok ( Transformed :: no ( plan) ) ;
127+ }
128+ let limit = LocalLimitExec :: new ( Arc :: clone ( input) , fetch) ;
129+ let plan: Arc < dyn ExecutionPlan > = Arc :: new ( CustomExec ( Arc :: new ( limit) ) ) ;
130+ Ok ( Transformed :: yes ( plan) )
131+ } )
132+ . map ( |transformed| transformed. data )
133+ }
134+
135+ fn name ( & self ) -> & str {
136+ "CustomLimitRule"
137+ }
138+
139+ fn schema_check ( & self ) -> bool {
140+ true
141+ }
142+ }
143+
94144/// Exposes a native plan's children and properties under a distinct plan type,
95145/// exercising the generic custom-plan sort pushdown path.
96146#[ derive( Debug , Clone ) ]
0 commit comments