lutra_sql/string.rs
1use core::fmt;
2
3pub fn escape(string: &str, quote: char) -> EscapeQuotedString<'_> {
4 EscapeQuotedString { string, quote }
5}
6
7pub struct EscapeQuotedString<'a> {
8 string: &'a str,
9 quote: char,
10}
11
12impl fmt::Display for EscapeQuotedString<'_> {
13 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
14 // EscapeQuotedString doesn't know which mode of escape was
15 // chosen by the user. So this code must to correctly display
16 // strings without knowing if the strings are already escaped
17 // or not.
18 //
19 // If the quote symbol in the string is repeated twice, OR, if
20 // the quote symbol is after backslash, display all the chars
21 // without any escape. However, if the quote symbol is used
22 // just between usual chars, `fmt()` should display it twice."
23 //
24 // The following table has examples
25 //
26 // | original query | mode | AST Node | serialized |
27 // | ------------- | --------- | -------------------------------------------------- | ------------ |
28 // | `"A""B""A"` | no-escape | `DoubleQuotedString(String::from("A\"\"B\"\"A"))` | `"A""B""A"` |
29 // | `"A""B""A"` | default | `DoubleQuotedString(String::from("A\"B\"A"))` | `"A""B""A"` |
30 // | `"A\"B\"A"` | no-escape | `DoubleQuotedString(String::from("A\\\"B\\\"A"))` | `"A\"B\"A"` |
31 // | `"A\"B\"A"` | default | `DoubleQuotedString(String::from("A\"B\"A"))` | `"A""B""A"` |
32 let quote = self.quote;
33 let mut previous_char = char::default();
34 let mut start_idx = 0;
35 let mut peekable_chars = self.string.char_indices().peekable();
36 while let Some(&(idx, ch)) = peekable_chars.peek() {
37 match ch {
38 char if char == quote => {
39 if previous_char == '\\' {
40 // the quote is already escaped with a backslash, skip
41 peekable_chars.next();
42 continue;
43 }
44 peekable_chars.next();
45 match peekable_chars.peek() {
46 Some((_, c)) if *c == quote => {
47 // the quote is already escaped with another quote, skip
48 peekable_chars.next();
49 }
50 _ => {
51 // The quote is not escaped.
52 // Including idx in the range, so the quote at idx will be printed twice:
53 // in this call to write_str() and in the next one.
54 f.write_str(&self.string[start_idx..=idx])?;
55 start_idx = idx;
56 }
57 }
58 }
59 _ => {
60 peekable_chars.next();
61 }
62 }
63 previous_char = ch;
64 }
65 f.write_str(&self.string[start_idx..])?;
66 Ok(())
67 }
68}