vibesql_executor/pipeline/
context.rs

1//! Execution Context
2//!
3//! This module provides the `ExecutionContext` struct that bundles all the context
4//! needed for query execution, eliminating the need for multiple evaluator constructor
5//! variants throughout the codebase.
6
7use std::collections::HashMap;
8
9use crate::{
10    evaluator::CombinedExpressionEvaluator, procedural, schema::CombinedSchema,
11    select::cte::CteResult,
12};
13
14/// Execution context that bundles all information needed for query execution.
15///
16/// This struct consolidates the various optional contexts (outer row, procedural,
17/// CTE, windows) that were previously passed separately, reducing the number of
18/// evaluator constructor variants needed.
19///
20/// # Example
21///
22/// ```text
23/// let ctx = ExecutionContext::new(schema, database)
24///     .with_cte_context(cte_results)
25///     .with_outer_context(outer_row, outer_schema);
26///
27/// let evaluator = ctx.create_evaluator();
28/// ```
29pub struct ExecutionContext<'a> {
30    /// The combined schema for column resolution
31    pub schema: &'a CombinedSchema,
32    /// Database reference for table access and subqueries
33    pub database: &'a vibesql_storage::Database,
34    /// Optional outer row for correlated subqueries
35    pub outer_row: Option<&'a vibesql_storage::Row>,
36    /// Optional outer schema for correlated subqueries
37    pub outer_schema: Option<&'a CombinedSchema>,
38    /// Optional procedural context for stored procedures/functions
39    pub procedural_context: Option<&'a procedural::ExecutionContext>,
40    /// Optional CTE context for WITH clause results
41    pub cte_context: Option<&'a HashMap<String, CteResult>>,
42    /// Optional window function mapping
43    pub window_mapping: Option<&'a HashMap<crate::select::WindowFunctionKey, usize>>,
44}
45
46impl<'a> ExecutionContext<'a> {
47    /// Create a new execution context with required parameters.
48    ///
49    /// # Arguments
50    /// * `schema` - The combined schema for column resolution
51    /// * `database` - Database reference for table access
52    pub fn new(schema: &'a CombinedSchema, database: &'a vibesql_storage::Database) -> Self {
53        Self {
54            schema,
55            database,
56            outer_row: None,
57            outer_schema: None,
58            procedural_context: None,
59            cte_context: None,
60            window_mapping: None,
61        }
62    }
63
64    /// Add outer context for correlated subqueries.
65    ///
66    /// # Arguments
67    /// * `outer_row` - The current row from the outer query
68    /// * `outer_schema` - The schema of the outer query
69    #[must_use]
70    pub fn with_outer_context(
71        mut self,
72        outer_row: &'a vibesql_storage::Row,
73        outer_schema: &'a CombinedSchema,
74    ) -> Self {
75        self.outer_row = Some(outer_row);
76        self.outer_schema = Some(outer_schema);
77        self
78    }
79
80    /// Add procedural context for stored procedures/functions.
81    ///
82    /// # Arguments
83    /// * `proc_ctx` - The procedural execution context
84    #[must_use]
85    pub fn with_procedural_context(mut self, proc_ctx: &'a procedural::ExecutionContext) -> Self {
86        self.procedural_context = Some(proc_ctx);
87        self
88    }
89
90    /// Add CTE context for WITH clause results.
91    ///
92    /// # Arguments
93    /// * `cte_ctx` - Map of CTE names to their results
94    #[must_use]
95    pub fn with_cte_context(mut self, cte_ctx: &'a HashMap<String, CteResult>) -> Self {
96        self.cte_context = Some(cte_ctx);
97        self
98    }
99
100    /// Add window function mapping.
101    ///
102    /// # Arguments
103    /// * `window_mapping` - Map of window function keys to result column indices
104    #[must_use]
105    pub fn with_window_mapping(
106        mut self,
107        window_mapping: &'a HashMap<crate::select::WindowFunctionKey, usize>,
108    ) -> Self {
109        self.window_mapping = Some(window_mapping);
110        self
111    }
112
113    /// Create a CombinedExpressionEvaluator with all the context from this struct.
114    ///
115    /// This replaces the many constructor variants like:
116    /// - `with_database()`
117    /// - `with_database_and_cte()`
118    /// - `with_database_and_outer_context()`
119    /// - `with_database_and_outer_context_and_cte()`
120    /// - `with_database_and_procedural_context()`
121    /// - `with_database_and_procedural_context_and_cte()`
122    /// - `with_database_and_windows()`
123    /// - `with_database_and_windows_and_cte()`
124    ///
125    /// By using a builder pattern, we consolidate all 8+ variants into a single method.
126    pub fn create_evaluator(&self) -> CombinedExpressionEvaluator<'a> {
127        // Use the most complete constructor and set optional fields
128        // We match on the combination of optional fields to call the right constructor
129        // This is temporary until we refactor CombinedExpressionEvaluator itself
130        match (
131            self.outer_row,
132            self.outer_schema,
133            self.procedural_context,
134            self.cte_context,
135            self.window_mapping,
136        ) {
137            // With outer context and CTE
138            (Some(outer_row), Some(outer_schema), None, Some(cte_ctx), None) => {
139                CombinedExpressionEvaluator::with_database_and_outer_context_and_cte(
140                    self.schema,
141                    self.database,
142                    outer_row,
143                    outer_schema,
144                    cte_ctx,
145                )
146            }
147            // With outer context only
148            (Some(outer_row), Some(outer_schema), None, None, None) => {
149                CombinedExpressionEvaluator::with_database_and_outer_context(
150                    self.schema,
151                    self.database,
152                    outer_row,
153                    outer_schema,
154                )
155            }
156            // With procedural context and CTE
157            (None, None, Some(proc_ctx), Some(cte_ctx), None) => {
158                CombinedExpressionEvaluator::with_database_and_procedural_context_and_cte(
159                    self.schema,
160                    self.database,
161                    proc_ctx,
162                    cte_ctx,
163                )
164            }
165            // With procedural context only
166            (None, None, Some(proc_ctx), None, None) => {
167                CombinedExpressionEvaluator::with_database_and_procedural_context(
168                    self.schema,
169                    self.database,
170                    proc_ctx,
171                )
172            }
173            // With CTE context only
174            (None, None, None, Some(cte_ctx), None) => {
175                CombinedExpressionEvaluator::with_database_and_cte(
176                    self.schema,
177                    self.database,
178                    cte_ctx,
179                )
180            }
181            // With window mapping and CTE
182            (None, None, None, Some(cte_ctx), Some(window_map)) => {
183                CombinedExpressionEvaluator::with_database_and_windows_and_cte(
184                    self.schema,
185                    self.database,
186                    window_map,
187                    cte_ctx,
188                )
189            }
190            // With window mapping only
191            (None, None, None, None, Some(window_map)) => {
192                CombinedExpressionEvaluator::with_database_and_windows(
193                    self.schema,
194                    self.database,
195                    window_map,
196                )
197            }
198            // Base case: just database
199            (None, None, None, None, None) => {
200                CombinedExpressionEvaluator::with_database(self.schema, self.database)
201            }
202            // Unsupported combinations fall back to base
203            // Note: outer + procedural is not a valid combination in current usage
204            _ => CombinedExpressionEvaluator::with_database(self.schema, self.database),
205        }
206    }
207
208    /// Check if this context has outer context (for correlated subqueries).
209    #[inline]
210    pub fn has_outer_context(&self) -> bool {
211        self.outer_row.is_some() && self.outer_schema.is_some()
212    }
213
214    /// Check if this context has CTE context.
215    #[inline]
216    pub fn has_cte_context(&self) -> bool {
217        self.cte_context.is_some()
218    }
219
220    /// Check if this context has procedural context.
221    #[inline]
222    pub fn has_procedural_context(&self) -> bool {
223        self.procedural_context.is_some()
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use crate::schema::CombinedSchema;
231    use vibesql_catalog::TableSchema;
232
233    /// Builder for creating ExecutionContext from SelectExecutor fields.
234    ///
235    /// This helper extracts common context-building logic that's repeated
236    /// across multiple execution methods.
237    struct ExecutionContextBuilder<'a> {
238        schema: &'a CombinedSchema,
239        database: &'a vibesql_storage::Database,
240        outer_row: Option<&'a vibesql_storage::Row>,
241        outer_schema: Option<&'a CombinedSchema>,
242        procedural_context: Option<&'a procedural::ExecutionContext>,
243        cte_context: Option<&'a HashMap<String, CteResult>>,
244        executor_cte_context: Option<&'a HashMap<String, CteResult>>,
245    }
246
247    impl<'a> ExecutionContextBuilder<'a> {
248        /// Create a new builder with required parameters.
249        fn new(schema: &'a CombinedSchema, database: &'a vibesql_storage::Database) -> Self {
250            Self {
251                schema,
252                database,
253                outer_row: None,
254                outer_schema: None,
255                procedural_context: None,
256                cte_context: None,
257                executor_cte_context: None,
258            }
259        }
260
261        /// Set CTE context from query-level CTEs.
262        #[must_use]
263        fn cte_from_query(mut self, cte_results: &'a HashMap<String, CteResult>) -> Self {
264            if !cte_results.is_empty() {
265                self.cte_context = Some(cte_results);
266            }
267            self
268        }
269
270        /// Build the ExecutionContext.
271        ///
272        /// CTE context priority: query-level CTEs take precedence over executor-level.
273        fn build(self) -> ExecutionContext<'a> {
274            let mut ctx = ExecutionContext::new(self.schema, self.database);
275
276            // Set outer context if both row and schema are present
277            if let (Some(outer_row), Some(outer_schema)) = (self.outer_row, self.outer_schema) {
278                ctx = ctx.with_outer_context(outer_row, outer_schema);
279            }
280
281            // Set procedural context if present
282            if let Some(proc_ctx) = self.procedural_context {
283                ctx = ctx.with_procedural_context(proc_ctx);
284            }
285
286            // Set CTE context (query-level takes precedence)
287            if let Some(cte_ctx) = self.cte_context {
288                ctx = ctx.with_cte_context(cte_ctx);
289            } else if let Some(executor_cte) = self.executor_cte_context {
290                ctx = ctx.with_cte_context(executor_cte);
291            }
292
293            ctx
294        }
295    }
296
297    fn create_test_schema() -> CombinedSchema {
298        let table_schema = TableSchema::new("test".to_string(), vec![]);
299        CombinedSchema::from_table("test".to_string(), table_schema)
300    }
301
302    #[test]
303    fn test_execution_context_builder_basic() {
304        let schema = create_test_schema();
305        let database = vibesql_storage::Database::new();
306
307        let ctx = ExecutionContext::new(&schema, &database);
308
309        assert!(!ctx.has_outer_context());
310        assert!(!ctx.has_cte_context());
311        assert!(!ctx.has_procedural_context());
312    }
313
314    #[test]
315    fn test_execution_context_with_cte() {
316        let schema = create_test_schema();
317        let database = vibesql_storage::Database::new();
318        let cte_results: HashMap<String, CteResult> = HashMap::new();
319
320        let ctx = ExecutionContext::new(&schema, &database).with_cte_context(&cte_results);
321
322        assert!(ctx.has_cte_context());
323    }
324
325    #[test]
326    fn test_execution_context_builder_chain() {
327        let schema = create_test_schema();
328        let database = vibesql_storage::Database::new();
329        let cte_results: HashMap<String, CteResult> = HashMap::new();
330
331        let ctx =
332            ExecutionContextBuilder::new(&schema, &database).cte_from_query(&cte_results).build();
333
334        // Empty CTE map should not set context
335        assert!(!ctx.has_cte_context());
336    }
337}