use crate::types::Client;
impl Client {
pub(crate) fn quote_char(&self) -> char {
match self {
Client::Mysql => '`',
Client::Sqlite | Client::Postgres => '"',
}
}
}
pub(crate) fn escape_identifier(ident: &str, client: &Client) -> String {
let quote = client.quote_char();
let trimmed = ident.trim();
if trimmed.is_empty() {
return String::new();
}
let mut out = String::with_capacity(trimmed.len() + 4);
for (i, part) in trimmed.split('.').enumerate() {
if i > 0 {
out.push('.');
}
let part = part.trim();
if part == "*" {
out.push('*');
continue;
}
out.push(quote);
for ch in part.chars() {
if ch == quote {
out.push(quote);
}
out.push(ch);
}
out.push(quote);
}
out
}
pub(crate) fn escape_identifier_list(idents: &[String], client: &Client) -> String {
idents
.iter()
.map(|ident| escape_identifier(ident, client))
.collect::<Vec<_>>()
.join(", ")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_identifier_mysql() {
assert_eq!(escape_identifier("name", &Client::Mysql), "`name`");
}
#[test]
fn qualified_identifier_mysql() {
assert_eq!(
escape_identifier("users.name", &Client::Mysql),
"`users`.`name`"
);
}
#[test]
fn wildcard_passthrough() {
assert_eq!(escape_identifier("*", &Client::Mysql), "*");
assert_eq!(escape_identifier("users.*", &Client::Mysql), "`users`.*");
}
#[test]
fn sqlite_uses_double_quotes() {
assert_eq!(escape_identifier("name", &Client::Sqlite), "\"name\"");
}
#[test]
fn injection_attempt_is_neutralized_mysql() {
assert_eq!(
escape_identifier("name` = 1; DROP TABLE users; -- ", &Client::Mysql),
"`name`` = 1; DROP TABLE users; --`"
);
}
#[test]
fn injection_attempt_is_neutralized_sqlite() {
assert_eq!(
escape_identifier("name\" OR \"1\"=\"1", &Client::Sqlite),
"\"name\"\" OR \"\"1\"\"=\"\"1\""
);
}
#[test]
fn empty_input_yields_empty() {
assert_eq!(escape_identifier(" ", &Client::Mysql), "");
}
}