Skip to main content

crabka_pgparser/
token.rs

1//! Lexical tokens for the SP2 SQL slice.
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum Token {
5    Ident(String),
6    Keyword(Keyword),
7    IntLit(String),
8    /// SP30: a decimal/exponent numeric literal (`1.5`, `.5`, `2.`, `1e10`), typed
9    /// `float8` by the executor (crabgresql has no `numeric`).
10    FloatLit(String),
11    StringLit(String),
12    LParen,
13    RParen,
14    Comma,
15    Semicolon,
16    Star,
17    Plus,
18    Minus,
19    Slash,
20    /// SP29: the `||` string-concatenation operator.
21    Concat,
22    /// SP31: the `::` cast operator (`expr::type`).
23    TypeCast,
24    /// SP33: the `.` qualified-name separator (`a.col`). Only lexed when it does
25    /// NOT begin a number lexeme — `.5` / `2.` stay a single `FloatLit`.
26    Dot,
27    Eq,
28    Ne,
29    Lt,
30    Le,
31    Gt,
32    Ge,
33    Param(u32),
34    Eof,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum Keyword {
39    Create,
40    Unique,
41    Index,
42    Global,
43    Local,
44    Table,
45    View,
46    Drop,
47    Insert,
48    Copy,
49    Into,
50    Values,
51    Select,
52    With,
53    Recursive,
54    From,
55    Where,
56    Order,
57    By,
58    Asc,
59    Desc,
60    Limit,
61    And,
62    Or,
63    Not,
64    True,
65    False,
66    Null,
67    As,
68    // SP4: transaction control + DML
69    Begin,
70    Start,
71    Transaction,
72    Commit,
73    End,
74    Rollback,
75    Abort,
76    Update,
77    Set,
78    Delete,
79    Isolation,
80    Level,
81    Read,
82    Committed,
83    Repeatable,
84    // SP6: row-level locking
85    For,
86    Share,
87    // SP27: aggregates + grouping
88    Group,
89    Having,
90    Distinct,
91    All,
92    // SP28: predicate + conditional expression breadth
93    Is,
94    In,
95    Between,
96    Like,
97    Ilike,
98    Case,
99    When,
100    Then,
101    Else,
102    Offset,
103    // SP31: explicit casts
104    Cast,
105    // SP33: joins
106    Join,
107    Inner,
108    Left,
109    Right,
110    Full,
111    Outer,
112    Cross,
113    On,
114    Using,
115    Natural,
116    // SP34: subqueries
117    Exists,
118    Any,
119    Some,
120    // SP38: set operations
121    Union,
122    Intersect,
123    Except,
124    // SP40: FDW DDL keywords
125    Foreign,
126    Data,
127    Wrapper,
128    Server,
129    Mapping,
130    User,
131    Options,
132    Import,
133    Schema,
134    To,
135    If,
136    CurrentUser,
137    Public,
138    Returning,
139}
140
141impl Keyword {
142    #[must_use]
143    pub fn from_word(w: &str) -> Option<Keyword> {
144        Some(match w {
145            "create" => Keyword::Create,
146            "unique" => Keyword::Unique,
147            "index" => Keyword::Index,
148            "global" => Keyword::Global,
149            "local" => Keyword::Local,
150            "table" => Keyword::Table,
151            "view" => Keyword::View,
152            "drop" => Keyword::Drop,
153            "insert" => Keyword::Insert,
154            "copy" => Keyword::Copy,
155            "into" => Keyword::Into,
156            "values" => Keyword::Values,
157            "select" => Keyword::Select,
158            "with" => Keyword::With,
159            "recursive" => Keyword::Recursive,
160            "from" => Keyword::From,
161            "where" => Keyword::Where,
162            "order" => Keyword::Order,
163            "by" => Keyword::By,
164            "asc" => Keyword::Asc,
165            "desc" => Keyword::Desc,
166            "limit" => Keyword::Limit,
167            "and" => Keyword::And,
168            "or" => Keyword::Or,
169            "not" => Keyword::Not,
170            "true" => Keyword::True,
171            "false" => Keyword::False,
172            "null" => Keyword::Null,
173            "as" => Keyword::As,
174            // SP4: transaction control + DML
175            "begin" => Keyword::Begin,
176            "start" => Keyword::Start,
177            "transaction" => Keyword::Transaction,
178            "commit" => Keyword::Commit,
179            "end" => Keyword::End,
180            "rollback" => Keyword::Rollback,
181            "abort" => Keyword::Abort,
182            "update" => Keyword::Update,
183            "set" => Keyword::Set,
184            "delete" => Keyword::Delete,
185            "isolation" => Keyword::Isolation,
186            "level" => Keyword::Level,
187            "read" => Keyword::Read,
188            "committed" => Keyword::Committed,
189            "repeatable" => Keyword::Repeatable,
190            // SP6: row-level locking
191            "for" => Keyword::For,
192            "share" => Keyword::Share,
193            // SP27: aggregates + grouping
194            "group" => Keyword::Group,
195            "having" => Keyword::Having,
196            "distinct" => Keyword::Distinct,
197            "all" => Keyword::All,
198            // SP28: predicate + conditional expression breadth
199            "is" => Keyword::Is,
200            "in" => Keyword::In,
201            "between" => Keyword::Between,
202            "like" => Keyword::Like,
203            "ilike" => Keyword::Ilike,
204            "case" => Keyword::Case,
205            "when" => Keyword::When,
206            "then" => Keyword::Then,
207            "else" => Keyword::Else,
208            "offset" => Keyword::Offset,
209            // SP31: explicit casts
210            "cast" => Keyword::Cast,
211            // SP33: joins
212            "join" => Keyword::Join,
213            "inner" => Keyword::Inner,
214            "left" => Keyword::Left,
215            "right" => Keyword::Right,
216            "full" => Keyword::Full,
217            "outer" => Keyword::Outer,
218            "cross" => Keyword::Cross,
219            "on" => Keyword::On,
220            "using" => Keyword::Using,
221            "natural" => Keyword::Natural,
222            // SP34: subqueries
223            "exists" => Keyword::Exists,
224            "any" => Keyword::Any,
225            "some" => Keyword::Some,
226            // SP38: set operations
227            "union" => Keyword::Union,
228            "intersect" => Keyword::Intersect,
229            "except" => Keyword::Except,
230            // SP40: FDW DDL keywords
231            "foreign" => Keyword::Foreign,
232            "data" => Keyword::Data,
233            "wrapper" => Keyword::Wrapper,
234            "server" => Keyword::Server,
235            "mapping" => Keyword::Mapping,
236            "user" => Keyword::User,
237            "options" => Keyword::Options,
238            "import" => Keyword::Import,
239            "schema" => Keyword::Schema,
240            "to" => Keyword::To,
241            "if" => Keyword::If,
242            "current_user" => Keyword::CurrentUser,
243            "public" => Keyword::Public,
244            "returning" => Keyword::Returning,
245            _ => return None,
246        })
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    #[test]
255    fn from_word_round_trips_every_keyword() {
256        // Every keyword must map from its lowercase spelling; a dropped arm would
257        // silently demote that word to an identifier (e.g. `ASC` parsed as a column).
258        let pairs: &[(&str, Keyword)] = &[
259            ("create", Keyword::Create),
260            ("unique", Keyword::Unique),
261            ("index", Keyword::Index),
262            ("global", Keyword::Global),
263            ("local", Keyword::Local),
264            ("table", Keyword::Table),
265            ("view", Keyword::View),
266            ("drop", Keyword::Drop),
267            ("insert", Keyword::Insert),
268            ("copy", Keyword::Copy),
269            ("into", Keyword::Into),
270            ("values", Keyword::Values),
271            ("select", Keyword::Select),
272            ("with", Keyword::With),
273            ("recursive", Keyword::Recursive),
274            ("from", Keyword::From),
275            ("where", Keyword::Where),
276            ("order", Keyword::Order),
277            ("by", Keyword::By),
278            ("asc", Keyword::Asc),
279            ("desc", Keyword::Desc),
280            ("limit", Keyword::Limit),
281            ("and", Keyword::And),
282            ("or", Keyword::Or),
283            ("not", Keyword::Not),
284            ("true", Keyword::True),
285            ("false", Keyword::False),
286            ("null", Keyword::Null),
287            ("as", Keyword::As),
288            ("begin", Keyword::Begin),
289            ("start", Keyword::Start),
290            ("transaction", Keyword::Transaction),
291            ("commit", Keyword::Commit),
292            ("end", Keyword::End),
293            ("rollback", Keyword::Rollback),
294            ("abort", Keyword::Abort),
295            ("update", Keyword::Update),
296            ("set", Keyword::Set),
297            ("delete", Keyword::Delete),
298            ("isolation", Keyword::Isolation),
299            ("level", Keyword::Level),
300            ("read", Keyword::Read),
301            ("committed", Keyword::Committed),
302            ("repeatable", Keyword::Repeatable),
303            ("for", Keyword::For),
304            ("share", Keyword::Share),
305            ("group", Keyword::Group),
306            ("having", Keyword::Having),
307            ("distinct", Keyword::Distinct),
308            ("all", Keyword::All),
309            ("cast", Keyword::Cast),
310            // SP33: joins
311            ("join", Keyword::Join),
312            ("inner", Keyword::Inner),
313            ("left", Keyword::Left),
314            ("right", Keyword::Right),
315            ("full", Keyword::Full),
316            ("outer", Keyword::Outer),
317            ("cross", Keyword::Cross),
318            ("on", Keyword::On),
319            ("using", Keyword::Using),
320            ("natural", Keyword::Natural),
321            ("exists", Keyword::Exists),
322            ("any", Keyword::Any),
323            ("some", Keyword::Some),
324            ("union", Keyword::Union),
325            ("intersect", Keyword::Intersect),
326            ("except", Keyword::Except),
327            // SP40: FDW DDL keywords
328            ("foreign", Keyword::Foreign),
329            ("data", Keyword::Data),
330            ("wrapper", Keyword::Wrapper),
331            ("server", Keyword::Server),
332            ("mapping", Keyword::Mapping),
333            ("user", Keyword::User),
334            ("options", Keyword::Options),
335            ("import", Keyword::Import),
336            ("schema", Keyword::Schema),
337            ("to", Keyword::To),
338            ("if", Keyword::If),
339            ("current_user", Keyword::CurrentUser),
340            ("public", Keyword::Public),
341            ("returning", Keyword::Returning),
342        ];
343        for (word, kw) in pairs {
344            assert_eq!(Keyword::from_word(word), Some(*kw), "from_word({word:?})");
345        }
346        // A non-keyword is an identifier, not a keyword.
347        assert_eq!(Keyword::from_word("widget"), None);
348        assert_eq!(Keyword::from_word("ascending"), None);
349    }
350}