1use crate::error::SqlError;
12
13pub const RESERVED_KEYWORDS: &[&str] = &[
18 "GRAPH", "MATCH", "OPTIONAL", "UPSERT", "UNDROP", "PURGE", "CASCADE", "SEARCH", "CRDT", ];
28
29fn reason_for(upper: &str) -> &'static str {
30 match upper {
31 "GRAPH" | "MATCH" | "OPTIONAL" => "graph dispatch keyword",
32 "UPSERT" => "preprocess rewrite keyword",
33 "UNDROP" => "DDL dispatch keyword",
34 "PURGE" | "CASCADE" => "DROP modifier keyword",
35 "SEARCH" | "CRDT" => "DSL dispatch keyword",
36 _ => "reserved by NodeDB",
37 }
38}
39
40pub fn is_reserved(name: &str) -> bool {
43 let upper = name.to_uppercase();
44 RESERVED_KEYWORDS.contains(&upper.as_str())
45}
46
47pub fn check_identifier(raw_name: &str) -> Result<String, SqlError> {
58 if raw_name.starts_with('"') && raw_name.ends_with('"') && raw_name.len() >= 2 {
59 return Ok(raw_name[1..raw_name.len() - 1].to_string());
61 }
62
63 let upper = raw_name.to_uppercase();
64 if RESERVED_KEYWORDS.contains(&upper.as_str()) {
65 let reason = reason_for(&upper);
66 return Err(SqlError::ReservedIdentifier {
67 name: raw_name.to_string(),
68 reason,
69 });
70 }
71
72 Ok(raw_name.to_lowercase())
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use crate::error::SqlError;
79
80 #[test]
83 fn reserved_upper() {
84 assert!(is_reserved("MATCH"));
85 }
86
87 #[test]
88 fn reserved_lower() {
89 assert!(is_reserved("match"));
90 }
91
92 #[test]
93 fn not_reserved() {
94 assert!(!is_reserved("id"));
95 }
96
97 #[test]
100 fn bare_reserved_is_err() {
101 let err = check_identifier("match").unwrap_err();
102 assert!(matches!(err, SqlError::ReservedIdentifier { .. }));
103 }
104
105 #[test]
106 fn quoted_lower_is_ok() {
107 assert_eq!(check_identifier("\"match\"").unwrap(), "match");
108 }
109
110 #[test]
111 fn quoted_upper_is_ok() {
112 assert_eq!(check_identifier("\"MATCH\"").unwrap(), "MATCH");
113 }
114
115 #[test]
116 fn clean_identifier_is_ok() {
117 assert_eq!(check_identifier("id").unwrap(), "id");
118 }
119
120 #[test]
123 fn graph_reserved() {
124 assert!(check_identifier("graph").is_err());
125 assert_eq!(check_identifier("\"graph\"").unwrap(), "graph");
126 }
127
128 #[test]
129 fn match_reserved() {
130 assert!(check_identifier("match").is_err());
131 assert_eq!(check_identifier("\"match\"").unwrap(), "match");
132 }
133
134 #[test]
135 fn optional_reserved() {
136 assert!(check_identifier("optional").is_err());
137 assert_eq!(check_identifier("\"optional\"").unwrap(), "optional");
138 }
139
140 #[test]
141 fn upsert_reserved() {
142 assert!(check_identifier("upsert").is_err());
143 assert_eq!(check_identifier("\"upsert\"").unwrap(), "upsert");
144 }
145
146 #[test]
147 fn undrop_reserved() {
148 assert!(check_identifier("undrop").is_err());
149 assert_eq!(check_identifier("\"undrop\"").unwrap(), "undrop");
150 }
151
152 #[test]
153 fn purge_reserved() {
154 assert!(check_identifier("purge").is_err());
155 assert_eq!(check_identifier("\"purge\"").unwrap(), "purge");
156 }
157
158 #[test]
159 fn cascade_reserved() {
160 assert!(check_identifier("cascade").is_err());
161 assert_eq!(check_identifier("\"cascade\"").unwrap(), "cascade");
162 }
163
164 #[test]
165 fn search_reserved() {
166 assert!(check_identifier("search").is_err());
167 assert_eq!(check_identifier("\"search\"").unwrap(), "search");
168 }
169
170 #[test]
171 fn crdt_reserved() {
172 assert!(check_identifier("crdt").is_err());
173 assert_eq!(check_identifier("\"crdt\"").unwrap(), "crdt");
174 }
175}