1use std::{collections::HashMap, sync::Arc};
5
6use arrow_array::{ArrayRef, RecordBatch};
7use arrow_schema::FieldRef;
8use reifydb_core::{
9 internal,
10 value::batch::{is_scalar, single_row},
11};
12use reifydb_rql::{
13 instruction::{CompiledClosure, CompiledFunction, Instruction},
14 nodes::FunctionParameter,
15};
16use reifydb_value::{
17 error,
18 value::{Value, constraint::TypeConstraint, system_columns::user_columns},
19};
20
21use crate::{Result, error::EvaluateError};
22
23pub fn strip_dollar_prefix(name: &str) -> &str {
24 name.strip_prefix('$').unwrap_or(name)
25}
26
27#[derive(Debug, Clone)]
28pub struct ClosureValue {
29 pub def: CompiledClosure,
30 pub captured: HashMap<String, Variable>,
31}
32
33#[derive(Debug, Clone)]
34pub struct Callable {
35 pub parameters: Vec<FunctionParameter>,
36 pub body: Vec<Instruction>,
37 pub captured: HashMap<String, Variable>,
38 pub return_type: Option<TypeConstraint>,
39}
40
41#[derive(Debug, Clone)]
42pub enum Variable {
43 Columns {
44 batch: RecordBatch,
45 },
46
47 ForIterator {
48 batch: RecordBatch,
49 index: usize,
50 },
51
52 Closure(ClosureValue),
53}
54
55impl Variable {
56 pub fn scalar(value: Value) -> Self {
57 Self::scalar_named("value", value)
58 }
59
60 pub fn scalar_named(name: &str, value: Value) -> Self {
61 Variable::Columns {
62 batch: single_row([(name, value)]).expect("one value always forms a one row batch"),
63 }
64 }
65
66 pub fn columns(batch: RecordBatch) -> Self {
67 Variable::Columns {
68 batch,
69 }
70 }
71
72 pub fn is_scalar(&self) -> bool {
73 matches!(
74 self,
75 Variable::Columns { batch } if is_scalar(batch)
76 )
77 }
78
79 pub fn into_column(self) -> Result<(FieldRef, ArrayRef)> {
80 let batch = match self {
81 Variable::Columns {
82 batch,
83 ..
84 }
85 | Variable::ForIterator {
86 batch,
87 ..
88 } => batch,
89 Variable::Closure(_) => single_row([("value", Value::none())])?,
90 };
91 let user: Vec<(&FieldRef, &ArrayRef)> = user_columns(&batch).collect();
92 let actual = user.len();
93 if let [(field, array)] = user.as_slice() {
94 Ok(((*field).clone(), (*array).clone()))
95 } else {
96 Err(error::TypeError::Runtime {
97 kind: error::RuntimeErrorKind::ExpectedSingleColumn {
98 actual,
99 },
100 message: format!("Expected a single column but got {}", actual),
101 }
102 .into())
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
108pub struct SymbolTable {
109 inner: Arc<SymbolTableInner>,
110}
111
112#[derive(Debug, Clone)]
113struct SymbolTableInner {
114 scopes: Vec<Scope>,
115
116 functions: HashMap<String, CompiledFunction>,
117}
118
119#[derive(Debug, Clone)]
120struct Scope {
121 variables: HashMap<String, VariableBinding>,
122}
123
124#[derive(Debug, Clone)]
125struct VariableBinding {
126 variable: Variable,
127 mutable: bool,
128}
129
130impl SymbolTable {
131 pub fn new() -> Self {
132 let global_scope = Scope {
133 variables: HashMap::new(),
134 };
135
136 Self {
137 inner: Arc::new(SymbolTableInner {
138 scopes: vec![global_scope],
139 functions: HashMap::new(),
140 }),
141 }
142 }
143
144 pub fn enter_scope(&mut self) {
145 let new_scope = Scope {
146 variables: HashMap::new(),
147 };
148 Arc::make_mut(&mut self.inner).scopes.push(new_scope);
149 }
150
151 pub fn exit_scope(&mut self) -> Result<()> {
152 if self.inner.scopes.len() <= 1 {
153 return Err(error!(internal!("Cannot exit global scope")));
154 }
155 Arc::make_mut(&mut self.inner).scopes.pop();
156 Ok(())
157 }
158
159 pub fn set(&mut self, name: String, variable: Variable, mutable: bool) -> Result<()> {
160 self.set_in_current_scope(name, variable, mutable)
161 }
162
163 pub fn reassign(&mut self, name: String, variable: Variable) -> Result<()> {
164 let inner = Arc::make_mut(&mut self.inner);
165
166 for scope in inner.scopes.iter_mut().rev() {
167 if let Some(existing) = scope.variables.get(&name) {
168 if !existing.mutable {
169 return Err(EvaluateError::VariableIsImmutable {
170 name: name.clone(),
171 }
172 .into());
173 }
174 let mutable = existing.mutable;
175 scope.variables.insert(
176 name,
177 VariableBinding {
178 variable,
179 mutable,
180 },
181 );
182 return Ok(());
183 }
184 }
185
186 Err(EvaluateError::VariableNotFound {
187 name: name.clone(),
188 }
189 .into())
190 }
191
192 pub fn set_in_current_scope(&mut self, name: String, variable: Variable, mutable: bool) -> Result<()> {
193 let inner = Arc::make_mut(&mut self.inner);
194 let current_scope = inner.scopes.last_mut().unwrap();
195
196 current_scope.variables.insert(
197 name,
198 VariableBinding {
199 variable,
200 mutable,
201 },
202 );
203 Ok(())
204 }
205
206 pub fn get(&self, name: &str) -> Option<&Variable> {
207 for scope in self.inner.scopes.iter().rev() {
208 if let Some(binding) = scope.variables.get(name) {
209 return Some(&binding.variable);
210 }
211 }
212 None
213 }
214
215 pub fn define_function(&mut self, name: String, func: CompiledFunction) {
216 Arc::make_mut(&mut self.inner).functions.insert(name, func);
217 }
218
219 pub fn get_function(&self, name: &str) -> Option<&CompiledFunction> {
220 self.inner.functions.get(name)
221 }
222
223 pub fn resolve_callable(&self, name: &str) -> Option<Callable> {
224 if let Some(func) = self.get_function(name) {
225 return Some(Callable {
226 parameters: func.parameters.clone(),
227 body: func.body.clone(),
228 captured: HashMap::new(),
229 return_type: func.return_type.clone(),
230 });
231 }
232 if let Some(Variable::Closure(closure)) = self.get(strip_dollar_prefix(name)) {
233 return Some(Callable {
234 parameters: closure.def.parameters.clone(),
235 body: closure.def.body.clone(),
236 captured: closure.captured.clone(),
237 return_type: None,
238 });
239 }
240 None
241 }
242}
243
244impl Default for SymbolTable {
245 fn default() -> Self {
246 Self::new()
247 }
248}
249
250#[cfg(test)]
251pub mod tests {
252 use reifydb_core::value::{
253 batch::batch,
254 column::{builder::ColumnBuilder, factory::none_typed},
255 };
256 use reifydb_value::value::{Value, column_view::ColumnView, value_type::ValueType};
257
258 use super::*;
259
260 fn create_test_columns(values: Vec<Value>) -> RecordBatch {
261 if values.is_empty() {
262 return batch(vec![none_typed("test_col", ValueType::Boolean, 0)]).unwrap();
263 }
264
265 let empty = none_typed("test_col", values[0].get_type(), 0);
266 let mut builder = ColumnBuilder::from_view(&ColumnView::try_from(&empty).unwrap());
267 for value in values {
268 builder.push_value(value);
269 }
270
271 batch(vec![builder.finish("test_col")]).unwrap()
272 }
273
274 #[test]
275 fn test_basic_variable_operations() {
276 let mut ctx = SymbolTable::new();
277 let cols = create_test_columns(vec![Value::utf8("Alice".to_string())]);
278
279 ctx.set("name".to_string(), Variable::columns(cols.clone()), false).unwrap();
280
281 assert!(ctx.get("name").is_some());
282 }
283
284 #[test]
285 fn test_mutable_variable() {
286 let mut ctx = SymbolTable::new();
287 let cols1 = create_test_columns(vec![Value::Int4(42)]);
288 let cols2 = create_test_columns(vec![Value::Int4(84)]);
289
290 ctx.set("counter".to_string(), Variable::columns(cols1.clone()), true).unwrap();
291 assert!(ctx.get("counter").is_some());
292
293 ctx.set("counter".to_string(), Variable::columns(cols2.clone()), true).unwrap();
294 assert!(ctx.get("counter").is_some());
295 }
296
297 #[test]
298 #[ignore]
299 fn test_immutable_variable_reassignment_fails() {
300 let mut ctx = SymbolTable::new();
301 let cols1 = create_test_columns(vec![Value::utf8("Alice".to_string())]);
302 let cols2 = create_test_columns(vec![Value::utf8("Bob".to_string())]);
303
304 ctx.set("name".to_string(), Variable::columns(cols1.clone()), false).unwrap();
305
306 let result = ctx.set("name".to_string(), Variable::columns(cols2), false);
307 assert!(result.is_err());
308
309 assert!(ctx.get("name").is_some());
311 }
312
313 #[test]
314 fn test_scope_management() {
315 let mut ctx = SymbolTable::new();
316
317 ctx.enter_scope();
318 ctx.enter_scope();
319 ctx.exit_scope().unwrap();
320 ctx.exit_scope().unwrap();
321
322 assert!(ctx.exit_scope().is_err());
324 }
325
326 #[test]
327 fn test_variable_shadowing() {
328 let mut ctx = SymbolTable::new();
329 let outer_cols = create_test_columns(vec![Value::utf8("outer".to_string())]);
330 let inner_cols = create_test_columns(vec![Value::utf8("inner".to_string())]);
331
332 ctx.set("var".to_string(), Variable::columns(outer_cols.clone()), false).unwrap();
333 assert!(ctx.get("var").is_some());
334
335 ctx.enter_scope();
337 ctx.set("var".to_string(), Variable::columns(inner_cols.clone()), false).unwrap();
338
339 assert!(ctx.get("var").is_some());
340
341 ctx.exit_scope().unwrap();
342 assert!(ctx.get("var").is_some());
343 }
344
345 #[test]
346 fn test_parent_scope_access() {
347 let mut ctx = SymbolTable::new();
348 let outer_cols = create_test_columns(vec![Value::utf8("outer".to_string())]);
349
350 ctx.set("global_var".to_string(), Variable::columns(outer_cols.clone()), false).unwrap();
351
352 ctx.enter_scope();
353
354 assert!(ctx.get("global_var").is_some());
356 }
357
358 #[test]
359 fn test_nonexistent_variable() {
360 let ctx = SymbolTable::new();
361
362 assert!(ctx.get("nonexistent").is_none());
363 }
364}