1use anyhow::{anyhow, Result};
2
3use super::{ArgCount, FunctionCategory, FunctionSignature, SqlFunction};
4use crate::data::datatable::DataValue;
5
6pub trait MethodFunction: SqlFunction {
9 fn handles_method(&self, method_name: &str) -> bool;
11
12 fn method_name(&self) -> &'static str;
14
15 fn evaluate_method(&self, receiver: &DataValue, args: &[DataValue]) -> Result<DataValue> {
17 let mut full_args = vec![receiver.clone()];
19 full_args.extend_from_slice(args);
20 self.evaluate(&full_args)
21 }
22}
23
24pub struct ToUpperMethod;
26
27impl SqlFunction for ToUpperMethod {
28 fn signature(&self) -> FunctionSignature {
29 FunctionSignature {
30 name: "TOUPPER",
31 category: FunctionCategory::String,
32 arg_count: ArgCount::Fixed(1),
33 description: "Converts string to uppercase",
34 returns: "STRING",
35 examples: vec![
36 "SELECT name.ToUpper() FROM users",
37 "SELECT TOUPPER(name) FROM users",
38 ],
39 }
40 }
41
42 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
43 self.validate_args(args)?;
44
45 match &args[0] {
46 DataValue::String(s) => Ok(DataValue::String(s.to_uppercase())),
47 DataValue::InternedString(s) => Ok(DataValue::String(s.to_uppercase())),
48 DataValue::Null => Ok(DataValue::Null),
49 _ => Err(anyhow!("ToUpper expects a string argument")),
50 }
51 }
52}
53
54impl MethodFunction for ToUpperMethod {
55 fn handles_method(&self, method_name: &str) -> bool {
56 method_name.eq_ignore_ascii_case("ToUpper")
57 || method_name.eq_ignore_ascii_case("ToUpperCase")
58 }
59
60 fn method_name(&self) -> &'static str {
61 "ToUpper"
62 }
63}
64
65pub struct ToLowerMethod;
67
68impl SqlFunction for ToLowerMethod {
69 fn signature(&self) -> FunctionSignature {
70 FunctionSignature {
71 name: "TOLOWER",
72 category: FunctionCategory::String,
73 arg_count: ArgCount::Fixed(1),
74 description: "Converts string to lowercase",
75 returns: "STRING",
76 examples: vec![
77 "SELECT name.ToLower() FROM users",
78 "SELECT TOLOWER(name) FROM users",
79 ],
80 }
81 }
82
83 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
84 self.validate_args(args)?;
85
86 match &args[0] {
87 DataValue::String(s) => Ok(DataValue::String(s.to_lowercase())),
88 DataValue::InternedString(s) => Ok(DataValue::String(s.to_lowercase())),
89 DataValue::Null => Ok(DataValue::Null),
90 _ => Err(anyhow!("ToLower expects a string argument")),
91 }
92 }
93}
94
95impl MethodFunction for ToLowerMethod {
96 fn handles_method(&self, method_name: &str) -> bool {
97 method_name.eq_ignore_ascii_case("ToLower")
98 || method_name.eq_ignore_ascii_case("ToLowerCase")
99 }
100
101 fn method_name(&self) -> &'static str {
102 "ToLower"
103 }
104}
105
106pub struct TrimMethod;
108
109impl SqlFunction for TrimMethod {
110 fn signature(&self) -> FunctionSignature {
111 FunctionSignature {
112 name: "TRIM",
113 category: FunctionCategory::String,
114 arg_count: ArgCount::Fixed(1),
115 description: "Removes leading and trailing whitespace",
116 returns: "STRING",
117 examples: vec![
118 "SELECT name.Trim() FROM users",
119 "SELECT TRIM(name) FROM users",
120 ],
121 }
122 }
123
124 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
125 self.validate_args(args)?;
126
127 match &args[0] {
128 DataValue::String(s) => Ok(DataValue::String(s.trim().to_string())),
129 DataValue::InternedString(s) => Ok(DataValue::String(s.trim().to_string())),
130 DataValue::Null => Ok(DataValue::Null),
131 _ => Err(anyhow!("Trim expects a string argument")),
132 }
133 }
134}
135
136impl MethodFunction for TrimMethod {
137 fn handles_method(&self, method_name: &str) -> bool {
138 method_name.eq_ignore_ascii_case("Trim")
139 }
140
141 fn method_name(&self) -> &'static str {
142 "Trim"
143 }
144}
145
146pub struct LengthMethod;
148
149impl SqlFunction for LengthMethod {
150 fn signature(&self) -> FunctionSignature {
151 FunctionSignature {
152 name: "LENGTH",
153 category: FunctionCategory::String,
154 arg_count: ArgCount::Fixed(1),
155 description: "Returns the length of a string",
156 returns: "INTEGER",
157 examples: vec![
158 "SELECT name.Length() FROM users",
159 "SELECT LENGTH(name) FROM users",
160 ],
161 }
162 }
163
164 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
165 self.validate_args(args)?;
166
167 match &args[0] {
168 DataValue::String(s) => Ok(DataValue::Integer(s.len() as i64)),
169 DataValue::InternedString(s) => Ok(DataValue::Integer(s.len() as i64)),
170 DataValue::Null => Ok(DataValue::Null),
171 _ => Err(anyhow!("Length expects a string argument")),
172 }
173 }
174}
175
176impl MethodFunction for LengthMethod {
177 fn handles_method(&self, method_name: &str) -> bool {
178 method_name.eq_ignore_ascii_case("Length") || method_name.eq_ignore_ascii_case("Len")
179 }
180
181 fn method_name(&self) -> &'static str {
182 "Length"
183 }
184}
185
186pub struct ContainsMethod;
188
189impl SqlFunction for ContainsMethod {
190 fn signature(&self) -> FunctionSignature {
191 FunctionSignature {
192 name: "CONTAINS",
193 category: FunctionCategory::String,
194 arg_count: ArgCount::Fixed(2),
195 description: "Checks if string contains substring",
196 returns: "BOOLEAN",
197 examples: vec![
198 "SELECT * FROM users WHERE name.Contains('john')",
199 "SELECT CONTAINS(name, 'john') FROM users",
200 ],
201 }
202 }
203
204 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
205 self.validate_args(args)?;
206
207 let haystack = match &args[0] {
208 DataValue::String(s) => s.as_str(),
209 DataValue::InternedString(s) => s.as_str(),
210 DataValue::Null => return Ok(DataValue::Boolean(false)),
211 _ => return Err(anyhow!("Contains expects string arguments")),
212 };
213
214 let needle = match &args[1] {
215 DataValue::String(s) => s.as_str(),
216 DataValue::InternedString(s) => s.as_str(),
217 DataValue::Null => return Ok(DataValue::Boolean(false)),
218 _ => return Err(anyhow!("Contains expects string arguments")),
219 };
220
221 Ok(DataValue::Boolean(haystack.contains(needle)))
222 }
223}
224
225impl MethodFunction for ContainsMethod {
226 fn handles_method(&self, method_name: &str) -> bool {
227 method_name.eq_ignore_ascii_case("Contains")
228 }
229
230 fn method_name(&self) -> &'static str {
231 "Contains"
232 }
233}
234
235pub struct StartsWithMethod;
237
238impl SqlFunction for StartsWithMethod {
239 fn signature(&self) -> FunctionSignature {
240 FunctionSignature {
241 name: "STARTSWITH",
242 category: FunctionCategory::String,
243 arg_count: ArgCount::Fixed(2),
244 description: "Checks if string starts with prefix",
245 returns: "BOOLEAN",
246 examples: vec![
247 "SELECT * FROM users WHERE name.StartsWith('John')",
248 "SELECT STARTSWITH(name, 'John') FROM users",
249 ],
250 }
251 }
252
253 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
254 self.validate_args(args)?;
255
256 let string = match &args[0] {
257 DataValue::String(s) => s.as_str(),
258 DataValue::InternedString(s) => s.as_str(),
259 DataValue::Null => return Ok(DataValue::Boolean(false)),
260 _ => return Err(anyhow!("StartsWith expects string arguments")),
261 };
262
263 let prefix = match &args[1] {
264 DataValue::String(s) => s.as_str(),
265 DataValue::InternedString(s) => s.as_str(),
266 DataValue::Null => return Ok(DataValue::Boolean(false)),
267 _ => return Err(anyhow!("StartsWith expects string arguments")),
268 };
269
270 Ok(DataValue::Boolean(string.starts_with(prefix)))
271 }
272}
273
274impl MethodFunction for StartsWithMethod {
275 fn handles_method(&self, method_name: &str) -> bool {
276 method_name.eq_ignore_ascii_case("StartsWith")
277 }
278
279 fn method_name(&self) -> &'static str {
280 "StartsWith"
281 }
282}
283
284pub struct EndsWithMethod;
286
287impl SqlFunction for EndsWithMethod {
288 fn signature(&self) -> FunctionSignature {
289 FunctionSignature {
290 name: "ENDSWITH",
291 category: FunctionCategory::String,
292 arg_count: ArgCount::Fixed(2),
293 description: "Checks if string ends with suffix",
294 returns: "BOOLEAN",
295 examples: vec![
296 "SELECT * FROM users WHERE email.EndsWith('.com')",
297 "SELECT ENDSWITH(email, '.com') FROM users",
298 ],
299 }
300 }
301
302 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
303 self.validate_args(args)?;
304
305 let string = match &args[0] {
306 DataValue::String(s) => s.as_str(),
307 DataValue::InternedString(s) => s.as_str(),
308 DataValue::Null => return Ok(DataValue::Boolean(false)),
309 _ => return Err(anyhow!("EndsWith expects string arguments")),
310 };
311
312 let suffix = match &args[1] {
313 DataValue::String(s) => s.as_str(),
314 DataValue::InternedString(s) => s.as_str(),
315 DataValue::Null => return Ok(DataValue::Boolean(false)),
316 _ => return Err(anyhow!("EndsWith expects string arguments")),
317 };
318
319 Ok(DataValue::Boolean(string.ends_with(suffix)))
320 }
321}
322
323impl MethodFunction for EndsWithMethod {
324 fn handles_method(&self, method_name: &str) -> bool {
325 method_name.eq_ignore_ascii_case("EndsWith")
326 }
327
328 fn method_name(&self) -> &'static str {
329 "EndsWith"
330 }
331}
332
333pub struct SubstringMethod;
335
336impl SqlFunction for SubstringMethod {
337 fn signature(&self) -> FunctionSignature {
338 FunctionSignature {
339 name: "SUBSTRING",
340 category: FunctionCategory::String,
341 arg_count: ArgCount::Range(2, 3),
342 description: "Extracts substring from string",
343 returns: "STRING",
344 examples: vec![
345 "SELECT name.Substring(0, 5) FROM users",
346 "SELECT SUBSTRING(name, 0, 5) FROM users",
347 ],
348 }
349 }
350
351 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
352 if args.len() < 2 || args.len() > 3 {
353 return Err(anyhow!("Substring expects 2 or 3 arguments"));
354 }
355
356 let string = match &args[0] {
357 DataValue::String(s) => s.as_str(),
358 DataValue::InternedString(s) => s.as_str(),
359 DataValue::Null => return Ok(DataValue::Null),
360 _ => return Err(anyhow!("Substring expects a string as first argument")),
361 };
362
363 let start = match &args[1] {
364 DataValue::Integer(i) => *i as usize,
365 _ => return Err(anyhow!("Substring expects integer start position")),
366 };
367
368 let result = if args.len() == 3 {
369 let length = match &args[2] {
370 DataValue::Integer(i) => *i as usize,
371 _ => return Err(anyhow!("Substring expects integer length")),
372 };
373
374 let end = (start + length).min(string.len());
375 string.chars().skip(start).take(end - start).collect()
376 } else {
377 string.chars().skip(start).collect()
378 };
379
380 Ok(DataValue::String(result))
381 }
382}
383
384impl MethodFunction for SubstringMethod {
385 fn handles_method(&self, method_name: &str) -> bool {
386 method_name.eq_ignore_ascii_case("Substring") || method_name.eq_ignore_ascii_case("Substr")
387 }
388
389 fn method_name(&self) -> &'static str {
390 "Substring"
391 }
392}
393
394pub struct ReplaceMethod;
396
397impl SqlFunction for ReplaceMethod {
398 fn signature(&self) -> FunctionSignature {
399 FunctionSignature {
400 name: "REPLACE",
401 category: FunctionCategory::String,
402 arg_count: ArgCount::Fixed(3),
403 description: "Replaces all occurrences of a substring",
404 returns: "STRING",
405 examples: vec![
406 "SELECT name.Replace('John', 'Jane') FROM users",
407 "SELECT REPLACE(name, 'John', 'Jane') FROM users",
408 ],
409 }
410 }
411
412 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
413 self.validate_args(args)?;
414
415 let string = match &args[0] {
416 DataValue::String(s) => s.as_str(),
417 DataValue::InternedString(s) => s.as_str(),
418 DataValue::Null => return Ok(DataValue::Null),
419 _ => return Err(anyhow!("Replace expects string arguments")),
420 };
421
422 let from = match &args[1] {
423 DataValue::String(s) => s.as_str(),
424 DataValue::InternedString(s) => s.as_str(),
425 _ => return Err(anyhow!("Replace expects string arguments")),
426 };
427
428 let to = match &args[2] {
429 DataValue::String(s) => s.as_str(),
430 DataValue::InternedString(s) => s.as_str(),
431 _ => return Err(anyhow!("Replace expects string arguments")),
432 };
433
434 Ok(DataValue::String(string.replace(from, to)))
435 }
436}
437
438impl MethodFunction for ReplaceMethod {
439 fn handles_method(&self, method_name: &str) -> bool {
440 method_name.eq_ignore_ascii_case("Replace")
441 }
442
443 fn method_name(&self) -> &'static str {
444 "Replace"
445 }
446}
447
448pub struct MidFunction;
450
451impl SqlFunction for MidFunction {
452 fn signature(&self) -> FunctionSignature {
453 FunctionSignature {
454 name: "MID",
455 category: FunctionCategory::String,
456 arg_count: ArgCount::Fixed(3),
457 description: "Extract substring from text (1-based indexing)",
458 returns: "STRING",
459 examples: vec![
460 "SELECT MID('Hello', 1, 3)", "SELECT MID('World', 2, 3)", "SELECT MID(name, 1, 5) FROM table",
463 ],
464 }
465 }
466
467 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
468 self.validate_args(args)?;
469
470 let text = match &args[0] {
472 DataValue::String(s) => s.clone(),
473 DataValue::InternedString(s) => s.to_string(),
474 DataValue::Integer(n) => n.to_string(),
475 DataValue::Float(f) => f.to_string(),
476 DataValue::Null => String::new(),
477 _ => return Err(anyhow!("MID first argument must be convertible to text")),
478 };
479
480 let start_pos = match &args[1] {
482 DataValue::Integer(n) => *n,
483 DataValue::Float(f) => *f as i64,
484 _ => return Err(anyhow!("MID start position must be a number")),
485 };
486
487 let length = match &args[2] {
489 DataValue::Integer(n) => *n,
490 DataValue::Float(f) => *f as i64,
491 _ => return Err(anyhow!("MID length must be a number")),
492 };
493
494 if start_pos < 1 {
496 return Err(anyhow!("MID start position must be >= 1"));
497 }
498 if length < 0 {
499 return Err(anyhow!("MID length must be >= 0"));
500 }
501
502 let start_idx = (start_pos - 1) as usize;
504 let chars: Vec<char> = text.chars().collect();
505
506 if start_idx >= chars.len() {
508 return Ok(DataValue::String(String::new()));
509 }
510
511 let end_idx = std::cmp::min(start_idx + length as usize, chars.len());
513 let result: String = chars[start_idx..end_idx].iter().collect();
514
515 Ok(DataValue::String(result))
516 }
517}
518
519pub struct UpperFunction;
521
522impl SqlFunction for UpperFunction {
523 fn signature(&self) -> FunctionSignature {
524 FunctionSignature {
525 name: "UPPER",
526 category: FunctionCategory::String,
527 arg_count: ArgCount::Fixed(1),
528 description: "Convert string to uppercase",
529 returns: "STRING",
530 examples: vec![
531 "SELECT UPPER('hello')", "SELECT UPPER(name) FROM table",
533 ],
534 }
535 }
536
537 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
538 self.validate_args(args)?;
539
540 match &args[0] {
541 DataValue::String(s) => Ok(DataValue::String(s.to_uppercase())),
542 DataValue::InternedString(s) => Ok(DataValue::String(s.to_uppercase())),
543 DataValue::Null => Ok(DataValue::Null),
544 _ => Err(anyhow!("UPPER expects a string argument")),
545 }
546 }
547}
548
549pub struct LowerFunction;
551
552impl SqlFunction for LowerFunction {
553 fn signature(&self) -> FunctionSignature {
554 FunctionSignature {
555 name: "LOWER",
556 category: FunctionCategory::String,
557 arg_count: ArgCount::Fixed(1),
558 description: "Convert string to lowercase",
559 returns: "STRING",
560 examples: vec![
561 "SELECT LOWER('HELLO')", "SELECT LOWER(name) FROM table",
563 ],
564 }
565 }
566
567 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
568 self.validate_args(args)?;
569
570 match &args[0] {
571 DataValue::String(s) => Ok(DataValue::String(s.to_lowercase())),
572 DataValue::InternedString(s) => Ok(DataValue::String(s.to_lowercase())),
573 DataValue::Null => Ok(DataValue::Null),
574 _ => Err(anyhow!("LOWER expects a string argument")),
575 }
576 }
577}
578
579pub struct TrimFunction;
581
582impl SqlFunction for TrimFunction {
583 fn signature(&self) -> FunctionSignature {
584 FunctionSignature {
585 name: "TRIM",
586 category: FunctionCategory::String,
587 arg_count: ArgCount::Fixed(1),
588 description: "Remove leading and trailing whitespace",
589 returns: "STRING",
590 examples: vec![
591 "SELECT TRIM(' hello ')", "SELECT TRIM(description) FROM table",
593 ],
594 }
595 }
596
597 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
598 self.validate_args(args)?;
599
600 match &args[0] {
601 DataValue::String(s) => Ok(DataValue::String(s.trim().to_string())),
602 DataValue::InternedString(s) => Ok(DataValue::String(s.trim().to_string())),
603 DataValue::Null => Ok(DataValue::Null),
604 _ => Err(anyhow!("TRIM expects a string argument")),
605 }
606 }
607}
608
609pub struct TextJoinFunction;
611
612impl SqlFunction for TextJoinFunction {
613 fn signature(&self) -> FunctionSignature {
614 FunctionSignature {
615 name: "TEXTJOIN",
616 category: FunctionCategory::String,
617 arg_count: ArgCount::Variadic,
618 description: "Join multiple text values with a delimiter",
619 returns: "STRING",
620 examples: vec![
621 "SELECT TEXTJOIN(',', 1, 'a', 'b', 'c')", "SELECT TEXTJOIN(' - ', 1, name, city) FROM table",
623 "SELECT TEXTJOIN('|', 0, col1, col2, col3) FROM table",
624 ],
625 }
626 }
627
628 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
629 if args.len() < 3 {
630 return Err(anyhow!("TEXTJOIN requires at least 3 arguments: delimiter, ignore_empty, text1, [text2, ...]"));
631 }
632
633 let delimiter = match &args[0] {
635 DataValue::String(s) => s.clone(),
636 DataValue::InternedString(s) => s.to_string(),
637 DataValue::Integer(n) => n.to_string(),
638 DataValue::Float(f) => f.to_string(),
639 DataValue::Boolean(b) => b.to_string(),
640 DataValue::Null => String::new(),
641 _ => String::new(),
642 };
643
644 let ignore_empty = match &args[1] {
646 DataValue::Integer(n) => *n != 0,
647 DataValue::Float(f) => *f != 0.0,
648 DataValue::Boolean(b) => *b,
649 DataValue::String(s) => !s.is_empty() && s != "0" && s.to_lowercase() != "false",
650 DataValue::InternedString(s) => {
651 !s.is_empty() && s.as_str() != "0" && s.to_lowercase() != "false"
652 }
653 DataValue::Null => false,
654 _ => true,
655 };
656
657 let mut values = Vec::new();
659 for i in 2..args.len() {
660 let string_value = match &args[i] {
661 DataValue::String(s) => Some(s.clone()),
662 DataValue::InternedString(s) => Some(s.to_string()),
663 DataValue::Integer(n) => Some(n.to_string()),
664 DataValue::Float(f) => Some(f.to_string()),
665 DataValue::Boolean(b) => Some(b.to_string()),
666 DataValue::DateTime(dt) => Some(dt.clone()),
667 DataValue::Null => {
668 if ignore_empty {
669 None
670 } else {
671 Some(String::new())
672 }
673 }
674 };
675
676 if let Some(s) = string_value {
677 if !ignore_empty || !s.is_empty() {
678 values.push(s);
679 }
680 }
681 }
682
683 Ok(DataValue::String(values.join(&delimiter)))
684 }
685}
686
687pub struct EditDistanceFunction;
689
690impl EditDistanceFunction {
691 #[must_use]
693 pub fn calculate_edit_distance(s1: &str, s2: &str) -> usize {
694 let len1 = s1.len();
695 let len2 = s2.len();
696 let mut matrix = vec![vec![0; len2 + 1]; len1 + 1];
697
698 for i in 0..=len1 {
699 matrix[i][0] = i;
700 }
701 for j in 0..=len2 {
702 matrix[0][j] = j;
703 }
704
705 for (i, c1) in s1.chars().enumerate() {
706 for (j, c2) in s2.chars().enumerate() {
707 let cost = usize::from(c1 != c2);
708 matrix[i + 1][j + 1] = std::cmp::min(
709 matrix[i][j + 1] + 1, std::cmp::min(
711 matrix[i + 1][j] + 1, matrix[i][j] + cost, ),
714 );
715 }
716 }
717
718 matrix[len1][len2]
719 }
720}
721
722impl SqlFunction for EditDistanceFunction {
723 fn signature(&self) -> FunctionSignature {
724 FunctionSignature {
725 name: "EDIT_DISTANCE",
726 category: FunctionCategory::String,
727 arg_count: ArgCount::Fixed(2),
728 description: "Calculate the Levenshtein edit distance between two strings",
729 returns: "INTEGER",
730 examples: vec![
731 "SELECT EDIT_DISTANCE('kitten', 'sitting')",
732 "SELECT EDIT_DISTANCE(name, 'John') FROM users",
733 "SELECT * FROM users WHERE EDIT_DISTANCE(name, 'Smith') <= 2",
734 ],
735 }
736 }
737
738 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
739 self.validate_args(args)?;
740
741 let s1 = match &args[0] {
742 DataValue::String(s) => s.clone(),
743 DataValue::InternedString(s) => s.to_string(),
744 DataValue::Null => return Ok(DataValue::Null),
745 _ => return Err(anyhow!("EDIT_DISTANCE expects string arguments")),
746 };
747
748 let s2 = match &args[1] {
749 DataValue::String(s) => s.clone(),
750 DataValue::InternedString(s) => s.to_string(),
751 DataValue::Null => return Ok(DataValue::Null),
752 _ => return Err(anyhow!("EDIT_DISTANCE expects string arguments")),
753 };
754
755 let distance = Self::calculate_edit_distance(&s1, &s2);
756 Ok(DataValue::Integer(distance as i64))
757 }
758}
759
760pub struct FrequencyFunction;
762
763impl SqlFunction for FrequencyFunction {
764 fn signature(&self) -> FunctionSignature {
765 FunctionSignature {
766 name: "FREQUENCY",
767 category: FunctionCategory::String,
768 arg_count: ArgCount::Fixed(2),
769 description: "Count occurrences of a substring within a string",
770 returns: "INTEGER",
771 examples: vec![
772 "SELECT FREQUENCY('hello world', 'o')", "SELECT FREQUENCY('mississippi', 'ss')", "SELECT FREQUENCY(text_column, 'error') FROM logs",
775 "SELECT name, FREQUENCY(name, 'a') as a_count FROM users",
776 ],
777 }
778 }
779
780 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
781 self.validate_args(args)?;
782
783 let text = match &args[0] {
785 DataValue::String(s) => s.clone(),
786 DataValue::InternedString(s) => s.to_string(),
787 DataValue::Null => return Ok(DataValue::Integer(0)),
788 _ => return Err(anyhow!("FREQUENCY expects string as first argument")),
789 };
790
791 let search = match &args[1] {
793 DataValue::String(s) => s.clone(),
794 DataValue::InternedString(s) => s.to_string(),
795 DataValue::Null => return Ok(DataValue::Integer(0)),
796 _ => return Err(anyhow!("FREQUENCY expects string as second argument")),
797 };
798
799 if search.is_empty() {
801 return Ok(DataValue::Integer(0));
802 }
803
804 let count = text.matches(&search).count();
806 Ok(DataValue::Integer(count as i64))
807 }
808}
809
810pub fn register_string_methods(registry: &mut super::FunctionRegistry) {
812 use std::sync::Arc;
813
814 registry.register(Box::new(MidFunction));
816 registry.register(Box::new(UpperFunction));
817 registry.register(Box::new(LowerFunction));
818 registry.register(Box::new(TrimFunction));
819 registry.register(Box::new(TextJoinFunction));
820 registry.register(Box::new(EditDistanceFunction));
821 registry.register(Box::new(FrequencyFunction));
822
823 let to_upper = Arc::new(ToUpperMethod);
825 registry.register(Box::new(ToUpperMethod));
826 registry.register_method(to_upper);
827
828 let to_lower = Arc::new(ToLowerMethod);
830 registry.register(Box::new(ToLowerMethod));
831 registry.register_method(to_lower);
832
833 let trim = Arc::new(TrimMethod);
835 registry.register(Box::new(TrimMethod));
836 registry.register_method(trim);
837
838 let length = Arc::new(LengthMethod);
840 registry.register(Box::new(LengthMethod));
841 registry.register_method(length);
842
843 let contains = Arc::new(ContainsMethod);
845 registry.register(Box::new(ContainsMethod));
846 registry.register_method(contains);
847
848 let starts_with = Arc::new(StartsWithMethod);
850 registry.register(Box::new(StartsWithMethod));
851 registry.register_method(starts_with);
852
853 let ends_with = Arc::new(EndsWithMethod);
855 registry.register(Box::new(EndsWithMethod));
856 registry.register_method(ends_with);
857
858 let substring = Arc::new(SubstringMethod);
860 registry.register(Box::new(SubstringMethod));
861 registry.register_method(substring);
862
863 let replace = Arc::new(ReplaceMethod);
865 registry.register(Box::new(ReplaceMethod));
866 registry.register_method(replace);
867}