1use crate::SyntaxNode;
2use crate::generated::keywords::RESERVED_KEYWORDS;
3
4pub fn quote_column_alias(text: &str) -> String {
5 if needs_quoting(text) {
6 format!(r#""{}""#, text.replace('"', r#""""#))
7 } else {
8 text.to_string()
9 }
10}
11
12pub fn unquote_ident(node: &SyntaxNode) -> Option<String> {
13 let text = node.text().to_string();
14
15 if !text.starts_with('"') || !text.ends_with('"') {
16 return None;
17 }
18
19 let text = &text[1..text.len() - 1];
20
21 if is_reserved_word(text) {
22 return None;
23 }
24
25 if text.is_empty() {
26 return None;
27 }
28
29 let mut chars = text.chars();
30
31 match chars.next() {
33 Some(c) if c.is_lowercase() || c == '_' => {}
34 _ => return None,
35 }
36
37 for c in chars {
38 if c.is_lowercase() || c.is_ascii_digit() || c == '_' || c == '$' {
39 continue;
40 }
41 return None;
42 }
43
44 Some(text.to_string())
45}
46
47pub fn needs_quoting(text: &str) -> bool {
48 if text.is_empty() {
49 return true;
50 }
51
52 let mut chars = text.chars();
57
58 match chars.next() {
59 Some(c) if c.is_lowercase() || c == '_' => {}
60 _ => return true,
61 }
62
63 for c in chars {
64 if c.is_lowercase() || c.is_ascii_digit() || c == '_' || c == '$' {
65 continue;
66 }
67 return true;
68 }
69
70 false
71}
72
73pub fn is_reserved_word(text: &str) -> bool {
74 RESERVED_KEYWORDS
75 .binary_search(&text.to_ascii_lowercase().as_str())
76 .is_ok()
77}
78
79#[cfg(test)]
80mod tests {
81 use insta::assert_snapshot;
82
83 use super::*;
84
85 #[test]
86 fn quote_column_alias_handles_embedded_quotes() {
87 assert_snapshot!(quote_column_alias(r#"foo"bar"#), @r#""foo""bar""#);
88 }
89
90 #[test]
91 fn quote_column_alias_doesnt_quote_reserved_words() {
92 assert_snapshot!(quote_column_alias("case"), @"case");
94 assert_snapshot!(quote_column_alias("array"), @"array");
95 }
96
97 #[test]
98 fn quote_column_alias_doesnt_quote_simple_identifiers() {
99 assert_snapshot!(quote_column_alias("col_name"), @"col_name");
100 }
101
102 #[test]
103 fn quote_column_alias_handles_special_column_name() {
104 assert_snapshot!(quote_column_alias("?column?"), @r#""?column?""#);
105 }
106}