1#[must_use]
7pub fn quote_ident(value: &str, quote: char) -> String {
8 let mut out = String::with_capacity(value.len() + 2);
9 out.push(quote);
10 for ch in value.chars() {
11 if ch == quote {
12 out.push(quote);
13 }
14 out.push(ch);
15 }
16 out.push(quote);
17 out
18}
19
20#[cfg(test)]
21mod tests {
22 use super::*;
23
24 #[test]
25 fn quote_ident_basic_double_quote() {
26 assert_eq!(quote_ident("users", '"'), "\"users\"");
27 assert_eq!(quote_ident("eu-docker", '"'), "\"eu-docker\"");
28 assert_eq!(quote_ident("test\"db", '"'), "\"test\"\"db\"");
29 }
30
31 #[test]
32 fn quote_ident_basic_backtick() {
33 assert_eq!(quote_ident("users", '`'), "`users`");
34 assert_eq!(quote_ident("eu-docker", '`'), "`eu-docker`");
35 assert_eq!(quote_ident("test`db", '`'), "`test``db`");
36 }
37
38 #[test]
39 fn quote_ident_only_quote_chars() {
40 assert_eq!(quote_ident("\"\"", '"'), "\"\"\"\"\"\"");
42 assert_eq!(quote_ident("``", '`'), "``````");
44 }
45
46 #[test]
47 fn quote_ident_quote_at_start_and_end() {
48 assert_eq!(quote_ident("\"x\"", '"'), "\"\"\"x\"\"\"");
49 assert_eq!(quote_ident("`x`", '`'), "```x```");
50 }
51
52 #[test]
53 fn quote_ident_foreign_quote_passes_through() {
54 assert_eq!(quote_ident("test`db", '"'), "\"test`db\"");
56 assert_eq!(quote_ident("test\"db", '`'), "`test\"db`");
57 }
58
59 #[test]
60 fn quote_ident_empty_string() {
61 assert_eq!(quote_ident("", '"'), "\"\"");
62 assert_eq!(quote_ident("", '`'), "``");
63 }
64
65 #[test]
66 fn quote_ident_long_string_completes() {
67 let long_name: String = "a".repeat(10_000);
68 let quoted = quote_ident(&long_name, '"');
69 assert_eq!(quoted.len(), 10_002);
70 }
71
72 #[test]
73 fn quote_ident_unicode_untouched() {
74 assert_eq!(quote_ident("数据", '"'), "\"数据\"");
75 assert_eq!(quote_ident("café", '`'), "`café`");
76 }
77
78 #[test]
79 fn quote_ident_dot_kept_inside_quotes() {
80 assert_eq!(quote_ident("schema.table", '"'), "\"schema.table\"");
81 assert_eq!(quote_ident("schema.table", '`'), "`schema.table`");
82 }
83}