Skip to main content

reifydb_routine/function/text/
starts_with.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::r#type::Type;
6
7use crate::function::{Function, FunctionCapability, FunctionContext, FunctionInfo, error::FunctionError};
8
9pub struct TextStartsWith {
10	info: FunctionInfo,
11}
12
13impl Default for TextStartsWith {
14	fn default() -> Self {
15		Self::new()
16	}
17}
18
19impl TextStartsWith {
20	pub fn new() -> Self {
21		Self {
22			info: FunctionInfo::new("text::starts_with"),
23		}
24	}
25}
26
27impl Function for TextStartsWith {
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::Boolean
38	}
39
40	fn execute(&self, ctx: &FunctionContext, args: &Columns) -> Result<Columns, FunctionError> {
41		if args.len() != 2 {
42			return Err(FunctionError::ArityMismatch {
43				function: ctx.fragment.clone(),
44				expected: 2,
45				actual: args.len(),
46			});
47		}
48
49		let str_col = &args[0];
50		let prefix_col = &args[1];
51
52		let (str_data, str_bv) = str_col.data().unwrap_option();
53		let (prefix_data, prefix_bv) = prefix_col.data().unwrap_option();
54		let row_count = str_data.len();
55
56		match (str_data, prefix_data) {
57			(
58				ColumnData::Utf8 {
59					container: str_container,
60					..
61				},
62				ColumnData::Utf8 {
63					container: prefix_container,
64					..
65				},
66			) => {
67				let mut result_data = Vec::with_capacity(row_count);
68				let mut result_bitvec = Vec::with_capacity(row_count);
69
70				for i in 0..row_count {
71					if str_container.is_defined(i) && prefix_container.is_defined(i) {
72						let s = &str_container[i];
73						let prefix = &prefix_container[i];
74						result_data.push(s.starts_with(prefix.as_str()));
75						result_bitvec.push(true);
76					} else {
77						result_data.push(false);
78						result_bitvec.push(false);
79					}
80				}
81
82				let result_col_data = ColumnData::bool_with_bitvec(result_data, result_bitvec);
83
84				let combined_bv = match (str_bv, prefix_bv) {
85					(Some(b), Some(e)) => Some(b.and(e)),
86					(Some(b), None) => Some(b.clone()),
87					(None, Some(e)) => Some(e.clone()),
88					(None, None) => None,
89				};
90
91				let final_data = match combined_bv {
92					Some(bv) => ColumnData::Option {
93						inner: Box::new(result_col_data),
94						bitvec: bv,
95					},
96					None => result_col_data,
97				};
98				Ok(Columns::new(vec![Column::new(ctx.fragment.clone(), final_data)]))
99			}
100			(
101				ColumnData::Utf8 {
102					..
103				},
104				other,
105			) => Err(FunctionError::InvalidArgumentType {
106				function: ctx.fragment.clone(),
107				argument_index: 1,
108				expected: vec![Type::Utf8],
109				actual: other.get_type(),
110			}),
111			(other, _) => Err(FunctionError::InvalidArgumentType {
112				function: ctx.fragment.clone(),
113				argument_index: 0,
114				expected: vec![Type::Utf8],
115				actual: other.get_type(),
116			}),
117		}
118	}
119}