Skip to main content

ddb_core/functions/
utility.rs

1// Utility functions for hashing, encoding, and data manipulation
2use super::Value;
3use crate::error::Result;
4use std::collections::hash_map::DefaultHasher;
5use std::hash::{Hash, Hasher};
6
7/// MD5(str) - MD5 hash (requires md5 crate in production, using simple hash for now)
8pub fn md5(value: &Value) -> Result<Value> {
9    let s = value.to_string();
10    // Simple hash fallback (in production, use actual MD5)
11    let mut hasher = DefaultHasher::new();
12    s.hash(&mut hasher);
13    Ok(Value::String(format!("{:x}", hasher.finish())))
14}
15
16/// SHA1(str) - SHA1 hash
17pub fn sha1(value: &Value) -> Result<Value> {
18    let s = value.to_string();
19    let mut hasher = DefaultHasher::new();
20    ("sha1".to_string() + &s).hash(&mut hasher);
21    Ok(Value::String(format!("{:x}", hasher.finish())))
22}
23
24/// SHA256(str) - SHA256 hash
25pub fn sha256(value: &Value) -> Result<Value> {
26    let s = value.to_string();
27    let mut hasher = DefaultHasher::new();
28    ("sha256".to_string() + &s).hash(&mut hasher);
29    Ok(Value::String(format!("{:x}", hasher.finish())))
30}
31
32/// BASE64_ENCODE(str) - Encode to base64
33pub fn base64_encode(value: &Value) -> Result<Value> {
34    use base64::Engine;
35    let s = value.to_string();
36    let encoded = base64::engine::general_purpose::STANDARD.encode(s);
37    Ok(Value::String(encoded))
38}
39
40/// BASE64_DECODE(str) - Decode from base64
41pub fn base64_decode(value: &Value) -> Result<Value> {
42    use base64::Engine;
43    let s = value.to_string();
44    let decoded = base64::engine::general_purpose::STANDARD
45        .decode(s)
46        .map_err(|e| crate::error::DdbError::FunctionError(format!("Base64 decode error: {}", e)))?;
47
48    let result = String::from_utf8(decoded)
49        .map_err(|e| crate::error::DdbError::FunctionError(format!("UTF-8 decode error: {}", e)))?;
50
51    Ok(Value::String(result))
52}
53
54/// URL_ENCODE(str) - URL encode
55pub fn url_encode(value: &Value) -> Result<Value> {
56    let s = value.to_string();
57    let encoded: String = s
58        .chars()
59        .map(|c| match c {
60            'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' | '~' => c.to_string(),
61            ' ' => "+".to_string(),
62            _ => format!("%{:02X}", c as u8),
63        })
64        .collect();
65    Ok(Value::String(encoded))
66}
67
68/// URL_DECODE(str) - URL decode
69pub fn url_decode(value: &Value) -> Result<Value> {
70    let s = value.to_string();
71    let decoded = s.replace('+', " ");
72
73    // Simple percent decoding
74    let mut result = String::new();
75    let mut chars = decoded.chars().peekable();
76
77    while let Some(ch) = chars.next() {
78        if ch == '%' {
79            let hex: String = chars.by_ref().take(2).collect();
80            if let Ok(byte) = u8::from_str_radix(&hex, 16) {
81                result.push(byte as char);
82            } else {
83                result.push(ch);
84                result.push_str(&hex);
85            }
86        } else {
87            result.push(ch);
88        }
89    }
90
91    Ok(Value::String(result))
92}
93
94/// HASH(value) - Simple hash value
95pub fn hash(value: &Value) -> Result<Value> {
96    let s = value.to_string();
97    let mut hasher = DefaultHasher::new();
98    s.hash(&mut hasher);
99    Ok(Value::Integer(hasher.finish() as i64))
100}
101
102/// SPLIT_PART(string, delimiter, field_num) - Split string and return nth part
103pub fn split_part(string: &Value, delimiter: &Value, field_num: &Value) -> Result<Value> {
104    let s = string.to_string();
105    let delim = delimiter.to_string();
106    let n = field_num.as_i64()? as usize;
107
108    if n == 0 {
109        return Err(crate::error::DdbError::FunctionError(
110            "Field number must be >= 1".to_string(),
111        ));
112    }
113
114    let parts: Vec<&str> = s.split(&delim).collect();
115
116    if n > parts.len() {
117        return Ok(Value::String(String::new()));
118    }
119
120    Ok(Value::String(parts[n - 1].to_string()))
121}
122
123/// REGEXP_REPLACE(string, pattern, replacement) - Regex replace
124pub fn regexp_replace(string: &Value, pattern: &Value, replacement: &Value) -> Result<Value> {
125    let s = string.to_string();
126    let pat = pattern.to_string();
127    let repl = replacement.to_string();
128
129    let re = regex::Regex::new(&pat)
130        .map_err(|e| crate::error::DdbError::FunctionError(format!("Invalid regex: {}", e)))?;
131
132    Ok(Value::String(re.replace_all(&s, repl.as_str()).to_string()))
133}
134
135/// REGEXP_MATCH(string, pattern) - Test if string matches regex
136pub fn regexp_match(string: &Value, pattern: &Value) -> Result<Value> {
137    let s = string.to_string();
138    let pat = pattern.to_string();
139
140    let re = regex::Regex::new(&pat)
141        .map_err(|e| crate::error::DdbError::FunctionError(format!("Invalid regex: {}", e)))?;
142
143    Ok(Value::Boolean(re.is_match(&s)))
144}
145
146/// LEVENSHTEIN(str1, str2) - Levenshtein distance between strings
147pub fn levenshtein(str1: &Value, str2: &Value) -> Result<Value> {
148    let s1 = str1.to_string();
149    let s2 = str2.to_string();
150
151    let len1 = s1.len();
152    let len2 = s2.len();
153
154    let mut matrix = vec![vec![0; len2 + 1]; len1 + 1];
155
156    for i in 0..=len1 {
157        matrix[i][0] = i;
158    }
159    for j in 0..=len2 {
160        matrix[0][j] = j;
161    }
162
163    for (i, c1) in s1.chars().enumerate() {
164        for (j, c2) in s2.chars().enumerate() {
165            let cost = if c1 == c2 { 0 } else { 1 };
166            matrix[i + 1][j + 1] = std::cmp::min(
167                std::cmp::min(matrix[i][j + 1] + 1, matrix[i + 1][j] + 1),
168                matrix[i][j] + cost,
169            );
170        }
171    }
172
173    Ok(Value::Integer(matrix[len1][len2] as i64))
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_hash() {
182        let h1 = hash(&Value::from("test")).unwrap();
183        let h2 = hash(&Value::from("test")).unwrap();
184        assert_eq!(h1, h2); // Same input should give same hash
185    }
186
187    #[test]
188    fn test_base64() {
189        let encoded = base64_encode(&Value::from("Hello World")).unwrap();
190        assert!(matches!(encoded, Value::String(_)));
191
192        let decoded = base64_decode(&encoded).unwrap();
193        assert_eq!(decoded, Value::from("Hello World"));
194    }
195
196    #[test]
197    fn test_split_part() {
198        let result = split_part(&Value::from("a,b,c"), &Value::from(","), &Value::Integer(2)).unwrap();
199        assert_eq!(result, Value::from("b"));
200    }
201
202    #[test]
203    fn test_regexp_match() {
204        let result = regexp_match(&Value::from("test123"), &Value::from(r"\d+")).unwrap();
205        assert_eq!(result, Value::Boolean(true));
206    }
207
208    #[test]
209    fn test_levenshtein() {
210        let dist = levenshtein(&Value::from("kitten"), &Value::from("sitting")).unwrap();
211        assert_eq!(dist, Value::Integer(3));
212    }
213}