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 {
292        value: format!("{number:b}"),
293    })
294}
295
296pub fn text_lowercase(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
297    Box::new(TextValue {
298        value: inputs[0].as_text().unwrap().to_lowercase(),
299    })
300}
301
302pub fn text_uppercase(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
303    Box::new(TextValue {
304        value: inputs[0].as_text().unwrap().to_uppercase(),
305    })
306}
307
308pub fn text_reverse(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
309    Box::new(TextValue {
310        value: inputs[0]
311            .as_text()
312            .unwrap()
313            .chars()
314            .rev()
315            .collect::<String>(),
316    })
317}
318
319pub fn text_replicate(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
320    let str = inputs[0].as_text().unwrap();
321    let count = inputs[1].as_int().unwrap() as usize;
322    Box::new(TextValue {
323        value: str.repeat(count),
324    })
325}
326
327pub fn text_space(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
328    let n = inputs[0].as_int().unwrap() as usize;
329    Box::new(TextValue {
330        value: " ".repeat(n),
331    })
332}
333
334pub fn text_trim(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
335    Box::new(TextValue {
336        value: inputs[0].as_text().unwrap().trim().to_string(),
337    })
338}
339
340pub fn text_left_trim(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
341    Box::new(TextValue {
342        value: inputs[0].as_text().unwrap().trim_start().to_string(),
343    })
344}
345
346pub fn text_right_trim(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
347    Box::new(TextValue {
348        value: inputs[0].as_text().unwrap().trim_end().to_string(),
349    })
350}
351
352pub fn text_len(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
353    Box::new(IntValue {
354        value: inputs[0].as_text().unwrap().len() as i64,
355    })
356}
357
358pub fn text_ascii(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
359    let text = inputs[0].as_text().unwrap();
360    let value = if text.is_empty() {
361        0
362    } else {
363        text.chars().next().unwrap() as i64
364    };
365
366    Box::new(IntValue { value })
367}
368
369pub fn text_left(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
370    let text = inputs[0].as_text().unwrap();
371    if text.is_empty() {
372        return Box::new(TextValue {
373            value: "".to_string(),
374        });
375    }
376
377    let number_of_chars = inputs[1].as_int().unwrap();
378    if number_of_chars > text.len() as i64 {
379        return Box::new(TextValue { value: text });
380    }
381
382    let substring = text
383        .chars()
384        .take(number_of_chars as usize)
385        .collect::<String>();
386    Box::new(TextValue { value: substring })
387}
388
389pub fn text_datalength(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
390    let text = inputs[0].as_text().unwrap();
391    Box::new(IntValue::new(text.len() as i64))
392}
393
394pub fn text_char(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
395    let code = inputs[0].as_int().unwrap() as u32;
396    let value = if let Some(character) = char::from_u32(code) {
397        character.to_string()
398    } else {
399        "".to_string()
400    };
401    Box::new(TextValue { value })
402}
403
404pub fn text_charindex(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
405    let substr = inputs[0].as_text().unwrap();
406    let input = inputs[1].as_text().unwrap();
407
408    let value = if let Some(index) = input.to_lowercase().find(&substr.to_lowercase()) {
409        index as i64 + 1
410    } else {
411        0
412    };
413
414    Box::new(IntValue { value })
415}
416
417pub fn text_replace(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
418    let text = inputs[0].as_text().unwrap();
419    let old_string = inputs[1].as_text().unwrap();
420    let new_string = inputs[2].as_text().unwrap();
421
422    let mut result = String::new();
423    let mut end = 0;
424    for (begin, matched_part) in text
425        .to_lowercase()
426        .match_indices(&old_string.to_lowercase())
427    {
428        result.push_str(text.get(end..begin).unwrap());
429        result.push_str(&new_string);
430        end = begin + matched_part.len();
431    }
432
433    result.push_str(text.get(end..text.len()).unwrap());
434    Box::new(TextValue { value: result })
435}
436
437pub fn text_substring(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
438    let text = inputs[0].as_text().unwrap();
439    // According to the specs, a string starts at position 1.
440    // but in Rust, the index of a string starts from 0
441    let start = inputs[1].as_int().unwrap() as usize - 1;
442    let length = inputs[2].as_int().unwrap();
443
444    if start > text.len() || length > text.len() as i64 {
445        return Box::new(TextValue { value: text });
446    }
447    if length < 0 {
448        return Box::new(TextValue {
449            value: "".to_string(),
450        });
451    }
452
453    // Convert it to Vec<Char> to be easy to substring with support of unicode
454    let chars: Vec<char> = text.chars().collect();
455    let slice = &chars[start..(start + length as usize)];
456    Box::new(TextValue {
457        value: slice.iter().collect(),
458    })
459}
460
461pub fn text_stuff(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
462    let text = inputs[0].as_text().unwrap();
463    let start = (inputs[1].as_int().unwrap() - 1) as usize;
464    let length = inputs[2].as_int().unwrap() as usize;
465    let new_string = inputs[3].as_text().unwrap();
466
467    if text.is_empty() {
468        return Box::new(TextValue { value: text });
469    }
470
471    if start > text.len() || length > text.len() {
472        return Box::new(TextValue { value: text });
473    }
474
475    let mut text = text.chars().collect::<Vec<_>>();
476    let new_string = new_string.chars().collect::<Vec<_>>();
477    text.splice(start..(start + length), new_string);
478    Box::new(TextValue {
479        value: text.into_iter().collect(),
480    })
481}
482
483pub fn text_right(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
484    let text = inputs[0].as_text().unwrap();
485    if text.is_empty() {
486        return Box::new(TextValue {
487            value: "".to_string(),
488        });
489    }
490
491    let number_of_chars = inputs[1].as_int().unwrap() as usize;
492    if number_of_chars > text.len() {
493        return Box::new(TextValue { value: text });
494    }
495
496    let text = text.as_str();
497    Box::new(TextValue {
498        value: text[text.len() - number_of_chars..text.len()].to_string(),
499    })
500}
501
502pub fn text_translate(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
503    let mut text = inputs[0].as_text().unwrap();
504    let characters = inputs[1].as_text().unwrap();
505    let translations = inputs[2].as_text().unwrap();
506
507    if translations.len() != characters.len() {
508        return Box::new(TextValue {
509            value: "".to_string(),
510        });
511    }
512
513    let translations = translations.chars().collect::<Vec<_>>();
514    for (idx, letter) in characters.char_indices() {
515        text = text.replace(letter, &char::to_string(&translations[idx]));
516    }
517
518    Box::new(TextValue { value: text })
519}
520
521pub fn text_unicode(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
522    let value = if let Some(c) = inputs[0].as_text().unwrap().chars().next() {
523        (c as u32).into()
524    } else {
525        0
526    };
527
528    Box::new(IntValue { value })
529}
530
531pub fn text_soundex(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
532    let text = inputs[0].as_text().unwrap();
533    if text.is_empty() {
534        return Box::new(TextValue {
535            value: "".to_string(),
536        });
537    }
538
539    let mut result = String::from(text.chars().next().unwrap());
540
541    for (idx, letter) in text.char_indices() {
542        if idx != 0 {
543            let letter = letter.to_ascii_uppercase();
544            if !matches!(letter, 'A' | 'E' | 'I' | 'O' | 'U' | 'H' | 'W' | 'Y') {
545                let int = match letter {
546                    'B' | 'F' | 'P' | 'V' => 1,
547                    'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => 2,
548                    'D' | 'T' => 3,
549                    'L' => 4,
550                    'M' | 'N' => 5,
551                    'R' => 6,
552                    _ => 0,
553                };
554                result.push_str(&int.to_string());
555
556                if result.len() == 4 {
557                    return Box::new(TextValue { value: result });
558                }
559            }
560        }
561    }
562
563    if result.len() < 4 {
564        let diff = 4 - result.len();
565        for _i in 0..diff {
566            result.push_str(&0.to_string());
567        }
568    }
569
570    Box::new(TextValue { value: result })
571}
572
573pub fn text_concat(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
574    let text: Vec<String> = inputs.iter().map(|v| v.to_string()).collect();
575    Box::new(TextValue {
576        value: text.concat(),
577    })
578}
579
580pub fn text_concat_ws(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
581    let separator = inputs[0].as_text().unwrap();
582    let text: Vec<String> = inputs.iter().skip(1).map(|v| v.to_string()).collect();
583    let value = text.join(&separator);
584    Box::new(TextValue { value })
585}
586
587pub fn text_strcmp(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
588    let value = match inputs[0].as_text().cmp(&inputs[1].as_text()) {
589        std::cmp::Ordering::Less => 1,
590        std::cmp::Ordering::Equal => 2,
591        std::cmp::Ordering::Greater => 0,
592    };
593    Box::new(IntValue { value })
594}
595
596pub fn text_quotename(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
597    let str = inputs[0].as_text().unwrap();
598    let quote = inputs
599        .get(1)
600        .map(|v| v.as_text().unwrap())
601        .map(|str| str.chars().collect())
602        .unwrap_or_else(|| vec!['[', ']']);
603
604    match quote.as_slice() {
605        [single] => Box::new(TextValue {
606            value: format!("{single}{str}{single}"),
607        }),
608        [start, end] => Box::new(TextValue {
609            value: format!("{start}{str}{end}"),
610        }),
611        _ => Box::new(NullValue),
612    }
613}
614
615pub fn text_str(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
616    let value = &inputs[0];
617    let length = if inputs.len() == 3 {
618        inputs[1].as_int().unwrap()
619    } else {
620        10
621    };
622
623    let decimals = if inputs.len() == 3 {
624        inputs[2].as_int().unwrap()
625    } else {
626        0
627    };
628
629    if value.data_type().is_int() {
630        let int_value = value.as_int().unwrap();
631        let number_string = format!("{:.dec$}", int_value, dec = decimals as usize);
632        if length > 0 {
633            if (length as usize) < number_string.len() {
634                return Box::new(TextValue {
635                    value: number_string[..length as usize].to_owned(),
636                });
637            } else {
638                return Box::new(TextValue {
639                    value: format!("{:<len$}", number_string, len = length as usize),
640                });
641            }
642        }
643
644        return Box::new(TextValue {
645            value: number_string.clone(),
646        });
647    }
648
649    let float_value = value.as_float().unwrap();
650    let number_string = format!("{:.dec$}", float_value, dec = decimals as usize);
651    if length > 0 {
652        if (length as usize) < number_string.len() {
653            return Box::new(TextValue {
654                value: number_string[..length as usize].to_owned(),
655            });
656        } else {
657            return Box::new(TextValue {
658                value: format!("{:<len$}", number_string, len = length as usize),
659            });
660        }
661    }
662
663    Box::new(TextValue {
664        value: number_string.clone(),
665    })
666}
667
668pub fn text_to_hex(inputs: &[Box<dyn Value>]) -> Box<dyn Value> {
669    let number = inputs[0].as_int().unwrap();
670    let value = format!("0x{}", number);
671    Box::new(TextValue { value })
672}