@@ -584,17 +584,29 @@ impl LogicalPlan {
584584 . map_elements ( f) ?
585585 . update_data ( |expr| LogicalPlan :: Sort ( Sort { expr, input, fetch } ) ) ,
586586 LogicalPlan :: Extension ( Extension { node } ) => {
587- // would be nice to avoid this copy -- maybe can
588- // update extension to just observer Exprs
589- let exprs = node. expressions ( ) . map_elements ( f) ?;
590- let plan = LogicalPlan :: Extension ( Extension {
591- node : UserDefinedLogicalNode :: with_exprs_and_inputs (
592- node. as_ref ( ) ,
593- exprs. data ,
594- node. inputs ( ) . into_iter ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) ,
595- ) ?,
596- } ) ;
597- Transformed :: new ( plan, exprs. transformed , exprs. tnr )
587+ let raw_exprs = node. expressions ( ) ;
588+ if raw_exprs. is_empty ( ) {
589+ // No expressions to transform — skip expensive clone of
590+ // all inputs and reconstruction via with_exprs_and_inputs.
591+ Transformed :: no ( LogicalPlan :: Extension ( Extension { node } ) )
592+ } else {
593+ // TODO: a more general optimization would be to change
594+ // `UserDefinedLogicalNode::expressions()` to return
595+ // references (`&[Expr]`) instead of cloned `Vec<Expr>`,
596+ // and only clone + rebuild when the transform actually
597+ // modifies an expression. This would avoid the clone +
598+ // `with_exprs_and_inputs` rebuild even for non-empty
599+ // expression lists when the transform is a no-op.
600+ let exprs = raw_exprs. map_elements ( f) ?;
601+ let plan = LogicalPlan :: Extension ( Extension {
602+ node : UserDefinedLogicalNode :: with_exprs_and_inputs (
603+ node. as_ref ( ) ,
604+ exprs. data ,
605+ node. inputs ( ) . into_iter ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) ,
606+ ) ?,
607+ } ) ;
608+ Transformed :: new ( plan, exprs. transformed , exprs. tnr )
609+ }
598610 }
599611 LogicalPlan :: TableScan ( TableScan {
600612 table_name,
0 commit comments