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