Skip to main content

reifydb_routine/function/text/
char.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_core::value::column::{Column, columns::Columns, data::ColumnData};
5use reifydb_type::value::{constraint::bytes::MaxBytes, container::utf8::Utf8Container, r#type::Type};
6
7use crate::function::{Function, FunctionCapability, FunctionContext, FunctionInfo, error::FunctionError};
8
9pub struct TextChar {
10	info: FunctionInfo,
11}
12
13impl Default for TextChar {
14	fn default() -> Self {
15		Self::new()
16	}
17}
18
19impl TextChar {
20	pub fn new() -> Self {
21		Self {
22			info: FunctionInfo::new("text::char"),
23		}
24	}
25}
26
27impl Function for TextChar {
28	fn info(&self) -> &FunctionInfo {
29		&self.info
30	}
31
32	fn capabilities(&self) -> &[FunctionCapability] {
33		&[FunctionCapability::Scalar]
34	}
35
36	fn return_type(&self, _input_types: &[Type]) -> Type {
37		Type::Utf8
38	}
39
40	fn execute(&self, ctx: &FunctionContext, args: &Columns) -> Result<Columns, FunctionError> {
41		if args.len() != 1 {
42			return Err(FunctionError::ArityMismatch {
43				function: ctx.fragment.clone(),
44				expected: 1,
45				actual: args.len(),
46			});
47		}
48
49		let column = &args[0];
50		let (data, bitvec) = column.data().unwrap_option();
51		let row_count = data.len();
52
53		let result_data = match data {
54			ColumnData::Int1(c) => {
55				convert_to_char(row_count, c.data().len(), |i| c.get(i).map(|&v| v as u32))
56			}
57			ColumnData::Int2(c) => {
58				convert_to_char(row_count, c.data().len(), |i| c.get(i).map(|&v| v as u32))
59			}
60			ColumnData::Int4(c) => {
61				convert_to_char(row_count, c.data().len(), |i| c.get(i).map(|&v| v as u32))
62			}
63			ColumnData::Int8(c) => {
64				convert_to_char(row_count, c.data().len(), |i| c.get(i).map(|&v| v as u32))
65			}
66			ColumnData::Uint1(c) => {
67				convert_to_char(row_count, c.data().len(), |i| c.get(i).map(|&v| v as u32))
68			}
69			ColumnData::Uint2(c) => {
70				convert_to_char(row_count, c.data().len(), |i| c.get(i).map(|&v| v as u32))
71			}
72			ColumnData::Uint4(c) => convert_to_char(row_count, c.data().len(), |i| c.get(i).copied()),
73			other => {
74				return Err(FunctionError::InvalidArgumentType {
75					function: ctx.fragment.clone(),
76					argument_index: 0,
77					expected: vec![Type::Int1, Type::Int2, Type::Int4, Type::Int8],
78					actual: other.get_type(),
79				});
80			}
81		};
82
83		let final_data = match bitvec {
84			Some(bv) => ColumnData::Option {
85				inner: Box::new(result_data),
86				bitvec: bv.clone(),
87			},
88			None => result_data,
89		};
90		Ok(Columns::new(vec![Column::new(ctx.fragment.clone(), final_data)]))
91	}
92}
93
94fn convert_to_char<F>(row_count: usize, _capacity: usize, get_value: F) -> ColumnData
95where
96	F: Fn(usize) -> Option<u32>,
97{
98	let mut result_data = Vec::with_capacity(row_count);
99
100	for i in 0..row_count {
101		match get_value(i) {
102			Some(code_point) => {
103				if let Some(ch) = char::from_u32(code_point) {
104					result_data.push(ch.to_string());
105				} else {
106					result_data.push(String::new());
107				}
108			}
109			None => {
110				result_data.push(String::new());
111			}
112		}
113	}
114
115	ColumnData::Utf8 {
116		container: Utf8Container::new(result_data),
117		max_bytes: MaxBytes::MAX,
118	}
119}