@@ -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_session:: registry:: CachedId ;
1118
@@ -16,6 +23,8 @@ use crate::plan::PlanParts;
1623use crate :: plan:: PlanRef ;
1724use crate :: plan:: PlanVTable ;
1825use crate :: plan:: check_child_count;
26+ use crate :: plan:: optimize;
27+ use crate :: plan:: optimizer:: reduce_parent;
1928
2029/// Applies an expression to the output of its child.
2130#[ derive( Clone , Debug ) ]
@@ -99,3 +108,94 @@ impl PlanVTable for Eval {
99108 }
100109 }
101110}
111+
112+ impl EvalPlan {
113+ /// Optimizes this plan top-down, applying parent-reduction rules as they become applicable.
114+ ///
115+ /// `blocked_child_type` suppresses one rule re-firing on its own residual output, which would
116+ /// otherwise loop when a rewrite leaves an expression above the same child kind.
117+ pub ( crate ) fn optimize_top_down (
118+ & self ,
119+ blocked_child_type : Option < PlanId > ,
120+ ) -> VortexResult < PlanRef > {
121+ if self . expression ( ) . is_root ( ) {
122+ return optimize ( self . child_plan ( ) ?) ;
123+ }
124+
125+ let child = self . child_plan ( ) ?;
126+ let child_type = child. id ( ) ;
127+ let parent = EvalPlan :: new ( self . expression ( ) . clone ( ) , child. clone ( ) ) . into_plan ( ) ;
128+ if blocked_child_type != Some ( child_type)
129+ && let Some ( rewritten) = reduce_parent ( & parent, 0 ) ?
130+ {
131+ return Self :: optimize_rewrite ( rewritten, child_type) ;
132+ }
133+
134+ let child = optimize ( child) ?;
135+
136+ let child_type = child. id ( ) ;
137+ let parent = EvalPlan :: new ( self . expression ( ) . clone ( ) , child) . into_plan ( ) ;
138+ if blocked_child_type != Some ( child_type)
139+ && let Some ( rewritten) = reduce_parent ( & parent, 0 ) ?
140+ {
141+ return Self :: optimize_rewrite ( rewritten, child_type) ;
142+ }
143+ Ok ( parent)
144+ }
145+
146+ fn optimize_rewrite ( rewritten : PlanRef , previous_child_type : PlanId ) -> VortexResult < PlanRef > {
147+ let Some ( eval) = rewritten. as_opt :: < Eval > ( ) else {
148+ return optimize ( rewritten) ;
149+ } ;
150+ // A residual expression may remain above the same child kind after a successful rewrite.
151+ // Do not immediately apply that rule again; recursively optimize only the retained child.
152+ let child_type = eval. child_plan ( ) ?. id ( ) ;
153+ let blocked = ( child_type == previous_child_type) . then_some ( previous_child_type) ;
154+ eval. optimize_top_down ( blocked)
155+ }
156+ }
157+
158+ /// Rewrites partition accessors in `expression` to read from a partitioned root.
159+ pub ( crate ) fn rewrite_partition_root (
160+ expression : BoundExpression ,
161+ root_dtype : DType ,
162+ collapsed : & [ ( FieldName , FieldName ) ] ,
163+ ) -> VortexResult < BoundExpression > {
164+ Ok ( expression
165+ . transform_down ( |node| {
166+ if let Some ( value_name) = node
167+ . as_scalar ( )
168+ . and_then ( |scalar_fn| scalar_fn. as_opt :: < GetItem > ( ) )
169+ {
170+ let partition_access = & node. children ( ) [ 0 ] ;
171+ if let Some ( partition_name) = partition_access
172+ . as_scalar ( )
173+ . and_then ( |scalar_fn| scalar_fn. as_opt :: < GetItem > ( ) )
174+ && partition_access. children ( ) [ 0 ] . is_root ( )
175+ && collapsed. iter ( ) . any ( |( partition, value) | {
176+ partition == partition_name && value == value_name
177+ } )
178+ {
179+ return Ok ( Transformed {
180+ value : BoundExpression :: try_new (
181+ GetItem . bind ( partition_name. clone ( ) ) ,
182+ [ BoundExpression :: new_root ( root_dtype. clone ( ) ) ] ,
183+ ) ?,
184+ changed : true ,
185+ order : TraversalOrder :: Skip ,
186+ } ) ;
187+ }
188+ }
189+
190+ if node. is_root ( ) {
191+ Ok ( Transformed {
192+ value : BoundExpression :: new_root ( root_dtype. clone ( ) ) ,
193+ changed : true ,
194+ order : TraversalOrder :: Skip ,
195+ } )
196+ } else {
197+ Ok ( Transformed :: no ( node) )
198+ }
199+ } ) ?
200+ . into_inner ( ) )
201+ }
0 commit comments