diff --git a/datafusion/core/tests/user_defined/user_defined_window_functions.rs b/datafusion/core/tests/user_defined/user_defined_window_functions.rs index 57baf271c5913..775325a337184 100644 --- a/datafusion/core/tests/user_defined/user_defined_window_functions.rs +++ b/datafusion/core/tests/user_defined/user_defined_window_functions.rs @@ -536,7 +536,7 @@ impl OddCounter { impl SimpleWindowUDF { fn new(test_state: Arc) -> Self { let signature = - Signature::exact(vec![DataType::Float64], Volatility::Immutable); + Signature::exact(vec![DataType::Int64], Volatility::Immutable); Self { signature, test_state: test_state.into(), diff --git a/datafusion/expr/src/expr.rs b/datafusion/expr/src/expr.rs index c7d825ce1d52f..ed88cc9860a70 100644 --- a/datafusion/expr/src/expr.rs +++ b/datafusion/expr/src/expr.rs @@ -953,7 +953,7 @@ impl AggregateFunction { pub enum WindowFunctionDefinition { /// A user defined aggregate function AggregateUDF(Arc), - /// A user defined aggregate function + /// A user defined window function WindowUDF(Arc), } diff --git a/datafusion/expr/src/expr_schema.rs b/datafusion/expr/src/expr_schema.rs index 854e907d68b1a..7b7ed7e5025fe 100644 --- a/datafusion/expr/src/expr_schema.rs +++ b/datafusion/expr/src/expr_schema.rs @@ -21,7 +21,7 @@ use crate::expr::{ InSubquery, Placeholder, ScalarFunction, TryCast, Unnest, WindowFunction, WindowFunctionParams, }; -use crate::type_coercion::functions::fields_with_udf; +use crate::type_coercion::functions::{UDFCoercionExt, fields_with_udf}; use crate::udf::ReturnFieldArgs; use crate::{LogicalPlan, Projection, Subquery, WindowFunctionDefinition, utils}; use arrow::compute::can_cast_types; @@ -152,44 +152,10 @@ impl ExprSchemable for Expr { } } } - Expr::ScalarFunction(_func) => { - let return_type = self.to_field(schema)?.1.data_type().clone(); - Ok(return_type) - } - Expr::WindowFunction(window_function) => Ok(self - .window_function_field(schema, window_function)? - .data_type() - .clone()), - Expr::AggregateFunction(AggregateFunction { - func, - params: AggregateFunctionParams { args, .. }, - }) => { - let fields = args - .iter() - .map(|e| e.to_field(schema).map(|(_, f)| f)) - .collect::>>()?; - let new_fields = fields_with_udf(&fields, func.as_ref()) - .map_err(|err| { - let data_types = fields - .iter() - .map(|f| f.data_type().clone()) - .collect::>(); - plan_datafusion_err!( - "{} {}", - match err { - DataFusionError::Plan(msg) => msg, - err => err.to_string(), - }, - utils::generate_signature_error_msg( - func.name(), - func.signature().clone(), - &data_types - ) - ) - })? - .into_iter() - .collect::>(); - Ok(func.return_field(&new_fields)?.data_type().clone()) + Expr::ScalarFunction(_) + | Expr::WindowFunction(_) + | Expr::AggregateFunction(_) => { + Ok(self.to_field(schema)?.1.data_type().clone()) } Expr::Not(_) | Expr::IsNull(_) @@ -349,18 +315,9 @@ impl ExprSchemable for Expr { } } Expr::Cast(Cast { expr, .. }) => expr.nullable(input_schema), - Expr::ScalarFunction(_func) => { - let field = self.to_field(input_schema)?.1; - - let nullable = field.is_nullable(); - Ok(nullable) - } - Expr::AggregateFunction(AggregateFunction { func, .. }) => { - Ok(func.is_nullable()) - } - Expr::WindowFunction(window_function) => Ok(self - .window_function_field(input_schema, window_function)? - .is_nullable()), + Expr::ScalarFunction(_) + | Expr::AggregateFunction(_) + | Expr::WindowFunction(_) => Ok(self.to_field(input_schema)?.1.is_nullable()), Expr::ScalarVariable(field, _) => Ok(field.is_nullable()), Expr::TryCast { .. } | Expr::Unnest(_) | Expr::Placeholder(_) => Ok(true), Expr::IsNull(_) @@ -532,69 +489,49 @@ impl ExprSchemable for Expr { ))) } Expr::WindowFunction(window_function) => { - self.window_function_field(schema, window_function) - } - Expr::AggregateFunction(aggregate_function) => { - let AggregateFunction { - func, - params: AggregateFunctionParams { args, .. }, + let WindowFunction { + fun, + params: WindowFunctionParams { args, .. }, .. - } = aggregate_function; + } = window_function.as_ref(); let fields = args .iter() .map(|e| e.to_field(schema).map(|(_, f)| f)) .collect::>>()?; - // Verify that function is invoked with correct number and type of arguments as defined in `TypeSignature` - let new_fields = fields_with_udf(&fields, func.as_ref()) - .map_err(|err| { - let arg_types = fields - .iter() - .map(|f| f.data_type()) - .cloned() - .collect::>(); - plan_datafusion_err!( - "{} {}", - match err { - DataFusionError::Plan(msg) => msg, - err => err.to_string(), - }, - utils::generate_signature_error_msg( - func.name(), - func.signature().clone(), - &arg_types, - ) - ) - })? - .into_iter() - .collect::>(); - + match fun { + WindowFunctionDefinition::AggregateUDF(udaf) => { + let new_fields = + verify_function_arguments(udaf.as_ref(), &fields)?; + let return_field = udaf.return_field(&new_fields)?; + Ok(return_field) + } + WindowFunctionDefinition::WindowUDF(udwf) => { + let new_fields = + verify_function_arguments(udwf.as_ref(), &fields)?; + let return_field = udwf + .field(WindowUDFFieldArgs::new(&new_fields, &schema_name))?; + Ok(return_field) + } + } + } + Expr::AggregateFunction(AggregateFunction { + func, + params: AggregateFunctionParams { args, .. }, + }) => { + let fields = args + .iter() + .map(|e| e.to_field(schema).map(|(_, f)| f)) + .collect::>>()?; + let new_fields = verify_function_arguments(func.as_ref(), &fields)?; func.return_field(&new_fields) } Expr::ScalarFunction(ScalarFunction { func, args }) => { - let (arg_types, fields): (Vec, Vec>) = args + let fields = args .iter() .map(|e| e.to_field(schema).map(|(_, f)| f)) - .collect::>>()? - .into_iter() - .map(|f| (f.data_type().clone(), f)) - .unzip(); - // Verify that function is invoked with correct number and type of arguments as defined in `TypeSignature` - let new_fields = - fields_with_udf(&fields, func.as_ref()).map_err(|err| { - plan_datafusion_err!( - "{} {}", - match err { - DataFusionError::Plan(msg) => msg, - err => err.to_string(), - }, - utils::generate_signature_error_msg( - func.name(), - func.signature().clone(), - &arg_types, - ) - ) - })?; + .collect::>>()?; + let new_fields = verify_function_arguments(func.as_ref(), &fields)?; let arguments = args .iter() @@ -672,6 +609,33 @@ impl ExprSchemable for Expr { } } +/// Verify that function is invoked with correct number and type of arguments as +/// defined in `TypeSignature`. +fn verify_function_arguments( + function: &F, + input_fields: &[FieldRef], +) -> Result> { + fields_with_udf(input_fields, function).map_err(|err| { + let data_types = input_fields + .iter() + .map(|f| f.data_type()) + .cloned() + .collect::>(); + plan_datafusion_err!( + "{} {}", + match err { + DataFusionError::Plan(msg) => msg, + err => err.to_string(), + }, + utils::generate_signature_error_message( + function.name(), + function.signature(), + &data_types + ) + ) + }) +} + /// Returns the innermost [Expr] that is provably null if `expr` is null. fn unwrap_certainly_null_expr(expr: &Expr) -> &Expr { match expr { @@ -682,90 +646,6 @@ fn unwrap_certainly_null_expr(expr: &Expr) -> &Expr { } } -impl Expr { - /// Common method for window functions that applies type coercion - /// to all arguments of the window function to check if it matches - /// its signature. - /// - /// If successful, this method returns the data type and - /// nullability of the window function's result. - /// - /// Otherwise, returns an error if there's a type mismatch between - /// the window function's signature and the provided arguments. - fn window_function_field( - &self, - schema: &dyn ExprSchema, - window_function: &WindowFunction, - ) -> Result { - let WindowFunction { - fun, - params: WindowFunctionParams { args, .. }, - .. - } = window_function; - - let fields = args - .iter() - .map(|e| e.to_field(schema).map(|(_, f)| f)) - .collect::>>()?; - match fun { - WindowFunctionDefinition::AggregateUDF(udaf) => { - let data_types = fields - .iter() - .map(|f| f.data_type()) - .cloned() - .collect::>(); - let new_fields = fields_with_udf(&fields, udaf.as_ref()) - .map_err(|err| { - plan_datafusion_err!( - "{} {}", - match err { - DataFusionError::Plan(msg) => msg, - err => err.to_string(), - }, - utils::generate_signature_error_msg( - fun.name(), - fun.signature(), - &data_types - ) - ) - })? - .into_iter() - .collect::>(); - - udaf.return_field(&new_fields) - } - WindowFunctionDefinition::WindowUDF(udwf) => { - let data_types = fields - .iter() - .map(|f| f.data_type()) - .cloned() - .collect::>(); - let new_fields = fields_with_udf(&fields, udwf.as_ref()) - .map_err(|err| { - plan_datafusion_err!( - "{} {}", - match err { - DataFusionError::Plan(msg) => msg, - err => err.to_string(), - }, - utils::generate_signature_error_msg( - fun.name(), - fun.signature(), - &data_types - ) - ) - })? - .into_iter() - .collect::>(); - let (_, function_name) = self.qualified_name(); - let field_args = WindowUDFFieldArgs::new(&new_fields, &function_name); - - udwf.field(field_args) - } - } - } -} - /// Cast subquery in InSubquery/ScalarSubquery to a given type. /// /// 1. **Projection plan**: If the subquery is a projection (i.e. a SELECT statement with specific diff --git a/datafusion/expr/src/type_coercion/functions.rs b/datafusion/expr/src/type_coercion/functions.rs index e1f2a19672825..6865bd12da0cb 100644 --- a/datafusion/expr/src/type_coercion/functions.rs +++ b/datafusion/expr/src/type_coercion/functions.rs @@ -94,58 +94,6 @@ impl UDFCoercionExt for WindowUDF { } } -/// Performs type coercion for scalar function arguments. -/// -/// Returns the data types to which each argument must be coerced to -/// match `signature`. -/// -/// For more details on coercion in general, please see the -/// [`type_coercion`](crate::type_coercion) module. -#[deprecated(since = "52.0.0", note = "use fields_with_udf")] -pub fn data_types_with_scalar_udf( - current_types: &[DataType], - func: &ScalarUDF, -) -> Result> { - let current_fields = current_types - .iter() - .map(|dt| Arc::new(Field::new("f", dt.clone(), true))) - .collect::>(); - Ok(fields_with_udf(¤t_fields, func)? - .iter() - .map(|f| f.data_type().clone()) - .collect()) -} - -/// Performs type coercion for aggregate function arguments. -/// -/// Returns the fields to which each argument must be coerced to -/// match `signature`. -/// -/// For more details on coercion in general, please see the -/// [`type_coercion`](crate::type_coercion) module. -#[deprecated(since = "52.0.0", note = "use fields_with_udf")] -pub fn fields_with_aggregate_udf( - current_fields: &[FieldRef], - func: &AggregateUDF, -) -> Result> { - fields_with_udf(current_fields, func) -} - -/// Performs type coercion for window function arguments. -/// -/// Returns the data types to which each argument must be coerced to -/// match `signature`. -/// -/// For more details on coercion in general, please see the -/// [`type_coercion`](crate::type_coercion) module. -#[deprecated(since = "52.0.0", note = "use fields_with_udf")] -pub fn fields_with_window_udf( - current_fields: &[FieldRef], - func: &WindowUDF, -) -> Result> { - fields_with_udf(current_fields, func) -} - /// Performs type coercion for UDF arguments. /// /// Returns the data types to which each argument must be coerced to @@ -200,6 +148,58 @@ pub fn fields_with_udf( .collect()) } +/// Performs type coercion for scalar function arguments. +/// +/// Returns the data types to which each argument must be coerced to +/// match `signature`. +/// +/// For more details on coercion in general, please see the +/// [`type_coercion`](crate::type_coercion) module. +#[deprecated(since = "52.0.0", note = "use fields_with_udf")] +pub fn data_types_with_scalar_udf( + current_types: &[DataType], + func: &ScalarUDF, +) -> Result> { + let current_fields = current_types + .iter() + .map(|dt| Arc::new(Field::new("f", dt.clone(), true))) + .collect::>(); + Ok(fields_with_udf(¤t_fields, func)? + .iter() + .map(|f| f.data_type().clone()) + .collect()) +} + +/// Performs type coercion for aggregate function arguments. +/// +/// Returns the fields to which each argument must be coerced to +/// match `signature`. +/// +/// For more details on coercion in general, please see the +/// [`type_coercion`](crate::type_coercion) module. +#[deprecated(since = "52.0.0", note = "use fields_with_udf")] +pub fn fields_with_aggregate_udf( + current_fields: &[FieldRef], + func: &AggregateUDF, +) -> Result> { + fields_with_udf(current_fields, func) +} + +/// Performs type coercion for window function arguments. +/// +/// Returns the data types to which each argument must be coerced to +/// match `signature`. +/// +/// For more details on coercion in general, please see the +/// [`type_coercion`](crate::type_coercion) module. +#[deprecated(since = "52.0.0", note = "use fields_with_udf")] +pub fn fields_with_window_udf( + current_fields: &[FieldRef], + func: &WindowUDF, +) -> Result> { + fields_with_udf(current_fields, func) +} + /// Performs type coercion for function arguments. /// /// Returns the data types to which each argument must be coerced to @@ -487,7 +487,7 @@ fn get_valid_types( let valid_types = match signature { TypeSignature::Variadic(valid_types) => valid_types .iter() - .map(|valid_type| current_types.iter().map(|_| valid_type.clone()).collect()) + .map(|valid_type| vec![valid_type.clone(); current_types.len()]) .collect(), TypeSignature::String(number) => { function_length_check(function_name, current_types.len(), *number)?; @@ -655,7 +655,7 @@ fn get_valid_types( valid_types .iter() - .map(|valid_type| (0..*number).map(|_| valid_type.clone()).collect()) + .map(|valid_type| vec![valid_type.clone(); *number]) .collect() } TypeSignature::UserDefined => { @@ -722,7 +722,7 @@ fn get_valid_types( current_types.len() ); } - vec![(0..*number).map(|i| current_types[i].clone()).collect()] + vec![current_types.to_vec()] } TypeSignature::OneOf(types) => types .iter() @@ -800,6 +800,7 @@ fn maybe_data_types_without_coercion( /// (losslessly converted) into a value of `type_to` /// /// See the module level documentation for more detail on coercion. +#[deprecated(since = "53.0.0", note = "Unused internal function")] pub fn can_coerce_from(type_into: &DataType, type_from: &DataType) -> bool { if type_into == type_from { return true; @@ -934,12 +935,12 @@ mod tests { #[test] fn test_string_conversion() { let cases = vec![ - (DataType::Utf8View, DataType::Utf8, true), - (DataType::Utf8View, DataType::LargeUtf8, true), + (DataType::Utf8View, DataType::Utf8), + (DataType::Utf8View, DataType::LargeUtf8), ]; for case in cases { - assert_eq!(can_coerce_from(&case.0, &case.1), case.2); + assert_eq!(coerced_from(&case.0, &case.1), Some(case.0)); } } diff --git a/datafusion/expr/src/utils.rs b/datafusion/expr/src/utils.rs index de4ebf5fa96e9..79797973324ef 100644 --- a/datafusion/expr/src/utils.rs +++ b/datafusion/expr/src/utils.rs @@ -937,6 +937,7 @@ pub fn find_valid_equijoin_key_pair( /// round(Float32) /// ``` #[expect(clippy::needless_pass_by_value)] +#[deprecated(since = "53.0.0", note = "Internal function")] pub fn generate_signature_error_msg( func_name: &str, func_signature: Signature, @@ -958,6 +959,26 @@ pub fn generate_signature_error_msg( ) } +/// Creates a detailed error message for a function with wrong signature. +/// +/// For example, a query like `select round(3.14, 1.1);` would yield: +/// ```text +/// Error during planning: No function matches 'round(Float64, Float64)'. You might need to add explicit type casts. +/// Candidate functions: +/// round(Float64, Int64) +/// round(Float32, Int64) +/// round(Float64) +/// round(Float32) +/// ``` +pub(crate) fn generate_signature_error_message( + func_name: &str, + func_signature: &Signature, + input_expr_types: &[DataType], +) -> String { + #[expect(deprecated)] + generate_signature_error_msg(func_name, func_signature.clone(), input_expr_types) +} + /// Splits a conjunctive [`Expr`] such as `A AND B AND C` => `[A, B, C]` /// /// See [`split_conjunction_owned`] for more details and an example. @@ -1734,7 +1755,8 @@ mod tests { .expect("valid parameter names"); // Generate error message with only 1 argument provided - let error_msg = generate_signature_error_msg("substr", sig, &[DataType::Utf8]); + let error_msg = + generate_signature_error_message("substr", &sig, &[DataType::Utf8]); assert!( error_msg.contains("str: Utf8, start_pos: Int64"), @@ -1753,7 +1775,8 @@ mod tests { Volatility::Immutable, ); - let error_msg = generate_signature_error_msg("my_func", sig, &[DataType::Int32]); + let error_msg = + generate_signature_error_message("my_func", &sig, &[DataType::Int32]); assert!( error_msg.contains("Any, Any"), diff --git a/datafusion/functions-nested/src/make_array.rs b/datafusion/functions-nested/src/make_array.rs index 410a545853acf..bc899126fb643 100644 --- a/datafusion/functions-nested/src/make_array.rs +++ b/datafusion/functions-nested/src/make_array.rs @@ -31,7 +31,6 @@ use arrow::datatypes::DataType; use arrow::datatypes::{DataType::Null, Field}; use datafusion_common::utils::SingleRowListArrayBuilder; use datafusion_common::{Result, plan_err}; -use datafusion_expr::TypeSignature; use datafusion_expr::binary::{ try_type_union_resolution_with_struct, type_union_resolution, }; @@ -80,10 +79,7 @@ impl Default for MakeArray { impl MakeArray { pub fn new() -> Self { Self { - signature: Signature::one_of( - vec![TypeSignature::Nullary, TypeSignature::UserDefined], - Volatility::Immutable, - ), + signature: Signature::user_defined(Volatility::Immutable), aliases: vec![String::from("make_list")], } } @@ -125,7 +121,11 @@ impl ScalarUDFImpl for MakeArray { } fn coerce_types(&self, arg_types: &[DataType]) -> Result> { - coerce_types_inner(arg_types, self.name()) + if arg_types.is_empty() { + Ok(vec![]) + } else { + coerce_types_inner(arg_types, self.name()) + } } fn documentation(&self) -> Option<&Documentation> { diff --git a/datafusion/functions-window/src/nth_value.rs b/datafusion/functions-window/src/nth_value.rs index c62f0a9ae4e89..c8980d9f1dc67 100644 --- a/datafusion/functions-window/src/nth_value.rs +++ b/datafusion/functions-window/src/nth_value.rs @@ -97,7 +97,7 @@ impl NthValue { Self { signature: Signature::one_of( vec![ - TypeSignature::Any(0), + TypeSignature::Nullary, TypeSignature::Any(1), TypeSignature::Any(2), ], diff --git a/datafusion/optimizer/src/analyzer/type_coercion.rs b/datafusion/optimizer/src/analyzer/type_coercion.rs index 02395c76bdd92..ec7ec2ade0470 100644 --- a/datafusion/optimizer/src/analyzer/type_coercion.rs +++ b/datafusion/optimizer/src/analyzer/type_coercion.rs @@ -42,16 +42,16 @@ use datafusion_expr::expr_rewriter::coerce_plan_expr_for_schema; use datafusion_expr::expr_schema::cast_subquery; use datafusion_expr::logical_plan::Subquery; use datafusion_expr::type_coercion::binary::{comparison_coercion, like_coercion}; -use datafusion_expr::type_coercion::functions::fields_with_udf; +use datafusion_expr::type_coercion::functions::{UDFCoercionExt, fields_with_udf}; use datafusion_expr::type_coercion::other::{ get_coerce_type_for_case_expression, get_coerce_type_for_list, }; use datafusion_expr::type_coercion::{is_datetime, is_utf8_or_utf8view_or_large_utf8}; use datafusion_expr::utils::merge_schema; use datafusion_expr::{ - AggregateUDF, Cast, Expr, ExprSchemable, Join, Limit, LogicalPlan, Operator, - Projection, ScalarUDF, Union, WindowFrame, WindowFrameBound, WindowFrameUnits, - is_false, is_not_false, is_not_true, is_not_unknown, is_true, is_unknown, lit, not, + Cast, Expr, ExprSchemable, Join, Limit, LogicalPlan, Operator, Projection, Union, + WindowFrame, WindowFrameBound, WindowFrameUnits, is_false, is_not_false, is_not_true, + is_not_unknown, is_true, is_unknown, lit, not, }; /// Performs type coercion by determining the schema @@ -637,11 +637,8 @@ impl TreeNodeRewriter for TypeCoercionRewriter<'_> { Ok(Transformed::yes(Expr::Case(case))) } Expr::ScalarFunction(ScalarFunction { func, args }) => { - let new_expr = coerce_arguments_for_signature_with_scalar_udf( - args, - self.schema, - &func, - )?; + let new_expr = + coerce_arguments_for_signature(args, self.schema, func.as_ref())?; Ok(Transformed::yes(Expr::ScalarFunction( ScalarFunction::new_udf(func, new_expr), ))) @@ -657,11 +654,8 @@ impl TreeNodeRewriter for TypeCoercionRewriter<'_> { null_treatment, }, }) => { - let new_expr = coerce_arguments_for_signature_with_aggregate_udf( - args, - self.schema, - &func, - )?; + let new_expr = + coerce_arguments_for_signature(args, self.schema, func.as_ref())?; Ok(Transformed::yes(Expr::AggregateFunction( expr::AggregateFunction::new_udf( func, @@ -692,13 +686,11 @@ impl TreeNodeRewriter for TypeCoercionRewriter<'_> { let args = match &fun { expr::WindowFunctionDefinition::AggregateUDF(udf) => { - coerce_arguments_for_signature_with_aggregate_udf( - args, - self.schema, - udf, - )? + coerce_arguments_for_signature(args, self.schema, udf.as_ref())? + } + expr::WindowFunctionDefinition::WindowUDF(udf) => { + coerce_arguments_for_signature(args, self.schema, udf.as_ref())? } - _ => args, }; let new_expr = Expr::from(WindowFunction { @@ -917,40 +909,10 @@ fn get_casted_expr_for_bool_op(expr: Expr, schema: &DFSchema) -> Result { /// `signature`, if possible. /// /// See the module level documentation for more detail on coercion. -fn coerce_arguments_for_signature_with_scalar_udf( - expressions: Vec, - schema: &DFSchema, - func: &ScalarUDF, -) -> Result> { - if expressions.is_empty() { - return Ok(expressions); - } - - let current_fields = expressions - .iter() - .map(|e| e.to_field(schema).map(|(_, f)| f)) - .collect::>>()?; - - let coerced_types = fields_with_udf(¤t_fields, func)? - .into_iter() - .map(|f| f.data_type().clone()) - .collect::>(); - - expressions - .into_iter() - .enumerate() - .map(|(i, expr)| expr.cast_to(&coerced_types[i], schema)) - .collect() -} - -/// Returns `expressions` coerced to types compatible with -/// `signature`, if possible. -/// -/// See the module level documentation for more detail on coercion. -fn coerce_arguments_for_signature_with_aggregate_udf( +fn coerce_arguments_for_signature( expressions: Vec, schema: &DFSchema, - func: &AggregateUDF, + func: &F, ) -> Result> { if expressions.is_empty() { return Ok(expressions); diff --git a/datafusion/spark/src/function/array/spark_array.rs b/datafusion/spark/src/function/array/spark_array.rs index 6d9f9a1695e1b..1ad0a394b8ca6 100644 --- a/datafusion/spark/src/function/array/spark_array.rs +++ b/datafusion/spark/src/function/array/spark_array.rs @@ -23,7 +23,7 @@ use datafusion_common::utils::SingleRowListArrayBuilder; use datafusion_common::{Result, internal_err}; use datafusion_expr::{ ColumnarValue, ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl, Signature, - TypeSignature, Volatility, + Volatility, }; use datafusion_functions_nested::make_array::{array_array, coerce_types_inner}; @@ -45,10 +45,7 @@ impl Default for SparkArray { impl SparkArray { pub fn new() -> Self { Self { - signature: Signature::one_of( - vec![TypeSignature::UserDefined, TypeSignature::Nullary], - Volatility::Immutable, - ), + signature: Signature::user_defined(Volatility::Immutable), } } } @@ -104,12 +101,12 @@ impl ScalarUDFImpl for SparkArray { make_scalar_function(make_array_inner)(args.as_slice()) } - fn aliases(&self) -> &[String] { - &[] - } - fn coerce_types(&self, arg_types: &[DataType]) -> Result> { - coerce_types_inner(arg_types, self.name()) + if arg_types.is_empty() { + Ok(vec![]) + } else { + coerce_types_inner(arg_types, self.name()) + } } } diff --git a/datafusion/spark/src/function/string/concat.rs b/datafusion/spark/src/function/string/concat.rs index f3dae22866c23..48a74e5763a0a 100644 --- a/datafusion/spark/src/function/string/concat.rs +++ b/datafusion/spark/src/function/string/concat.rs @@ -20,8 +20,7 @@ use datafusion_common::arrow::datatypes::FieldRef; use datafusion_common::{Result, ScalarValue}; use datafusion_expr::ReturnFieldArgs; use datafusion_expr::{ - ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature, - Volatility, + ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility, }; use datafusion_functions::string::concat::ConcatFunc; use std::any::Any; @@ -54,10 +53,7 @@ impl Default for SparkConcat { impl SparkConcat { pub fn new() -> Self { Self { - signature: Signature::one_of( - vec![TypeSignature::UserDefined, TypeSignature::Nullary], - Volatility::Immutable, - ), + signature: Signature::user_defined(Volatility::Immutable), } } }