Skip to main content

reifydb_engine/expression/cast/
text.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::fmt::Display;
5
6use reifydb_core::value::column::buffer::ColumnBuffer;
7use reifydb_type::{
8	error::TypeError,
9	fragment::LazyFragment,
10	value::{
11		blob::Blob,
12		container::{
13			blob::BlobContainer, bool::BoolContainer, identity_id::IdentityIdContainer,
14			number::NumberContainer, temporal::TemporalContainer, uuid::UuidContainer,
15		},
16		is::{IsNumber, IsTemporal, IsUuid},
17		r#type::Type,
18	},
19};
20
21use crate::{Result, error::CastError};
22
23pub fn to_text(data: &ColumnBuffer, lazy_fragment: impl LazyFragment) -> Result<ColumnBuffer> {
24	match data {
25		ColumnBuffer::Blob {
26			container,
27			..
28		} => from_blob(container, lazy_fragment),
29		ColumnBuffer::Bool(container) => from_bool(container),
30		ColumnBuffer::Int1(container) => from_number(container),
31		ColumnBuffer::Int2(container) => from_number(container),
32		ColumnBuffer::Int4(container) => from_number(container),
33		ColumnBuffer::Int8(container) => from_number(container),
34		ColumnBuffer::Int16(container) => from_number(container),
35		ColumnBuffer::Uint1(container) => from_number(container),
36		ColumnBuffer::Uint2(container) => from_number(container),
37		ColumnBuffer::Uint4(container) => from_number(container),
38		ColumnBuffer::Uint8(container) => from_number(container),
39		ColumnBuffer::Uint16(container) => from_number(container),
40		ColumnBuffer::Float4(container) => from_number(container),
41		ColumnBuffer::Float8(container) => from_number(container),
42		ColumnBuffer::Date(container) => from_temporal(container),
43		ColumnBuffer::DateTime(container) => from_temporal(container),
44		ColumnBuffer::Time(container) => from_temporal(container),
45		ColumnBuffer::Duration(container) => from_temporal(container),
46		ColumnBuffer::Uuid4(container) => from_uuid(container),
47		ColumnBuffer::Uuid7(container) => from_uuid(container),
48		ColumnBuffer::IdentityId(container) => from_identity_id(container),
49		_ => {
50			let from = data.get_type();
51			Err(TypeError::UnsupportedCast {
52				from,
53				to: Type::Utf8,
54				fragment: lazy_fragment.fragment(),
55			}
56			.into())
57		}
58	}
59}
60
61#[inline]
62pub fn from_blob(container: &BlobContainer, lazy_fragment: impl LazyFragment) -> Result<ColumnBuffer> {
63	let mut out = ColumnBuffer::with_capacity(Type::Utf8, container.len());
64	for idx in 0..container.len() {
65		if container.is_defined(idx) {
66			let blob = Blob::new(container.get(idx).unwrap_or(&[]).to_vec());
67			match blob.to_utf8() {
68				Ok(s) => out.push(s),
69				Err(e) => {
70					return Err(CastError::InvalidBlobToUtf8 {
71						fragment: lazy_fragment.fragment(),
72						cause: e.diagnostic(),
73					}
74					.into());
75				}
76			}
77		} else {
78			out.push_none()
79		}
80	}
81	Ok(out)
82}
83
84#[inline]
85fn from_bool(container: &BoolContainer) -> Result<ColumnBuffer> {
86	let mut out = ColumnBuffer::with_capacity(Type::Utf8, container.len());
87	for idx in 0..container.len() {
88		if container.is_defined(idx) {
89			out.push::<String>(container.data().get(idx).to_string());
90		} else {
91			out.push_none();
92		}
93	}
94	Ok(out)
95}
96
97#[inline]
98fn from_number<T>(container: &NumberContainer<T>) -> Result<ColumnBuffer>
99where
100	T: Copy + Display + IsNumber + Default,
101{
102	let mut out = ColumnBuffer::with_capacity(Type::Utf8, container.len());
103	for idx in 0..container.len() {
104		if container.is_defined(idx) {
105			out.push::<String>(container[idx].to_string());
106		} else {
107			out.push_none();
108		}
109	}
110	Ok(out)
111}
112
113#[inline]
114fn from_temporal<T>(container: &TemporalContainer<T>) -> Result<ColumnBuffer>
115where
116	T: Copy + Display + IsTemporal + Default,
117{
118	let mut out = ColumnBuffer::with_capacity(Type::Utf8, container.len());
119	for idx in 0..container.len() {
120		if container.is_defined(idx) {
121			out.push::<String>(container[idx].to_string());
122		} else {
123			out.push_none();
124		}
125	}
126	Ok(out)
127}
128
129#[inline]
130fn from_uuid<T>(container: &UuidContainer<T>) -> Result<ColumnBuffer>
131where
132	T: Copy + Display + IsUuid + Default,
133{
134	let mut out = ColumnBuffer::with_capacity(Type::Utf8, container.len());
135	for idx in 0..container.len() {
136		if container.is_defined(idx) {
137			out.push::<String>(container[idx].to_string());
138		} else {
139			out.push_none();
140		}
141	}
142	Ok(out)
143}
144
145#[inline]
146fn from_identity_id(container: &IdentityIdContainer) -> Result<ColumnBuffer> {
147	let mut out = ColumnBuffer::with_capacity(Type::Utf8, container.len());
148	for idx in 0..container.len() {
149		if container.is_defined(idx) {
150			out.push::<String>(container[idx].to_string());
151		} else {
152			out.push_none();
153		}
154	}
155	Ok(out)
156}
157
158#[cfg(test)]
159pub mod tests {
160	use reifydb_core::value::column::buffer::ColumnBuffer;
161	use reifydb_type::{
162		fragment::Fragment,
163		value::{blob::Blob, container::blob::BlobContainer},
164	};
165
166	use crate::expression::cast::text::from_blob;
167
168	#[test]
169	fn test_from_blob() {
170		let blobs = vec![
171			Blob::from_utf8(Fragment::internal("Hello")),
172			Blob::from_utf8(Fragment::internal("World")),
173		];
174		let container = BlobContainer::new(blobs);
175
176		let result = from_blob(&container, || Fragment::testing_empty()).unwrap();
177
178		match result {
179			ColumnBuffer::Utf8 {
180				container,
181				..
182			} => {
183				assert_eq!(container.get(0), Some("Hello"));
184				assert_eq!(container.get(1), Some("World"));
185			}
186			_ => panic!("Expected UTF8 column data"),
187		}
188	}
189
190	#[test]
191	fn test_from_blob_invalid() {
192		let blobs = vec![
193			Blob::new(vec![0xFF, 0xFE]), // Invalid UTF-8
194		];
195		let container = BlobContainer::new(blobs);
196
197		let result = from_blob(&container, || Fragment::testing_empty());
198		assert!(result.is_err());
199	}
200}