use vibesql_ast::*;
use vibesql_storage::Database;
use crate::errors::ExecutorError;
pub fn execute_create_procedure(
stmt: &CreateProcedureStmt,
db: &mut Database,
) -> Result<(), ExecutorError> {
use vibesql_catalog::{ParameterMode, Procedure, ProcedureBody, ProcedureParam, SqlSecurity};
let catalog_params = stmt
.parameters
.iter()
.map(|param| {
let mode = match param.mode {
vibesql_ast::ParameterMode::In => ParameterMode::In,
vibesql_ast::ParameterMode::Out => ParameterMode::Out,
vibesql_ast::ParameterMode::InOut => ParameterMode::InOut,
};
ProcedureParam { mode, name: param.name.clone(), data_type: param.data_type.clone() }
})
.collect();
let catalog_body = match &stmt.body {
vibesql_ast::ProcedureBody::BeginEnd(statements) => {
ProcedureBody::BeginEnd(statements.clone())
}
vibesql_ast::ProcedureBody::RawSql(sql) => ProcedureBody::RawSql(sql.clone()),
};
let sql_security = stmt
.sql_security
.as_ref()
.map(|sec| match sec {
vibesql_ast::SqlSecurity::Definer => SqlSecurity::Definer,
vibesql_ast::SqlSecurity::Invoker => SqlSecurity::Invoker,
})
.unwrap_or(SqlSecurity::Definer);
let procedure =
if stmt.sql_security.is_some() || stmt.comment.is_some() || stmt.language.is_some() {
Procedure::with_characteristics(
stmt.procedure_name.clone(),
db.catalog.get_current_schema().to_string(),
catalog_params,
catalog_body,
sql_security,
stmt.comment.clone(),
stmt.language.clone().unwrap_or_else(|| "SQL".to_string()),
)
} else {
Procedure::new(
stmt.procedure_name.clone(),
db.catalog.get_current_schema().to_string(),
catalog_params,
catalog_body,
)
};
db.catalog.create_procedure_with_characteristics(procedure)?;
Ok(())
}
pub fn execute_drop_procedure(
stmt: &DropProcedureStmt,
db: &mut Database,
) -> Result<(), ExecutorError> {
let procedure_exists = db.catalog.procedure_exists(&stmt.procedure_name);
if stmt.if_exists && !procedure_exists {
return Ok(());
}
db.catalog.drop_procedure(&stmt.procedure_name)?;
Ok(())
}
fn extract_variable_name(expr: &Expression) -> Result<String, ExecutorError> {
match expr {
Expression::ColumnRef(col_id) if col_id.schema_canonical().is_none() && col_id.table_canonical().is_none() => {
let column = col_id.column_canonical();
let var_name = if let Some(stripped) = column.strip_prefix('@') {
stripped.to_string()
} else {
column.to_string()
};
Ok(var_name)
}
Expression::ColumnRef(col_id) if col_id.schema_canonical().is_none() && col_id.table_canonical().is_some() => {
Err(ExecutorError::Other(
"OUT/INOUT parameter target must be a session variable (e.g., @var_name), not a table.column reference".to_string()
))
}
Expression::Literal(_) => {
Err(ExecutorError::Other(
"OUT/INOUT parameter target cannot be a literal value".to_string()
))
}
_ => {
Err(ExecutorError::Other(
"OUT/INOUT parameter target must be a session variable (e.g., @var_name), not a complex expression".to_string()
))
}
}
}
pub fn execute_call(stmt: &CallStmt, db: &mut Database) -> Result<(), ExecutorError> {
use crate::procedural::{execute_procedural_statement, ControlFlow, ExecutionContext};
let (parameters, body) = {
let procedure = db.catalog.get_procedure(&stmt.procedure_name);
match procedure {
Some(proc) => (proc.parameters.clone(), proc.body.clone()),
None => {
let schema_name = db.catalog.get_current_schema().to_string();
let available_procedures = db.catalog.list_procedures();
return Err(ExecutorError::ProcedureNotFound {
procedure_name: stmt.procedure_name.clone(),
schema_name,
available_procedures,
});
}
}
};
if stmt.arguments.len() != parameters.len() {
let param_sig = parameters
.iter()
.map(|p| {
let mode = match p.mode {
vibesql_catalog::ParameterMode::In => "IN",
vibesql_catalog::ParameterMode::Out => "OUT",
vibesql_catalog::ParameterMode::InOut => "INOUT",
};
format!("{} {} {:?}", mode, p.name, p.data_type)
})
.collect::<Vec<_>>()
.join(", ");
return Err(ExecutorError::ParameterCountMismatch {
routine_name: stmt.procedure_name.clone(),
routine_type: "Procedure".to_string(),
expected: parameters.len(),
actual: stmt.arguments.len(),
parameter_signature: param_sig,
});
}
let mut ctx = ExecutionContext::new();
for (param, arg_expr) in parameters.iter().zip(&stmt.arguments) {
match param.mode {
vibesql_catalog::ParameterMode::In => {
let value = crate::procedural::executor::evaluate_expression(arg_expr, db, &ctx)?;
ctx.set_parameter(¶m.name, value);
}
vibesql_catalog::ParameterMode::Out => {
ctx.set_parameter(¶m.name, vibesql_types::SqlValue::Null);
let var_name = extract_variable_name(arg_expr)?;
ctx.register_out_parameter(¶m.name, var_name);
}
vibesql_catalog::ParameterMode::InOut => {
let value = crate::procedural::executor::evaluate_expression(arg_expr, db, &ctx)?;
ctx.set_parameter(¶m.name, value);
let var_name = extract_variable_name(arg_expr)?;
ctx.register_out_parameter(¶m.name, var_name);
}
}
}
match &body {
vibesql_catalog::ProcedureBody::BeginEnd(statements) => {
for stmt in statements {
match execute_procedural_statement(stmt, &mut ctx, db)? {
ControlFlow::Continue => {
}
ControlFlow::Return(_) => {
break;
}
ControlFlow::Leave(_) | ControlFlow::Iterate(_) => {
return Err(ExecutorError::UnsupportedFeature(
"Control flow (LEAVE/ITERATE) not yet supported in Phase 2".to_string(),
));
}
}
}
Ok(())
}
vibesql_catalog::ProcedureBody::RawSql(_) => {
Err(ExecutorError::UnsupportedFeature(
"RawSql procedure bodies require parsing (use BEGIN/END block instead)".to_string(),
))
}
}?;
for (param_name, target_var_name) in ctx.get_out_parameters() {
let value = ctx
.get_parameter(param_name)
.cloned()
.ok_or_else(|| ExecutorError::Other(format!("Parameter '{}' not found", param_name)))?;
db.set_session_variable(target_var_name, value);
}
Ok(())
}