Skip to main content

radixdb_sql/statements/
query.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
15use super::*;
16
17/// Keywords that cannot be used as implicit table aliases in FROM clauses.
18/// Shared by SimpleTableSource, FunctionTableSource, and ValuesTableSource parsing.
19const RESERVED_ALIAS_KEYWORDS: &[&str] = &[
20    "JOIN",
21    "LEFT",
22    "RIGHT",
23    "INNER",
24    "OUTER",
25    "CROSS",
26    "FULL",
27    "NATURAL",
28    "ON",
29    "USING",
30    "WHERE",
31    "GROUP",
32    "HAVING",
33    "ORDER",
34    "LIMIT",
35    "OFFSET",
36    "FETCH",
37    "WINDOW",
38    "FOR",
39    "INTO",
40    "UNION",
41    "INTERSECT",
42    "EXCEPT",
43];
44
45/// Check if a token's uppercase literal is a reserved alias keyword.
46fn is_reserved_alias_keyword(upper: &str) -> bool {
47    RESERVED_ALIAS_KEYWORDS
48        .iter()
49        .any(|&kw| kw.eq_ignore_ascii_case(upper))
50}
51
52fn is_table_alias_token(token: &Token) -> bool {
53    matches!(token.token_type, TokenType::Identifier | TokenType::Keyword)
54        && !is_reserved_alias_keyword(&token.literal)
55}
56
57impl Parser {
58    /// Parse a SELECT statement
59    pub fn parse_select_statement(&mut self) -> Option<SelectStatement> {
60        let token = self.cur_token.clone();
61
62        let mut stmt = SelectStatement {
63            token,
64            distinct: false,
65            distinct_on: vec![],
66            columns: Vec::new(),
67            with: None,
68            table_expr: None,
69            where_clause: None,
70            group_by: GroupByClause::default(),
71            having: None,
72            window_defs: Vec::new(),
73            order_by: Vec::new(),
74            limit: None,
75            offset: None,
76            set_operations: Vec::new(),
77        };
78
79        // Check for DISTINCT / DISTINCT ON (expr, ...)
80        if self.peek_token_is_keyword("DISTINCT") {
81            self.next_token();
82            stmt.distinct = true;
83            self.parse_distinct_on(&mut stmt);
84        }
85
86        // Parse column list
87        self.next_token();
88        stmt.columns = self.parse_select_columns();
89
90        // Parse FROM clause
91        if self.peek_token_is_keyword("FROM") {
92            self.next_token(); // consume FROM
93            self.next_token(); // move to table expression
94            stmt.table_expr = Some(Box::new(self.parse_table_expression()?));
95        }
96
97        // Parse WHERE clause
98        if self.peek_token_is_keyword("WHERE") {
99            self.next_token(); // consume WHERE
100            self.current_clause = "WHERE".to_string();
101            self.next_token();
102            stmt.where_clause = Some(Box::new(self.parse_expression(Precedence::Lowest)?));
103        }
104
105        // Parse GROUP BY clause
106        if self.peek_token_is_keyword("GROUP") {
107            self.next_token(); // consume GROUP
108            if !self.expect_keyword("BY") {
109                return None;
110            }
111            self.current_clause = "GROUP BY".to_string();
112            stmt.group_by = self.parse_group_by_clause();
113        }
114
115        // Parse HAVING clause
116        if self.peek_token_is_keyword("HAVING") {
117            self.next_token(); // consume HAVING
118            self.current_clause = "HAVING".to_string();
119            self.next_token();
120            stmt.having = Some(Box::new(self.parse_expression(Precedence::Lowest)?));
121        }
122
123        // Parse WINDOW clause (named window definitions)
124        if self.peek_token_is_keyword("WINDOW") {
125            self.next_token(); // consume WINDOW
126            self.current_clause = "WINDOW".to_string();
127            stmt.window_defs = self.parse_window_definitions();
128        }
129
130        // Parse UNION, INTERSECT, EXCEPT set operations
131        while self.peek_token_is_keyword("UNION")
132            || self.peek_token_is_keyword("INTERSECT")
133            || self.peek_token_is_keyword("EXCEPT")
134        {
135            if let Some(set_op) = self.parse_set_operation() {
136                stmt.set_operations.push(set_op);
137            } else {
138                break;
139            }
140        }
141
142        // Parse ORDER BY clause (applies to entire compound query)
143        if self.peek_token_is_keyword("ORDER") {
144            self.next_token(); // consume ORDER
145            if !self.expect_keyword("BY") {
146                return None;
147            }
148            self.current_clause = "ORDER BY".to_string();
149            stmt.order_by = self.parse_order_by_expressions();
150        }
151
152        // Parse LIMIT clause
153        if self.peek_token_is_keyword("LIMIT") {
154            self.next_token(); // consume LIMIT
155            self.current_clause = "LIMIT".to_string();
156            self.next_token();
157            stmt.limit = Some(Box::new(self.parse_expression(Precedence::Lowest)?));
158        }
159
160        // Parse OFFSET clause
161        if self.peek_token_is_keyword("OFFSET") {
162            self.next_token(); // consume OFFSET
163            self.current_clause = "OFFSET".to_string();
164            self.next_token();
165            stmt.offset = Some(Box::new(self.parse_expression(Precedence::Lowest)?));
166
167            // Optional ROWS/ROW keyword after OFFSET value
168            if self.peek_token_is_keyword("ROWS") || self.peek_token_is_keyword("ROW") {
169                self.next_token();
170            }
171        }
172
173        // Parse FETCH FIRST/NEXT n ROWS ONLY clause (alternative to LIMIT)
174        if self.peek_token_is_keyword("FETCH") {
175            self.next_token(); // consume FETCH
176
177            // FIRST or NEXT (both are equivalent)
178            if !self.peek_token_is_keyword("FIRST") && !self.peek_token_is_keyword("NEXT") {
179                self.add_error(format!(
180                    "expected FIRST or NEXT after FETCH at {}",
181                    self.peek_token.position
182                ));
183                return None;
184            }
185            self.next_token(); // consume FIRST/NEXT
186
187            self.current_clause = "FETCH".to_string();
188            self.next_token();
189            stmt.limit = Some(Box::new(self.parse_expression(Precedence::Lowest)?));
190
191            // Optional ROWS/ROW keyword
192            if self.peek_token_is_keyword("ROWS") || self.peek_token_is_keyword("ROW") {
193                self.next_token();
194            }
195
196            // Optional ONLY keyword
197            if self.peek_token_is_keyword("ONLY") {
198                self.next_token();
199            }
200        }
201
202        self.current_clause.clear();
203        Some(stmt)
204    }
205
206    /// Parse a set operation (UNION, INTERSECT, EXCEPT)
207    /// Handles SQL standard precedence: INTERSECT/EXCEPT bind tighter than UNION
208    pub(super) fn parse_set_operation(&mut self) -> Option<SetOperation> {
209        self.next_token(); // consume UNION/INTERSECT/EXCEPT
210
211        let keyword = self.cur_token.literal.to_uppercase();
212        let operation = if keyword == "UNION" {
213            if self.peek_token_is_keyword("ALL") {
214                self.next_token();
215                SetOperationType::UnionAll
216            } else {
217                SetOperationType::Union
218            }
219        } else if keyword == "INTERSECT" {
220            if self.peek_token_is_keyword("ALL") {
221                self.next_token();
222                SetOperationType::IntersectAll
223            } else {
224                SetOperationType::Intersect
225            }
226        } else if keyword == "EXCEPT" {
227            if self.peek_token_is_keyword("ALL") {
228                self.next_token();
229                SetOperationType::ExceptAll
230            } else {
231                SetOperationType::Except
232            }
233        } else {
234            return None;
235        };
236
237        // Expect SELECT
238        if !self.expect_keyword("SELECT") {
239            return None;
240        }
241
242        // Parse the right side SELECT
243        let mut right = self.parse_simple_select()?;
244
245        // INTERSECT binds more tightly than UNION/EXCEPT. UNION and EXCEPT
246        // have the same precedence and are applied left-to-right by the outer
247        // set-operation loop.
248        if keyword == "UNION" || keyword == "EXCEPT" {
249            while self.peek_token_is_keyword("INTERSECT") {
250                if let Some(set_op) = self.parse_set_operation() {
251                    right.set_operations.push(set_op);
252                } else {
253                    break;
254                }
255            }
256        }
257
258        Some(SetOperation {
259            operation,
260            right: Box::new(right),
261        })
262    }
263
264    /// Parse a simple SELECT (without set operations, used for right side of UNION etc)
265    pub(super) fn parse_simple_select(&mut self) -> Option<SelectStatement> {
266        let token = self.cur_token.clone();
267
268        let mut stmt = SelectStatement {
269            token,
270            distinct: false,
271            distinct_on: vec![],
272            columns: Vec::new(),
273            with: None,
274            table_expr: None,
275            where_clause: None,
276            group_by: GroupByClause::default(),
277            having: None,
278            window_defs: Vec::new(),
279            order_by: Vec::new(),
280            limit: None,
281            offset: None,
282            set_operations: Vec::new(),
283        };
284
285        // Check for DISTINCT / DISTINCT ON (expr, ...)
286        if self.peek_token_is_keyword("DISTINCT") {
287            self.next_token();
288            stmt.distinct = true;
289            self.parse_distinct_on(&mut stmt);
290        }
291
292        // Parse column list
293        self.next_token();
294        stmt.columns = self.parse_select_columns();
295
296        // Parse FROM clause
297        if self.peek_token_is_keyword("FROM") {
298            self.next_token(); // consume FROM
299            self.next_token(); // move to table expression
300            stmt.table_expr = Some(Box::new(self.parse_table_expression()?));
301        }
302
303        // Parse WHERE clause
304        if self.peek_token_is_keyword("WHERE") {
305            self.next_token(); // consume WHERE
306            self.current_clause = "WHERE".to_string();
307            self.next_token();
308            stmt.where_clause = Some(Box::new(self.parse_expression(Precedence::Lowest)?));
309        }
310
311        // Parse GROUP BY clause
312        if self.peek_token_is_keyword("GROUP") {
313            self.next_token(); // consume GROUP
314            if !self.expect_keyword("BY") {
315                return None;
316            }
317            self.current_clause = "GROUP BY".to_string();
318            stmt.group_by = self.parse_group_by_clause();
319        }
320
321        // Parse HAVING clause
322        if self.peek_token_is_keyword("HAVING") {
323            self.next_token(); // consume HAVING
324            self.current_clause = "HAVING".to_string();
325            self.next_token();
326            stmt.having = Some(Box::new(self.parse_expression(Precedence::Lowest)?));
327        }
328
329        self.current_clause.clear();
330        Some(stmt)
331    }
332
333    /// Parse SELECT columns
334    pub(super) fn parse_select_columns(&mut self) -> Vec<Expression> {
335        let mut columns = Vec::new();
336
337        // Parse first column
338        if let Some(col) = self.parse_select_column() {
339            columns.push(col);
340        }
341
342        // Parse additional columns
343        while self.peek_token_is_punctuator(",") {
344            self.next_token(); // consume comma
345            self.next_token(); // move to next column
346            if let Some(col) = self.parse_select_column() {
347                columns.push(col);
348            }
349        }
350
351        columns
352    }
353
354    /// Parse a single SELECT column
355    pub(super) fn parse_select_column(&mut self) -> Option<Expression> {
356        // Check for * (all columns)
357        if self.cur_token_is(TokenType::Operator) && self.cur_token.literal == "*" {
358            return Some(Expression::Star(StarExpression {
359                token: self.cur_token.clone(),
360            }));
361        }
362
363        // Parse expression
364        let expr = self.parse_expression(Precedence::Lowest)?;
365
366        // Check for alias with AS keyword
367        if self.peek_token_is_keyword("AS") {
368            self.next_token(); // consume AS
369                               // Allow both identifiers and keywords as aliases (e.g., AS level, AS type)
370            if !self.peek_token_is(TokenType::Identifier) && !self.peek_token_is(TokenType::Keyword)
371            {
372                self.peek_error(TokenType::Identifier);
373                return None;
374            }
375            self.next_token();
376            return Some(Expression::Aliased(AliasedExpression {
377                token: self.cur_token.clone(),
378                expression: Box::new(expr),
379                alias: self.cur_token_as_column_identifier(),
380            }));
381        }
382
383        // Check for implicit alias (identifier without AS)
384        // Must be an identifier that's not a reserved keyword like FROM, WHERE, etc.
385        if self.peek_token_is(TokenType::Identifier) {
386            let alias_candidate = self.peek_token.literal.to_uppercase();
387            // List of keywords that cannot be implicit aliases (they end the column list or start clauses)
388            let reserved = [
389                "FROM",
390                "WHERE",
391                "GROUP",
392                "HAVING",
393                "ORDER",
394                "LIMIT",
395                "OFFSET",
396                "UNION",
397                "INTERSECT",
398                "EXCEPT",
399                "INTO",
400                "FOR",
401                "WINDOW",
402                "FETCH",
403                "ON",
404                "USING",
405                "NATURAL",
406                "LEFT",
407                "RIGHT",
408                "INNER",
409                "OUTER",
410                "CROSS",
411                "FULL",
412                "JOIN",
413            ];
414            if !reserved.contains(&alias_candidate.as_str()) {
415                self.next_token();
416                return Some(Expression::Aliased(AliasedExpression {
417                    token: self.cur_token.clone(),
418                    expression: Box::new(expr),
419                    alias: Identifier::new(self.cur_token.clone(), self.cur_token.literal.clone()),
420                }));
421            }
422        }
423
424        Some(expr)
425    }
426
427    /// Parse a table expression (for FROM clause)
428    pub(super) fn parse_table_expression(&mut self) -> Option<Expression> {
429        let left = self.parse_simple_table_expression()?;
430        self.parse_join_table_expression(left)
431    }
432
433    /// Parse a simple table expression (table name, subquery, VALUES, or CTE reference)
434    pub(super) fn parse_simple_table_expression(&mut self) -> Option<Expression> {
435        // Check for subquery or VALUES
436        if self.cur_token_is_punctuator("(") {
437            self.next_token();
438            if self.cur_token_is_keyword("SELECT") {
439                let subquery = self.parse_select_statement()?;
440
441                if !self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != ")" {
442                    self.add_error(format!(
443                        "expected ')' after subquery at {}",
444                        self.cur_token.position
445                    ));
446                    return None;
447                }
448
449                let mut alias = None;
450                if self.peek_token_is_keyword("AS") {
451                    self.next_token();
452                    if !is_table_alias_token(&self.peek_token) {
453                        self.add_error(format!(
454                            "expected alias after AS at {}",
455                            self.peek_token.position
456                        ));
457                        return None;
458                    }
459                    self.next_token();
460                    alias = Some(Identifier::new(
461                        self.cur_token.clone(),
462                        self.cur_token.literal.clone(),
463                    ));
464                } else if is_table_alias_token(&self.peek_token) {
465                    self.next_token();
466                    alias = Some(Identifier::new(
467                        self.cur_token.clone(),
468                        self.cur_token.literal.clone(),
469                    ));
470                }
471
472                return Some(Expression::SubquerySource(Box::new(SubqueryTableSource {
473                    token: self.cur_token.clone(),
474                    subquery: Box::new(subquery),
475                    alias,
476                })));
477            } else if self.cur_token_is_keyword("VALUES") {
478                // Parse VALUES clause as table source
479                return self.parse_values_table_source();
480            }
481        }
482
483        // Parse table name - accept both identifiers and keywords (for CTE references like 'first')
484        if !self.cur_token_is(TokenType::Identifier) && !self.cur_token_is(TokenType::Keyword) {
485            self.add_error(format!(
486                "expected table name at {}",
487                self.cur_token.position
488            ));
489            return None;
490        }
491
492        let token = self.cur_token.clone();
493
494        // Check for table-valued function: identifier followed by '('
495        if self.peek_token_is_punctuator("(") {
496            let name = Identifier::new(token.clone(), self.cur_token.literal.clone());
497            return self.parse_function_table_source(token, name);
498        }
499
500        let name = self.parse_relation_identifier_current()?;
501
502        // Check for AS OF clause (temporal queries)
503        let as_of = if self.peek_token_is_keyword("AS") {
504            self.next_token(); // consume AS
505            if self.peek_token_is_keyword("OF") {
506                self.next_token(); // consume OF
507                self.next_token(); // move to TRANSACTION or TIMESTAMP
508
509                let as_of_type = self.cur_token.literal.to_uppercase();
510                if as_of_type != "TRANSACTION" && as_of_type != "TIMESTAMP" {
511                    self.add_error(format!(
512                        "expected TRANSACTION or TIMESTAMP after AS OF at {}",
513                        self.cur_token.position
514                    ));
515                    return None;
516                }
517
518                self.next_token();
519                let value = self.parse_expression(Precedence::Lowest)?;
520
521                Some(AsOfClause {
522                    token: self.cur_token.clone(),
523                    as_of_type,
524                    value: Box::new(value),
525                })
526            } else {
527                // This is an alias starting with AS
528                None
529            }
530        } else {
531            None
532        };
533
534        // Check for alias (can occur after AS OF or after table name)
535        let mut alias = None;
536        if self.peek_token_is_keyword("AS") {
537            self.next_token(); // consume AS
538            if !is_table_alias_token(&self.peek_token) {
539                self.add_error(format!(
540                    "expected alias after AS at {}",
541                    self.peek_token.position
542                ));
543                return None;
544            }
545            self.next_token();
546            alias = Some(Identifier::new(
547                self.cur_token.clone(),
548                self.cur_token.literal.clone(),
549            ));
550        } else if is_table_alias_token(&self.peek_token) {
551            self.next_token();
552            alias = Some(Identifier::new(
553                self.cur_token.clone(),
554                self.cur_token.literal.clone(),
555            ));
556        }
557
558        Some(Expression::TableSource(Box::new(SimpleTableSource {
559            token,
560            name,
561            alias,
562            as_of,
563        })))
564    }
565
566    /// Parse a JOIN table expression
567    pub(super) fn parse_join_table_expression(
568        &mut self,
569        mut left: Expression,
570    ) -> Option<Expression> {
571        loop {
572            // Check for JOIN keywords
573            let join_type = if self.peek_token_is_keyword("JOIN") {
574                self.next_token();
575                SmartString::const_new("INNER")
576            } else if self.peek_token_is_keyword("INNER") {
577                self.next_token();
578                if !self.expect_keyword("JOIN") {
579                    return None;
580                }
581                SmartString::const_new("INNER")
582            } else if self.peek_token_is_keyword("LEFT") {
583                self.next_token();
584                if self.peek_token_is_keyword("OUTER") {
585                    self.next_token();
586                }
587                if !self.expect_keyword("JOIN") {
588                    return None;
589                }
590                SmartString::const_new("LEFT")
591            } else if self.peek_token_is_keyword("RIGHT") {
592                self.next_token();
593                if self.peek_token_is_keyword("OUTER") {
594                    self.next_token();
595                }
596                if !self.expect_keyword("JOIN") {
597                    return None;
598                }
599                SmartString::const_new("RIGHT")
600            } else if self.peek_token_is_keyword("FULL") {
601                self.next_token();
602                if self.peek_token_is_keyword("OUTER") {
603                    self.next_token();
604                }
605                if !self.expect_keyword("JOIN") {
606                    return None;
607                }
608                SmartString::const_new("FULL")
609            } else if self.peek_token_is_keyword("CROSS") {
610                self.next_token();
611                if !self.expect_keyword("JOIN") {
612                    return None;
613                }
614                SmartString::const_new("CROSS")
615            } else if self.peek_token_is_keyword("NATURAL") {
616                self.next_token();
617                let natural_type = if self.peek_token_is_keyword("LEFT") {
618                    self.next_token();
619                    if self.peek_token_is_keyword("OUTER") {
620                        self.next_token();
621                    }
622                    "NATURAL LEFT"
623                } else if self.peek_token_is_keyword("RIGHT") {
624                    self.next_token();
625                    if self.peek_token_is_keyword("OUTER") {
626                        self.next_token();
627                    }
628                    "NATURAL RIGHT"
629                } else {
630                    "NATURAL"
631                };
632                if !self.expect_keyword("JOIN") {
633                    return None;
634                }
635                SmartString::const_new(natural_type)
636            } else if self.peek_token_is_punctuator(",") {
637                // Implicit CROSS JOIN with comma syntax: FROM t1, t2
638                self.next_token(); // consume comma
639                SmartString::const_new("CROSS")
640            } else {
641                // No more joins
642                break;
643            };
644
645            let token = self.cur_token.clone();
646            self.next_token();
647            let right = self.parse_simple_table_expression()?;
648
649            // Parse ON or USING clause (not for CROSS JOIN or NATURAL JOIN)
650            let mut condition = None;
651            let mut using_columns = Vec::new();
652
653            if !join_type.starts_with("CROSS") && !join_type.starts_with("NATURAL") {
654                if self.peek_token_is_keyword("ON") {
655                    self.next_token(); // consume ON
656                    self.next_token();
657                    condition = Some(Box::new(self.parse_expression(Precedence::Lowest)?));
658                } else if self.peek_token_is_keyword("USING") {
659                    self.next_token(); // consume USING
660                    if !self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != "(" {
661                        self.add_error(format!(
662                            "expected '(' after USING at {}",
663                            self.cur_token.position
664                        ));
665                        return None;
666                    }
667                    using_columns = self.parse_identifier_list();
668                    if !self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != ")" {
669                        self.add_error(format!(
670                            "expected ')' after USING columns at {}",
671                            self.cur_token.position
672                        ));
673                        return None;
674                    }
675                } else {
676                    self.add_error(format!("{} JOIN requires an ON or USING clause", join_type));
677                    return None;
678                }
679            }
680
681            left = Expression::JoinSource(Box::new(JoinTableSource {
682                token,
683                left: Box::new(left),
684                join_type,
685                right: Box::new(right),
686                condition,
687                using_columns,
688            }));
689        }
690
691        Some(left)
692    }
693
694    /// Parse a WITH statement (CTE)
695    pub(super) fn parse_with_statement(&mut self) -> Option<Statement> {
696        let with_clause = self.parse_with_clause()?;
697
698        // After WITH, expect SELECT or INSERT
699        self.next_token();
700        if self.cur_token_is_keyword("SELECT") {
701            let mut select = self.parse_select_statement()?;
702            select.with = Some(with_clause);
703            Some(Statement::Select(select))
704        } else if self.cur_token_is_keyword("INSERT") {
705            // WITH ... INSERT INTO ... SELECT
706            let mut insert = self.parse_insert_statement()?;
707            // The INSERT must use SELECT (not VALUES) for CTE to make sense
708            if let Some(ref mut select) = insert.select {
709                select.with = Some(with_clause);
710            } else {
711                self.add_error(
712                    "WITH clause requires INSERT ... SELECT, not INSERT ... VALUES".to_string(),
713                );
714                return None;
715            }
716            Some(Statement::Insert(insert))
717        } else {
718            self.add_error(format!(
719                "expected SELECT or INSERT after WITH clause at {}",
720                self.cur_token.position
721            ));
722            None
723        }
724    }
725
726    /// Parse a WITH clause
727    pub(super) fn parse_with_clause(&mut self) -> Option<WithClause> {
728        let token = self.cur_token.clone();
729        let mut is_recursive = false;
730
731        // Check for RECURSIVE
732        if self.peek_token_is_keyword("RECURSIVE") {
733            self.next_token();
734            is_recursive = true;
735        }
736
737        let mut ctes = Vec::new();
738
739        // Parse first CTE
740        self.next_token();
741        if let Some(cte) = self.parse_common_table_expression(is_recursive) {
742            ctes.push(cte);
743        }
744
745        // Parse additional CTEs
746        while self.peek_token_is_punctuator(",") {
747            self.next_token(); // consume comma
748            self.next_token(); // move to CTE name
749            if let Some(cte) = self.parse_common_table_expression(is_recursive) {
750                ctes.push(cte);
751            }
752        }
753
754        Some(WithClause {
755            token,
756            ctes,
757            is_recursive,
758        })
759    }
760
761    /// Parse a Common Table Expression
762    pub(super) fn parse_common_table_expression(
763        &mut self,
764        is_recursive: bool,
765    ) -> Option<CommonTableExpression> {
766        // Accept both identifiers and keywords as CTE names (context-dependent identifiers)
767        // Keywords like FIRST, LAST, VALUE, etc. are valid CTE names in SQL
768        if !self.cur_token_is(TokenType::Identifier) && !self.cur_token_is(TokenType::Keyword) {
769            self.add_error(format!("expected CTE name at {}", self.cur_token.position));
770            return None;
771        }
772
773        let token = self.cur_token.clone();
774        let name = Identifier::new(token.clone(), self.cur_token.literal.clone());
775
776        // Optional column list
777        let mut column_names = Vec::new();
778        if self.peek_token_is_punctuator("(") {
779            self.next_token(); // consume (
780            column_names = self.parse_identifier_list();
781            if !self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != ")" {
782                self.add_error(format!(
783                    "expected ')' after column list at {}",
784                    self.cur_token.position
785                ));
786                return None;
787            }
788        }
789
790        // Expect AS
791        if !self.expect_keyword("AS") {
792            return None;
793        }
794
795        // Expect (
796        if !self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != "(" {
797            self.add_error(format!(
798                "expected '(' after AS at {}",
799                self.cur_token.position
800            ));
801            return None;
802        }
803
804        // Parse the CTE query
805        self.next_token();
806        if !self.cur_token_is_keyword("SELECT") {
807            self.add_error(format!(
808                "expected SELECT in CTE at {}",
809                self.cur_token.position
810            ));
811            return None;
812        }
813
814        let query = self.parse_select_statement()?;
815
816        // Expect )
817        if !self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != ")" {
818            self.add_error(format!(
819                "expected ')' after CTE query at {}",
820                self.cur_token.position
821            ));
822            return None;
823        }
824
825        Some(CommonTableExpression {
826            token,
827            name,
828            column_names,
829            query: Box::new(query),
830            is_recursive,
831        })
832    }
833
834    /// Parse DISTINCT ON (expr1, expr2, ...) if present after DISTINCT keyword.
835    pub(super) fn parse_distinct_on(&mut self, stmt: &mut SelectStatement) {
836        if self.peek_token_is_keyword("ON") {
837            self.next_token(); // consume ON
838            if self.peek_token_is_punctuator("(") {
839                self.next_token(); // consume (
840                stmt.distinct_on = self.parse_distinct_on_columns();
841                if !self.peek_token_is_punctuator(")") {
842                    self.add_error(format!(
843                        "expected ')' after DISTINCT ON columns at {}",
844                        self.peek_token.position
845                    ));
846                    return;
847                }
848                self.next_token(); // consume )
849            } else {
850                self.add_error(format!(
851                    "expected '(' after DISTINCT ON at {}",
852                    self.peek_token.position
853                ));
854            }
855        }
856    }
857
858    /// Parse comma-separated expression list inside DISTINCT ON (...)
859    pub(super) fn parse_distinct_on_columns(&mut self) -> Vec<Expression> {
860        let mut exprs = Vec::new();
861        self.next_token();
862        if let Some(expr) = self.parse_expression(Precedence::Lowest) {
863            exprs.push(expr);
864        }
865        while self.peek_token_is_punctuator(",") {
866            self.next_token(); // consume comma
867            self.next_token(); // move to next expression
868            if let Some(expr) = self.parse_expression(Precedence::Lowest) {
869                exprs.push(expr);
870            }
871        }
872        exprs
873    }
874
875    /// Parse VALUES clause as a table source (e.g., (VALUES (1, 'a'), (2, 'b')) AS t(col1, col2))
876    pub(super) fn parse_values_table_source(&mut self) -> Option<Expression> {
877        let token = self.cur_token.clone(); // VALUES token
878
879        // Parse value lists - we're already on VALUES, call parse_value_lists which expects (
880        let rows = self.parse_value_lists()?;
881
882        // Expect ) to close the outer parenthesis
883        if !self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != ")" {
884            self.add_error(format!(
885                "expected ')' after VALUES at {}",
886                self.cur_token.position
887            ));
888            return None;
889        }
890
891        // Parse optional alias
892        let mut alias = None;
893        let mut column_aliases = Vec::new();
894
895        if self.peek_token_is_keyword("AS") {
896            self.next_token(); // consume AS
897            if !is_table_alias_token(&self.peek_token) {
898                self.add_error(format!(
899                    "expected alias after AS at {}",
900                    self.peek_token.position
901                ));
902                return None;
903            }
904            self.next_token();
905            alias = Some(Identifier::new(
906                self.cur_token.clone(),
907                self.cur_token.literal.clone(),
908            ));
909
910            // Parse optional column aliases: AS t(col1, col2)
911            if self.peek_token_is_punctuator("(") {
912                self.next_token(); // consume (
913                column_aliases = self.parse_identifier_list();
914                if !self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != ")" {
915                    self.add_error(format!(
916                        "expected ')' after column aliases at {}",
917                        self.cur_token.position
918                    ));
919                    return None;
920                }
921            }
922        } else if is_table_alias_token(&self.peek_token) {
923            // Implicit alias without AS
924            self.next_token();
925            alias = Some(Identifier::new(
926                self.cur_token.clone(),
927                self.cur_token.literal.clone(),
928            ));
929
930            // Parse optional column aliases
931            if self.peek_token_is_punctuator("(") {
932                self.next_token(); // consume (
933                column_aliases = self.parse_identifier_list();
934                if !self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != ")" {
935                    self.add_error(format!(
936                        "expected ')' after column aliases at {}",
937                        self.cur_token.position
938                    ));
939                    return None;
940                }
941            }
942        }
943
944        Some(Expression::ValuesSource(Box::new(ValuesTableSource {
945            token,
946            rows,
947            alias,
948            column_aliases,
949        })))
950    }
951
952    /// Parse a function table source (table-valued function in FROM clause)
953    /// e.g., generate_series(1, 10) AS gs(value)
954    pub(super) fn parse_function_table_source(
955        &mut self,
956        token: Token,
957        name: Identifier,
958    ) -> Option<Expression> {
959        self.next_token(); // consume '('
960        self.next_token(); // advance to first arg or ')'
961
962        let mut arguments = Vec::new();
963
964        // Parse arguments (comma-separated expressions)
965        if !self.cur_token_is_punctuator(")") {
966            if let Some(arg) = self.parse_expression(Precedence::Lowest) {
967                arguments.push(arg);
968            }
969            while self.peek_token_is_punctuator(",") {
970                self.next_token(); // consume ','
971                self.next_token(); // move to next arg
972                if let Some(arg) = self.parse_expression(Precedence::Lowest) {
973                    arguments.push(arg);
974                } else {
975                    self.add_error(format!(
976                        "expected expression after ',' at {}",
977                        self.cur_token.position
978                    ));
979                    return None;
980                }
981            }
982        }
983
984        // Expect closing ')' - if cur_token is already ')' (zero-arg case), we're done;
985        // otherwise it should be the peek token
986        if !self.cur_token_is_punctuator(")")
987            && (!self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != ")")
988        {
989            self.add_error(format!(
990                "expected ')' after function arguments at {}",
991                self.cur_token.position
992            ));
993            return None;
994        }
995
996        // Parse optional alias and column aliases (same pattern as VALUES table source)
997        let mut alias = None;
998        let mut column_aliases = Vec::new();
999
1000        if self.peek_token_is_keyword("AS") {
1001            self.next_token(); // consume AS
1002            if !is_table_alias_token(&self.peek_token) {
1003                self.add_error(format!(
1004                    "expected alias after AS at {}",
1005                    self.peek_token.position
1006                ));
1007                return None;
1008            }
1009            self.next_token();
1010            alias = Some(Identifier::new(
1011                self.cur_token.clone(),
1012                self.cur_token.literal.clone(),
1013            ));
1014
1015            // Parse optional column aliases: AS gs(value)
1016            if self.peek_token_is_punctuator("(") {
1017                self.next_token(); // consume (
1018                column_aliases = self.parse_identifier_list();
1019                if !self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != ")" {
1020                    self.add_error(format!(
1021                        "expected ')' after column aliases at {}",
1022                        self.cur_token.position
1023                    ));
1024                    return None;
1025                }
1026            }
1027        } else if is_table_alias_token(&self.peek_token) {
1028            // Implicit alias without AS keyword
1029            self.next_token();
1030            alias = Some(Identifier::new(
1031                self.cur_token.clone(),
1032                self.cur_token.literal.clone(),
1033            ));
1034
1035            // Parse optional column aliases
1036            if self.peek_token_is_punctuator("(") {
1037                self.next_token(); // consume (
1038                column_aliases = self.parse_identifier_list();
1039                if !self.expect_peek(TokenType::Punctuator) || self.cur_token.literal != ")" {
1040                    self.add_error(format!(
1041                        "expected ')' after column aliases at {}",
1042                        self.cur_token.position
1043                    ));
1044                    return None;
1045                }
1046            }
1047        }
1048
1049        Some(Expression::FunctionTableSource(Box::new(
1050            FunctionTableSource {
1051                token,
1052                function: name,
1053                arguments,
1054                alias,
1055                column_aliases,
1056            },
1057        )))
1058    }
1059}