Skip to main content

reifydb_sub_flow/engine/
eval.rs

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