pub fn escape_literal(input: &str) -> String {
escape_internal(input, false)
}
const KWS: [&str; 167] = [
"ALL",
"ANALYSE",
"ANALYZE",
"AND",
"ANY",
"ARRAY",
"AS",
"ASC",
"ASYMMETRIC",
"AUTHORIZATION",
"BETWEEN",
"BIGINT",
"BINARY",
"BIT",
"BOOLEAN",
"BOTH",
"CASE",
"CAST",
"CHAR",
"CHARACTER",
"CHECK",
"COALESCE",
"COLLATE",
"COLLATION",
"COLUMN",
"CONCURRENTLY",
"CONSTRAINT",
"CREATE",
"CROSS",
"CURRENT_CATALOG",
"CURRENT_DATE",
"CURRENT_ROLE",
"CURRENT_SCHEMA",
"CURRENT_TIME",
"CURRENT_TIMESTAMP",
"CURRENT_USER",
"DAY",
"DEC",
"DECIMAL",
"DEFAULT",
"DEFERRABLE",
"DESC",
"DISTINCT",
"DO",
"ELSE",
"END",
"EXCEPT",
"EXISTS",
"EXTRACT",
"FALSE",
"FETCH",
"FILTER",
"FLOAT",
"FOR",
"FOREIGN",
"FREEZE",
"FROM",
"FULL",
"GRANT",
"GREATEST",
"GROUP",
"GROUPING",
"HAVING",
"HOUR",
"ILIKE",
"IN",
"INITIALLY",
"INNER",
"INOUT",
"INT",
"INTEGER",
"INTERSECT",
"INTERVAL",
"INTO",
"IS",
"ISNULL",
"JOIN",
"JSON_ARRAY",
"JSON_ARRAYAGG",
"JSON_OBJECT",
"JSON_OBJECTAGG",
"LATERAL",
"LEADING",
"LEAST",
"LEFT",
"LIKE",
"LIMIT",
"LOCALTIME",
"LOCALTIMESTAMP",
"MINUTE",
"MONTH",
"NATIONAL",
"NATURAL",
"NCHAR",
"NONE",
"NORMALIZE",
"NOT",
"NOTNULL",
"NULL",
"NULLIF",
"NUMERIC",
"OFFSET",
"ON",
"ONLY",
"OR",
"ORDER",
"OUT",
"OUTER",
"OVER",
"OVERLAPS",
"OVERLAY",
"PLACING",
"POSITION",
"PRECISION",
"PRIMARY",
"REAL",
"REFERENCES",
"RETURNING",
"RIGHT",
"ROW",
"SECOND",
"SELECT",
"SESSION_USER",
"SETOF",
"SIMILAR",
"SMALLINT",
"SOME",
"SUBSTRING",
"SYMMETRIC",
"SYSTEM_USER",
"TABLE",
"TABLESAMPLE",
"THEN",
"TIME",
"TIMESTAMP",
"TO",
"TRAILING",
"TREAT",
"TRIM",
"TRUE",
"UNION",
"UNIQUE",
"USER",
"USING",
"VALUES",
"VARCHAR",
"VARIADIC",
"VARYING",
"VERBOSE",
"WHEN",
"WHERE",
"WINDOW",
"WITH",
"WITHIN",
"WITHOUT",
"XMLATTRIBUTES",
"XMLCONCAT",
"XMLELEMENT",
"XMLEXISTS",
"XMLFOREST",
"XMLNAMESPACES",
"XMLPARSE",
"XMLPI",
"XMLROOT",
"XMLSERIALIZE",
"XMLTABLE",
"YEAR",
];
pub fn should_escape(input: &str) -> bool {
for (i, l) in input.chars().enumerate() {
if !l.is_ascii() {
return true;
}
match l {
'a'..='z' | '_' => {}
'0'..='9' | '$' if i != 0 => {}
_ => {
return true;
}
}
}
if KWS.binary_search(&input.to_uppercase().as_str()).is_ok() {
return true;
}
false
}
pub fn escape_identifier(input: &str) -> String {
if !should_escape(input) {
return input.to_owned();
}
escape_internal(input, true)
}
fn escape_internal(input: &str, as_ident: bool) -> String {
let mut num_backslashes = 0;
let mut num_quotes = 0;
let quote_char = if as_ident { '"' } else { '\'' };
for ch in input.chars() {
if ch == quote_char {
num_quotes += 1;
} else if ch == '\\' {
num_backslashes += 1;
}
}
let mut result_size = input.len() + num_quotes + 3; if !as_ident && num_backslashes > 0 {
result_size += num_backslashes + 2;
}
let mut output = String::with_capacity(result_size);
if !as_ident && num_backslashes > 0 {
output.push(' ');
output.push('E');
}
output.push(quote_char);
if num_quotes == 0 && (num_backslashes == 0 || as_ident) {
output.push_str(input);
} else {
for ch in input.chars() {
if ch == quote_char || (!as_ident && ch == '\\') {
output.push(ch);
}
output.push(ch);
}
}
output.push(quote_char);
output
}