use crate::SyntaxNode;
use crate::generated::keywords::RESERVED_KEYWORDS;
pub fn quote_column_alias(text: &str) -> String {
if needs_quoting(text) {
format!(r#""{}""#, text.replace('"', r#""""#))
} else {
text.to_string()
}
}
pub fn unquote_ident(node: &SyntaxNode) -> Option<String> {
let text = node.text().to_string();
if !text.starts_with('"') || !text.ends_with('"') {
return None;
}
let text = &text[1..text.len() - 1];
if is_reserved_word(text) {
return None;
}
if text.is_empty() {
return None;
}
let mut chars = text.chars();
match chars.next() {
Some(c) if c.is_lowercase() || c == '_' => {}
_ => return None,
}
for c in chars {
if c.is_lowercase() || c.is_ascii_digit() || c == '_' || c == '$' {
continue;
}
return None;
}
Some(text.to_string())
}
fn needs_quoting(text: &str) -> bool {
if text.is_empty() {
return true;
}
let mut chars = text.chars();
match chars.next() {
Some(c) if c.is_lowercase() || c == '_' => {}
_ => return true,
}
for c in chars {
if c.is_lowercase() || c.is_ascii_digit() || c == '_' || c == '$' {
continue;
}
return true;
}
false
}
pub fn is_reserved_word(text: &str) -> bool {
RESERVED_KEYWORDS
.binary_search(&text.to_lowercase().as_str())
.is_ok()
}
pub fn normalize_identifier(text: &str) -> String {
text.strip_prefix('"')
.and_then(|t| t.strip_suffix('"'))
.map(|x| x.replace(r#""""#, "\""))
.unwrap_or_else(|| text.to_ascii_lowercase())
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use super::*;
#[test]
fn quote_column_alias_handles_embedded_quotes() {
assert_snapshot!(quote_column_alias(r#"foo"bar"#), @r#""foo""bar""#);
}
#[test]
fn quote_column_alias_doesnt_quote_reserved_words() {
assert_snapshot!(quote_column_alias("case"), @"case");
assert_snapshot!(quote_column_alias("array"), @"array");
}
#[test]
fn quote_column_alias_doesnt_quote_simple_identifiers() {
assert_snapshot!(quote_column_alias("col_name"), @"col_name");
}
#[test]
fn quote_column_alias_handles_special_column_name() {
assert_snapshot!(quote_column_alias("?column?"), @r#""?column?""#);
}
}