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    /// Flatten a parsed catalog object path into the legacy relation carrier.
310    pub(crate) fn relation_identifier_from_object_name(name: &ObjectName) -> Identifier {
311        Identifier::new(
312            name.components[0].token.clone(),
313            name.components
314                .iter()
315                .map(|component| component.value.as_str())
316                .collect::<Vec<_>>()
317                .join("."),
318        )
319    }
320
321    pub(crate) fn reject_unsupported_qualified_alter(
322        &mut self,
323        is_qualified: bool,
324        operation: AlterTableOperation,
325    ) -> bool {
326        if is_qualified && operation != AlterTableOperation::AddConstraint {
327            self.add_error("qualified ALTER TABLE supports only OWNER or ADD CONSTRAINT".into());
328            true
329        } else {
330            false
331        }
332    }
333
334    /// Check if a keyword is truly reserved and cannot be used as an identifier
335    /// Note: Some keywords like LEFT, RIGHT, FIRST, LAST are handled specially in
336    /// parse_keyword_prefix() where they can be functions or identifiers.
337    /// Uses O(1) HashSet lookup instead of O(n) match chain.
338    pub(crate) fn is_reserved_keyword(keyword: &str) -> bool {
339        // Use uppercase for case-insensitive comparison
340        // Note: Keywords are typically already uppercase from the lexer
341        RESERVED_KEYWORDS.contains(keyword.to_uppercase().as_str())
342    }
343
344    /// Check if the current token is a specific keyword
345    pub(crate) fn cur_token_is_keyword(&self, keyword: &str) -> bool {
346        self.cur_token.token_type == TokenType::Keyword
347            && self.cur_token.literal.eq_ignore_ascii_case(keyword)
348    }
349
350    /// Check if the peek token is a specific keyword
351    pub(crate) fn peek_token_is_keyword(&self, keyword: &str) -> bool {
352        self.peek_token.token_type == TokenType::Keyword
353            && self.peek_token.literal.eq_ignore_ascii_case(keyword)
354    }
355
356    /// Check if the current token is a specific punctuator
357    pub(crate) fn cur_token_is_punctuator(&self, punc: &str) -> bool {
358        self.cur_token.token_type == TokenType::Punctuator && self.cur_token.literal == punc
359    }
360
361    /// Check if the peek token is a specific punctuator
362    pub(crate) fn peek_token_is_punctuator(&self, punc: &str) -> bool {
363        self.peek_token.token_type == TokenType::Punctuator && self.peek_token.literal == punc
364    }
365
366    /// Check if the peek token is a specific operator
367    pub(crate) fn peek_token_is_operator(&self, op: &str) -> bool {
368        self.peek_token.token_type == TokenType::Operator && self.peek_token.literal == op
369    }
370
371    /// Check if the peek token can be used as an identifier (true identifier or non-reserved keyword)
372    pub(crate) fn peek_token_is_identifier_like(&self) -> bool {
373        match self.peek_token.token_type {
374            TokenType::Identifier => true,
375            TokenType::Keyword => !Self::is_reserved_keyword(&self.peek_token.literal),
376            _ => false,
377        }
378    }
379
380    /// Expect the peek token to be an identifier (or non-reserved keyword) and advance
381    pub(crate) fn expect_peek_identifier_like(&mut self) -> bool {
382        if self.peek_token_is_identifier_like() {
383            self.next_token();
384            true
385        } else {
386            self.peek_error(TokenType::Identifier);
387            false
388        }
389    }
390
391    /// Expect the peek token to be of a specific type and advance
392    pub(crate) fn expect_peek(&mut self, t: TokenType) -> bool {
393        if self.peek_token_is(t) {
394            self.next_token();
395            true
396        } else {
397            self.peek_error(t);
398            false
399        }
400    }
401
402    /// Expect the peek token to be a specific keyword and advance
403    pub(crate) fn expect_keyword(&mut self, keyword: &str) -> bool {
404        if self.peek_token_is_keyword(keyword) {
405            self.next_token();
406            true
407        } else {
408            self.add_error(format!(
409                "expected {} after {}, got {}",
410                keyword,
411                self.cur_token.literal,
412                Self::format_token_for_error(&self.peek_token)
413            ));
414            false
415        }
416    }
417
418    /// Get the precedence of the peek token
419    pub(crate) fn peek_precedence(&self) -> Precedence {
420        match self.peek_token.token_type {
421            TokenType::Operator => Precedence::for_operator(&self.peek_token.literal),
422            TokenType::Keyword => Precedence::for_operator(&self.peek_token.literal),
423            TokenType::Punctuator => {
424                if self.peek_token.literal == "." {
425                    Precedence::Dot
426                } else if self.peek_token.literal == "(" {
427                    Precedence::Call
428                } else if self.peek_token.literal == "[" {
429                    Precedence::Index
430                } else {
431                    Precedence::Lowest
432                }
433            }
434            _ => Precedence::Lowest,
435        }
436    }
437
438    /// Get the precedence of the current token
439    pub(crate) fn cur_precedence(&self) -> Precedence {
440        match self.cur_token.token_type {
441            TokenType::Operator => Precedence::for_operator(&self.cur_token.literal),
442            TokenType::Keyword => Precedence::for_operator(&self.cur_token.literal),
443            TokenType::Punctuator => {
444                if self.cur_token.literal == "." {
445                    Precedence::Dot
446                } else if self.cur_token.literal == "(" {
447                    Precedence::Call
448                } else if self.cur_token.literal == "[" {
449                    Precedence::Index
450                } else {
451                    Precedence::Lowest
452                }
453            }
454            _ => Precedence::Lowest,
455        }
456    }
457
458    /// Add an error for unexpected peek token type
459    pub(crate) fn peek_error(&mut self, expected: TokenType) {
460        let position = self.peek_token.position;
461        let expected_desc = match expected {
462            TokenType::Identifier => "identifier (name)",
463            TokenType::Keyword => "keyword",
464            TokenType::Punctuator => "'(' or ')'",
465            TokenType::String => "string literal",
466            TokenType::Integer => "integer",
467            TokenType::Float => "number",
468            _ => "token",
469        };
470
471        if self.peek_token.token_type == TokenType::Eof {
472            if !self.current_clause.is_empty() {
473                self.add_error_at(
474                    format!("expected {} after {}", expected_desc, self.current_clause),
475                    position,
476                );
477            } else {
478                self.add_error_at(
479                    format!("unexpected end of input, expected {}", expected_desc),
480                    position,
481                );
482            }
483        } else if expected == TokenType::Identifier
484            && self.peek_token.token_type == TokenType::Keyword
485            && Self::is_reserved_keyword(&self.peek_token.literal)
486        {
487            self.add_error_at(
488                format!(
489                    "'{}' is a reserved keyword and cannot be used as an identifier. \
490                 Use double quotes to escape it: \"{}\"",
491                    self.peek_token.literal.to_uppercase(),
492                    self.peek_token.literal
493                ),
494                position,
495            );
496        } else {
497            self.add_error_at(
498                format!(
499                    "expected {}, got {}",
500                    expected_desc,
501                    Self::format_token_for_error(&self.peek_token)
502                ),
503                position,
504            );
505        }
506    }
507
508    /// Format a token for display in error messages (shows "end of input" for EOF)
509    pub(crate) fn format_token_for_error(token: &Token) -> String {
510        if token.token_type == TokenType::Eof {
511            "end of input".to_string()
512        } else {
513            format!("'{}'", token.literal)
514        }
515    }
516
517    /// Add an error message
518    pub(crate) fn add_error(&mut self, msg: String) {
519        self.add_error_at(msg, self.cur_token.position);
520    }
521
522    pub(crate) fn add_error_at(&mut self, msg: String, position: super::token::Position) {
523        self.errors.push(ParseError::new(msg, position));
524    }
525
526    pub(crate) fn source_range_from(&self, start: Position) -> SourceRange {
527        SourceRange::new(start, self.peek_token.position)
528    }
529
530    pub(crate) fn source_range_through_peek_from(&self, start: Position) -> SourceRange {
531        let mut end = self.peek_token.position;
532        end.offset = end.offset.saturating_add(self.peek_token.literal.len());
533        end.column = end
534            .column
535            .saturating_add(self.peek_token.literal.chars().count());
536        SourceRange::new(start, end)
537    }
538
539    pub(crate) fn normalized_source_for(&self, range: &SourceRange) -> String {
540        let start = range.start.offset.min(self.source.len());
541        let end = range.end.offset.min(self.source.len());
542        self.source[start..end].to_owned()
543    }
544
545    pub(crate) fn enter_expression(&mut self) -> bool {
546        if self.expression_depth >= MAX_EXPRESSION_NESTING {
547            self.add_error(format!(
548                "expression nesting depth exceeds limit of {MAX_EXPRESSION_NESTING}"
549            ));
550            return false;
551        }
552        self.expression_depth += 1;
553        true
554    }
555
556    /// Get collected errors
557    pub fn errors(&self) -> &[ParseError] {
558        &self.errors
559    }
560
561    /// Get the next parameter index
562    pub(crate) fn next_parameter_index(&mut self) -> usize {
563        let idx = self.parameter_counter;
564        self.parameter_counter += 1;
565        idx
566    }
567}
568
569#[cfg(test)]
570mod tests {
571    use super::*;
572
573    #[test]
574    fn test_parser_creation() {
575        let parser = Parser::new("SELECT * FROM users");
576        assert!(parser.cur_token_is_keyword("SELECT"));
577    }
578
579    #[test]
580    fn test_next_token() {
581        let mut parser = Parser::new("SELECT * FROM users");
582        assert!(parser.cur_token_is_keyword("SELECT"));
583        parser.next_token();
584        assert!(parser.cur_token_is(TokenType::Operator));
585        assert_eq!(parser.cur_token.literal, "*");
586    }
587
588    #[test]
589    fn test_peek_token() {
590        let parser = Parser::new("SELECT * FROM users");
591        assert!(parser.cur_token_is_keyword("SELECT"));
592        assert!(parser.peek_token_is_operator("*"));
593    }
594}