@@ -235,7 +235,7 @@ impl PlanParentReduceRule<StructPlan> for ExpressionStructRule {
235235 . ok_or_else ( || vortex_err ! ( "Struct field '{field_name}' has no plan" ) ) ?;
236236 let lowered = step_into_struct_field ( expanded, field_name, field. dtype ( ) . clone ( ) ) ?;
237237
238- return Ok ( Some ( ExpressionPlan :: new ( lowered, field) . optimize ( ) ? ) ) ;
238+ return Ok ( Some ( ExpressionPlan :: new_ref ( lowered, field) ) ) ;
239239 }
240240
241241 let residual = partitioned. root ;
@@ -283,13 +283,13 @@ impl PlanParentReduceRule<StructPlan> for ExpressionStructRule {
283283 . children
284284 . get ( field_index) ?
285285 . ok_or_else ( || vortex_err ! ( "Struct field '{field_name}' has no plan" ) ) ?;
286- let field = ExpressionPlan :: new ( expression, field) . optimize ( ) ? ;
286+ let field = ExpressionPlan :: new_ref ( expression, field) ;
287287 pruned_fields. push ( ( field_name, field) ) ;
288288 }
289289 let rewritten: PlanRef = Arc :: new ( child. with_pruned_fields ( pruned_fields) ?) ;
290290 let residual = rewrite_partition_root ( residual, rewritten. dtype ( ) . clone ( ) , & collapsed) ?;
291291
292- Ok ( Some ( Arc :: new ( ExpressionPlan :: new ( residual, rewritten) ) ) )
292+ Ok ( Some ( ExpressionPlan :: new_ref ( residual, rewritten) ) )
293293 }
294294}
295295
@@ -401,3 +401,112 @@ fn bound_pack(names: FieldNames, children: Vec<BoundExpression>) -> VortexResult
401401 children,
402402 )
403403}
404+
405+ #[ cfg( test) ]
406+ mod tests {
407+ use std:: sync:: Arc ;
408+ use std:: sync:: atomic:: AtomicUsize ;
409+ use std:: sync:: atomic:: Ordering ;
410+
411+ use vortex_array:: dtype:: DType ;
412+ use vortex_array:: dtype:: Nullability ;
413+ use vortex_array:: dtype:: PType ;
414+ use vortex_array:: dtype:: StructFields ;
415+ use vortex_array:: expr:: get_item;
416+ use vortex_array:: expr:: root;
417+ use vortex_error:: VortexResult ;
418+ use vortex_session:: registry:: ReadContext ;
419+
420+ use super :: StructPlan ;
421+ use crate :: LayoutRef ;
422+ use crate :: layouts:: flat:: FlatLayout ;
423+ use crate :: layouts:: struct_:: StructLayout ;
424+ use crate :: plan:: ExpressionPlan ;
425+ use crate :: plan:: LazyPlanChildren ;
426+ use crate :: plan:: Plan ;
427+ use crate :: plan:: PlanRef ;
428+ use crate :: plan:: RowIdxPlan ;
429+ use crate :: segments:: SegmentId ;
430+
431+ struct CountingPlan {
432+ dtype : DType ,
433+ optimizations : Arc < AtomicUsize > ,
434+ }
435+
436+ impl CountingPlan {
437+ fn new_ref ( dtype : DType , optimizations : Arc < AtomicUsize > ) -> PlanRef {
438+ Arc :: new ( Self {
439+ dtype,
440+ optimizations,
441+ } )
442+ }
443+ }
444+
445+ impl Plan for CountingPlan {
446+ fn optimize ( & self ) -> VortexResult < PlanRef > {
447+ self . optimizations . fetch_add ( 1 , Ordering :: Relaxed ) ;
448+ Ok ( Self :: new_ref (
449+ self . dtype . clone ( ) ,
450+ Arc :: clone ( & self . optimizations ) ,
451+ ) )
452+ }
453+
454+ fn dtype ( & self ) -> & DType {
455+ & self . dtype
456+ }
457+
458+ fn row_count ( & self ) -> u64 {
459+ 1
460+ }
461+ }
462+
463+ fn flat ( dtype : DType , segment_id : u32 ) -> LayoutRef {
464+ FlatLayout :: new ( 1 , dtype, SegmentId :: from ( segment_id) , ReadContext :: new ( [ ] ) ) . into_layout ( )
465+ }
466+
467+ #[ test]
468+ fn expression_optimizes_only_referenced_struct_fields ( ) -> VortexResult < ( ) > {
469+ let field_dtype = DType :: Primitive ( PType :: I32 , Nullability :: NonNullable ) ;
470+ let struct_dtype = DType :: Struct (
471+ StructFields :: from_iter ( [ ( "a" , field_dtype. clone ( ) ) , ( "b" , field_dtype. clone ( ) ) ] ) ,
472+ Nullability :: NonNullable ,
473+ ) ;
474+ let layout = StructLayout :: new (
475+ 1 ,
476+ struct_dtype. clone ( ) ,
477+ vec ! [ flat( field_dtype. clone( ) , 0 ) , flat( field_dtype. clone( ) , 1 ) ] ,
478+ ) ;
479+ let a_optimizations = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
480+ let b_optimizations = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
481+ let children: Arc < [ Option < PlanRef > ] > = [
482+ Some ( CountingPlan :: new_ref (
483+ field_dtype. clone ( ) ,
484+ Arc :: clone ( & a_optimizations) ,
485+ ) ) ,
486+ Some ( CountingPlan :: new_ref (
487+ field_dtype,
488+ Arc :: clone ( & b_optimizations) ,
489+ ) ) ,
490+ None ,
491+ ]
492+ . into ( ) ;
493+ let child_count = children. len ( ) ;
494+ let struct_plan: PlanRef = Arc :: new ( StructPlan {
495+ layout,
496+ dtype : struct_dtype,
497+ children : LazyPlanChildren :: new ( child_count, move |index| {
498+ Ok ( children. get ( index) . cloned ( ) . flatten ( ) )
499+ } ) ,
500+ } ) ;
501+ let plan = RowIdxPlan :: new_ref ( 0 , struct_plan) ;
502+
503+ let expression = get_item ( "a" , root ( ) )
504+ . optimize_recursive ( plan. dtype ( ) ) ?
505+ . bind ( plan. dtype ( ) ) ?;
506+ ExpressionPlan :: new ( expression, plan) . optimize ( ) ?;
507+
508+ assert_eq ! ( a_optimizations. load( Ordering :: Relaxed ) , 1 ) ;
509+ assert_eq ! ( b_optimizations. load( Ordering :: Relaxed ) , 0 ) ;
510+ Ok ( ( ) )
511+ }
512+ }
0 commit comments