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