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