1use crate::data::datatable::DataValue;
2use crate::sql::functions::{ArgCount, FunctionCategory, FunctionSignature, SqlFunction};
3use anyhow::{anyhow, Result};
4use std::collections::HashMap;
5
6pub struct ReverseFunction;
8
9impl SqlFunction for ReverseFunction {
10 fn signature(&self) -> FunctionSignature {
11 FunctionSignature {
12 name: "REVERSE",
13 category: FunctionCategory::String,
14 arg_count: ArgCount::Fixed(1),
15 description: "Reverses the characters in a string",
16 returns: "Reversed string",
17 examples: vec![
18 "SELECT REVERSE('hello') -- returns 'olleh'",
19 "SELECT REVERSE('SQL CLI') -- returns 'ILC LQS'",
20 ],
21 }
22 }
23
24 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
25 if args.len() != 1 {
26 return Err(anyhow!("REVERSE requires exactly 1 argument"));
27 }
28
29 match &args[0] {
30 DataValue::String(s) => Ok(DataValue::String(s.chars().rev().collect())),
31 DataValue::InternedString(s) => Ok(DataValue::String(s.chars().rev().collect())),
32 DataValue::Null => Ok(DataValue::Null),
33 _ => Err(anyhow!("REVERSE requires a string argument")),
34 }
35 }
36}
37
38pub struct InitCapFunction;
40
41impl SqlFunction for InitCapFunction {
42 fn signature(&self) -> FunctionSignature {
43 FunctionSignature {
44 name: "INITCAP",
45 category: FunctionCategory::String,
46 arg_count: ArgCount::Fixed(1),
47 description: "Capitalizes the first letter of each word",
48 returns: "String with each word capitalized",
49 examples: vec![
50 "SELECT INITCAP('hello world') -- returns 'Hello World'",
51 "SELECT INITCAP('sql-cli is great') -- returns 'Sql-Cli Is Great'",
52 ],
53 }
54 }
55
56 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
57 if args.len() != 1 {
58 return Err(anyhow!("INITCAP requires exactly 1 argument"));
59 }
60
61 match &args[0] {
62 DataValue::String(s) => {
63 let mut result = String::new();
64 let mut capitalize_next = true;
65
66 for ch in s.chars() {
67 if ch.is_alphabetic() {
68 if capitalize_next {
69 result.push(ch.to_uppercase().next().unwrap_or(ch));
70 capitalize_next = false;
71 } else {
72 result.push(ch.to_lowercase().next().unwrap_or(ch));
73 }
74 } else {
75 result.push(ch);
76 capitalize_next = !ch.is_ascii_alphanumeric();
77 }
78 }
79
80 Ok(DataValue::String(result))
81 }
82 DataValue::InternedString(s) => {
83 let mut result = String::new();
84 let mut capitalize_next = true;
85
86 for ch in s.chars() {
87 if ch.is_alphabetic() {
88 if capitalize_next {
89 result.push(ch.to_uppercase().next().unwrap_or(ch));
90 capitalize_next = false;
91 } else {
92 result.push(ch.to_lowercase().next().unwrap_or(ch));
93 }
94 } else {
95 result.push(ch);
96 capitalize_next = !ch.is_ascii_alphanumeric();
97 }
98 }
99
100 Ok(DataValue::String(result))
101 }
102 DataValue::Null => Ok(DataValue::Null),
103 _ => Err(anyhow!("INITCAP requires a string argument")),
104 }
105 }
106}
107
108pub struct ProperFunction;
110
111impl SqlFunction for ProperFunction {
112 fn signature(&self) -> FunctionSignature {
113 FunctionSignature {
114 name: "PROPER",
115 category: FunctionCategory::String,
116 arg_count: ArgCount::Fixed(1),
117 description: "Alias for INITCAP - capitalizes first letter of each word",
118 returns: "String with each word capitalized",
119 examples: vec!["SELECT PROPER('hello world') -- returns 'Hello World'"],
120 }
121 }
122
123 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
124 InitCapFunction.evaluate(args)
125 }
126}
127
128pub struct Rot13Function;
130
131impl SqlFunction for Rot13Function {
132 fn signature(&self) -> FunctionSignature {
133 FunctionSignature {
134 name: "ROT13",
135 category: FunctionCategory::String,
136 arg_count: ArgCount::Fixed(1),
137 description: "Applies ROT13 encoding (shifts letters by 13 positions)",
138 returns: "ROT13 encoded string",
139 examples: vec![
140 "SELECT ROT13('hello') -- returns 'uryyb'",
141 "SELECT ROT13('uryyb') -- returns 'hello' (ROT13 is reversible)",
142 "SELECT ROT13('SQL123') -- returns 'FDY123' (numbers unchanged)",
143 ],
144 }
145 }
146
147 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
148 if args.len() != 1 {
149 return Err(anyhow!("ROT13 requires exactly 1 argument"));
150 }
151
152 match &args[0] {
153 DataValue::String(s) => {
154 let result: String = s
155 .chars()
156 .map(|ch| match ch {
157 'A'..='M' | 'a'..='m' => ((ch as u8) + 13) as char,
158 'N'..='Z' | 'n'..='z' => ((ch as u8) - 13) as char,
159 _ => ch,
160 })
161 .collect();
162 Ok(DataValue::String(result))
163 }
164 DataValue::InternedString(s) => {
165 let result: String = s
166 .chars()
167 .map(|ch| match ch {
168 'A'..='M' | 'a'..='m' => ((ch as u8) + 13) as char,
169 'N'..='Z' | 'n'..='z' => ((ch as u8) - 13) as char,
170 _ => ch,
171 })
172 .collect();
173 Ok(DataValue::String(result))
174 }
175 DataValue::Null => Ok(DataValue::Null),
176 _ => Err(anyhow!("ROT13 requires a string argument")),
177 }
178 }
179}
180
181pub struct SoundexFunction;
183
184impl SqlFunction for SoundexFunction {
185 fn signature(&self) -> FunctionSignature {
186 FunctionSignature {
187 name: "SOUNDEX",
188 category: FunctionCategory::String,
189 arg_count: ArgCount::Fixed(1),
190 description: "Returns the Soundex code for phonetic matching",
191 returns: "4-character Soundex code",
192 examples: vec![
193 "SELECT SOUNDEX('Smith') -- returns 'S530'",
194 "SELECT SOUNDEX('Smythe') -- returns 'S530' (sounds similar)",
195 "SELECT SOUNDEX('Johnson') -- returns 'J525'",
196 "SELECT SOUNDEX('Jonson') -- returns 'J525' (sounds similar)",
197 ],
198 }
199 }
200
201 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
202 if args.len() != 1 {
203 return Err(anyhow!("SOUNDEX requires exactly 1 argument"));
204 }
205
206 match &args[0] {
207 DataValue::String(s) => Ok(DataValue::String(compute_soundex(s))),
208 DataValue::InternedString(s) => Ok(DataValue::String(compute_soundex(s))),
209 DataValue::Null => Ok(DataValue::Null),
210 _ => Err(anyhow!("SOUNDEX requires a string argument")),
211 }
212 }
213}
214
215fn compute_soundex(s: &str) -> String {
216 if s.is_empty() {
217 return String::new();
218 }
219
220 let s_upper = s.to_uppercase();
221 let mut result = String::new();
222
223 if let Some(first_char) = s_upper.chars().next() {
225 if first_char.is_alphabetic() {
226 result.push(first_char);
227 } else {
228 return String::new();
229 }
230 }
231
232 let soundex_map: HashMap<char, char> = [
234 ('B', '1'),
235 ('F', '1'),
236 ('P', '1'),
237 ('V', '1'),
238 ('C', '2'),
239 ('G', '2'),
240 ('J', '2'),
241 ('K', '2'),
242 ('Q', '2'),
243 ('S', '2'),
244 ('X', '2'),
245 ('Z', '2'),
246 ('D', '3'),
247 ('T', '3'),
248 ('L', '4'),
249 ('M', '5'),
250 ('N', '5'),
251 ('R', '6'),
252 ]
253 .iter()
254 .cloned()
255 .collect();
256
257 let mut last_code = ' ';
258 for ch in s_upper.chars().skip(1) {
259 if let Some(&code) = soundex_map.get(&ch) {
260 if code != last_code {
261 result.push(code);
262 last_code = code;
263 }
264 } else if "AEIOUYHW".contains(ch) {
265 last_code = ' ';
266 }
267
268 if result.len() >= 4 {
269 break;
270 }
271 }
272
273 while result.len() < 4 {
275 result.push('0');
276 }
277
278 result.chars().take(4).collect()
279}
280
281pub struct PigLatinFunction;
283
284impl SqlFunction for PigLatinFunction {
285 fn signature(&self) -> FunctionSignature {
286 FunctionSignature {
287 name: "PIG_LATIN",
288 category: FunctionCategory::String,
289 arg_count: ArgCount::Fixed(1),
290 description: "Converts text to Pig Latin",
291 returns: "Pig Latin version of the text",
292 examples: vec![
293 "SELECT PIG_LATIN('hello') -- returns 'ellohay'",
294 "SELECT PIG_LATIN('apple') -- returns 'appleway'",
295 "SELECT PIG_LATIN('SQL') -- returns 'SQLay'",
296 "SELECT PIG_LATIN('hello world') -- returns 'ellohay orldway'",
297 ],
298 }
299 }
300
301 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
302 if args.len() != 1 {
303 return Err(anyhow!("PIG_LATIN requires exactly 1 argument"));
304 }
305
306 match &args[0] {
307 DataValue::String(s) => {
308 let result: Vec<String> = s
309 .split_whitespace()
310 .map(|word| pig_latin_word(word))
311 .collect();
312 Ok(DataValue::String(result.join(" ")))
313 }
314 DataValue::InternedString(s) => {
315 let result: Vec<String> = s
316 .split_whitespace()
317 .map(|word| pig_latin_word(word))
318 .collect();
319 Ok(DataValue::String(result.join(" ")))
320 }
321 DataValue::Null => Ok(DataValue::Null),
322 _ => Err(anyhow!("PIG_LATIN requires a string argument")),
323 }
324 }
325}
326
327fn pig_latin_word(word: &str) -> String {
328 if word.is_empty() {
329 return String::new();
330 }
331
332 let vowels = "aeiouAEIOU";
333 let word_lower = word.to_lowercase();
334
335 if let Some(first_char) = word_lower.chars().next() {
337 if vowels.contains(first_char) {
338 return format!("{}way", word);
339 }
340 }
341
342 if let Some(vowel_pos) = word_lower.chars().position(|c| vowels.contains(c)) {
344 if vowel_pos > 0 {
345 let consonant_cluster = &word[..vowel_pos];
346 let rest = &word[vowel_pos..];
347 return format!("{}{}ay", rest, consonant_cluster.to_lowercase());
348 }
349 }
350
351 format!("{}ay", word.to_lowercase())
353}
354
355pub struct MorseCodeFunction;
357
358impl SqlFunction for MorseCodeFunction {
359 fn signature(&self) -> FunctionSignature {
360 FunctionSignature {
361 name: "MORSE_CODE",
362 category: FunctionCategory::String,
363 arg_count: ArgCount::Fixed(1),
364 description: "Converts text to Morse code",
365 returns: "Morse code representation",
366 examples: vec![
367 "SELECT MORSE_CODE('SOS') -- returns '... --- ...'",
368 "SELECT MORSE_CODE('HELLO') -- returns '.... . .-.. .-.. ---'",
369 "SELECT MORSE_CODE('SQL 123') -- returns '... --.- .-.. .---- ..--- ...--'",
370 ],
371 }
372 }
373
374 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
375 if args.len() != 1 {
376 return Err(anyhow!("MORSE_CODE requires exactly 1 argument"));
377 }
378
379 match &args[0] {
380 DataValue::String(s) => Ok(DataValue::String(to_morse_code(s))),
381 DataValue::InternedString(s) => Ok(DataValue::String(to_morse_code(s))),
382 DataValue::Null => Ok(DataValue::Null),
383 _ => Err(anyhow!("MORSE_CODE requires a string argument")),
384 }
385 }
386}
387
388fn to_morse_code(text: &str) -> String {
389 let morse_map: HashMap<char, &str> = [
390 ('A', ".-"),
391 ('B', "-..."),
392 ('C', "-.-."),
393 ('D', "-.."),
394 ('E', "."),
395 ('F', "..-."),
396 ('G', "--."),
397 ('H', "...."),
398 ('I', ".."),
399 ('J', ".---"),
400 ('K', "-.-"),
401 ('L', ".-.."),
402 ('M', "--"),
403 ('N', "-."),
404 ('O', "---"),
405 ('P', ".--."),
406 ('Q', "--.-"),
407 ('R', ".-."),
408 ('S', "..."),
409 ('T', "-"),
410 ('U', "..-"),
411 ('V', "...-"),
412 ('W', ".--"),
413 ('X', "-..-"),
414 ('Y', "-.--"),
415 ('Z', "--.."),
416 ('0', "-----"),
417 ('1', ".----"),
418 ('2', "..---"),
419 ('3', "...--"),
420 ('4', "....-"),
421 ('5', "....."),
422 ('6', "-...."),
423 ('7', "--..."),
424 ('8', "---.."),
425 ('9', "----."),
426 (' ', " "),
427 ]
428 .iter()
429 .cloned()
430 .collect();
431
432 text.to_uppercase()
433 .chars()
434 .filter_map(|ch| morse_map.get(&ch).copied())
435 .collect::<Vec<_>>()
436 .join(" ")
437 .replace(" ", " ") }
439
440pub struct ScrambleFunction;
442
443impl SqlFunction for ScrambleFunction {
444 fn signature(&self) -> FunctionSignature {
445 FunctionSignature {
446 name: "SCRAMBLE",
447 category: FunctionCategory::String,
448 arg_count: ArgCount::Fixed(1),
449 description: "Scrambles letters in words (keeps first and last letter)",
450 returns: "Scrambled text that's still somewhat readable",
451 examples: vec![
452 "SELECT SCRAMBLE('hello') -- might return 'hlelo'",
453 "SELECT SCRAMBLE('according') -- might return 'acdorcnig'",
454 "SELECT SCRAMBLE('The quick brown fox') -- scrambles each word",
455 ],
456 }
457 }
458
459 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
460 if args.len() != 1 {
461 return Err(anyhow!("SCRAMBLE requires exactly 1 argument"));
462 }
463
464 match &args[0] {
465 DataValue::String(s) => Ok(DataValue::String(scramble_text(s))),
466 DataValue::InternedString(s) => Ok(DataValue::String(scramble_text(s))),
467 DataValue::Null => Ok(DataValue::Null),
468 _ => Err(anyhow!("SCRAMBLE requires a string argument")),
469 }
470 }
471}
472
473fn scramble_text(text: &str) -> String {
474 use rand::seq::SliceRandom;
475 use rand::thread_rng;
476
477 let words: Vec<String> = text
478 .split_whitespace()
479 .map(|word| {
480 if word.len() <= 3 {
481 word.to_string()
482 } else {
483 let chars: Vec<char> = word.chars().collect();
484 let first = chars[0];
485 let last = chars[chars.len() - 1];
486 let mut middle: Vec<char> = chars[1..chars.len() - 1].to_vec();
487 middle.shuffle(&mut thread_rng());
488
489 let mut result = String::new();
490 result.push(first);
491 result.extend(middle);
492 result.push(last);
493 result
494 }
495 })
496 .collect();
497
498 words.join(" ")
499}