Skip to main content

reifydb_engine/expression/cast/
text.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
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, number::NumberContainer, temporal::TemporalContainer,
13			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		_ => {
48			let from = data.get_type();
49			Err(TypeError::UnsupportedCast {
50				from,
51				to: Type::Utf8,
52				fragment: lazy_fragment.fragment(),
53			}
54			.into())
55		}
56	}
57}
58
59#[inline]
60pub fn from_blob(container: &BlobContainer, lazy_fragment: impl LazyFragment) -> Result<ColumnData> {
61	let mut out = ColumnData::with_capacity(Type::Utf8, container.len());
62	for idx in 0..container.len() {
63		if container.is_defined(idx) {
64			match container[idx].to_utf8() {
65				Ok(s) => out.push(s),
66				Err(e) => {
67					return Err(CastError::InvalidBlobToUtf8 {
68						fragment: lazy_fragment.fragment(),
69						cause: e.diagnostic(),
70					}
71					.into());
72				}
73			}
74		} else {
75			out.push_none()
76		}
77	}
78	Ok(out)
79}
80
81#[inline]
82fn from_bool(container: &BoolContainer) -> Result<ColumnData> {
83	let mut out = ColumnData::with_capacity(Type::Utf8, container.len());
84	for idx in 0..container.len() {
85		if container.is_defined(idx) {
86			out.push::<String>(container.data().get(idx).to_string());
87		} else {
88			out.push_none();
89		}
90	}
91	Ok(out)
92}
93
94#[inline]
95fn from_number<T>(container: &NumberContainer<T>) -> Result<ColumnData>
96where
97	T: Copy + Display + IsNumber + Default,
98{
99	let mut out = ColumnData::with_capacity(Type::Utf8, container.len());
100	for idx in 0..container.len() {
101		if container.is_defined(idx) {
102			out.push::<String>(container[idx].to_string());
103		} else {
104			out.push_none();
105		}
106	}
107	Ok(out)
108}
109
110#[inline]
111fn from_temporal<T>(container: &TemporalContainer<T>) -> Result<ColumnData>
112where
113	T: Copy + Display + IsTemporal + Default,
114{
115	let mut out = ColumnData::with_capacity(Type::Utf8, container.len());
116	for idx in 0..container.len() {
117		if container.is_defined(idx) {
118			out.push::<String>(container[idx].to_string());
119		} else {
120			out.push_none();
121		}
122	}
123	Ok(out)
124}
125
126#[inline]
127fn from_uuid<T>(container: &UuidContainer<T>) -> Result<ColumnData>
128where
129	T: Copy + Display + IsUuid + Default,
130{
131	let mut out = ColumnData::with_capacity(Type::Utf8, container.len());
132	for idx in 0..container.len() {
133		if container.is_defined(idx) {
134			out.push::<String>(container[idx].to_string());
135		} else {
136			out.push_none();
137		}
138	}
139	Ok(out)
140}
141
142#[cfg(test)]
143pub mod tests {
144	use reifydb_core::value::column::data::ColumnData;
145	use reifydb_type::{
146		fragment::Fragment,
147		value::{blob::Blob, container::blob::BlobContainer},
148	};
149
150	use crate::expression::cast::text::from_blob;
151
152	#[test]
153	fn test_from_blob() {
154		let blobs = vec![
155			Blob::from_utf8(Fragment::internal("Hello")),
156			Blob::from_utf8(Fragment::internal("World")),
157		];
158		let container = BlobContainer::new(blobs);
159
160		let result = from_blob(&container, || Fragment::testing_empty()).unwrap();
161
162		match result {
163			ColumnData::Utf8 {
164				container,
165				..
166			} => {
167				assert_eq!(container[0], "Hello");
168				assert_eq!(container[1], "World");
169			}
170			_ => panic!("Expected UTF8 column data"),
171		}
172	}
173
174	#[test]
175	fn test_from_blob_invalid() {
176		let blobs = vec![
177			Blob::new(vec![0xFF, 0xFE]), // Invalid UTF-8
178		];
179		let container = BlobContainer::new(blobs);
180
181		let result = from_blob(&container, || Fragment::testing_empty());
182		assert!(result.is_err());
183	}
184}