mz_sql_lexer/keywords.rs
1// Copyright 2018 sqlparser-rs contributors. All rights reserved.
2// Copyright Materialize, Inc. and contributors. All rights reserved.
3//
4// This file is derived from the sqlparser-rs project, available at
5// https://github.com/andygrove/sqlparser-rs. It was incorporated
6// directly into Materialize on December 21, 2019.
7//
8// Licensed under the Apache License, Version 2.0 (the "License");
9// you may not use this file except in compliance with the License.
10// You may obtain a copy of the License in the LICENSE file at the
11// root of this repository, or online at
12//
13// http://www.apache.org/licenses/LICENSE-2.0
14//
15// Unless required by applicable law or agreed to in writing, software
16// distributed under the License is distributed on an "AS IS" BASIS,
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18// See the License for the specific language governing permissions and
19// limitations under the License.
20
21use std::fmt;
22use std::str::FromStr;
23
24use uncased::UncasedStr;
25
26// The `Keyword` type and the keyword constants are automatically generated from
27// the list in keywords.txt by the crate's build script.
28//
29// We go to the trouble of code generation primarily to create a "perfect hash
30// function" at compile time via the phf crate, which enables very fast,
31// case-insensitive keyword parsing. From there it's easy to generate a few
32// more convenience functions and accessors.
33//
34// If the only keywords were `Insert` and `Select`, we'd generate the following
35// code:
36//
37// pub enum Keyword {
38// Insert,
39// Select,
40// }
41//
42// pub const INSERT: Keyword = Keyword::Insert;
43// pub const SELECT: Keyword = Keyword::Select;
44//
45// impl Keyword {
46// pub fn as_str(&self) -> &'static str {
47// match self {
48// Keyword::Insert => "INSERT",
49// Keyword::Select => "SELECT",
50// }
51// }
52// }
53//
54// static KEYWORDS: phf::Map<&'static UncasedStr, Keyword> = { /* ... */ };
55//
56include!(concat!(env!("OUT_DIR"), "/keywords.rs"));
57
58impl Keyword {
59 /// Reports whether this keyword requires quoting when used as an
60 /// identifier in any context.
61 ///
62 /// The only exception to the rule is when the keyword follows `AS` in a
63 /// column or table alias.
64 pub fn is_always_reserved(self) -> bool {
65 matches!(
66 self,
67 // Keywords that can appear at the top-level of a SELECT
68 // statement.
69 WITH | SELECT | FROM | WHERE | GROUP | HAVING |
70 QUALIFY | WINDOW | ORDER | LIMIT | OFFSET | FETCH |
71 OPTIONS | RETURNING |
72 // Set operations.
73 UNION | EXCEPT | INTERSECT
74 )
75 }
76
77 /// Reports whether this keyword requires quoting when used in scalar expressions.
78 ///
79 /// These are the keywords `Parser::parse_prefix` won't parse as an identifier.
80 /// (Note that for some keywords `parse_prefix` checks whether they are followed by an opening
81 /// parenthesis before treating them as keywords. These keywords do not need to be marked as
82 /// reserved here.)
83 ///
84 /// This refers to the PostgreSQL notion of "reserved" keywords,
85 /// which generally refers to built in tables, functions, and
86 /// constructs that cannot be used as identifiers without quoting.
87 /// See <https://www.postgresql.org/docs/current/sql-keywords-appendix.html>
88 /// for more details.
89 pub fn is_reserved_in_scalar_expression(self) -> bool {
90 matches!(self, TRUE | FALSE | NULL | ARRAY | CASE | CAST | NOT) || self.is_always_reserved()
91 }
92
93 /// Reports whether this keyword requires quoting when used as a table
94 /// alias.
95 ///
96 /// Note that this rule is only applies when the table alias is "bare";
97 /// i.e., when the table alias is not preceded by `AS`.
98 ///
99 /// Ensures that `FROM <table_name> <table_alias>` can be parsed
100 /// unambiguously.
101 pub fn is_reserved_in_table_alias(self) -> bool {
102 matches!(
103 self,
104 // These keywords are ambiguous when used as a table alias, as they
105 // conflict with the syntax for joins.
106 ON | JOIN | INNER | CROSS | FULL | LEFT | RIGHT | NATURAL | USING |
107 // Needed for UPDATE.
108 SET |
109 // `OUTER` is not strictly ambiguous, but it prevents `a OUTER JOIN
110 // b` from parsing as `a AS outer JOIN b`, instead producing a nice
111 // syntax error.
112 OUTER
113 ) || self.is_always_reserved()
114 }
115
116 /// Reports whether this keyword requires quoting when used as a column
117 /// alias.
118 ///
119 /// Note that this rule is only applies when the column alias is "bare";
120 /// i.e., when the column alias is not preceded by `AS`.
121 ///
122 /// Ensures that `SELECT <column_name> <column_alias>` can be parsed
123 /// unambiguously.
124 pub fn is_reserved_in_column_alias(self) -> bool {
125 matches!(
126 self,
127 // These timelike keywords conflict with interval timeframe
128 // suffixes. They are not strictly ambiguous, but marking them
129 // reserved prevents e.g. `SELECT pg_catalog.interval '1' year` from
130 // parsing as `SELECT pg_catalog.interval '1' AS YEAR`.
131 YEAR | MONTH | DAY | HOUR | MINUTE | SECOND
132 ) || self.is_always_reserved()
133 }
134
135 /// Reports whether a keyword is considered reserved in any context:
136 /// either in table aliases, column aliases, or in all contexts.
137 pub fn is_sometimes_reserved(self) -> bool {
138 self.is_always_reserved()
139 || self.is_reserved_in_table_alias()
140 || self.is_reserved_in_column_alias()
141 || self.is_reserved_in_scalar_expression()
142 }
143}
144
145impl FromStr for Keyword {
146 type Err = ();
147
148 fn from_str(s: &str) -> Result<Keyword, ()> {
149 match KEYWORDS.get(UncasedStr::new(s)) {
150 Some(kw) => Ok(*kw),
151 None => Err(()),
152 }
153 }
154}
155
156impl fmt::Display for Keyword {
157 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158 f.write_str(self.as_str())
159 }
160}