gitql_std/text/
mod.rs

1use gitql_ast::types::any::AnyType;
2use gitql_ast::types::float::FloatType;
3use gitql_ast::types::integer::IntType;
4use gitql_ast::types::optional::OptionType;
5use gitql_ast::types::text::TextType;
6use gitql_ast::types::variant::VariantType;
7use gitql_core::signature::Signature;
8use gitql_core::signature::StandardFunction;
9use gitql_core::values::integer::IntValue;
10use gitql_core::values::null::NullValue;
11use gitql_core::values::text::TextValue;
12use gitql_core::values::Value;
13
14use std::collections::HashMap;
15
16#[inline(always)]
17pub fn register_std_text_functions(map: &mut HashMap<&'static str, StandardFunction>) {
18    map.insert("bin", text_bin);
19    map.insert("lower", text_lowercase);
20    map.insert("upper", text_uppercase);
21    map.insert("reverse", text_reverse);
22    map.insert("replicate", text_replicate);
23    map.insert("space", text_space);
24    map.insert("trim", text_trim);
25    map.insert("ltrim", text_left_trim);
26    map.insert("rtrim", text_right_trim);
27    map.insert("len", text_len);
28    map.insert("ascii", text_ascii);
29    map.insert("left", text_left);
30    map.insert("datalength", text_datalength);
31    map.insert("char", text_char);
32    map.insert("nchar", text_char);
33    map.insert("charindex", text_charindex);
34    map.insert("replace", text_replace);
35    map.insert("substring", text_substring);
36    map.insert("stuff", text_stuff);
37    map.insert("right", text_right);
38    map.insert("translate", text_translate);
39    map.insert("soundex", text_soundex);
40    map.insert("concat", text_concat);
41    map.insert("concat_ws", text_concat_ws);
42    map.insert("unicode", text_unicode);
43    map.insert("strcmp", text_strcmp);
44    map.insert("quotename", text_quotename);
45    map.insert("str", text_str);
46    map.insert("to_hex", text_to_hex);
47}
48
49#[inline(always)]
50pub fn register_std_text_function_signatures(map: &mut HashMap<&'static str, Signature>) {
51    map.insert(
52        "bin",
53        Signature {
54            parameters: vec![Box::new(IntType)],
55            return_type: Box::new(TextType),
56        },
57    );
58    map.insert(
59        "lower",
60        Signature {
61            parameters: vec![Box::new(TextType)],
62            return_type: Box::new(TextType),
63        },
64    );
65    map.insert(
66        "upper",
67        Signature {
68            parameters: vec![Box::new(TextType)],
69            return_type: Box::new(TextType),
70        },
71    );
72    map.insert(
73        "reverse",
74        Signature {
75            parameters: vec![Box::new(TextType)],
76            return_type: Box::new(TextType),
77        },
78    );
79    map.insert(
80        "replicate",
81        Signature {
82            parameters: vec![Box::new(TextType), Box::new(IntType)],
83            return_type: Box::new(TextType),
84        },
85    );
86    map.insert(
87        "space",
88        Signature {
89            parameters: vec![Box::new(IntType)],
90            return_type: Box::new(TextType),
91        },
92    );
93    map.insert(
94        "trim",
95        Signature {
96            parameters: vec![Box::new(TextType)],
97            return_type: Box::new(TextType),
98        },
99    );
100    map.insert(
101        "ltrim",
102        Signature {
103            parameters: vec![Box::new(TextType)],
104            return_type: Box::new(TextType),
105        },
106    );
107    map.insert(
108        "rtrim",
109        Signature {
110            parameters: vec![Box::new(TextType)],
111            return_type: Box::new(TextType),
112        },
113    );
114    map.insert(
115        "len",
116        Signature {
117            parameters: vec![Box::new(TextType)],
118            return_type: Box::new(IntType),
119        },
120    );
121    map.insert(
122        "ascii",
123        Signature {
124            parameters: vec![Box::new(TextType)],
125            return_type: Box::new(IntType),
126        },
127    );
128    map.insert(
129        "left",
130        Signature {
131            parameters: vec![Box::new(TextType), Box::new(IntType)],
132            return_type: Box::new(TextType),
133        },
134    );
135    map.insert(
136        "datalength",
137        Signature {
138            parameters: vec![Box::new(TextType)],
139            return_type: Box::new(IntType),
140        },
141    );
142    map.insert(
143        "char",
144        Signature {
145            parameters: vec![Box::new(IntType)],
146            return_type: Box::new(TextType),
147        },
148    );
149    map.insert(
150        "nchar",
151        Signature {
152            parameters: vec![Box::new(IntType)],
153            return_type: Box::new(TextType),
154        },
155    );
156    map.insert(
157        "charindex",
158        Signature {
159            parameters: vec![Box::new(TextType), Box::new(TextType)],
160            return_type: Box::new(IntType),
161        },
162    );
163    map.insert(
164        "replace",
165        Signature {
166            parameters: vec![Box::new(TextType), Box::new(TextType), Box::new(TextType)],
167            return_type: Box::new(TextType),
168        },
169    );
170    map.insert(
171        "substring",
172        Signature {
173            parameters: vec![Box::new(TextType), Box::new(IntType), Box::new(IntType)],
174            return_type: Box::new(TextType),
175        },
176    );
177    map.insert(
178        "stuff",
179        Signature {
180            parameters: vec![
181                Box::new(TextType),
182                Box::new(IntType),
183                Box::new(IntType),
184                Box::new(TextType),
185            ],
186            return_type: Box::new(TextType),
187        },
188    );
189    map.insert(
190        "right",
191        Signature {
192            parameters: vec![Box::new(TextType), Box::new(IntType)],
193            return_type: Box::new(TextType),
194        },
195    );
196    map.insert(
197        "translate",
198        Signature {
199            parameters: vec![Box::new(TextType), Box::new(TextType), Box::new(TextType)],
200            return_type: Box::new(TextType),
201        },
202    );
203    map.insert(
204        "soundex",
205        Signature {
206            parameters: vec![Box::new(TextType)],
207            return_type: Box::new(TextType),
208        },
209    );
210    map.insert(
211        "concat",
212        Signature {
213            parameters: vec![
214                Box::new(AnyType),
215                Box::new(AnyType),
216                Box::new(VariantType {
217                    variants: vec![Box::new(AnyType)],
218                }),
219            ],
220            return_type: Box::new(TextType),
221        },
222    );
223    map.insert(
224        "concat_ws",
225        Signature {
226            parameters: vec![
227                Box::new(TextType),
228                Box::new(AnyType),
229                Box::new(AnyType),
230                Box::new(VariantType {
231                    variants: vec![Box::new(AnyType)],
232                }),
233            ],
234            return_type: Box::new(TextType),
235        },
236    );
237    map.insert(
238        "unicode",
239        Signature {
240            parameters: vec![Box::new(TextType)],
241            return_type: Box::new(IntType),
242        },
243    );
244    map.insert(
245        "strcmp",
246        Signature {
247            parameters: vec![Box::new(TextType), Box::new(TextType)],
248            return_type: Box::new(IntType),
249        },
250    );
251    map.insert(
252        "quotename",
253        Signature {
254            parameters: vec![
255                Box::new(TextType),
256                Box::new(OptionType {
257                    base: Some(Box::new(TextType)),
258                }),
259            ],
260            return_type: Box::new(TextType),
261        },
262    );
263    map.insert(
264        "str",
265        Signature {
266            parameters: vec![
267                Box::new(VariantType {
268                    variants: vec![Box::new(IntType), Box::new(FloatType)],
269                }),
270                Box::new(OptionType {
271                    base: Some(Box::new(IntType)),
272                }),
273                Box::new(OptionType {
274                    base: Some(Box::new(IntType)),
275                }),
276            ],
277            return_type: Box::new(TextType),
278        },
279    );
280    map.insert(
281        "text_to_hex",
282        Signature {
283            parameters: vec![Box::new(IntType)],
284            return_type: Box::new(TextType),
285        },
286    );
287}
288
289pub fn text_bin(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
290    let number = inputs[0].as_int().unwrap();
291    Box::new(TextValue::new(format!("{number:b}")))
292}
293
294pub fn text_lowercase(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
295    Box::new(TextValue::new(inputs[0].as_text().unwrap().to_lowercase()))
296}
297
298pub fn text_uppercase(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
299    Box::new(TextValue::new(inputs[0].as_text().unwrap().to_uppercase()))
300}
301
302pub fn text_reverse(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
303    Box::new(TextValue::new(
304        inputs[0]
305            .as_text()
306            .unwrap()
307            .chars()
308            .rev()
309            .collect::<String>(),
310    ))
311}
312
313pub fn text_replicate(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
314    let str = inputs[0].as_text().unwrap();
315    let count = inputs[1].as_int().unwrap() as usize;
316    Box::new(TextValue::new(str.repeat(count)))
317}
318
319pub fn text_space(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
320    let n = inputs[0].as_int().unwrap() as usize;
321    Box::new(TextValue::new(" ".repeat(n)))
322}
323
324pub fn text_trim(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
325    Box::new(TextValue::new(
326        inputs[0].as_text().unwrap().trim().to_string(),
327    ))
328}
329
330pub fn text_left_trim(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
331    Box::new(TextValue::new(
332        inputs[0].as_text().unwrap().trim_start().to_string(),
333    ))
334}
335
336pub fn text_right_trim(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
337    Box::new(TextValue::new(
338        inputs[0].as_text().unwrap().trim_end().to_string(),
339    ))
340}
341
342pub fn text_len(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
343    Box::new(IntValue::new(inputs[0].as_text().unwrap().len() as i64))
344}
345
346pub fn text_ascii(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
347    let text = inputs[0].as_text().unwrap();
348    let value = if text.is_empty() {
349        0
350    } else {
351        text.chars().next().unwrap() as i64
352    };
353    Box::new(IntValue::new(value))
354}
355
356pub fn text_left(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
357    let text = inputs[0].as_text().unwrap();
358    if text.is_empty() {
359        return Box::new(TextValue::empty());
360    }
361
362    let number_of_chars = inputs[1].as_int().unwrap();
363    if number_of_chars > text.len() as i64 {
364        return Box::new(TextValue::new(text));
365    }
366
367    let substring = text
368        .chars()
369        .take(number_of_chars as usize)
370        .collect::<String>();
371    Box::new(TextValue { value: substring })
372}
373
374pub fn text_datalength(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
375    let text = inputs[0].as_text().unwrap();
376    Box::new(IntValue::new(text.len() as i64))
377}
378
379pub fn text_char(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
380    let code = inputs[0].as_int().unwrap() as u32;
381    if let Some(character) = char::from_u32(code) {
382        Box::new(TextValue::new(character.to_string()))
383    } else {
384        Box::new(TextValue::empty())
385    }
386}
387
388pub fn text_charindex(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
389    let substr = inputs[0].as_text().unwrap();
390    let input = inputs[1].as_text().unwrap();
391    if let Some(index) = input.to_lowercase().find(&substr.to_lowercase()) {
392        Box::new(IntValue::new(index as i64 + 1))
393    } else {
394        Box::new(IntValue::new_zero())
395    }
396}
397
398pub fn text_replace(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
399    let text = inputs[0].as_text().unwrap();
400    let old_string = inputs[1].as_text().unwrap();
401    let new_string = inputs[2].as_text().unwrap();
402
403    let mut result = String::new();
404    let mut end = 0;
405    for (begin, matched_part) in text
406        .to_lowercase()
407        .match_indices(&old_string.to_lowercase())
408    {
409        result.push_str(text.get(end..begin).unwrap());
410        result.push_str(&new_string);
411        end = begin + matched_part.len();
412    }
413
414    result.push_str(text.get(end..text.len()).unwrap());
415    Box::new(TextValue::new(result))
416}
417
418pub fn text_substring(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
419    let text = inputs[0].as_text().unwrap();
420    // According to the specs, a string starts at position 1.
421    // but in Rust, the index of a string starts from 0
422    let start = inputs[1].as_int().unwrap() as usize - 1;
423    let length = inputs[2].as_int().unwrap();
424
425    if start > text.len() || length > text.len() as i64 {
426        return Box::new(TextValue::new(text));
427    }
428    if length < 0 {
429        return Box::new(TextValue::empty());
430    }
431
432    // Convert it to Vec<Char> to be easy to substring with support of unicode
433    let chars: Vec<char> = text.chars().collect();
434    let slice = &chars[start..(start + length as usize)];
435    Box::new(TextValue::new(slice.iter().collect()))
436}
437
438pub fn text_stuff(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
439    let text = inputs[0].as_text().unwrap();
440    let start = (inputs[1].as_int().unwrap() - 1) as usize;
441    let length = inputs[2].as_int().unwrap() as usize;
442
443    if text.is_empty() || start > text.len() || length > text.len() {
444        return Box::new(TextValue::new(text));
445    }
446
447    let mut text = text.chars().collect::<Vec<_>>();
448    let new_string = inputs[3].as_text().unwrap();
449    let new_string = new_string.chars().collect::<Vec<_>>();
450    text.splice(start..(start + length), new_string);
451    Box::new(TextValue::new(text.into_iter().collect()))
452}
453
454pub fn text_right(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
455    let text = inputs[0].as_text().unwrap();
456    if text.is_empty() {
457        return Box::new(TextValue::empty());
458    }
459
460    let number_of_chars = inputs[1].as_int().unwrap() as usize;
461    if number_of_chars > text.len() {
462        return Box::new(TextValue::new(text));
463    }
464
465    let text = text.as_str();
466    Box::new(TextValue::new(
467        text[text.len() - number_of_chars..text.len()].to_string(),
468    ))
469}
470
471pub fn text_translate(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
472    let mut text = inputs[0].as_text().unwrap();
473    let characters = inputs[1].as_text().unwrap();
474    let translations = inputs[2].as_text().unwrap();
475
476    if translations.len() != characters.len() {
477        return Box::new(TextValue::empty());
478    }
479
480    let translations = translations.chars().collect::<Vec<_>>();
481    for (idx, letter) in characters.char_indices() {
482        text = text.replace(letter, &char::to_string(&translations[idx]));
483    }
484
485    Box::new(TextValue::new(text))
486}
487
488pub fn text_unicode(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
489    let value = if let Some(c) = inputs[0].as_text().unwrap().chars().next() {
490        (c as u32).into()
491    } else {
492        0
493    };
494    Box::new(IntValue::new(value))
495}
496
497pub fn text_soundex(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
498    let text = inputs[0].as_text().unwrap();
499    if text.is_empty() {
500        return Box::new(TextValue::empty());
501    }
502
503    let mut result = String::from(text.chars().next().unwrap());
504    for (idx, letter) in text.char_indices() {
505        if idx != 0 {
506            let letter = letter.to_ascii_uppercase();
507            if !matches!(letter, 'A' | 'E' | 'I' | 'O' | 'U' | 'H' | 'W' | 'Y') {
508                let int = match letter {
509                    'B' | 'F' | 'P' | 'V' => 1,
510                    'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => 2,
511                    'D' | 'T' => 3,
512                    'L' => 4,
513                    'M' | 'N' => 5,
514                    'R' => 6,
515                    _ => 0,
516                };
517                result.push_str(&int.to_string());
518
519                if result.len() == 4 {
520                    return Box::new(TextValue::new(result));
521                }
522            }
523        }
524    }
525
526    if result.len() < 4 {
527        let diff = 4 - result.len();
528        for _i in 0..diff {
529            result.push_str(&0.to_string());
530        }
531    }
532
533    Box::new(TextValue::new(result))
534}
535
536pub fn text_concat(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
537    let text: Vec<String> = inputs.iter().map(|v| v.to_string()).collect();
538    Box::new(TextValue::new(text.concat()))
539}
540
541pub fn text_concat_ws(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
542    let separator = inputs[0].as_text().unwrap();
543    let text: Vec<String> = inputs.iter().skip(1).map(|v| v.to_string()).collect();
544    let value = text.join(&separator);
545    Box::new(TextValue::new(value))
546}
547
548pub fn text_strcmp(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
549    let value = match inputs[0].as_text().cmp(&inputs[1].as_text()) {
550        std::cmp::Ordering::Less => 1,
551        std::cmp::Ordering::Equal => 2,
552        std::cmp::Ordering::Greater => 0,
553    };
554    Box::new(IntValue::new(value))
555}
556
557pub fn text_quotename(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
558    let str = inputs[0].as_text().unwrap();
559    let quote = inputs
560        .get(1)
561        .map(|v| v.as_text().unwrap())
562        .map(|str| str.chars().collect())
563        .unwrap_or_else(|| vec!['[', ']']);
564
565    match quote.as_slice() {
566        [single] => Box::new(TextValue {
567            value: format!("{single}{str}{single}"),
568        }),
569        [start, end] => Box::new(TextValue {
570            value: format!("{start}{str}{end}"),
571        }),
572        _ => Box::new(NullValue),
573    }
574}
575
576pub fn text_str(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
577    let value = &inputs[0];
578    let length = if inputs.len() == 3 {
579        inputs[1].as_int().unwrap()
580    } else {
581        10
582    };
583
584    let decimals = if inputs.len() == 3 {
585        inputs[2].as_int().unwrap()
586    } else {
587        0
588    };
589
590    if value.data_type().is_int() {
591        let int_value = value.as_int().unwrap();
592        let number_string = format!("{:.dec$}", int_value, dec = decimals as usize);
593        if length > 0 {
594            if (length as usize) < number_string.len() {
595                return Box::new(TextValue::new(number_string[..length as usize].to_owned()));
596            }
597
598            return Box::new(TextValue::new(format!(
599                "{:<len$}",
600                number_string,
601                len = length as usize
602            )));
603        }
604
605        return Box::new(TextValue::new(number_string.clone()));
606    }
607
608    let float_value = value.as_float().unwrap();
609    let number_string = format!("{:.dec$}", float_value, dec = decimals as usize);
610    if length > 0 {
611        if (length as usize) < number_string.len() {
612            return Box::new(TextValue::new(number_string[..length as usize].to_owned()));
613        }
614
615        return Box::new(TextValue::new(format!(
616            "{:<len$}",
617            number_string,
618            len = length as usize
619        )));
620    }
621
622    Box::new(TextValue::new(number_string.clone()))
623}
624
625pub fn text_to_hex(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
626    let number = inputs[0].as_int().unwrap();
627    let value = format!("0x{}", number);
628    Box::new(TextValue::new(value))
629}