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_routine::routine::registry::Routines;
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
32pub fn evaluate_operator_config(
33	expressions: &[Expression],
34	routines: &Routines,
35	runtime_context: &RuntimeContext,
36) -> Result<BTreeMap<String, Value>> {
37	let mut result = BTreeMap::new();
38
39	let compile_ctx = CompileContext {
40		symbols: &EMPTY_SYMBOL_TABLE,
41	};
42
43	let empty_columns = Columns::empty();
44
45	let session = EvalContext {
46		params: &EMPTY_PARAMS,
47		symbols: &EMPTY_SYMBOL_TABLE,
48		routines,
49		runtime_context,
50		arena: None,
51		identity: IdentityId::root(),
52		is_aggregate_context: false,
53		columns: Columns::empty(),
54		row_count: 1,
55		target: None,
56		take: None,
57	};
58	let exec_ctx = session.with_eval(empty_columns, 1);
59
60	for expr in expressions {
61		if let Expression::Alias(alias_expr) = expr {
62			let key = alias_expr.alias.name().to_string();
63
64			let expr = compile_expression(&compile_ctx, &alias_expr.expression)?;
65			let column = expr.execute(&exec_ctx)?;
66
67			let value = if !column.data().is_empty() {
68				column.data().get_value(0)
69			} else {
70				Value::none()
71			};
72			result.insert(key, value);
73		}
74	}
75
76	Ok(result)
77}
78
79#[cfg(test)]
80pub mod tests {
81	use reifydb_routine::routine::registry::Routines;
82	use reifydb_rql::expression::{AliasExpression, ConstantExpression, Expression, IdentExpression};
83	use reifydb_runtime::context::{RuntimeContext, clock::Clock};
84	use reifydb_type::{fragment::Fragment, value::Value};
85
86	use super::evaluate_operator_config;
87
88	fn create_alias_expression(alias_name: &str, inner_expression: Expression) -> Expression {
89		Expression::Alias(AliasExpression {
90			alias: IdentExpression(Fragment::internal(alias_name)),
91			expression: Box::new(inner_expression),
92			fragment: Fragment::testing_empty(),
93		})
94	}
95
96	fn create_constant_text(text: &str) -> Expression {
97		Expression::Constant(ConstantExpression::Text {
98			fragment: Fragment::internal(text),
99		})
100	}
101
102	fn create_constant_number(num: i64) -> Expression {
103		Expression::Constant(ConstantExpression::Number {
104			fragment: Fragment::internal(num.to_string()),
105		})
106	}
107
108	fn create_constant_bool(value: bool) -> Expression {
109		Expression::Constant(ConstantExpression::Bool {
110			fragment: Fragment::internal(value.to_string()),
111		})
112	}
113
114	fn create_constant_undefined() -> Expression {
115		Expression::Constant(ConstantExpression::None {
116			fragment: Fragment::internal("none"),
117		})
118	}
119
120	#[test]
121	fn test_empty_expressions() {
122		let routines = Routines::empty();
123		let runtime_context = RuntimeContext::with_clock(Clock::Real);
124		let expressions: Vec<Expression> = vec![];
125
126		let result = evaluate_operator_config(&expressions, &routines, &runtime_context).unwrap();
127
128		assert!(result.is_empty());
129	}
130
131	#[test]
132	fn test_single_alias_string() {
133		let routines = Routines::empty();
134		let runtime_context = RuntimeContext::with_clock(Clock::Real);
135		let expressions = vec![create_alias_expression("key1", create_constant_text("value1"))];
136
137		let result = evaluate_operator_config(&expressions, &routines, &runtime_context).unwrap();
138
139		assert_eq!(result.len(), 1);
140		assert_eq!(result.get("key1"), Some(&Value::Utf8("value1".into())));
141	}
142
143	#[test]
144	fn test_single_alias_number() {
145		let routines = Routines::empty();
146		let runtime_context = RuntimeContext::with_clock(Clock::Real);
147		let expressions = vec![create_alias_expression("count", create_constant_number(42))];
148
149		let result = evaluate_operator_config(&expressions, &routines, &runtime_context).unwrap();
150
151		assert_eq!(result.len(), 1);
152		assert_eq!(result.get("count"), Some(&Value::Int1(42)));
153	}
154
155	#[test]
156	fn test_single_alias_bool() {
157		let routines = Routines::empty();
158		let runtime_context = RuntimeContext::with_clock(Clock::Real);
159		let expressions = vec![create_alias_expression("enabled", create_constant_bool(true))];
160
161		let result = evaluate_operator_config(&expressions, &routines, &runtime_context).unwrap();
162
163		assert_eq!(result.len(), 1);
164		assert_eq!(result.get("enabled"), Some(&Value::Boolean(true)));
165	}
166
167	#[test]
168	fn test_single_alias_undefined() {
169		let routines = Routines::empty();
170		let runtime_context = RuntimeContext::with_clock(Clock::Real);
171		let expressions = vec![create_alias_expression("optional", create_constant_undefined())];
172
173		let result = evaluate_operator_config(&expressions, &routines, &runtime_context).unwrap();
174
175		assert_eq!(result.len(), 1);
176		assert_eq!(result.get("optional"), Some(&Value::none()));
177	}
178
179	#[test]
180	fn test_multiple_aliases() {
181		let routines = Routines::empty();
182		let runtime_context = RuntimeContext::with_clock(Clock::Real);
183		let expressions = vec![
184			create_alias_expression("key1", create_constant_text("value1")),
185			create_alias_expression("key2", create_constant_number(100)),
186			create_alias_expression("key3", create_constant_bool(false)),
187		];
188
189		let result = evaluate_operator_config(&expressions, &routines, &runtime_context).unwrap();
190
191		assert_eq!(result.len(), 3);
192		assert_eq!(result.get("key1"), Some(&Value::Utf8("value1".into())));
193		assert_eq!(result.get("key2"), Some(&Value::Int1(100)));
194		assert_eq!(result.get("key3"), Some(&Value::Boolean(false)));
195	}
196
197	#[test]
198	fn test_non_alias_expressions_skipped() {
199		let routines = Routines::empty();
200		let runtime_context = RuntimeContext::with_clock(Clock::Real);
201		let expressions = vec![
202			create_alias_expression("valid", create_constant_text("included")),
203			create_constant_text("standalone"),
204			create_constant_number(999),
205		];
206
207		let result = evaluate_operator_config(&expressions, &routines, &runtime_context).unwrap();
208
209		assert_eq!(result.len(), 1);
210		assert_eq!(result.get("valid"), Some(&Value::Utf8("included".into())));
211	}
212
213	#[test]
214	fn test_only_non_alias_expressions() {
215		let routines = Routines::empty();
216		let runtime_context = RuntimeContext::with_clock(Clock::Real);
217		let expressions = vec![
218			create_constant_text("standalone"),
219			create_constant_number(999),
220			create_constant_bool(true),
221		];
222
223		let result = evaluate_operator_config(&expressions, &routines, &runtime_context).unwrap();
224
225		assert!(result.is_empty());
226	}
227
228	#[test]
229	fn test_unknown_function_returns_error() {
230		let routines = Routines::empty();
231		let runtime_context = RuntimeContext::with_clock(Clock::Real);
232		let expressions = vec![create_alias_expression("custom_function", create_constant_text("data"))];
233
234		let result = evaluate_operator_config(&expressions, &routines, &runtime_context);
235
236		assert!(result.is_ok());
237	}
238}