Skip to content

Commit a49d543

Browse files
chore: Attach Diagnostic to "function x does not exist" error (#14849)
* Add detailed error message for invalid function error * remove clone * better note * fix test case
1 parent 111ff7e commit a49d543

File tree

2 files changed

+27
-8
lines changed

2 files changed

+27
-8
lines changed

datafusion/sql/src/expr/function.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::planner::{ContextProvider, PlannerContext, SqlToRel};
2020
use arrow::datatypes::DataType;
2121
use datafusion_common::{
2222
internal_datafusion_err, internal_err, not_impl_err, plan_datafusion_err, plan_err,
23-
DFSchema, Dependency, Result,
23+
DFSchema, Dependency, Diagnostic, Result, Span,
2424
};
2525
use datafusion_expr::expr::{ScalarFunction, Unnest};
2626
use datafusion_expr::planner::{PlannerResult, RawAggregateExpr, RawWindowExpr};
@@ -217,7 +217,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
217217
// it shouldn't have ordering requirement as function argument
218218
// required ordering should be defined in OVER clause.
219219
let is_function_window = over.is_some();
220-
220+
let sql_parser_span = name.0[0].span;
221221
let name = if name.0.len() > 1 {
222222
// DF doesn't handle compound identifiers
223223
// (e.g. "foo.bar") for function names yet
@@ -236,7 +236,6 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
236236
}
237237
}
238238
}
239-
240239
// User-defined function (UDF) should have precedence
241240
if let Some(fm) = self.context_provider.get_function_meta(&name) {
242241
let args = self.function_args_to_expr(args, schema, planner_context)?;
@@ -259,7 +258,6 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
259258
"Aggregate ORDER BY is not implemented for window functions"
260259
);
261260
}
262-
263261
// Then, window function
264262
if let Some(WindowType::WindowSpec(window)) = over {
265263
let partition_by = window
@@ -399,12 +397,21 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
399397
)));
400398
}
401399
}
402-
403400
// Could not find the relevant function, so return an error
404401
if let Some(suggested_func_name) =
405402
suggest_valid_function(&name, is_function_window, self.context_provider)
406403
{
407404
plan_err!("Invalid function '{name}'.\nDid you mean '{suggested_func_name}'?")
405+
.map_err(|e| {
406+
let span = Span::try_from_sqlparser_span(sql_parser_span);
407+
let mut diagnostic =
408+
Diagnostic::new_error(format!("Invalid function '{name}'"), span);
409+
diagnostic.add_note(
410+
format!("Possible function '{}'", suggested_func_name),
411+
None,
412+
);
413+
e.with_diagnostic(diagnostic)
414+
})
408415
} else {
409416
internal_err!("No functions registered with this context.")
410417
}

datafusion/sql/tests/cases/diagnostic.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use std::collections::HashMap;
18+
use datafusion_functions::string;
19+
use std::{collections::HashMap, sync::Arc};
1920

2021
use datafusion_common::{Diagnostic, Location, Result, Span};
2122
use datafusion_sql::planner::{ParserOptions, SqlToRel};
@@ -35,8 +36,8 @@ fn do_query(sql: &'static str) -> Diagnostic {
3536
collect_spans: true,
3637
..ParserOptions::default()
3738
};
38-
39-
let state = MockSessionState::default();
39+
let state = MockSessionState::default()
40+
.with_scalar_function(Arc::new(string::concat().as_ref().clone()));
4041
let context = MockContextProvider { state };
4142
let sql_to_rel = SqlToRel::new_with_options(&context, options);
4243
match sql_to_rel.sql_statement_to_plan(statement) {
@@ -274,3 +275,14 @@ fn test_ambiguous_column_suggestion() -> Result<()> {
274275

275276
Ok(())
276277
}
278+
279+
#[test]
280+
fn test_invalid_function() -> Result<()> {
281+
let query = "SELECT /*whole*/concat_not_exist/*whole*/()";
282+
let spans = get_spans(query);
283+
let diag = do_query(query);
284+
assert_eq!(diag.message, "Invalid function 'concat_not_exist'");
285+
assert_eq!(diag.notes[0].message, "Possible function 'concat'");
286+
assert_eq!(diag.span, Some(spans["whole"]));
287+
Ok(())
288+
}

0 commit comments

Comments
 (0)