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