database_mcp_sql/
identifier.rs1use database_mcp_server::AppError;
4
5#[must_use]
9pub fn quote_identifier(name: &str, quote_char: char) -> String {
10 let doubled: String = std::iter::repeat_n(quote_char, 2).collect();
11 let escaped = name.replace(quote_char, &doubled);
12 format!("{quote_char}{escaped}{quote_char}")
13}
14
15#[must_use]
19pub fn quote_string(value: &str) -> String {
20 let escaped = value.replace('\'', "''");
21 format!("'{escaped}'")
22}
23
24pub fn validate_identifier(name: &str) -> Result<(), AppError> {
31 if name.is_empty() || name.chars().all(char::is_whitespace) {
32 return Err(AppError::InvalidIdentifier(name.to_string()));
33 }
34 if name.chars().any(char::is_control) {
35 return Err(AppError::InvalidIdentifier(name.to_string()));
36 }
37 Ok(())
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
45 fn accepts_standard_names() {
46 assert!(validate_identifier("users").is_ok());
47 assert!(validate_identifier("my_table").is_ok());
48 assert!(validate_identifier("DB_123").is_ok());
49 }
50
51 #[test]
52 fn accepts_hyphenated_names() {
53 assert!(validate_identifier("eu-docker").is_ok());
54 assert!(validate_identifier("access-logs").is_ok());
55 }
56
57 #[test]
58 fn accepts_special_chars() {
59 assert!(validate_identifier("my.db").is_ok());
60 assert!(validate_identifier("123db").is_ok());
61 assert!(validate_identifier("café").is_ok());
62 assert!(validate_identifier("a b").is_ok());
63 }
64
65 #[test]
66 fn rejects_empty() {
67 assert!(validate_identifier("").is_err());
68 }
69
70 #[test]
71 fn rejects_whitespace_only() {
72 assert!(validate_identifier(" ").is_err());
73 assert!(validate_identifier("\t").is_err());
74 }
75
76 #[test]
77 fn rejects_control_chars() {
78 assert!(validate_identifier("test\x00db").is_err());
79 assert!(validate_identifier("test\ndb").is_err());
80 assert!(validate_identifier("test\x1Fdb").is_err());
81 }
82
83 #[test]
84 fn quote_with_double_quotes() {
85 assert_eq!(quote_identifier("users", '"'), "\"users\"");
86 assert_eq!(quote_identifier("eu-docker", '"'), "\"eu-docker\"");
87 assert_eq!(quote_identifier("test\"db", '"'), "\"test\"\"db\"");
88 }
89
90 #[test]
91 fn quote_with_backticks() {
92 assert_eq!(quote_identifier("users", '`'), "`users`");
93 assert_eq!(quote_identifier("test`db", '`'), "`test``db`");
94 }
95
96 #[test]
97 fn quote_string_normal() {
98 assert_eq!(quote_string("my_db"), "'my_db'");
99 }
100
101 #[test]
102 fn quote_string_empty() {
103 assert_eq!(quote_string(""), "''");
104 }
105
106 #[test]
107 fn quote_string_with_single_quotes() {
108 assert_eq!(quote_string("it's"), "'it''s'");
109 assert_eq!(quote_string("a'b'c"), "'a''b''c'");
110 }
111}