diff --git a/datafusion/core/tests/sqllogictests/test_files/errors.slt b/datafusion/core/tests/sqllogictests/test_files/errors.slt index b8f650d61ea4f..938209d21cc8e 100644 --- a/datafusion/core/tests/sqllogictests/test_files/errors.slt +++ b/datafusion/core/tests/sqllogictests/test_files/errors.slt @@ -38,7 +38,7 @@ WITH HEADER ROW LOCATION '../../testing/data/csv/aggregate_test_100.csv' # csv_query_error -statement error Error during planning: Coercion from \[Utf8\] to the signature Uniform\(1, \[Float64, Float32\]\) failed\. +statement error DataFusion error: Error during planning: No function matches the given name and argument types 'sin\(Utf8\)'. You might need to add explicit type casts.\n\tCandidate functions:\n\tsin\(Float64/Float32\) SELECT sin(c1) FROM aggregate_test_100 # cast_expressions_error diff --git a/datafusion/core/tests/sqllogictests/test_files/scalar.slt b/datafusion/core/tests/sqllogictests/test_files/scalar.slt index f405a0857ada7..13b8a74860cc8 100644 --- a/datafusion/core/tests/sqllogictests/test_files/scalar.slt +++ b/datafusion/core/tests/sqllogictests/test_files/scalar.slt @@ -1209,3 +1209,23 @@ NULL NULL NULL NULL 8 15.625 statement ok drop table test + +# error message for wrong function signature (Variadic: arbitrary number of args all from some common types) +statement error Error during planning: No function matches the given name and argument types 'concat\(\)'. You might need to add explicit type casts.\n\tCandidate functions:\n\tconcat\(Utf8, ..\) +SELECT concat(); + +# error message for wrong function signature (Uniform: t args all from some common types) +statement error Error during planning: No function matches the given name and argument types 'nullif\(Int64\)'. You might need to add explicit type casts.\n\tCandidate functions:\n\tnullif\(Boolean/UInt8/UInt16/UInt32/UInt64/Int8/Int16/Int32/Int64/Float32/Float64/Utf8/LargeUtf8, Boolean/UInt8/UInt16/UInt32/UInt64/Int8/Int16/Int32/Int64/Float32/Float64/Utf8/LargeUtf8\) +SELECT nullif(1); + +# error message for wrong function signature (Exact: exact number of args of an exact type) +statement error Error during planning: No function matches the given name and argument types 'pi\(Float64\)'. You might need to add explicit type casts.\n\tCandidate functions:\n\tpi\(\) +SELECT pi(3.14); + +# error message for wrong function signature (Any: fixed number of args of arbitrary types) +statement error Error during planning: No function matches the given name and argument types 'arrowtypeof\(Int64, Int64\)'. You might need to add explicit type casts.\n\tCandidate functions:\n\tarrowtypeof\(Any\) +SELECT arrow_typeof(1, 1); + +# error message for wrong function signature (OneOf: fixed number of args of arbitrary types) +statement error Error during planning: No function matches the given name and argument types 'power\(Int64, Int64, Int64\)'. You might need to add explicit type casts.\n\tCandidate functions:\n\tpower\(Int64, Int64\)\n\tpower\(Float64, Float64\) +SELECT power(1, 2, 3); diff --git a/datafusion/expr/src/function.rs b/datafusion/expr/src/function.rs index fa3d94cbff2b2..5ba6852248572 100644 --- a/datafusion/expr/src/function.rs +++ b/datafusion/expr/src/function.rs @@ -17,6 +17,7 @@ //! Function module contains typing and signature for built-in and user defined functions. +use crate::function_err::generate_signature_error_msg; use crate::nullif::SUPPORTED_NULLIF_TYPES; use crate::type_coercion::functions::data_types; use crate::ColumnarValue; @@ -100,13 +101,16 @@ pub fn return_type( // or the execution panics. if input_expr_types.is_empty() && !fun.supports_zero_argument() { - return Err(DataFusionError::Internal(format!( - "Builtin scalar function {fun} does not support empty arguments" + return Err(DataFusionError::Plan(generate_signature_error_msg( + fun, + input_expr_types, ))); } // verify that this is a valid set of data types for this function - data_types(input_expr_types, &signature(fun))?; + data_types(input_expr_types, &signature(fun)).map_err(|_| { + DataFusionError::Plan(generate_signature_error_msg(fun, input_expr_types)) + })?; // the return type of the built in function. // Some built-in functions' return type depends on the incoming type. diff --git a/datafusion/expr/src/function_err.rs b/datafusion/expr/src/function_err.rs new file mode 100644 index 0000000000000..39ac4ef8039a7 --- /dev/null +++ b/datafusion/expr/src/function_err.rs @@ -0,0 +1,91 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! Function_err module enhances frontend error messages for unresolved functions due to incorrect parameters, +//! by providing the correct function signatures. +//! +//! 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) +//! ``` + +use crate::function::signature; +use crate::{BuiltinScalarFunction, TypeSignature}; +use arrow::datatypes::DataType; + +impl TypeSignature { + fn to_string_repr(&self) -> Vec { + match self { + TypeSignature::Variadic(types) => { + vec![format!("{}, ..", join_types(types, "/"))] + } + TypeSignature::Uniform(arg_count, valid_types) => { + vec![std::iter::repeat(join_types(valid_types, "/")) + .take(*arg_count) + .collect::>() + .join(", ")] + } + TypeSignature::Exact(types) => { + vec![join_types(types, ", ")] + } + TypeSignature::Any(arg_count) => { + vec![std::iter::repeat("Any") + .take(*arg_count) + .collect::>() + .join(", ")] + } + TypeSignature::VariadicEqual => vec!["T, .., T".to_string()], + TypeSignature::VariadicAny => vec!["Any, .., Any".to_string()], + TypeSignature::OneOf(sigs) => { + sigs.iter().flat_map(|s| s.to_string_repr()).collect() + } + } + } +} + +/// Helper function to join types with specified delimiter. +fn join_types(types: &[T], delimiter: &str) -> String { + types + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(delimiter) +} + +/// Creates a detailed error message for a function with wrong signature. +pub fn generate_signature_error_msg( + fun: &BuiltinScalarFunction, + input_expr_types: &[DataType], +) -> String { + let candidate_signatures = signature(fun) + .type_signature + .to_string_repr() + .iter() + .map(|args_str| format!("\t{}({})", fun, args_str)) + .collect::>() + .join("\n"); + + format!( + "No function matches the given name and argument types '{}({})'. You might need to add explicit type casts.\n\tCandidate functions:\n{}", + fun, join_types(input_expr_types, ", "), candidate_signatures + ) +} diff --git a/datafusion/expr/src/lib.rs b/datafusion/expr/src/lib.rs index c4cab235c0a1b..45777e09d4bb9 100644 --- a/datafusion/expr/src/lib.rs +++ b/datafusion/expr/src/lib.rs @@ -37,6 +37,7 @@ pub mod expr_rewriter; pub mod expr_schema; pub mod field_util; pub mod function; +mod function_err; mod literal; pub mod logical_plan; mod nullif; diff --git a/datafusion/physical-expr/src/functions.rs b/datafusion/physical-expr/src/functions.rs index 2dbf4559ddbb6..2cb0d218e708d 100644 --- a/datafusion/physical-expr/src/functions.rs +++ b/datafusion/physical-expr/src/functions.rs @@ -2750,11 +2750,10 @@ mod tests { "Builtin scalar function {fun} does not support empty arguments" ))); } - Err(DataFusionError::Internal(err)) => { - if err - != format!( - "Builtin scalar function {fun} does not support empty arguments" - ) { + Err(DataFusionError::Plan(err)) => { + if !err + .contains("No function matches the given name and argument types") + { return Err(DataFusionError::Internal(format!( "Builtin scalar function {fun} didn't got the right error message with empty arguments"))); }