Skip to main content

reifydb_sub_flow/engine/
eval.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{collections::BTreeMap, sync::LazyLock};
5
6use reifydb_core::value::column::columns::Columns;
7use reifydb_engine::{
8	expression::{
9		compile::compile_expression,
10		context::{CompileContext, EvalSession},
11	},
12	vm::stack::SymbolTable,
13};
14use reifydb_routine::function::registry::Functions;
15use reifydb_rql::expression::Expression;
16use reifydb_runtime::context::RuntimeContext;
17use reifydb_type::{
18	Result,
19	params::Params,
20	value::{Value, identity::IdentityId},
21};
22
23static EMPTY_PARAMS: Params = Params::None;
24static EMPTY_SYMBOL_TABLE: LazyLock<SymbolTable> = LazyLock::new(|| SymbolTable::new());
25
26/// Evaluate a list of expressions into operator configuration
27///
28/// Only processes `Expression::Alias` variants:
29/// - The alias name becomes the BTreeMap key
30/// - The inner expression is evaluated to become the value
31/// - Non-Alias expressions are skipped
32///
33/// # Arguments
34/// * `expressions` - The expressions to evaluate (typically from FlowNode::Apply)
35/// * `functions` - The function registry to use for expression evaluation
36/// * `runtime_context` - The runtime context to use for time-based expressions
37///
38/// # Returns
39/// BTreeMap<String, Value> where keys are alias names and values are evaluated results
40///
41/// # Errors
42/// Returns error if expression evaluation fails
43pub fn evaluate_operator_config(
44	expressions: &[Expression],
45	functions: &Functions,
46	runtime_context: &RuntimeContext,
47) -> Result<BTreeMap<String, Value>> {
48	let mut result = BTreeMap::new();
49
50	let compile_ctx = CompileContext {
51		functions,
52		symbols: &EMPTY_SYMBOL_TABLE,
53	};
54
55	let empty_columns = Columns::empty();
56
57	let session = EvalSession {
58		params: &EMPTY_PARAMS,
59		symbols: &EMPTY_SYMBOL_TABLE,
60		functions,
61		runtime_context,
62		arena: None,
63		identity: IdentityId::root(),
64		is_aggregate_context: false,
65	};
66	let exec_ctx = session.eval(empty_columns, 1);
67
68	for expr in expressions {
69		match expr {
70			Expression::Alias(alias_expr) => {
71				let key = alias_expr.alias.name().to_string();
72
73				let expr = compile_expression(&compile_ctx, &alias_expr.expression)?;
74				let column = expr.execute(&exec_ctx)?;
75
76				let value = if column.data().len() > 0 {
77					column.data().get_value(0)
78				} else {
79					Value::none()
80				};
81				result.insert(key, value);
82			}
83			_ => {}
84		}
85	}
86
87	Ok(result)
88}
89
90#[cfg(test)]
91pub mod tests {
92	use reifydb_routine::function::registry::Functions;
93	use reifydb_rql::expression::{AliasExpression, ConstantExpression, Expression, IdentExpression};
94	use reifydb_runtime::context::RuntimeContext;
95	use reifydb_type::{fragment::Fragment, value::Value};
96
97	use super::evaluate_operator_config;
98
99	fn create_alias_expression(alias_name: &str, inner_expression: Expression) -> Expression {
100		Expression::Alias(AliasExpression {
101			alias: IdentExpression(Fragment::internal(alias_name.to_string())),
102			expression: Box::new(inner_expression),
103			fragment: Fragment::testing_empty(),
104		})
105	}
106
107	fn create_constant_text(text: &str) -> Expression {
108		Expression::Constant(ConstantExpression::Text {
109			fragment: Fragment::internal(text.to_string()),
110		})
111	}
112
113	fn create_constant_number(num: i64) -> Expression {
114		Expression::Constant(ConstantExpression::Number {
115			fragment: Fragment::internal(num.to_string()),
116		})
117	}
118
119	fn create_constant_bool(value: bool) -> Expression {
120		Expression::Constant(ConstantExpression::Bool {
121			fragment: Fragment::internal(value.to_string()),
122		})
123	}
124
125	fn create_constant_undefined() -> Expression {
126		Expression::Constant(ConstantExpression::None {
127			fragment: Fragment::internal("none".to_string()),
128		})
129	}
130
131	#[test]
132	fn test_empty_expressions() {
133		let functions = Functions::builder().build();
134		let runtime_context = RuntimeContext::default();
135		let expressions: Vec<Expression> = vec![];
136
137		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
138
139		assert!(result.is_empty());
140	}
141
142	#[test]
143	fn test_single_alias_string() {
144		let functions = Functions::builder().build();
145		let runtime_context = RuntimeContext::default();
146		let expressions = vec![create_alias_expression("key1", create_constant_text("value1"))];
147
148		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
149
150		assert_eq!(result.len(), 1);
151		assert_eq!(result.get("key1"), Some(&Value::Utf8("value1".into())));
152	}
153
154	#[test]
155	fn test_single_alias_number() {
156		let functions = Functions::builder().build();
157		let runtime_context = RuntimeContext::default();
158		let expressions = vec![create_alias_expression("count", create_constant_number(42))];
159
160		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
161
162		assert_eq!(result.len(), 1);
163		assert_eq!(result.get("count"), Some(&Value::Int1(42)));
164	}
165
166	#[test]
167	fn test_single_alias_bool() {
168		let functions = Functions::builder().build();
169		let runtime_context = RuntimeContext::default();
170		let expressions = vec![create_alias_expression("enabled", create_constant_bool(true))];
171
172		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
173
174		assert_eq!(result.len(), 1);
175		assert_eq!(result.get("enabled"), Some(&Value::Boolean(true)));
176	}
177
178	#[test]
179	fn test_single_alias_undefined() {
180		let functions = Functions::builder().build();
181		let runtime_context = RuntimeContext::default();
182		let expressions = vec![create_alias_expression("optional", create_constant_undefined())];
183
184		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
185
186		assert_eq!(result.len(), 1);
187		assert_eq!(result.get("optional"), Some(&Value::none()));
188	}
189
190	#[test]
191	fn test_multiple_aliases() {
192		let functions = Functions::builder().build();
193		let runtime_context = RuntimeContext::default();
194		let expressions = vec![
195			create_alias_expression("key1", create_constant_text("value1")),
196			create_alias_expression("key2", create_constant_number(100)),
197			create_alias_expression("key3", create_constant_bool(false)),
198		];
199
200		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
201
202		assert_eq!(result.len(), 3);
203		assert_eq!(result.get("key1"), Some(&Value::Utf8("value1".into())));
204		assert_eq!(result.get("key2"), Some(&Value::Int1(100)));
205		assert_eq!(result.get("key3"), Some(&Value::Boolean(false)));
206	}
207
208	// Expression filtering tests
209
210	#[test]
211	fn test_non_alias_expressions_skipped() {
212		let functions = Functions::builder().build();
213		let runtime_context = RuntimeContext::default();
214		let expressions = vec![
215			create_alias_expression("valid", create_constant_text("included")),
216			create_constant_text("standalone"), // Non-alias, should be skipped
217			create_constant_number(999),        // Non-alias, should be skipped
218		];
219
220		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
221
222		assert_eq!(result.len(), 1);
223		assert_eq!(result.get("valid"), Some(&Value::Utf8("included".into())));
224	}
225
226	#[test]
227	fn test_only_non_alias_expressions() {
228		let functions = Functions::builder().build();
229		let runtime_context = RuntimeContext::default();
230		let expressions =
231			vec![create_constant_text("text"), create_constant_number(42), create_constant_bool(true)];
232
233		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
234
235		assert!(result.is_empty());
236	}
237
238	// Value type coverage tests
239
240	#[test]
241	fn test_all_basic_value_types() {
242		let functions = Functions::builder().build();
243		let runtime_context = RuntimeContext::default();
244		let expressions = vec![
245			create_alias_expression("text_val", create_constant_text("hello")),
246			create_alias_expression("num_val", create_constant_number(-42)),
247			create_alias_expression("bool_true", create_constant_bool(true)),
248			create_alias_expression("bool_false", create_constant_bool(false)),
249			create_alias_expression("undef_val", create_constant_undefined()),
250		];
251
252		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
253
254		assert_eq!(result.len(), 5);
255		assert_eq!(result.get("text_val"), Some(&Value::Utf8("hello".into())));
256		assert_eq!(result.get("num_val"), Some(&Value::Int1(-42)));
257		assert_eq!(result.get("bool_true"), Some(&Value::Boolean(true)));
258		assert_eq!(result.get("bool_false"), Some(&Value::Boolean(false)));
259		assert_eq!(result.get("undef_val"), Some(&Value::none()));
260	}
261
262	#[test]
263	fn test_duplicate_alias_names_last_wins() {
264		let functions = Functions::builder().build();
265		let runtime_context = RuntimeContext::default();
266		let expressions = vec![
267			create_alias_expression("key", create_constant_text("first")),
268			create_alias_expression("key", create_constant_text("second")),
269			create_alias_expression("key", create_constant_number(42)),
270		];
271
272		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
273
274		assert_eq!(result.len(), 1);
275		assert_eq!(result.get("key"), Some(&Value::Int1(42)));
276	}
277
278	#[test]
279	fn test_empty_string_value() {
280		let functions = Functions::builder().build();
281		let runtime_context = RuntimeContext::default();
282		let expressions = vec![create_alias_expression("empty", create_constant_text(""))];
283
284		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
285
286		assert_eq!(result.len(), 1);
287		assert_eq!(result.get("empty"), Some(&Value::Utf8("".into())));
288	}
289
290	#[test]
291	fn test_special_characters_in_alias_name() {
292		let functions = Functions::builder().build();
293		let runtime_context = RuntimeContext::default();
294		let expressions = vec![
295			create_alias_expression("key_with_underscore", create_constant_number(1)),
296			create_alias_expression("keyWithCamelCase", create_constant_number(2)),
297			create_alias_expression("key123", create_constant_number(3)),
298		];
299
300		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
301
302		assert_eq!(result.len(), 3);
303		assert_eq!(result.get("key_with_underscore"), Some(&Value::Int1(1)));
304		assert_eq!(result.get("keyWithCamelCase"), Some(&Value::Int1(2)));
305		assert_eq!(result.get("key123"), Some(&Value::Int1(3)));
306	}
307
308	#[test]
309	fn test_large_number_values() {
310		let functions = Functions::builder().build();
311		let runtime_context = RuntimeContext::default();
312		let expressions = vec![
313			create_alias_expression("small", create_constant_number(0)),
314			create_alias_expression("large_positive", create_constant_number(i64::MAX)),
315			create_alias_expression("large_negative", create_constant_number(i64::MIN)),
316		];
317
318		let result = evaluate_operator_config(&expressions, &functions, &runtime_context).unwrap();
319
320		assert_eq!(result.len(), 3);
321		assert_eq!(result.get("small"), Some(&Value::Int1(0)));
322		assert_eq!(result.get("large_positive"), Some(&Value::Int8(i64::MAX)));
323		assert_eq!(result.get("large_negative"), Some(&Value::Int8(i64::MIN)));
324	}
325}