Skip to main content

dbmcp_sql/
sanitize.rs

1//! Identifier-safety primitives shared across SQL backends.
2
3/// Quotes `value` as a SQL identifier using `quote`.
4///
5/// Wraps the value in `quote` and doubles every internal occurrence of `quote`.
6#[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        // Input: "" (2 double-quotes). Each doubled → 4, plus wrapping → 6.
41        assert_eq!(quote_ident("\"\"", '"'), "\"\"\"\"\"\"");
42        // Input: `` (2 backticks). Each doubled → 4, plus wrapping → 6.
43        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        // Backtick is foreign to ANSI quoting; double-quote is foreign to MySQL.
55        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}