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 ("after", AFTER_KW, Shared),
58 ("all", ALL_KW, Shared),
59 ("alter", ALTER_KW, Shared),
60 ("and", AND_KW, Shared),
61 ("any", ANY_KW, Shared),
62 ("as", AS_KW, Shared),
63 ("asc", ASC_KW, Shared),
64 ("begin", BEGIN_KW, Shared),
65 ("between", BETWEEN_KW, Shared),
66 ("by", BY_KW, Shared),
67 ("call", CALL_KW, Shared),
68 ("called", CALLED_KW, SnowflakeOnly),
69 ("caller", CALLER_KW, SnowflakeOnly),
70 ("case", CASE_KW, Shared),
71 ("cast", CAST_KW, Shared),
72 ("commit", COMMIT_KW, Shared),
73 ("connect", CONNECT_KW, SnowflakeOnly),
75 ("copy", COPY_KW, SnowflakeOnly),
76 ("create", CREATE_KW, Shared),
77 ("cross", CROSS_KW, Shared),
78 ("current", CURRENT_KW, Shared),
79 ("cursor", CURSOR_KW, SnowflakeOnly),
80 ("declare", DECLARE_KW, Shared),
81 ("delete", DELETE_KW, Shared),
82 ("desc", DESC_KW, Shared),
83 ("describe", DESCRIBE_KW, Shared),
84 ("distinct", DISTINCT_KW, Shared),
85 ("do", DO_KW, Shared),
86 ("drop", DROP_KW, Shared),
87 ("else", ELSE_KW, Shared),
88 ("elseif", ELSEIF_KW, SnowflakeOnly),
89 ("end", END_KW, Shared),
90 ("except", EXCEPT_KW, Shared),
91 ("exception", EXCEPTION_KW, SnowflakeOnly),
92 ("execute", EXECUTE_KW, Shared),
93 ("exists", EXISTS_KW, Shared),
94 ("false", FALSE_KW, Shared),
95 ("fetch", FETCH_KW, Shared),
96 ("first", FIRST_KW, Shared),
97 ("flatten", FLATTEN_KW, SnowflakeOnly),
99 ("following", FOLLOWING_KW, Shared),
100 ("for", FOR_KW, Shared),
101 ("from", FROM_KW, Shared),
102 ("full", FULL_KW, Shared),
103 ("function", FUNCTION_KW, Shared),
104 ("grant", GRANT_KW, Shared),
105 ("grants", GRANTS_KW, SnowflakeOnly),
106 ("group", GROUP_KW, Shared),
107 ("handler", HANDLER_KW, SnowflakeOnly),
108 ("having", HAVING_KW, Shared),
109 ("if", IF_KW, Shared),
110 ("ilike", ILIKE_KW, SnowflakeOnly),
112 ("immediate", IMMEDIATE_KW, SnowflakeOnly),
113 ("imports", IMPORTS_KW, SnowflakeOnly),
114 ("in", IN_KW, Shared),
115 ("inner", INNER_KW, Shared),
116 ("input", INPUT_KW, Shared),
117 ("insert", INSERT_KW, Shared),
118 ("intersect", INTERSECT_KW, Shared),
119 ("into", INTO_KW, Shared),
120 ("is", IS_KW, Shared),
121 ("java", JAVA_KW, Shared),
122 ("javascript", JAVASCRIPT_KW, SnowflakeOnly),
125 ("join", JOIN_KW, Shared),
126 ("language", LANGUAGE_KW, Shared),
127 ("last", LAST_KW, Shared),
128 ("lateral", LATERAL_KW, Shared),
129 ("left", LEFT_KW, Shared),
130 ("let", LET_KW, Shared),
131 ("like", LIKE_KW, Shared),
132 ("limit", LIMIT_KW, Shared),
133 ("loop", LOOP_KW, Shared),
134 ("matched", MATCHED_KW, Shared),
135 ("merge", MERGE_KW, Shared),
136 ("minus", MINUS_KW, Shared),
137 ("natural", NATURAL_KW, Shared),
138 ("not", NOT_KW, Shared),
139 ("null", NULL_KW, Shared),
140 ("nulls", NULLS_KW, Shared),
141 ("offset", OFFSET_KW, Shared),
142 ("on", ON_KW, Shared),
143 ("or", OR_KW, Shared),
144 ("order", ORDER_KW, Shared),
145 ("out", OUT_KW, Shared),
146 ("outer", OUTER_KW, Shared),
147 ("output", OUTPUT_KW, Shared),
148 ("over", OVER_KW, Shared),
149 ("overwrite", OVERWRITE_KW, Shared),
150 ("owner", OWNER_KW, SnowflakeOnly),
151 ("packages", PACKAGES_KW, SnowflakeOnly),
152 ("partition", PARTITION_KW, Shared),
153 ("pivot", PIVOT_KW, Shared),
154 ("preceding", PRECEDING_KW, Shared),
155 ("prior", PRIOR_KW, SnowflakeOnly),
156 ("procedure", PROCEDURE_KW, Shared),
157 ("python", PYTHON_KW, Shared),
158 ("qualify", QUALIFY_KW, Shared),
164 ("range", RANGE_KW, Shared),
165 ("recursive", RECURSIVE_KW, Shared),
166 ("regexp", REGEXP_KW, SnowflakeOnly),
167 ("repeat", REPEAT_KW, Shared),
168 ("replace", REPLACE_KW, Shared),
169 ("resultset", RESULTSET_KW, SnowflakeOnly),
170 ("return", RETURN_KW, Shared),
171 ("returns", RETURNS_KW, Shared),
172 ("revoke", REVOKE_KW, Shared),
173 ("right", RIGHT_KW, Shared),
174 ("rlike", RLIKE_KW, SnowflakeOnly),
175 ("rollback", ROLLBACK_KW, Shared),
176 ("row", ROW_KW, Shared),
177 ("rows", ROWS_KW, Shared),
178 ("runtime_version", RUNTIME_VERSION_KW, SnowflakeOnly),
179 ("sample", SAMPLE_KW, SnowflakeOnly),
181 ("scala", SCALA_KW, SnowflakeOnly),
182 ("schedule", SCHEDULE_KW, SnowflakeOnly),
183 ("secure", SECURE_KW, SnowflakeOnly),
185 ("select", SELECT_KW, Shared),
186 ("set", SET_KW, Shared),
187 ("show", SHOW_KW, Shared),
188 ("sql", SQL_KW, Shared),
189 ("start", START_KW, Shared),
190 ("strict", STRICT_KW, SnowflakeOnly),
191 ("table", TABLE_KW, Shared),
192 ("tablesample", TABLESAMPLE_KW, Shared),
193 ("task", TASK_KW, SnowflakeOnly),
195 ("temp", TEMP_KW, Shared),
196 ("temporary", TEMPORARY_KW, Shared),
197 ("then", THEN_KW, Shared),
198 ("top", TOP_KW, SnowflakeOnly),
200 ("transient", TRANSIENT_KW, SnowflakeOnly),
201 ("true", TRUE_KW, Shared),
202 ("truncate", TRUNCATE_KW, Shared),
203 ("try_cast", TRY_CAST_KW, SnowflakeOnly),
205 ("unbounded", UNBOUNDED_KW, Shared),
206 ("undrop", UNDROP_KW, SnowflakeOnly),
208 ("union", UNION_KW, Shared),
209 ("unpivot", UNPIVOT_KW, Shared),
210 ("until", UNTIL_KW, Shared),
211 ("update", UPDATE_KW, Shared),
212 ("use", USE_KW, Shared),
213 ("using", USING_KW, Shared),
214 ("values", VALUES_KW, Shared),
215 ("view", VIEW_KW, Shared),
216 ("volatile", VOLATILE_KW, SnowflakeOnly),
217 ("warehouse", WAREHOUSE_KW, SnowflakeOnly),
218 ("when", WHEN_KW, Shared),
219 ("where", WHERE_KW, Shared),
220 ("while", WHILE_KW, Shared),
221 ("window", WINDOW_KW, Shared),
222 ("with", WITH_KW, Shared),
223 ("within", WITHIN_KW, Shared),
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 .binary_search_by(|(text, _, _)| text.cmp(&lower))
253 .ok()
254 .map(|index| {
255 let (_, kind, dialect) = KEYWORDS[index];
256 (kind, dialect)
257 })
258}
259
260#[must_use]
267pub fn keyword_kind(ident: &str) -> Option<SyntaxKind> {
268 keyword_kind_for(ident, Dialect::Snowflake)
269}
270
271#[must_use]
278pub fn keyword_kind_for(ident: &str, dialect: Dialect) -> Option<SyntaxKind> {
279 let (kind, kw_dialect) = lookup(ident)?;
280 kw_dialect.reserved_in(dialect).then_some(kind)
281}
282
283pub fn keyword_texts() -> impl ExactSizeIterator<Item = &'static str> {
288 KEYWORDS.iter().map(|(text, _, _)| *text)
289}
290
291#[cfg(test)]
292mod tests {
293 use super::{keyword_kind, keyword_kind_for, keyword_texts, KeywordDialect, KEYWORDS};
294 use crate::{Dialect, SyntaxKind};
295
296 #[test]
297 fn keyword_lookup_is_case_insensitive() {
298 assert_eq!(keyword_kind("select"), Some(SyntaxKind::SELECT_KW));
299 assert_eq!(keyword_kind("SeLeCt"), Some(SyntaxKind::SELECT_KW));
300 assert_eq!(keyword_kind("QUALIFY"), Some(SyntaxKind::QUALIFY_KW));
301 assert_eq!(keyword_kind("javascript"), Some(SyntaxKind::JAVASCRIPT_KW));
302 assert_eq!(keyword_kind("try_cast"), Some(SyntaxKind::TRY_CAST_KW));
303 assert_eq!(keyword_kind("TASK"), Some(SyntaxKind::TASK_KW));
304 assert_eq!(
305 keyword_kind("runtime_version"),
306 Some(SyntaxKind::RUNTIME_VERSION_KW)
307 );
308 assert_eq!(keyword_kind("definitely_not_a_keyword"), None);
309 }
310
311 #[test]
312 fn every_keyword_variant_is_mapped() {
313 let range_count = SyntaxKind::__KW_END as u16 - SyntaxKind::__KW_START as u16 - 1;
315 assert_eq!(
316 KEYWORDS.len() as u16,
317 range_count,
318 "KEYWORDS table is out of sync with the SyntaxKind keyword block"
319 );
320 let mut seen = std::collections::HashSet::new();
321 for (text, kind, _) in KEYWORDS {
322 assert_eq!(
323 keyword_kind(text),
324 Some(*kind),
325 "keyword_kind({text:?}) is wrong"
326 );
327 assert_eq!(
328 keyword_kind(&text.to_uppercase()),
329 Some(*kind),
330 "keyword_kind is not case-insensitive for {text:?}"
331 );
332 assert!(
333 kind.is_keyword(),
334 "{kind:?} should be inside the keyword range"
335 );
336 assert!(seen.insert(*kind), "duplicate keyword kind for {text:?}");
337 assert!(
338 text.bytes().all(|b| !b.is_ascii_uppercase()),
339 "KEYWORDS text must be lowercase: {text:?}"
340 );
341 }
342 }
343
344 #[test]
345 fn keywords_are_sorted_for_binary_search() {
346 for window in KEYWORDS.windows(2) {
347 let (left, _, _) = window[0];
348 let (right, _, _) = window[1];
349 assert!(
350 left < right,
351 "KEYWORDS must be sorted: {left:?} >= {right:?}"
352 );
353 }
354 }
355
356 #[test]
357 fn keyword_texts_exposes_the_lookup_table_order() {
358 let texts: Vec<_> = keyword_texts().collect();
359 assert_eq!(texts.len(), KEYWORDS.len());
360 assert_eq!(texts.first(), Some(&"after"));
361 assert_eq!(texts.last(), Some(&"within"));
362 for (text, (table_text, _, _)) in texts.iter().zip(KEYWORDS) {
363 assert_eq!(text, table_text);
364 }
365 }
366
367 #[test]
368 fn every_keyword_has_a_dialect_classification() {
369 for (text, kind, dialect) in KEYWORDS {
372 assert!(
373 matches!(
374 dialect,
375 KeywordDialect::Shared
376 | KeywordDialect::SnowflakeOnly
377 | KeywordDialect::DatabricksOnly
378 ),
379 "{text:?} ({kind:?}) has no dialect classification"
380 );
381 }
382 }
383
384 #[test]
385 fn snowflake_classification_is_byte_identical_to_legacy_keyword_kind() {
386 for (text, kind, _) in KEYWORDS {
389 assert_eq!(
390 keyword_kind_for(text, Dialect::Snowflake),
391 Some(*kind),
392 "Snowflake reservation changed for {text:?}"
393 );
394 assert_eq!(
395 keyword_kind(text),
396 keyword_kind_for(text, Dialect::Snowflake)
397 );
398 }
399 }
400
401 #[test]
402 fn shared_keywords_are_reserved_in_every_dialect() {
403 for word in ["select", "from", "where", "join", "group", "order", "case"] {
405 assert!(
406 keyword_kind_for(word, Dialect::Databricks).is_some(),
407 "{word}"
408 );
409 assert!(
410 keyword_kind_for(word, Dialect::Snowflake).is_some(),
411 "{word}"
412 );
413 }
414 }
415
416 #[test]
417 fn snowflake_only_keywords_are_identifiers_under_databricks() {
418 for word in [
420 "task",
421 "flatten",
422 "warehouse",
423 "schedule",
424 "transient",
425 "volatile",
426 "secure",
427 "undrop",
428 "elseif",
429 "cursor",
430 "resultset",
431 "connect",
432 "prior",
433 "top",
434 "copy",
435 "owner",
436 ] {
437 assert!(
438 keyword_kind_for(word, Dialect::Snowflake).is_some(),
439 "{word} should be reserved in Snowflake"
440 );
441 assert_eq!(
442 keyword_kind_for(word, Dialect::Databricks),
443 None,
444 "{word} must be a plain identifier under Databricks"
445 );
446 }
447 }
448}