vibesql_executor/procedural/
executor.rs

1//! Execute individual procedural statements
2//!
3//! Handles execution of:
4//! - DECLARE (variable declarations)
5//! - SET (variable assignments)
6//! - SQL statements (SELECT, INSERT, UPDATE, DELETE, etc.)
7//! - RETURN (return from function/procedure)
8//! - Control flow (delegated to control_flow module)
9
10use vibesql_ast::{ProceduralStatement, Statement};
11use vibesql_storage::Database;
12use vibesql_types::SqlValue;
13
14use crate::{
15    errors::ExecutorError,
16    procedural::{ControlFlow, ExecutionContext},
17};
18
19/// Execute a procedural statement
20pub fn execute_procedural_statement(
21    stmt: &ProceduralStatement,
22    ctx: &mut ExecutionContext,
23    db: &mut Database,
24) -> Result<ControlFlow, ExecutorError> {
25    match stmt {
26        ProceduralStatement::Declare { name, data_type, default_value } => {
27            // Declare a local variable
28            let value = if let Some(expr) = default_value {
29                // Evaluate default value expression
30                evaluate_expression(expr, db, ctx)?
31            } else {
32                // No default, use NULL
33                SqlValue::Null
34            };
35
36            // Store variable with type checking
37            let typed_value = cast_to_type(value, data_type)?;
38            ctx.set_variable(name, typed_value);
39            Ok(ControlFlow::Continue)
40        }
41
42        ProceduralStatement::Set { name, value } => {
43            // Set variable, parameter, or session variable value
44            let new_value = evaluate_expression(value, db, ctx)?;
45
46            // Check if it's a session variable (starts with @)
47            if let Some(var_name) = name.strip_prefix('@') {
48                // Strip @ prefix
49                db.set_session_variable(var_name, new_value);
50            } else if ctx.has_parameter(name) {
51                // Try to update parameter first (for OUT/INOUT)
52                if let Some(param) = ctx.get_parameter_mut(name) {
53                    *param = new_value;
54                }
55            } else if ctx.has_variable(name) {
56                // Update local variable
57                ctx.set_variable(name, new_value);
58            } else {
59                return Err(ExecutorError::VariableNotFound {
60                    variable_name: name.clone(),
61                    available_variables: ctx.get_available_names(),
62                });
63            }
64
65            Ok(ControlFlow::Continue)
66        }
67
68        ProceduralStatement::Return(expr) => {
69            // Evaluate and return the expression
70            let value = evaluate_expression(expr, db, ctx)?;
71            Ok(ControlFlow::Return(value))
72        }
73
74        ProceduralStatement::Leave(label) => {
75            // Check if label exists
76            if !ctx.has_label(label) {
77                return Err(ExecutorError::LabelNotFound(label.clone()));
78            }
79            Ok(ControlFlow::Leave(label.clone()))
80        }
81
82        ProceduralStatement::Iterate(label) => {
83            // Check if label exists
84            if !ctx.has_label(label) {
85                return Err(ExecutorError::LabelNotFound(label.clone()));
86            }
87            Ok(ControlFlow::Iterate(label.clone()))
88        }
89
90        ProceduralStatement::If { condition, then_statements, else_statements } => {
91            super::control_flow::execute_if(condition, then_statements, else_statements, ctx, db)
92        }
93
94        ProceduralStatement::While { condition, statements } => {
95            super::control_flow::execute_while(condition, statements, ctx, db)
96        }
97
98        ProceduralStatement::Loop { statements } => {
99            super::control_flow::execute_loop(statements, ctx, db)
100        }
101
102        ProceduralStatement::Repeat { statements, condition } => {
103            super::control_flow::execute_repeat(statements, condition, ctx, db)
104        }
105
106        ProceduralStatement::Sql(sql_stmt) => {
107            // Execute SQL statement
108            execute_sql_statement(sql_stmt, db, ctx)?;
109            Ok(ControlFlow::Continue)
110        }
111    }
112}
113
114/// Evaluate an expression in the procedural context
115///
116/// This function evaluates expressions with access to local variables and parameters.
117/// Phase 2 supports simple expressions with variable references and basic operations.
118pub fn evaluate_expression(
119    expr: &vibesql_ast::Expression,
120    _db: &mut Database,
121    ctx: &ExecutionContext,
122) -> Result<SqlValue, ExecutorError> {
123    use vibesql_ast::{BinaryOperator, Expression};
124
125    match expr {
126        // Variable, parameter, or session variable reference
127        Expression::ColumnRef(col_id)
128            if col_id.schema_canonical().is_none() && col_id.table_canonical().is_none() =>
129        {
130            let column = col_id.column_canonical();
131            // Check if it's a session variable (starts with @)
132            if let Some(var_name) = column.strip_prefix('@') {
133                // Strip @ prefix
134                _db.get_session_variable(var_name).cloned().ok_or_else(|| {
135                    ExecutorError::VariableNotFound {
136                        variable_name: format!("@{}", var_name),
137                        available_variables: vec![], // Session variables not listed
138                    }
139                })
140            } else {
141                // Regular variable or parameter reference
142                ctx.get_value(column).cloned().ok_or_else(|| ExecutorError::VariableNotFound {
143                    variable_name: column.to_string(),
144                    available_variables: ctx.get_available_names(),
145                })
146            }
147        }
148
149        // Literal values
150        Expression::Literal(value) => Ok(value.clone()),
151
152        // Binary operations (basic arithmetic and comparison)
153        Expression::BinaryOp { left, op, right } => {
154            let left_val = evaluate_expression(left, _db, ctx)?;
155            let right_val = evaluate_expression(right, _db, ctx)?;
156
157            match op {
158                BinaryOperator::Plus => {
159                    // Simple addition for integers
160                    match (left_val, right_val) {
161                        (SqlValue::Integer(l), SqlValue::Integer(r)) => {
162                            Ok(SqlValue::Integer(l + r))
163                        }
164                        _ => Err(ExecutorError::TypeError(
165                            "Binary operation only supports integers in Phase 2".to_string(),
166                        )),
167                    }
168                }
169                BinaryOperator::Minus => match (left_val, right_val) {
170                    (SqlValue::Integer(l), SqlValue::Integer(r)) => Ok(SqlValue::Integer(l - r)),
171                    _ => Err(ExecutorError::TypeError(
172                        "Binary operation only supports integers in Phase 2".to_string(),
173                    )),
174                },
175                BinaryOperator::Multiply => match (left_val, right_val) {
176                    (SqlValue::Integer(l), SqlValue::Integer(r)) => Ok(SqlValue::Integer(l * r)),
177                    _ => Err(ExecutorError::TypeError(
178                        "Binary operation only supports integers in Phase 2".to_string(),
179                    )),
180                },
181                BinaryOperator::GreaterThan => match (left_val, right_val) {
182                    (SqlValue::Integer(l), SqlValue::Integer(r)) => Ok(SqlValue::Boolean(l > r)),
183                    _ => Err(ExecutorError::TypeError(
184                        "Comparison only supports integers in Phase 2".to_string(),
185                    )),
186                },
187                _ => Err(ExecutorError::UnsupportedFeature(format!(
188                    "Binary operator {:?} not yet supported in procedural expressions",
189                    op
190                ))),
191            }
192        }
193
194        // Function calls - basic support for CONCAT
195        Expression::Function { name, args, .. } if name.eq_ignore_ascii_case("CONCAT") => {
196            let mut result = String::new();
197            for arg in args {
198                let val = evaluate_expression(arg, _db, ctx)?;
199                result.push_str(&val.to_string());
200            }
201            Ok(SqlValue::Varchar(arcstr::ArcStr::from(result)))
202        }
203
204        // Other expressions not yet supported
205        _ => Err(ExecutorError::UnsupportedFeature(format!(
206            "Expression type not yet supported in procedures: {:?}",
207            expr
208        ))),
209    }
210}
211
212/// Execute a SQL statement within a procedural context
213///
214/// **Phase 3 Implementation**
215///
216/// This function executes SQL statements with access to procedural variables and parameters.
217/// Supports:
218/// - SELECT with procedural context (results discarded)
219/// - Procedural SELECT INTO (results stored in variables)
220/// - INSERT/UPDATE/DELETE with procedural variables (requires PR #1565)
221fn execute_sql_statement(
222    stmt: &Statement,
223    db: &mut Database,
224    ctx: &mut ExecutionContext,
225) -> Result<(), ExecutorError> {
226    match stmt {
227        Statement::Select(select_stmt) => {
228            // Check if this is procedural SELECT INTO (storing results in variables)
229            if let Some(into_vars) = &select_stmt.into_variables {
230                // Execute SELECT with procedural context
231                let executor = crate::SelectExecutor::new_with_procedural_context(db, ctx);
232                let results = executor.execute(select_stmt)?;
233
234                // Validate exactly one row returned
235                if results.len() != 1 {
236                    return Err(ExecutorError::SelectIntoRowCount {
237                        expected: 1,
238                        actual: results.len(),
239                    });
240                }
241
242                // Get the single row
243                let row = &results[0];
244
245                // Validate column count matches variable count
246                if row.values.len() != into_vars.len() {
247                    return Err(ExecutorError::SelectIntoColumnCount {
248                        expected: into_vars.len(),
249                        actual: row.values.len(),
250                    });
251                }
252
253                // Store results in procedural variables
254                for (var_name, value) in into_vars.iter().zip(row.values.iter()) {
255                    ctx.set_variable(var_name, value.clone());
256                }
257
258                Ok(())
259            } else {
260                // Regular SELECT (discard results)
261                let executor = crate::SelectExecutor::new_with_procedural_context(db, ctx);
262                let _results = executor.execute(select_stmt)?;
263                Ok(())
264            }
265        }
266        Statement::Insert(insert_stmt) => {
267            // Execute INSERT with procedural context
268            let _count =
269                crate::InsertExecutor::execute_with_procedural_context(db, insert_stmt, ctx)?;
270            Ok(())
271        }
272        Statement::Update(update_stmt) => {
273            // Execute UPDATE with procedural context
274            let _count =
275                crate::UpdateExecutor::execute_with_procedural_context(update_stmt, db, ctx)?;
276            Ok(())
277        }
278        Statement::Delete(delete_stmt) => {
279            // Execute DELETE with procedural context
280            let _count =
281                crate::DeleteExecutor::execute_with_procedural_context(delete_stmt, db, ctx)?;
282            Ok(())
283        }
284        _ => {
285            // Other SQL statements (DDL, transactions, etc.) are not supported in procedures
286            Err(ExecutorError::UnsupportedFeature(format!(
287                "SQL statement type not supported in procedure bodies: {:?}",
288                stmt
289            )))
290        }
291    }
292}
293
294/// Cast a value to a specific data type
295fn cast_to_type(
296    value: SqlValue,
297    target_type: &vibesql_types::DataType,
298) -> Result<SqlValue, ExecutorError> {
299    use vibesql_types::DataType;
300
301    // If value is already NULL, return NULL regardless of target type
302    if matches!(value, SqlValue::Null) {
303        return Ok(SqlValue::Null);
304    }
305
306    match target_type {
307        DataType::Integer => match value {
308            SqlValue::Integer(i) => Ok(SqlValue::Integer(i)),
309            SqlValue::Bigint(b) => Ok(SqlValue::Integer(b)),
310            SqlValue::Smallint(s) => Ok(SqlValue::Integer(s as i64)),
311            SqlValue::Varchar(s) | SqlValue::Character(s) => {
312                s.parse::<i64>().map(SqlValue::Integer).map_err(|_| {
313                    ExecutorError::TypeError(format!("Cannot convert '{}' to INTEGER", s))
314                })
315            }
316            _ => Err(ExecutorError::TypeError(format!("Cannot convert {:?} to INTEGER", value))),
317        },
318
319        DataType::Varchar { .. } | DataType::Character { .. } => {
320            // Convert to string
321            Ok(SqlValue::Varchar(arcstr::ArcStr::from(value.to_string())))
322        }
323
324        DataType::Boolean => match value {
325            SqlValue::Boolean(b) => Ok(SqlValue::Boolean(b)),
326            SqlValue::Integer(i) => Ok(SqlValue::Boolean(i != 0)),
327            SqlValue::Varchar(ref s) | SqlValue::Character(ref s) => {
328                let s_upper = s.to_uppercase();
329                if s_upper == "TRUE" || s_upper == "T" || s_upper == "1" {
330                    Ok(SqlValue::Boolean(true))
331                } else if s_upper == "FALSE" || s_upper == "F" || s_upper == "0" {
332                    Ok(SqlValue::Boolean(false))
333                } else {
334                    Err(ExecutorError::TypeError(format!("Cannot convert '{}' to BOOLEAN", s)))
335                }
336            }
337            _ => Err(ExecutorError::TypeError(format!("Cannot convert {:?} to BOOLEAN", value))),
338        },
339
340        // For other types, accept the value as-is for now
341        _ => Ok(value),
342    }
343}
344
345// TODO: Add comprehensive integration tests for SELECT with procedural variables
346// These tests would verify:
347// 1. SELECT with procedural variables in WHERE clause
348// 2. SELECT with procedural variables in SELECT list
349// 3. SELECT with procedural parameters (IN/OUT/INOUT)
350// 4. Nested procedures with variable scoping
351//
352// For now, the implementation can be verified by:
353// - Build succeeds (procedural_context field is properly threaded)
354// - Existing procedural tests pass (no regressions)
355// - Manual testing with procedures containing SELECT statements