@@ -20,20 +20,67 @@ 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+ let mut funcs = Vec :: new ( ) ;
48+
49+ funcs. extend ( AggregateFunction :: iter ( ) . map ( |func| func. to_string ( ) ) ) ;
50+ funcs. extend ( ctx. udafs_names ( ) ) ;
51+ funcs. extend ( BuiltInWindowFunction :: iter ( ) . map ( |func| func. to_string ( ) ) ) ;
52+ funcs. extend ( ctx. udwfs_names ( ) ) ;
53+
54+ funcs
55+ } else {
56+ // All scalar functions and aggregate functions
57+ let mut funcs = Vec :: new ( ) ;
58+
59+ funcs. extend ( BuiltinScalarFunction :: iter ( ) . map ( |func| func. to_string ( ) ) ) ;
60+ funcs. extend ( ctx. udfs_names ( ) ) ;
61+ funcs. extend ( AggregateFunction :: iter ( ) . map ( |func| func. to_string ( ) ) ) ;
62+ funcs. extend ( ctx. udafs_names ( ) ) ;
63+
64+ funcs
65+ } ;
66+ find_closest_match ( valid_funcs, input_function_name)
67+ }
68+
69+ /// Find the closest matching string to the target string in the candidates list, using edit distance(case insensitve)
70+ /// Input `candidates` must not be empty otherwise it will panic
71+ fn find_closest_match ( candidates : Vec < String > , target : & str ) -> String {
72+ let target = target. to_lowercase ( ) ;
73+ candidates
74+ . into_iter ( )
75+ . min_by_key ( |candidate| {
76+ datafusion_common:: utils:: datafusion_strsim:: levenshtein (
77+ & candidate. to_lowercase ( ) ,
78+ & target,
79+ )
80+ } )
81+ . expect ( "No candidates provided." ) // Panic if `candidates` argument is empty
82+ }
83+
3784impl < ' a , S : ContextProvider > SqlToRel < ' a , S > {
3885 pub ( super ) fn sql_function_to_expr (
3986 & self ,
@@ -211,7 +258,8 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
211258 }
212259
213260 // Could not find the relevant function, so return an error
214- let suggested_func_name = suggest_valid_function ( & name, is_function_window) ;
261+ let suggested_func_name =
262+ suggest_valid_function ( & name, is_function_window, self . context_provider ) ;
215263 plan_err ! ( "Invalid function '{name}'.\n Did you mean '{suggested_func_name}'?" )
216264 }
217265
0 commit comments