sql_cli/sql/functions/
string_methods.rs

1use anyhow::{anyhow, Result};
2
3use super::{ArgCount, FunctionCategory, FunctionSignature, SqlFunction};
4use crate::data::datatable::DataValue;
5
6/// Trait for method-style functions that operate on a column/value
7/// These are called with dot notation: column.Method(args)
8pub trait MethodFunction: SqlFunction {
9    /// Check if this method function handles the given method name
10    fn handles_method(&self, method_name: &str) -> bool;
11
12    /// Get the method name this function handles
13    fn method_name(&self) -> &'static str;
14
15    /// Evaluate as a method (first arg is implicit 'self')
16    fn evaluate_method(&self, receiver: &DataValue, args: &[DataValue]) -> Result<DataValue> {
17        // Default implementation: prepend receiver to args and call evaluate
18        let mut full_args = vec![receiver.clone()];
19        full_args.extend_from_slice(args);
20        self.evaluate(&full_args)
21    }
22}
23
24/// `ToUpper` method function
25pub 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
65/// `ToLower` method function
66pub 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
106/// Trim method function
107pub 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
146/// Length method function (returns integer)
147pub 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
186/// Contains method function (returns boolean)
187pub 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
235/// `StartsWith` method function
236pub 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
284/// `EndsWith` method function
285pub 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
333/// Substring method function
334pub 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
394/// Replace method function
395pub 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
448/// MID function - Extract substring (SQL/Excel compatible, 1-based indexing)
449pub 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)", // Returns 'Hel'
461                "SELECT MID('World', 2, 3)", // Returns 'orl'
462                "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        // Get the string
471        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        // Get start position (1-based)
481        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        // Get length
488        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        // Validate arguments
495        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        // Convert to 0-based index
503        let start_idx = (start_pos - 1) as usize;
504        let chars: Vec<char> = text.chars().collect();
505
506        // If start position is beyond string length, return empty string
507        if start_idx >= chars.len() {
508            return Ok(DataValue::String(String::new()));
509        }
510
511        // Extract substring
512        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
519/// UPPER function - Convert string to uppercase
520pub 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')", // Returns 'HELLO'
532                "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
549/// LOWER function - Convert string to lowercase
550pub 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')", // Returns 'hello'
562                "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
579/// TRIM function - Remove leading and trailing whitespace
580pub 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  ')", // Returns 'hello'
592                "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
609/// TEXTJOIN function - Join multiple text values with a delimiter
610pub 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')", // Returns 'a,b,c'
622                "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        // First argument: delimiter
634        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        // Second argument: ignore_empty (treat as boolean - 0 is false, anything else is true)
645        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        // Remaining arguments: values to join
658        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
687/// Edit distance (Levenshtein distance) function
688pub struct EditDistanceFunction;
689
690impl EditDistanceFunction {
691    /// Calculate the Levenshtein distance between two strings
692    #[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, // deletion
710                    std::cmp::min(
711                        matrix[i + 1][j] + 1, // insertion
712                        matrix[i][j] + cost,  // substitution
713                    ),
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
760/// FREQUENCY function - Count occurrences of a substring in a string
761pub 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')",  // Returns 2
773                "SELECT FREQUENCY('mississippi', 'ss')", // Returns 2
774                "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        // Get the string to search in
784        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        // Get the substring to search for
792        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        // Empty search string returns 0
800        if search.is_empty() {
801            return Ok(DataValue::Integer(0));
802        }
803
804        // Count occurrences
805        let count = text.matches(&search).count();
806        Ok(DataValue::Integer(count as i64))
807    }
808}
809
810/// Register all string method functions
811pub fn register_string_methods(registry: &mut super::FunctionRegistry) {
812    use std::sync::Arc;
813
814    // Register new string functions (non-method versions)
815    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    // Register ToUpper
824    let to_upper = Arc::new(ToUpperMethod);
825    registry.register(Box::new(ToUpperMethod));
826    registry.register_method(to_upper);
827
828    // Register ToLower
829    let to_lower = Arc::new(ToLowerMethod);
830    registry.register(Box::new(ToLowerMethod));
831    registry.register_method(to_lower);
832
833    // Register Trim
834    let trim = Arc::new(TrimMethod);
835    registry.register(Box::new(TrimMethod));
836    registry.register_method(trim);
837
838    // Register Length
839    let length = Arc::new(LengthMethod);
840    registry.register(Box::new(LengthMethod));
841    registry.register_method(length);
842
843    // Register Contains
844    let contains = Arc::new(ContainsMethod);
845    registry.register(Box::new(ContainsMethod));
846    registry.register_method(contains);
847
848    // Register StartsWith
849    let starts_with = Arc::new(StartsWithMethod);
850    registry.register(Box::new(StartsWithMethod));
851    registry.register_method(starts_with);
852
853    // Register EndsWith
854    let ends_with = Arc::new(EndsWithMethod);
855    registry.register(Box::new(EndsWithMethod));
856    registry.register_method(ends_with);
857
858    // Register Substring
859    let substring = Arc::new(SubstringMethod);
860    registry.register(Box::new(SubstringMethod));
861    registry.register_method(substring);
862
863    // Register Replace
864    let replace = Arc::new(ReplaceMethod);
865    registry.register(Box::new(ReplaceMethod));
866    registry.register_method(replace);
867}