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			// Reconstruct a Blob from the borrowed bytes so the existing
67			// to_utf8 invariant logic + diagnostic stays unchanged.
68			let blob = Blob::new(container.get(idx).unwrap_or(&[]).to_vec());
69			match blob.to_utf8() {
70				Ok(s) => out.push(s),
71				Err(e) => {
72					return Err(CastError::InvalidBlobToUtf8 {
73						fragment: lazy_fragment.fragment(),
74						cause: e.diagnostic(),
75					}
76					.into());
77				}
78			}
79		} else {
80			out.push_none()
81		}
82	}
83	Ok(out)
84}
85
86#[inline]
87fn from_bool(container: &BoolContainer) -> Result<ColumnBuffer> {
88	let mut out = ColumnBuffer::with_capacity(Type::Utf8, container.len());
89	for idx in 0..container.len() {
90		if container.is_defined(idx) {
91			out.push::<String>(container.data().get(idx).to_string());
92		} else {
93			out.push_none();
94		}
95	}
96	Ok(out)
97}
98
99#[inline]
100fn from_number<T>(container: &NumberContainer<T>) -> Result<ColumnBuffer>
101where
102	T: Copy + Display + IsNumber + Default,
103{
104	let mut out = ColumnBuffer::with_capacity(Type::Utf8, container.len());
105	for idx in 0..container.len() {
106		if container.is_defined(idx) {
107			out.push::<String>(container[idx].to_string());
108		} else {
109			out.push_none();
110		}
111	}
112	Ok(out)
113}
114
115#[inline]
116fn from_temporal<T>(container: &TemporalContainer<T>) -> Result<ColumnBuffer>
117where
118	T: Copy + Display + IsTemporal + Default,
119{
120	let mut out = ColumnBuffer::with_capacity(Type::Utf8, container.len());
121	for idx in 0..container.len() {
122		if container.is_defined(idx) {
123			out.push::<String>(container[idx].to_string());
124		} else {
125			out.push_none();
126		}
127	}
128	Ok(out)
129}
130
131#[inline]
132fn from_uuid<T>(container: &UuidContainer<T>) -> Result<ColumnBuffer>
133where
134	T: Copy + Display + IsUuid + Default,
135{
136	let mut out = ColumnBuffer::with_capacity(Type::Utf8, container.len());
137	for idx in 0..container.len() {
138		if container.is_defined(idx) {
139			out.push::<String>(container[idx].to_string());
140		} else {
141			out.push_none();
142		}
143	}
144	Ok(out)
145}
146
147#[inline]
148fn from_identity_id(container: &IdentityIdContainer) -> Result<ColumnBuffer> {
149	let mut out = ColumnBuffer::with_capacity(Type::Utf8, container.len());
150	for idx in 0..container.len() {
151		if container.is_defined(idx) {
152			out.push::<String>(container[idx].to_string());
153		} else {
154			out.push_none();
155		}
156	}
157	Ok(out)
158}
159
160#[cfg(test)]
161pub mod tests {
162	use reifydb_core::value::column::buffer::ColumnBuffer;
163	use reifydb_type::{
164		fragment::Fragment,
165		value::{blob::Blob, container::blob::BlobContainer},
166	};
167
168	use crate::expression::cast::text::from_blob;
169
170	#[test]
171	fn test_from_blob() {
172		let blobs = vec![
173			Blob::from_utf8(Fragment::internal("Hello")),
174			Blob::from_utf8(Fragment::internal("World")),
175		];
176		let container = BlobContainer::new(blobs);
177
178		let result = from_blob(&container, || Fragment::testing_empty()).unwrap();
179
180		match result {
181			ColumnBuffer::Utf8 {
182				container,
183				..
184			} => {
185				assert_eq!(container.get(0), Some("Hello"));
186				assert_eq!(container.get(1), Some("World"));
187			}
188			_ => panic!("Expected UTF8 column data"),
189		}
190	}
191
192	#[test]
193	fn test_from_blob_invalid() {
194		let blobs = vec![
195			Blob::new(vec![0xFF, 0xFE]), // Invalid UTF-8
196		];
197		let container = BlobContainer::new(blobs);
198
199		let result = from_blob(&container, || Fragment::testing_empty());
200		assert!(result.is_err());
201	}
202}