akar_function/scalar/
string.rs1use super::get_cached_regex;
2use crate::registry::*;
3use akar_common::types::Value;
4
5fn soundex_impl(s: &str) -> String {
8 let mut chars = s.chars().filter(|c| c.is_ascii_alphabetic());
9 let first_char = match chars.next() {
10 Some(c) => c.to_ascii_uppercase(),
11 None => return "".to_string(),
12 };
13
14 let mut result = String::with_capacity(4);
15 result.push(first_char);
16
17 let get_code = |c: char| -> char {
18 match c.to_ascii_uppercase() {
19 'B' | 'F' | 'P' | 'V' => '1',
20 'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => '2',
21 'D' | 'T' => '3',
22 'L' => '4',
23 'M' | 'N' => '5',
24 'R' => '6',
25 _ => '0',
26 }
27 };
28
29 let mut prev_code = get_code(first_char);
30 for c in chars {
31 let code = get_code(c);
32 if code != '0' && code != prev_code {
33 result.push(code);
34 if result.len() == 4 {
35 break;
36 }
37 }
38 if !c.eq_ignore_ascii_case(&'H') && !c.eq_ignore_ascii_case(&'W') {
39 prev_code = code;
40 }
41 }
42
43 while result.len() < 4 {
44 result.push('0');
45 }
46 result
47}
48
49pub(crate) fn evaluate_string(op: StringOp, args: &[Value]) -> Result<Value, String> {
50 if args.is_empty() {
51 return Err("String function requires arguments".into());
52 }
53
54 match op {
55 StringOp::Concat => {
56 let s: String = args
57 .iter()
58 .map(|v| match v {
59 Value::String(s) => s.clone(),
60 Value::Null => "NULL".into(),
61 other => format!("{:?}", other),
62 })
63 .collect();
64 Ok(Value::String(s))
65 }
66 StringOp::Contains => {
67 let s = get_string(&args[0])?;
68 let pat = get_string(&args[1])?;
69 Ok(Value::Bool(s.contains(&pat)))
70 }
71 StringOp::StartsWith => {
72 let s = get_string(&args[0])?;
73 let pat = get_string(&args[1])?;
74 Ok(Value::Bool(s.starts_with(&pat)))
75 }
76 StringOp::EndsWith => {
77 let s = get_string(&args[0])?;
78 let pat = get_string(&args[1])?;
79 Ok(Value::Bool(s.ends_with(&pat)))
80 }
81 StringOp::Like => {
82 let s = get_string(&args[0])?;
83 let pat = get_string(&args[1])?;
84 let mut regex_str = String::with_capacity(pat.len() + 2);
86 regex_str.push('^');
87 for ch in pat.chars() {
88 match ch {
89 '%' => regex_str.push_str(".*"),
90 '_' => regex_str.push('.'),
91 '.' | '\\' | '+' | '*' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '^' | '$' | '|' => {
93 regex_str.push('\\');
94 regex_str.push(ch);
95 }
96 other => regex_str.push(other),
97 }
98 }
99 regex_str.push('$');
100 let re = get_cached_regex(®ex_str)?;
101 Ok(Value::Bool(re.is_match(&s)))
102 }
103 StringOp::ToUpper => {
104 let s = get_string(&args[0])?;
105 Ok(Value::String(s.to_uppercase()))
106 }
107 StringOp::ToLower => {
108 let s = get_string(&args[0])?;
109 Ok(Value::String(s.to_lowercase()))
110 }
111 StringOp::Trim => {
112 let s = get_string(&args[0])?;
113 Ok(Value::String(s.trim().to_string()))
114 }
115 StringOp::LTrim => {
116 let s = get_string(&args[0])?;
117 Ok(Value::String(s.trim_start().to_string()))
118 }
119 StringOp::RTrim => {
120 let s = get_string(&args[0])?;
121 Ok(Value::String(s.trim_end().to_string()))
122 }
123 StringOp::Length => {
124 let s = get_string(&args[0])?;
125 Ok(Value::Int64(s.len() as i64))
126 }
127 StringOp::Reverse => {
128 let s = get_string(&args[0])?;
129 Ok(Value::String(s.chars().rev().collect()))
130 }
131 StringOp::Repeat => {
132 let s = get_string(&args[0])?;
133 let n = match &args[1] {
134 Value::Int64(x) => *x as usize,
135 _ => return Err("Repeat count must be integer".into()),
136 };
137 Ok(Value::String(s.repeat(n)))
138 }
139 StringOp::Replace => {
140 let s = get_string(&args[0])?;
141 let from = get_string(&args[1])?;
142 let to = get_string(&args[2])?;
143 Ok(Value::String(s.replace(&from, &to)))
144 }
145 StringOp::Substring => {
146 let s = get_string(&args[0])?;
147 let start = match &args[1] {
149 Value::Int64(x) => {
150 if *x < 1 {
151 return Err("Substring start must be >= 1".into());
152 }
153 (*x - 1) as usize
154 }
155 _ => return Err("Start must be integer".into()),
156 };
157 let len = if args.len() > 2 {
158 match &args[2] {
159 Value::Int64(x) => Some(*x as usize),
160 _ => None,
161 }
162 } else {
163 None
164 };
165 let result = match len {
166 Some(l) => s.chars().skip(start).take(l).collect(),
167 None => s.chars().skip(start).collect(),
168 };
169 Ok(Value::String(result))
170 }
171 StringOp::RegexMatches => {
172 let s = get_string(&args[0])?;
173 let pat = get_string(&args[1])?;
174 let re = get_cached_regex(&pat)?;
175 Ok(Value::Bool(re.is_match(&s)))
176 }
177 StringOp::RegexReplace => {
178 let s = get_string(&args[0])?;
179 let pat = get_string(&args[1])?;
180 let repl = get_string(&args[2])?;
181 let re = get_cached_regex(&pat)?;
182 Ok(Value::String(re.replace_all(&s, repl).to_string()))
183 }
184 StringOp::Split => {
185 let s = get_string(&args[0])?;
186 let delim = if args.len() > 1 {
187 get_string(&args[1])?
188 } else {
189 ",".to_string()
190 };
191 let parts: Vec<Value> = s.split(&delim).map(|p| Value::String(p.to_string())).collect();
192 Ok(Value::List(parts))
193 }
194 StringOp::Head => {
195 let s = get_string(&args[0])?;
196 let n = if args.len() > 1 {
197 match &args[1] {
198 Value::Int64(x) => *x as usize,
199 _ => 1,
200 }
201 } else {
202 1
203 };
204 Ok(Value::String(s.chars().take(n).collect()))
205 }
206 StringOp::Tail => {
207 let s = get_string(&args[0])?;
208 let n = if args.len() > 1 {
209 match &args[1] {
210 Value::Int64(x) => *x as usize,
211 _ => 1,
212 }
213 } else {
214 1
215 };
216 let chars: String = s.chars().collect();
217 let start = chars.len().saturating_sub(n);
218 Ok(Value::String(chars.chars().skip(start).collect()))
219 }
220 StringOp::Left => {
221 let s = get_string(&args[0])?;
222 let n = match &args[1] {
223 Value::Int64(x) => *x as usize,
224 _ => return Err("left requires integer length".into()),
225 };
226 Ok(Value::String(s.chars().take(n).collect()))
227 }
228 StringOp::Right => {
229 let s = get_string(&args[0])?;
230 let n = match &args[1] {
231 Value::Int64(x) => *x as usize,
232 _ => return Err("right requires integer length".into()),
233 };
234 let chars: Vec<char> = s.chars().collect();
235 let start = chars.len().saturating_sub(n);
236 Ok(Value::String(chars[start..].iter().collect()))
237 }
238 StringOp::Lpad => {
239 let s = get_string(&args[0])?;
240 let len = match &args[1] {
241 Value::Int64(x) => *x as usize,
242 _ => return Err("lpad requires integer length".into()),
243 };
244 let pad = if args.len() >= 3 {
245 get_string(&args[2])?
246 } else {
247 " ".into()
248 };
249 if s.len() >= len {
250 return Ok(Value::String(s[..len].to_string()));
251 }
252 let pad_needed = len - s.len();
253 let pad_repeat = pad.repeat((pad_needed / pad.len()) + 1);
254 Ok(Value::String(format!("{}{}", &pad_repeat[..pad_needed], s)))
255 }
256 StringOp::Rpad => {
257 let s = get_string(&args[0])?;
258 let len = match &args[1] {
259 Value::Int64(x) => *x as usize,
260 _ => return Err("rpad requires integer length".into()),
261 };
262 let pad = if args.len() >= 3 {
263 get_string(&args[2])?
264 } else {
265 " ".into()
266 };
267 if s.len() >= len {
268 return Ok(Value::String(s[..len].to_string()));
269 }
270 let pad_needed = len - s.len();
271 let pad_repeat = pad.repeat((pad_needed / pad.len()) + 1);
272 Ok(Value::String(format!("{}{}", s, &pad_repeat[..pad_needed])))
273 }
274 StringOp::InitCap => {
276 let s = get_string(&args[0])?;
277 let lower = s.to_lowercase();
278 let mut chars = lower.chars();
279 match chars.next() {
280 None => Ok(Value::String(String::new())),
281 Some(c) => Ok(Value::String(c.to_uppercase().collect::<String>() + chars.as_str())),
282 }
283 }
284 StringOp::ConcatWs => {
285 if args.len() < 2 {
286 return Err("concat_ws requires at least 2 arguments (separator + strings)".into());
287 }
288 let separator = get_string(&args[0])?;
289 let mut result = String::new();
290 let mut first = true;
291 for arg in args.iter().skip(1) {
292 match arg {
293 Value::Null => {
294 continue;
296 }
297 Value::String(s) => {
298 if !first {
299 result.push_str(&separator);
300 }
301 result.push_str(s);
302 first = false;
303 }
304 _ => {
305 if !first {
306 result.push_str(&separator);
307 }
308 result.push_str(&format!("{:?}", arg));
309 first = false;
310 }
311 }
312 }
313 Ok(Value::String(result))
314 }
315 StringOp::SplitPart => {
316 if args.len() < 3 {
317 return Err("split_part requires 3 arguments (string, delimiter, index)".into());
318 }
319 let s = get_string(&args[0])?;
320 let delim = get_string(&args[1])?;
321 let idx = match &args[2] {
322 Value::Int64(x) => *x,
323 _ => return Err("split_part index must be integer".into()),
324 };
325 let parts: Vec<&str> = s.split(&delim).collect();
327 if idx <= 0 || (idx as usize) > parts.len() {
328 Ok(Value::String(String::new()))
329 } else {
330 Ok(Value::String(parts[(idx - 1) as usize].to_string()))
331 }
332 }
333 StringOp::ArrayExtract => {
334 if args.len() < 2 {
335 return Err("array_extract requires 2 arguments (string, index)".into());
336 }
337 let s = get_string(&args[0])?;
338 let idx = match &args[1] {
339 Value::Int64(x) => *x,
340 _ => return Err("array_extract index must be integer".into()),
341 };
342 let chars: Vec<char> = s.chars().collect();
343 if idx == 0 || chars.is_empty() {
344 Ok(Value::String(String::new()))
345 } else if idx > 0 {
346 let pos = (idx as usize).saturating_sub(1).min(chars.len() - 1);
348 Ok(Value::String(chars[pos].to_string()))
349 } else {
350 let abs_idx = (-idx) as usize;
352 if abs_idx > chars.len() {
353 Ok(Value::String(String::new()))
354 } else {
355 let pos = chars.len() - abs_idx;
356 Ok(Value::String(chars[pos].to_string()))
357 }
358 }
359 }
360 StringOp::RegexpFullMatch => {
362 let s = get_string(&args[0])?;
363 let pat = get_string(&args[1])?;
364 let re = get_cached_regex(&pat)?;
365 Ok(Value::Bool(
366 re.find(&s).is_some_and(|m| m.start() == 0 && m.end() == s.len()),
367 ))
368 }
369 StringOp::RegexpExtract => {
370 let s = get_string(&args[0])?;
371 let pat = get_string(&args[1])?;
372 let group = if args.len() > 2 {
373 match &args[2] {
374 Value::Int64(x) => *x as usize,
375 _ => return Err("RegexpExtract group must be integer".into()),
376 }
377 } else {
378 0
379 };
380 let re = get_cached_regex(&pat)?;
381 let result = re
382 .captures(&s)
383 .and_then(|caps| caps.get(group))
384 .map(|m| m.as_str().to_string())
385 .unwrap_or_default();
386 Ok(Value::String(result))
387 }
388 StringOp::RegexpExtractAll => {
389 let s = get_string(&args[0])?;
390 let pat = get_string(&args[1])?;
391 let group = if args.len() > 2 {
392 match &args[2] {
393 Value::Int64(x) => *x as usize,
394 _ => return Err("RegexpExtractAll group must be integer".into()),
395 }
396 } else {
397 0
398 };
399 let re = get_cached_regex(&pat)?;
400 let matches: Vec<Value> = re
401 .captures_iter(&s)
402 .filter_map(|caps| caps.get(group))
403 .map(|m| Value::String(m.as_str().to_string()))
404 .collect();
405 Ok(Value::List(matches))
406 }
407 StringOp::RegexpSplitToArray => {
408 let s = get_string(&args[0])?;
409 let pat = get_string(&args[1])?;
410 let re = get_cached_regex(&pat)?;
411 let parts: Vec<Value> = re.split(&s).map(|p| Value::String(p.to_string())).collect();
412 Ok(Value::List(parts))
413 }
414 StringOp::Levenshtein => {
415 let a = get_string(&args[0])?;
416 let b = get_string(&args[1])?;
417 let a_chars: Vec<char> = a.chars().collect();
418 let b_chars: Vec<char> = b.chars().collect();
419 let n = b_chars.len();
420 let mut prev_row: Vec<usize> = (0..=n).collect();
421 let mut curr_row = vec![0usize; n + 1];
422 for (i, ca) in a_chars.iter().enumerate() {
423 curr_row[0] = i + 1;
424 for (j, cb) in b_chars.iter().enumerate() {
425 let cost = if ca == cb { 0 } else { 1 };
426 curr_row[j + 1] = (curr_row[j] + 1).min(prev_row[j + 1] + 1).min(prev_row[j] + cost);
427 }
428 std::mem::swap(&mut prev_row, &mut curr_row);
429 }
430 Ok(Value::Int64(prev_row[n] as i64))
431 }
432 StringOp::Soundex => {
433 let s = get_string(&args[0])?;
434 Ok(Value::String(soundex_impl(&s)))
435 }
436 }
437}
438
439pub(crate) fn get_string(v: &Value) -> Result<String, String> {
440 match v {
441 Value::String(s) => Ok(s.clone()),
442 Value::Null => Ok("NULL".into()),
443 _ => Err(format!("Expected string, got {:?}", v.logical_type())),
444 }
445}