@@ -5,7 +5,14 @@ use std::borrow::Cow;
55use std:: fmt;
66
77use vortex_array:: EmptyMetadata ;
8+ use vortex_array:: dtype:: DType ;
9+ use vortex_array:: dtype:: FieldName ;
810use vortex_array:: expr:: BoundExpression ;
11+ use vortex_array:: expr:: traversal:: NodeExt ;
12+ use vortex_array:: expr:: traversal:: Transformed ;
13+ use vortex_array:: expr:: traversal:: TraversalOrder ;
14+ use vortex_array:: scalar_fn:: ScalarFnVTableExt ;
15+ use vortex_array:: scalar_fn:: fns:: get_item:: GetItem ;
916use vortex_error:: VortexResult ;
1017use vortex_error:: vortex_bail;
1118use vortex_session:: registry:: CachedId ;
@@ -17,6 +24,8 @@ use crate::plan::PlanParts;
1724use crate :: plan:: PlanRef ;
1825use crate :: plan:: PlanVTable ;
1926use crate :: plan:: check_child_count;
27+ use crate :: plan:: optimize;
28+ use crate :: plan:: optimizer:: reduce_parent;
2029
2130/// Applies an expression to the output of its child.
2231#[ derive( Clone , Debug ) ]
@@ -123,3 +132,94 @@ fn validate_expression_child(expression: &BoundExpression, child: &PlanRef) -> V
123132 }
124133 Ok ( ( ) )
125134}
135+
136+ impl EvalPlan {
137+ /// Optimizes this plan top-down, applying parent-reduction rules as they become applicable.
138+ ///
139+ /// `blocked_child_type` suppresses one rule re-firing on its own residual output, which would
140+ /// otherwise loop when a rewrite leaves an expression above the same child kind.
141+ pub ( crate ) fn optimize_top_down (
142+ & self ,
143+ blocked_child_type : Option < PlanId > ,
144+ ) -> VortexResult < PlanRef > {
145+ if self . expression ( ) . is_root ( ) {
146+ return optimize ( self . child_plan ( ) ?) ;
147+ }
148+
149+ let child = self . child_plan ( ) ?;
150+ let child_type = child. id ( ) ;
151+ let parent = EvalPlan :: try_new ( self . expression ( ) . clone ( ) , child. clone ( ) ) ?. into_plan ( ) ;
152+ if blocked_child_type != Some ( child_type)
153+ && let Some ( rewritten) = reduce_parent ( & parent, 0 ) ?
154+ {
155+ return Self :: optimize_rewrite ( rewritten, child_type) ;
156+ }
157+
158+ let child = optimize ( child) ?;
159+
160+ let child_type = child. id ( ) ;
161+ let parent = EvalPlan :: try_new ( self . expression ( ) . clone ( ) , child) ?. into_plan ( ) ;
162+ if blocked_child_type != Some ( child_type)
163+ && let Some ( rewritten) = reduce_parent ( & parent, 0 ) ?
164+ {
165+ return Self :: optimize_rewrite ( rewritten, child_type) ;
166+ }
167+ Ok ( parent)
168+ }
169+
170+ fn optimize_rewrite ( rewritten : PlanRef , previous_child_type : PlanId ) -> VortexResult < PlanRef > {
171+ let Some ( eval) = rewritten. as_opt :: < Eval > ( ) else {
172+ return optimize ( rewritten) ;
173+ } ;
174+ // A residual expression may remain above the same child kind after a successful rewrite.
175+ // Do not immediately apply that rule again; recursively optimize only the retained child.
176+ let child_type = eval. child_plan ( ) ?. id ( ) ;
177+ let blocked = ( child_type == previous_child_type) . then_some ( previous_child_type) ;
178+ eval. optimize_top_down ( blocked)
179+ }
180+ }
181+
182+ /// Rewrites partition accessors in `expression` to read from a partitioned root.
183+ pub ( crate ) fn rewrite_partition_root (
184+ expression : BoundExpression ,
185+ root_dtype : DType ,
186+ collapsed : & [ ( FieldName , FieldName ) ] ,
187+ ) -> VortexResult < BoundExpression > {
188+ Ok ( expression
189+ . transform_down ( |node| {
190+ if let Some ( value_name) = node
191+ . as_scalar ( )
192+ . and_then ( |scalar_fn| scalar_fn. as_opt :: < GetItem > ( ) )
193+ {
194+ let partition_access = & node. children ( ) [ 0 ] ;
195+ if let Some ( partition_name) = partition_access
196+ . as_scalar ( )
197+ . and_then ( |scalar_fn| scalar_fn. as_opt :: < GetItem > ( ) )
198+ && partition_access. children ( ) [ 0 ] . is_root ( )
199+ && collapsed. iter ( ) . any ( |( partition, value) | {
200+ partition == partition_name && value == value_name
201+ } )
202+ {
203+ return Ok ( Transformed {
204+ value : BoundExpression :: try_new (
205+ GetItem . bind ( partition_name. clone ( ) ) ,
206+ [ BoundExpression :: new_root ( root_dtype. clone ( ) ) ] ,
207+ ) ?,
208+ changed : true ,
209+ order : TraversalOrder :: Skip ,
210+ } ) ;
211+ }
212+ }
213+
214+ if node. is_root ( ) {
215+ Ok ( Transformed {
216+ value : BoundExpression :: new_root ( root_dtype. clone ( ) ) ,
217+ changed : true ,
218+ order : TraversalOrder :: Skip ,
219+ } )
220+ } else {
221+ Ok ( Transformed :: no ( node) )
222+ }
223+ } ) ?
224+ . into_inner ( ) )
225+ }
0 commit comments