Skip to main content

ddb_core/functions/
string.rs

1// String functions
2use super::Value;
3use crate::error::Result;
4
5/// CONCAT(str1, str2, ...) - Concatenate strings
6pub fn concat(values: &[Value]) -> Result<Value> {
7    let result = values
8        .iter()
9        .map(|v| v.to_string())
10        .collect::<Vec<_>>()
11        .join("");
12    Ok(Value::String(result))
13}
14
15/// CONCAT_WS(separator, str1, str2, ...) - Concatenate with separator
16pub fn concat_ws(separator: &Value, values: &[Value]) -> Result<Value> {
17    let sep = separator.to_string();
18    let result = values
19        .iter()
20        .map(|v| v.to_string())
21        .collect::<Vec<_>>()
22        .join(&sep);
23    Ok(Value::String(result))
24}
25
26/// UPPER(str) / UCASE(str) - Convert to uppercase
27pub fn upper(value: &Value) -> Result<Value> {
28    Ok(Value::String(value.to_string().to_uppercase()))
29}
30
31/// LOWER(str) / LCASE(str) - Convert to lowercase
32pub fn lower(value: &Value) -> Result<Value> {
33    Ok(Value::String(value.to_string().to_lowercase()))
34}
35
36/// LENGTH(str) / CHAR_LENGTH(str) - String length
37pub fn length(value: &Value) -> Result<Value> {
38    Ok(Value::Integer(value.to_string().len() as i64))
39}
40
41/// TRIM(str) - Remove leading/trailing whitespace
42pub fn trim(value: &Value) -> Result<Value> {
43    Ok(Value::String(value.to_string().trim().to_string()))
44}
45
46/// LTRIM(str) - Remove leading whitespace
47pub fn ltrim(value: &Value) -> Result<Value> {
48    Ok(Value::String(value.to_string().trim_start().to_string()))
49}
50
51/// RTRIM(str) - Remove trailing whitespace
52pub fn rtrim(value: &Value) -> Result<Value> {
53    Ok(Value::String(value.to_string().trim_end().to_string()))
54}
55
56/// SUBSTR(str, pos, [len]) / SUBSTRING(str, pos, [len])
57pub fn substr(value: &Value, pos: &Value, len: Option<&Value>) -> Result<Value> {
58    let s = value.to_string();
59    let start = (pos.as_i64()? - 1).max(0) as usize; // SQL is 1-indexed
60
61    if start >= s.len() {
62        return Ok(Value::String(String::new()));
63    }
64
65    let result = if let Some(length) = len {
66        let length = length.as_i64()?.max(0) as usize;
67        s.chars().skip(start).take(length).collect()
68    } else {
69        s.chars().skip(start).collect()
70    };
71
72    Ok(Value::String(result))
73}
74
75/// LEFT(str, len) - Return leftmost characters
76pub fn left(value: &Value, len: &Value) -> Result<Value> {
77    let s = value.to_string();
78    let n = len.as_i64()?.max(0) as usize;
79    Ok(Value::String(s.chars().take(n).collect()))
80}
81
82/// RIGHT(str, len) - Return rightmost characters
83pub fn right(value: &Value, len: &Value) -> Result<Value> {
84    let s = value.to_string();
85    let n = len.as_i64()?.max(0) as usize;
86    let chars: Vec<char> = s.chars().collect();
87    let start = chars.len().saturating_sub(n);
88    Ok(Value::String(chars[start..].iter().collect()))
89}
90
91/// REPLACE(str, from, to) - Replace all occurrences
92pub fn replace(value: &Value, from: &Value, to: &Value) -> Result<Value> {
93    let s = value.to_string();
94    let from_str = from.to_string();
95    let to_str = to.to_string();
96    Ok(Value::String(s.replace(&from_str, &to_str)))
97}
98
99/// REVERSE(str) - Reverse string
100pub fn reverse(value: &Value) -> Result<Value> {
101    Ok(Value::String(value.to_string().chars().rev().collect()))
102}
103
104/// LPAD(str, len, pad) - Pad left side
105pub fn lpad(value: &Value, len: &Value, pad: &Value) -> Result<Value> {
106    let s = value.to_string();
107    let target_len = len.as_i64()? as usize;
108    let pad_str = pad.to_string();
109
110    if s.len() >= target_len {
111        return Ok(Value::String(s.chars().take(target_len).collect()));
112    }
113
114    let padding_needed = target_len - s.len();
115    let pad_repeated = pad_str.repeat((padding_needed / pad_str.len()) + 1);
116    let result = format!("{}{}", &pad_repeated[..padding_needed], s);
117
118    Ok(Value::String(result))
119}
120
121/// RPAD(str, len, pad) - Pad right side
122pub fn rpad(value: &Value, len: &Value, pad: &Value) -> Result<Value> {
123    let s = value.to_string();
124    let target_len = len.as_i64()? as usize;
125    let pad_str = pad.to_string();
126
127    if s.len() >= target_len {
128        return Ok(Value::String(s.chars().take(target_len).collect()));
129    }
130
131    let padding_needed = target_len - s.len();
132    let pad_repeated = pad_str.repeat((padding_needed / pad_str.len()) + 1);
133    let result = format!("{}{}", s, &pad_repeated[..padding_needed]);
134
135    Ok(Value::String(result))
136}
137
138/// POSITION(substr IN str) / INSTR(str, substr)
139pub fn position(substr: &Value, string: &Value) -> Result<Value> {
140    let s = string.to_string();
141    let sub = substr.to_string();
142
143    match s.find(&sub) {
144        Some(pos) => Ok(Value::Integer((pos + 1) as i64)), // SQL is 1-indexed
145        None => Ok(Value::Integer(0)),
146    }
147}
148
149/// REPEAT(str, count) - Repeat string n times
150pub fn repeat(value: &Value, count: &Value) -> Result<Value> {
151    let s = value.to_string();
152    let n = count.as_i64()?.max(0) as usize;
153    Ok(Value::String(s.repeat(n)))
154}
155
156/// SPACE(count) - Return string of spaces
157pub fn space(count: &Value) -> Result<Value> {
158    let n = count.as_i64()?.max(0) as usize;
159    Ok(Value::String(" ".repeat(n)))
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_concat() {
168        let vals = vec![Value::from("Hello"), Value::from(" "), Value::from("World")];
169        assert_eq!(concat(&vals).unwrap(), Value::from("Hello World"));
170    }
171
172    #[test]
173    fn test_upper_lower() {
174        assert_eq!(upper(&Value::from("hello")).unwrap(), Value::from("HELLO"));
175        assert_eq!(lower(&Value::from("WORLD")).unwrap(), Value::from("world"));
176    }
177
178    #[test]
179    fn test_substr() {
180        let s = Value::from("Hello World");
181        assert_eq!(
182            substr(&s, &Value::Integer(1), Some(&Value::Integer(5))).unwrap(),
183            Value::from("Hello")
184        );
185        assert_eq!(
186            substr(&s, &Value::Integer(7), None).unwrap(),
187            Value::from("World")
188        );
189    }
190
191    #[test]
192    fn test_trim() {
193        assert_eq!(trim(&Value::from("  hello  ")).unwrap(), Value::from("hello"));
194        assert_eq!(ltrim(&Value::from("  hello")).unwrap(), Value::from("hello"));
195        assert_eq!(rtrim(&Value::from("hello  ")).unwrap(), Value::from("hello"));
196    }
197
198    #[test]
199    fn test_replace() {
200        assert_eq!(
201            replace(&Value::from("hello world"), &Value::from("world"), &Value::from("rust")).unwrap(),
202            Value::from("hello rust")
203        );
204    }
205}