reifydb_engine/
stack.rs

1// Copyright (c) reifydb.com 2025
2// This file is licensed under the AGPL-3.0-or-later, see license.md file
3
4use std::collections::HashMap;
5
6use reifydb_core::value::column::Columns;
7use reifydb_type::{Value, diagnostic, error};
8
9/// A variable can be either a scalar value or a dataframe
10#[derive(Debug, Clone)]
11pub enum Variable {
12	/// A scalar value that can be used directly in expressions
13	Scalar(Value),
14	/// A dataframe (columns) that requires explicit conversion to scalar
15	Frame(Columns<'static>),
16}
17
18impl Variable {
19	/// Create a scalar variable
20	pub fn scalar(value: Value) -> Self {
21		Variable::Scalar(value)
22	}
23
24	/// Create a frame variable
25	pub fn frame(columns: Columns<'static>) -> Self {
26		Variable::Frame(columns)
27	}
28
29	/// Get the scalar value if this is a scalar variable
30	pub fn as_scalar(&self) -> Option<&Value> {
31		match self {
32			Variable::Scalar(value) => Some(value),
33			Variable::Frame(_) => None,
34		}
35	}
36
37	/// Get the frame if this is a frame variable
38	pub fn as_frame(&self) -> Option<&Columns<'static>> {
39		match self {
40			Variable::Scalar(_) => None,
41			Variable::Frame(columns) => Some(columns),
42		}
43	}
44}
45
46/// Context for storing and managing variables during query execution with scope support
47#[derive(Debug, Clone)]
48pub struct Stack {
49	scopes: Vec<Scope>,
50}
51
52/// Represents a single scope containing variables
53#[derive(Debug, Clone)]
54struct Scope {
55	variables: HashMap<String, VariableBinding>,
56	scope_type: ScopeType,
57}
58
59/// Different types of scopes for organizational purposes
60#[derive(Debug, Clone, PartialEq)]
61pub enum ScopeType {
62	Global,
63	Function,
64	Block,
65	Conditional,
66}
67
68/// Represents a variable binding with its value and mutability
69#[derive(Debug, Clone)]
70struct VariableBinding {
71	variable: Variable,
72	mutable: bool,
73}
74
75impl Stack {
76	/// Create a new variable context with a global scope
77	pub fn new() -> Self {
78		let global_scope = Scope {
79			variables: HashMap::new(),
80			scope_type: ScopeType::Global,
81		};
82
83		Self {
84			scopes: vec![global_scope],
85		}
86	}
87
88	/// Enter a new scope (push onto stack)
89	pub fn enter_scope(&mut self, scope_type: ScopeType) {
90		let new_scope = Scope {
91			variables: HashMap::new(),
92			scope_type,
93		};
94		self.scopes.push(new_scope);
95	}
96
97	/// Exit the current scope (pop from stack)
98	/// Returns error if trying to exit the global scope
99	pub fn exit_scope(&mut self) -> crate::Result<()> {
100		if self.scopes.len() <= 1 {
101			return Err(error!(diagnostic::internal::internal("Cannot exit global scope".to_string())));
102		}
103		self.scopes.pop();
104		Ok(())
105	}
106
107	/// Get the current scope depth (0 = global scope)
108	pub fn scope_depth(&self) -> usize {
109		self.scopes.len() - 1
110	}
111
112	/// Get the type of the current scope
113	pub fn current_scope_type(&self) -> &ScopeType {
114		&self.scopes.last().unwrap().scope_type
115	}
116
117	/// Set a variable in the current (innermost) scope (allows shadowing)
118	pub fn set(&mut self, name: String, variable: Variable, mutable: bool) -> crate::Result<()> {
119		self.set_in_current_scope(name, variable, mutable)
120	}
121
122	/// Reassign an existing variable (checks mutability)
123	pub fn reassign(&mut self, name: String, variable: Variable) -> crate::Result<()> {
124		let current_scope = self.scopes.last_mut().unwrap();
125
126		// Check if variable exists and is mutable
127		if let Some(existing) = current_scope.variables.get(&name) {
128			if !existing.mutable {
129				return Err(reifydb_type::Error(
130					reifydb_type::diagnostic::runtime::variable_is_immutable(&name),
131				));
132			}
133			// Update existing variable, keeping its mutability
134			current_scope.variables.insert(
135				name,
136				VariableBinding {
137					variable,
138					mutable: existing.mutable,
139				},
140			);
141		} else {
142			return Err(reifydb_type::Error(reifydb_type::diagnostic::runtime::variable_not_found(&name)));
143		}
144
145		Ok(())
146	}
147
148	/// Set a variable specifically in the current scope
149	/// Allows shadowing - new variable declarations can shadow existing ones
150	pub fn set_in_current_scope(&mut self, name: String, variable: Variable, mutable: bool) -> crate::Result<()> {
151		let current_scope = self.scopes.last_mut().unwrap();
152
153		// Allow shadowing - simply insert the new variable binding
154		current_scope.variables.insert(
155			name,
156			VariableBinding {
157				variable,
158				mutable,
159			},
160		);
161		Ok(())
162	}
163
164	/// Get a variable by searching from innermost to outermost scope
165	pub fn get(&self, name: &str) -> Option<&Variable> {
166		// Search from innermost scope (end of vector) to outermost scope (beginning)
167		for scope in self.scopes.iter().rev() {
168			if let Some(binding) = scope.variables.get(name) {
169				return Some(&binding.variable);
170			}
171		}
172		None
173	}
174
175	/// Get a variable with its scope depth information
176	pub fn get_with_scope(&self, name: &str) -> Option<(&Variable, usize)> {
177		// Search from innermost scope to outermost scope
178		for (depth_from_end, scope) in self.scopes.iter().rev().enumerate() {
179			if let Some(binding) = scope.variables.get(name) {
180				let scope_depth = self.scopes.len() - 1 - depth_from_end;
181				return Some((&binding.variable, scope_depth));
182			}
183		}
184		None
185	}
186
187	/// Check if a variable exists in the current scope only
188	pub fn exists_in_current_scope(&self, name: &str) -> bool {
189		self.scopes.last().unwrap().variables.contains_key(name)
190	}
191
192	/// Check if a variable exists in any scope (searches all scopes)
193	pub fn exists_in_any_scope(&self, name: &str) -> bool {
194		self.get(name).is_some()
195	}
196
197	/// Check if a variable is mutable (searches from innermost scope)
198	pub fn is_mutable(&self, name: &str) -> bool {
199		for scope in self.scopes.iter().rev() {
200			if let Some(binding) = scope.variables.get(name) {
201				return binding.mutable;
202			}
203		}
204		false
205	}
206
207	/// Get all variable names from all scopes (for debugging)
208	pub fn all_variable_names(&self) -> Vec<String> {
209		let mut names = Vec::new();
210		for (scope_idx, scope) in self.scopes.iter().enumerate() {
211			for name in scope.variables.keys() {
212				names.push(format!("{}@scope{}", name, scope_idx));
213			}
214		}
215		names
216	}
217
218	/// Get variable names visible in current scope (respects shadowing)
219	pub fn visible_variable_names(&self) -> Vec<String> {
220		let mut visible = HashMap::new();
221
222		// Process scopes from outermost to innermost so inner scopes override outer ones
223		for scope in &self.scopes {
224			for name in scope.variables.keys() {
225				visible.insert(name.clone(), ());
226			}
227		}
228
229		visible.keys().cloned().collect()
230	}
231
232	/// Clear all variables in all scopes (reset to just global scope)
233	pub fn clear(&mut self) {
234		self.scopes.clear();
235		self.scopes.push(Scope {
236			variables: HashMap::new(),
237			scope_type: ScopeType::Global,
238		});
239	}
240}
241
242impl Default for Stack {
243	fn default() -> Self {
244		Self::new()
245	}
246}
247
248#[cfg(test)]
249mod tests {
250	use reifydb_core::value::column::{Column, ColumnData};
251	use reifydb_type::Value;
252
253	use super::*;
254
255	// Helper function to create test columns
256	fn create_test_columns(values: Vec<Value>) -> Columns<'static> {
257		if values.is_empty() {
258			let column_data = ColumnData::undefined(0);
259			let column = Column::new("test_col", column_data);
260			return Columns::new(vec![column]);
261		}
262
263		let mut column_data = ColumnData::undefined(0);
264		for value in values {
265			column_data.push_value(value);
266		}
267
268		let column = Column::new("test_col", column_data);
269		Columns::new(vec![column])
270	}
271
272	#[test]
273	fn test_basic_variable_operations() {
274		let mut ctx = Stack::new();
275		let cols = create_test_columns(vec![Value::utf8("Alice".to_string())]);
276
277		// Set a variable
278		ctx.set("name".to_string(), Variable::frame(cols.clone()), false).unwrap();
279
280		// Get the variable
281		assert!(ctx.get("name").is_some());
282		assert!(!ctx.is_mutable("name"));
283		assert!(ctx.exists_in_any_scope("name"));
284		assert!(ctx.exists_in_current_scope("name"));
285	}
286
287	#[test]
288	fn test_mutable_variable() {
289		let mut ctx = Stack::new();
290		let cols1 = create_test_columns(vec![Value::Int4(42)]);
291		let cols2 = create_test_columns(vec![Value::Int4(84)]);
292
293		// Set as mutable
294		ctx.set("counter".to_string(), Variable::frame(cols1.clone()), true).unwrap();
295		assert!(ctx.is_mutable("counter"));
296		assert!(ctx.get("counter").is_some());
297
298		// Update mutable variable
299		ctx.set("counter".to_string(), Variable::frame(cols2.clone()), true).unwrap();
300		assert!(ctx.get("counter").is_some());
301	}
302
303	#[test]
304	#[ignore]
305	fn test_immutable_variable_reassignment_fails() {
306		let mut ctx = Stack::new();
307		let cols1 = create_test_columns(vec![Value::utf8("Alice".to_string())]);
308		let cols2 = create_test_columns(vec![Value::utf8("Bob".to_string())]);
309
310		// Set as immutable
311		ctx.set("name".to_string(), Variable::frame(cols1.clone()), false).unwrap();
312
313		// Try to reassign immutable variable - should fail
314		let result = ctx.set("name".to_string(), Variable::frame(cols2), false);
315		assert!(result.is_err());
316
317		// Original value should be preserved
318		assert!(ctx.get("name").is_some());
319	}
320
321	#[test]
322	fn test_scope_management() {
323		let mut ctx = Stack::new();
324
325		// Initially in global scope
326		assert_eq!(ctx.scope_depth(), 0);
327		assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
328
329		// Enter a function scope
330		ctx.enter_scope(ScopeType::Function);
331		assert_eq!(ctx.scope_depth(), 1);
332		assert_eq!(ctx.current_scope_type(), &ScopeType::Function);
333
334		// Enter a block scope
335		ctx.enter_scope(ScopeType::Block);
336		assert_eq!(ctx.scope_depth(), 2);
337		assert_eq!(ctx.current_scope_type(), &ScopeType::Block);
338
339		// Exit block scope
340		ctx.exit_scope().unwrap();
341		assert_eq!(ctx.scope_depth(), 1);
342		assert_eq!(ctx.current_scope_type(), &ScopeType::Function);
343
344		// Exit function scope
345		ctx.exit_scope().unwrap();
346		assert_eq!(ctx.scope_depth(), 0);
347		assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
348
349		// Cannot exit global scope
350		assert!(ctx.exit_scope().is_err());
351	}
352
353	#[test]
354	fn test_variable_shadowing() {
355		let mut ctx = Stack::new();
356		let outer_cols = create_test_columns(vec![Value::utf8("outer".to_string())]);
357		let inner_cols = create_test_columns(vec![Value::utf8("inner".to_string())]);
358
359		// Set variable in global scope
360		ctx.set("var".to_string(), Variable::frame(outer_cols.clone()), false).unwrap();
361		assert!(ctx.get("var").is_some());
362
363		// Enter new scope and shadow the variable
364		ctx.enter_scope(ScopeType::Block);
365		ctx.set("var".to_string(), Variable::frame(inner_cols.clone()), false).unwrap();
366
367		// Should see the inner variable
368		assert!(ctx.get("var").is_some());
369		assert!(ctx.exists_in_current_scope("var"));
370
371		// Exit scope - should see outer variable again
372		ctx.exit_scope().unwrap();
373		assert!(ctx.get("var").is_some());
374	}
375
376	#[test]
377	fn test_parent_scope_access() {
378		let mut ctx = Stack::new();
379		let outer_cols = create_test_columns(vec![Value::utf8("outer".to_string())]);
380
381		// Set variable in global scope
382		ctx.set("global_var".to_string(), Variable::frame(outer_cols.clone()), false).unwrap();
383
384		// Enter new scope
385		ctx.enter_scope(ScopeType::Function);
386
387		// Should still be able to access parent scope variable
388		assert!(ctx.get("global_var").is_some());
389		assert!(!ctx.exists_in_current_scope("global_var"));
390		assert!(ctx.exists_in_any_scope("global_var"));
391
392		// Get with scope information
393		let (_, scope_depth) = ctx.get_with_scope("global_var").unwrap();
394		assert_eq!(scope_depth, 0); // Found in global scope
395	}
396
397	#[test]
398	fn test_scope_specific_mutability() {
399		let mut ctx = Stack::new();
400		let cols1 = create_test_columns(vec![Value::utf8("value1".to_string())]);
401		let cols2 = create_test_columns(vec![Value::utf8("value2".to_string())]);
402
403		// Set immutable variable in global scope
404		ctx.set("var".to_string(), Variable::frame(cols1.clone()), false).unwrap();
405
406		// Enter new scope and create new variable with same name (shadowing)
407		ctx.enter_scope(ScopeType::Block);
408		ctx.set("var".to_string(), Variable::frame(cols2.clone()), true).unwrap(); // This one is mutable
409
410		// Should be mutable in current scope
411		assert!(ctx.is_mutable("var"));
412
413		// Exit scope - should be immutable again (from global scope)
414		ctx.exit_scope().unwrap();
415		assert!(!ctx.is_mutable("var"));
416	}
417
418	#[test]
419	fn test_visible_variable_names() {
420		let mut ctx = Stack::new();
421		let cols = create_test_columns(vec![Value::utf8("test".to_string())]);
422
423		// Set variables in global scope
424		ctx.set("global1".to_string(), Variable::frame(cols.clone()), false).unwrap();
425		ctx.set("global2".to_string(), Variable::frame(cols.clone()), false).unwrap();
426
427		let global_visible = ctx.visible_variable_names();
428		assert_eq!(global_visible.len(), 2);
429		assert!(global_visible.contains(&"global1".to_string()));
430		assert!(global_visible.contains(&"global2".to_string()));
431
432		// Enter new scope and add more variables
433		ctx.enter_scope(ScopeType::Function);
434		ctx.set("local1".to_string(), Variable::frame(cols.clone()), false).unwrap();
435		ctx.set("global1".to_string(), Variable::frame(cols.clone()), false).unwrap(); // Shadow global1
436
437		let function_visible = ctx.visible_variable_names();
438		assert_eq!(function_visible.len(), 3); // global1 (shadowed), global2, local1
439		assert!(function_visible.contains(&"global1".to_string()));
440		assert!(function_visible.contains(&"global2".to_string()));
441		assert!(function_visible.contains(&"local1".to_string()));
442	}
443
444	#[test]
445	fn test_clear_resets_to_global() {
446		let mut ctx = Stack::new();
447		let cols = create_test_columns(vec![Value::utf8("test".to_string())]);
448
449		// Add variables and enter scopes
450		ctx.set("var1".to_string(), Variable::frame(cols.clone()), false).unwrap();
451		ctx.enter_scope(ScopeType::Function);
452		ctx.set("var2".to_string(), Variable::frame(cols.clone()), false).unwrap();
453		ctx.enter_scope(ScopeType::Block);
454		ctx.set("var3".to_string(), Variable::frame(cols.clone()), false).unwrap();
455
456		assert_eq!(ctx.scope_depth(), 2);
457		assert_eq!(ctx.visible_variable_names().len(), 3);
458
459		// Clear should reset to global scope with no variables
460		ctx.clear();
461		assert_eq!(ctx.scope_depth(), 0);
462		assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
463		assert_eq!(ctx.visible_variable_names().len(), 0);
464	}
465
466	#[test]
467	fn test_nonexistent_variable() {
468		let ctx = Stack::new();
469
470		assert!(ctx.get("nonexistent").is_none());
471		assert!(!ctx.exists_in_any_scope("nonexistent"));
472		assert!(!ctx.exists_in_current_scope("nonexistent"));
473		assert!(!ctx.is_mutable("nonexistent"));
474		assert!(ctx.get_with_scope("nonexistent").is_none());
475	}
476}