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