@@ -33,8 +33,8 @@ use datafusion_common::{
3333use datafusion_expr:: expr:: ScalarFunction ;
3434use datafusion_expr:: simplify:: ExprSimplifyResult ;
3535use datafusion_expr:: {
36- ColumnarValue , Documentation , Expr , ReturnFieldArgs , ScalarFunctionArgs , ScalarUDF ,
37- ScalarUDFImpl , Signature , Volatility ,
36+ ColumnarValue , Documentation , Expr , ExpressionPlacement , ReturnFieldArgs ,
37+ ScalarFunctionArgs , ScalarUDF , ScalarUDFImpl , Signature , Volatility ,
3838} ;
3939use datafusion_macros:: user_doc;
4040
@@ -499,6 +499,37 @@ impl ScalarUDFImpl for GetFieldFunc {
499499 fn documentation ( & self ) -> Option < & Documentation > {
500500 self . doc ( )
501501 }
502+
503+ fn placement ( & self , args : & [ ExpressionPlacement ] ) -> ExpressionPlacement {
504+ // get_field is leaf-pushable if:
505+ // 1. The struct/map argument is a Column or PlaceAtLeafs (not Literal or PlaceAtRoot)
506+ // 2. All key arguments are literals (static field access, not dynamic per-row lookup)
507+ //
508+ // Literal base is not considered leaf-pushable because it would be constant-folded anyway.
509+ if args. is_empty ( ) {
510+ return ExpressionPlacement :: PlaceAtRoot ;
511+ }
512+
513+ // Check if the base (struct/map) argument is Column or PlaceAtLeafs
514+ if !matches ! (
515+ args[ 0 ] ,
516+ ExpressionPlacement :: Column | ExpressionPlacement :: PlaceAtLeafs
517+ ) {
518+ return ExpressionPlacement :: PlaceAtRoot ;
519+ }
520+
521+ // All key arguments (after the first) must be literals for static field access
522+ let keys_literal = args
523+ . iter ( )
524+ . skip ( 1 )
525+ . all ( |a| * a == ExpressionPlacement :: Literal ) ;
526+
527+ if keys_literal {
528+ ExpressionPlacement :: PlaceAtLeafs
529+ } else {
530+ ExpressionPlacement :: PlaceAtRoot
531+ }
532+ }
502533}
503534
504535#[ cfg( test) ]
@@ -542,4 +573,80 @@ mod tests {
542573
543574 Ok ( ( ) )
544575 }
576+
577+ #[ test]
578+ fn test_placement_with_args_literal_key ( ) {
579+ let func = GetFieldFunc :: new ( ) ;
580+
581+ // get_field(col, 'literal') -> leaf-pushable (static field access)
582+ let args = vec ! [ ExpressionPlacement :: Column , ExpressionPlacement :: Literal ] ;
583+ assert_eq ! ( func. placement( & args) , ExpressionPlacement :: PlaceAtLeafs ) ;
584+
585+ // get_field(col, 'a', 'b') -> leaf-pushable (nested static field access)
586+ let args = vec ! [
587+ ExpressionPlacement :: Column ,
588+ ExpressionPlacement :: Literal ,
589+ ExpressionPlacement :: Literal ,
590+ ] ;
591+ assert_eq ! ( func. placement( & args) , ExpressionPlacement :: PlaceAtLeafs ) ;
592+
593+ // get_field(get_field(col, 'a'), 'b') represented as PlaceAtLeafs for base
594+ let args = vec ! [
595+ ExpressionPlacement :: PlaceAtLeafs ,
596+ ExpressionPlacement :: Literal ,
597+ ] ;
598+ assert_eq ! ( func. placement( & args) , ExpressionPlacement :: PlaceAtLeafs ) ;
599+ }
600+
601+ #[ test]
602+ fn test_placement_with_args_column_key ( ) {
603+ let func = GetFieldFunc :: new ( ) ;
604+
605+ // get_field(col, other_col) -> NOT leaf-pushable (dynamic per-row lookup)
606+ let args = vec ! [ ExpressionPlacement :: Column , ExpressionPlacement :: Column ] ;
607+ assert_eq ! ( func. placement( & args) , ExpressionPlacement :: PlaceAtRoot ) ;
608+
609+ // get_field(col, 'a', other_col) -> NOT leaf-pushable (dynamic nested lookup)
610+ let args = vec ! [
611+ ExpressionPlacement :: Column ,
612+ ExpressionPlacement :: Literal ,
613+ ExpressionPlacement :: Column ,
614+ ] ;
615+ assert_eq ! ( func. placement( & args) , ExpressionPlacement :: PlaceAtRoot ) ;
616+ }
617+
618+ #[ test]
619+ fn test_placement_with_args_root ( ) {
620+ let func = GetFieldFunc :: new ( ) ;
621+
622+ // get_field(root_expr, 'literal') -> NOT leaf-pushable
623+ let args = vec ! [
624+ ExpressionPlacement :: PlaceAtRoot ,
625+ ExpressionPlacement :: Literal ,
626+ ] ;
627+ assert_eq ! ( func. placement( & args) , ExpressionPlacement :: PlaceAtRoot ) ;
628+
629+ // get_field(col, root_expr) -> NOT leaf-pushable
630+ let args = vec ! [
631+ ExpressionPlacement :: Column ,
632+ ExpressionPlacement :: PlaceAtRoot ,
633+ ] ;
634+ assert_eq ! ( func. placement( & args) , ExpressionPlacement :: PlaceAtRoot ) ;
635+ }
636+
637+ #[ test]
638+ fn test_placement_with_args_edge_cases ( ) {
639+ let func = GetFieldFunc :: new ( ) ;
640+
641+ // Empty args -> NOT leaf-pushable
642+ assert_eq ! ( func. placement( & [ ] ) , ExpressionPlacement :: PlaceAtRoot ) ;
643+
644+ // Just base, no key -> PlaceAtLeafs (not a valid call but should handle gracefully)
645+ let args = vec ! [ ExpressionPlacement :: Column ] ;
646+ assert_eq ! ( func. placement( & args) , ExpressionPlacement :: PlaceAtLeafs ) ;
647+
648+ // Literal base with literal key -> NOT leaf-pushable (would be constant-folded)
649+ let args = vec ! [ ExpressionPlacement :: Literal , ExpressionPlacement :: Literal ] ;
650+ assert_eq ! ( func. placement( & args) , ExpressionPlacement :: PlaceAtRoot ) ;
651+ }
545652}
0 commit comments