use vibesql_ast::Expression;
use vibesql_catalog::{Function, FunctionBody};
use vibesql_parser::Parser;
use vibesql_storage::Database;
use vibesql_types::SqlValue;
use crate::{errors::ExecutorError, procedural::ExecutionContext};
pub fn execute_user_function(
func: &Function,
args: &[Expression],
db: &mut Database,
) -> Result<SqlValue, ExecutorError> {
if args.len() != func.parameters.len() {
return Err(ExecutorError::ArgumentCountMismatch {
expected: func.parameters.len(),
actual: args.len(),
});
}
let mut ctx = ExecutionContext::new_function_context();
ctx.enter_recursion().map_err(|e| ExecutorError::RecursionLimitExceeded {
message: e,
call_stack: vec![], max_depth: 100,
})?;
for (param, arg_expr) in func.parameters.iter().zip(args.iter()) {
let value = super::executor::evaluate_expression(arg_expr, db, &ctx)?;
ctx.set_parameter(¶m.name, value);
}
let result = match &func.body {
FunctionBody::RawSql(sql) => {
execute_simple_return(sql, &mut ctx, db)?
}
FunctionBody::BeginEnd(sql) => {
execute_begin_end_body(sql, &mut ctx, db, &func.name)?
}
};
ctx.exit_recursion();
Ok(result)
}
fn execute_simple_return(
sql: &str,
ctx: &mut ExecutionContext,
db: &mut Database,
) -> Result<SqlValue, ExecutorError> {
let sql_trimmed = sql.trim();
let return_upper = "RETURN";
if !sql_trimmed.to_uppercase().starts_with(return_upper) {
return Err(ExecutorError::InvalidFunctionBody(
"Simple function body must start with RETURN".to_string(),
));
}
let expr_str = sql_trimmed[return_upper.len()..].trim();
let select_wrapper = format!("SELECT {}", expr_str);
let stmt = Parser::parse_sql(&select_wrapper).map_err(|e| {
ExecutorError::ParseError(format!("Failed to parse RETURN expression: {}", e))
})?;
#[allow(clippy::collapsible_match)]
if let vibesql_ast::Statement::Select(select_stmt) = stmt {
if let Some(first_item) = select_stmt.select_list.first() {
if let vibesql_ast::SelectItem::Expression { expr, alias: _, .. } = first_item {
let value = super::executor::evaluate_expression(expr, db, ctx)?;
return Ok(value);
}
}
}
Err(ExecutorError::InvalidFunctionBody("Could not parse RETURN expression".to_string()))
}
fn execute_begin_end_body(
_sql: &str,
_ctx: &mut ExecutionContext,
_db: &mut Database,
func_name: &str,
) -> Result<SqlValue, ExecutorError> {
Err(ExecutorError::UnsupportedFeature(format!(
"Complex BEGIN...END function bodies not yet fully supported for function '{}'. \
Use simple RETURN expression functions (e.g., 'RETURN x + 10') instead. \
Full BEGIN...END support requires updating FunctionBody to store parsed statements.",
func_name
)))
}
impl ExecutionContext {
pub fn new_function_context() -> Self {
let mut ctx = Self::new();
ctx.is_function = true;
ctx
}
pub fn is_function_context(&self) -> bool {
self.is_function
}
}
#[cfg(test)]
mod tests {
use vibesql_catalog::FunctionParam;
use vibesql_types::DataType;
use super::*;
#[test]
fn test_simple_return_function() {
let mut db = Database::new();
let func = Function::new(
"add_ten".to_string(),
"public".to_string(),
vec![FunctionParam { name: "x".to_string(), data_type: DataType::Integer }],
DataType::Integer,
FunctionBody::RawSql("RETURN x + 10".to_string()),
);
let args = vec![Expression::Literal(SqlValue::Integer(5))];
let result = execute_user_function(&func, &args, &mut db).unwrap();
assert_eq!(result, SqlValue::Integer(15));
}
#[test]
fn test_argument_count_mismatch() {
let mut db = Database::new();
let func = Function::new(
"add_ten".to_string(),
"public".to_string(),
vec![FunctionParam { name: "x".to_string(), data_type: DataType::Integer }],
DataType::Integer,
FunctionBody::RawSql("RETURN x + 10".to_string()),
);
let args = vec![];
let result = execute_user_function(&func, &args, &mut db);
assert!(matches!(result, Err(ExecutorError::ArgumentCountMismatch { .. })));
}
#[test]
fn test_begin_end_not_supported_yet() {
let mut db = Database::new();
let func = Function::new(
"factorial".to_string(),
"public".to_string(),
vec![FunctionParam { name: "n".to_string(), data_type: DataType::Integer }],
DataType::Integer,
FunctionBody::BeginEnd("BEGIN DECLARE x INT DEFAULT 5; RETURN x; END".to_string()),
);
let args = vec![Expression::Literal(SqlValue::Integer(5))];
let result = execute_user_function(&func, &args, &mut db);
assert!(matches!(result, Err(ExecutorError::UnsupportedFeature(_))));
}
}