use std::collections::HashMap;
use vibesql_types::SqlValue;
const MAX_RECURSION_DEPTH: usize = 100;
#[derive(Debug, Clone, PartialEq)]
pub enum ControlFlow {
Continue,
Return(SqlValue),
Leave(String),
Iterate(String),
}
#[derive(Debug, Clone)]
pub struct ExecutionContext {
variables: HashMap<String, SqlValue>,
parameters: HashMap<String, SqlValue>,
labels: HashMap<String, bool>,
recursion_depth: usize,
max_recursion: usize,
pub(crate) is_function: bool,
out_parameters: HashMap<String, String>,
}
impl ExecutionContext {
pub fn new() -> Self {
Self {
variables: HashMap::new(),
parameters: HashMap::new(),
labels: HashMap::new(),
recursion_depth: 0,
max_recursion: MAX_RECURSION_DEPTH,
is_function: false,
out_parameters: HashMap::new(),
}
}
pub fn with_recursion_depth(depth: usize) -> Self {
Self {
variables: HashMap::new(),
parameters: HashMap::new(),
labels: HashMap::new(),
recursion_depth: depth,
max_recursion: MAX_RECURSION_DEPTH,
is_function: false,
out_parameters: HashMap::new(),
}
}
pub fn set_variable(&mut self, name: &str, value: SqlValue) {
self.variables.insert(name.to_uppercase(), value);
}
pub fn get_variable(&self, name: &str) -> Option<&SqlValue> {
self.variables.get(&name.to_uppercase())
}
pub fn has_variable(&self, name: &str) -> bool {
self.variables.contains_key(&name.to_uppercase())
}
pub fn set_parameter(&mut self, name: &str, value: SqlValue) {
self.parameters.insert(name.to_uppercase(), value);
}
pub fn get_parameter(&self, name: &str) -> Option<&SqlValue> {
self.parameters.get(&name.to_uppercase())
}
pub fn get_parameter_mut(&mut self, name: &str) -> Option<&mut SqlValue> {
self.parameters.get_mut(&name.to_uppercase())
}
pub fn has_parameter(&self, name: &str) -> bool {
self.parameters.contains_key(&name.to_uppercase())
}
pub fn get_value(&self, name: &str) -> Option<&SqlValue> {
self.get_variable(name).or_else(|| self.get_parameter(name))
}
pub fn push_label(&mut self, label: &str) {
self.labels.insert(label.to_uppercase(), true);
}
pub fn pop_label(&mut self, label: &str) {
self.labels.remove(&label.to_uppercase());
}
pub fn has_label(&self, label: &str) -> bool {
self.labels.contains_key(&label.to_uppercase())
}
pub fn enter_recursion(&mut self) -> Result<(), String> {
self.recursion_depth += 1;
if self.recursion_depth > self.max_recursion {
Err(format!("Maximum recursion depth ({}) exceeded", self.max_recursion))
} else {
Ok(())
}
}
pub fn exit_recursion(&mut self) {
if self.recursion_depth > 0 {
self.recursion_depth -= 1;
}
}
pub fn recursion_depth(&self) -> usize {
self.recursion_depth
}
pub fn get_all_parameters(&self) -> &HashMap<String, SqlValue> {
&self.parameters
}
pub fn register_out_parameter(&mut self, param_name: &str, target_var_name: String) {
self.out_parameters.insert(param_name.to_uppercase(), target_var_name);
}
pub fn get_out_parameters(&self) -> &HashMap<String, String> {
&self.out_parameters
}
pub fn get_available_names(&self) -> Vec<String> {
let mut names: Vec<String> =
self.variables.keys().chain(self.parameters.keys()).cloned().collect();
names.sort();
names
}
}
impl Default for ExecutionContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_variable_storage() {
let mut ctx = ExecutionContext::new();
ctx.set_variable("x", SqlValue::Integer(42));
assert_eq!(ctx.get_variable("x"), Some(&SqlValue::Integer(42)));
assert_eq!(ctx.get_variable("X"), Some(&SqlValue::Integer(42))); assert!(ctx.has_variable("x"));
}
#[test]
fn test_parameter_storage() {
let mut ctx = ExecutionContext::new();
ctx.set_parameter("param1", SqlValue::Integer(100));
assert_eq!(ctx.get_parameter("param1"), Some(&SqlValue::Integer(100)));
assert!(ctx.has_parameter("PARAM1"));
}
#[test]
fn test_get_value_precedence() {
let mut ctx = ExecutionContext::new();
ctx.set_parameter("x", SqlValue::Integer(1));
ctx.set_variable("x", SqlValue::Integer(2));
assert_eq!(ctx.get_value("x"), Some(&SqlValue::Integer(2)));
}
#[test]
fn test_label_management() {
let mut ctx = ExecutionContext::new();
ctx.push_label("loop1");
assert!(ctx.has_label("loop1"));
assert!(ctx.has_label("LOOP1"));
ctx.pop_label("loop1");
assert!(!ctx.has_label("loop1"));
}
#[test]
fn test_recursion_limit() {
let mut ctx = ExecutionContext::new();
for _ in 0..100 {
assert!(ctx.enter_recursion().is_ok());
}
assert!(ctx.enter_recursion().is_err());
}
}