Skip to main content

reifydb_engine/expression/cast/
mod.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2026 ReifyDB
3
4pub mod any;
5pub mod blob;
6pub mod boolean;
7pub mod number;
8pub mod temporal;
9pub mod text;
10pub mod uuid;
11
12use reifydb_core::value::column::buffer::ColumnBuffer;
13use reifydb_value::{
14	error::TypeError, fragment::LazyFragment, storage::DataBitVec, util::bitvec::BitVec,
15	value::value_type::ValueType,
16};
17
18use crate::{
19	Result,
20	expression::{cast::uuid::to_uuid, context::EvalContext},
21};
22
23pub fn cast_column_data(
24	ctx: &EvalContext,
25	data: &ColumnBuffer,
26	target: ValueType,
27	lazy_fragment: impl LazyFragment + Clone,
28) -> Result<ColumnBuffer> {
29	if let ColumnBuffer::Option {
30		inner,
31		bitvec,
32	} = data
33	{
34		let inner_target = match &target {
35			ValueType::Option(t) => t.as_ref().clone(),
36			other => other.clone(),
37		};
38		let total_len = inner.len();
39		let defined_count = DataBitVec::count_ones(bitvec);
40
41		if defined_count == 0 {
42			return Ok(ColumnBuffer::none_typed(inner_target, total_len));
43		}
44
45		if defined_count < total_len {
46			let mut compacted = inner.as_ref().clone();
47			compacted.filter(bitvec)?;
48
49			let mut cast_compacted = cast_column_data(ctx, &compacted, inner_target, lazy_fragment)?;
50
51			let sentinel = defined_count;
52			let mut expand_indices = Vec::with_capacity(total_len);
53			let mut src_idx = 0usize;
54			for i in 0..total_len {
55				if DataBitVec::get(bitvec, i) {
56					expand_indices.push(src_idx);
57					src_idx += 1;
58				} else {
59					expand_indices.push(sentinel);
60				}
61			}
62			cast_compacted.reorder(&expand_indices);
63
64			return Ok(match cast_compacted {
65				already @ ColumnBuffer::Option {
66					..
67				} => already,
68				other => ColumnBuffer::Option {
69					inner: Box::new(other),
70					bitvec: bitvec.clone(),
71				},
72			});
73		}
74
75		let cast_inner = cast_column_data(ctx, inner, inner_target, lazy_fragment)?;
76		return Ok(match cast_inner {
77			already @ ColumnBuffer::Option {
78				..
79			} => already,
80			other => ColumnBuffer::Option {
81				inner: Box::new(other),
82				bitvec: bitvec.clone(),
83			},
84		});
85	}
86
87	if let ValueType::Option(inner_target) = &target {
88		let cast_inner = cast_column_data(ctx, data, *inner_target.clone(), lazy_fragment)?;
89		return Ok(match cast_inner {
90			already @ ColumnBuffer::Option {
91				..
92			} => already,
93			other => {
94				let bitvec = BitVec::repeat(other.len(), true);
95				ColumnBuffer::Option {
96					inner: Box::new(other),
97					bitvec,
98				}
99			}
100		});
101	}
102
103	let shape_type = data.get_type();
104	if target == shape_type {
105		return Ok(data.clone());
106	}
107	match (&shape_type, &target) {
108		(ValueType::Any, _) => any::from_any(ctx, data, target, lazy_fragment),
109		(_, t) if t.is_number() => number::to_number(ctx, data, target, lazy_fragment),
110		(_, t) if t.is_blob() => blob::to_blob(data, lazy_fragment),
111		(_, t) if t.is_bool() => boolean::to_boolean(data, lazy_fragment),
112		(_, t) if t.is_utf8() => text::to_text(data, lazy_fragment),
113		(_, t) if t.is_temporal() => temporal::to_temporal(data, target, lazy_fragment),
114		(_, ValueType::IdentityId) => to_uuid(data, target, lazy_fragment),
115		(ValueType::IdentityId, _) => to_uuid(data, target, lazy_fragment),
116		(_, t) if t.is_uuid() => to_uuid(data, target, lazy_fragment),
117		(source, t) if source.is_uuid() || t.is_uuid() => to_uuid(data, target, lazy_fragment),
118		_ => Err(TypeError::UnsupportedCast {
119			from: shape_type,
120			to: target,
121			fragment: lazy_fragment.fragment(),
122		}
123		.into()),
124	}
125}
126
127#[cfg(test)]
128pub mod tests {
129	use reifydb_core::value::column::buffer::ColumnBuffer;
130	use reifydb_rql::expression::{
131		CastExpression, ConstantExpression,
132		ConstantExpression::Number,
133		Expression::{Cast, Constant, Prefix},
134		PrefixExpression, PrefixOperator, TypeExpression,
135	};
136	use reifydb_value::{fragment::Fragment, value::value_type::ValueType};
137
138	use crate::expression::{context::EvalContext, eval::evaluate};
139
140	#[test]
141	fn test_cast_integer() {
142		let mut ctx = EvalContext::testing();
143		let result = evaluate(
144			&mut ctx,
145			&Cast(CastExpression {
146				fragment: Fragment::testing_empty(),
147				expression: Box::new(Constant(Number {
148					fragment: Fragment::internal("42"),
149				})),
150				to: TypeExpression {
151					fragment: Fragment::testing_empty(),
152					ty: ValueType::Int4,
153				},
154			}),
155		)
156		.unwrap();
157
158		assert_eq!(*result.data(), ColumnBuffer::int4([42]));
159	}
160
161	#[test]
162	fn test_cast_negative_integer() {
163		let mut ctx = EvalContext::testing();
164		let result = evaluate(
165			&mut ctx,
166			&Cast(CastExpression {
167				fragment: Fragment::testing_empty(),
168				expression: Box::new(Prefix(PrefixExpression {
169					operator: PrefixOperator::Minus(Fragment::testing_empty()),
170					expression: Box::new(Constant(Number {
171						fragment: Fragment::internal("42"),
172					})),
173					fragment: Fragment::testing_empty(),
174				})),
175				to: TypeExpression {
176					fragment: Fragment::testing_empty(),
177					ty: ValueType::Int4,
178				},
179			}),
180		)
181		.unwrap();
182
183		assert_eq!(*result.data(), ColumnBuffer::int4([-42]));
184	}
185
186	#[test]
187	fn test_cast_negative_min() {
188		let mut ctx = EvalContext::testing();
189		let result = evaluate(
190			&mut ctx,
191			&Cast(CastExpression {
192				fragment: Fragment::testing_empty(),
193				expression: Box::new(Prefix(PrefixExpression {
194					operator: PrefixOperator::Minus(Fragment::testing_empty()),
195					expression: Box::new(Constant(Number {
196						fragment: Fragment::internal("128"),
197					})),
198					fragment: Fragment::testing_empty(),
199				})),
200				to: TypeExpression {
201					fragment: Fragment::testing_empty(),
202					ty: ValueType::Int1,
203				},
204			}),
205		)
206		.unwrap();
207
208		assert_eq!(*result.data(), ColumnBuffer::int1([-128]));
209	}
210
211	#[test]
212	fn test_cast_float_8() {
213		let mut ctx = EvalContext::testing();
214		let result = evaluate(
215			&mut ctx,
216			&Cast(CastExpression {
217				fragment: Fragment::testing_empty(),
218				expression: Box::new(Constant(Number {
219					fragment: Fragment::internal("4.2"),
220				})),
221				to: TypeExpression {
222					fragment: Fragment::testing_empty(),
223					ty: ValueType::Float8,
224				},
225			}),
226		)
227		.unwrap();
228
229		assert_eq!(*result.data(), ColumnBuffer::float8([4.2]));
230	}
231
232	#[test]
233	fn test_cast_float_4() {
234		let mut ctx = EvalContext::testing();
235		let result = evaluate(
236			&mut ctx,
237			&Cast(CastExpression {
238				fragment: Fragment::testing_empty(),
239				expression: Box::new(Constant(Number {
240					fragment: Fragment::internal("4.2"),
241				})),
242				to: TypeExpression {
243					fragment: Fragment::testing_empty(),
244					ty: ValueType::Float4,
245				},
246			}),
247		)
248		.unwrap();
249
250		assert_eq!(*result.data(), ColumnBuffer::float4([4.2]));
251	}
252
253	#[test]
254	fn test_cast_negative_float_4() {
255		let mut ctx = EvalContext::testing();
256		let result = evaluate(
257			&mut ctx,
258			&Cast(CastExpression {
259				fragment: Fragment::testing_empty(),
260				expression: Box::new(Constant(Number {
261					fragment: Fragment::internal("-1.1"),
262				})),
263				to: TypeExpression {
264					fragment: Fragment::testing_empty(),
265					ty: ValueType::Float4,
266				},
267			}),
268		)
269		.unwrap();
270
271		assert_eq!(*result.data(), ColumnBuffer::float4([-1.1]));
272	}
273
274	#[test]
275	fn test_cast_negative_float_8() {
276		let mut ctx = EvalContext::testing();
277		let result = evaluate(
278			&mut ctx,
279			&Cast(CastExpression {
280				fragment: Fragment::testing_empty(),
281				expression: Box::new(Constant(Number {
282					fragment: Fragment::internal("-1.1"),
283				})),
284				to: TypeExpression {
285					fragment: Fragment::testing_empty(),
286					ty: ValueType::Float8,
287				},
288			}),
289		)
290		.unwrap();
291
292		assert_eq!(*result.data(), ColumnBuffer::float8([-1.1]));
293	}
294
295	#[test]
296	fn test_cast_string_to_bool() {
297		let mut ctx = EvalContext::testing();
298		let result = evaluate(
299			&mut ctx,
300			&Cast(CastExpression {
301				fragment: Fragment::testing_empty(),
302				expression: Box::new(Constant(ConstantExpression::Text {
303					fragment: Fragment::internal("0"),
304				})),
305				to: TypeExpression {
306					fragment: Fragment::testing_empty(),
307					ty: ValueType::Boolean,
308				},
309			}),
310		)
311		.unwrap();
312
313		assert_eq!(*result.data(), ColumnBuffer::bool([false]));
314	}
315
316	#[test]
317	fn test_cast_string_neg_one_to_bool_should_fail() {
318		let mut ctx = EvalContext::testing();
319		let result = evaluate(
320			&mut ctx,
321			&Cast(CastExpression {
322				fragment: Fragment::testing_empty(),
323				expression: Box::new(Constant(ConstantExpression::Text {
324					fragment: Fragment::internal("-1"),
325				})),
326				to: TypeExpression {
327					fragment: Fragment::testing_empty(),
328					ty: ValueType::Boolean,
329				},
330			}),
331		);
332
333		assert!(result.is_err());
334
335		// Check that the error is the expected CAST_004
336		// (invalid_boolean) error
337		let err = result.unwrap_err();
338		let diagnostic = err.0;
339		assert_eq!(diagnostic.code, "CAST_004");
340		assert!(diagnostic.cause.is_some());
341		let cause = diagnostic.cause.unwrap();
342		assert_eq!(cause.code, "BOOLEAN_003"); // invalid_number_boolean
343	}
344
345	#[test]
346	fn test_cast_boolean_to_date_should_fail() {
347		let mut ctx = EvalContext::testing();
348		let result = evaluate(
349			&mut ctx,
350			&Cast(CastExpression {
351				fragment: Fragment::testing_empty(),
352				expression: Box::new(Constant(ConstantExpression::Bool {
353					fragment: Fragment::internal("true"),
354				})),
355				to: TypeExpression {
356					fragment: Fragment::testing_empty(),
357					ty: ValueType::Date,
358				},
359			}),
360		);
361
362		assert!(result.is_err());
363
364		// Check that the error is the expected CAST_001
365		// (unsupported_cast) error
366		let err = result.unwrap_err();
367		let diagnostic = err.0;
368		assert_eq!(diagnostic.code, "CAST_001");
369	}
370
371	#[test]
372	fn test_cast_text_to_decimal() {
373		let mut ctx = EvalContext::testing();
374		let result = evaluate(
375			&mut ctx,
376			&Cast(CastExpression {
377				fragment: Fragment::testing_empty(),
378				expression: Box::new(Constant(ConstantExpression::Text {
379					fragment: Fragment::internal("123.456789"),
380				})),
381				to: TypeExpression {
382					fragment: Fragment::testing_empty(),
383					ty: ValueType::Decimal,
384				},
385			}),
386		)
387		.unwrap();
388
389		if let ColumnBuffer::Decimal {
390			container,
391			..
392		} = result.data()
393		{
394			assert_eq!(container.len(), 1);
395			assert!(container.is_defined(0));
396			let value = &container[0];
397			assert_eq!(value.to_string(), "123.456789");
398		} else {
399			panic!("Expected Decimal column data");
400		}
401	}
402}