use super::config::Engine;
pub fn quote_identifier(name: &str, engine: Engine) -> String {
let bare = !name.is_empty()
&& !name.starts_with(|character: char| character.is_ascii_digit())
&& name.chars().all(|character| {
character.is_ascii_lowercase() || character.is_ascii_digit() || character == '_'
});
if bare {
return name.to_string();
}
match engine {
Engine::MySql => format!("`{}`", name.replace('`', "``")),
Engine::Postgres | Engine::Sqlite => format!("\"{}\"", name.replace('"', "\"\"")),
}
}
pub(crate) fn quote_literal(value: &str) -> String {
format!("'{}'", value.replace('\'', "''"))
}
pub(crate) fn quote_literal_for(engine: Engine, value: &str) -> String {
let escaped = match engine {
Engine::MySql => value.replace('\\', "\\\\").replace('\'', "''"),
Engine::Postgres | Engine::Sqlite => value.replace('\'', "''"),
};
format!("'{escaped}'")
}
pub(crate) fn placeholder(engine: Engine, index: usize) -> String {
match engine {
Engine::Postgres => format!("${index}"),
Engine::MySql | Engine::Sqlite => "?".to_string(),
}
}
pub(crate) fn typed_placeholder(engine: Engine, index: usize, type_name: &str) -> String {
let placeholder = placeholder(engine, index);
match engine {
Engine::MySql if type_name.eq_ignore_ascii_case("BIT") => {
format!("cast(conv({placeholder}, 2, 10) as unsigned)")
}
Engine::Postgres if !type_name.is_empty() => {
format!("cast({placeholder} as {})", cast_target(type_name))
}
_ => placeholder,
}
}
fn cast_target(type_name: &str) -> &str {
match type_name.to_ascii_uppercase().as_str() {
"CHAR" | "BPCHAR" => "text",
"BIT" | "VARBIT" => "varbit",
_ => type_name,
}
}
pub(crate) fn text_type(engine: Engine) -> &'static str {
match engine {
Engine::MySql => "char",
Engine::Postgres | Engine::Sqlite => "text",
}
}
const KEYWORD_LITERALS: &[&str] = &["NOW()", "CURRENT_TIMESTAMP", "CURRENT_DATE", "CURRENT_TIME"];
pub(crate) fn keyword_literal(value: &str) -> Option<&'static str> {
let trimmed = value.trim();
KEYWORD_LITERALS
.iter()
.find(|keyword| keyword.eq_ignore_ascii_case(trimmed))
.copied()
}