1use crate::{Dialect, SyntaxKind};
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum KeywordDialect {
23 Shared,
25 SnowflakeOnly,
28 #[allow(dead_code)]
31 DatabricksOnly,
32}
33
34impl KeywordDialect {
35 #[inline]
37 #[must_use]
38 pub fn reserved_in(self, dialect: Dialect) -> bool {
39 match self {
40 KeywordDialect::Shared => true,
41 KeywordDialect::SnowflakeOnly => matches!(dialect, Dialect::Snowflake),
42 KeywordDialect::DatabricksOnly => matches!(dialect, Dialect::Databricks),
43 }
44 }
45}
46
47const KEYWORDS: &[(&str, SyntaxKind, KeywordDialect)] = {
54 use KeywordDialect::{Shared, SnowflakeOnly};
55 use SyntaxKind::*;
56 &[
57 ("select", SELECT_KW, Shared),
58 ("from", FROM_KW, Shared),
59 ("where", WHERE_KW, Shared),
60 ("group", GROUP_KW, Shared),
61 ("by", BY_KW, Shared),
62 ("having", HAVING_KW, Shared),
63 ("order", ORDER_KW, Shared),
64 ("limit", LIMIT_KW, Shared),
65 ("offset", OFFSET_KW, Shared),
66 ("fetch", FETCH_KW, Shared),
67 ("top", TOP_KW, SnowflakeOnly),
69 ("as", AS_KW, Shared),
70 ("and", AND_KW, Shared),
71 ("or", OR_KW, Shared),
72 ("not", NOT_KW, Shared),
73 ("null", NULL_KW, Shared),
74 ("is", IS_KW, Shared),
75 ("in", IN_KW, Shared),
76 ("like", LIKE_KW, Shared),
77 ("ilike", ILIKE_KW, SnowflakeOnly),
79 ("rlike", RLIKE_KW, SnowflakeOnly),
80 ("regexp", REGEXP_KW, SnowflakeOnly),
81 ("between", BETWEEN_KW, Shared),
82 ("case", CASE_KW, Shared),
83 ("when", WHEN_KW, Shared),
84 ("then", THEN_KW, Shared),
85 ("else", ELSE_KW, Shared),
86 ("end", END_KW, Shared),
87 ("join", JOIN_KW, Shared),
88 ("inner", INNER_KW, Shared),
89 ("left", LEFT_KW, Shared),
90 ("right", RIGHT_KW, Shared),
91 ("full", FULL_KW, Shared),
92 ("outer", OUTER_KW, Shared),
93 ("cross", CROSS_KW, Shared),
94 ("lateral", LATERAL_KW, Shared),
95 ("natural", NATURAL_KW, Shared),
96 ("on", ON_KW, Shared),
97 ("using", USING_KW, Shared),
98 ("with", WITH_KW, Shared),
99 ("recursive", RECURSIVE_KW, Shared),
100 ("union", UNION_KW, Shared),
101 ("all", ALL_KW, Shared),
102 ("any", ANY_KW, Shared),
103 ("except", EXCEPT_KW, Shared),
104 ("intersect", INTERSECT_KW, Shared),
105 ("minus", MINUS_KW, Shared),
106 ("distinct", DISTINCT_KW, Shared),
107 ("qualify", QUALIFY_KW, Shared),
113 ("over", OVER_KW, Shared),
114 ("partition", PARTITION_KW, Shared),
115 ("window", WINDOW_KW, Shared),
116 ("rows", ROWS_KW, Shared),
117 ("range", RANGE_KW, Shared),
118 ("unbounded", UNBOUNDED_KW, Shared),
119 ("preceding", PRECEDING_KW, Shared),
120 ("following", FOLLOWING_KW, Shared),
121 ("current", CURRENT_KW, Shared),
122 ("row", ROW_KW, Shared),
123 ("asc", ASC_KW, Shared),
124 ("desc", DESC_KW, Shared),
125 ("nulls", NULLS_KW, Shared),
126 ("first", FIRST_KW, Shared),
127 ("last", LAST_KW, Shared),
128 ("true", TRUE_KW, Shared),
129 ("false", FALSE_KW, Shared),
130 ("cast", CAST_KW, Shared),
131 ("try_cast", TRY_CAST_KW, SnowflakeOnly),
133 ("exists", EXISTS_KW, Shared),
134 ("values", VALUES_KW, Shared),
135 ("pivot", PIVOT_KW, Shared),
136 ("unpivot", UNPIVOT_KW, Shared),
137 ("sample", SAMPLE_KW, SnowflakeOnly),
139 ("tablesample", TABLESAMPLE_KW, Shared),
140 ("create", CREATE_KW, Shared),
141 ("replace", REPLACE_KW, Shared),
142 ("if", IF_KW, Shared),
143 ("table", TABLE_KW, Shared),
144 ("view", VIEW_KW, Shared),
145 ("temporary", TEMPORARY_KW, Shared),
146 ("temp", TEMP_KW, Shared),
147 ("transient", TRANSIENT_KW, SnowflakeOnly),
149 ("volatile", VOLATILE_KW, SnowflakeOnly),
150 ("secure", SECURE_KW, SnowflakeOnly),
151 ("insert", INSERT_KW, Shared),
152 ("into", INTO_KW, Shared),
153 ("update", UPDATE_KW, Shared),
154 ("delete", DELETE_KW, Shared),
155 ("merge", MERGE_KW, Shared),
156 ("set", SET_KW, Shared),
157 ("flatten", FLATTEN_KW, SnowflakeOnly),
159 ("connect", CONNECT_KW, SnowflakeOnly),
161 ("start", START_KW, Shared),
162 ("prior", PRIOR_KW, SnowflakeOnly),
163 ("language", LANGUAGE_KW, Shared),
164 ("javascript", JAVASCRIPT_KW, SnowflakeOnly),
167 ("python", PYTHON_KW, Shared),
168 ("java", JAVA_KW, Shared),
169 ("scala", SCALA_KW, SnowflakeOnly),
170 ("sql", SQL_KW, Shared),
171 ("begin", BEGIN_KW, Shared),
172 ("declare", DECLARE_KW, Shared),
173 ("let", LET_KW, Shared),
174 ("return", RETURN_KW, Shared),
175 ("call", CALL_KW, Shared),
176 ("procedure", PROCEDURE_KW, Shared),
177 ("function", FUNCTION_KW, Shared),
178 ("returns", RETURNS_KW, Shared),
179 ("task", TASK_KW, SnowflakeOnly),
181 ("warehouse", WAREHOUSE_KW, SnowflakeOnly),
182 ("schedule", SCHEDULE_KW, SnowflakeOnly),
183 ("after", AFTER_KW, Shared),
184 ("copy", COPY_KW, SnowflakeOnly),
185 ("grants", GRANTS_KW, SnowflakeOnly),
186 ("handler", HANDLER_KW, SnowflakeOnly),
187 ("packages", PACKAGES_KW, SnowflakeOnly),
188 ("imports", IMPORTS_KW, SnowflakeOnly),
189 ("runtime_version", RUNTIME_VERSION_KW, SnowflakeOnly),
190 ("execute", EXECUTE_KW, Shared),
191 ("owner", OWNER_KW, SnowflakeOnly),
192 ("caller", CALLER_KW, SnowflakeOnly),
193 ("strict", STRICT_KW, SnowflakeOnly),
194 ("called", CALLED_KW, SnowflakeOnly),
195 ("input", INPUT_KW, Shared),
196 ("output", OUTPUT_KW, Shared),
197 ("out", OUT_KW, Shared),
198 ("matched", MATCHED_KW, Shared),
199 ("drop", DROP_KW, Shared),
200 ("alter", ALTER_KW, Shared),
201 ("within", WITHIN_KW, Shared),
202 ("for", FOR_KW, Shared),
203 ("immediate", IMMEDIATE_KW, SnowflakeOnly),
204 ("overwrite", OVERWRITE_KW, Shared),
205 ("grant", GRANT_KW, Shared),
206 ("revoke", REVOKE_KW, Shared),
207 ("use", USE_KW, Shared),
208 ("show", SHOW_KW, Shared),
209 ("describe", DESCRIBE_KW, Shared),
210 ("truncate", TRUNCATE_KW, Shared),
211 ("commit", COMMIT_KW, Shared),
212 ("rollback", ROLLBACK_KW, Shared),
213 ("undrop", UNDROP_KW, SnowflakeOnly),
215 ("elseif", ELSEIF_KW, SnowflakeOnly),
216 ("while", WHILE_KW, Shared),
217 ("loop", LOOP_KW, Shared),
218 ("repeat", REPEAT_KW, Shared),
219 ("until", UNTIL_KW, Shared),
220 ("do", DO_KW, Shared),
221 ("exception", EXCEPTION_KW, SnowflakeOnly),
222 ("cursor", CURSOR_KW, SnowflakeOnly),
223 ("resultset", RESULTSET_KW, SnowflakeOnly),
224 ]
225};
226
227const MAX_KEYWORD_LEN: usize = 16;
228
229#[inline]
234fn lower_for_lookup(ident: &str, buf: &mut [u8; MAX_KEYWORD_LEN]) -> Option<usize> {
235 let bytes = ident.as_bytes();
236 if bytes.is_empty() || bytes.len() > MAX_KEYWORD_LEN {
237 return None;
238 }
239 for (slot, &b) in buf.iter_mut().zip(bytes) {
240 *slot = b.to_ascii_lowercase();
241 }
242 Some(bytes.len())
243}
244
245#[inline]
247fn lookup(ident: &str) -> Option<(SyntaxKind, KeywordDialect)> {
248 let mut buf = [0u8; MAX_KEYWORD_LEN];
249 let len = lower_for_lookup(ident, &mut buf)?;
250 let lower = std::str::from_utf8(&buf[..len]).ok()?;
251 KEYWORDS
252 .iter()
253 .find(|(text, _, _)| *text == lower)
254 .map(|(_, kind, dialect)| (*kind, *dialect))
255}
256
257#[must_use]
264pub fn keyword_kind(ident: &str) -> Option<SyntaxKind> {
265 keyword_kind_for(ident, Dialect::Snowflake)
266}
267
268#[must_use]
275pub fn keyword_kind_for(ident: &str, dialect: Dialect) -> Option<SyntaxKind> {
276 let (kind, kw_dialect) = lookup(ident)?;
277 kw_dialect.reserved_in(dialect).then_some(kind)
278}
279
280#[cfg(test)]
281mod tests {
282 use super::{keyword_kind, keyword_kind_for, KeywordDialect, KEYWORDS};
283 use crate::{Dialect, SyntaxKind};
284
285 #[test]
286 fn keyword_lookup_is_case_insensitive() {
287 assert_eq!(keyword_kind("select"), Some(SyntaxKind::SELECT_KW));
288 assert_eq!(keyword_kind("SeLeCt"), Some(SyntaxKind::SELECT_KW));
289 assert_eq!(keyword_kind("QUALIFY"), Some(SyntaxKind::QUALIFY_KW));
290 assert_eq!(keyword_kind("javascript"), Some(SyntaxKind::JAVASCRIPT_KW));
291 assert_eq!(keyword_kind("try_cast"), Some(SyntaxKind::TRY_CAST_KW));
292 assert_eq!(keyword_kind("TASK"), Some(SyntaxKind::TASK_KW));
293 assert_eq!(
294 keyword_kind("runtime_version"),
295 Some(SyntaxKind::RUNTIME_VERSION_KW)
296 );
297 assert_eq!(keyword_kind("definitely_not_a_keyword"), None);
298 }
299
300 #[test]
301 fn every_keyword_variant_is_mapped() {
302 let range_count = SyntaxKind::__KW_END as u16 - SyntaxKind::__KW_START as u16 - 1;
304 assert_eq!(
305 KEYWORDS.len() as u16,
306 range_count,
307 "KEYWORDS table is out of sync with the SyntaxKind keyword block"
308 );
309 let mut seen = std::collections::HashSet::new();
310 for (text, kind, _) in KEYWORDS {
311 assert_eq!(
312 keyword_kind(text),
313 Some(*kind),
314 "keyword_kind({text:?}) is wrong"
315 );
316 assert_eq!(
317 keyword_kind(&text.to_uppercase()),
318 Some(*kind),
319 "keyword_kind is not case-insensitive for {text:?}"
320 );
321 assert!(
322 kind.is_keyword(),
323 "{kind:?} should be inside the keyword range"
324 );
325 assert!(seen.insert(*kind), "duplicate keyword kind for {text:?}");
326 assert!(
327 text.bytes().all(|b| !b.is_ascii_uppercase()),
328 "KEYWORDS text must be lowercase: {text:?}"
329 );
330 }
331 }
332
333 #[test]
334 fn every_keyword_has_a_dialect_classification() {
335 for (text, kind, dialect) in KEYWORDS {
338 assert!(
339 matches!(
340 dialect,
341 KeywordDialect::Shared
342 | KeywordDialect::SnowflakeOnly
343 | KeywordDialect::DatabricksOnly
344 ),
345 "{text:?} ({kind:?}) has no dialect classification"
346 );
347 }
348 }
349
350 #[test]
351 fn snowflake_classification_is_byte_identical_to_legacy_keyword_kind() {
352 for (text, kind, _) in KEYWORDS {
355 assert_eq!(
356 keyword_kind_for(text, Dialect::Snowflake),
357 Some(*kind),
358 "Snowflake reservation changed for {text:?}"
359 );
360 assert_eq!(
361 keyword_kind(text),
362 keyword_kind_for(text, Dialect::Snowflake)
363 );
364 }
365 }
366
367 #[test]
368 fn shared_keywords_are_reserved_in_every_dialect() {
369 for word in ["select", "from", "where", "join", "group", "order", "case"] {
371 assert!(
372 keyword_kind_for(word, Dialect::Databricks).is_some(),
373 "{word}"
374 );
375 assert!(
376 keyword_kind_for(word, Dialect::Snowflake).is_some(),
377 "{word}"
378 );
379 }
380 }
381
382 #[test]
383 fn snowflake_only_keywords_are_identifiers_under_databricks() {
384 for word in [
386 "task",
387 "flatten",
388 "warehouse",
389 "schedule",
390 "transient",
391 "volatile",
392 "secure",
393 "undrop",
394 "elseif",
395 "cursor",
396 "resultset",
397 "connect",
398 "prior",
399 "top",
400 "copy",
401 "owner",
402 ] {
403 assert!(
404 keyword_kind_for(word, Dialect::Snowflake).is_some(),
405 "{word} should be reserved in Snowflake"
406 );
407 assert_eq!(
408 keyword_kind_for(word, Dialect::Databricks),
409 None,
410 "{word} must be a plain identifier under Databricks"
411 );
412 }
413 }
414}