use super::*;
use crate::procedural::{executor::execute_procedural_statement, ControlFlow, ExecutionContext};
#[test]
fn test_if_simple_boolean_true() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("result", SqlValue::Varchar(arcstr::ArcStr::from("initial")));
let if_stmt = ProceduralStatement::If {
condition: Box::new(Expression::Literal(SqlValue::Boolean(true))),
then_statements: vec![ProceduralStatement::Set {
name: "result".to_string(),
value: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from(
"executed",
)))),
}],
else_statements: None,
};
let result = execute_procedural_statement(&if_stmt, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(result.unwrap(), ControlFlow::Continue);
assert_eq!(
ctx.get_variable("result"),
Some(&SqlValue::Varchar(arcstr::ArcStr::from("executed")))
);
}
#[test]
fn test_if_simple_boolean_false() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("result", SqlValue::Varchar(arcstr::ArcStr::from("initial")));
let if_stmt = ProceduralStatement::If {
condition: Box::new(Expression::Literal(SqlValue::Boolean(false))),
then_statements: vec![ProceduralStatement::Set {
name: "result".to_string(),
value: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from(
"executed",
)))),
}],
else_statements: None,
};
let result = execute_procedural_statement(&if_stmt, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(result.unwrap(), ControlFlow::Continue);
assert_eq!(
ctx.get_variable("result"),
Some(&SqlValue::Varchar(arcstr::ArcStr::from("initial")))
);
}
#[test]
fn test_if_with_else() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("result", SqlValue::Varchar(arcstr::ArcStr::from("initial")));
let if_stmt = ProceduralStatement::If {
condition: Box::new(Expression::Literal(SqlValue::Boolean(false))),
then_statements: vec![ProceduralStatement::Set {
name: "result".to_string(),
value: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from("then")))),
}],
else_statements: Some(vec![ProceduralStatement::Set {
name: "result".to_string(),
value: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from("else")))),
}]),
};
let result = execute_procedural_statement(&if_stmt, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(result.unwrap(), ControlFlow::Continue);
assert_eq!(ctx.get_variable("result"), Some(&SqlValue::Varchar(arcstr::ArcStr::from("else"))));
}
#[test]
fn test_if_integer_condition() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("result", SqlValue::Varchar(arcstr::ArcStr::from("initial")));
let if_stmt = ProceduralStatement::If {
condition: Box::new(Expression::Literal(SqlValue::Integer(1))),
then_statements: vec![ProceduralStatement::Set {
name: "result".to_string(),
value: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from(
"nonzero",
)))),
}],
else_statements: None,
};
let result = execute_procedural_statement(&if_stmt, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(
ctx.get_variable("result"),
Some(&SqlValue::Varchar(arcstr::ArcStr::from("nonzero")))
);
ctx.set_variable("result", SqlValue::Varchar(arcstr::ArcStr::from("initial")));
let if_stmt_zero = ProceduralStatement::If {
condition: Box::new(Expression::Literal(SqlValue::Integer(0))),
then_statements: vec![ProceduralStatement::Set {
name: "result".to_string(),
value: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from("zero")))),
}],
else_statements: None,
};
let result = execute_procedural_statement(&if_stmt_zero, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(
ctx.get_variable("result"),
Some(&SqlValue::Varchar(arcstr::ArcStr::from("initial")))
);
}
#[test]
fn test_if_null_condition() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("result", SqlValue::Varchar(arcstr::ArcStr::from("initial")));
let if_stmt = ProceduralStatement::If {
condition: Box::new(Expression::Literal(SqlValue::Null)),
then_statements: vec![ProceduralStatement::Set {
name: "result".to_string(),
value: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from(
"executed",
)))),
}],
else_statements: None,
};
let result = execute_procedural_statement(&if_stmt, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(
ctx.get_variable("result"),
Some(&SqlValue::Varchar(arcstr::ArcStr::from("initial")))
);
}
#[test]
fn test_while_loop_basic() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("counter", SqlValue::Integer(0));
let while_stmt = ProceduralStatement::While {
condition: Box::new(Expression::Literal(SqlValue::Boolean(true))),
statements: vec![
ProceduralStatement::Set {
name: "counter".to_string(),
value: Box::new(Expression::Literal(SqlValue::Integer(1))),
},
ProceduralStatement::Leave("".to_string()),
],
};
ctx.push_label("");
let result = execute_procedural_statement(&while_stmt, &mut ctx, &mut db);
ctx.pop_label("");
assert!(result.is_ok());
assert_eq!(ctx.get_variable("counter"), Some(&SqlValue::Integer(1)));
}
#[test]
fn test_while_loop_false_condition() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("counter", SqlValue::Integer(0));
let while_stmt = ProceduralStatement::While {
condition: Box::new(Expression::Literal(SqlValue::Boolean(false))),
statements: vec![ProceduralStatement::Set {
name: "counter".to_string(),
value: Box::new(Expression::Literal(SqlValue::Integer(100))),
}],
};
let result = execute_procedural_statement(&while_stmt, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(result.unwrap(), ControlFlow::Continue);
assert_eq!(ctx.get_variable("counter"), Some(&SqlValue::Integer(0)));
}
#[test]
fn test_loop_with_leave() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("counter", SqlValue::Integer(0));
ctx.push_label("my_loop");
let loop_stmt = ProceduralStatement::Loop {
statements: vec![
ProceduralStatement::Set {
name: "counter".to_string(),
value: Box::new(Expression::Literal(SqlValue::Integer(1))),
},
ProceduralStatement::Leave("my_loop".to_string()),
],
};
let result = execute_procedural_statement(&loop_stmt, &mut ctx, &mut db);
ctx.pop_label("my_loop");
assert!(result.is_ok());
assert_eq!(result.unwrap(), ControlFlow::Continue);
assert_eq!(ctx.get_variable("counter"), Some(&SqlValue::Integer(1)));
}
#[test]
fn test_loop_leave_invalid_label() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
let leave_stmt = ProceduralStatement::Leave("nonexistent_label".to_string());
let result = execute_procedural_statement(&leave_stmt, &mut ctx, &mut db);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ExecutorError::LabelNotFound(_)));
}
#[test]
fn test_repeat_until() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("counter", SqlValue::Integer(0));
let repeat_stmt = ProceduralStatement::Repeat {
statements: vec![ProceduralStatement::Set {
name: "counter".to_string(),
value: Box::new(Expression::Literal(SqlValue::Integer(1))),
}],
condition: Box::new(Expression::Literal(SqlValue::Boolean(true))),
};
let result = execute_procedural_statement(&repeat_stmt, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(result.unwrap(), ControlFlow::Continue);
assert_eq!(ctx.get_variable("counter"), Some(&SqlValue::Integer(1)));
}
#[test]
fn test_repeat_executes_at_least_once() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("executed", SqlValue::Boolean(false));
let repeat_stmt = ProceduralStatement::Repeat {
statements: vec![ProceduralStatement::Set {
name: "executed".to_string(),
value: Box::new(Expression::Literal(SqlValue::Boolean(true))),
}],
condition: Box::new(Expression::Literal(SqlValue::Boolean(true))),
};
let result = execute_procedural_statement(&repeat_stmt, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(ctx.get_variable("executed"), Some(&SqlValue::Boolean(true)));
}
#[test]
fn test_iterate_validates_label() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.push_label("valid_loop");
let iterate_stmt = ProceduralStatement::Iterate("valid_loop".to_string());
let result = execute_procedural_statement(&iterate_stmt, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(result.unwrap(), ControlFlow::Iterate("valid_loop".to_string()));
ctx.pop_label("valid_loop");
}
#[test]
fn test_iterate_invalid_label() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
let iterate_stmt = ProceduralStatement::Iterate("nonexistent_label".to_string());
let result = execute_procedural_statement(&iterate_stmt, &mut ctx, &mut db);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ExecutorError::LabelNotFound(_)));
}
#[test]
fn test_nested_if() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("result", SqlValue::Varchar(arcstr::ArcStr::from("initial")));
let nested_if = ProceduralStatement::If {
condition: Box::new(Expression::Literal(SqlValue::Boolean(true))),
then_statements: vec![ProceduralStatement::If {
condition: Box::new(Expression::Literal(SqlValue::Boolean(true))),
then_statements: vec![ProceduralStatement::Set {
name: "result".to_string(),
value: Box::new(Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from(
"nested",
)))),
}],
else_statements: None,
}],
else_statements: None,
};
let result = execute_procedural_statement(&nested_if, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(
ctx.get_variable("result"),
Some(&SqlValue::Varchar(arcstr::ArcStr::from("nested")))
);
}
#[test]
fn test_if_with_return() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
let if_stmt = ProceduralStatement::If {
condition: Box::new(Expression::Literal(SqlValue::Boolean(true))),
then_statements: vec![ProceduralStatement::Return(Box::new(Expression::Literal(
SqlValue::Integer(42),
)))],
else_statements: None,
};
let result = execute_procedural_statement(&if_stmt, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(result.unwrap(), ControlFlow::Return(SqlValue::Integer(42)));
}
#[test]
fn test_variable_persistence() {
let mut db = setup_test_db();
let mut ctx = ExecutionContext::new();
ctx.set_variable("x", SqlValue::Integer(1));
let if_stmt = ProceduralStatement::If {
condition: Box::new(Expression::Literal(SqlValue::Boolean(true))),
then_statements: vec![ProceduralStatement::Set {
name: "x".to_string(),
value: Box::new(Expression::Literal(SqlValue::Integer(2))),
}],
else_statements: None,
};
let result = execute_procedural_statement(&if_stmt, &mut ctx, &mut db);
assert!(result.is_ok());
assert_eq!(ctx.get_variable("x"), Some(&SqlValue::Integer(2)));
}
#[test]
fn test_procedure_body_caching_performance() {
use std::time::Instant;
let mut db = setup_test_db();
let create_stmt = CreateProcedureStmt {
procedure_name: "test_cache_proc".to_string(),
parameters: vec![
ProcedureParameter {
mode: ParameterMode::In,
name: "x".to_string(),
data_type: DataType::Integer,
},
ProcedureParameter {
mode: ParameterMode::Out,
name: "result".to_string(),
data_type: DataType::Integer,
},
],
body: ProcedureBody::BeginEnd(vec![
ProceduralStatement::Declare {
name: "temp".to_string(),
data_type: DataType::Integer,
default_value: None,
},
ProceduralStatement::Set {
name: "temp".to_string(),
value: Box::new(Expression::BinaryOp {
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"x", false,
))),
op: BinaryOperator::Multiply,
right: Box::new(Expression::Literal(SqlValue::Integer(2))),
}),
},
ProceduralStatement::Set {
name: "result".to_string(),
value: Box::new(Expression::BinaryOp {
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"temp", false,
))),
op: BinaryOperator::Plus,
right: Box::new(Expression::Literal(SqlValue::Integer(10))),
}),
},
]),
sql_security: None,
comment: None,
language: None,
};
advanced_objects::execute_create_procedure(&create_stmt, &mut db).unwrap();
let first_call_start = Instant::now();
let call_stmt = CallStmt {
procedure_name: "test_cache_proc".to_string(),
arguments: vec![
Expression::Literal(SqlValue::Integer(5)),
Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("@out", false)),
],
};
advanced_objects::execute_call(&call_stmt, &mut db).unwrap();
let first_call_duration = first_call_start.elapsed();
assert_eq!(db.get_session_variable("out"), Some(&SqlValue::Integer(20)));
let iterations = 100;
let cached_calls_start = Instant::now();
for i in 0..iterations {
let call_stmt = CallStmt {
procedure_name: "test_cache_proc".to_string(),
arguments: vec![
Expression::Literal(SqlValue::Integer(i)),
Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
&format!("@out{}", i),
false,
)),
],
};
advanced_objects::execute_call(&call_stmt, &mut db).unwrap();
}
let cached_calls_duration = cached_calls_start.elapsed();
let cached_avg = cached_calls_duration / (iterations as u32);
println!("First call: {:?}", first_call_duration);
println!("Average cached call: {:?}", cached_avg);
for i in 0..iterations {
let expected = SqlValue::Integer(i * 2 + 10);
assert_eq!(
db.get_session_variable(&format!("out{}", i)),
Some(&expected),
"Call {} should have correct result",
i
);
}
}
#[test]
fn test_cache_invalidation_on_drop() {
let mut db = setup_test_db();
let create_stmt = CreateProcedureStmt {
procedure_name: "test_invalidate".to_string(),
parameters: vec![ProcedureParameter {
mode: ParameterMode::Out,
name: "result".to_string(),
data_type: DataType::Integer,
}],
body: ProcedureBody::BeginEnd(vec![ProceduralStatement::Set {
name: "result".to_string(),
value: Box::new(Expression::Literal(SqlValue::Integer(42))),
}]),
sql_security: None,
comment: None,
language: None,
};
advanced_objects::execute_create_procedure(&create_stmt, &mut db).unwrap();
let call_stmt = CallStmt {
procedure_name: "test_invalidate".to_string(),
arguments: vec![Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"@out", false,
))],
};
advanced_objects::execute_call(&call_stmt, &mut db).unwrap();
assert_eq!(db.get_session_variable("out"), Some(&SqlValue::Integer(42)));
let drop_stmt =
DropProcedureStmt { procedure_name: "test_invalidate".to_string(), if_exists: false };
advanced_objects::execute_drop_procedure(&drop_stmt, &mut db).unwrap();
assert!(!db.catalog.procedure_exists("test_invalidate"));
let create_stmt2 = CreateProcedureStmt {
procedure_name: "test_invalidate".to_string(),
parameters: vec![ProcedureParameter {
mode: ParameterMode::Out,
name: "result".to_string(),
data_type: DataType::Integer,
}],
body: ProcedureBody::BeginEnd(vec![ProceduralStatement::Set {
name: "result".to_string(),
value: Box::new(Expression::Literal(SqlValue::Integer(99))),
}]),
sql_security: None,
comment: None,
language: None,
};
advanced_objects::execute_create_procedure(&create_stmt2, &mut db).unwrap();
let call_stmt2 = CallStmt {
procedure_name: "test_invalidate".to_string(),
arguments: vec![Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"@out2", false,
))],
};
advanced_objects::execute_call(&call_stmt2, &mut db).unwrap();
assert_eq!(db.get_session_variable("out2"), Some(&SqlValue::Integer(99)));
}