Skip to main content

reifydb_evaluate/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		// The cast code must carry the underlying reason as its cause, not flatten to one code.
242		let err = result.unwrap_err();
243		let diagnostic = err.0;
244		assert_eq!(diagnostic.code, "CAST_004");
245		assert!(diagnostic.cause.is_some());
246		let cause = diagnostic.cause.unwrap();
247		assert_eq!(cause.code, "BOOLEAN_003"); // invalid_number_boolean
248	}
249
250	#[test]
251	fn test_cast_boolean_to_date_should_fail() {
252		let mut ctx = EvalContext::testing();
253		let result = evaluate(
254			&mut ctx,
255			&Cast(CastExpression {
256				fragment: Fragment::testing_empty(),
257				expression: Box::new(Constant(ConstantExpression::Bool {
258					fragment: Fragment::internal("true"),
259				})),
260				to: TypeExpression {
261					fragment: Fragment::testing_empty(),
262					ty: ValueType::Date,
263				},
264			}),
265		);
266
267		assert!(result.is_err());
268
269		// An impossible cast pair is unsupported (CAST_001), not a value-level failure.
270		let err = result.unwrap_err();
271		let diagnostic = err.0;
272		assert_eq!(diagnostic.code, "CAST_001");
273	}
274
275	#[test]
276	fn test_cast_text_to_decimal() {
277		let mut ctx = EvalContext::testing();
278		let result = evaluate(
279			&mut ctx,
280			&Cast(CastExpression {
281				fragment: Fragment::testing_empty(),
282				expression: Box::new(Constant(ConstantExpression::Text {
283					fragment: Fragment::internal("123.456789"),
284				})),
285				to: TypeExpression {
286					fragment: Fragment::testing_empty(),
287					ty: ValueType::Decimal,
288				},
289			}),
290		)
291		.unwrap();
292
293		if let ColumnBuffer::Decimal {
294			container,
295			..
296		} = result.data()
297		{
298			assert_eq!(container.len(), 1);
299			assert!(container.is_defined(0));
300			let value = &container[0];
301			assert_eq!(value.to_string(), "123.456789");
302		} else {
303			panic!("Expected Decimal column data");
304		}
305	}
306}