1use arrow_array::ArrayRef;
5use arrow_schema::FieldRef;
6use reifydb_core::{
7 expression::Expression,
8 value::column::{cast::cast_column_data, write::check_digest_write},
9};
10use reifydb_value::{
11 fragment::LazyFragment,
12 value::{
13 Value,
14 column_view::ColumnView,
15 constraint::{TypeConstraint, precision::Precision},
16 value_type::ValueType,
17 },
18};
19
20use crate::{Result, expression::context::EvalContext, lower::LoweredExpr};
21
22pub fn evaluate(ctx: &EvalContext, expr: &Expression) -> Result<(FieldRef, ArrayRef)> {
23 let column = LoweredExpr::new(expr.clone(), "evaluate").evaluate(ctx)?;
24
25 if let Some(ty) = ctx.target.as_ref().map(|c| c.column_type()) {
26 let view = ColumnView::try_from(&column)?;
27 check_digest_write(&view, &ty, expr.lazy_fragment())?;
28 cast_for_write(ctx, &view, ty, &expr.lazy_fragment())
29 } else {
30 Ok(column)
31 }
32}
33
34pub fn cast_for_write(
35 ctx: &EvalContext,
36 view: &ColumnView<'_>,
37 target: ValueType,
38 fragment: impl LazyFragment + Clone,
39) -> Result<(FieldRef, ArrayRef)> {
40 refuse_lost_scale(view, &target, fragment.clone())?;
41 cast_column_data(ctx, view, target, fragment)
42}
43
44pub fn loses_scale(value: &Value, target: &ValueType) -> bool {
45 matches!(
46 (value, target.inner_type()),
47 (Value::Decimal(decimal), ValueType::Decimal { scale, .. })
48 if decimal.scale() > scale.value() && decimal.rescale(scale.value()).is_none()
49 )
50}
51
52fn refuse_lost_scale(data: &ColumnView, target: &ValueType, fragment: impl LazyFragment) -> Result<()> {
53 match (data.get_type().inner_type(), target.inner_type()) {
54 (
55 ValueType::Decimal {
56 scale: from,
57 ..
58 },
59 ValueType::Decimal {
60 scale: to,
61 ..
62 },
63 ) if from > to => data.iter().filter(|value| loses_scale(value, target)).try_for_each(|mut value| {
64 let exact = TypeConstraint::unconstrained(ValueType::decimal(Precision::MAX, *to));
65 exact.coerce(&mut value).map_err(|mut e| {
66 e.0.fragment = fragment.fragment();
67 e
68 })
69 }),
70 _ => Ok(()),
71 }
72}
73
74#[cfg(test)]
75pub mod tests {
76 use reifydb_core::{
77 expression::{
78 CastExpression, ConstantExpression,
79 ConstantExpression::Number,
80 Expression::{Cast, Constant, Prefix},
81 PrefixExpression, PrefixOperator, TypeExpression,
82 },
83 value::column::factory::{bool, float4, float8, int1, int4},
84 };
85 use reifydb_value::{
86 fragment::Fragment,
87 value::{
88 column_view::{ColumnView, ViewData},
89 container::decimal_array::{decimal_at, decimals},
90 value_type::ValueType,
91 },
92 };
93
94 use crate::expression::{context::EvalContext, eval::evaluate};
95
96 #[test]
97 fn test_cast_integer() {
98 let ctx = EvalContext::testing();
99 let result = evaluate(
100 &ctx,
101 &Cast(CastExpression {
102 fragment: Fragment::testing_empty(),
103 expression: Box::new(Constant(Number {
104 fragment: Fragment::internal("42"),
105 })),
106 to: TypeExpression {
107 fragment: Fragment::testing_empty(),
108 ty: ValueType::Int4,
109 constraint: None,
110 },
111 }),
112 )
113 .unwrap();
114
115 assert_eq!(result.1.as_ref(), int4("", [42]).1.as_ref());
116 }
117
118 #[test]
119 fn test_cast_negative_integer() {
120 let ctx = EvalContext::testing();
121 let result = evaluate(
122 &ctx,
123 &Cast(CastExpression {
124 fragment: Fragment::testing_empty(),
125 expression: Box::new(Prefix(PrefixExpression {
126 operator: PrefixOperator::Minus(Fragment::testing_empty()),
127 expression: Box::new(Constant(Number {
128 fragment: Fragment::internal("42"),
129 })),
130 fragment: Fragment::testing_empty(),
131 })),
132 to: TypeExpression {
133 fragment: Fragment::testing_empty(),
134 ty: ValueType::Int4,
135 constraint: None,
136 },
137 }),
138 )
139 .unwrap();
140
141 assert_eq!(result.1.as_ref(), int4("", [-42]).1.as_ref());
142 }
143
144 #[test]
145 fn test_cast_negative_min() {
146 let ctx = EvalContext::testing();
147 let result = evaluate(
148 &ctx,
149 &Cast(CastExpression {
150 fragment: Fragment::testing_empty(),
151 expression: Box::new(Prefix(PrefixExpression {
152 operator: PrefixOperator::Minus(Fragment::testing_empty()),
153 expression: Box::new(Constant(Number {
154 fragment: Fragment::internal("128"),
155 })),
156 fragment: Fragment::testing_empty(),
157 })),
158 to: TypeExpression {
159 fragment: Fragment::testing_empty(),
160 ty: ValueType::Int1,
161 constraint: None,
162 },
163 }),
164 )
165 .unwrap();
166
167 assert_eq!(result.1.as_ref(), int1("", [-128]).1.as_ref());
168 }
169
170 #[test]
171 fn test_cast_float_8() {
172 let ctx = EvalContext::testing();
173 let result = evaluate(
174 &ctx,
175 &Cast(CastExpression {
176 fragment: Fragment::testing_empty(),
177 expression: Box::new(Constant(Number {
178 fragment: Fragment::internal("4.2"),
179 })),
180 to: TypeExpression {
181 fragment: Fragment::testing_empty(),
182 ty: ValueType::Float8,
183 constraint: None,
184 },
185 }),
186 )
187 .unwrap();
188
189 assert_eq!(result.1.as_ref(), float8("", [4.2]).1.as_ref());
190 }
191
192 #[test]
193 fn test_cast_float_4() {
194 let ctx = EvalContext::testing();
195 let result = evaluate(
196 &ctx,
197 &Cast(CastExpression {
198 fragment: Fragment::testing_empty(),
199 expression: Box::new(Constant(Number {
200 fragment: Fragment::internal("4.2"),
201 })),
202 to: TypeExpression {
203 fragment: Fragment::testing_empty(),
204 ty: ValueType::Float4,
205 constraint: None,
206 },
207 }),
208 )
209 .unwrap();
210
211 assert_eq!(result.1.as_ref(), float4("", [4.2]).1.as_ref());
212 }
213
214 #[test]
215 fn test_cast_negative_float_4() {
216 let ctx = EvalContext::testing();
217 let result = evaluate(
218 &ctx,
219 &Cast(CastExpression {
220 fragment: Fragment::testing_empty(),
221 expression: Box::new(Constant(Number {
222 fragment: Fragment::internal("-1.1"),
223 })),
224 to: TypeExpression {
225 fragment: Fragment::testing_empty(),
226 ty: ValueType::Float4,
227 constraint: None,
228 },
229 }),
230 )
231 .unwrap();
232
233 assert_eq!(result.1.as_ref(), float4("", [-1.1]).1.as_ref());
234 }
235
236 #[test]
237 fn test_cast_negative_float_8() {
238 let ctx = EvalContext::testing();
239 let result = evaluate(
240 &ctx,
241 &Cast(CastExpression {
242 fragment: Fragment::testing_empty(),
243 expression: Box::new(Constant(Number {
244 fragment: Fragment::internal("-1.1"),
245 })),
246 to: TypeExpression {
247 fragment: Fragment::testing_empty(),
248 ty: ValueType::Float8,
249 constraint: None,
250 },
251 }),
252 )
253 .unwrap();
254
255 assert_eq!(result.1.as_ref(), float8("", [-1.1]).1.as_ref());
256 }
257
258 #[test]
259 fn test_cast_string_to_bool() {
260 let ctx = EvalContext::testing();
261 let result = evaluate(
262 &ctx,
263 &Cast(CastExpression {
264 fragment: Fragment::testing_empty(),
265 expression: Box::new(Constant(ConstantExpression::Text {
266 fragment: Fragment::internal("0"),
267 })),
268 to: TypeExpression {
269 fragment: Fragment::testing_empty(),
270 ty: ValueType::Boolean,
271 constraint: None,
272 },
273 }),
274 )
275 .unwrap();
276
277 assert_eq!(result.1.as_ref(), bool("", [false]).1.as_ref());
278 }
279
280 #[test]
281 fn test_cast_string_neg_one_to_bool_should_fail() {
282 let ctx = EvalContext::testing();
283 let result = evaluate(
284 &ctx,
285 &Cast(CastExpression {
286 fragment: Fragment::testing_empty(),
287 expression: Box::new(Constant(ConstantExpression::Text {
288 fragment: Fragment::internal("-1"),
289 })),
290 to: TypeExpression {
291 fragment: Fragment::testing_empty(),
292 ty: ValueType::Boolean,
293 constraint: None,
294 },
295 }),
296 );
297
298 assert!(result.is_err());
299
300 let err = result.unwrap_err();
302 let diagnostic = err.0;
303 assert_eq!(diagnostic.code, "CAST_004");
304 assert!(diagnostic.cause.is_some());
305 let cause = diagnostic.cause.unwrap();
306 assert_eq!(cause.code, "BOOLEAN_003"); }
308
309 #[test]
310 fn test_cast_boolean_to_date_should_fail() {
311 let ctx = EvalContext::testing();
312 let result = evaluate(
313 &ctx,
314 &Cast(CastExpression {
315 fragment: Fragment::testing_empty(),
316 expression: Box::new(Constant(ConstantExpression::Bool {
317 fragment: Fragment::internal("true"),
318 })),
319 to: TypeExpression {
320 fragment: Fragment::testing_empty(),
321 ty: ValueType::Date,
322 constraint: None,
323 },
324 }),
325 );
326
327 assert!(result.is_err());
328
329 let err = result.unwrap_err();
331 let diagnostic = err.0;
332 assert_eq!(diagnostic.code, "CAST_001");
333 }
334
335 #[test]
336 fn test_cast_text_to_decimal() {
337 let ctx = EvalContext::testing();
338 let result = evaluate(
339 &ctx,
340 &Cast(CastExpression {
341 fragment: Fragment::testing_empty(),
342 expression: Box::new(Constant(ConstantExpression::Text {
343 fragment: Fragment::internal("123.456789"),
344 })),
345 to: TypeExpression {
346 fragment: Fragment::testing_empty(),
347 ty: ValueType::DECIMAL,
348 constraint: None,
349 },
350 }),
351 )
352 .unwrap();
353
354 if let ViewData::Decimal(container) = &ColumnView::try_from(&result).unwrap().data {
355 assert_eq!(container.len(), 1);
356 assert!(decimal_at(container, 0).is_some());
357 let value = &decimals(container)[0];
358 assert_eq!(value.to_string(), "123.4567890000");
359 } else {
360 panic!("Expected Decimal column data");
361 }
362 }
363}