@@ -1200,7 +1200,7 @@ pub(crate) mod tests {
12001200 use super :: * ;
12011201 use crate :: equivalence:: { EquivalenceProperties , convert_to_orderings} ;
12021202 use crate :: expressions:: { BinaryExpr , col} ;
1203- use crate :: utils:: tests:: TestScalarUDF ;
1203+ use crate :: utils:: tests:: { ScopedExprMock , TestScalarUDF } ;
12041204 use crate :: { PhysicalExprRef , ScalarFunctionExpr } ;
12051205
12061206 use arrow:: compute:: SortOptions ;
@@ -3038,4 +3038,162 @@ pub(crate) mod tests {
30383038
30393039 Ok ( ( ) )
30403040 }
3041+
3042+ #[ test]
3043+ fn test_update_expr_does_not_modify_out_of_scope_children ( ) -> Result < ( ) > {
3044+ // Outer schema: [a, b, c]
3045+ // Expression: ScopedExprMock(in_scope=a@0, out_of_scope=x@0)
3046+ // Projection: [c@2 as c_new, a@0 as a_new, b@1 as b_new]
3047+ // After unproject: in_scope should become c@2, out_of_scope should stay x@0
3048+ let in_scope_child: Arc < dyn PhysicalExpr > = Arc :: new ( Column :: new ( "a_new" , 1 ) ) ;
3049+ let out_of_scope_child: Arc < dyn PhysicalExpr > =
3050+ Arc :: new ( Column :: new ( "x" , 0 ) ) ;
3051+
3052+ let expr: Arc < dyn PhysicalExpr > = Arc :: new ( ScopedExprMock {
3053+ in_scope_child,
3054+ out_of_scope_child,
3055+ } ) ;
3056+
3057+ let projected_exprs = vec ! [
3058+ ProjectionExpr {
3059+ expr: Arc :: new( Column :: new( "c" , 2 ) ) ,
3060+ alias: "c_new" . to_string( ) ,
3061+ } ,
3062+ ProjectionExpr {
3063+ expr: Arc :: new( Column :: new( "a" , 0 ) ) ,
3064+ alias: "a_new" . to_string( ) ,
3065+ } ,
3066+ ProjectionExpr {
3067+ expr: Arc :: new( Column :: new( "b" , 1 ) ) ,
3068+ alias: "b_new" . to_string( ) ,
3069+ } ,
3070+ ] ;
3071+
3072+ let result =
3073+ update_expr ( & expr, & projected_exprs, true ) ?. expect ( "Should be valid" ) ;
3074+
3075+ let mock = result
3076+ . as_any ( )
3077+ . downcast_ref :: < ScopedExprMock > ( )
3078+ . expect ( "Should still be ScopedExprMock" ) ;
3079+
3080+ // The in-scope child "a_new@1" should be unprojected to "a@0"
3081+ let in_scope_col = mock
3082+ . in_scope_child
3083+ . as_any ( )
3084+ . downcast_ref :: < Column > ( )
3085+ . expect ( "in_scope_child should be Column" ) ;
3086+ assert_eq ! ( in_scope_col. name( ) , "a" ) ;
3087+ assert_eq ! ( in_scope_col. index( ) , 0 ) ;
3088+
3089+ // The out-of-scope child "x@0" should be UNCHANGED
3090+ let out_of_scope_col = mock
3091+ . out_of_scope_child
3092+ . as_any ( )
3093+ . downcast_ref :: < Column > ( )
3094+ . expect ( "out_of_scope_child should be Column" ) ;
3095+ assert_eq ! ( out_of_scope_col. name( ) , "x" ) ;
3096+ assert_eq ! ( out_of_scope_col. index( ) , 0 ) ;
3097+
3098+ Ok ( ( ) )
3099+ }
3100+
3101+ #[ test]
3102+ fn test_project_ordering_does_not_modify_out_of_scope_children ( ) {
3103+ // Schema: [a: Int32, b: Int32]
3104+ let schema = Arc :: new ( Schema :: new ( vec ! [
3105+ Field :: new( "a" , DataType :: Int32 , false ) ,
3106+ Field :: new( "b" , DataType :: Int32 , false ) ,
3107+ ] ) ) ;
3108+
3109+ let in_scope_child: Arc < dyn PhysicalExpr > = Arc :: new ( Column :: new ( "a" , 0 ) ) ;
3110+ let out_of_scope_child: Arc < dyn PhysicalExpr > =
3111+ Arc :: new ( Column :: new ( "x" , 0 ) ) ;
3112+
3113+ let scoped_expr: Arc < dyn PhysicalExpr > = Arc :: new ( ScopedExprMock {
3114+ in_scope_child,
3115+ out_of_scope_child,
3116+ } ) ;
3117+
3118+ let ordering =
3119+ LexOrdering :: new ( vec ! [ PhysicalSortExpr :: new( scoped_expr, SortOptions :: new( false , false ) ) ] )
3120+ . unwrap ( ) ;
3121+
3122+ let result = project_ordering ( & ordering, & schema) . expect ( "Should project" ) ;
3123+
3124+ let projected_expr = & result. first ( ) . expr ;
3125+ let mock = projected_expr
3126+ . as_any ( )
3127+ . downcast_ref :: < ScopedExprMock > ( )
3128+ . expect ( "Should still be ScopedExprMock" ) ;
3129+
3130+ // The in-scope column "a" should be reindexed (stays at 0 in this case)
3131+ let in_scope_col = mock
3132+ . in_scope_child
3133+ . as_any ( )
3134+ . downcast_ref :: < Column > ( )
3135+ . expect ( "in_scope_child should be Column" ) ;
3136+ assert_eq ! ( in_scope_col. name( ) , "a" ) ;
3137+ assert_eq ! ( in_scope_col. index( ) , 0 ) ;
3138+
3139+ // The out-of-scope child "x@0" should be UNCHANGED
3140+ let out_of_scope_col = mock
3141+ . out_of_scope_child
3142+ . as_any ( )
3143+ . downcast_ref :: < Column > ( )
3144+ . expect ( "out_of_scope_child should be Column" ) ;
3145+ assert_eq ! ( out_of_scope_col. name( ) , "x" ) ;
3146+ assert_eq ! ( out_of_scope_col. index( ) , 0 ) ;
3147+ }
3148+
3149+ #[ test]
3150+ fn test_projection_mapping_does_not_modify_out_of_scope_children ( ) -> Result < ( ) > {
3151+ // Input schema: [a: Int32, b: Int32]
3152+ let input_schema = Arc :: new ( Schema :: new ( vec ! [
3153+ Field :: new( "a" , DataType :: Int32 , false ) ,
3154+ Field :: new( "b" , DataType :: Int32 , false ) ,
3155+ ] ) ) ;
3156+
3157+ let in_scope_child: Arc < dyn PhysicalExpr > = Arc :: new ( Column :: new ( "a" , 0 ) ) ;
3158+ let out_of_scope_child: Arc < dyn PhysicalExpr > =
3159+ Arc :: new ( Column :: new ( "x" , 0 ) ) ;
3160+
3161+ let scoped_expr: Arc < dyn PhysicalExpr > = Arc :: new ( ScopedExprMock {
3162+ in_scope_child,
3163+ out_of_scope_child,
3164+ } ) ;
3165+
3166+ // Project: [ScopedExprMock as "result"]
3167+ let projection_exprs = vec ! [ ( scoped_expr, "result" . to_string( ) ) ] ;
3168+
3169+ let mapping = ProjectionMapping :: try_new ( projection_exprs, & input_schema) ?;
3170+
3171+ // The source expression in the mapping should have its in-scope column
3172+ // validated but the out-of-scope column left untouched
3173+ let ( source_expr, _targets) = mapping. iter ( ) . next ( ) . unwrap ( ) ;
3174+ let mock = source_expr
3175+ . as_any ( )
3176+ . downcast_ref :: < ScopedExprMock > ( )
3177+ . expect ( "Should still be ScopedExprMock" ) ;
3178+
3179+ // In-scope child: "a@0" should still be "a@0" (name matches schema)
3180+ let in_scope_col = mock
3181+ . in_scope_child
3182+ . as_any ( )
3183+ . downcast_ref :: < Column > ( )
3184+ . expect ( "in_scope_child should be Column" ) ;
3185+ assert_eq ! ( in_scope_col. name( ) , "a" ) ;
3186+ assert_eq ! ( in_scope_col. index( ) , 0 ) ;
3187+
3188+ // Out-of-scope child: "x@0" should be UNCHANGED
3189+ let out_of_scope_col = mock
3190+ . out_of_scope_child
3191+ . as_any ( )
3192+ . downcast_ref :: < Column > ( )
3193+ . expect ( "out_of_scope_child should be Column" ) ;
3194+ assert_eq ! ( out_of_scope_col. name( ) , "x" ) ;
3195+ assert_eq ! ( out_of_scope_col. index( ) , 0 ) ;
3196+
3197+ Ok ( ( ) )
3198+ }
30413199}
0 commit comments