Skip to main content

reifydb_engine/expression/cast/
mod.rs

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