@@ -20,20 +20,63 @@ use arrow_schema::DataType;
2020use datafusion_common:: {
2121 not_impl_err, plan_datafusion_err, plan_err, DFSchema , Dependency , Result ,
2222} ;
23- use datafusion_expr:: expr:: { ScalarFunction , Unnest } ;
24- use datafusion_expr:: function:: suggest_valid_function;
2523use datafusion_expr:: window_frame:: { check_window_frame, regularize_window_order_by} ;
2624use datafusion_expr:: {
27- expr, AggregateFunction , BuiltinScalarFunction , Expr , ExprSchemable , WindowFrame ,
28- WindowFunctionDefinition ,
25+ expr, AggregateFunction , Expr , ExprSchemable , WindowFrame , WindowFunctionDefinition ,
26+ } ;
27+ use datafusion_expr:: {
28+ expr:: { ScalarFunction , Unnest } ,
29+ BuiltInWindowFunction , BuiltinScalarFunction ,
2930} ;
3031use sqlparser:: ast:: {
3132 Expr as SQLExpr , Function as SQLFunction , FunctionArg , FunctionArgExpr , WindowType ,
3233} ;
3334use std:: str:: FromStr ;
35+ use strum:: IntoEnumIterator ;
3436
3537use super :: arrow_cast:: ARROW_CAST_NAME ;
3638
39+ /// Suggest a valid function based on an invalid input function name
40+ pub fn suggest_valid_function (
41+ input_function_name : & str ,
42+ is_window_func : bool ,
43+ ctx : & dyn ContextProvider ,
44+ ) -> String {
45+ let valid_funcs = if is_window_func {
46+ // All aggregate functions and builtin window functions
47+ AggregateFunction :: iter ( )
48+ . map ( |func| func. to_string ( ) )
49+ . chain ( BuiltInWindowFunction :: iter ( ) . map ( |func| func. to_string ( ) ) )
50+ . collect ( )
51+ } else {
52+ // All scalar functions and aggregate functions
53+ let mut funcs = Vec :: new ( ) ;
54+
55+ funcs. extend ( BuiltinScalarFunction :: iter ( ) . map ( |func| func. to_string ( ) ) ) ;
56+ funcs. extend ( ctx. udfs ( ) . into_keys ( ) ) ;
57+ funcs. extend ( AggregateFunction :: iter ( ) . map ( |func| func. to_string ( ) ) ) ;
58+ funcs. extend ( ctx. udafs ( ) . into_keys ( ) ) ;
59+
60+ funcs
61+ } ;
62+ find_closest_match ( valid_funcs, input_function_name)
63+ }
64+
65+ /// Find the closest matching string to the target string in the candidates list, using edit distance(case insensitve)
66+ /// Input `candidates` must not be empty otherwise it will panic
67+ fn find_closest_match ( candidates : Vec < String > , target : & str ) -> String {
68+ let target = target. to_lowercase ( ) ;
69+ candidates
70+ . into_iter ( )
71+ . min_by_key ( |candidate| {
72+ datafusion_common:: utils:: datafusion_strsim:: levenshtein (
73+ & candidate. to_lowercase ( ) ,
74+ & target,
75+ )
76+ } )
77+ . expect ( "No candidates provided." ) // Panic if `candidates` argument is empty
78+ }
79+
3780impl < ' a , S : ContextProvider > SqlToRel < ' a , S > {
3881 pub ( super ) fn sql_function_to_expr (
3982 & self ,
@@ -211,7 +254,8 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
211254 }
212255
213256 // Could not find the relevant function, so return an error
214- let suggested_func_name = suggest_valid_function ( & name, is_function_window) ;
257+ let suggested_func_name =
258+ suggest_valid_function ( & name, is_function_window, self . context_provider ) ;
215259 plan_err ! ( "Invalid function '{name}'.\n Did you mean '{suggested_func_name}'?" )
216260 }
217261
0 commit comments