Skip to main content

radixdb_sql/
parser.rs

1// Copyright 2026 RadixDB Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! SQL Parser - Main Parser struct and core parsing logic
16
17use std::sync::LazyLock;
18
19use rustc_hash::FxHashSet;
20
21use super::ast::*;
22use super::error::{ParseError, ParseErrors};
23use super::lexer::Lexer;
24use super::precedence::Precedence;
25use super::token::{Position, Token, TokenType};
26
27/// Reserved SQL keywords that cannot be used as identifiers (O(1) lookup)
28static RESERVED_KEYWORDS: LazyLock<FxHashSet<&'static str>> = LazyLock::new(|| {
29    [
30        // Core SQL keywords that should never be identifiers
31        "SELECT",
32        "FROM",
33        "WHERE",
34        "AND",
35        "OR",
36        "NOT",
37        "INSERT",
38        "INTO",
39        "VALUES",
40        "UPDATE",
41        "SET",
42        "DELETE",
43        "CREATE",
44        "DROP",
45        "TABLE",
46        "INDEX",
47        "VIEW",
48        "EXTENSION",
49        "PLANNER",
50        "SUPPORT",
51        "ALTER",
52        "ADD",
53        "PRIMARY",
54        "KEY",
55        "FOREIGN",
56        "REFERENCES",
57        "NULL",
58        "TRUE",
59        "FALSE",
60        "AS",
61        "ON",
62        "JOIN",
63        // LEFT and RIGHT are handled specially - they can be function names
64        // or column names when not followed by JOIN
65        "INNER",
66        "OUTER",
67        "FULL",
68        "CROSS",
69        "GROUP",
70        "BY",
71        "ORDER",
72        "HAVING",
73        "LIMIT",
74        "OFFSET",
75        "UNION",
76        "INTERSECT",
77        "EXCEPT",
78        "CASE",
79        "WHEN",
80        "THEN",
81        "ELSE",
82        "END",
83        "DISTINCT",
84        "ALL",
85        "EXISTS",
86        "IN",
87        "BETWEEN",
88        "LIKE",
89        "GLOB",
90        "REGEXP",
91        "RLIKE",
92        "IS",
93        "ASC",
94        "DESC",
95        "NULLS",
96        // FIRST and LAST are handled specially - they can be function names
97        // or column names, or ORDER BY modifiers (NULLS FIRST/LAST)
98        "BEGIN",
99        "COMMIT",
100        "ROLLBACK",
101        "SAVEPOINT",
102        "RELEASE",
103        "IF",
104        "WITH",
105        "RECURSIVE",
106    ]
107    .into_iter()
108    .collect()
109});
110
111const MAX_EXPRESSION_NESTING: usize = 128;
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub(crate) enum PositionalParameterStyle {
115    Anonymous,
116    Explicit,
117}
118
119/// SQL Parser using Pratt parsing algorithm
120pub struct Parser {
121    /// Original SQL source retained for diagnostics from the public Parser API.
122    pub(crate) source: Box<str>,
123    /// The lexer providing tokens
124    lexer: Lexer,
125    /// Current token being examined
126    pub(crate) cur_token: Token,
127    /// Next token (peek)
128    pub(crate) peek_token: Token,
129    /// Collected errors
130    errors: Vec<ParseError>,
131    /// Current clause context (for error messages and parameter tracking)
132    pub(crate) current_clause: String,
133    /// Parameter counter within current statement
134    parameter_counter: usize,
135    /// Positional placeholder syntax used by the current statement.
136    pub(crate) positional_parameter_style: Option<PositionalParameterStyle>,
137    /// Current recursive expression depth, bounded before entering parser frames.
138    pub(crate) expression_depth: usize,
139    /// Non-zero while parsing a stored procedural definition.
140    pub(crate) procedural_definition_depth: usize,
141    /// Number of non-comment tokens consumed from this source.
142    pub(crate) token_count: usize,
143}
144
145impl Parser {
146    fn next_parser_token(lexer: &mut Lexer) -> Token {
147        loop {
148            let token = lexer.next_token();
149            if token.token_type != TokenType::Comment {
150                return token;
151            }
152        }
153    }
154
155    /// Create a new parser for the given input
156    pub fn new(input: &str) -> Self {
157        let normalized = if input.contains('\r') {
158            input.replace("\r\n", "\n").replace('\r', "\n")
159        } else {
160            input.to_owned()
161        };
162        let mut lexer = Lexer::new(&normalized);
163        let cur_token = Self::next_parser_token(&mut lexer);
164        let peek_token = Self::next_parser_token(&mut lexer);
165
166        Parser {
167            source: normalized.into(),
168            lexer,
169            cur_token,
170            peek_token,
171            errors: Vec::new(),
172            current_clause: String::new(),
173            parameter_counter: 1,
174            positional_parameter_style: None,
175            expression_depth: 0,
176            procedural_definition_depth: 0,
177            token_count: 2,
178        }
179    }
180
181    /// Parse the input and return a Program
182    pub fn parse_program(&mut self) -> Result<Program, ParseErrors> {
183        // Pre-allocate for common case (most queries have 1 statement)
184        let mut statements = Vec::with_capacity(1);
185
186        while !self.cur_token_is(TokenType::Eof) {
187            // Skip comments
188            if self.cur_token_is(TokenType::Comment) {
189                self.next_token();
190                continue;
191            }
192
193            if let Some(stmt) = self.parse_statement() {
194                statements.push(stmt);
195            }
196
197            if self.peek_token_is_punctuator(";") {
198                // One explicit delimiter is required between adjacent statements.
199                // Additional/trailing delimiters remain harmless.
200                while self.peek_token_is_punctuator(";") {
201                    self.next_token();
202                }
203                self.next_token();
204            } else if self.peek_token_is(TokenType::Eof) {
205                self.next_token();
206            } else {
207                self.add_error(format!(
208                    "expected ';' between statements before {}",
209                    Self::format_token_for_error(&self.peek_token)
210                ));
211                break;
212            }
213            self.parameter_counter = 1;
214            self.positional_parameter_style = None;
215        }
216
217        if !self.errors.is_empty() {
218            return Err(ParseErrors::from_errors_with_sql(
219                self.errors.clone(),
220                self.source.as_ref(),
221            ));
222        }
223
224        Ok(Program { statements })
225    }
226
227    /// Advance to the next token
228    pub(crate) fn next_token(&mut self) {
229        let next = Self::next_parser_token(&mut self.lexer);
230        self.cur_token = std::mem::replace(&mut self.peek_token, next);
231        self.token_count = self.token_count.saturating_add(1);
232        if self.procedural_definition_depth > 0
233            && self.cur_token.token_type == TokenType::Parameter
234            && !self.cur_token.literal.starts_with(':')
235        {
236            self.add_error_at(
237                "stored procedural source cannot contain external '$n' or '?' parameters"
238                    .to_string(),
239                self.cur_token.position,
240            );
241        }
242    }
243
244    /// Check if the current token is of the given type
245    pub(crate) fn cur_token_is(&self, t: TokenType) -> bool {
246        self.cur_token.token_type == t
247    }
248
249    /// Check if the peek token is of the given type
250    pub(crate) fn peek_token_is(&self, t: TokenType) -> bool {
251        self.peek_token.token_type == t
252    }
253
254    /// Check if the current token can be used as an identifier
255    /// This allows keywords like TIMESTAMP, DATE, etc. to be used as column/table names
256    pub(crate) fn cur_token_is_identifier_like(&self) -> bool {
257        match self.cur_token.token_type {
258            TokenType::Identifier => true,
259            TokenType::Keyword => {
260                // Allow non-reserved keywords as identifiers
261                // Reserved keywords that cannot be used as identifiers
262                !Self::is_reserved_keyword(&self.cur_token.literal)
263            }
264            _ => false,
265        }
266    }
267
268    /// Create an Identifier from the current token.
269    /// Identifier::new automatically lowercases keyword tokens.
270    pub(crate) fn cur_token_as_column_identifier(&self) -> Identifier {
271        Identifier::new(self.cur_token.clone(), self.cur_token.literal.clone())
272    }
273
274    /// Parse the relation spelling at the current token into the legacy
275    /// physical-name carrier used by SQL DDL/DML AST nodes.
276    ///
277    /// Catalog-backed routines already retain [`ObjectName`] components. The
278    /// table executor still addresses ordinary storage tables by one string,
279    /// so relation paths are preserved losslessly as `namespace.name` here
280    /// instead of being misparsed as a column qualification. This keeps one
281    /// lexer/parser and makes system relations such as `audit.event`
282    /// reachable through ordinary SQL until table AST nodes themselves carry
283    /// stable catalog identities.
284    pub(crate) fn parse_relation_identifier_current(&mut self) -> Option<Identifier> {
285        if !matches!(
286            self.cur_token.token_type,
287            TokenType::Identifier | TokenType::Keyword
288        ) {
289            self.add_error(format!(
290                "expected relation name, got {}",
291                Self::format_token_for_error(&self.cur_token)
292            ));
293            return None;
294        }
295
296        let token = self.cur_token.clone();
297        let mut value = self.cur_token.literal.clone();
298        while self.peek_token_is_punctuator(".") {
299            self.next_token();
300            if !self.expect_peek_identifier_like() {
301                return None;
302            }
303            value.push('.');
304            value.push_str(&self.cur_token.literal);
305        }
306        Some(Identifier::new(token, value))
307    }
308
309    /// Check if a keyword is truly reserved and cannot be used as an identifier
310    /// Note: Some keywords like LEFT, RIGHT, FIRST, LAST are handled specially in
311    /// parse_keyword_prefix() where they can be functions or identifiers.
312    /// Uses O(1) HashSet lookup instead of O(n) match chain.
313    pub(crate) fn is_reserved_keyword(keyword: &str) -> bool {
314        // Use uppercase for case-insensitive comparison
315        // Note: Keywords are typically already uppercase from the lexer
316        RESERVED_KEYWORDS.contains(keyword.to_uppercase().as_str())
317    }
318
319    /// Check if the current token is a specific keyword
320    pub(crate) fn cur_token_is_keyword(&self, keyword: &str) -> bool {
321        self.cur_token.token_type == TokenType::Keyword
322            && self.cur_token.literal.eq_ignore_ascii_case(keyword)
323    }
324
325    /// Check if the peek token is a specific keyword
326    pub(crate) fn peek_token_is_keyword(&self, keyword: &str) -> bool {
327        self.peek_token.token_type == TokenType::Keyword
328            && self.peek_token.literal.eq_ignore_ascii_case(keyword)
329    }
330
331    /// Check if the current token is a specific punctuator
332    pub(crate) fn cur_token_is_punctuator(&self, punc: &str) -> bool {
333        self.cur_token.token_type == TokenType::Punctuator && self.cur_token.literal == punc
334    }
335
336    /// Check if the peek token is a specific punctuator
337    pub(crate) fn peek_token_is_punctuator(&self, punc: &str) -> bool {
338        self.peek_token.token_type == TokenType::Punctuator && self.peek_token.literal == punc
339    }
340
341    /// Check if the peek token is a specific operator
342    pub(crate) fn peek_token_is_operator(&self, op: &str) -> bool {
343        self.peek_token.token_type == TokenType::Operator && self.peek_token.literal == op
344    }
345
346    /// Check if the peek token can be used as an identifier (true identifier or non-reserved keyword)
347    pub(crate) fn peek_token_is_identifier_like(&self) -> bool {
348        match self.peek_token.token_type {
349            TokenType::Identifier => true,
350            TokenType::Keyword => !Self::is_reserved_keyword(&self.peek_token.literal),
351            _ => false,
352        }
353    }
354
355    /// Expect the peek token to be an identifier (or non-reserved keyword) and advance
356    pub(crate) fn expect_peek_identifier_like(&mut self) -> bool {
357        if self.peek_token_is_identifier_like() {
358            self.next_token();
359            true
360        } else {
361            self.peek_error(TokenType::Identifier);
362            false
363        }
364    }
365
366    /// Expect the peek token to be of a specific type and advance
367    pub(crate) fn expect_peek(&mut self, t: TokenType) -> bool {
368        if self.peek_token_is(t) {
369            self.next_token();
370            true
371        } else {
372            self.peek_error(t);
373            false
374        }
375    }
376
377    /// Expect the peek token to be a specific keyword and advance
378    pub(crate) fn expect_keyword(&mut self, keyword: &str) -> bool {
379        if self.peek_token_is_keyword(keyword) {
380            self.next_token();
381            true
382        } else {
383            self.add_error(format!(
384                "expected {} after {}, got {}",
385                keyword,
386                self.cur_token.literal,
387                Self::format_token_for_error(&self.peek_token)
388            ));
389            false
390        }
391    }
392
393    /// Get the precedence of the peek token
394    pub(crate) fn peek_precedence(&self) -> Precedence {
395        match self.peek_token.token_type {
396            TokenType::Operator => Precedence::for_operator(&self.peek_token.literal),
397            TokenType::Keyword => Precedence::for_operator(&self.peek_token.literal),
398            TokenType::Punctuator => {
399                if self.peek_token.literal == "." {
400                    Precedence::Dot
401                } else if self.peek_token.literal == "(" {
402                    Precedence::Call
403                } else if self.peek_token.literal == "[" {
404                    Precedence::Index
405                } else {
406                    Precedence::Lowest
407                }
408            }
409            _ => Precedence::Lowest,
410        }
411    }
412
413    /// Get the precedence of the current token
414    pub(crate) fn cur_precedence(&self) -> Precedence {
415        match self.cur_token.token_type {
416            TokenType::Operator => Precedence::for_operator(&self.cur_token.literal),
417            TokenType::Keyword => Precedence::for_operator(&self.cur_token.literal),
418            TokenType::Punctuator => {
419                if self.cur_token.literal == "." {
420                    Precedence::Dot
421                } else if self.cur_token.literal == "(" {
422                    Precedence::Call
423                } else if self.cur_token.literal == "[" {
424                    Precedence::Index
425                } else {
426                    Precedence::Lowest
427                }
428            }
429            _ => Precedence::Lowest,
430        }
431    }
432
433    /// Add an error for unexpected peek token type
434    pub(crate) fn peek_error(&mut self, expected: TokenType) {
435        let position = self.peek_token.position;
436        let expected_desc = match expected {
437            TokenType::Identifier => "identifier (name)",
438            TokenType::Keyword => "keyword",
439            TokenType::Punctuator => "'(' or ')'",
440            TokenType::String => "string literal",
441            TokenType::Integer => "integer",
442            TokenType::Float => "number",
443            _ => "token",
444        };
445
446        if self.peek_token.token_type == TokenType::Eof {
447            if !self.current_clause.is_empty() {
448                self.add_error_at(
449                    format!("expected {} after {}", expected_desc, self.current_clause),
450                    position,
451                );
452            } else {
453                self.add_error_at(
454                    format!("unexpected end of input, expected {}", expected_desc),
455                    position,
456                );
457            }
458        } else if expected == TokenType::Identifier
459            && self.peek_token.token_type == TokenType::Keyword
460            && Self::is_reserved_keyword(&self.peek_token.literal)
461        {
462            self.add_error_at(
463                format!(
464                    "'{}' is a reserved keyword and cannot be used as an identifier. \
465                 Use double quotes to escape it: \"{}\"",
466                    self.peek_token.literal.to_uppercase(),
467                    self.peek_token.literal
468                ),
469                position,
470            );
471        } else {
472            self.add_error_at(
473                format!(
474                    "expected {}, got {}",
475                    expected_desc,
476                    Self::format_token_for_error(&self.peek_token)
477                ),
478                position,
479            );
480        }
481    }
482
483    /// Format a token for display in error messages (shows "end of input" for EOF)
484    pub(crate) fn format_token_for_error(token: &Token) -> String {
485        if token.token_type == TokenType::Eof {
486            "end of input".to_string()
487        } else {
488            format!("'{}'", token.literal)
489        }
490    }
491
492    /// Add an error message
493    pub(crate) fn add_error(&mut self, msg: String) {
494        self.add_error_at(msg, self.cur_token.position);
495    }
496
497    pub(crate) fn add_error_at(&mut self, msg: String, position: super::token::Position) {
498        self.errors.push(ParseError::new(msg, position));
499    }
500
501    pub(crate) fn source_range_from(&self, start: Position) -> SourceRange {
502        SourceRange::new(start, self.peek_token.position)
503    }
504
505    pub(crate) fn source_range_through_peek_from(&self, start: Position) -> SourceRange {
506        let mut end = self.peek_token.position;
507        end.offset = end.offset.saturating_add(self.peek_token.literal.len());
508        end.column = end
509            .column
510            .saturating_add(self.peek_token.literal.chars().count());
511        SourceRange::new(start, end)
512    }
513
514    pub(crate) fn normalized_source_for(&self, range: &SourceRange) -> String {
515        let start = range.start.offset.min(self.source.len());
516        let end = range.end.offset.min(self.source.len());
517        self.source[start..end].to_owned()
518    }
519
520    pub(crate) fn enter_expression(&mut self) -> bool {
521        if self.expression_depth >= MAX_EXPRESSION_NESTING {
522            self.add_error(format!(
523                "expression nesting depth exceeds limit of {MAX_EXPRESSION_NESTING}"
524            ));
525            return false;
526        }
527        self.expression_depth += 1;
528        true
529    }
530
531    /// Get collected errors
532    pub fn errors(&self) -> &[ParseError] {
533        &self.errors
534    }
535
536    /// Get the next parameter index
537    pub(crate) fn next_parameter_index(&mut self) -> usize {
538        let idx = self.parameter_counter;
539        self.parameter_counter += 1;
540        idx
541    }
542}
543
544#[cfg(test)]
545mod tests {
546    use super::*;
547
548    #[test]
549    fn test_parser_creation() {
550        let parser = Parser::new("SELECT * FROM users");
551        assert!(parser.cur_token_is_keyword("SELECT"));
552    }
553
554    #[test]
555    fn test_next_token() {
556        let mut parser = Parser::new("SELECT * FROM users");
557        assert!(parser.cur_token_is_keyword("SELECT"));
558        parser.next_token();
559        assert!(parser.cur_token_is(TokenType::Operator));
560        assert_eq!(parser.cur_token.literal, "*");
561    }
562
563    #[test]
564    fn test_peek_token() {
565        let parser = Parser::new("SELECT * FROM users");
566        assert!(parser.cur_token_is_keyword("SELECT"));
567        assert!(parser.peek_token_is_operator("*"));
568    }
569}