Repository: apache/datafusion
Pull request #15123: chore: remove deprecated variants of UDF's invoke (invoke, invoke_no_args, invoke_batch)
Author: login: Blizzara
Labels: name: sql
description: SQL Planner
name: logical-expr
description: Logical plan and expressions
name: optimizer
description: Optimizer rules
name: core
description: Core DataFusion crate
name: proto
description: Related to proto crate
Created: 2025-03-10T13:06:40Z | Merged: 2025-03-17T13:42:32Z

Leaving only invoke_with_args

## Which issue does this PR close?

Followup form https://github.com/apache/datafusion/pull/15049

- Closes https://github.com/apache/datafusion/issues/14652

## Rationale for this change

The UDF trait has 4 different ways of implementing `invoke`, with 3 of them deprecated. This cleans the trait up.

## What changes are included in this PR?

- `invoke`, `invoke_no_args`, and `invoke_batch` are removed
- `invoke_with_args` has its default implementation removed, forcing users to implement it
- ScalarUDFImpl::fun is also removed, since that's been deprecated for a while and uses `invoke`
- `max` UDF is fixed to implement `invoke_with_args` instead
- a test dummy UDF is fixed to implement `invoke_with_args` instead

## Are these changes tested?

Existing tests

## Are there any user-facing changes?

yes, BREAKING CHANGE: remove deprecated variants of UDF's invoke (invoke, invoke_no_args, invoke_batch)

<!--
If there are any breaking changes to public APIs, please add the `api change` label.
-->



Diff:
diff --git a/datafusion/core/tests/physical_optimizer/projection_pushdown.rs b/datafusion/core/tests/physical_optimizer/projection_pushdown.rs
index b0b5f731063f5..abe058df99d04 100644
--- a/datafusion/core/tests/physical_optimizer/projection_pushdown.rs
+++ b/datafusion/core/tests/physical_optimizer/projection_pushdown.rs
@@ -29,7 +29,9 @@ use datafusion_common::Result;
 use datafusion_common::{JoinSide, JoinType, ScalarValue};
 use datafusion_execution::object_store::ObjectStoreUrl;
 use datafusion_execution::{SendableRecordBatchStream, TaskContext};
-use datafusion_expr::{Operator, ScalarUDF, ScalarUDFImpl, Signature, Volatility};
+use datafusion_expr::{
+    Operator, ScalarFunctionArgs, ScalarUDF, ScalarUDFImpl, Signature, Volatility,
+};
 use datafusion_physical_expr::expressions::{
     binary, cast, col, BinaryExpr, CaseExpr, CastExpr, Column, Literal, NegativeExpr,
 };
@@ -57,6 +59,7 @@ use datafusion_physical_plan::streaming::StreamingTableExec;
 use datafusion_physical_plan::union::UnionExec;
 use datafusion_physical_plan::{get_plan_string, ExecutionPlan};
 
+use datafusion_expr_common::columnar_value::ColumnarValue;
 use itertools::Itertools;
 
 /// Mocked UDF
@@ -89,6 +92,10 @@ impl ScalarUDFImpl for DummyUDF {
     fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
         Ok(DataType::Int32)
     }
+
+    fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
+        panic!("dummy - not implemented")
+    }
 }
 
 #[test]
diff --git a/datafusion/core/tests/user_defined/user_defined_scalar_functions.rs b/datafusion/core/tests/user_defined/user_defined_scalar_functions.rs
index 87694d83bb99d..68684a81c6569 100644
--- a/datafusion/core/tests/user_defined/user_defined_scalar_functions.rs
+++ b/datafusion/core/tests/user_defined/user_defined_scalar_functions.rs
@@ -679,6 +679,10 @@ impl ScalarUDFImpl for CastToI64UDF {
         Ok(DataType::Int64)
     }
 
+    fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
+        panic!("dummy - not implemented")
+    }
+
     // Demonstrate simplifying a UDF
     fn simplify(
         &self,
@@ -946,6 +950,10 @@ impl ScalarUDFImpl for ScalarFunctionWrapper {
         Ok(self.return_type.clone())
     }
 
+    fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
+        panic!("dummy - not implemented")
+    }
+
     fn simplify(
         &self,
         args: Vec<Expr>,
diff --git a/datafusion/expr/src/udf.rs b/datafusion/expr/src/udf.rs
index 86bc5852b830f..9b2400774a3d6 100644
--- a/datafusion/expr/src/udf.rs
+++ b/datafusion/expr/src/udf.rs
@@ -20,9 +20,7 @@
 use crate::expr::schema_name_from_exprs_comma_separated_without_space;
 use crate::simplify::{ExprSimplifyResult, SimplifyInfo};
 use crate::sort_properties::{ExprProperties, SortProperties};
-use crate::{
-    ColumnarValue, Documentation, Expr, ScalarFunctionImplementation, Signature,
-};
+use crate::{ColumnarValue, Documentation, Expr, Signature};
 use arrow::datatypes::DataType;
 use datafusion_common::{not_impl_err, ExprSchema, Result, ScalarValue};
 use datafusion_expr_common::interval_arithmetic::Interval;
@@ -198,27 +196,11 @@ impl ScalarUDF {
         self.inner.simplify(args, info)
     }
 
-    #[deprecated(since = "42.1.0", note = "Use `invoke_with_args` instead")]
-    pub fn invoke(&self, args: &[ColumnarValue]) -> Result<ColumnarValue> {
-        #[allow(deprecated)]
-        self.inner.invoke(args)
-    }
-
     #[allow(deprecated)]
     pub fn is_nullable(&self, args: &[Expr], schema: &dyn ExprSchema) -> bool {
         self.inner.is_nullable(args, schema)
     }
 
-    #[deprecated(since = "46.0.0", note = "Use `invoke_with_args` instead")]
-    pub fn invoke_batch(
-        &self,
-        args: &[ColumnarValue],
-        number_rows: usize,
-    ) -> Result<ColumnarValue> {
-        #[allow(deprecated)]
-        self.inner.invoke_batch(args, number_rows)
-    }
-
     /// Invoke the function on `args`, returning the appropriate result.
     ///
     /// See [`ScalarUDFImpl::invoke_with_args`] for details.
@@ -226,25 +208,6 @@ impl ScalarUDF {
         self.inner.invoke_with_args(args)
     }
 
-    /// Invoke the function without `args` but number of rows, returning the appropriate result.
-    ///
-    /// Note: This method is deprecated and will be removed in future releases.
-    /// User defined functions should implement [`Self::invoke_with_args`] instead.
-    #[deprecated(since = "42.1.0", note = "Use `invoke_with_args` instead")]
-    pub fn invoke_no_args(&self, number_rows: usize) -> Result<ColumnarValue> {
-        #[allow(deprecated)]
-        self.inner.invoke_no_args(number_rows)
-    }
-
-    /// Returns a `ScalarFunctionImplementation` that can invoke the function
-    /// during execution
-    #[deprecated(since = "42.0.0", note = "Use `invoke_with_args` instead")]
-    pub fn fun(&self) -> ScalarFunctionImplementation {
-        let captured = Arc::clone(&self.inner);
-        #[allow(deprecated)]
-        Arc::new(move |args| captured.invoke(args))
-    }
-
     /// Get the circuits of inner implementation
     pub fn short_circuits(&self) -> bool {
         self.inner.short_circuits()
@@ -568,47 +531,6 @@ pub trait ScalarUDFImpl: Debug + Send + Sync {
         true
     }
 
-    /// Invoke the function on `args`, returning the appropriate result
-    ///
-    /// Note: This method is deprecated and will be removed in future releases.
-    /// User defined functions should implement [`Self::invoke_with_args`] instead.
-    #[deprecated(since = "42.1.0", note = "Use `invoke_with_args` instead")]
-    fn invoke(&self, _args: &[ColumnarValue]) -> Result<ColumnarValue> {
-        not_impl_err!(
-            "Function {} does not implement invoke but called",
-            self.name()
-        )
-    }
-
-    /// Invoke the function with `args` and the number of rows,
-    /// returning the appropriate result.
-    ///
-    /// Note: See notes on  [`Self::invoke_with_args`]
-    ///
-    /// Note: This method is deprecated and will be removed in future releases.
-    /// User defined functions should implement [`Self::invoke_with_args`] instead.
-    ///
-    /// See <https://github.com/apache/datafusion/issues/13515> for more details.
-    #[deprecated(since = "46.0.0", note = "Use `invoke_with_args` instead")]
-    fn invoke_batch(
-        &self,
-        args: &[ColumnarValue],
-        number_rows: usize,
-    ) -> Result<ColumnarValue> {
-        match args.is_empty() {
-            true =>
-            {
-                #[allow(deprecated)]
-                self.invoke_no_args(number_rows)
-            }
-            false =>
-            {
-                #[allow(deprecated)]
-                self.invoke(args)
-            }
-        }
-    }
-
     /// Invoke the function returning the appropriate result.
     ///
     /// # Performance
@@ -619,23 +541,7 @@ pub trait ScalarUDFImpl: Debug + Send + Sync {
     ///
     /// [`ColumnarValue::values_to_arrays`] can be used to convert the arguments
     /// to arrays, which will likely be simpler code, but be slower.
-    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
-        #[allow(deprecated)]
-        self.invoke_batch(&args.args, args.number_rows)
-    }
-
-    /// Invoke the function without `args`, instead the number of rows are provided,
-    /// returning the appropriate result.
-    ///
-    /// Note: This method is deprecated and will be removed in future releases.
-    /// User defined functions should implement [`Self::invoke_with_args`] instead.
-    #[deprecated(since = "42.1.0", note = "Use `invoke_with_args` instead")]
-    fn invoke_no_args(&self, _number_rows: usize) -> Result<ColumnarValue> {
-        not_impl_err!(
-            "Function {} does not implement invoke_no_args but called",
-            self.name()
-        )
-    }
+    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue>;
 
     /// Returns any aliases (alternate names) for this function.
     ///
diff --git a/datafusion/functions-nested/src/max.rs b/datafusion/functions-nested/src/max.rs
index 22bd14740b5eb..32957edc62b5c 100644
--- a/datafusion/functions-nested/src/max.rs
+++ b/datafusion/functions-nested/src/max.rs
@@ -24,7 +24,9 @@ use datafusion_common::cast::as_list_array;
 use datafusion_common::utils::take_function_args;
 use datafusion_common::{exec_err, ScalarValue};
 use datafusion_doc::Documentation;
-use datafusion_expr::{ColumnarValue, ScalarUDFImpl, Signature, Volatility};
+use datafusion_expr::{
+    ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
+};
 use datafusion_functions_aggregate::min_max;
 use datafusion_macros::user_doc;
 use itertools::Itertools;
@@ -96,12 +98,11 @@ impl ScalarUDFImpl for ArrayMax {
         }
     }
 
-    fn invoke_batch(
+    fn invoke_with_args(
         &self,
-        args: &[ColumnarValue],
-        _number_rows: usize,
+        args: ScalarFunctionArgs,
     ) -> datafusion_common::Result<ColumnarValue> {
-        make_scalar_function(array_max_inner)(args)
+        make_scalar_function(array_max_inner)(&args.args)
     }
 
     fn aliases(&self) -> &[String] {
diff --git a/datafusion/optimizer/src/common_subexpr_eliminate.rs b/datafusion/optimizer/src/common_subexpr_eliminate.rs
index 5dc1a7e5ac5b3..69b5fbb9f8c0f 100644
--- a/datafusion/optimizer/src/common_subexpr_eliminate.rs
+++ b/datafusion/optimizer/src/common_subexpr_eliminate.rs
@@ -797,8 +797,8 @@ mod test {
     use datafusion_expr::logical_plan::{table_scan, JoinType};
     use datafusion_expr::{
         grouping_set, is_null, not, AccumulatorFactoryFunction, AggregateUDF,
-        ColumnarValue, ScalarUDF, ScalarUDFImpl, Signature, SimpleAggregateUDF,
-        Volatility,
+        ColumnarValue, ScalarFunctionArgs, ScalarUDF, ScalarUDFImpl, Signature,
+        SimpleAggregateUDF, Volatility,
     };
     use datafusion_expr::{lit, logical_plan::builder::LogicalPlanBuilder};
 
@@ -1598,7 +1598,7 @@ mod test {
             Ok(DataType::Int32)
         }
 
-        fn invoke(&self, _: &[ColumnarValue]) -> Result<ColumnarValue> {
+        fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
             panic!("not implemented")
         }
     }
@@ -1705,5 +1705,9 @@ mod test {
         fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
             Ok(DataType::Float64)
         }
+
+        fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
+            panic!("dummy - not implemented")
+        }
     }
 }
diff --git a/datafusion/optimizer/src/simplify_expressions/expr_simplifier.rs b/datafusion/optimizer/src/simplify_expressions/expr_simplifier.rs
index d5a1b84e6aff4..ce10c7e5c631c 100644
--- a/datafusion/optimizer/src/simplify_expressions/expr_simplifier.rs
+++ b/datafusion/optimizer/src/simplify_expressions/expr_simplifier.rs
@@ -4307,6 +4307,10 @@ mod tests {
         fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
             Ok(DataType::Int16)
         }
+
+        fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
+            panic!("dummy - not implemented")
+        }
     }
 
     #[test]
diff --git a/datafusion/proto/tests/cases/mod.rs b/datafusion/proto/tests/cases/mod.rs
index 25efa26902686..92d961fc75562 100644
--- a/datafusion/proto/tests/cases/mod.rs
+++ b/datafusion/proto/tests/cases/mod.rs
@@ -16,17 +16,17 @@
 // under the License.
 
 use arrow::datatypes::{DataType, Field};
-use std::any::Any;
-use std::fmt::Debug;
-
+use datafusion::logical_expr::ColumnarValue;
 use datafusion_common::plan_err;
 use datafusion_expr::function::AccumulatorArgs;
 use datafusion_expr::{
-    Accumulator, AggregateUDFImpl, PartitionEvaluator, ScalarUDFImpl, Signature,
-    Volatility, WindowUDFImpl,
+    Accumulator, AggregateUDFImpl, PartitionEvaluator, ScalarFunctionArgs, ScalarUDFImpl,
+    Signature, Volatility, WindowUDFImpl,
 };
 use datafusion_functions_window_common::field::WindowUDFFieldArgs;
 use datafusion_functions_window_common::partition::PartitionEvaluatorArgs;
+use std::any::Any;
+use std::fmt::Debug;
 
 mod roundtrip_logical_plan;
 mod roundtrip_physical_plan;
@@ -69,6 +69,14 @@ impl ScalarUDFImpl for MyRegexUdf {
             plan_err!("regex_udf only accepts Utf8 arguments")
         }
     }
+
+    fn invoke_with_args(
+        &self,
+        _args: ScalarFunctionArgs,
+    ) -> datafusion_common::Result<ColumnarValue> {
+        panic!("dummy - not implemented")
+    }
+
     fn aliases(&self) -> &[String] {
         &self.aliases
     }
diff --git a/datafusion/sql/src/unparser/expr.rs b/datafusion/sql/src/unparser/expr.rs
index 45c3e1c03d238..7905207fafb8e 100644
--- a/datafusion/sql/src/unparser/expr.rs
+++ b/datafusion/sql/src/unparser/expr.rs
@@ -1661,8 +1661,8 @@ mod tests {
     use datafusion_expr::{
         case, cast, col, cube, exists, grouping_set, interval_datetime_lit,
         interval_year_month_lit, lit, not, not_exists, out_ref_col, placeholder, rollup,
-        table_scan, try_cast, when, ScalarUDF, ScalarUDFImpl, Signature, Volatility,
-        WindowFrame, WindowFunctionDefinition,
+        table_scan, try_cast, when, ColumnarValue, ScalarFunctionArgs, ScalarUDF,
+        ScalarUDFImpl, Signature, Volatility, WindowFrame, WindowFunctionDefinition,
     };
     use datafusion_expr::{interval_month_day_nano_lit, ExprFunctionExt};
     use datafusion_functions::expr_fn::{get_field, named_struct};
@@ -1711,6 +1711,10 @@ mod tests {
         fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
             Ok(DataType::Int32)
         }
+
+        fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
+            panic!("dummy - not implemented")
+        }
     }
     // See sql::tests for E2E tests.
 
diff --git a/datafusion/sql/tests/sql_integration.rs b/datafusion/sql/tests/sql_integration.rs
index 023ea88cb55f3..5717f9943e59f 100644
--- a/datafusion/sql/tests/sql_integration.rs
+++ b/datafusion/sql/tests/sql_integration.rs
@@ -30,8 +30,8 @@ use datafusion_expr::{
     col,
     logical_plan::{LogicalPlan, Prepare},
     test::function_stub::sum_udaf,
-    CreateIndex, DdlStatement, ScalarUDF, ScalarUDFImpl, Signature, Statement,
-    Volatility,
+    ColumnarValue, CreateIndex, DdlStatement, ScalarFunctionArgs, ScalarUDF,
+    ScalarUDFImpl, Signature, Statement, Volatility,
 };
 use datafusion_functions::{string, unicode};
 use datafusion_sql::{
@@ -2477,6 +2477,10 @@ impl ScalarUDFImpl for DummyUDF {
     fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
         Ok(self.return_type.clone())
     }
+
+    fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
+        panic!("dummy - not implemented")
+    }
 }
 
 /// Create logical plan, write with formatter, compare to expected output


Reviews:
user: login: Weijun-H
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T13:18:32Z
user: login: Weijun-H
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T13:18:48Z
user: login: Blizzara
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T13:29:52Z
user: login: Weijun-H
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T14:01:14Z
user: login: jayzhan211
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T12:48:33Z
user: login: alamb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T11:02:32Z
user: login: alamb
body: Thank you @Blizzara  -- I think we should make this change even though it isn't following the normal deprecation cycle

I think an API that compiles but errors at runtime is even worse than having to fix a compiler error.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T11:03:26Z

Review comments:
diff_hunk: @@ -89,6 +92,10 @@ impl ScalarUDFImpl for DummyUDF {
     fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
         Ok(DataType::Int32)
     }
+
+    fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
+        panic!("dummy - not implemented")
path: datafusion/core/tests/physical_optimizer/projection_pushdown.rs
user: login: Weijun-H
body: what about using `not_impl_err` instead of `painc`?
created_at: 2025-03-10T13:18:32Z
author_association: MEMBER
diff_hunk: @@ -214,53 +214,18 @@ impl ScalarUDF {
         self.inner.simplify(args, info)
     }
 
-    #[deprecated(since = "42.1.0", note = "Use `invoke_with_args` instead")]
-    pub fn invoke(&self, args: &[ColumnarValue]) -> Result<ColumnarValue> {
-        #[allow(deprecated)]
-        self.inner.invoke(args)
-    }
-
path: datafusion/expr/src/udf.rs
user: login: Weijun-H
body: 👍 
created_at: 2025-03-10T13:18:48Z
author_association: MEMBER
diff_hunk: @@ -89,6 +92,10 @@ impl ScalarUDFImpl for DummyUDF {
     fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
         Ok(DataType::Int32)
     }
+
+    fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
+        panic!("dummy - not implemented")
path: datafusion/core/tests/physical_optimizer/projection_pushdown.rs
user: login: Blizzara
body: I just copied https://github.com/apache/datafusion/pull/15123/files#diff-351499880963d6a383c92e156e75019cd9ce33107724a9635853d7d4cd1898d0R1602 - but happy to change, would you prefer `not_impl_err`?
created_at: 2025-03-10T13:29:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -89,6 +92,10 @@ impl ScalarUDFImpl for DummyUDF {
     fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
         Ok(DataType::Int32)
     }
+
+    fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
+        panic!("dummy - not implemented")
path: datafusion/core/tests/physical_optimizer/projection_pushdown.rs
user: login: Weijun-H
body: I checked other traits, it would prefer `unimplemented!()`.
created_at: 2025-03-10T14:01:14Z
author_association: MEMBER
diff_hunk: @@ -89,6 +92,10 @@ impl ScalarUDFImpl for DummyUDF {
     fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
         Ok(DataType::Int32)
     }
+
+    fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
+        panic!("dummy - not implemented")
path: datafusion/core/tests/physical_optimizer/projection_pushdown.rs
user: login: alamb
body: FWIW this is a test
created_at: 2025-03-13T11:02:32Z
author_association: CONTRIBUTOR

PR comments:
user: login: goldmedal
created_at: 2025-03-10T16:39:23Z
body: Should we remove the deprecated UDF API now? According to our [API health policy](https://datafusion.apache.org/contributor-guide/api-health.html#deprecation-guidelines):
```
Deprecated methods will remain in the codebase for a period of 6 major versions or 6 months, whichever is longer, to provide users ample time to transition away from them.
```
They were annotated on 2024/11/22. I think we should keep them until at least 2025/05/22 or 48.0.0.
 I'm not sure if downstream projects have updated their usage.
author_association: CONTRIBUTOR
user: login: alamb
created_at: 2025-03-10T17:13:30Z
body: > Should we remove the deprecated UDF API now? According to our [API health policy](https://datafusion.apache.org/contributor-guide/api-health.html#deprecation-guidelines):
> 
> ```
> Deprecated methods will remain in the codebase for a period of 6 major versions or 6 months, whichever is longer, to provide users ample time to transition away from them.
> ```
> 
> They were annotated on 2024/11/22. I think we should keep them until at least 2025/05/22 or 48.0.0. I'm not sure if downstream projects have updated their usage.

In general this is true -- however, in this case since several of the example APIs can't actually be used (see [this section of the upgrade guide](https://datafusion.apache.org/library-user-guide/upgrading.html#use-invoke-with-args-instead-of-invoke-and-invoke-batch)) I think it is worth considering just removing the APIs and forcing downstream crates to update
author_association: CONTRIBUTOR
user: login: Blizzara
created_at: 2025-03-11T09:29:58Z
body: Yeah, from my perspective this one isn't so bad - implementing any of the deprecated methods is kinda okay, since the defaults are wired to propagate the call correctly here. Calling them is not fine, but as they're marked as deprecated that should be enough to prevent the calls.

The other removal (return_type_from_exprs) is more important since there the call chain doesn't work.

But then if we do one removal, it might make sense to just do the other one too, to end up with a clean interface.. Up to you guys, I'm happy to have this merged or turn it back into draft and merge later :)
author_association: CONTRIBUTOR
user: login: alamb
created_at: 2025-03-13T11:01:56Z
body: > But then if we do one removal, it might make sense to just do the other one too, to end up with a clean interface.. Up to you guys, I'm happy to have this merged or turn it back into draft and merge later :)

I agree

I made a note on the release ticket that we should write a note in the upgrade guide
- https://github.com/apache/datafusion/issues/15072
author_association: CONTRIBUTOR
user: login: alamb
created_at: 2025-03-16T11:00:51Z
body: I merged this PR up from main and plan to leave it open for a day or two more. Then I'll merge it in. 
author_association: CONTRIBUTOR
user: login: alamb
created_at: 2025-03-16T11:02:10Z
body: > Leaving only invoke_with_args

![done-and-done-spongebob](https://github.com/user-attachments/assets/b7981224-df7c-44fc-ad1a-3d966c619ad5)

author_association: CONTRIBUTOR
user: login: alamb
created_at: 2025-03-17T13:42:39Z
body: 🔨  let's go

author_association: CONTRIBUTOR
user: login: alamb
created_at: 2025-03-17T13:42:42Z
body: Thanks again @Weijun-H @jayzhan211 and @Blizzara 
author_association: CONTRIBUTOR