pub const RESERVED_WORDS: &[&str] = &[
"order",
"group",
"user",
"table",
"select",
"from",
"where",
"join",
"left",
"right",
"inner",
"outer",
"on",
"and",
"or",
"not",
"null",
"true",
"false",
"limit",
"offset",
"as",
"in",
"is",
"like",
"between",
"having",
"union",
"all",
"distinct",
"case",
"when",
"then",
"else",
"end",
"create",
"alter",
"drop",
"insert",
"update",
"delete",
"index",
"key",
"primary",
"foreign",
"references",
"default",
"constraint",
"check",
];
pub fn escape_identifier(name: &str) -> String {
if name.contains('.') {
return name
.split('.')
.map(escape_single_identifier)
.collect::<Vec<_>>()
.join(".");
}
escape_single_identifier(name)
}
fn escape_single_identifier(name: &str) -> String {
let name = &name.replace('\0', "");
let lower = name.to_lowercase();
let needs_escaping = RESERVED_WORDS.contains(&lower.as_str())
|| name.chars().any(|c| !c.is_alphanumeric() && c != '_')
|| name.chars().next().map(|c| c.is_numeric()).unwrap_or(false);
if needs_escaping {
format!("\"{}\"", name.replace('"', "\"\""))
} else {
name.to_string()
}
}
pub trait SqlGenerator {
fn quote_identifier(&self, name: &str) -> String;
fn placeholder(&self, index: usize) -> String;
fn fuzzy_operator(&self) -> &str;
fn bool_literal(&self, val: bool) -> String;
fn string_concat(&self, parts: &[&str]) -> String;
fn limit_offset(&self, limit: Option<usize>, offset: Option<usize>) -> String;
fn json_access(&self, col: &str, path: &[&str]) -> String {
let mut parts = vec![self.quote_identifier(col)];
for key in path {
parts.push(self.quote_identifier(key));
}
parts.join(".")
}
fn json_contains(&self, col: &str, value: &str) -> String {
format!("{} @> {}", col, value)
}
fn json_key_exists(&self, col: &str, key: &str) -> String {
format!("{} ? {}", col, key)
}
fn json_exists(&self, col: &str, path: &str) -> String {
format!("JSON_EXISTS({}, '{}')", col, path)
}
fn json_query(&self, col: &str, path: &str) -> String {
format!("JSON_QUERY({}, '{}')", col, path)
}
fn json_value(&self, col: &str, path: &str) -> String {
format!("JSON_VALUE({}, '{}')", col, path)
}
fn in_array(&self, col: &str, value: &str) -> String {
format!("{} = ANY({})", col, value)
}
fn not_in_array(&self, col: &str, value: &str) -> String {
format!("{} != ALL({})", col, value)
}
}