Skip to main content

reifydb_engine/expression/
eval.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_core::value::column::{ColumnWithName, cast::cast_column_data};
5use reifydb_rql::expression::Expression;
6
7use crate::{
8	Result,
9	expression::{
10		compile::compile_expression,
11		context::{CompileContext, EvalContext},
12	},
13};
14
15pub fn evaluate(ctx: &EvalContext, expr: &Expression) -> Result<ColumnWithName> {
16	let compile_ctx = CompileContext {
17		symbols: ctx.symbols,
18	};
19	let compiled = compile_expression(&compile_ctx, expr)?;
20	let column = compiled.execute(ctx)?;
21
22	if let Some(ty) = ctx.target.as_ref().map(|c| c.column_type()) {
23		let data = cast_column_data(ctx, column.data(), ty, &expr.lazy_fragment())?;
24		Ok(ColumnWithName {
25			name: column.name,
26			data,
27		})
28	} else {
29		Ok(column)
30	}
31}
32
33#[cfg(test)]
34pub mod tests {
35	use reifydb_core::value::column::buffer::ColumnBuffer;
36	use reifydb_rql::expression::{
37		CastExpression, ConstantExpression,
38		ConstantExpression::Number,
39		Expression::{Cast, Constant, Prefix},
40		PrefixExpression, PrefixOperator, TypeExpression,
41	};
42	use reifydb_value::{fragment::Fragment, value::value_type::ValueType};
43
44	use crate::expression::{context::EvalContext, eval::evaluate};
45
46	#[test]
47	fn test_cast_integer() {
48		let mut ctx = EvalContext::testing();
49		let result = evaluate(
50			&mut ctx,
51			&Cast(CastExpression {
52				fragment: Fragment::testing_empty(),
53				expression: Box::new(Constant(Number {
54					fragment: Fragment::internal("42"),
55				})),
56				to: TypeExpression {
57					fragment: Fragment::testing_empty(),
58					ty: ValueType::Int4,
59				},
60			}),
61		)
62		.unwrap();
63
64		assert_eq!(*result.data(), ColumnBuffer::int4([42]));
65	}
66
67	#[test]
68	fn test_cast_negative_integer() {
69		let mut ctx = EvalContext::testing();
70		let result = evaluate(
71			&mut ctx,
72			&Cast(CastExpression {
73				fragment: Fragment::testing_empty(),
74				expression: Box::new(Prefix(PrefixExpression {
75					operator: PrefixOperator::Minus(Fragment::testing_empty()),
76					expression: Box::new(Constant(Number {
77						fragment: Fragment::internal("42"),
78					})),
79					fragment: Fragment::testing_empty(),
80				})),
81				to: TypeExpression {
82					fragment: Fragment::testing_empty(),
83					ty: ValueType::Int4,
84				},
85			}),
86		)
87		.unwrap();
88
89		assert_eq!(*result.data(), ColumnBuffer::int4([-42]));
90	}
91
92	#[test]
93	fn test_cast_negative_min() {
94		let mut ctx = EvalContext::testing();
95		let result = evaluate(
96			&mut ctx,
97			&Cast(CastExpression {
98				fragment: Fragment::testing_empty(),
99				expression: Box::new(Prefix(PrefixExpression {
100					operator: PrefixOperator::Minus(Fragment::testing_empty()),
101					expression: Box::new(Constant(Number {
102						fragment: Fragment::internal("128"),
103					})),
104					fragment: Fragment::testing_empty(),
105				})),
106				to: TypeExpression {
107					fragment: Fragment::testing_empty(),
108					ty: ValueType::Int1,
109				},
110			}),
111		)
112		.unwrap();
113
114		assert_eq!(*result.data(), ColumnBuffer::int1([-128]));
115	}
116
117	#[test]
118	fn test_cast_float_8() {
119		let mut ctx = EvalContext::testing();
120		let result = evaluate(
121			&mut ctx,
122			&Cast(CastExpression {
123				fragment: Fragment::testing_empty(),
124				expression: Box::new(Constant(Number {
125					fragment: Fragment::internal("4.2"),
126				})),
127				to: TypeExpression {
128					fragment: Fragment::testing_empty(),
129					ty: ValueType::Float8,
130				},
131			}),
132		)
133		.unwrap();
134
135		assert_eq!(*result.data(), ColumnBuffer::float8([4.2]));
136	}
137
138	#[test]
139	fn test_cast_float_4() {
140		let mut ctx = EvalContext::testing();
141		let result = evaluate(
142			&mut ctx,
143			&Cast(CastExpression {
144				fragment: Fragment::testing_empty(),
145				expression: Box::new(Constant(Number {
146					fragment: Fragment::internal("4.2"),
147				})),
148				to: TypeExpression {
149					fragment: Fragment::testing_empty(),
150					ty: ValueType::Float4,
151				},
152			}),
153		)
154		.unwrap();
155
156		assert_eq!(*result.data(), ColumnBuffer::float4([4.2]));
157	}
158
159	#[test]
160	fn test_cast_negative_float_4() {
161		let mut ctx = EvalContext::testing();
162		let result = evaluate(
163			&mut ctx,
164			&Cast(CastExpression {
165				fragment: Fragment::testing_empty(),
166				expression: Box::new(Constant(Number {
167					fragment: Fragment::internal("-1.1"),
168				})),
169				to: TypeExpression {
170					fragment: Fragment::testing_empty(),
171					ty: ValueType::Float4,
172				},
173			}),
174		)
175		.unwrap();
176
177		assert_eq!(*result.data(), ColumnBuffer::float4([-1.1]));
178	}
179
180	#[test]
181	fn test_cast_negative_float_8() {
182		let mut ctx = EvalContext::testing();
183		let result = evaluate(
184			&mut ctx,
185			&Cast(CastExpression {
186				fragment: Fragment::testing_empty(),
187				expression: Box::new(Constant(Number {
188					fragment: Fragment::internal("-1.1"),
189				})),
190				to: TypeExpression {
191					fragment: Fragment::testing_empty(),
192					ty: ValueType::Float8,
193				},
194			}),
195		)
196		.unwrap();
197
198		assert_eq!(*result.data(), ColumnBuffer::float8([-1.1]));
199	}
200
201	#[test]
202	fn test_cast_string_to_bool() {
203		let mut ctx = EvalContext::testing();
204		let result = evaluate(
205			&mut ctx,
206			&Cast(CastExpression {
207				fragment: Fragment::testing_empty(),
208				expression: Box::new(Constant(ConstantExpression::Text {
209					fragment: Fragment::internal("0"),
210				})),
211				to: TypeExpression {
212					fragment: Fragment::testing_empty(),
213					ty: ValueType::Boolean,
214				},
215			}),
216		)
217		.unwrap();
218
219		assert_eq!(*result.data(), ColumnBuffer::bool([false]));
220	}
221
222	#[test]
223	fn test_cast_string_neg_one_to_bool_should_fail() {
224		let mut ctx = EvalContext::testing();
225		let result = evaluate(
226			&mut ctx,
227			&Cast(CastExpression {
228				fragment: Fragment::testing_empty(),
229				expression: Box::new(Constant(ConstantExpression::Text {
230					fragment: Fragment::internal("-1"),
231				})),
232				to: TypeExpression {
233					fragment: Fragment::testing_empty(),
234					ty: ValueType::Boolean,
235				},
236			}),
237		);
238
239		assert!(result.is_err());
240
241		// Check that the error is the expected CAST_004
242		// (invalid_boolean) error
243		let err = result.unwrap_err();
244		let diagnostic = err.0;
245		assert_eq!(diagnostic.code, "CAST_004");
246		assert!(diagnostic.cause.is_some());
247		let cause = diagnostic.cause.unwrap();
248		assert_eq!(cause.code, "BOOLEAN_003"); // invalid_number_boolean
249	}
250
251	#[test]
252	fn test_cast_boolean_to_date_should_fail() {
253		let mut ctx = EvalContext::testing();
254		let result = evaluate(
255			&mut ctx,
256			&Cast(CastExpression {
257				fragment: Fragment::testing_empty(),
258				expression: Box::new(Constant(ConstantExpression::Bool {
259					fragment: Fragment::internal("true"),
260				})),
261				to: TypeExpression {
262					fragment: Fragment::testing_empty(),
263					ty: ValueType::Date,
264				},
265			}),
266		);
267
268		assert!(result.is_err());
269
270		// Check that the error is the expected CAST_001
271		// (unsupported_cast) error
272		let err = result.unwrap_err();
273		let diagnostic = err.0;
274		assert_eq!(diagnostic.code, "CAST_001");
275	}
276
277	#[test]
278	fn test_cast_text_to_decimal() {
279		let mut ctx = EvalContext::testing();
280		let result = evaluate(
281			&mut ctx,
282			&Cast(CastExpression {
283				fragment: Fragment::testing_empty(),
284				expression: Box::new(Constant(ConstantExpression::Text {
285					fragment: Fragment::internal("123.456789"),
286				})),
287				to: TypeExpression {
288					fragment: Fragment::testing_empty(),
289					ty: ValueType::Decimal,
290				},
291			}),
292		)
293		.unwrap();
294
295		if let ColumnBuffer::Decimal {
296			container,
297			..
298		} = result.data()
299		{
300			assert_eq!(container.len(), 1);
301			assert!(container.is_defined(0));
302			let value = &container[0];
303			assert_eq!(value.to_string(), "123.456789");
304		} else {
305			panic!("Expected Decimal column data");
306		}
307	}
308}