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