Skip to content

Commit

Permalink
add hint display for non-existent function
Browse files Browse the repository at this point in the history
  • Loading branch information
xzhseh committed Feb 21, 2024
1 parent e9e94b4 commit f47a474
Showing 1 changed file with 75 additions and 27 deletions.
102 changes: 75 additions & 27 deletions src/frontend/src/handler/create_sql_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,56 @@ use crate::catalog::CatalogError;
use crate::expr::{Expr, ExprImpl, Literal};
use crate::{bind_data_type, Binder};

/// The error type for hint display
/// Currently we will try invalid item first
/// Then try to find non-existent functions
enum ErrMsgType {
Parameter,
Function,
// Not yet support
None,
}

const DEFAULT_ERR_MSG: &str = "Failed to conduct semantic check";

/// Used for hint display
const PROMPT: &str = "In SQL UDF definition: ";

pub const SQL_UDF_PATTERN: &str = "[sql udf]";
/// Used for detecting non-existent function
const FUNCTION_KEYWORD: &str = "function";

/// Create a mock `udf_context`, which is used for semantic check
fn create_mock_udf_context(
arg_types: Vec<DataType>,
arg_names: Vec<String>,
) -> HashMap<String, ExprImpl> {
let mut ret: HashMap<String, ExprImpl> = (1..=arg_types.len())
.map(|i| {
let mock_expr =
ExprImpl::Literal(Box::new(Literal::new(None, arg_types[i - 1].clone())));
(format!("${i}"), mock_expr)
})
.collect();
/// Used for detecting invalid parameters
pub const SQL_UDF_PATTERN: &str = "[sql udf]";

for (i, arg_name) in arg_names.into_iter().enumerate() {
let mock_expr = ExprImpl::Literal(Box::new(Literal::new(None, arg_types[i].clone())));
ret.insert(arg_name, mock_expr);
/// Validate the error message to see if
/// it's possible to improve the display to users
fn validate_err_msg(invalid_msg: &str) -> ErrMsgType {
// First try invalid parameters
if invalid_msg.find(SQL_UDF_PATTERN).is_some() {
ErrMsgType::Parameter
} else if invalid_msg.find(FUNCTION_KEYWORD).is_some() {
ErrMsgType::Function
} else {
// Nothing could be better display
ErrMsgType::None
}
}

ret
/// Extract the target name to hint display
/// according to the type of the error message item
fn extract_hint_display_target(err_msg_type: ErrMsgType, invalid_msg: &str) -> Option<&str> {
match err_msg_type {
// e.g., [sql udf] failed to find named parameter <target name>
ErrMsgType::Parameter => invalid_msg.split_whitespace().last(),
// e.g., function <target name> does not exist
ErrMsgType::Function => {
let func = invalid_msg.split_whitespace().nth(1).unwrap_or("null");
// Note: we do not want the parenthesis
func.find('(').map(|i| &func[0..i])
}
// Nothing to hint display, return default error message
ErrMsgType::None => None,
}
}

/// Find the pattern for better hint display
Expand All @@ -77,6 +102,27 @@ fn find_target(input: &str, target: &str) -> Option<usize> {
Some(ma.start())
}

/// Create a mock `udf_context`, which is used for semantic check
fn create_mock_udf_context(
arg_types: Vec<DataType>,
arg_names: Vec<String>,
) -> HashMap<String, ExprImpl> {
let mut ret: HashMap<String, ExprImpl> = (1..=arg_types.len())
.map(|i| {
let mock_expr =
ExprImpl::Literal(Box::new(Literal::new(None, arg_types[i - 1].clone())));
(format!("${i}"), mock_expr)
})
.collect();

for (i, arg_name) in arg_names.into_iter().enumerate() {
let mock_expr = ExprImpl::Literal(Box::new(Literal::new(None, arg_types[i].clone())));
ret.insert(arg_name, mock_expr);
}

ret
}

pub async fn handle_create_sql_function(
handler_args: HandlerArgs,
or_replace: bool,
Expand Down Expand Up @@ -232,20 +278,22 @@ pub async fn handle_create_sql_function(
Err(e) => {
if let ErrorCode::BindErrorRoot { expr: _, error } = e.inner() {
let invalid_msg = error.to_string();

println!("invalid_msg: {}", invalid_msg);

// Valid error message for hint display
let Some(_) = invalid_msg.as_str().find(SQL_UDF_PATTERN) else {
return Err(
ErrorCode::InvalidInputSyntax(DEFAULT_ERR_MSG.into()).into()
);
};
// First validate the message
let err_msg_type = validate_err_msg(invalid_msg.as_str());

// Get the name of the invalid item
// We will just display the first one found
let invalid_item_name =
invalid_msg.split_whitespace().last().unwrap_or("null");

// Find the invalid parameter / column
let Some(invalid_item_name) =
extract_hint_display_target(err_msg_type, invalid_msg.as_str()) else {
return Err(
ErrorCode::InvalidInputSyntax(DEFAULT_ERR_MSG.into()).into()
);
};

// Find the invalid parameter / column / function
let Some(idx) = find_target(body.as_str(), invalid_item_name) else {
return Err(
ErrorCode::InvalidInputSyntax(DEFAULT_ERR_MSG.into()).into()
Expand Down

0 comments on commit f47a474

Please sign in to comment.