use vibesql_ast::{ProceduralStatement, Statement};
use vibesql_storage::Database;
use vibesql_types::SqlValue;
use crate::{
errors::ExecutorError,
procedural::{ControlFlow, ExecutionContext},
};
pub fn execute_procedural_statement(
stmt: &ProceduralStatement,
ctx: &mut ExecutionContext,
db: &mut Database,
) -> Result<ControlFlow, ExecutorError> {
match stmt {
ProceduralStatement::Declare { name, data_type, default_value } => {
let value = if let Some(expr) = default_value {
evaluate_expression(expr, db, ctx)?
} else {
SqlValue::Null
};
let typed_value = cast_to_type(value, data_type)?;
ctx.set_variable(name, typed_value);
Ok(ControlFlow::Continue)
}
ProceduralStatement::Set { name, value } => {
let new_value = evaluate_expression(value, db, ctx)?;
if let Some(var_name) = name.strip_prefix('@') {
db.set_session_variable(var_name, new_value);
} else if ctx.has_parameter(name) {
if let Some(param) = ctx.get_parameter_mut(name) {
*param = new_value;
}
} else if ctx.has_variable(name) {
ctx.set_variable(name, new_value);
} else {
return Err(ExecutorError::VariableNotFound {
variable_name: name.clone(),
available_variables: ctx.get_available_names(),
});
}
Ok(ControlFlow::Continue)
}
ProceduralStatement::Return(expr) => {
let value = evaluate_expression(expr, db, ctx)?;
Ok(ControlFlow::Return(value))
}
ProceduralStatement::Leave(label) => {
if !ctx.has_label(label) {
return Err(ExecutorError::LabelNotFound(label.clone()));
}
Ok(ControlFlow::Leave(label.clone()))
}
ProceduralStatement::Iterate(label) => {
if !ctx.has_label(label) {
return Err(ExecutorError::LabelNotFound(label.clone()));
}
Ok(ControlFlow::Iterate(label.clone()))
}
ProceduralStatement::If { condition, then_statements, else_statements } => {
super::control_flow::execute_if(condition, then_statements, else_statements, ctx, db)
}
ProceduralStatement::While { condition, statements } => {
super::control_flow::execute_while(condition, statements, ctx, db)
}
ProceduralStatement::Loop { statements } => {
super::control_flow::execute_loop(statements, ctx, db)
}
ProceduralStatement::Repeat { statements, condition } => {
super::control_flow::execute_repeat(statements, condition, ctx, db)
}
ProceduralStatement::Sql(sql_stmt) => {
execute_sql_statement(sql_stmt, db, ctx)?;
Ok(ControlFlow::Continue)
}
}
}
pub fn evaluate_expression(
expr: &vibesql_ast::Expression,
_db: &mut Database,
ctx: &ExecutionContext,
) -> Result<SqlValue, ExecutorError> {
use vibesql_ast::{BinaryOperator, Expression};
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();
if let Some(var_name) = column.strip_prefix('@') {
_db.get_session_variable(var_name).cloned().ok_or_else(|| {
ExecutorError::VariableNotFound {
variable_name: format!("@{}", var_name),
available_variables: vec![], }
})
} else {
ctx.get_value(column).cloned().ok_or_else(|| ExecutorError::VariableNotFound {
variable_name: column.to_string(),
available_variables: ctx.get_available_names(),
})
}
}
Expression::Literal(value) => Ok(value.clone()),
Expression::BinaryOp { left, op, right } => {
let left_val = evaluate_expression(left, _db, ctx)?;
let right_val = evaluate_expression(right, _db, ctx)?;
match op {
BinaryOperator::Plus => {
match (left_val, right_val) {
(SqlValue::Integer(l), SqlValue::Integer(r)) => {
Ok(SqlValue::Integer(l + r))
}
_ => Err(ExecutorError::TypeError(
"Binary operation only supports integers in Phase 2".to_string(),
)),
}
}
BinaryOperator::Minus => match (left_val, right_val) {
(SqlValue::Integer(l), SqlValue::Integer(r)) => Ok(SqlValue::Integer(l - r)),
_ => Err(ExecutorError::TypeError(
"Binary operation only supports integers in Phase 2".to_string(),
)),
},
BinaryOperator::Multiply => match (left_val, right_val) {
(SqlValue::Integer(l), SqlValue::Integer(r)) => Ok(SqlValue::Integer(l * r)),
_ => Err(ExecutorError::TypeError(
"Binary operation only supports integers in Phase 2".to_string(),
)),
},
BinaryOperator::GreaterThan => match (left_val, right_val) {
(SqlValue::Integer(l), SqlValue::Integer(r)) => Ok(SqlValue::Boolean(l > r)),
_ => Err(ExecutorError::TypeError(
"Comparison only supports integers in Phase 2".to_string(),
)),
},
_ => Err(ExecutorError::UnsupportedFeature(format!(
"Binary operator {:?} not yet supported in procedural expressions",
op
))),
}
}
Expression::Function { name, args, .. } if name.eq_ignore_ascii_case("CONCAT") => {
let mut result = String::new();
for arg in args {
let val = evaluate_expression(arg, _db, ctx)?;
result.push_str(&val.to_string());
}
Ok(SqlValue::Varchar(arcstr::ArcStr::from(result)))
}
_ => Err(ExecutorError::UnsupportedFeature(format!(
"Expression type not yet supported in procedures: {:?}",
expr
))),
}
}
fn execute_sql_statement(
stmt: &Statement,
db: &mut Database,
ctx: &mut ExecutionContext,
) -> Result<(), ExecutorError> {
match stmt {
Statement::Select(select_stmt) => {
if let Some(into_vars) = &select_stmt.into_variables {
let executor = crate::SelectExecutor::new_with_procedural_context(db, ctx);
let results = executor.execute(select_stmt)?;
if results.len() != 1 {
return Err(ExecutorError::SelectIntoRowCount {
expected: 1,
actual: results.len(),
});
}
let row = &results[0];
if row.values.len() != into_vars.len() {
return Err(ExecutorError::SelectIntoColumnCount {
expected: into_vars.len(),
actual: row.values.len(),
});
}
for (var_name, value) in into_vars.iter().zip(row.values.iter()) {
ctx.set_variable(var_name, value.clone());
}
Ok(())
} else {
let executor = crate::SelectExecutor::new_with_procedural_context(db, ctx);
let _results = executor.execute(select_stmt)?;
Ok(())
}
}
Statement::Insert(insert_stmt) => {
let _count =
crate::InsertExecutor::execute_with_procedural_context(db, insert_stmt, ctx)?;
Ok(())
}
Statement::Update(update_stmt) => {
let _count =
crate::UpdateExecutor::execute_with_procedural_context(update_stmt, db, ctx)?;
Ok(())
}
Statement::Delete(delete_stmt) => {
let _count =
crate::DeleteExecutor::execute_with_procedural_context(delete_stmt, db, ctx)?;
Ok(())
}
_ => {
Err(ExecutorError::UnsupportedFeature(format!(
"SQL statement type not supported in procedure bodies: {:?}",
stmt
)))
}
}
}
fn cast_to_type(
value: SqlValue,
target_type: &vibesql_types::DataType,
) -> Result<SqlValue, ExecutorError> {
use vibesql_types::DataType;
if matches!(value, SqlValue::Null) {
return Ok(SqlValue::Null);
}
match target_type {
DataType::Integer => match value {
SqlValue::Integer(i) => Ok(SqlValue::Integer(i)),
SqlValue::Bigint(b) => Ok(SqlValue::Integer(b)),
SqlValue::Smallint(s) => Ok(SqlValue::Integer(s as i64)),
SqlValue::Varchar(s) | SqlValue::Character(s) => {
s.parse::<i64>().map(SqlValue::Integer).map_err(|_| {
ExecutorError::TypeError(format!("Cannot convert '{}' to INTEGER", s))
})
}
_ => Err(ExecutorError::TypeError(format!("Cannot convert {:?} to INTEGER", value))),
},
DataType::Varchar { .. } | DataType::Character { .. } => {
Ok(SqlValue::Varchar(arcstr::ArcStr::from(value.to_string())))
}
DataType::Boolean => match value {
SqlValue::Boolean(b) => Ok(SqlValue::Boolean(b)),
SqlValue::Integer(i) => Ok(SqlValue::Boolean(i != 0)),
SqlValue::Varchar(ref s) | SqlValue::Character(ref s) => {
let s_upper = s.to_uppercase();
if s_upper == "TRUE" || s_upper == "T" || s_upper == "1" {
Ok(SqlValue::Boolean(true))
} else if s_upper == "FALSE" || s_upper == "F" || s_upper == "0" {
Ok(SqlValue::Boolean(false))
} else {
Err(ExecutorError::TypeError(format!("Cannot convert '{}' to BOOLEAN", s)))
}
}
_ => Err(ExecutorError::TypeError(format!("Cannot convert {:?} to BOOLEAN", value))),
},
_ => Ok(value),
}
}