Skip to main content

sqlglot_rust/parser/
sql_parser.rs

1use crate::ast::*;
2use crate::errors::{Result, SqlglotError};
3use crate::tokens::{Token, TokenType, Tokenizer};
4
5/// Convert a token's `quote_char` into a `QuoteStyle`.
6fn quote_style_from_char(c: char) -> QuoteStyle {
7    match c {
8        '"' => QuoteStyle::DoubleQuote,
9        '`' => QuoteStyle::Backtick,
10        '[' => QuoteStyle::Bracket,
11        _ => QuoteStyle::None,
12    }
13}
14
15/// A recursive-descent SQL parser.
16///
17/// Supports CTEs (WITH), subqueries, UNION/INTERSECT/EXCEPT, CAST,
18/// window functions (OVER), EXISTS, EXTRACT, INTERVAL, and more.
19pub struct Parser {
20    tokens: Vec<Token>,
21    pos: usize,
22    /// Whether to preserve comments during parsing.
23    #[allow(dead_code)]
24    preserve_comments: bool,
25    /// Accumulated comments pending attachment to the next AST node.
26    pending_comments: Vec<String>,
27}
28
29impl Parser {
30    /// Create a new parser from a SQL string.
31    pub fn new(sql: &str) -> Result<Self> {
32        let mut tokenizer = Tokenizer::new(sql);
33        let tokens = tokenizer.tokenize()?;
34        Ok(Self {
35            tokens,
36            pos: 0,
37            preserve_comments: false,
38            pending_comments: Vec::new(),
39        })
40    }
41
42    /// Create a new parser that preserves SQL comments in the AST.
43    pub fn new_with_comments(sql: &str) -> Result<Self> {
44        let mut tokenizer = Tokenizer::with_comments(sql);
45        let tokens = tokenizer.tokenize()?;
46        Ok(Self {
47            tokens,
48            pos: 0,
49            preserve_comments: true,
50            pending_comments: Vec::new(),
51        })
52    }
53
54    // ── Comment helpers ────────────────────────────────────────────
55
56    /// Consume any comment tokens at the current position, accumulating
57    /// their text into `pending_comments`.
58    fn collect_comments(&mut self) {
59        while self.pos < self.tokens.len() {
60            match self.tokens[self.pos].token_type {
61                TokenType::LineComment | TokenType::BlockComment => {
62                    let token = &self.tokens[self.pos];
63                    self.pending_comments.push(token.value.clone());
64                    self.pos += 1;
65                }
66                _ => break,
67            }
68        }
69    }
70
71    /// Take all pending comments, leaving the buffer empty.
72    fn take_comments(&mut self) -> Vec<String> {
73        std::mem::take(&mut self.pending_comments)
74    }
75
76    // ── Token helpers ──────────────────────────────────────────────
77
78    fn peek(&self) -> &Token {
79        &self.tokens[self.pos.min(self.tokens.len() - 1)]
80    }
81
82    fn peek_type(&self) -> &TokenType {
83        &self.peek().token_type
84    }
85
86    fn advance(&mut self) -> &Token {
87        let token = &self.tokens[self.pos.min(self.tokens.len() - 1)];
88        if self.pos < self.tokens.len() {
89            self.pos += 1;
90        }
91        token
92    }
93
94    fn expect(&mut self, expected: TokenType) -> Result<Token> {
95        let token = self.peek().clone();
96        if token.token_type == expected {
97            self.advance();
98            Ok(token)
99        } else {
100            Err(SqlglotError::ParserError {
101                message: format!(
102                    "Expected {expected:?}, got {:?} ('{}') at line {} col {}",
103                    token.token_type, token.value, token.line, token.col
104                ),
105            })
106        }
107    }
108
109    fn match_token(&mut self, expected: TokenType) -> bool {
110        if self.peek().token_type == expected {
111            self.advance();
112            true
113        } else {
114            false
115        }
116    }
117
118    /// Check if the current token's uppercased value matches a keyword string.
119    fn check_keyword(&self, keyword: &str) -> bool {
120        self.peek().value.to_uppercase() == keyword
121    }
122
123    /// Check if the token at `current + offset` matches a keyword string.
124    fn check_keyword_offset(&self, keyword: &str, offset: usize) -> bool {
125        let idx = self.pos + offset;
126        if idx < self.tokens.len() {
127            self.tokens[idx].value.to_uppercase() == keyword
128        } else {
129            false
130        }
131    }
132
133    /// Match a keyword by string value (for multi-word context-sensitive keywords).
134    fn match_keyword(&mut self, keyword: &str) -> bool {
135        if self.check_keyword(keyword) {
136            self.advance();
137            true
138        } else {
139            false
140        }
141    }
142
143    /// Expect a keyword by string value, returning an error if not found.
144    fn expect_keyword(&mut self, keyword: &str) -> Result<()> {
145        if self.check_keyword(keyword) {
146            self.advance();
147            Ok(())
148        } else {
149            let token = self.peek().clone();
150            Err(SqlglotError::ParserError {
151                message: format!(
152                    "Expected keyword '{keyword}', got '{value}' at line {line} col {col}",
153                    value = token.value,
154                    line = token.line,
155                    col = token.col
156                ),
157            })
158        }
159    }
160
161    /// Reconstruct a single token's surface representation for raw command
162    /// preservation. String literals are wrapped in their original quotes;
163    /// identifiers may carry a quote_char from the tokenizer.
164    fn token_text(token: &Token) -> String {
165        match token.token_type {
166            TokenType::String => format!("'{}'", token.value.replace('\'', "''")),
167            TokenType::Identifier if token.quote_char != '\0' => {
168                let (l, r) = match token.quote_char {
169                    '[' => ('[', ']'),
170                    c => (c, c),
171                };
172                format!("{l}{}{r}", token.value)
173            }
174            _ => token.value.clone(),
175        }
176    }
177
178    /// Join a slice of tokens with whitespace tuned for SQL — no space
179    /// before `,` `)` `;` `.`, no space after `(` or `.`.
180    fn join_tokens_for_raw(tokens: &[Token]) -> String {
181        let mut out = String::new();
182        let mut prev_no_space_after = true; // suppress leading space
183        for t in tokens {
184            let no_space_before = matches!(
185                t.token_type,
186                TokenType::Comma
187                    | TokenType::RParen
188                    | TokenType::Semicolon
189                    | TokenType::Dot
190                    | TokenType::RBracket
191            );
192            if !out.is_empty() && !prev_no_space_after && !no_space_before {
193                out.push(' ');
194            }
195            out.push_str(&Self::token_text(t));
196            prev_no_space_after = matches!(
197                t.token_type,
198                TokenType::LParen | TokenType::Dot | TokenType::LBracket
199            );
200        }
201        out
202    }
203
204    /// Consume tokens up to (but not including) the next top-level `;` or EOF,
205    /// returning the raw text of the consumed tokens with whitespace
206    /// reconstructed by [`join_tokens_for_raw`]. Honors parenthesis depth so
207    /// embedded `;` inside `(...)` does not terminate the statement.
208    fn consume_raw_to_statement_end(&mut self) -> String {
209        let start = self.pos;
210        let mut depth: i32 = 0;
211        while self.pos < self.tokens.len() {
212            let tt = &self.tokens[self.pos].token_type;
213            match tt {
214                TokenType::Eof => break,
215                TokenType::Semicolon if depth == 0 => break,
216                TokenType::LParen | TokenType::LBracket => {
217                    depth += 1;
218                    self.pos += 1;
219                }
220                TokenType::RParen | TokenType::RBracket => {
221                    // A closing paren at depth 0 belongs to an enclosing
222                    // context (e.g. CTE body, subquery) — stop without
223                    // consuming it.
224                    if depth == 0 {
225                        break;
226                    }
227                    depth -= 1;
228                    self.pos += 1;
229                }
230                _ => self.pos += 1,
231            }
232        }
233        Self::join_tokens_for_raw(&self.tokens[start..self.pos])
234    }
235
236    /// Parse a comma-separated list of raw items inside an already-opened
237    /// parenthesized context. Stops at the matching `)` and returns each item
238    /// reconstructed from tokens.
239    fn parse_parenthesized_raw_items(&mut self) -> Result<Vec<String>> {
240        let mut items = Vec::new();
241
242        // Allow empty parens for tolerance.
243        if self.match_token(TokenType::RParen) {
244            return Ok(items);
245        }
246
247        loop {
248            let start = self.pos;
249            let mut paren_depth: i32 = 0;
250            let mut bracket_depth: i32 = 0;
251
252            while self.pos < self.tokens.len() {
253                match self.peek_type() {
254                    TokenType::Eof => break,
255                    TokenType::LParen => {
256                        paren_depth += 1;
257                        self.pos += 1;
258                    }
259                    TokenType::RParen => {
260                        if paren_depth == 0 && bracket_depth == 0 {
261                            break;
262                        }
263                        if paren_depth > 0 {
264                            paren_depth -= 1;
265                        }
266                        self.pos += 1;
267                    }
268                    TokenType::LBracket => {
269                        bracket_depth += 1;
270                        self.pos += 1;
271                    }
272                    TokenType::RBracket => {
273                        if bracket_depth > 0 {
274                            bracket_depth -= 1;
275                        }
276                        self.pos += 1;
277                    }
278                    TokenType::Comma if paren_depth == 0 && bracket_depth == 0 => break,
279                    _ => self.pos += 1,
280                }
281            }
282
283            if start == self.pos {
284                let token = self.peek().clone();
285                return Err(SqlglotError::ParserError {
286                    message: format!(
287                        "Expected expression inside parenthesized list, got '{}' at line {} col {}",
288                        token.value, token.line, token.col
289                    ),
290                });
291            }
292
293            items.push(Self::join_tokens_for_raw(&self.tokens[start..self.pos]));
294
295            if self.match_token(TokenType::Comma) {
296                continue;
297            }
298
299            self.expect(TokenType::RParen)?;
300            break;
301        }
302
303        Ok(items)
304    }
305
306    /// Helper for the dispatcher: consume one verb token (already known) and
307    /// then capture the entire tail as a [`CommandStatement`].
308    fn parse_command_kind(&mut self, kind: &str) -> Result<Statement> {
309        self.advance(); // consume the verb token
310        let body = self.consume_raw_to_statement_end();
311        Ok(Statement::Command(CommandStatement {
312            comments: vec![],
313            kind: kind.to_string(),
314            body,
315        }))
316    }
317
318    /// `COMMENT ON {TABLE|COLUMN|...} <name> IS '...'` — preserved as raw.
319    /// `COMMENT` can also appear inside `CREATE TABLE` column definitions and
320    /// in other positions; only the standalone DDL form lands here because
321    /// the dispatcher peeks at the *first* token.
322    fn parse_comment_on_command(&mut self) -> Result<Statement> {
323        // Look ahead for "COMMENT ON" — if not "ON", fall back to parser error
324        // (the COMMENT token would otherwise have been consumed inside an
325        // expression / column-def parser, not at statement boundary).
326        if self.peek_offset(1).map(|t| t.value.to_uppercase()) != Some("ON".to_string()) {
327            return Err(SqlglotError::UnexpectedToken {
328                token: self.peek().clone(),
329            });
330        }
331        self.advance(); // COMMENT
332        let body = self.consume_raw_to_statement_end();
333        Ok(Statement::Command(CommandStatement {
334            comments: vec![],
335            kind: "COMMENT".to_string(),
336            body,
337        }))
338    }
339
340    /// Returns `true` when the current Identifier token is a known
341    /// statement-starting verb that we preserve verbatim.
342    fn match_command_keyword(&self) -> bool {
343        let v = self.peek().value.to_uppercase();
344        matches!(
345            v.as_str(),
346            "GO" | "DECLARE"
347                | "LOAD"
348                | "REM"
349                | "REMARK"
350                | "RESET"
351                | "PRAGMA"
352                | "VACUUM"
353                | "REINDEX"
354                | "CALL"
355                | "LOCK"
356                | "UNLOCK"
357                | "CLUSTER"
358                | "REFRESH"
359                | "CHECKPOINT"
360                | "LISTEN"
361                | "NOTIFY"
362                | "PREPARE"
363                | "EXECUTE"
364                | "DEALLOCATE"
365                | "DISCARD"
366                | "COPY"
367                | "ATTACH"
368                | "DETACH"
369                | "COMMENT"
370                | "DESCRIBE"
371                | "DESC"
372                | "OPTIMIZE"
373                | "SYSTEM"
374                | "KILL"
375                | "FLUSH"
376                | "RESTORE"
377                | "BACKUP"
378                | "EXCHANGE"
379                | "RENAME"
380                | "WATCH"
381                | "MSCK"
382                | "UNLOAD"
383                | "ASSERT"
384                | "REPAIR"
385                | "PURGE"
386                | "ABORT"
387                | "VALIDATE"
388                | "MOVE"
389                | "CLOSE"
390                | "FETCH"
391                | "REPLICATE"
392                | "START"
393                | "RAISE"
394                | "UNDROP"
395                | "EXCEPTION"
396                | "CONNECT"
397                | "DISCONNECT"
398                | "SEND"
399                | "ENABLE"
400                | "DISABLE"
401                | "REPLAY"
402                | "SYNCHRONIZE"
403                | "CHECK"
404                | "REPORT"
405                | "BIND"
406                | "UNBIND"
407                | "INCLUDE"
408                | "EXPORT"
409                | "IMPORT"
410                | "ADMIN"
411                | "SPLIT"
412                | "TRACE"
413                | "RESUME"
414                | "SUSPEND"
415                | "ROUTE"
416                | "EMIT"
417                | "FOR"
418                | "WHILE"
419                | "LOOP"
420                | "RETURN"
421                | "REPEAT"
422                | "EXIT"
423                | "LEAVE"
424                | "ITERATE"
425                | "CONTINUE"
426                | "GOTO"
427                | "RAISERROR"
428                | "PRINT"
429                | "WAITFOR"
430                | "TRUNCATE"
431                | "DO"
432                | "CONNECTION"
433                | "ELSEIF"
434                | "ELSIF"
435                | "UNTIL"
436                | "CONNECT_BY_ROOT"
437                | "APPLY"
438                | "EXEC"
439                | "OPEN"
440                | "REVERT"
441                | "DEALLOC"
442                | "GRANT"
443                | "REVOKE"
444                | "DENY"
445                | "UNSET"
446                | "USE"
447                | "PRELOAD"
448                | "RECOMPRESS"
449                | "COMPUTE"
450                | "INVALIDATE"
451                | "ANALYSE"
452                | "BOOTSTRAP"
453                | "LATCH"
454                | "UNLATCH"
455                | "SETOF"
456                | "CHECKSUM"
457                | "DELIMITER"
458                | "GET"
459                | "HELP"
460                | "BINLOG"
461                | "RELOAD"
462                | "PARSE"
463                | "BUFFER"
464                | "BUILDS"
465                | "COMPACT"
466                | "FREEZE"
467                | "UNFREEZE"
468                | "BORROW"
469                | "UNLISTEN"
470                | "REPACK"
471                | "RESIGNAL"
472                | "SIGNAL"
473                | "THROW"
474                | "DBCC"
475                | "SUMMARIZE"
476                | "BATCH"
477        )
478    }
479
480    /// Variant of [`parse_command_kind`] for verbs that arrive as an
481    /// Identifier token (no dedicated TokenType).
482    fn parse_command_from_identifier(&mut self) -> Result<Statement> {
483        let verb = self.peek().value.to_uppercase();
484        self.advance();
485        let body = self.consume_raw_to_statement_end();
486        Ok(Statement::Command(CommandStatement {
487            comments: vec![],
488            kind: verb,
489            body,
490        }))
491    }
492
493    /// Look at the token `offset` positions ahead of the current one,
494    /// returning `None` if past EOF.
495    fn peek_offset(&self, offset: usize) -> Option<&Token> {
496        self.tokens.get(self.pos + offset)
497    }
498
499    /// Helper to check if current token is an identifier or keyword that can serve as a name.
500    fn is_name_token(&self) -> bool {
501        matches!(
502            self.peek_type(),
503            TokenType::Identifier
504                | TokenType::All
505                | TokenType::Year
506                | TokenType::Month
507                | TokenType::Day
508                | TokenType::Hour
509                | TokenType::Minute
510                | TokenType::Second
511                | TokenType::Interval
512                | TokenType::Key
513                | TokenType::Filter
514                | TokenType::First
515                | TokenType::Next
516                | TokenType::Only
517                | TokenType::Respect
518                | TokenType::Epoch
519                | TokenType::Schema
520                | TokenType::Database
521                | TokenType::View
522                | TokenType::Collate
523                | TokenType::Comment
524                | TokenType::Left
525                | TokenType::Right
526                | TokenType::Replace
527                | TokenType::Cube
528                | TokenType::Rollup
529                | TokenType::Grouping
530                | TokenType::Pivot
531                | TokenType::Unpivot
532                | TokenType::Sets
533                | TokenType::Range
534                | TokenType::Conflict
535                | TokenType::Unnest
536                | TokenType::Text
537                | TokenType::Show
538                | TokenType::Describe
539                | TokenType::Analyze
540                | TokenType::Index
541                | TokenType::Cast
542                | TokenType::Group
543                | TokenType::Order
544                | TokenType::Explain
545                | TokenType::Table
546                | TokenType::Offset
547                | TokenType::Merge
548                | TokenType::Nulls
549                | TokenType::Temp
550                | TokenType::Temporary
551                | TokenType::Rows
552                | TokenType::Partition
553                | TokenType::Any
554                | TokenType::Escape
555        )
556    }
557
558    /// Consume a name token (identifier or unreserved keyword used as identifier).
559    fn expect_name(&mut self) -> Result<String> {
560        let (name, _) = self.expect_name_with_quote()?;
561        Ok(name)
562    }
563
564    /// If the current token is `@` / `:` / `Parameter` immediately followed by
565    /// a name token (no whitespace tracking — they are adjacent in the token
566    /// stream), consume both and return them as a combined alias name.
567    /// Used to accept auto-generated aliases like `AS @rpm` or `AS :minutes`
568    /// without changing parameter-marker handling elsewhere.
569    fn try_parse_prefixed_alias(&mut self) -> Result<Option<(String, QuoteStyle)>> {
570        let prefix = match self.peek_type() {
571            TokenType::AtSign => '@',
572            TokenType::Colon => ':',
573            // Standalone Parameter token (`$` not absorbed into an identifier).
574            TokenType::Parameter if self.peek().value == "$" => '$',
575            _ => return Ok(None),
576        };
577        let next = match self.peek_offset(1) {
578            Some(t) => t,
579            None => return Ok(None),
580        };
581        let is_name_like = matches!(
582            next.token_type,
583            TokenType::Identifier
584                | TokenType::Year
585                | TokenType::Month
586                | TokenType::Day
587                | TokenType::Hour
588                | TokenType::Minute
589                | TokenType::Second
590                | TokenType::Key
591                | TokenType::Filter
592                | TokenType::First
593                | TokenType::Next
594                | TokenType::Only
595                | TokenType::Schema
596                | TokenType::Database
597                | TokenType::View
598                | TokenType::Collate
599                | TokenType::Comment
600                | TokenType::Replace
601                | TokenType::Text
602                | TokenType::Show
603                | TokenType::Describe
604                | TokenType::Analyze
605                | TokenType::Index
606                | TokenType::Cast
607                | TokenType::Group
608                | TokenType::Order
609                | TokenType::Range
610        );
611        if !is_name_like {
612            return Ok(None);
613        }
614        self.advance(); // consume prefix
615        let name_tok = self.advance().clone();
616        let mut combined = String::with_capacity(name_tok.value.len() + 1);
617        combined.push(prefix);
618        combined.push_str(&name_tok.value);
619        Ok(Some((combined, quote_style_from_char(name_tok.quote_char))))
620    }
621
622    /// Like `expect_name` but also returns the quote style of the token.
623    fn expect_name_with_quote(&mut self) -> Result<(String, QuoteStyle)> {
624        if self.is_name_token() {
625            let token = self.advance().clone();
626            let qs = quote_style_from_char(token.quote_char);
627            let mut name = token.value.clone();
628            // Append trailing `${...}` template variables so identifiers
629            // like `t1_${type}` round-trip as a single name token.
630            while matches!(self.peek_type(), TokenType::Parameter)
631                && self.peek().value.starts_with("${")
632            {
633                name.push_str(&self.advance().value.clone());
634            }
635            return Ok((name, qs));
636        }
637        // Leading `${...}` template variable as a name (rare).
638        if matches!(self.peek_type(), TokenType::Parameter) && self.peek().value.starts_with("${") {
639            let mut name = self.advance().value.clone();
640            // Only fuse plain identifiers or further `${...}` segments —
641            // never reserved keywords (Order, By, etc.) even though those
642            // tokenize as name-like, or the template would swallow the
643            // surrounding clause.
644            while matches!(self.peek_type(), TokenType::Identifier)
645                || (matches!(self.peek_type(), TokenType::Parameter)
646                    && self.peek().value.starts_with("${"))
647            {
648                name.push_str(&self.advance().value.clone());
649            }
650            return Ok((name, QuoteStyle::None));
651        }
652        // ClickHouse typed placeholder used as an identifier:
653        // `{db:Identifier}`, `{tbl:Identifier}`. Accept anywhere a name is
654        // expected so `FROM {db:Identifier}.t` and friends parse.
655        if matches!(self.peek_type(), TokenType::Parameter) && self.peek().value.starts_with('{') {
656            let name = self.advance().value.clone();
657            return Ok((name, QuoteStyle::None));
658        }
659        // Also accept any keyword-like identifier
660        let token = self.peek().clone();
661        if matches!(
662            token.token_type,
663            TokenType::Identifier
664                | TokenType::Int
665                | TokenType::Integer
666                | TokenType::BigInt
667                | TokenType::SmallInt
668                | TokenType::TinyInt
669                | TokenType::Float
670                | TokenType::Double
671                | TokenType::Decimal
672                | TokenType::Numeric
673                | TokenType::Real
674                | TokenType::Varchar
675                | TokenType::Char
676                | TokenType::Text
677                | TokenType::Boolean
678                | TokenType::Date
679                | TokenType::Timestamp
680                | TokenType::TimestampTz
681                | TokenType::Time
682                | TokenType::Interval
683                | TokenType::Blob
684                | TokenType::Bytea
685                | TokenType::Json
686                | TokenType::Jsonb
687                | TokenType::Uuid
688                | TokenType::Array
689                | TokenType::Map
690                | TokenType::Struct
691                | TokenType::Offset
692                | TokenType::Limit
693                | TokenType::Default
694                | TokenType::Begin
695                | TokenType::Recursive
696                | TokenType::Ignore
697                | TokenType::Pivot
698                | TokenType::Unpivot
699                | TokenType::Rows
700                | TokenType::Range
701                | TokenType::Values
702        ) {
703            let t = self.advance().clone();
704            let qs = quote_style_from_char(t.quote_char);
705            Ok((t.value.clone(), qs))
706        } else {
707            Err(SqlglotError::ParserError {
708                message: format!(
709                    "Expected identifier, got {:?} ('{}') at line {} col {}",
710                    token.token_type, token.value, token.line, token.col
711                ),
712            })
713        }
714    }
715
716    // ── Top-level parsing ──────────────────────────────────────────
717
718    /// Parse a single SQL statement.
719    pub fn parse_statement(&mut self) -> Result<Statement> {
720        self.collect_comments();
721        let stmt = self.parse_statement_inner()?;
722        // ClickHouse trailing `WITH TOTALS` / `WITH TIES` / `WITH ROLLUP` /
723        // `WITH CUBE` postfix at the end of a SELECT — these are query-level
724        // modifiers we don't model; swallow them so the statement closes.
725        if matches!(self.peek_type(), TokenType::With) {
726            let after = self.peek_offset(1);
727            let is_postfix_modifier = after
728                .map(|t| {
729                    matches!(
730                        t.token_type,
731                        TokenType::Identifier | TokenType::Cube | TokenType::Rollup
732                    ) && matches!(
733                        t.value.to_uppercase().as_str(),
734                        "TOTALS" | "TIES" | "FILL" | "ROLLUP" | "CUBE"
735                    )
736                })
737                .unwrap_or(false);
738            if is_postfix_modifier {
739                self.advance();
740                self.advance();
741                // Swallow any chained option words up to `;`/EOF/FORMAT/SETTINGS.
742                while !matches!(self.peek_type(), TokenType::Semicolon | TokenType::Eof) {
743                    if self.is_name_token()
744                        && matches!(
745                            self.peek().value.to_uppercase().as_str(),
746                            "SETTINGS" | "FORMAT"
747                        )
748                    {
749                        break;
750                    }
751                    self.advance();
752                }
753            }
754        }
755        // ClickHouse trailing `SETTINGS k=v, k=v` clause / `FORMAT name`
756        // (statement-level). Swallow up to the next `;` or EOF.
757        if self.is_name_token()
758            && matches!(
759                self.peek().value.to_uppercase().as_str(),
760                "SETTINGS" | "FORMAT"
761            )
762        {
763            while !matches!(self.peek_type(), TokenType::Semicolon | TokenType::Eof) {
764                self.advance();
765            }
766        }
767        // BigQuery pipe-syntax: `<query> |> WHERE … |> AGGREGATE … |> …`.
768        // The `|>` operator chains query stages. We don't model them; swallow
769        // the entire chain to end of statement so the leading query stands.
770        if self.peek_type() == &TokenType::BitwiseOr
771            && self
772                .peek_offset(1)
773                .map(|t| matches!(t.token_type, TokenType::Gt))
774                .unwrap_or(false)
775        {
776            while !matches!(self.peek_type(), TokenType::Semicolon | TokenType::Eof) {
777                self.advance();
778            }
779        }
780        // Consume trailing semicolons
781        while self.match_token(TokenType::Semicolon) {}
782        Ok(stmt)
783    }
784
785    fn parse_statement_inner(&mut self) -> Result<Statement> {
786        self.collect_comments();
787        let comments = self.take_comments();
788        // MySQL / PSM labeled block: `mylabel: BEGIN … END mylabel`.
789        // Swallow the leading `<name>:` so the block dispatches normally.
790        if self.is_name_token()
791            && matches!(
792                self.peek_offset(1).map(|t| &t.token_type),
793                Some(TokenType::Colon)
794            )
795        {
796            let saved = self.pos;
797            self.advance();
798            self.advance();
799            // Only treat as a label if a known block keyword follows;
800            // otherwise rewind so we don't misinterpret `alias: type`.
801            let is_block = matches!(
802                self.peek_type(),
803                TokenType::Begin | TokenType::If | TokenType::Case
804            ) || self.check_keyword("WHILE")
805                || self.check_keyword("LOOP")
806                || self.check_keyword("FOR")
807                || self.check_keyword("REPEAT");
808            if !is_block {
809                self.pos = saved;
810            }
811        }
812        let mut stmt = match self.peek_type() {
813            TokenType::With => self.parse_with_statement(),
814            TokenType::Select => {
815                let select = self.parse_select_body(vec![])?;
816                self.maybe_parse_set_operation(Statement::Select(select))
817            }
818            TokenType::LParen => {
819                // Could be a parenthesized SELECT / VALUES / TABLE form.
820                let saved_pos = self.pos;
821                self.advance(); // consume '('
822                if matches!(
823                    self.peek_type(),
824                    TokenType::Select
825                        | TokenType::With
826                        | TokenType::From
827                        | TokenType::Values
828                        | TokenType::Table
829                        | TokenType::LParen
830                ) {
831                    let inner = self.parse_statement_inner()?;
832                    self.expect(TokenType::RParen)?;
833                    self.maybe_parse_set_operation(inner)
834                } else {
835                    self.pos = saved_pos;
836                    Err(SqlglotError::ParserError {
837                        message: "Expected statement".into(),
838                    })
839                }
840            }
841            TokenType::Insert => self.parse_insert().map(Statement::Insert),
842            TokenType::Replace => self.parse_insert().map(Statement::Insert),
843            TokenType::Update => self.parse_update().map(Statement::Update),
844            TokenType::Delete => self.parse_delete().map(Statement::Delete),
845            TokenType::Merge => self.parse_merge().map(Statement::Merge),
846            TokenType::Create => self.parse_create_or_command(),
847            TokenType::Drop => self.parse_drop(),
848            TokenType::Alter => self.parse_alter_or_command(),
849            TokenType::Truncate => {
850                let saved = self.pos;
851                match self.parse_truncate() {
852                    Ok(t) => {
853                        // Tolerate Oracle-flavored trailing modifiers on
854                        // TRUNCATE (PURGE, DROP STORAGE, REUSE STORAGE,
855                        // KEEP …, CASCADE, etc.) by swallowing all trailing
856                        // tokens up to the statement boundary.
857                        while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon) {
858                            self.advance();
859                        }
860                        Ok(Statement::Truncate(t))
861                    }
862                    Err(_) => {
863                        self.pos = saved;
864                        self.parse_command_kind("TRUNCATE")
865                    }
866                }
867            }
868            TokenType::Begin | TokenType::Commit | TokenType::Rollback | TokenType::Savepoint => {
869                // PL/pgSQL / MySQL stored-procedure block: `BEGIN <stmt> …
870                // END`. If `BEGIN` is followed by anything that isn't an
871                // obvious transaction modifier, capture the whole block as
872                // a command so the surrounding parse completes.
873                if matches!(self.peek_type(), TokenType::Begin) {
874                    let next = self.peek_offset(1).map(|t| &t.token_type);
875                    let is_psm_block = matches!(
876                        next,
877                        Some(TokenType::Identifier)
878                            | Some(TokenType::If)
879                            | Some(TokenType::Case)
880                            | Some(TokenType::Select)
881                            | Some(TokenType::Insert)
882                            | Some(TokenType::Update)
883                            | Some(TokenType::Delete)
884                    );
885                    if is_psm_block {
886                        return self.parse_command_kind("BEGIN");
887                    }
888                }
889                self.parse_transaction().map(Statement::Transaction)
890            }
891            TokenType::Explain => self.parse_explain().map(Statement::Explain),
892            TokenType::Use => self.parse_use().map(Statement::Use),
893            // Raw-tail command statements: SET / SHOW / DESCRIBE / ANALYZE
894            // (when standalone, not as part of EXPLAIN) / COMMENT ON ... .
895            // We preserve the verb plus the entire remainder up to `;` or EOF
896            // so the AST round-trips even though we don't model these in detail.
897            TokenType::Set => self.parse_command_kind("SET"),
898            TokenType::Show => self.parse_command_kind("SHOW"),
899            TokenType::Describe => self.parse_command_kind("DESCRIBE"),
900            // `DESC <name>` is a Hive/MySQL synonym for DESCRIBE. The lone
901            // `Desc` token also appears mid-statement (ORDER BY x DESC), so
902            // we only treat it as a statement when at the very start.
903            TokenType::Desc => self.parse_command_kind("DESC"),
904            // Hive multi-insert: `FROM tbl INSERT OVERWRITE TABLE x SELECT ...`
905            // [INSERT OVERWRITE TABLE y SELECT ...]+. Capture the whole thing
906            // as a raw command body so it round-trips.
907            TokenType::From => {
908                // Hive `FROM tbl INSERT OVERWRITE TABLE x …` / `FROM tbl
909                // SELECT cols`. DuckDB implicit SELECT: `FROM tbl …`. Try
910                // the structured DuckDB FROM-first parse only when there is
911                // no INSERT/SELECT marker at the top paren level; otherwise
912                // capture as a raw command so it round-trips. Fall back to
913                // command capture on parse failure as well.
914                let mut i = self.pos + 1;
915                let mut depth = 0i32;
916                let mut hive = false;
917                while i < self.tokens.len() {
918                    match &self.tokens[i].token_type {
919                        TokenType::Eof | TokenType::Semicolon => break,
920                        TokenType::LParen => depth += 1,
921                        TokenType::RParen => {
922                            if depth == 0 {
923                                break;
924                            }
925                            depth -= 1;
926                        }
927                        TokenType::Insert | TokenType::Select if depth == 0 => {
928                            hive = true;
929                            break;
930                        }
931                        _ => {}
932                    }
933                    i += 1;
934                }
935                if hive {
936                    self.parse_command_kind("FROM")
937                } else {
938                    let saved_from = self.pos;
939                    match self.parse_select_body(vec![]) {
940                        Ok(select) => self.maybe_parse_set_operation(Statement::Select(select)),
941                        Err(_) => {
942                            self.pos = saved_from;
943                            self.parse_command_kind("FROM")
944                        }
945                    }
946                }
947            }
948            TokenType::Analyze => self.parse_command_kind("ANALYZE"),
949            TokenType::Check => self.parse_command_kind("CHECK"),
950            TokenType::Comment => self.parse_comment_on_command(),
951            TokenType::Grant => self.parse_command_kind("GRANT"),
952            TokenType::Revoke => self.parse_command_kind("REVOKE"),
953            // Procedural / control-flow statements (Spark, MySQL stored
954            // procs, PL/SQL, T-SQL): IF / FOR / WHILE / LOOP / CASE blocks
955            // and the matching ELSE / END / WHEN tokens at statement start.
956            // Capture verbatim so the AST round-trips.
957            TokenType::If => self.parse_command_kind("IF"),
958            TokenType::Else => self.parse_command_kind("ELSE"),
959            TokenType::End => self.parse_command_kind("END"),
960            TokenType::Case => self.parse_command_kind("CASE"),
961            TokenType::When => self.parse_command_kind("WHEN"),
962            TokenType::Then => self.parse_command_kind("THEN"),
963            TokenType::Do => self.parse_command_kind("DO"),
964            // Spark: `TABLE name` and `TABLE name |> …` are SELECT-equivalent
965            // shorthand. Capture verbatim so the AST round-trips.
966            TokenType::Table => self.parse_command_kind("TABLE"),
967            TokenType::Values => self.parse_command_kind("VALUES"),
968            // DuckDB SQL-shorthand: `PIVOT tbl ON col USING agg(...)` and
969            // `UNPIVOT tbl ON col INTO ...`. Preserve verbatim.
970            TokenType::Pivot => self.parse_command_kind("PIVOT"),
971            TokenType::Unpivot => self.parse_command_kind("UNPIVOT"),
972            // PG cursor verbs: FETCH, MOVE, CLOSE.
973            TokenType::Fetch => self.parse_command_kind("FETCH"),
974            // Vendor-specific verbs that tokenize as plain identifiers:
975            //   GO (T-SQL batch separator), DECLARE (T-SQL/PL-pgSQL),
976            //   LOAD (PG / MySQL extensions), REM / REMARK (SQL*Plus),
977            //   RESET / PRAGMA / VACUUM / REINDEX (PG / SQLite), CALL (PSM).
978            TokenType::Identifier if self.match_command_keyword() => {
979                self.parse_command_from_identifier()
980            }
981            // PL/pgSQL / MySQL stored-procedure assignment `var := expr` or
982            // `var = expr` at statement position. Preserve verbatim.
983            TokenType::Identifier
984                if matches!(
985                    self.peek_offset(1).map(|t| &t.token_type),
986                    Some(TokenType::Colon)
987                ) && matches!(
988                    self.peek_offset(2).map(|t| &t.token_type),
989                    Some(TokenType::Eq)
990                ) =>
991            {
992                self.parse_command_kind("ASSIGN")
993            }
994            // PL/SQL / PL/pgSQL variable declaration at top level:
995            //   `name TYPE [:= default]`. Some corpora split DECLARE blocks
996            //   into individual lines; treat these as opaque commands.
997            //   Heuristic: <identifier> followed by either a data-type
998            //   token, or an identifier that looks type-like (uppercase
999            //   keyword such as NUMBER/VARCHAR2/BOOLEAN/PLS_INTEGER/etc.).
1000            TokenType::Identifier
1001                if self
1002                    .peek_offset(1)
1003                    .map(|t| {
1004                        self.is_data_type_token_kind(&t.token_type)
1005                            || (matches!(t.token_type, TokenType::Identifier)
1006                                && matches!(
1007                                    t.value.to_uppercase().as_str(),
1008                                    "NUMBER"
1009                                        | "VARCHAR2"
1010                                        | "NVARCHAR2"
1011                                        | "PLS_INTEGER"
1012                                        | "BINARY_INTEGER"
1013                                        | "ROWID"
1014                                        | "UROWID"
1015                                        | "CLOB"
1016                                        | "NCLOB"
1017                                        | "BFILE"
1018                                        | "LONG"
1019                                        | "RAW"
1020                                        | "XMLTYPE"
1021                                        | "RECORD"
1022                                ))
1023                            || matches!(t.token_type, TokenType::Percent | TokenType::Percent2)
1024                    })
1025                    .unwrap_or(false)
1026                    && self
1027                        .peek_offset(2)
1028                        .map(|t| {
1029                            // Confirm declaration shape: trailing `:=`,
1030                            // `%TYPE`/`%ROWTYPE`, semicolon, EOF, or
1031                            // `(precision)` parenthesised type modifier.
1032                            matches!(
1033                                t.token_type,
1034                                TokenType::Colon
1035                                    | TokenType::Semicolon
1036                                    | TokenType::Eof
1037                                    | TokenType::Percent
1038                                    | TokenType::Percent2
1039                                    | TokenType::LParen
1040                            ) || matches!(t.token_type, TokenType::Identifier)
1041                                && matches!(
1042                                    t.value.to_uppercase().as_str(),
1043                                    "NOT" | "DEFAULT" | "CONSTANT"
1044                                )
1045                        })
1046                        .unwrap_or(true) =>
1047            {
1048                self.parse_command_kind("PLSQL_DECL")
1049            }
1050            _ => Err(SqlglotError::UnexpectedToken {
1051                token: self.peek().clone(),
1052            }),
1053        }?;
1054        if !comments.is_empty() {
1055            attach_comments_to_statement(&mut stmt, comments);
1056        }
1057        Ok(stmt)
1058    }
1059
1060    /// Parse multiple statements separated by semicolons.
1061    pub fn parse_statements(&mut self) -> Result<Vec<Statement>> {
1062        let mut stmts = Vec::new();
1063        while !matches!(self.peek_type(), TokenType::Eof) {
1064            while self.match_token(TokenType::Semicolon) {}
1065            if matches!(self.peek_type(), TokenType::Eof) {
1066                break;
1067            }
1068            stmts.push(self.parse_statement()?);
1069            // ClickHouse trailing `FORMAT <name>` after a statement is a
1070            // client-side output directive, not part of the AST. Swallow
1071            // it (and any whitespace-separated payload up to the next
1072            // semicolon / EOF) so the statement still parses.
1073            if self.peek().value.eq_ignore_ascii_case("FORMAT") {
1074                let saved = self.pos;
1075                self.advance();
1076                if self.is_name_token() {
1077                    self.advance();
1078                    while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon) {
1079                        self.advance();
1080                    }
1081                } else {
1082                    self.pos = saved;
1083                }
1084            }
1085        }
1086        Ok(stmts)
1087    }
1088
1089    // ── WITH / CTE parsing ─────────────────────────────────────────
1090
1091    fn parse_with_statement(&mut self) -> Result<Statement> {
1092        self.expect(TokenType::With)?;
1093        let recursive = self.match_token(TokenType::Recursive);
1094
1095        // T-SQL `WITH XMLNAMESPACES ('uri' AS prefix [, ...]) <stmt>`. The
1096        // XML namespaces are not modeled in the AST; swallow the keyword
1097        // and its parenthesized binding list opaquely so the surrounding
1098        // SELECT / INSERT / UPDATE / DELETE / MERGE parses cleanly.
1099        if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("XMLNAMESPACES") {
1100            self.advance(); // XMLNAMESPACES
1101            if self.match_token(TokenType::LParen) {
1102                let mut depth = 1_i32;
1103                while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
1104                    match self.peek_type() {
1105                        TokenType::LParen => depth += 1,
1106                        TokenType::RParen => depth -= 1,
1107                        _ => {}
1108                    }
1109                    self.advance();
1110                }
1111            }
1112            return self.parse_with_body(vec![]);
1113        }
1114
1115        // ClickHouse scalar-binding form: `WITH (expr) AS name [, ...] SELECT …`
1116        // (and the symmetric `WITH expr AS name`). Detect by peeking for a
1117        // `<expr> AS <name>` pattern rather than the canonical `<name> AS
1118        // (select …)`. We swallow these bindings — they aren't modeled as
1119        // CTEs — then fall through to the main query.
1120        if self.is_clickhouse_scalar_with() {
1121            loop {
1122                let _ = self.parse_expr()?;
1123                self.expect(TokenType::As)?;
1124                // The binding name may use a data-type keyword (`Uuid`,
1125                // `Text`, etc.) — accept any single token that isn't a
1126                // structural delimiter so the loop advances.
1127                if self.is_name_token() || self.is_data_type_token() {
1128                    self.advance();
1129                } else if !matches!(
1130                    self.peek_type(),
1131                    TokenType::Comma
1132                        | TokenType::Eof
1133                        | TokenType::Semicolon
1134                        | TokenType::Select
1135                        | TokenType::Insert
1136                        | TokenType::Update
1137                        | TokenType::Delete
1138                        | TokenType::Merge
1139                ) {
1140                    self.advance();
1141                }
1142                if !self.match_token(TokenType::Comma) {
1143                    break;
1144                }
1145                // The next binding might still be `name AS (select …)`; if so,
1146                // fall back to the canonical CTE parser for the remainder.
1147                if !self.is_clickhouse_scalar_with() {
1148                    let mut ctes = vec![self.parse_cte(recursive)?];
1149                    while self.match_token(TokenType::Comma) {
1150                        ctes.push(self.parse_cte(recursive)?);
1151                    }
1152                    return self.parse_with_body(ctes);
1153                }
1154            }
1155            return self.parse_with_body(vec![]);
1156        }
1157
1158        let mut ctes = vec![self.parse_cte(recursive)?];
1159        while self.match_token(TokenType::Comma) {
1160            ctes.push(self.parse_cte(recursive)?);
1161        }
1162        // PostgreSQL recursive-query SEARCH / CYCLE clauses appear between
1163        // the last CTE and the main query body. Swallow them opaquely.
1164        // Forms:
1165        //   SEARCH { DEPTH | BREADTH } FIRST BY <col_list> SET <col>
1166        //   CYCLE <col_list> SET <col> [TO <val> DEFAULT <val>] USING <col>
1167        loop {
1168            let saved = self.pos;
1169            if self.match_keyword("SEARCH") {
1170                let _ = self.match_keyword("DEPTH") || self.match_keyword("BREADTH");
1171                let _ = self.match_keyword("FIRST");
1172                let _ = self.match_token(TokenType::By);
1173                // Swallow tokens until SET or end-of-search clause.
1174                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
1175                    && !self.check_keyword("SET")
1176                {
1177                    self.advance();
1178                }
1179                if self.match_keyword("SET") {
1180                    let _ = self.is_name_token() && {
1181                        self.advance();
1182                        true
1183                    };
1184                }
1185                continue;
1186            }
1187            if self.check_keyword("CYCLE") {
1188                self.advance();
1189                while !matches!(
1190                    self.peek_type(),
1191                    TokenType::Select
1192                        | TokenType::Insert
1193                        | TokenType::Update
1194                        | TokenType::Delete
1195                        | TokenType::Merge
1196                        | TokenType::With
1197                        | TokenType::Eof
1198                        | TokenType::Semicolon
1199                ) {
1200                    self.advance();
1201                }
1202                continue;
1203            }
1204            self.pos = saved;
1205            break;
1206        }
1207        self.parse_with_body(ctes)
1208    }
1209
1210    /// Returns true if the current token sequence looks like a ClickHouse
1211    /// scalar `WITH expr AS name` rather than a canonical `name AS (select …)`
1212    /// CTE binding. Used by [`parse_with_statement`] to switch parsing modes.
1213    fn is_clickhouse_scalar_with(&self) -> bool {
1214        // Canonical CTE binding starts with `<name>` then either `(` (column
1215        // list) or `AS`. Anything else — a parenthesized expression, a number,
1216        // a string, a function call, an operator — must be the scalar form.
1217        match self.peek_type() {
1218            TokenType::LParen => true,
1219            TokenType::LBracket => true,
1220            TokenType::Number | TokenType::String | TokenType::HexString => true,
1221            t if matches!(t, TokenType::Minus | TokenType::Plus) => true,
1222            _ => {
1223                // Plain identifier followed by anything other than `(` or `AS`
1224                // also indicates the scalar form (e.g. `WITH x + 1 AS y`).
1225                if self.is_name_token() {
1226                    let next = self.peek_offset(1).map(|t| &t.token_type);
1227                    match next {
1228                        Some(TokenType::LParen) => {
1229                            // `name(...)` is canonical column-list form only
1230                            // if the body is a `name [, name]*` followed by
1231                            // `) AS`. Otherwise (function call like
1232                            // `arrayJoin([...])`) it's the scalar form.
1233                            !self.parens_are_name_list_then_as(1)
1234                        }
1235                        Some(TokenType::As) => false,
1236                        _ => true,
1237                    }
1238                } else {
1239                    false
1240                }
1241            }
1242        }
1243    }
1244
1245    /// Starting at `tokens[self.pos + offset]` (which must be `(`), check
1246    /// whether the body is a comma-separated identifier list followed by
1247    /// `)` and then `AS` — the shape of a CTE column-list binding.
1248    fn parens_are_name_list_then_as(&self, offset: usize) -> bool {
1249        let mut i = self.pos + offset;
1250        if self.tokens.get(i).map(|t| &t.token_type) != Some(&TokenType::LParen) {
1251            return false;
1252        }
1253        i += 1;
1254        loop {
1255            // Accept any name-like token in the column list, not just plain
1256            // identifiers — DuckDB CTEs frequently use unreserved keywords
1257            // like `key`, `value`, `order`, `range` as column names.
1258            let is_name_like = matches!(
1259                self.tokens.get(i).map(|t| &t.token_type),
1260                Some(TokenType::Identifier)
1261                    | Some(TokenType::Key)
1262                    | Some(TokenType::Year)
1263                    | Some(TokenType::Month)
1264                    | Some(TokenType::Day)
1265                    | Some(TokenType::Hour)
1266                    | Some(TokenType::Minute)
1267                    | Some(TokenType::Second)
1268                    | Some(TokenType::Filter)
1269                    | Some(TokenType::First)
1270                    | Some(TokenType::Next)
1271                    | Some(TokenType::Only)
1272                    | Some(TokenType::Schema)
1273                    | Some(TokenType::Database)
1274                    | Some(TokenType::View)
1275                    | Some(TokenType::Collate)
1276                    | Some(TokenType::Comment)
1277                    | Some(TokenType::Replace)
1278                    | Some(TokenType::Text)
1279                    | Some(TokenType::Show)
1280                    | Some(TokenType::Describe)
1281                    | Some(TokenType::Analyze)
1282                    | Some(TokenType::Index)
1283                    | Some(TokenType::Cast)
1284                    | Some(TokenType::Group)
1285                    | Some(TokenType::Order)
1286                    | Some(TokenType::Range)
1287                    | Some(TokenType::Partition)
1288                    | Some(TokenType::Rows)
1289                    | Some(TokenType::Table)
1290                    | Some(TokenType::Offset)
1291                    | Some(TokenType::Temp)
1292                    | Some(TokenType::Temporary)
1293                    | Some(TokenType::Nulls)
1294                    | Some(TokenType::Conflict)
1295                    | Some(TokenType::Unnest)
1296                    | Some(TokenType::Explain)
1297                    | Some(TokenType::Merge)
1298                    | Some(TokenType::Any)
1299                    | Some(TokenType::Escape)
1300            );
1301            if is_name_like {
1302                i += 1;
1303            } else {
1304                return false;
1305            }
1306            match self.tokens.get(i).map(|t| &t.token_type) {
1307                Some(TokenType::Comma) => i += 1,
1308                Some(TokenType::RParen) => {
1309                    i += 1;
1310                    // DuckDB recursive cycle clause: `(cols) USING KEY (...)
1311                    // AS (...)`. Treat the cycle keyword as a sign this is a
1312                    // canonical CTE binding, not a ClickHouse scalar.
1313                    if self.tokens.get(i).map(|t| t.value.to_uppercase())
1314                        == Some("USING".to_string())
1315                    {
1316                        return true;
1317                    }
1318                    if self.tokens.get(i).map(|t| &t.token_type) != Some(&TokenType::As) {
1319                        return false;
1320                    }
1321                    // Canonical form requires the body after `AS` to be
1322                    // a parenthesized SELECT (or `[NOT] MATERIALIZED (…)`
1323                    // for DuckDB / PostgreSQL). If it isn't, this is the
1324                    // ClickHouse scalar form.
1325                    i += 1;
1326                    let after_as = self.tokens.get(i).map(|t| &t.token_type);
1327                    if after_as == Some(&TokenType::LParen) {
1328                        return true;
1329                    }
1330                    let after_as_value = self.tokens.get(i).map(|t| t.value.as_str());
1331                    if matches!(
1332                        after_as_value,
1333                        Some(v) if v.eq_ignore_ascii_case("MATERIALIZED")
1334                            || v.eq_ignore_ascii_case("NOT")
1335                    ) {
1336                        return true;
1337                    }
1338                    return false;
1339                }
1340                _ => return false,
1341            }
1342        }
1343    }
1344
1345    fn parse_with_body(&mut self, ctes: Vec<Cte>) -> Result<Statement> {
1346        match self.peek_type() {
1347            TokenType::Select => {
1348                let select = self.parse_select_body(ctes)?;
1349                self.maybe_parse_set_operation(Statement::Select(select))
1350            }
1351            // DuckDB `WITH x AS (...) FROM tbl SELECT cols` (FROM-first form).
1352            // We rely on parse_select_body's existing FROM-first tolerance.
1353            TokenType::From => {
1354                let select = self.parse_select_body(ctes)?;
1355                self.maybe_parse_set_operation(Statement::Select(select))
1356            }
1357            // PostgreSQL / DuckDB `WITH x AS (...) TABLE tbl` body — equivalent
1358            // to `SELECT * FROM tbl`. Swallow the table reference and trailing
1359            // clauses opaquely and emit a stub Select so the surrounding
1360            // statement parses cleanly.
1361            // DuckDB / PostgreSQL `TABLE tbl` as the body of a WITH query —
1362            // shorthand for `SELECT * FROM tbl`. Swallow the trailing tokens
1363            // opaquely and emit a stub Select so the surrounding parse runs.
1364            TokenType::Table => {
1365                self.advance();
1366                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon) {
1367                    self.advance();
1368                }
1369                let select = SelectStatement {
1370                    comments: vec![],
1371                    ctes,
1372                    distinct: false,
1373                    top: None,
1374                    columns: vec![SelectItem::Wildcard],
1375                    from: None,
1376                    joins: vec![],
1377                    where_clause: None,
1378                    group_by: vec![],
1379                    having: None,
1380                    order_by: vec![],
1381                    limit: None,
1382                    offset: None,
1383                    fetch_first: None,
1384                    qualify: None,
1385                    window_definitions: vec![],
1386                };
1387                Ok(Statement::Select(select))
1388            }
1389            TokenType::Insert => {
1390                let ins = self.parse_insert()?;
1391                let _ = ctes;
1392                Ok(Statement::Insert(ins))
1393            }
1394            TokenType::Update => {
1395                let upd = self.parse_update()?;
1396                let _ = ctes;
1397                Ok(Statement::Update(upd))
1398            }
1399            TokenType::Delete => {
1400                let del = self.parse_delete()?;
1401                let _ = ctes;
1402                Ok(Statement::Delete(del))
1403            }
1404            TokenType::Merge => {
1405                let mrg = self.parse_merge()?;
1406                let _ = ctes;
1407                Ok(Statement::Merge(mrg))
1408            }
1409            _ => Err(SqlglotError::ParserError {
1410                message: "Expected SELECT or INSERT after WITH clause".into(),
1411            }),
1412        }
1413    }
1414
1415    fn parse_cte(&mut self, recursive: bool) -> Result<Cte> {
1416        let (name, name_quote_style) = self.expect_name_with_quote()?;
1417
1418        let columns = if self.match_token(TokenType::LParen) {
1419            let mut cols = vec![self.expect_name()?];
1420            while self.match_token(TokenType::Comma) {
1421                cols.push(self.expect_name()?);
1422            }
1423            self.expect(TokenType::RParen)?;
1424            cols
1425        } else {
1426            vec![]
1427        };
1428
1429        // DuckDB recursive CTE cycle clause:
1430        //   `WITH RECURSIVE tbl(a, b) USING KEY (a, max(b)) AS (...)`.
1431        // Swallow `USING KEY (...)` opaquely so the surrounding parse runs.
1432        if self.check_keyword("USING") {
1433            let saved = self.pos;
1434            self.advance();
1435            if self.check_keyword("KEY") {
1436                self.advance();
1437                if self.match_token(TokenType::LParen) {
1438                    let mut depth = 1_i32;
1439                    while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
1440                        match self.peek_type() {
1441                            TokenType::LParen => depth += 1,
1442                            TokenType::RParen => depth -= 1,
1443                            _ => {}
1444                        }
1445                        self.advance();
1446                    }
1447                }
1448            } else {
1449                self.pos = saved;
1450            }
1451        }
1452
1453        self.expect(TokenType::As)?;
1454        let materialized = if self.match_keyword("MATERIALIZED") {
1455            Some(true)
1456        } else if self.check_keyword("NOT") {
1457            let saved = self.pos;
1458            self.advance();
1459            if self.match_keyword("MATERIALIZED") {
1460                Some(false)
1461            } else {
1462                self.pos = saved;
1463                None
1464            }
1465        } else {
1466            None
1467        };
1468
1469        self.expect(TokenType::LParen)?;
1470        let query = self.parse_statement_inner()?;
1471        self.expect(TokenType::RParen)?;
1472
1473        Ok(Cte {
1474            name,
1475            name_quote_style,
1476            columns,
1477            query: Box::new(query),
1478            materialized,
1479            recursive,
1480        })
1481    }
1482
1483    // ── SELECT ──────────────────────────────────────────────────────
1484
1485    fn parse_select_body(&mut self, ctes: Vec<Cte>) -> Result<SelectStatement> {
1486        // DuckDB allows starting a query with `FROM ...` and implies
1487        // `SELECT *`. Detect that and synthesise the wildcard projection.
1488        let from_first = !matches!(self.peek_type(), TokenType::Select)
1489            && matches!(self.peek_type(), TokenType::From);
1490        if !from_first {
1491            self.expect(TokenType::Select)?;
1492        }
1493
1494        // MySQL `SELECT` modifiers (between SELECT and the column list):
1495        // DISTINCTROW (alias of DISTINCT), HIGH_PRIORITY, STRAIGHT_JOIN,
1496        // SQL_SMALL_RESULT, SQL_BIG_RESULT, SQL_BUFFER_RESULT, SQL_CACHE /
1497        // SQL_NO_CACHE, SQL_CALC_FOUND_ROWS. Swallow any number of these.
1498        let mut distinctrow = false;
1499        loop {
1500            if self.is_name_token() {
1501                let v = self.peek().value.to_uppercase();
1502                if matches!(
1503                    v.as_str(),
1504                    "DISTINCTROW"
1505                        | "HIGH_PRIORITY"
1506                        | "STRAIGHT_JOIN"
1507                        | "SQL_SMALL_RESULT"
1508                        | "SQL_BIG_RESULT"
1509                        | "SQL_BUFFER_RESULT"
1510                        | "SQL_CACHE"
1511                        | "SQL_NO_CACHE"
1512                        | "SQL_CALC_FOUND_ROWS"
1513                ) {
1514                    if v == "DISTINCTROW" {
1515                        distinctrow = true;
1516                    }
1517                    self.advance();
1518                    continue;
1519                }
1520            }
1521            break;
1522        }
1523        let distinct = distinctrow || self.match_token(TokenType::Distinct);
1524        // PostgreSQL / DuckDB `DISTINCT ON (expr, ...)` — swallow the column
1525        // list so the surrounding query parses. We don't model DISTINCT ON in
1526        // the AST; treat it as plain DISTINCT.
1527        if distinct && self.match_token(TokenType::On) {
1528            self.expect(TokenType::LParen)?;
1529            let mut depth = 1;
1530            while depth > 0 {
1531                match self.peek_type() {
1532                    TokenType::LParen => depth += 1,
1533                    TokenType::RParen => {
1534                        depth -= 1;
1535                        if depth == 0 {
1536                            self.advance();
1537                            break;
1538                        }
1539                    }
1540                    TokenType::Eof => break,
1541                    _ => {}
1542                }
1543                self.advance();
1544            }
1545        }
1546        // SQL-standard `SELECT ALL` quantifier (§7.12). Equivalent to omitting
1547        // the quantifier; consume it so it does not get mis-parsed as a column.
1548        if !distinct {
1549            let _ = self.match_token(TokenType::All);
1550        }
1551
1552        // BigQuery `SELECT [DISTINCT] AS STRUCT|VALUE …` — type-tag for the
1553        // implicit row constructor. We don't model it; swallow the prefix.
1554        if self.peek_type() == &TokenType::As {
1555            let v = self
1556                .peek_offset(1)
1557                .map(|t| t.value.to_uppercase())
1558                .unwrap_or_default();
1559            if matches!(v.as_str(), "STRUCT" | "VALUE") {
1560                self.advance(); // AS
1561                self.advance(); // STRUCT|VALUE
1562            }
1563        }
1564
1565        // TOP N (SQL Server style)
1566        // Use parse_primary() instead of parse_expr() to prevent the parser
1567        // from consuming `*` (SELECT all columns) as a multiplication operator.
1568        // This correctly handles: TOP 5, TOP 100, TOP (expr), TOP (@var)
1569        let top = if self.match_token(TokenType::Top) {
1570            Some(Box::new(self.parse_primary()?))
1571        } else {
1572            None
1573        };
1574
1575        let columns = if from_first {
1576            vec![SelectItem::Wildcard]
1577        } else {
1578            self.parse_select_items()?
1579        };
1580
1581        let from = if self.match_token(TokenType::From) {
1582            Some(FromClause {
1583                source: self.parse_table_source()?,
1584            })
1585        } else {
1586            None
1587        };
1588
1589        let joins = self.parse_joins()?;
1590
1591        // ClickHouse `PREWHERE expr` hint clause (sits between FROM/joins and
1592        // WHERE). Parsed as a regular boolean expression and folded into the
1593        // WHERE clause via `AND` so the AST stays simple.
1594        let prewhere = if self.check_keyword("PREWHERE") {
1595            self.advance();
1596            Some(self.parse_expr()?)
1597        } else {
1598            None
1599        };
1600
1601        let where_clause = if self.match_token(TokenType::Where) {
1602            let e = self.parse_expr()?;
1603            // ClickHouse: `WHERE (expr) AS alias` — alias-binds the
1604            // predicate. Swallow the AS-alias tail; we don't model it.
1605            if self.match_token(TokenType::As) && self.is_name_token() {
1606                self.advance();
1607            }
1608            Some(e)
1609        } else {
1610            None
1611        };
1612
1613        let where_clause = match (prewhere, where_clause) {
1614            (Some(pw), Some(w)) => Some(Expr::BinaryOp {
1615                left: Box::new(pw),
1616                op: BinaryOperator::And,
1617                right: Box::new(w),
1618            }),
1619            (Some(pw), None) => Some(pw),
1620            (None, w) => w,
1621        };
1622
1623        // Teradata `PREFERRING <expr> [PARTITION BY <list>]` skyline clause.
1624        // Sits between WHERE and GROUP BY. Swallow opaquely up to a known
1625        // terminator so the surrounding query parses.
1626        if self.check_keyword("PREFERRING") {
1627            self.advance();
1628            loop {
1629                match self.peek_type() {
1630                    TokenType::Eof
1631                    | TokenType::Semicolon
1632                    | TokenType::Group
1633                    | TokenType::Order
1634                    | TokenType::Having
1635                    | TokenType::Qualify
1636                    | TokenType::Limit
1637                    | TokenType::Union
1638                    | TokenType::Intersect
1639                    | TokenType::Except
1640                    | TokenType::RParen => break,
1641                    _ => {}
1642                }
1643                self.advance();
1644            }
1645        }
1646
1647        let group_by = if self.match_token(TokenType::Group) {
1648            self.expect(TokenType::By)?;
1649            let items = self.parse_group_by_list()?;
1650            // ClickHouse / MySQL `GROUP BY ... WITH ROLLUP|CUBE|TOTALS` —
1651            // swallow the modifier; we don't model it in the AST.
1652            if self.match_token(TokenType::With) {
1653                let _ = self.match_token(TokenType::Rollup)
1654                    || self.match_token(TokenType::Cube)
1655                    || self.match_keyword("TOTALS");
1656            }
1657            // Hive / Spark `GROUP BY k1, k2 GROUPING SETS ((k1), (k2))` —
1658            // swallow the trailing parenthesized list.
1659            if self.match_token(TokenType::Grouping) {
1660                if self.check_keyword("SETS") {
1661                    self.advance();
1662                }
1663                if self.match_token(TokenType::LParen) {
1664                    let mut depth = 1;
1665                    while depth > 0 {
1666                        match self.peek_type() {
1667                            TokenType::LParen => depth += 1,
1668                            TokenType::RParen => {
1669                                depth -= 1;
1670                                if depth == 0 {
1671                                    self.advance();
1672                                    break;
1673                                }
1674                            }
1675                            TokenType::Eof => break,
1676                            _ => {}
1677                        }
1678                        self.advance();
1679                    }
1680                }
1681            }
1682            items
1683        } else {
1684            vec![]
1685        };
1686
1687        let having = if self.match_token(TokenType::Having) {
1688            let expr = self.parse_expr()?;
1689            // ClickHouse corpora occasionally include a trailing alias after
1690            // HAVING expression text (`HAVING cond AS x`). Swallow alias so it
1691            // doesn't leak as an unexpected token.
1692            if self.match_token(TokenType::As) && self.is_name_token() {
1693                self.advance();
1694            }
1695            Some(expr)
1696        } else {
1697            None
1698        };
1699
1700        let qualify = if self.match_token(TokenType::Qualify) {
1701            Some(self.parse_expr()?)
1702        } else {
1703            None
1704        };
1705
1706        // Named WINDOW definitions
1707        let window_definitions = if self.match_token(TokenType::Window) {
1708            self.parse_window_definitions()?
1709        } else {
1710            vec![]
1711        };
1712
1713        let order_by = if self.match_token(TokenType::Order) {
1714            self.expect(TokenType::By)?;
1715            self.parse_order_by_items()?
1716        } else {
1717            vec![]
1718        };
1719
1720        // Hive / Spark non-standard ordering clauses; behave syntactically
1721        // like ORDER BY. We parse and discard them so the surrounding query
1722        // continues to parse.
1723        loop {
1724            let is_sort = self.check_keyword("SORT");
1725            let is_distribute = self.check_keyword("DISTRIBUTE");
1726            let is_cluster = self.check_keyword("CLUSTER");
1727            if !(is_sort || is_distribute || is_cluster) {
1728                break;
1729            }
1730            let saved = self.pos;
1731            self.advance();
1732            if self.peek_type() == &TokenType::By {
1733                self.advance();
1734                let _ = self.parse_order_by_items()?;
1735            } else {
1736                self.pos = saved;
1737                break;
1738            }
1739        }
1740
1741        let (mut limit, mut offset) = if self.match_token(TokenType::Limit) {
1742            let first = self.parse_expr()?;
1743            // MySQL / ClickHouse `LIMIT offset, count` form — convert to
1744            // `LIMIT count OFFSET offset`.
1745            if self.match_token(TokenType::Comma) {
1746                let count = self.parse_expr()?;
1747                (Some(count), Some(first))
1748            } else {
1749                (Some(first), None)
1750            }
1751        } else {
1752            (None, None)
1753        };
1754
1755        // ClickHouse `LIMIT N BY col[, ...]` / `LIMIT N BY col LIMIT M` —
1756        // consume the BY-list and an optional outer LIMIT so the trailing
1757        // SETTINGS / FORMAT clauses still parse.
1758        if limit.is_some() && self.match_token(TokenType::By) {
1759            let _ = self.parse_expr_list_allow_item_alias()?;
1760            if self.match_token(TokenType::Limit) {
1761                let _ = self.parse_expr()?;
1762            }
1763        }
1764
1765        if offset.is_none() && self.match_token(TokenType::Offset) {
1766            let expr = self.parse_expr()?;
1767            // T-SQL / ANSI SQL:2008 form: OFFSET n ROWS [FETCH …].
1768            // Consume the optional ROWS/ROW keyword so FETCH can match next.
1769            let _ = self.match_token(TokenType::Rows) || self.match_keyword("ROW");
1770            offset = Some(expr);
1771        } else if offset.is_some() {
1772            // Already populated from `LIMIT a, b`; still consume an explicit
1773            // `OFFSET n` if it appears so it does not leak into the trailer.
1774            if self.match_token(TokenType::Offset) {
1775                let expr = self.parse_expr()?;
1776                let _ = self.match_token(TokenType::Rows) || self.match_keyword("ROW");
1777                offset = Some(expr);
1778            }
1779        }
1780
1781        // Trino / Presto: `OFFSET n LIMIT m` (ordering opposite to MySQL).
1782        // We've parsed OFFSET; accept a trailing LIMIT n.
1783        if limit.is_none() && self.match_token(TokenType::Limit) {
1784            limit = Some(self.parse_expr()?);
1785        }
1786
1787        // FETCH FIRST|NEXT n ROWS ONLY (Oracle / ANSI SQL:2008 / T-SQL)
1788        let fetch_first = if self.match_token(TokenType::Fetch) {
1789            // consume FIRST or NEXT
1790            let _ = self.match_token(TokenType::First) || self.match_token(TokenType::Next);
1791            let count = self.parse_expr()?;
1792            // consume ROWS or ROW
1793            let _ = self.match_keyword("ROWS") || self.match_keyword("ROW");
1794            // consume ONLY
1795            let _ = self.match_token(TokenType::Only);
1796            Some(count)
1797        } else {
1798            None
1799        };
1800
1801        // ClickHouse trailing `WITH TOTALS` / `WITH TIES` / `WITH ROLLUP` /
1802        // `WITH CUBE` / `WITH FILL` modifiers in subquery position. These
1803        // are query-level modifiers we don't model; swallow so the
1804        // surrounding `)` is reached.
1805        if matches!(self.peek_type(), TokenType::With) {
1806            let after = self.peek_offset(1);
1807            let is_postfix_modifier = after
1808                .map(|t| {
1809                    matches!(
1810                        t.token_type,
1811                        TokenType::Identifier | TokenType::Cube | TokenType::Rollup
1812                    ) && matches!(
1813                        t.value.to_uppercase().as_str(),
1814                        "TOTALS" | "TIES" | "FILL" | "ROLLUP" | "CUBE"
1815                    )
1816                })
1817                .unwrap_or(false);
1818            if is_postfix_modifier {
1819                self.advance(); // WITH
1820                self.advance(); // modifier keyword
1821            }
1822        }
1823
1824        // ClickHouse `SETTINGS k = v, ...` / `FORMAT <name>` and MySQL
1825        // `INTO OUTFILE 'file'` style trailing clauses. None of these have
1826        // a dedicated AST representation; consume to keep the surrounding
1827        // statement parseable.
1828        loop {
1829            if self.check_keyword("SETTINGS")
1830                || self.check_keyword("FORMAT")
1831                || self.check_keyword("INTO")
1832            {
1833                self.skip_trailing_options();
1834                break;
1835            }
1836            break;
1837        }
1838
1839        Ok(SelectStatement {
1840            comments: vec![],
1841            ctes,
1842            distinct,
1843            top,
1844            columns,
1845            from,
1846            joins,
1847            where_clause,
1848            group_by,
1849            having,
1850            order_by,
1851            limit,
1852            offset,
1853            fetch_first,
1854            qualify,
1855            window_definitions,
1856        })
1857    }
1858
1859    fn parse_window_definitions(&mut self) -> Result<Vec<WindowDefinition>> {
1860        let mut defs = Vec::new();
1861        loop {
1862            let name = self.expect_name()?;
1863            self.expect(TokenType::As)?;
1864            self.expect(TokenType::LParen)?;
1865            let spec = self.parse_window_spec()?;
1866            self.expect(TokenType::RParen)?;
1867            defs.push(WindowDefinition { name, spec });
1868            if !self.match_token(TokenType::Comma) {
1869                break;
1870            }
1871        }
1872        Ok(defs)
1873    }
1874
1875    /// Check if we should parse a set operation (UNION / INTERSECT / EXCEPT)
1876    fn maybe_parse_set_operation(&mut self, left: Statement) -> Result<Statement> {
1877        let op = match self.peek_type() {
1878            TokenType::Union => SetOperationType::Union,
1879            TokenType::Intersect => SetOperationType::Intersect,
1880            TokenType::Except => SetOperationType::Except,
1881            _ => {
1882                // Spark / Oracle `MINUS` as a synonym for `EXCEPT`.
1883                if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("MINUS") {
1884                    self.advance();
1885                    let all = self.match_token(TokenType::All);
1886                    let _ = self.match_token(TokenType::Distinct);
1887                    let right = self.parse_statement_inner()?;
1888                    return Ok(Statement::SetOperation(SetOperationStatement {
1889                        comments: vec![],
1890                        op: SetOperationType::Except,
1891                        all,
1892                        left: Box::new(left),
1893                        right: Box::new(right),
1894                        order_by: vec![],
1895                        limit: None,
1896                        offset: None,
1897                    }));
1898                }
1899                return Ok(left);
1900            }
1901        };
1902        self.advance();
1903
1904        let all = self.match_token(TokenType::All);
1905        let _ = self.match_token(TokenType::Distinct); // UNION DISTINCT
1906
1907        // DuckDB `UNION ALL BY NAME` / `UNION BY NAME` — column-name-based
1908        // set operation. Swallow the modifier so the inner SELECT parses.
1909        if self.match_token(TokenType::By) {
1910            if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("NAME") {
1911                self.advance();
1912            }
1913        }
1914
1915        let right = self.parse_statement_inner()?;
1916
1917        // Check for further set operations chaining
1918        let combined = Statement::SetOperation(SetOperationStatement {
1919            comments: vec![],
1920            op,
1921            all,
1922            left: Box::new(left),
1923            right: Box::new(right),
1924            order_by: vec![],
1925            limit: None,
1926            offset: None,
1927        });
1928
1929        // Parse trailing ORDER BY / LIMIT / OFFSET that applies to the whole set operation
1930        if matches!(
1931            self.peek_type(),
1932            TokenType::Union | TokenType::Intersect | TokenType::Except
1933        ) {
1934            self.maybe_parse_set_operation(combined)
1935        } else {
1936            // Check for global ORDER BY / LIMIT
1937            if let Statement::SetOperation(mut sop) = combined {
1938                if self.match_token(TokenType::Order) {
1939                    self.expect(TokenType::By)?;
1940                    sop.order_by = self.parse_order_by_items()?;
1941                }
1942                if self.match_token(TokenType::Limit) {
1943                    sop.limit = Some(self.parse_expr()?);
1944                }
1945                if self.match_token(TokenType::Offset) {
1946                    sop.offset = Some(self.parse_expr()?);
1947                    // ANSI SQL:2008 / T-SQL: OFFSET n ROWS. Consume optional ROW(S).
1948                    let _ = self.match_token(TokenType::Rows) || self.match_keyword("ROW");
1949                }
1950                // Accept trailing LIMIT after OFFSET (OFFSET n LIMIT m ordering).
1951                if sop.limit.is_none() && self.match_token(TokenType::Limit) {
1952                    sop.limit = Some(self.parse_expr()?);
1953                }
1954                Ok(Statement::SetOperation(sop))
1955            } else {
1956                Ok(combined)
1957            }
1958        }
1959    }
1960
1961    fn parse_select_items(&mut self) -> Result<Vec<SelectItem>> {
1962        let mut items = vec![self.parse_select_item()?];
1963        while self.match_token(TokenType::Comma) {
1964            // DuckDB / BigQuery / Snowflake allow a trailing comma in the
1965            // SELECT list before `FROM` / end of select clause. Bail out if
1966            // the next token can't start a select item.
1967            if matches!(
1968                self.peek_type(),
1969                TokenType::From
1970                    | TokenType::Where
1971                    | TokenType::Group
1972                    | TokenType::Order
1973                    | TokenType::Limit
1974                    | TokenType::Having
1975                    | TokenType::Qualify
1976                    | TokenType::Eof
1977                    | TokenType::Semicolon
1978                    | TokenType::RParen
1979                    | TokenType::Union
1980                    | TokenType::Intersect
1981                    | TokenType::Except
1982            ) {
1983                break;
1984            }
1985            items.push(self.parse_select_item()?);
1986        }
1987        Ok(items)
1988    }
1989
1990    /// Consume DuckDB / Snowflake star modifiers — `EXCLUDE (...)`,
1991    /// `EXCEPT (...)`, `RENAME (...)`, `REPLACE (...)` — that may follow
1992    /// `*` or `t.*` in a SELECT list. Each modifier may appear at most
1993    /// once; we tolerate any order.
1994    fn swallow_star_modifiers(&mut self) {
1995        loop {
1996            let matched = self.check_keyword("EXCLUDE")
1997                || self.check_keyword("RENAME")
1998                || (self.check_keyword("REPLACE")
1999                    && matches!(
2000                        self.peek_offset(1).map(|t| &t.token_type),
2001                        Some(TokenType::LParen)
2002                    ))
2003                || (self.peek_type() == &TokenType::Except
2004                    && matches!(
2005                        self.peek_offset(1).map(|t| &t.token_type),
2006                        Some(TokenType::LParen)
2007                    ));
2008            // sqlfluff `SELECT * GLOB '…' FROM t` / `* SIMILAR TO '…'` /
2009            // `* LIKE '…'` style column-filter shorthand. Swallow the
2010            // operator and its pattern literal so the rest parses.
2011            let pattern_modifier = if matches!(self.peek_type(), TokenType::Like | TokenType::ILike)
2012                || (self.check_keyword("GLOB")
2013                    || self.check_keyword("REGEXP")
2014                    || self.check_keyword("RLIKE")
2015                    || self.check_keyword("IREGEXP")
2016                    || self.check_keyword("SIMILAR"))
2017            {
2018                let next_is_string = matches!(
2019                    self.peek_offset(1).map(|t| &t.token_type),
2020                    Some(TokenType::String)
2021                );
2022                let is_similar_to = self.check_keyword("SIMILAR")
2023                    && self
2024                        .peek_offset(1)
2025                        .map(|t| t.value.eq_ignore_ascii_case("TO"))
2026                        .unwrap_or(false);
2027                next_is_string || is_similar_to
2028            } else {
2029                false
2030            };
2031            if !matched && !pattern_modifier {
2032                break;
2033            }
2034            if pattern_modifier {
2035                // Operator keyword (and optional TO for SIMILAR TO) +
2036                // pattern string. We're tolerant of extra ESCAPE clause.
2037                self.advance(); // GLOB / LIKE / etc.
2038                if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("TO") {
2039                    self.advance();
2040                }
2041                if matches!(self.peek_type(), TokenType::String) {
2042                    self.advance();
2043                    if self.match_token(TokenType::Escape) {
2044                        if matches!(self.peek_type(), TokenType::String) {
2045                            self.advance();
2046                        }
2047                    }
2048                }
2049                continue;
2050            }
2051            self.advance(); // keyword
2052            if self.match_token(TokenType::LParen) {
2053                let mut depth = 1;
2054                while depth > 0 {
2055                    match self.peek_type() {
2056                        TokenType::LParen => depth += 1,
2057                        TokenType::RParen => {
2058                            depth -= 1;
2059                            if depth == 0 {
2060                                self.advance();
2061                                break;
2062                            }
2063                        }
2064                        TokenType::Eof => break,
2065                        _ => {}
2066                    }
2067                    self.advance();
2068                }
2069            } else if self.is_name_token() {
2070                // EXCLUDE col (single-column without parens)
2071                self.advance();
2072            }
2073        }
2074    }
2075
2076    fn parse_select_item(&mut self) -> Result<SelectItem> {
2077        if self.peek().token_type == TokenType::Star {
2078            self.advance();
2079            // DuckDB / Snowflake `* EXCLUDE (col, ...)`,
2080            // `* RENAME (a AS b, ...)`, `* REPLACE (expr AS col, ...)`.
2081            // Swallow the modifier so the surrounding select parses.
2082            self.swallow_star_modifiers();
2083            return Ok(SelectItem::Wildcard);
2084        }
2085
2086        // DuckDB struct-shorthand alias-first form: `alias: expr` in a SELECT
2087        // list. Only fire when we see `<name> :` followed by something that
2088        // is not another `:` (which would form `::` cast) — i.e. a leading
2089        // alias-then-colon pattern. The alias may be any name-like token.
2090        if self.is_name_token() {
2091            let pos1 = self.peek_offset(1).map(|t| &t.token_type);
2092            let pos2 = self.peek_offset(2).map(|t| &t.token_type);
2093            if matches!(pos1, Some(TokenType::Colon)) && !matches!(pos2, Some(TokenType::Colon)) {
2094                // Save state so we can roll back if the trailing expression
2095                // fails to parse (avoids misclassifying obscure forms).
2096                let saved = self.pos;
2097                let alias_tok = self.advance().clone();
2098                self.advance(); // consume ':'
2099                if let Ok(expr) = self.parse_expr() {
2100                    return Ok(SelectItem::Expr {
2101                        expr,
2102                        alias: Some(alias_tok.value),
2103                        alias_quote_style: quote_style_from_char(alias_tok.quote_char),
2104                    });
2105                }
2106                self.pos = saved;
2107            }
2108        }
2109
2110        let expr = self.parse_expr()?;
2111
2112        // Check for table.* pattern
2113        if let Expr::QualifiedWildcard { ref table } = expr {
2114            self.swallow_star_modifiers();
2115            return Ok(SelectItem::QualifiedWildcard {
2116                table: table.clone(),
2117            });
2118        }
2119
2120        // Hive scripting: `SELECT TRANSFORM(cols) [ROW FORMAT ...] USING
2121        // 'cmd' [AS (cols)] [ROW FORMAT ...] [RECORDREADER 'cls']`. The
2122        // tail clauses appear between the function call and `FROM`. We
2123        // don't model the scripting AST yet; swallow opaquely so the rest
2124        // of the SELECT parses.
2125        if matches!(
2126            &expr,
2127            Expr::Function { name, .. } if name.eq_ignore_ascii_case("TRANSFORM")
2128        ) {
2129            while !matches!(
2130                self.peek_type(),
2131                TokenType::From | TokenType::Eof | TokenType::Semicolon | TokenType::Comma
2132            ) {
2133                let v = self.peek().value.to_uppercase();
2134                let is_tail = self.peek_type() == &TokenType::Using
2135                    || self.peek_type() == &TokenType::As
2136                    || matches!(
2137                        v.as_str(),
2138                        "ROW"
2139                            | "FORMAT"
2140                            | "SERDE"
2141                            | "WITH"
2142                            | "SERDEPROPERTIES"
2143                            | "RECORDREADER"
2144                            | "RECORDWRITER"
2145                            | "FIELDS"
2146                            | "TERMINATED"
2147                            | "BY"
2148                            | "COLLECTION"
2149                            | "ITEMS"
2150                            | "MAP"
2151                            | "KEYS"
2152                            | "LINES"
2153                            | "NULL"
2154                            | "DEFINED"
2155                            | "STORED"
2156                            | "DELIMITED"
2157                            | "ESCAPED"
2158                            | "LOCATION"
2159                            | "OUTPUTFORMAT"
2160                            | "INPUTFORMAT"
2161                    );
2162                if !is_tail
2163                    && !matches!(
2164                        self.peek_type(),
2165                        TokenType::String
2166                            | TokenType::LParen
2167                            | TokenType::RParen
2168                            | TokenType::Identifier
2169                            | TokenType::Eq
2170                    )
2171                {
2172                    break;
2173                }
2174                self.advance();
2175            }
2176            return Ok(SelectItem::Expr {
2177                expr,
2178                alias: None,
2179                alias_quote_style: QuoteStyle::None,
2180            });
2181        }
2182
2183        let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2184            Some((name, qs)) => (Some(name), qs),
2185            None => (None, QuoteStyle::None),
2186        };
2187
2188        Ok(SelectItem::Expr {
2189            expr,
2190            alias,
2191            alias_quote_style,
2192        })
2193    }
2194
2195    fn parse_optional_alias(&mut self) -> Result<Option<(String, QuoteStyle)>> {
2196        if self.match_token(TokenType::As) {
2197            // After AS, also accept `@name` / `:name` as an alias. Both forms
2198            // appear in auto-generated SQL corpora (e.g. `AS @rpm`, `AS :minutes`)
2199            // where the symbol is part of the column name from the source data.
2200            if let Some((name, qs)) = self.try_parse_prefixed_alias()? {
2201                return Ok(Some((name, qs)));
2202            }
2203            // PostgreSQL / SQLite tolerate reserved-word literals as aliases
2204            // (`SELECT bool 't' AS true`). Accept TRUE / FALSE / NULL tokens.
2205            if matches!(
2206                self.peek_type(),
2207                TokenType::True | TokenType::False | TokenType::Null
2208            ) {
2209                let token = self.advance().clone();
2210                return Ok(Some((token.value, QuoteStyle::None)));
2211            }
2212            // DuckDB allows column aliases that collide with reserved
2213            // keywords (`AS matched`, `AS or`, `AS using`). After AS, take
2214            // whatever non-structural token appears.
2215            if matches!(
2216                self.peek_type(),
2217                TokenType::Matched
2218                    | TokenType::Or
2219                    | TokenType::And
2220                    | TokenType::Using
2221                    | TokenType::When
2222                    | TokenType::Where
2223                    | TokenType::Asc
2224                    | TokenType::Desc
2225                    | TokenType::Limit
2226                    | TokenType::Group
2227                    | TokenType::Having
2228                    | TokenType::On
2229                    | TokenType::Into
2230                    | TokenType::From
2231                    | TokenType::Order
2232                    | TokenType::Like
2233            ) {
2234                let token = self.advance().clone();
2235                return Ok(Some((token.value, QuoteStyle::None)));
2236            }
2237            // SQLite / MySQL / Snowflake / T-SQL accept a string literal as an
2238            // alias (`AS 'Record Id'`; T-SQL `SELECT 1 AS 'col'`). The alias is
2239            // an identifier despite the quoting, so normalize to DoubleQuote and
2240            // let the generator re-quote it in the target dialect's canonical
2241            // style (double-quote / backtick / bracket) with proper escaping.
2242            // Accepted for every dialect — intentionally lenient on input, like
2243            // the TRUE/FALSE/NULL and DuckDB-keyword branches above; dialects
2244            // that reject string-literal aliases on input still receive valid,
2245            // correctly quoted output. Only after an explicit AS: an implicit
2246            // trailing string is concatenation in MySQL, not an alias.
2247            if matches!(self.peek_type(), TokenType::String) {
2248                let token = self.advance().clone();
2249                return Ok(Some((token.value, QuoteStyle::DoubleQuote)));
2250            }
2251            return Ok(Some(self.expect_name_with_quote()?));
2252        }
2253        // Implicit alias
2254        if self.is_name_token() {
2255            let peeked_upper = self.peek().value.to_uppercase();
2256            if !matches!(
2257                peeked_upper.as_str(),
2258                "FROM"
2259                    | "WHERE"
2260                    | "GROUP"
2261                    | "ORDER"
2262                    | "LIMIT"
2263                    | "HAVING"
2264                    | "UNION"
2265                    | "INTERSECT"
2266                    | "EXCEPT"
2267                    | "JOIN"
2268                    | "INNER"
2269                    | "LEFT"
2270                    | "RIGHT"
2271                    | "FULL"
2272                    | "CROSS"
2273                    | "ON"
2274                    | "WINDOW"
2275                    | "QUALIFY"
2276                    | "INTO"
2277                    | "SET"
2278                    | "RETURNING"
2279                    | "PIVOT"
2280                    | "UNPIVOT"
2281                    | "PREWHERE"
2282                    | "SETTINGS"
2283                    | "FORMAT"
2284                    | "SAMPLE"
2285                    | "TABLESAMPLE"
2286                    | "LATERAL"
2287                    | "USING"
2288                    | "OFFSET"
2289                    | "FETCH"
2290                    | "FOR"
2291                    | "WITH"
2292                    | "OPTION"
2293                    | "MATCH_RECOGNIZE"
2294                    | "SORT"
2295                    | "DISTRIBUTE"
2296                    | "CLUSTER"
2297                    | "GLOBAL"
2298                    | "PREFERRING"
2299                    | "FORCE"
2300                    | "USE"
2301                    | "IGNORE"
2302                    | "STRAIGHT_JOIN"
2303                    | "DISTRIBUTED"
2304                    | "VALUE"
2305                    | "VALUES"
2306                    | "DEFAULT"
2307                    | "PARTITION"
2308            ) {
2309                let token = self.advance().clone();
2310                let qs = quote_style_from_char(token.quote_char);
2311                return Ok(Some((token.value.clone(), qs)));
2312            }
2313        }
2314        Ok(None)
2315    }
2316
2317    fn parse_table_source(&mut self) -> Result<TableSource> {
2318        let mut source = self.parse_base_table_source()?;
2319        // PostgreSQL table-inheritance star: `FROM parent*` includes all
2320        // child tables. Swallow the trailing `*` so the table alias /
2321        // joins continue to parse.
2322        let _ = self.match_token(TokenType::Star);
2323        // BigQuery / Snowflake / MySQL TiDB time-travel:
2324        //   `<tbl> [FOR SYSTEM_TIME] AS OF [TIMESTAMP] <expr>` or
2325        //   `<tbl> AS OF VERSION <expr>` / `AS OF TIMESTAMP <expr>`.
2326        // We don't model the time-travel clause in the AST; swallow the
2327        // keywords and the expression so the surrounding query parses.
2328        if self.is_name_token()
2329            && self.peek().value.eq_ignore_ascii_case("FOR")
2330            && self
2331                .peek_offset(1)
2332                .map(|t| t.value.eq_ignore_ascii_case("SYSTEM_TIME"))
2333                .unwrap_or(false)
2334        {
2335            self.advance(); // FOR
2336            self.advance(); // SYSTEM_TIME
2337        }
2338        if self.peek_type() == &TokenType::As
2339            && self
2340                .peek_offset(1)
2341                .map(|t| t.value.eq_ignore_ascii_case("OF"))
2342                .unwrap_or(false)
2343        {
2344            self.advance(); // AS
2345            self.advance(); // OF
2346            // Optional TIMESTAMP / VERSION qualifier.
2347            if matches!(self.peek_type(), TokenType::Timestamp)
2348                || (self.is_name_token()
2349                    && matches!(
2350                        self.peek().value.to_uppercase().as_str(),
2351                        "VERSION" | "SCN" | "SEQUENCE"
2352                    ))
2353            {
2354                self.advance();
2355            }
2356            let _ = self.parse_expr()?;
2357        }
2358        // Hive / Spark / Trino `TABLESAMPLE [method] (...)` after a table
2359        // reference. We don't model the sample clause in the AST; just
2360        // consume the optional method identifier (BERNOULLI / SYSTEM /
2361        // RESERVOIR) and the parenthesized body so the surrounding query
2362        // parses. Also accept an optional `REPEATABLE (n)` trailer.
2363        if self.match_token(TokenType::Tablesample) {
2364            // Optional sampling method identifier.
2365            if matches!(self.peek_type(), TokenType::Identifier) {
2366                self.advance();
2367            }
2368            if self.match_token(TokenType::LParen) {
2369                let mut depth = 1;
2370                while depth > 0 {
2371                    match self.peek_type() {
2372                        TokenType::LParen => depth += 1,
2373                        TokenType::RParen => {
2374                            depth -= 1;
2375                            if depth == 0 {
2376                                self.advance();
2377                                break;
2378                            }
2379                        }
2380                        TokenType::Eof => break,
2381                        _ => {}
2382                    }
2383                    self.advance();
2384                }
2385            }
2386            if self.check_keyword("REPEATABLE") {
2387                self.advance();
2388                if self.match_token(TokenType::LParen) {
2389                    let mut depth = 1;
2390                    while depth > 0 {
2391                        match self.peek_type() {
2392                            TokenType::LParen => depth += 1,
2393                            TokenType::RParen => {
2394                                depth -= 1;
2395                                if depth == 0 {
2396                                    self.advance();
2397                                    break;
2398                                }
2399                            }
2400                            TokenType::Eof => break,
2401                            _ => {}
2402                        }
2403                        self.advance();
2404                    }
2405                }
2406            }
2407            // Optional trailing alias on the sampled table — `… TABLESAMPLE
2408            // (…) s`. We attach it to the underlying table reference when
2409            // possible, otherwise just consume the identifier.
2410            if let TableSource::Table(ref mut tr) = source {
2411                if tr.alias.is_none() {
2412                    if let Some((name, qs)) = self.parse_optional_alias()? {
2413                        tr.alias = Some(name);
2414                        tr.alias_quote_style = qs;
2415                    }
2416                }
2417            }
2418        }
2419        // Check for trailing PIVOT / UNPIVOT
2420        let source = self.parse_pivot_or_unpivot(source)?;
2421        // ClickHouse: `SELECT * FROM t SAMPLE 0.1` (no parens) — and the
2422        // optional `OFFSET m` modifier. The keyword tokenizes as a plain
2423        // identifier so this also handles dialects that don't reserve it.
2424        if self.check_keyword("SAMPLE") {
2425            self.advance();
2426            // Accept a number, identifier, or parenthesized expression.
2427            if matches!(self.peek_type(), TokenType::Number) {
2428                self.advance();
2429                // Optional `/ N` ratio.
2430                if self.peek_type() == &TokenType::Slash {
2431                    self.advance();
2432                    if matches!(self.peek_type(), TokenType::Number) {
2433                        self.advance();
2434                    }
2435                }
2436            }
2437            if self.check_keyword("OFFSET") {
2438                self.advance();
2439                if matches!(self.peek_type(), TokenType::Number) {
2440                    self.advance();
2441                }
2442            }
2443        }
2444        Ok(source)
2445    }
2446
2447    fn parse_base_table_source(&mut self) -> Result<TableSource> {
2448        // LATERAL
2449        if self.match_token(TokenType::Lateral) {
2450            let source = self.parse_table_source()?;
2451            return Ok(TableSource::Lateral {
2452                source: Box::new(source),
2453            });
2454        }
2455
2456        // Spark / DuckDB / Postgres `FROM VALUES (...) [, (...)]+ [alias[(cols)]]`
2457        // (un-parenthesised VALUES list). Swallow the rows.
2458        if self.match_token(TokenType::Values) {
2459            // First row.
2460            if self.match_token(TokenType::LParen) {
2461                let mut depth = 1;
2462                while depth > 0 {
2463                    match self.peek_type() {
2464                        TokenType::LParen => depth += 1,
2465                        TokenType::RParen => {
2466                            depth -= 1;
2467                            if depth == 0 {
2468                                self.advance();
2469                                break;
2470                            }
2471                        }
2472                        TokenType::Eof => break,
2473                        _ => {}
2474                    }
2475                    self.advance();
2476                }
2477            }
2478            // Additional rows.
2479            while self.peek_type() == &TokenType::Comma {
2480                let saved = self.pos;
2481                self.advance();
2482                if !self.match_token(TokenType::LParen) {
2483                    // Not a row — restore comma for the outer parser.
2484                    self.pos = saved;
2485                    break;
2486                }
2487                let mut depth = 1;
2488                while depth > 0 {
2489                    match self.peek_type() {
2490                        TokenType::LParen => depth += 1,
2491                        TokenType::RParen => {
2492                            depth -= 1;
2493                            if depth == 0 {
2494                                self.advance();
2495                                break;
2496                            }
2497                        }
2498                        TokenType::Eof => break,
2499                        _ => {}
2500                    }
2501                    self.advance();
2502                }
2503            }
2504            let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2505                Some((name, qs)) => (Some(name), qs),
2506                None => (None, QuoteStyle::None),
2507            };
2508            if alias.is_some() && self.peek_type() == &TokenType::LParen {
2509                let saved = self.pos;
2510                self.advance();
2511                let mut ok = true;
2512                loop {
2513                    if !self.is_name_token() {
2514                        ok = false;
2515                        break;
2516                    }
2517                    self.advance();
2518                    if self.match_token(TokenType::RParen) {
2519                        break;
2520                    }
2521                    if !self.match_token(TokenType::Comma) {
2522                        ok = false;
2523                        break;
2524                    }
2525                }
2526                if !ok {
2527                    self.pos = saved;
2528                }
2529            }
2530            return Ok(TableSource::TableFunction {
2531                name: "VALUES".to_string(),
2532                args: vec![],
2533                alias,
2534                alias_quote_style,
2535            });
2536        }
2537
2538        // UNNEST(expr)
2539        if self.match_token(TokenType::Unnest) {
2540            self.expect(TokenType::LParen)?;
2541            let expr = self.parse_expr()?;
2542            // Multi-arg form (Trino): UNNEST(a, b, c). Drop extras.
2543            while self.match_token(TokenType::Comma) {
2544                let _ = self.parse_expr()?;
2545            }
2546            self.expect(TokenType::RParen)?;
2547            let (mut alias, mut alias_quote_style) = match self.parse_optional_alias()? {
2548                Some((name, qs)) => (Some(name), qs),
2549                None => (None, QuoteStyle::None),
2550            };
2551            // BigQuery `WITH OFFSET [AS name]` / Postgres `WITH ORDINALITY`.
2552            let mut with_offset = false;
2553            if self.check_keyword("WITH") {
2554                let saved = self.pos;
2555                self.advance();
2556                if self.check_keyword("OFFSET") || self.check_keyword("ORDINALITY") {
2557                    self.advance();
2558                    with_offset = true;
2559                    // Optional alias after OFFSET / ORDINALITY.
2560                    if alias.is_none() {
2561                        if let Some((n, qs)) = self.parse_optional_alias()? {
2562                            alias = Some(n);
2563                            alias_quote_style = qs;
2564                        }
2565                    } else if self.is_name_token() {
2566                        // `UNNEST(a) id WITH OFFSET pos` — extra trailing
2567                        // name; absorb so we don't trip the join parser.
2568                        self.advance();
2569                    }
2570                } else {
2571                    self.pos = saved;
2572                }
2573            }
2574            // Optional positional column list: `AS t (n, a)`.
2575            if alias.is_some() && self.peek_type() == &TokenType::LParen {
2576                let saved = self.pos;
2577                self.advance();
2578                let mut ok = true;
2579                loop {
2580                    if !self.is_name_token() {
2581                        ok = false;
2582                        break;
2583                    }
2584                    self.advance();
2585                    if self.match_token(TokenType::RParen) {
2586                        break;
2587                    }
2588                    if !self.match_token(TokenType::Comma) {
2589                        ok = false;
2590                        break;
2591                    }
2592                }
2593                if !ok {
2594                    self.pos = saved;
2595                }
2596            }
2597            return Ok(TableSource::Unnest {
2598                expr: Box::new(expr),
2599                alias,
2600                alias_quote_style,
2601                with_offset,
2602            });
2603        }
2604
2605        // Subquery: (SELECT ...)
2606        if self.peek_type() == &TokenType::LParen {
2607            let saved = self.pos;
2608            self.advance();
2609            // A derived-table body is a subquery when it begins with a statement
2610            // keyword, or with another `(` when the body is itself a
2611            // parenthesised (possibly set-operation) query — e.g. redundant
2612            // nesting `((SELECT …))` or a set operation whose branches are each
2613            // parenthesised `((SELECT …) EXCEPT (SELECT …))`. Delegate to the
2614            // recursive statement parser (the same path the top-level set-op
2615            // parser uses) rather than hand-counting parens, which cannot tell
2616            // redundant wrapping apart from a parenthesised first branch
2617            // (CR-014).
2618            let direct_subquery = matches!(
2619                self.peek_type(),
2620                TokenType::Select
2621                    | TokenType::With
2622                    | TokenType::Explain
2623                    | TokenType::From
2624                    | TokenType::Describe
2625                    | TokenType::Show
2626                    | TokenType::Table
2627            );
2628            // A `(`-led body may instead be a parenthesised join / table list
2629            // (`((t1 JOIN t2)) alias`). Attempt the subquery interpretation and,
2630            // on failure, restore to `saved` so the parenthesised-join handling
2631            // further below still runs.
2632            let paren_body = self.peek_type() == &TokenType::LParen;
2633            let mut subquery: Option<Statement> = None;
2634            if direct_subquery {
2635                let query = self.parse_statement_inner()?;
2636                // Set operations across parenthesised subqueries: `(SELECT …)
2637                // UNION ALL (SELECT …) [ORDER BY …] [LIMIT …]`.
2638                let query = self.maybe_parse_set_operation(query)?;
2639                self.expect(TokenType::RParen)?;
2640                subquery = Some(query);
2641            } else if paren_body {
2642                let attempt = self
2643                    .parse_statement_inner()
2644                    .and_then(|q| self.maybe_parse_set_operation(q));
2645                match attempt {
2646                    Ok(query) if self.peek_type() == &TokenType::RParen => {
2647                        self.advance();
2648                        subquery = Some(query);
2649                    }
2650                    _ => self.pos = saved,
2651                }
2652            }
2653            if let Some(query) = subquery {
2654                let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2655                    Some((name, qs)) => (Some(name), qs),
2656                    None => (None, QuoteStyle::None),
2657                };
2658                // Positional column-list alias: `(SELECT ...) t(c1, c2)`
2659                if alias.is_some() && self.peek_type() == &TokenType::LParen {
2660                    let saved2 = self.pos;
2661                    self.advance();
2662                    let mut ok = true;
2663                    loop {
2664                        if !self.is_name_token() {
2665                            ok = false;
2666                            break;
2667                        }
2668                        self.advance();
2669                        if self.match_token(TokenType::RParen) {
2670                            break;
2671                        }
2672                        if !self.match_token(TokenType::Comma) {
2673                            ok = false;
2674                            break;
2675                        }
2676                    }
2677                    if !ok {
2678                        self.pos = saved2;
2679                    }
2680                }
2681                return Ok(TableSource::Subquery {
2682                    query: Box::new(query),
2683                    alias,
2684                    alias_quote_style,
2685                });
2686            }
2687            // `(VALUES (...), (...)) alias[(cols)]` — common in DuckDB /
2688            // Postgres derived tables. We don't model the VALUES rows in the
2689            // AST as a table source; swallow the parenthesized body and
2690            // synthesise an empty subquery placeholder.
2691            if self.peek_type() == &TokenType::Values {
2692                // Re-advance past the values list, balancing parens (we are
2693                // inside the outer LParen at depth 1).
2694                let mut depth = 1;
2695                while depth > 0 {
2696                    match self.peek_type() {
2697                        TokenType::LParen => depth += 1,
2698                        TokenType::RParen => {
2699                            depth -= 1;
2700                            if depth == 0 {
2701                                self.advance();
2702                                break;
2703                            }
2704                        }
2705                        TokenType::Eof => break,
2706                        _ => {}
2707                    }
2708                    self.advance();
2709                }
2710                let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2711                    Some((name, qs)) => (Some(name), qs),
2712                    None => (None, QuoteStyle::None),
2713                };
2714                if alias.is_some() && self.peek_type() == &TokenType::LParen {
2715                    let saved2 = self.pos;
2716                    self.advance();
2717                    let mut ok = true;
2718                    loop {
2719                        if !self.is_name_token() {
2720                            ok = false;
2721                            break;
2722                        }
2723                        self.advance();
2724                        if self.match_token(TokenType::RParen) {
2725                            break;
2726                        }
2727                        if !self.match_token(TokenType::Comma) {
2728                            ok = false;
2729                            break;
2730                        }
2731                    }
2732                    if !ok {
2733                        self.pos = saved2;
2734                    }
2735                }
2736                // Synthesise an empty values placeholder. Reuse Subquery
2737                // with a single-row Insert wrapper is awkward; instead,
2738                // wrap as a TableFunction("VALUES") with empty args.
2739                return Ok(TableSource::TableFunction {
2740                    name: "VALUES".to_string(),
2741                    args: vec![],
2742                    alias,
2743                    alias_quote_style,
2744                });
2745            }
2746            self.pos = saved;
2747
2748            // MySQL / SQLite / others permit parenthesized join expressions
2749            // as a table source: `(t1 LEFT JOIN t2 ON …)` or comma-list
2750            // `(t1, t2)`. Recurse into the parens, then consume joins /
2751            // commas until the matching `)`. Emit the first source so the
2752            // surrounding query parses; trailing tables are discarded
2753            // (their predicates were already parsed into the JOIN node we
2754            // throw away — acceptance only).
2755            if self.peek_type() == &TokenType::LParen {
2756                let inner_saved = self.pos;
2757                self.advance();
2758                let after_lparen = self.pos;
2759                if let Ok(inner) = self.parse_table_source() {
2760                    let _ = self.parse_joins();
2761                    while self.match_token(TokenType::Comma) {
2762                        if self.parse_table_source().is_err() {
2763                            self.pos = inner_saved;
2764                            // Fall through to the generic parse_table_ref
2765                            // path below, which will surface the original
2766                            // error message.
2767                            break;
2768                        }
2769                        let _ = self.parse_joins();
2770                    }
2771                    if self.pos != inner_saved && self.match_token(TokenType::RParen) {
2772                        let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2773                            Some((name, qs)) => (Some(name), qs),
2774                            None => (None, QuoteStyle::None),
2775                        };
2776                        if let Some(name) = alias.clone() {
2777                            if let TableSource::Table(mut tr) = inner {
2778                                tr.alias = Some(name);
2779                                tr.alias_quote_style = alias_quote_style;
2780                                return Ok(TableSource::Table(tr));
2781                            }
2782                        }
2783                        return Ok(inner);
2784                    }
2785                }
2786                // Restore so the caller sees the LParen and emits a useful
2787                // error rather than silently misparsing partial state.
2788                self.pos = inner_saved;
2789                let _ = after_lparen; // suppress unused warning when build optimises
2790            }
2791        }
2792
2793        // Regular table reference (possibly with function syntax)
2794        let table_ref = self.parse_table_ref()?;
2795
2796        // MySQL / TiDB partition selector: `tbl PARTITION (p0, p1)`. Swallow
2797        // it so the table reference parses cleanly.
2798        if matches!(self.peek_type(), TokenType::Partition)
2799            && matches!(
2800                self.peek_offset(1).map(|t| &t.token_type),
2801                Some(TokenType::LParen)
2802            )
2803        {
2804            self.advance();
2805            self.advance();
2806            while !matches!(self.peek_type(), TokenType::RParen | TokenType::Eof) {
2807                self.advance();
2808            }
2809            let _ = self.match_token(TokenType::RParen);
2810        }
2811
2812        // Check if it's actually a table function: name(args...). Also
2813        // accept dotted qualifiers so DuckDB `schema.func(...)` /
2814        // `catalog.schema.func(...)` parse.
2815        if self.peek_type() == &TokenType::LParen {
2816            // SQL/PGQ `GRAPH_TABLE(graph MATCH … COLUMNS (…))`,
2817            // SQL/XML `XMLTABLE('xpath' PASSING expr COLUMNS …)`,
2818            // SQL/JSON `JSON_TABLE(expr, '$' COLUMNS (…))`. Swallow the
2819            // body opaquely so the rest of the query parses.
2820            let fname = table_ref.name.to_uppercase();
2821            if matches!(
2822                fname.as_str(),
2823                "GRAPH_TABLE" | "XMLTABLE" | "JSON_TABLE" | "OPENJSON" | "OPENROWSET" | "OPENXML"
2824            ) {
2825                self.advance();
2826                let mut depth = 1usize;
2827                while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
2828                    match self.peek_type() {
2829                        TokenType::LParen => depth += 1,
2830                        TokenType::RParen => {
2831                            depth -= 1;
2832                            if depth == 0 {
2833                                self.advance();
2834                                break;
2835                            }
2836                        }
2837                        _ => {}
2838                    }
2839                    self.advance();
2840                }
2841                let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2842                    Some((name, qs)) => (Some(name), qs),
2843                    None => (None, QuoteStyle::None),
2844                };
2845                if alias.is_some() && self.peek_type() == &TokenType::LParen {
2846                    let saved = self.pos;
2847                    self.advance();
2848                    let mut ok = true;
2849                    loop {
2850                        if !self.is_name_token() {
2851                            ok = false;
2852                            break;
2853                        }
2854                        self.advance();
2855                        if self.match_token(TokenType::RParen) {
2856                            break;
2857                        }
2858                        if !self.match_token(TokenType::Comma) {
2859                            ok = false;
2860                            break;
2861                        }
2862                    }
2863                    if !ok {
2864                        self.pos = saved;
2865                    }
2866                }
2867                return Ok(TableSource::TableFunction {
2868                    name: match (&table_ref.catalog, &table_ref.schema) {
2869                        (Some(c), Some(s)) => format!("{}.{}.{}", c, s, table_ref.name),
2870                        (None, Some(s)) => format!("{}.{}", s, table_ref.name),
2871                        _ => table_ref.name,
2872                    },
2873                    args: vec![],
2874                    alias,
2875                    alias_quote_style,
2876                });
2877            }
2878            self.advance();
2879            // Hive `noop(on tbl partition by ... order by ... )` table-valued
2880            // function. Arguments start with the `ON` keyword and include
2881            // PARTITION/ORDER/CLUSTER/DISTRIBUTE/SORT BY clauses we don't
2882            // model. Swallow the body opaquely.
2883            let args = if matches!(self.peek_type(), TokenType::On) {
2884                let mut depth = 0usize;
2885                while !matches!(self.peek_type(), TokenType::Eof) {
2886                    match self.peek_type() {
2887                        TokenType::LParen => depth += 1,
2888                        TokenType::RParen => {
2889                            if depth == 0 {
2890                                break;
2891                            }
2892                            depth -= 1;
2893                        }
2894                        _ => {}
2895                    }
2896                    self.advance();
2897                }
2898                vec![]
2899            } else if self.peek_type() != &TokenType::RParen {
2900                self.parse_expr_list()?
2901            } else {
2902                vec![]
2903            };
2904            self.expect(TokenType::RParen)?;
2905            let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2906                Some((name, qs)) => (Some(name), qs),
2907                None => (None, QuoteStyle::None),
2908            };
2909            // DuckDB / Postgres positional column-list alias:
2910            //   range(10) t(i)   →   alias = "t", columns = (i)
2911            // We consume the parenthesized list but do not model it in the AST.
2912            if alias.is_some() && self.peek_type() == &TokenType::LParen {
2913                let saved = self.pos;
2914                self.advance();
2915                let mut ok = true;
2916                loop {
2917                    if !self.is_name_token() {
2918                        ok = false;
2919                        break;
2920                    }
2921                    self.advance();
2922                    if self.match_token(TokenType::RParen) {
2923                        break;
2924                    }
2925                    if !self.match_token(TokenType::Comma) {
2926                        ok = false;
2927                        break;
2928                    }
2929                }
2930                if !ok {
2931                    self.pos = saved;
2932                }
2933            }
2934            return Ok(TableSource::TableFunction {
2935                name: match (&table_ref.catalog, &table_ref.schema) {
2936                    (Some(c), Some(s)) => format!("{}.{}.{}", c, s, table_ref.name),
2937                    (None, Some(s)) => format!("{}.{}", s, table_ref.name),
2938                    _ => table_ref.name,
2939                },
2940                args,
2941                alias,
2942                alias_quote_style,
2943            });
2944        }
2945
2946        // Also support positional column-list alias on a plain table reference:
2947        //   FROM tbl t(c1, c2)
2948        if self.peek_type() == &TokenType::LParen && table_ref.alias.is_some() {
2949            let saved = self.pos;
2950            self.advance();
2951            let mut ok = true;
2952            loop {
2953                if !self.is_name_token() {
2954                    ok = false;
2955                    break;
2956                }
2957                self.advance();
2958                if self.match_token(TokenType::RParen) {
2959                    break;
2960                }
2961                if !self.match_token(TokenType::Comma) {
2962                    ok = false;
2963                    break;
2964                }
2965            }
2966            if !ok {
2967                self.pos = saved;
2968            }
2969        }
2970
2971        // MySQL / MariaDB index hints — `USE INDEX (idx)`, `FORCE INDEX (idx)`,
2972        // `IGNORE INDEX (idx)`, optionally with `FOR JOIN|ORDER BY|GROUP BY`.
2973        // Swallow any sequence of these so the rest of the query parses.
2974        loop {
2975            let saved = self.pos;
2976            let is_hint = matches!(self.peek_type(), TokenType::Use | TokenType::Ignore)
2977                || self.check_keyword("FORCE");
2978            if !is_hint {
2979                break;
2980            }
2981            self.advance();
2982            if !self.check_keyword("INDEX") && !self.check_keyword("KEY") {
2983                self.pos = saved;
2984                break;
2985            }
2986            self.advance();
2987            // Optional `FOR JOIN | FOR ORDER BY | FOR GROUP BY`.
2988            if self.match_keyword("FOR") {
2989                if matches!(
2990                    self.peek_type(),
2991                    TokenType::Join | TokenType::Order | TokenType::Group
2992                ) {
2993                    self.advance();
2994                    let _ = self.match_token(TokenType::By);
2995                }
2996            }
2997            if self.match_token(TokenType::LParen) {
2998                let mut depth = 1;
2999                while depth > 0 {
3000                    match self.peek_type() {
3001                        TokenType::LParen => depth += 1,
3002                        TokenType::RParen => {
3003                            depth -= 1;
3004                            if depth == 0 {
3005                                self.advance();
3006                                break;
3007                            }
3008                        }
3009                        TokenType::Eof => break,
3010                        _ => {}
3011                    }
3012                    self.advance();
3013                }
3014            }
3015        }
3016
3017        // ClickHouse `FROM tbl [AS alias] FINAL` — swallow the FINAL modifier.
3018        // The token tokenizes as Identifier so check_keyword is enough.
3019        if self.check_keyword("FINAL") {
3020            self.advance();
3021        }
3022
3023        // MySQL: `FROM t PARTITION (p0[, p1, ...])` — swallow partition
3024        // selector. May appear before or after the alias; we accept it
3025        // here (i.e., before parse_optional_alias has run).
3026        if matches!(self.peek_type(), TokenType::Partition)
3027            && matches!(
3028                self.peek_offset(1).map(|t| &t.token_type),
3029                Some(TokenType::LParen)
3030            )
3031        {
3032            self.advance();
3033            self.advance();
3034            let mut depth = 1;
3035            while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
3036                match self.peek_type() {
3037                    TokenType::LParen => depth += 1,
3038                    TokenType::RParen => {
3039                        depth -= 1;
3040                        if depth == 0 {
3041                            self.advance();
3042                            break;
3043                        }
3044                    }
3045                    _ => {}
3046                }
3047                self.advance();
3048            }
3049        }
3050
3051        Ok(TableSource::Table(table_ref))
3052    }
3053
3054    /// After parsing a base table source, check if PIVOT or UNPIVOT follows.
3055    fn parse_pivot_or_unpivot(&mut self, source: TableSource) -> Result<TableSource> {
3056        if self.match_token(TokenType::Pivot) {
3057            self.expect(TokenType::LParen)?;
3058            let aggregate = self.parse_expr()?;
3059            // Snowflake / Databricks: optional `AS <alias>` on the aggregate
3060            // expression: `PIVOT (sum(sales) AS sales FOR …)`.
3061            if self.peek_type() == &TokenType::As
3062                && self
3063                    .peek_offset(1)
3064                    .map(|t| {
3065                        matches!(
3066                            t.token_type,
3067                            TokenType::Identifier | TokenType::String | TokenType::Number
3068                        )
3069                    })
3070                    .unwrap_or(false)
3071            {
3072                self.advance();
3073                self.advance();
3074            }
3075            // Multi-aggregate PIVOT: `PIVOT (SUM(x), COUNT(x) FOR …)`. Drop
3076            // the extra aggregates — we only keep the first one in the AST.
3077            while self.match_token(TokenType::Comma) {
3078                let _ = self.parse_expr()?;
3079                if self.peek_type() == &TokenType::As
3080                    && self
3081                        .peek_offset(1)
3082                        .map(|t| {
3083                            matches!(
3084                                t.token_type,
3085                                TokenType::Identifier | TokenType::String | TokenType::Number
3086                            )
3087                        })
3088                        .unwrap_or(false)
3089                {
3090                    self.advance();
3091                    self.advance();
3092                }
3093            }
3094            self.expect_keyword("FOR")?;
3095            // Snowflake `FOR (col1, col2) IN …` — grouped pivot key. Use the
3096            // first column name as the AST's for_column.
3097            let for_column = if self.peek_type() == &TokenType::LParen {
3098                self.advance();
3099                let first = self.expect_name()?;
3100                while self.match_token(TokenType::Comma) {
3101                    let _ = self.expect_name()?;
3102                }
3103                self.expect(TokenType::RParen)?;
3104                first
3105            } else {
3106                self.expect_name()?
3107            };
3108            self.expect(TokenType::In)?;
3109            self.expect(TokenType::LParen)?;
3110            let in_values = self.parse_pivot_values()?;
3111            self.expect(TokenType::RParen)?;
3112            self.expect(TokenType::RParen)?;
3113            let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3114                Some((name, qs)) => (Some(name), qs),
3115                None => (None, QuoteStyle::None),
3116            };
3117            return Ok(TableSource::Pivot {
3118                source: Box::new(source),
3119                aggregate: Box::new(aggregate),
3120                for_column,
3121                in_values,
3122                alias,
3123                alias_quote_style,
3124            });
3125        }
3126        if self.match_token(TokenType::Unpivot) {
3127            // BigQuery: `UNPIVOT INCLUDE|EXCLUDE NULLS (...)`.
3128            if self.check_keyword("INCLUDE") || self.check_keyword("EXCLUDE") {
3129                let saved = self.pos;
3130                self.advance();
3131                if !self.match_keyword("NULLS") {
3132                    self.pos = saved;
3133                }
3134            }
3135            self.expect(TokenType::LParen)?;
3136            // Snowflake/DuckDB allow a grouped value-column tuple:
3137            // `UNPIVOT ((col1, col2) FOR period IN (...))`. Swallow the
3138            // grouping parens — we only model a single value-column name.
3139            let value_column = if self.peek_type() == &TokenType::LParen {
3140                self.advance();
3141                let first = self.expect_name()?;
3142                while self.match_token(TokenType::Comma) {
3143                    let _ = self.expect_name()?;
3144                }
3145                self.expect(TokenType::RParen)?;
3146                first
3147            } else {
3148                self.expect_name()?
3149            };
3150            self.expect_keyword("FOR")?;
3151            let for_column = self.expect_name()?;
3152            self.expect(TokenType::In)?;
3153            self.expect(TokenType::LParen)?;
3154            let in_columns = self.parse_pivot_values()?;
3155            self.expect(TokenType::RParen)?;
3156            self.expect(TokenType::RParen)?;
3157            let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3158                Some((name, qs)) => (Some(name), qs),
3159                None => (None, QuoteStyle::None),
3160            };
3161            return Ok(TableSource::Unpivot {
3162                source: Box::new(source),
3163                value_column,
3164                for_column,
3165                in_columns,
3166                alias,
3167                alias_quote_style,
3168            });
3169        }
3170        Ok(source)
3171    }
3172
3173    /// Parse comma-separated pivot values, each optionally aliased.
3174    fn parse_pivot_values(&mut self) -> Result<Vec<PivotValue>> {
3175        let mut values = Vec::new();
3176        loop {
3177            let value = self.parse_expr()?;
3178            // Snowflake / BigQuery permit string or numeric aliases on pivot
3179            // values: `(a, b) AS 'semester_1'` / `(a, b) AS 1`. Accept those
3180            // alongside the regular identifier alias.
3181            let (alias, alias_quote_style) = if self.match_token(TokenType::As)
3182                && matches!(self.peek_type(), TokenType::String | TokenType::Number)
3183            {
3184                let tok = self.advance().clone();
3185                (Some(tok.value), QuoteStyle::None)
3186            } else {
3187                match self.parse_optional_alias()? {
3188                    Some((name, qs)) => (Some(name), qs),
3189                    None => (None, QuoteStyle::None),
3190                }
3191            };
3192            values.push(PivotValue {
3193                value,
3194                alias,
3195                alias_quote_style,
3196            });
3197            if !self.match_token(TokenType::Comma) {
3198                break;
3199            }
3200        }
3201        Ok(values)
3202    }
3203
3204    fn parse_table_ref(&mut self) -> Result<TableRef> {
3205        // T-SQL table variable: `FROM @t` / `INTO @t` etc. The @ is its own
3206        // token; fuse with the following name into a single identifier.
3207        if matches!(self.peek_type(), TokenType::AtSign)
3208            && self
3209                .peek_offset(1)
3210                .map(|t| {
3211                    matches!(t.token_type, TokenType::Identifier)
3212                        || matches!(t.token_type, TokenType::AtSign)
3213                })
3214                .unwrap_or(false)
3215        {
3216            let mut name = String::from("@");
3217            self.advance();
3218            if matches!(self.peek_type(), TokenType::AtSign) {
3219                name.push('@');
3220                self.advance();
3221            }
3222            let n = self.advance().clone();
3223            name.push_str(&n.value);
3224            let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3225                Some((a, qs)) => (Some(a), qs),
3226                None => (None, QuoteStyle::None),
3227            };
3228            return Ok(TableRef {
3229                catalog: None,
3230                schema: None,
3231                name,
3232                alias,
3233                name_quote_style: QuoteStyle::None,
3234                alias_quote_style,
3235            });
3236        }
3237        let (first, first_qs) = self.expect_name_with_quote()?;
3238
3239        // Check for schema.table or catalog.schema.table. We also tolerate 4+
3240        // part qualified names (DuckDB / SQL Server `srv.db.sch.tbl`) by
3241        // folding additional segments into the catalog field.
3242        let (catalog, schema, name, name_qs) = if self.match_token(TokenType::Dot) {
3243            let (second, second_qs) = self.expect_name_with_quote()?;
3244            if self.match_token(TokenType::Dot) {
3245                let (mut third, mut third_qs) = self.expect_name_with_quote()?;
3246                let mut catalog = first;
3247                let mut schema = second;
3248                while self.match_token(TokenType::Dot) {
3249                    let (next, next_qs) = self.expect_name_with_quote()?;
3250                    catalog.push('.');
3251                    catalog.push_str(&schema);
3252                    schema = third;
3253                    third = next;
3254                    third_qs = next_qs;
3255                }
3256                (Some(catalog), Some(schema), third, third_qs)
3257            } else {
3258                (None, Some(first), second, second_qs)
3259            }
3260        } else {
3261            (None, None, first, first_qs)
3262        };
3263
3264        let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3265            Some((name, qs)) => (Some(name), qs),
3266            None => (None, QuoteStyle::None),
3267        };
3268
3269        Ok(TableRef {
3270            catalog,
3271            schema,
3272            name,
3273            alias,
3274            name_quote_style: name_qs,
3275            alias_quote_style,
3276        })
3277    }
3278
3279    /// Like `parse_table_ref` but does not consume an alias.
3280    fn parse_table_ref_no_alias(&mut self) -> Result<TableRef> {
3281        let (first, first_qs) = self.expect_name_with_quote()?;
3282
3283        let (catalog, schema, name, name_qs) = if self.match_token(TokenType::Dot) {
3284            let (second, second_qs) = self.expect_name_with_quote()?;
3285            if self.match_token(TokenType::Dot) {
3286                let (mut third, mut third_qs) = self.expect_name_with_quote()?;
3287                let mut catalog = first;
3288                let mut schema = second;
3289                while self.match_token(TokenType::Dot) {
3290                    let (next, next_qs) = self.expect_name_with_quote()?;
3291                    catalog.push('.');
3292                    catalog.push_str(&schema);
3293                    schema = third;
3294                    third = next;
3295                    third_qs = next_qs;
3296                }
3297                (Some(catalog), Some(schema), third, third_qs)
3298            } else {
3299                (None, Some(first), second, second_qs)
3300            }
3301        } else {
3302            (None, None, first, first_qs)
3303        };
3304
3305        Ok(TableRef {
3306            catalog,
3307            schema,
3308            name,
3309            alias: None,
3310            name_quote_style: name_qs,
3311            alias_quote_style: QuoteStyle::None,
3312        })
3313    }
3314
3315    fn parse_joins(&mut self) -> Result<Vec<JoinClause>> {
3316        let mut joins = Vec::new();
3317        loop {
3318            // Hive `LATERAL VIEW [OUTER] func(args) tbl_alias [AS col, ...]`.
3319            // Model as a CROSS JOIN over a table-function so the rest of the
3320            // query parses; the AS column list is dropped.
3321            if self.peek_type() == &TokenType::Lateral
3322                && self
3323                    .peek_offset(1)
3324                    .map(|t| t.value.eq_ignore_ascii_case("VIEW"))
3325                    .unwrap_or(false)
3326            {
3327                self.advance(); // LATERAL
3328                self.advance(); // VIEW
3329                let _outer = self.check_keyword("OUTER") && {
3330                    self.advance();
3331                    true
3332                };
3333                // func(args) — parse name and arg list
3334                let fname = self.expect_name().unwrap_or_default();
3335                let mut fargs = Vec::new();
3336                if self.match_token(TokenType::LParen) {
3337                    if self.peek_type() != &TokenType::RParen {
3338                        fargs.push(self.parse_expr()?);
3339                        while self.match_token(TokenType::Comma) {
3340                            fargs.push(self.parse_expr()?);
3341                        }
3342                    }
3343                    self.expect(TokenType::RParen)?;
3344                }
3345                let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3346                    Some((name, qs)) => (Some(name), qs),
3347                    None => (None, QuoteStyle::None),
3348                };
3349                // Optional `[AS] col1[, col2, ...]` column list. Hive
3350                // allows the AS to be omitted entirely; Spark sometimes
3351                // emits `tbl_name col`. Consume names while we keep seeing
3352                // identifier-then-comma pairs.
3353                let _ = self.match_token(TokenType::As);
3354                if self.is_name_token() {
3355                    self.advance();
3356                    while self.match_token(TokenType::Comma) {
3357                        if !self.is_name_token() {
3358                            break;
3359                        }
3360                        self.advance();
3361                    }
3362                }
3363                joins.push(JoinClause {
3364                    join_type: JoinType::Cross,
3365                    table: TableSource::TableFunction {
3366                        name: fname,
3367                        args: fargs,
3368                        alias,
3369                        alias_quote_style,
3370                    },
3371                    on: None,
3372                    using: Vec::new(),
3373                });
3374                continue;
3375            }
3376            // ClickHouse: ARRAY JOIN / LEFT ARRAY JOIN — flatten arrays as join source.
3377            // We model it as a CROSS JOIN over the array expression.
3378            let saved_array = self.pos;
3379            let _left_array = self.match_token(TokenType::Left);
3380            if self.match_token(TokenType::Array) && self.match_token(TokenType::Join) {
3381                // parse the array expression(s) — comma-separated
3382                let mut sources = Vec::new();
3383                loop {
3384                    // ClickHouse permits inline array literals as the source:
3385                    //   ARRAY JOIN [1,2,3] AS x, [(...), (...)] AS y
3386                    // Wrap as Unnest so we don't reject the syntax.
3387                    let src = if matches!(self.peek_type(), TokenType::LBracket) {
3388                        let arr = self.parse_primary()?;
3389                        let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3390                            Some((name, qs)) => (Some(name), qs),
3391                            None => (None, QuoteStyle::None),
3392                        };
3393                        TableSource::Unnest {
3394                            expr: Box::new(arr),
3395                            alias,
3396                            alias_quote_style,
3397                            with_offset: false,
3398                        }
3399                    } else {
3400                        self.parse_table_source()?
3401                    };
3402                    sources.push(src);
3403                    if !self.match_token(TokenType::Comma) {
3404                        break;
3405                    }
3406                }
3407                for src in sources {
3408                    joins.push(JoinClause {
3409                        join_type: JoinType::Cross,
3410                        table: src,
3411                        on: None,
3412                        using: Vec::new(),
3413                    });
3414                }
3415                continue;
3416            } else {
3417                self.pos = saved_array;
3418            }
3419            // ClickHouse / Hive join strictness modifiers — consume and drop:
3420            //   GLOBAL? ALL | ANY | SEMI | ANTI | ASOF [LEFT|RIGHT|INNER|OUTER] JOIN
3421            let saved_strictness = self.pos;
3422            let _global_prefix = self.check_keyword("GLOBAL") && {
3423                self.advance();
3424                true
3425            };
3426            let consumed_strictness = if self.match_token(TokenType::All) {
3427                true
3428            } else if self.match_token(TokenType::Any) {
3429                true
3430            } else if self.check_keyword("SEMI")
3431                || self.check_keyword("ANTI")
3432                || self.check_keyword("ASOF")
3433                || self.check_keyword("PASTE")
3434            {
3435                self.advance();
3436                // DuckDB / ClickHouse allow compound forms like
3437                // `ASOF ANTI JOIN` / `ASOF SEMI JOIN` — absorb a
3438                // following second strictness keyword too.
3439                if self.check_keyword("SEMI")
3440                    || self.check_keyword("ANTI")
3441                    || self.check_keyword("ASOF")
3442                {
3443                    self.advance();
3444                }
3445                true
3446            } else {
3447                _global_prefix
3448            };
3449            // If the strictness modifier wasn't followed by a join keyword,
3450            // rewind so we don't accidentally consume a stray ALL/ANY (e.g.
3451            // `ORDER BY ALL`).
3452            if consumed_strictness
3453                && !matches!(
3454                    self.peek_type(),
3455                    TokenType::Join
3456                        | TokenType::Inner
3457                        | TokenType::Left
3458                        | TokenType::Right
3459                        | TokenType::Full
3460                        | TokenType::Cross
3461                )
3462            {
3463                self.pos = saved_strictness;
3464            }
3465            let join_type = match self.peek_type() {
3466                // `FROM a, b` is treated as `FROM a CROSS JOIN b`. Note the
3467                // SQL standard gives comma a lower precedence than explicit
3468                // JOIN operators (so `FROM a, b JOIN c ON ...` should be
3469                // `a CROSS JOIN (b JOIN c ...)`), but we flatten everything
3470                // into a left-deep chain. Column resolution still works for
3471                // the common cases since the join order is associative when
3472                // ON-clauses only reference adjacent tables.
3473                TokenType::Comma => {
3474                    self.advance();
3475                    JoinType::Cross
3476                }
3477                // `NATURAL [LEFT|RIGHT|FULL [OUTER]] JOIN tbl` — auto-equi-join
3478                // on shared column names. We don't model NATURAL semantics yet;
3479                // promote to the corresponding non-natural join type and treat
3480                // the implicit USING clause as empty.
3481                t if matches!(t, TokenType::Identifier)
3482                    && self.peek().value.eq_ignore_ascii_case("NATURAL") =>
3483                {
3484                    self.advance(); // NATURAL
3485                    let jt = match self.peek_type() {
3486                        TokenType::Left => {
3487                            self.advance();
3488                            let _ = self.match_token(TokenType::Outer);
3489                            JoinType::Left
3490                        }
3491                        TokenType::Right => {
3492                            self.advance();
3493                            let _ = self.match_token(TokenType::Outer);
3494                            JoinType::Right
3495                        }
3496                        TokenType::Full => {
3497                            self.advance();
3498                            let _ = self.match_token(TokenType::Outer);
3499                            JoinType::Full
3500                        }
3501                        TokenType::Inner => {
3502                            self.advance();
3503                            JoinType::Inner
3504                        }
3505                        _ => JoinType::Inner,
3506                    };
3507                    self.expect(TokenType::Join)?;
3508                    jt
3509                }
3510                // MySQL `STRAIGHT_JOIN` — non-reordered INNER JOIN.
3511                t if matches!(t, TokenType::Identifier)
3512                    && self.peek().value.eq_ignore_ascii_case("STRAIGHT_JOIN") =>
3513                {
3514                    self.advance();
3515                    JoinType::Inner
3516                }
3517                TokenType::Join => {
3518                    self.advance();
3519                    JoinType::Inner
3520                }
3521                TokenType::Inner => {
3522                    self.advance();
3523                    self.expect(TokenType::Join)?;
3524                    JoinType::Inner
3525                }
3526                TokenType::Left => {
3527                    self.advance();
3528                    let _ = self.match_token(TokenType::Outer);
3529                    // Hive / Spark: LEFT SEMI JOIN / LEFT ANTI JOIN
3530                    let _ = self.check_keyword("SEMI") && {
3531                        self.advance();
3532                        true
3533                    } || self.check_keyword("ANTI") && {
3534                        self.advance();
3535                        true
3536                    };
3537                    // ClickHouse: LEFT ANY|ALL JOIN
3538                    let _ = self.match_token(TokenType::Any) || self.match_token(TokenType::All);
3539                    // Some dialects (Spark/Hive variants) allow a trailing
3540                    // OUTER after the strictness modifier.
3541                    let _ = self.match_token(TokenType::Outer);
3542                    self.expect(TokenType::Join)?;
3543                    JoinType::Left
3544                }
3545                TokenType::Right => {
3546                    self.advance();
3547                    let _ = self.match_token(TokenType::Outer);
3548                    let _ = self.check_keyword("SEMI") && {
3549                        self.advance();
3550                        true
3551                    } || self.check_keyword("ANTI") && {
3552                        self.advance();
3553                        true
3554                    };
3555                    let _ = self.match_token(TokenType::Any) || self.match_token(TokenType::All);
3556                    let _ = self.match_token(TokenType::Outer);
3557                    self.expect(TokenType::Join)?;
3558                    JoinType::Right
3559                }
3560                TokenType::Full => {
3561                    self.advance();
3562                    let _ = self.match_token(TokenType::Outer);
3563                    self.expect(TokenType::Join)?;
3564                    JoinType::Full
3565                }
3566                TokenType::Cross => {
3567                    self.advance();
3568                    // T-SQL `CROSS APPLY <source>` ≈ `CROSS JOIN LATERAL ...`.
3569                    if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("APPLY") {
3570                        self.advance();
3571                        JoinType::Cross
3572                    } else {
3573                        self.expect(TokenType::Join)?;
3574                        JoinType::Cross
3575                    }
3576                }
3577                TokenType::Outer => {
3578                    // T-SQL `OUTER APPLY <source>` ≈ `LEFT JOIN LATERAL ... ON TRUE`.
3579                    self.advance();
3580                    if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("APPLY") {
3581                        self.advance();
3582                        JoinType::Left
3583                    } else {
3584                        break;
3585                    }
3586                }
3587                _ => break,
3588            };
3589
3590            let table = self.parse_table_source()?;
3591            let mut on = None;
3592            let mut using = vec![];
3593
3594            if self.match_token(TokenType::On) {
3595                on = Some(self.parse_expr()?);
3596            } else if self.match_token(TokenType::Using) {
3597                // ClickHouse permits a bare column name without parens:
3598                // `JOIN t USING k`.
3599                if self.match_token(TokenType::LParen) {
3600                    using = vec![self.expect_name()?];
3601                    while self.match_token(TokenType::Comma) {
3602                        using.push(self.expect_name()?);
3603                    }
3604                    self.expect(TokenType::RParen)?;
3605                } else {
3606                    using = vec![self.expect_name()?];
3607                    while self.match_token(TokenType::Comma) {
3608                        if !self.is_name_token() {
3609                            break;
3610                        }
3611                        using.push(self.expect_name()?);
3612                    }
3613                }
3614            }
3615
3616            joins.push(JoinClause {
3617                join_type,
3618                table,
3619                on,
3620                using,
3621            });
3622        }
3623        Ok(joins)
3624    }
3625
3626    fn parse_order_by_items(&mut self) -> Result<Vec<OrderByItem>> {
3627        let mut items = Vec::new();
3628        // DuckDB / Snowflake `ORDER BY ALL` shortcut.
3629        if self.match_token(TokenType::All) {
3630            let ascending = if self.match_token(TokenType::Desc) {
3631                false
3632            } else {
3633                let _ = self.match_token(TokenType::Asc);
3634                true
3635            };
3636            items.push(OrderByItem {
3637                expr: Expr::Wildcard,
3638                ascending,
3639                nulls_first: None,
3640            });
3641            return Ok(items);
3642        }
3643        loop {
3644            // MySQL: `ORDER BY BINARY col [ASC|DESC]` — BINARY here is a
3645            // collation modifier on the sort key. Swallow it; the rest of
3646            // the expression parses normally.
3647            if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("BINARY") {
3648                let saved = self.pos;
3649                self.advance();
3650                // Only consume BINARY when followed by something that can
3651                // start an order-by key (name, literal, paren, etc.); if it
3652                // looks like the end of the list, rewind.
3653                if matches!(
3654                    self.peek_type(),
3655                    TokenType::Comma | TokenType::Semicolon | TokenType::Eof | TokenType::RParen
3656                ) {
3657                    self.pos = saved;
3658                }
3659            }
3660            let expr = self.parse_expr()?;
3661            // ClickHouse: `ORDER BY expr AS alias`. Swallow the alias.
3662            if self.match_token(TokenType::As) && self.is_name_token() {
3663                self.advance();
3664            }
3665            let ascending = if self.match_token(TokenType::Desc) {
3666                false
3667            } else {
3668                let _ = self.match_token(TokenType::Asc);
3669                true
3670            };
3671
3672            let nulls_first = if self.match_token(TokenType::Nulls) {
3673                if self.match_token(TokenType::First) {
3674                    Some(true)
3675                } else {
3676                    self.expect(TokenType::Identifier)?; // LAST
3677                    Some(false)
3678                }
3679            } else {
3680                None
3681            };
3682
3683            items.push(OrderByItem {
3684                expr,
3685                ascending,
3686                nulls_first,
3687            });
3688            if !self.match_token(TokenType::Comma) {
3689                break;
3690            }
3691        }
3692        Ok(items)
3693    }
3694
3695    fn parse_expr_list(&mut self) -> Result<Vec<Expr>> {
3696        let mut exprs = vec![self.parse_expr()?];
3697        while self.match_token(TokenType::Comma) {
3698            // Tolerate a trailing comma — DuckDB / PostgreSQL accept
3699            // `IN ('a', 'b', )` and similar list shapes.
3700            if matches!(self.peek_type(), TokenType::RParen | TokenType::RBracket) {
3701                break;
3702            }
3703            exprs.push(self.parse_expr()?);
3704        }
3705        Ok(exprs)
3706    }
3707
3708    /// Parse a comma-separated expression list where each item may carry an
3709    /// inline alias (`expr AS name` or `expr name`). Used for dialects (notably
3710    /// ClickHouse) that permit aliases inside partition/grouping lists.
3711    fn parse_expr_list_allow_item_alias(&mut self) -> Result<Vec<Expr>> {
3712        let mut exprs = Vec::new();
3713        loop {
3714            exprs.push(self.parse_expr()?);
3715            if self.match_token(TokenType::As) && self.is_name_token() {
3716                self.advance();
3717            }
3718            if !self.match_token(TokenType::Comma) {
3719                break;
3720            }
3721            if matches!(self.peek_type(), TokenType::RParen | TokenType::RBracket) {
3722                break;
3723            }
3724        }
3725        Ok(exprs)
3726    }
3727
3728    /// Parse array-literal elements: comma-separated expressions, each
3729    /// optionally followed by `AS alias` (ClickHouse lets bindings
3730    /// appear inside `[…]`). The closing token is the caller's
3731    /// responsibility.
3732    fn parse_array_items(&mut self, close: TokenType) -> Result<Vec<Expr>> {
3733        if self.peek_type() == &close {
3734            return Ok(vec![]);
3735        }
3736        let mut items = Vec::new();
3737        loop {
3738            let expr = self.parse_expr()?;
3739            if self.match_token(TokenType::As) {
3740                let _ = self.parse_optional_alias();
3741            }
3742            items.push(expr);
3743            if !self.match_token(TokenType::Comma) {
3744                break;
3745            }
3746        }
3747        Ok(items)
3748    }
3749
3750    /// Parse a GROUP BY list, which may contain regular expressions,
3751    /// CUBE(...), ROLLUP(...), and GROUPING SETS(...).
3752    fn parse_group_by_list(&mut self) -> Result<Vec<Expr>> {
3753        // DuckDB / Snowflake `GROUP BY ALL` shortcut — emit a wildcard
3754        // marker so downstream code can recognise it. PostgreSQL also
3755        // allows `GROUP BY ALL <col>, <col>` (treated identically to a
3756        // regular GROUP BY list); fall through to the normal parser when
3757        // the next token is a column expression rather than a clause
3758        // terminator.
3759        if self.match_token(TokenType::All) {
3760            let terminates = matches!(
3761                self.peek_type(),
3762                TokenType::Comma
3763                    | TokenType::Semicolon
3764                    | TokenType::Eof
3765                    | TokenType::RParen
3766                    | TokenType::Having
3767                    | TokenType::Order
3768                    | TokenType::Limit
3769                    | TokenType::Offset
3770                    | TokenType::Window
3771                    | TokenType::Union
3772                    | TokenType::Intersect
3773                    | TokenType::Except
3774                    | TokenType::Qualify
3775            );
3776            if terminates {
3777                return Ok(vec![Expr::Wildcard]);
3778            }
3779            // Followed by a real grouping expression — fall through.
3780        }
3781        let mut items = vec![self.parse_group_by_item()?];
3782        // ClickHouse: `GROUP BY col AS alias [, …]` — swallow alias.
3783        if self.match_token(TokenType::As) && self.is_name_token() {
3784            self.advance();
3785        }
3786        // MySQL: `GROUP BY col ASC|DESC [, …]` — swallow direction.
3787        let _ = self.match_token(TokenType::Asc) || self.match_token(TokenType::Desc);
3788        while self.match_token(TokenType::Comma) {
3789            items.push(self.parse_group_by_item()?);
3790            if self.match_token(TokenType::As) && self.is_name_token() {
3791                self.advance();
3792            }
3793            let _ = self.match_token(TokenType::Asc) || self.match_token(TokenType::Desc);
3794        }
3795        Ok(items)
3796    }
3797
3798    /// Parse a single GROUP BY item: a CUBE, ROLLUP, GROUPING SETS, or regular expression.
3799    fn parse_group_by_item(&mut self) -> Result<Expr> {
3800        match self.peek_type() {
3801            TokenType::Cube => {
3802                self.advance();
3803                self.expect(TokenType::LParen)?;
3804                let exprs = if self.peek_type() == &TokenType::RParen {
3805                    vec![]
3806                } else {
3807                    self.parse_group_by_element_list()?
3808                };
3809                self.expect(TokenType::RParen)?;
3810                Ok(Expr::Cube { exprs })
3811            }
3812            TokenType::Rollup => {
3813                self.advance();
3814                self.expect(TokenType::LParen)?;
3815                let exprs = if self.peek_type() == &TokenType::RParen {
3816                    vec![]
3817                } else {
3818                    self.parse_group_by_element_list()?
3819                };
3820                self.expect(TokenType::RParen)?;
3821                Ok(Expr::Rollup { exprs })
3822            }
3823            TokenType::Grouping => {
3824                // Could be GROUPING SETS or GROUPING() function
3825                let saved = self.pos;
3826                self.advance();
3827                if self.peek_type() == &TokenType::Sets {
3828                    // GROUPING SETS (...)
3829                    self.advance();
3830                    self.expect(TokenType::LParen)?;
3831                    let sets = self.parse_grouping_sets_elements()?;
3832                    self.expect(TokenType::RParen)?;
3833                    Ok(Expr::GroupingSets { sets })
3834                } else {
3835                    // It's the GROUPING() function, backtrack and parse as expression
3836                    self.pos = saved;
3837                    self.parse_expr()
3838                }
3839            }
3840            _ => self.parse_expr(),
3841        }
3842    }
3843
3844    /// Parse elements inside CUBE(...) or ROLLUP(...).
3845    /// Each element can be a single expression or a parenthesized tuple of expressions.
3846    fn parse_group_by_element_list(&mut self) -> Result<Vec<Expr>> {
3847        let mut items = vec![self.parse_group_by_element()?];
3848        while self.match_token(TokenType::Comma) {
3849            items.push(self.parse_group_by_element()?);
3850        }
3851        Ok(items)
3852    }
3853
3854    /// Parse a single element inside CUBE/ROLLUP: either `expr` or `(expr, expr, ...)`.
3855    fn parse_group_by_element(&mut self) -> Result<Expr> {
3856        if self.peek_type() == &TokenType::LParen {
3857            self.advance();
3858            let exprs = self.parse_expr_list()?;
3859            self.expect(TokenType::RParen)?;
3860            if exprs.len() == 1 {
3861                Ok(Expr::Nested(Box::new(exprs.into_iter().next().unwrap())))
3862            } else {
3863                Ok(Expr::Tuple(exprs))
3864            }
3865        } else {
3866            let e = self.parse_expr()?;
3867            // ClickHouse: `GROUP BY expr AS alias`. Swallow the alias.
3868            if self.match_token(TokenType::As) && self.is_name_token() {
3869                self.advance();
3870            }
3871            Ok(e)
3872        }
3873    }
3874
3875    /// Parse elements inside GROUPING SETS (...).
3876    /// Each element can be: (), (expr, ...), CUBE(...), ROLLUP(...), or a single expr.
3877    fn parse_grouping_sets_elements(&mut self) -> Result<Vec<Expr>> {
3878        let mut items = vec![self.parse_grouping_sets_element()?];
3879        while self.match_token(TokenType::Comma) {
3880            items.push(self.parse_grouping_sets_element()?);
3881        }
3882        Ok(items)
3883    }
3884
3885    /// Parse a single GROUPING SETS element.
3886    fn parse_grouping_sets_element(&mut self) -> Result<Expr> {
3887        match self.peek_type() {
3888            TokenType::Cube => {
3889                self.advance();
3890                self.expect(TokenType::LParen)?;
3891                let exprs = if self.peek_type() == &TokenType::RParen {
3892                    vec![]
3893                } else {
3894                    self.parse_group_by_element_list()?
3895                };
3896                self.expect(TokenType::RParen)?;
3897                Ok(Expr::Cube { exprs })
3898            }
3899            TokenType::Rollup => {
3900                self.advance();
3901                self.expect(TokenType::LParen)?;
3902                let exprs = if self.peek_type() == &TokenType::RParen {
3903                    vec![]
3904                } else {
3905                    self.parse_group_by_element_list()?
3906                };
3907                self.expect(TokenType::RParen)?;
3908                Ok(Expr::Rollup { exprs })
3909            }
3910            TokenType::LParen => {
3911                self.advance();
3912                if self.peek_type() == &TokenType::RParen {
3913                    // Empty grouping set: ()
3914                    self.advance();
3915                    Ok(Expr::Tuple(vec![]))
3916                } else {
3917                    let exprs = self.parse_expr_list()?;
3918                    self.expect(TokenType::RParen)?;
3919                    if exprs.len() == 1 {
3920                        Ok(Expr::Nested(Box::new(exprs.into_iter().next().unwrap())))
3921                    } else {
3922                        Ok(Expr::Tuple(exprs))
3923                    }
3924                }
3925            }
3926            _ => self.parse_expr(),
3927        }
3928    }
3929
3930    // ── INSERT ──────────────────────────────────────────────────────
3931
3932    fn parse_insert(&mut self) -> Result<InsertStatement> {
3933        // Accept MySQL `REPLACE INTO ...` as a synonym for `INSERT INTO ...`.
3934        if !self.match_token(TokenType::Insert) {
3935            self.expect(TokenType::Replace)?;
3936        }
3937        // SQLite / DuckDB conflict-resolution prefix:
3938        //   `INSERT OR REPLACE|IGNORE|FAIL|ABORT|ROLLBACK INTO ...`.
3939        // Swallow opaquely; we don't model conflict resolution at the
3940        // statement level (ON CONFLICT covers most cases downstream).
3941        if self.match_token(TokenType::Or) {
3942            if self.match_token(TokenType::Replace) {
3943                // matched
3944            } else if self.match_token(TokenType::Ignore) {
3945                // matched
3946            } else if self.is_name_token() {
3947                let v = self.peek().value.to_uppercase();
3948                if matches!(v.as_str(), "FAIL" | "ABORT" | "ROLLBACK") {
3949                    self.advance();
3950                }
3951            }
3952        }
3953        // MySQL modifiers between INSERT/REPLACE and INTO:
3954        //   `INSERT LOW_PRIORITY|DELAYED|HIGH_PRIORITY [IGNORE] INTO ...`,
3955        //   `INSERT IGNORE INTO ...`. Swallow them so the rest parses.
3956        loop {
3957            if self.match_token(TokenType::Ignore) {
3958                continue;
3959            }
3960            if self.is_name_token() {
3961                let v = self.peek().value.to_uppercase();
3962                if matches!(v.as_str(), "LOW_PRIORITY" | "DELAYED" | "HIGH_PRIORITY") {
3963                    self.advance();
3964                    continue;
3965                }
3966            }
3967            break;
3968        }
3969        let _ = self.match_token(TokenType::Into);
3970        // Hive: `INSERT OVERWRITE [LOCAL] DIRECTORY '/path'` or
3971        // `INSERT OVERWRITE TABLE tbl ...`. Consume OVERWRITE (tokenized as
3972        // an identifier) and any DIRECTORY clause that follows.
3973        if self.check_keyword("OVERWRITE") {
3974            self.advance();
3975            if self.check_keyword("LOCAL") {
3976                self.advance();
3977            }
3978            if self.check_keyword("DIRECTORY") {
3979                self.advance();
3980                // Consume `'path'` (string) and any STORED AS / ROW FORMAT
3981                // clauses until we hit SELECT/WITH/LParen/VALUES/EOF.
3982                if matches!(self.peek_type(), TokenType::String) {
3983                    self.advance();
3984                }
3985                while !matches!(
3986                    self.peek_type(),
3987                    TokenType::Select
3988                        | TokenType::With
3989                        | TokenType::LParen
3990                        | TokenType::Values
3991                        | TokenType::Eof
3992                        | TokenType::Semicolon
3993                ) {
3994                    self.advance();
3995                }
3996            }
3997        }
3998        // Hive: `INSERT INTO TABLE tbl ...` and `INSERT OVERWRITE TABLE tbl ...`.
3999        let _ = self.match_token(TokenType::Table);
4000        let table = self.parse_table_ref()?;
4001
4002        // Hive `PARTITION (k=v, ...)` between table and column list / source.
4003        if self.peek_type() == &TokenType::Partition {
4004            self.advance();
4005            if self.match_token(TokenType::LParen) {
4006                let mut depth = 1;
4007                while depth > 0 {
4008                    match self.peek_type() {
4009                        TokenType::LParen => depth += 1,
4010                        TokenType::RParen => depth -= 1,
4011                        TokenType::Eof => break,
4012                        _ => {}
4013                    }
4014                    if depth == 0 {
4015                        self.advance();
4016                        break;
4017                    }
4018                    self.advance();
4019                }
4020            }
4021        }
4022
4023        let columns = if self.match_token(TokenType::LParen) {
4024            // BigQuery / SQLFluff fixture: `INSERT INTO t (SELECT ... )` —
4025            // no column list, the parenthesized SELECT is the source.
4026            // Rewind to the `(` and let the source dispatch handle it.
4027            if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
4028                self.pos -= 1;
4029                Vec::new()
4030            } else {
4031                // ClickHouse `INSERT INTO t (COLUMNS('.*') EXCEPT (...))` — when
4032                // the list contains a function call or anything other than plain
4033                // identifiers, fall back to a balanced-paren swallow.
4034                let saved = self.pos;
4035                let try_simple: Result<Vec<String>> = (|| {
4036                    let mut cols = vec![self.parse_dotted_name()?];
4037                    while self.match_token(TokenType::Comma) {
4038                        cols.push(self.parse_dotted_name()?);
4039                    }
4040                    self.expect(TokenType::RParen)?;
4041                    Ok(cols)
4042                })();
4043                match try_simple {
4044                    Ok(c) => c,
4045                    Err(_) => {
4046                        self.pos = saved;
4047                        let mut depth = 1_i32;
4048                        while depth > 0 && self.peek_type() != &TokenType::Eof {
4049                            match self.peek_type() {
4050                                TokenType::LParen => depth += 1,
4051                                TokenType::RParen => depth -= 1,
4052                                _ => {}
4053                            }
4054                            self.advance();
4055                        }
4056                        Vec::new()
4057                    }
4058                }
4059            }
4060        } else {
4061            vec![]
4062        };
4063
4064        // ClickHouse `INSERT INTO t [(cols)] SETTINGS k=v[, …] VALUES …`.
4065        // Swallow the SETTINGS clause before the source clause so the
4066        // surrounding parse completes.
4067        if self.check_keyword("SETTINGS") {
4068            self.advance();
4069            loop {
4070                if !self.is_name_token() {
4071                    break;
4072                }
4073                self.advance(); // key
4074                if !self.match_token(TokenType::Eq) {
4075                    break;
4076                }
4077                // value: number / string / identifier / unary-signed number
4078                let _ = self.match_token(TokenType::Minus) || self.match_token(TokenType::Plus);
4079                if matches!(self.peek_type(), TokenType::Number | TokenType::String)
4080                    || self.is_name_token()
4081                {
4082                    self.advance();
4083                }
4084                if !self.match_token(TokenType::Comma) {
4085                    break;
4086                }
4087            }
4088        }
4089
4090        let source = if self.match_token(TokenType::Values) || self.match_keyword("VALUE") {
4091            let mut rows = Vec::new();
4092            loop {
4093                self.expect(TokenType::LParen)?;
4094                // MySQL allows `VALUES ()` as an empty row to insert all
4095                // defaults — accept and emit as an empty row.
4096                let row = if self.peek_type() == &TokenType::RParen {
4097                    Vec::new()
4098                } else {
4099                    self.parse_expr_list()?
4100                };
4101                self.expect(TokenType::RParen)?;
4102                rows.push(row);
4103                // ClickHouse permits comma-less rows: `VALUES (1)(2)(3)`.
4104                if self.peek_type() == &TokenType::LParen {
4105                    continue;
4106                }
4107                if !self.match_token(TokenType::Comma) {
4108                    break;
4109                }
4110                // Trailing comma: `VALUES (1,2), (3,4),` — DuckDB / sqlfluff
4111                // fixture truncation. Accept and stop the row loop.
4112                if !matches!(self.peek_type(), TokenType::LParen) {
4113                    break;
4114                }
4115            }
4116            InsertSource::Values(rows)
4117        } else if matches!(
4118            self.peek_type(),
4119            TokenType::Select | TokenType::With | TokenType::LParen
4120        ) {
4121            InsertSource::Query(Box::new(self.parse_statement_inner()?))
4122        } else if self.match_token(TokenType::Default) {
4123            self.expect(TokenType::Values)?;
4124            InsertSource::Default
4125        } else if self.match_token(TokenType::Set) {
4126            // MySQL `INSERT INTO t SET col = val, col = val, ...`.
4127            // Collapse into a single-row VALUES placeholder by collecting
4128            // the right-hand expressions; column names are dropped.
4129            let mut row = Vec::new();
4130            loop {
4131                let _ = self.expect_name()?;
4132                self.expect(TokenType::Eq)?;
4133                row.push(self.parse_expr()?);
4134                if !self.match_token(TokenType::Comma) {
4135                    break;
4136                }
4137            }
4138            InsertSource::Values(vec![row])
4139        } else if self.peek_type() == &TokenType::From {
4140            // DuckDB `INSERT INTO t FROM source` shorthand for
4141            // `INSERT INTO t SELECT * FROM source`. Synthesize a SELECT *
4142            // statement so the existing query path handles it.
4143            self.advance();
4144            let from = Some(FromClause {
4145                source: self.parse_table_source()?,
4146            });
4147            let joins = self.parse_joins()?;
4148            let stmt = Statement::Select(SelectStatement {
4149                comments: vec![],
4150                ctes: vec![],
4151                distinct: false,
4152                top: None,
4153                columns: vec![SelectItem::Wildcard],
4154                from,
4155                joins,
4156                where_clause: None,
4157                group_by: vec![],
4158                having: None,
4159                order_by: vec![],
4160                limit: None,
4161                offset: None,
4162                fetch_first: None,
4163                qualify: None,
4164                window_definitions: vec![],
4165            });
4166            InsertSource::Query(Box::new(stmt))
4167        } else if self.peek().value.eq_ignore_ascii_case("FORMAT") {
4168            // ClickHouse `INSERT INTO t FORMAT name <raw payload>`.
4169            // Swallow the format name and the remainder of the statement
4170            // as opaque bytes; we cannot parse JSONEachRow / TabSeparated
4171            // payloads, but we should not reject the statement.
4172            self.advance();
4173            let _ = self.expect_name();
4174            while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon) {
4175                self.advance();
4176            }
4177            InsertSource::Default
4178        } else {
4179            return Err(SqlglotError::ParserError {
4180                message: "Expected VALUES, SELECT, or DEFAULT VALUES after INSERT".into(),
4181            });
4182        };
4183
4184        // MySQL 8.0.19+ row alias: `INSERT INTO t (cols) VALUES (...) AS
4185        // alias [(col_alias, ...)] ON DUPLICATE KEY UPDATE ...`. Swallow
4186        // the alias so the ON DUPLICATE clause parses.
4187        if self.peek_type() == &TokenType::As
4188            && self
4189                .peek_offset(1)
4190                .map(|t| {
4191                    matches!(
4192                        t.token_type,
4193                        TokenType::Identifier
4194                            | TokenType::Key
4195                            | TokenType::Year
4196                            | TokenType::Month
4197                            | TokenType::Day
4198                            | TokenType::Hour
4199                            | TokenType::Minute
4200                            | TokenType::Second
4201                    ) || t
4202                        .value
4203                        .chars()
4204                        .next()
4205                        .is_some_and(|c| c.is_alphabetic() || c == '_')
4206                })
4207                .unwrap_or(false)
4208        {
4209            self.advance(); // AS
4210            self.advance(); // alias name
4211            if self.match_token(TokenType::LParen) {
4212                let mut depth = 1_i32;
4213                while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
4214                    match self.peek_type() {
4215                        TokenType::LParen => depth += 1,
4216                        TokenType::RParen => depth -= 1,
4217                        _ => {}
4218                    }
4219                    self.advance();
4220                }
4221            }
4222        }
4223
4224        // MySQL `ON DUPLICATE KEY UPDATE col=val, ...`. Swallow the clause.
4225        if self.peek_type() == &TokenType::On
4226            && self
4227                .peek_offset(1)
4228                .map(|t| t.value.eq_ignore_ascii_case("DUPLICATE"))
4229                .unwrap_or(false)
4230        {
4231            self.advance();
4232            self.advance();
4233            // KEY UPDATE
4234            if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("KEY") {
4235                self.advance();
4236            }
4237            if self.match_token(TokenType::Update) {
4238                // assignments until end-of-statement
4239                loop {
4240                    let _ = self.expect_name();
4241                    if !self.match_token(TokenType::Eq) {
4242                        break;
4243                    }
4244                    let _ = self.parse_expr();
4245                    if !self.match_token(TokenType::Comma) {
4246                        break;
4247                    }
4248                }
4249            }
4250        }
4251
4252        // ON CONFLICT
4253        let on_conflict = if self.match_token(TokenType::On) {
4254            if self.match_token(TokenType::Conflict) {
4255                let columns = if self.match_token(TokenType::LParen) {
4256                    self.parse_parenthesized_raw_items()?
4257                } else {
4258                    vec![]
4259                };
4260                self.expect(TokenType::Do)?;
4261                let action = if self.match_token(TokenType::Nothing) {
4262                    ConflictAction::DoNothing
4263                } else {
4264                    self.expect(TokenType::Update)?;
4265                    self.expect(TokenType::Set)?;
4266                    let mut assignments = Vec::new();
4267                    loop {
4268                        let col = self.expect_name()?;
4269                        self.expect(TokenType::Eq)?;
4270                        let val = self.parse_expr()?;
4271                        assignments.push((col, val));
4272                        if !self.match_token(TokenType::Comma) {
4273                            break;
4274                        }
4275                    }
4276                    ConflictAction::DoUpdate(assignments)
4277                };
4278                // Postgres / DuckDB allow `ON CONFLICT (...) DO UPDATE SET
4279                // ... WHERE predicate` to limit the update. Swallow the
4280                // predicate opaquely.
4281                if self.match_token(TokenType::Where) {
4282                    let _ = self.parse_expr()?;
4283                }
4284                Some(OnConflict { columns, action })
4285            } else {
4286                None
4287            }
4288        } else {
4289            None
4290        };
4291
4292        let returning = if self.match_token(TokenType::Returning) {
4293            self.parse_select_items()?
4294        } else {
4295            vec![]
4296        };
4297
4298        Ok(InsertStatement {
4299            comments: vec![],
4300            table,
4301            columns,
4302            source,
4303            on_conflict,
4304            returning,
4305        })
4306    }
4307
4308    // ── UPDATE ──────────────────────────────────────────────────────
4309
4310    fn parse_update(&mut self) -> Result<UpdateStatement> {
4311        self.expect(TokenType::Update)?;
4312        let table = self.parse_table_ref()?;
4313        // MySQL multi-table UPDATE: `UPDATE t1, t2 [, ...] SET ...`.
4314        // Swallow the additional table refs (we keep only the first as
4315        // the primary target).
4316        while self.match_token(TokenType::Comma) {
4317            let _ = self.parse_table_ref()?;
4318        }
4319        // PG SQL:2011 temporal `UPDATE t FOR PORTION OF col FROM a TO b
4320        // [AS alias] SET ...`. Swallow the qualifier verbatim.
4321        if self.check_keyword("FOR")
4322            && self
4323                .peek_offset(1)
4324                .map(|t| t.value.eq_ignore_ascii_case("PORTION"))
4325                .unwrap_or(false)
4326        {
4327            while !matches!(
4328                self.peek_type(),
4329                TokenType::Set | TokenType::Eof | TokenType::Semicolon
4330            ) {
4331                self.advance();
4332            }
4333        }
4334        // MySQL `UPDATE t PARTITION (p0[, p1]) SET ...` — swallow.
4335        if matches!(self.peek_type(), TokenType::Partition)
4336            && matches!(
4337                self.peek_offset(1).map(|t| &t.token_type),
4338                Some(TokenType::LParen)
4339            )
4340        {
4341            self.advance();
4342            self.advance();
4343            let mut depth = 1;
4344            while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
4345                match self.peek_type() {
4346                    TokenType::LParen => depth += 1,
4347                    TokenType::RParen => {
4348                        depth -= 1;
4349                        if depth == 0 {
4350                            self.advance();
4351                            break;
4352                        }
4353                    }
4354                    _ => {}
4355                }
4356                self.advance();
4357            }
4358        }
4359        // MySQL multi-table UPDATE: `UPDATE t1 [LEFT|RIGHT|INNER|CROSS] JOIN
4360        // t2 ON ... SET ...`. Swallow the joins so the existing single-target
4361        // update parses; the joined tables are dropped from the AST.
4362        let _ = self.parse_joins();
4363        self.expect(TokenType::Set)?;
4364
4365        let mut assignments = Vec::new();
4366        loop {
4367            // Accept qualified LHS like `alias.col` (Oracle, T-SQL idiom),
4368            // and PG/Snowflake subscripts/field access on the LHS such as
4369            // `arr[1] = …`, `arr[1:3] = …`, `obj['k']`, `(a,b) = …`.
4370            // Accept LHS row-tuple `(a, b, c) = (rhs)` (PostgreSQL).
4371            if self.peek_type() == &TokenType::LParen {
4372                let saved = self.pos;
4373                self.advance();
4374                let mut depth = 1;
4375                while depth > 0 && self.peek_type() != &TokenType::Eof {
4376                    match self.peek_type() {
4377                        TokenType::LParen => depth += 1,
4378                        TokenType::RParen => depth -= 1,
4379                        _ => {}
4380                    }
4381                    self.advance();
4382                }
4383                if self.peek_type() == &TokenType::Eq {
4384                    self.advance();
4385                    let val = self.parse_expr()?;
4386                    assignments.push(("__tuple__".to_string(), val));
4387                    if !self.match_token(TokenType::Comma) {
4388                        break;
4389                    }
4390                    continue;
4391                }
4392                self.pos = saved;
4393            }
4394            let mut col = self.expect_name()?;
4395            while self.match_token(TokenType::Dot) {
4396                col.push('.');
4397                col.push_str(&self.expect_name()?);
4398            }
4399            // Swallow `[index]` / `[a:b]` subscripts in the LHS — we don't
4400            // model array-element assignment in the AST.
4401            while self.peek_type() == &TokenType::LBracket {
4402                self.advance();
4403                let mut depth = 1;
4404                while depth > 0 && self.peek_type() != &TokenType::Eof {
4405                    match self.peek_type() {
4406                        TokenType::LBracket => depth += 1,
4407                        TokenType::RBracket => depth -= 1,
4408                        _ => {}
4409                    }
4410                    self.advance();
4411                }
4412            }
4413            self.expect(TokenType::Eq)?;
4414            let val = self.parse_expr()?;
4415            assignments.push((col, val));
4416            if !self.match_token(TokenType::Comma) {
4417                break;
4418            }
4419        }
4420
4421        let from = if self.match_token(TokenType::From) {
4422            Some(FromClause {
4423                source: self.parse_table_source()?,
4424            })
4425        } else {
4426            None
4427        };
4428
4429        let where_clause = if self.match_token(TokenType::Where) {
4430            Some(self.parse_expr()?)
4431        } else {
4432            None
4433        };
4434
4435        // Teradata `PREFERRING <expr> [PARTITION BY <list>]` skyline
4436        // clause on UPDATE. Swallow up to a known terminator.
4437        if self.check_keyword("PREFERRING") {
4438            self.advance();
4439            loop {
4440                match self.peek_type() {
4441                    TokenType::Eof
4442                    | TokenType::Semicolon
4443                    | TokenType::RParen
4444                    | TokenType::Returning => break,
4445                    _ => self.advance(),
4446                };
4447            }
4448        }
4449
4450        // MySQL: `UPDATE … [ORDER BY …] [LIMIT N]`. Swallow.
4451        if self.match_token(TokenType::Order) {
4452            self.expect(TokenType::By)?;
4453            let _ = self.parse_order_by_items()?;
4454        }
4455        if self.match_token(TokenType::Limit) {
4456            let _ = self.parse_expr()?;
4457        }
4458
4459        let returning = if self.match_token(TokenType::Returning) {
4460            self.parse_select_items()?
4461        } else {
4462            vec![]
4463        };
4464
4465        Ok(UpdateStatement {
4466            comments: vec![],
4467            table,
4468            assignments,
4469            from,
4470            where_clause,
4471            returning,
4472        })
4473    }
4474
4475    // ── DELETE ──────────────────────────────────────────────────────
4476
4477    fn parse_delete(&mut self) -> Result<DeleteStatement> {
4478        self.expect(TokenType::Delete)?;
4479        // MySQL multi-table form: `DELETE t1[, t2, ...] FROM <join expr>`.
4480        // Swallow the leading table-alias list (we don't model it) before
4481        // the mandatory FROM.
4482        let mut multi_table = false;
4483        if !matches!(self.peek_type(), TokenType::From) {
4484            let saved = self.pos;
4485            if self.is_name_token() {
4486                self.advance();
4487                let _ = self.match_token(TokenType::Dot);
4488                if self.is_name_token() {
4489                    self.advance();
4490                }
4491                while self.match_token(TokenType::Comma) {
4492                    if !self.is_name_token() {
4493                        break;
4494                    }
4495                    self.advance();
4496                    let _ = self.match_token(TokenType::Dot);
4497                    if self.is_name_token() {
4498                        self.advance();
4499                    }
4500                }
4501                if matches!(self.peek_type(), TokenType::From) {
4502                    multi_table = true;
4503                } else {
4504                    self.pos = saved;
4505                }
4506            }
4507        }
4508        // BigQuery / some Snowflake forms allow `DELETE <table> WHERE …`
4509        // (FROM optional). If FROM is missing but the next token starts a
4510        // table-ref, treat it as the implicit FROM target.
4511        let from_optional = !matches!(self.peek_type(), TokenType::From);
4512        if !from_optional {
4513            self.expect(TokenType::From)?;
4514        }
4515        let table = self.parse_table_ref()?;
4516        // MySQL: `DELETE FROM t PARTITION (p0[, p1, ...])` — swallow
4517        // partition selector.
4518        if matches!(self.peek_type(), TokenType::Partition)
4519            && matches!(
4520                self.peek_offset(1).map(|t| &t.token_type),
4521                Some(TokenType::LParen)
4522            )
4523        {
4524            self.advance();
4525            self.advance();
4526            let mut depth = 1;
4527            while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
4528                match self.peek_type() {
4529                    TokenType::LParen => depth += 1,
4530                    TokenType::RParen => {
4531                        depth -= 1;
4532                        if depth == 0 {
4533                            self.advance();
4534                            break;
4535                        }
4536                    }
4537                    _ => {}
4538                }
4539                self.advance();
4540            }
4541        }
4542        if multi_table {
4543            // Swallow JOIN clauses, additional comma-joined tables, and
4544            // any opaque tail up to USING / WHERE / RETURNING / ; / EOF.
4545            loop {
4546                if matches!(
4547                    self.peek_type(),
4548                    TokenType::Where
4549                        | TokenType::Using
4550                        | TokenType::Returning
4551                        | TokenType::Semicolon
4552                        | TokenType::Eof
4553                ) {
4554                    break;
4555                }
4556                self.advance();
4557            }
4558        }
4559
4560        let using = if self.match_token(TokenType::Using) {
4561            Some(FromClause {
4562                source: self.parse_table_source()?,
4563            })
4564        } else {
4565            None
4566        };
4567
4568        // Teradata `PREFERRING <expr> [PARTITION BY <list>]` skyline
4569        // clause on DELETE.
4570        if self.check_keyword("PREFERRING") {
4571            self.advance();
4572            loop {
4573                match self.peek_type() {
4574                    TokenType::Eof
4575                    | TokenType::Semicolon
4576                    | TokenType::Where
4577                    | TokenType::Returning
4578                    | TokenType::RParen => break,
4579                    _ => self.advance(),
4580                };
4581            }
4582        }
4583
4584        let where_clause = if self.match_token(TokenType::Where) {
4585            Some(self.parse_expr()?)
4586        } else {
4587            None
4588        };
4589
4590        // MySQL: `DELETE FROM tbl [WHERE ...] [ORDER BY ...] [LIMIT N]`.
4591        // Swallow ORDER BY and LIMIT modifiers — we don't model them on
4592        // DeleteStatement yet.
4593        if self.match_token(TokenType::Order) {
4594            self.expect(TokenType::By)?;
4595            let _ = self.parse_order_by_items()?;
4596        }
4597        if self.match_token(TokenType::Limit) {
4598            let _ = self.parse_expr()?;
4599        }
4600
4601        let returning = if self.match_token(TokenType::Returning) {
4602            self.parse_select_items()?
4603        } else {
4604            vec![]
4605        };
4606
4607        Ok(DeleteStatement {
4608            comments: vec![],
4609            table,
4610            using,
4611            where_clause,
4612            returning,
4613        })
4614    }
4615
4616    // ── MERGE ───────────────────────────────────────────────────────
4617
4618    fn parse_merge(&mut self) -> Result<MergeStatement> {
4619        self.expect(TokenType::Merge)?;
4620        let _ = self.match_token(TokenType::Into);
4621        let target = self.parse_table_ref()?;
4622
4623        self.expect(TokenType::Using)?;
4624        let source = self.parse_table_source()?;
4625
4626        // DuckDB supports `MERGE INTO t USING src USING (cols)` as a
4627        // shorthand for the ON condition (column-equality join, akin to
4628        // SQL USING for JOINs). Swallow the column list opaquely and
4629        // synthesize a trivial truthy ON expression so downstream parsing
4630        // continues. We don't model USING-style MERGE in the AST yet.
4631        let on = if self.match_token(TokenType::Using) {
4632            self.expect(TokenType::LParen)?;
4633            let _ = self.expect_name()?;
4634            while self.match_token(TokenType::Comma) {
4635                let _ = self.expect_name()?;
4636            }
4637            self.expect(TokenType::RParen)?;
4638            Expr::Boolean(true)
4639        } else {
4640            self.expect(TokenType::On)?;
4641            self.parse_expr()?
4642        };
4643
4644        let mut clauses = Vec::new();
4645        while self.match_token(TokenType::When) {
4646            clauses.push(self.parse_merge_clause()?);
4647        }
4648
4649        if clauses.is_empty() {
4650            return Err(SqlglotError::ParserError {
4651                message: "MERGE requires at least one WHEN clause".into(),
4652            });
4653        }
4654
4655        // OUTPUT clause (T-SQL extension)
4656        let output = if self.match_keyword("OUTPUT") {
4657            self.parse_select_items()?
4658        } else {
4659            vec![]
4660        };
4661
4662        // PostgreSQL: `MERGE … RETURNING <select_list>`. We don't yet model
4663        // RETURNING for MERGE, so swallow the items and discard them.
4664        if self.match_token(TokenType::Returning) {
4665            let _ = self.parse_select_items()?;
4666        }
4667
4668        Ok(MergeStatement {
4669            comments: vec![],
4670            target,
4671            source,
4672            on,
4673            clauses,
4674            output,
4675        })
4676    }
4677
4678    fn parse_merge_clause(&mut self) -> Result<MergeClause> {
4679        let kind = if self.match_token(TokenType::Not) {
4680            self.expect(TokenType::Matched)?;
4681            if self.match_keyword("BY") {
4682                if self.match_keyword("SOURCE") {
4683                    MergeClauseKind::NotMatchedBySource
4684                } else {
4685                    // BY TARGET is the default / explicit form
4686                    let _ = self.match_keyword("TARGET");
4687                    MergeClauseKind::NotMatched
4688                }
4689            } else {
4690                MergeClauseKind::NotMatched
4691            }
4692        } else {
4693            self.expect(TokenType::Matched)?;
4694            MergeClauseKind::Matched
4695        };
4696
4697        let condition = if self.match_token(TokenType::And) {
4698            Some(self.parse_expr()?)
4699        } else {
4700            None
4701        };
4702
4703        self.expect(TokenType::Then)?;
4704
4705        let action = self.parse_merge_action(&kind)?;
4706
4707        Ok(MergeClause {
4708            kind,
4709            condition,
4710            action,
4711        })
4712    }
4713
4714    fn parse_merge_action(&mut self, kind: &MergeClauseKind) -> Result<MergeAction> {
4715        if self.match_token(TokenType::Update) {
4716            self.expect(TokenType::Set)?;
4717            let mut assignments = Vec::new();
4718            loop {
4719                let mut col = self.expect_name()?;
4720                // Support dotted column names like target.col
4721                while self.match_token(TokenType::Dot) {
4722                    col.push('.');
4723                    col.push_str(&self.expect_name()?);
4724                }
4725                self.expect(TokenType::Eq)?;
4726                let val = self.parse_expr()?;
4727                assignments.push((col, val));
4728                if !self.match_token(TokenType::Comma) {
4729                    break;
4730                }
4731            }
4732            Ok(MergeAction::Update(assignments))
4733        } else if self.match_token(TokenType::Insert) {
4734            // INSERT ROW (BigQuery)
4735            if self.match_keyword("ROW") {
4736                return Ok(MergeAction::InsertRow);
4737            }
4738
4739            let columns = if self.match_token(TokenType::LParen) {
4740                let mut cols = vec![self.expect_name()?];
4741                while self.match_token(TokenType::Comma) {
4742                    cols.push(self.expect_name()?);
4743                }
4744                self.expect(TokenType::RParen)?;
4745                cols
4746            } else {
4747                vec![]
4748            };
4749
4750            self.expect(TokenType::Values)?;
4751            self.expect(TokenType::LParen)?;
4752            let values = self.parse_expr_list()?;
4753            self.expect(TokenType::RParen)?;
4754
4755            Ok(MergeAction::Insert { columns, values })
4756        } else if self.match_token(TokenType::Delete) {
4757            Ok(MergeAction::Delete)
4758        } else {
4759            Err(SqlglotError::ParserError {
4760                message: format!(
4761                    "Expected UPDATE, INSERT, or DELETE after WHEN {} THEN",
4762                    match kind {
4763                        MergeClauseKind::Matched => "MATCHED",
4764                        MergeClauseKind::NotMatched => "NOT MATCHED",
4765                        MergeClauseKind::NotMatchedBySource => "NOT MATCHED BY SOURCE",
4766                    }
4767                ),
4768            })
4769        }
4770    }
4771
4772    // ── CREATE ──────────────────────────────────────────────────────
4773
4774    fn parse_create(&mut self) -> Result<Statement> {
4775        self.expect(TokenType::Create)?;
4776
4777        let or_replace = if self.check_keyword("OR") {
4778            self.advance();
4779            self.expect(TokenType::Replace)?;
4780            true
4781        } else {
4782            false
4783        };
4784
4785        let temporary = self.match_token(TokenType::Temporary) || self.match_token(TokenType::Temp);
4786
4787        let materialized = self.match_token(TokenType::Materialized);
4788
4789        if self.match_token(TokenType::View) {
4790            return self
4791                .parse_create_view(or_replace, materialized)
4792                .map(Statement::CreateView);
4793        }
4794
4795        self.expect(TokenType::Table)?;
4796
4797        let if_not_exists = if self.match_token(TokenType::If) {
4798            self.expect(TokenType::Not)?;
4799            self.expect(TokenType::Exists)?;
4800            true
4801        } else {
4802            false
4803        };
4804
4805        let table = self.parse_table_ref_no_alias()?;
4806
4807        // CREATE TABLE ... AS SELECT ...
4808        if self.match_token(TokenType::As) {
4809            let query = self.parse_statement_inner()?;
4810            // Greenplum / Citus / etc. trailing `DISTRIBUTED BY (...)` /
4811            // `DISTRIBUTED RANDOMLY` / `DISTRIBUTED REPLICATED`. Swallow.
4812            if self.check_keyword("DISTRIBUTED") {
4813                self.advance();
4814                if self.check_keyword("BY") || matches!(self.peek_type(), TokenType::By) {
4815                    self.advance();
4816                    if self.match_token(TokenType::LParen) {
4817                        let mut depth = 1;
4818                        while depth > 0 {
4819                            match self.peek_type() {
4820                                TokenType::LParen => depth += 1,
4821                                TokenType::RParen => {
4822                                    depth -= 1;
4823                                    if depth == 0 {
4824                                        self.advance();
4825                                        break;
4826                                    }
4827                                }
4828                                TokenType::Eof => break,
4829                                _ => {}
4830                            }
4831                            self.advance();
4832                        }
4833                    }
4834                } else if self.is_name_token() {
4835                    // RANDOMLY / REPLICATED — single keyword
4836                    self.advance();
4837                }
4838            }
4839            return Ok(Statement::CreateTable(CreateTableStatement {
4840                comments: vec![],
4841                if_not_exists,
4842                temporary,
4843                table,
4844                columns: vec![],
4845                constraints: vec![],
4846                as_select: Some(Box::new(query)),
4847            }));
4848        }
4849
4850        self.expect(TokenType::LParen)?;
4851
4852        let mut columns = Vec::new();
4853        let mut constraints = Vec::new();
4854
4855        loop {
4856            // Check for table-level constraints
4857            if matches!(
4858                self.peek_type(),
4859                TokenType::Primary
4860                    | TokenType::Unique
4861                    | TokenType::Foreign
4862                    | TokenType::Check
4863                    | TokenType::Constraint
4864            ) {
4865                constraints.push(self.parse_table_constraint()?);
4866            } else if self.peek_type() != &TokenType::RParen {
4867                columns.push(self.parse_column_def()?);
4868            }
4869
4870            if !self.match_token(TokenType::Comma) {
4871                break;
4872            }
4873        }
4874        self.expect(TokenType::RParen)?;
4875
4876        // Tolerate dialect-specific trailing clauses (ClickHouse `ENGINE = X`,
4877        // `ORDER BY (...)`, `PARTITION BY ...`, `SETTINGS ...`, MySQL
4878        // `ENGINE=InnoDB DEFAULT CHARSET=utf8`, etc.) by consuming tokens
4879        // until the next statement boundary. Respects paren depth so a
4880        // top-level `;` inside `ORDER BY (a, b)` is not mistaken for end.
4881        self.skip_trailing_options();
4882
4883        Ok(Statement::CreateTable(CreateTableStatement {
4884            comments: vec![],
4885            if_not_exists,
4886            temporary,
4887            table,
4888            columns,
4889            constraints,
4890            as_select: None,
4891        }))
4892    }
4893
4894    /// Discard tokens up to (but not including) a top-level `;` or EOF.
4895    /// Used to skip dialect-specific tail clauses we don't model in the AST
4896    /// (CREATE TABLE engines, options, etc.).
4897    fn skip_trailing_options(&mut self) {
4898        let mut depth: i32 = 0;
4899        loop {
4900            match self.peek_type() {
4901                TokenType::Eof => break,
4902                TokenType::Semicolon if depth == 0 => break,
4903                TokenType::LParen => {
4904                    depth += 1;
4905                    self.advance();
4906                }
4907                TokenType::RParen => {
4908                    depth -= 1;
4909                    if depth < 0 {
4910                        break;
4911                    }
4912                    self.advance();
4913                }
4914                _ => {
4915                    self.advance();
4916                }
4917            }
4918        }
4919    }
4920
4921    fn parse_create_view(
4922        &mut self,
4923        or_replace: bool,
4924        materialized: bool,
4925    ) -> Result<CreateViewStatement> {
4926        let if_not_exists = if self.match_token(TokenType::If) {
4927            self.expect(TokenType::Not)?;
4928            self.expect(TokenType::Exists)?;
4929            true
4930        } else {
4931            false
4932        };
4933
4934        // Parse name without alias (so AS is not consumed as an alias)
4935        let name = self.parse_table_ref_no_alias()?;
4936
4937        let columns = if self.match_token(TokenType::LParen) {
4938            let mut cols = vec![self.expect_name()?];
4939            while self.match_token(TokenType::Comma) {
4940                cols.push(self.expect_name()?);
4941            }
4942            self.expect(TokenType::RParen)?;
4943            cols
4944        } else {
4945            vec![]
4946        };
4947
4948        self.expect(TokenType::As)?;
4949        let query = self.parse_statement_inner()?;
4950
4951        Ok(CreateViewStatement {
4952            comments: vec![],
4953            name,
4954            columns,
4955            query: Box::new(query),
4956            or_replace,
4957            materialized,
4958            if_not_exists,
4959        })
4960    }
4961
4962    fn parse_table_constraint(&mut self) -> Result<TableConstraint> {
4963        let name = if self.match_token(TokenType::Constraint) {
4964            Some(self.expect_name()?)
4965        } else {
4966            None
4967        };
4968
4969        if self.match_token(TokenType::Primary) {
4970            self.expect(TokenType::Key)?;
4971            self.expect(TokenType::LParen)?;
4972            let columns = self.parse_name_list()?;
4973            self.expect(TokenType::RParen)?;
4974            // TiDB / MySQL: `PRIMARY KEY (cols) GLOBAL|LOCAL` index scope
4975            // modifier and `USING BTREE|HASH` index-type modifier.
4976            if self.is_name_token()
4977                && matches!(
4978                    self.peek().value.to_uppercase().as_str(),
4979                    "GLOBAL" | "LOCAL"
4980                )
4981            {
4982                self.advance();
4983            }
4984            if self.match_token(TokenType::Using) && self.is_name_token() {
4985                self.advance();
4986            }
4987            self.swallow_constraint_modifiers();
4988            Ok(TableConstraint::PrimaryKey { name, columns })
4989        } else if self.match_token(TokenType::Unique) {
4990            let _ = self.match_token(TokenType::Index) || self.match_token(TokenType::Key);
4991            // Optional index name before `(`.
4992            if !matches!(self.peek_type(), TokenType::LParen) && self.is_name_token() {
4993                self.advance();
4994            }
4995            self.expect(TokenType::LParen)?;
4996            let columns = self.parse_name_list()?;
4997            self.expect(TokenType::RParen)?;
4998            if self.is_name_token()
4999                && matches!(
5000                    self.peek().value.to_uppercase().as_str(),
5001                    "GLOBAL" | "LOCAL"
5002                )
5003            {
5004                self.advance();
5005            }
5006            if self.match_token(TokenType::Using) && self.is_name_token() {
5007                self.advance();
5008            }
5009            self.swallow_constraint_modifiers();
5010            Ok(TableConstraint::Unique { name, columns })
5011        } else if self.match_token(TokenType::Foreign) {
5012            self.expect(TokenType::Key)?;
5013            self.expect(TokenType::LParen)?;
5014            let columns = self.parse_name_list()?;
5015            self.expect(TokenType::RParen)?;
5016            self.expect(TokenType::References)?;
5017            let ref_table = self.parse_table_ref()?;
5018            self.expect(TokenType::LParen)?;
5019            let ref_columns = self.parse_name_list()?;
5020            self.expect(TokenType::RParen)?;
5021
5022            // PG / ANSI `MATCH FULL | PARTIAL | SIMPLE` clause — swallow.
5023            if self.check_keyword("MATCH") {
5024                self.advance();
5025                if self.is_name_token() {
5026                    self.advance();
5027                }
5028            }
5029
5030            let mut on_delete = None;
5031            let mut on_update = None;
5032            // Accept ON DELETE / ON UPDATE clauses in any order. Match the
5033            // ON keyword only when the following token is DELETE / UPDATE
5034            // so a misplaced ON UPDATE doesn't consume the bare ON token
5035            // and orphan the rest of the action list.
5036            while self.peek_type() == &TokenType::On {
5037                let next = self.peek_offset(1).map(|t| &t.token_type);
5038                if matches!(next, Some(TokenType::Delete)) {
5039                    self.advance();
5040                    self.advance();
5041                    on_delete = Some(self.parse_referential_action()?);
5042                } else if matches!(next, Some(TokenType::Update)) {
5043                    self.advance();
5044                    self.advance();
5045                    on_update = Some(self.parse_referential_action()?);
5046                } else {
5047                    break;
5048                }
5049            }
5050
5051            self.swallow_constraint_modifiers();
5052            Ok(TableConstraint::ForeignKey {
5053                name,
5054                columns,
5055                ref_table,
5056                ref_columns,
5057                on_delete,
5058                on_update,
5059            })
5060        } else if self.match_token(TokenType::Check) {
5061            self.expect(TokenType::LParen)?;
5062            let expr = self.parse_expr()?;
5063            self.expect(TokenType::RParen)?;
5064            self.swallow_constraint_modifiers();
5065            Ok(TableConstraint::Check { name, expr })
5066        } else {
5067            Err(SqlglotError::ParserError {
5068                message: "Expected constraint type".into(),
5069            })
5070        }
5071    }
5072
5073    /// Swallow trailing constraint modifiers shared by FK / CHECK / PK /
5074    /// UNIQUE: `NOT VALID`, `[NOT] ENFORCED`, `DEFERRABLE`, `NOT DEFERRABLE`,
5075    /// `INITIALLY DEFERRED | IMMEDIATE`, `NO INHERIT`. Best-effort — we
5076    /// don't model them in the AST.
5077    fn swallow_constraint_modifiers(&mut self) {
5078        loop {
5079            if self.check_keyword("NOT")
5080                && self
5081                    .peek_offset(1)
5082                    .map(|t| t.value.to_uppercase())
5083                    .as_deref()
5084                    .is_some_and(|v| matches!(v, "VALID" | "ENFORCED" | "DEFERRABLE"))
5085            {
5086                self.advance();
5087                self.advance();
5088                continue;
5089            }
5090            if self.check_keyword("ENFORCED")
5091                || self.check_keyword("DEFERRABLE")
5092                || self.check_keyword("CLUSTERED")
5093                || self.check_keyword("NONCLUSTERED")
5094                || self.check_keyword("INVISIBLE")
5095                || self.check_keyword("VISIBLE")
5096            {
5097                self.advance();
5098                continue;
5099            }
5100            if self.check_keyword("INITIALLY") {
5101                self.advance();
5102                if self.is_name_token() {
5103                    self.advance();
5104                }
5105                continue;
5106            }
5107            if self.check_keyword("NO")
5108                && self
5109                    .peek_offset(1)
5110                    .map(|t| t.value.eq_ignore_ascii_case("INHERIT"))
5111                    .unwrap_or(false)
5112            {
5113                self.advance();
5114                self.advance();
5115                continue;
5116            }
5117            break;
5118        }
5119    }
5120
5121    fn parse_referential_action(&mut self) -> Result<ReferentialAction> {
5122        if self.match_token(TokenType::Cascade) {
5123            Ok(ReferentialAction::Cascade)
5124        } else if self.match_token(TokenType::Restrict) {
5125            Ok(ReferentialAction::Restrict)
5126        } else if self.match_token(TokenType::Set) {
5127            if self.match_token(TokenType::Null) {
5128                Ok(ReferentialAction::SetNull)
5129            } else if self.match_token(TokenType::Default) {
5130                Ok(ReferentialAction::SetDefault)
5131            } else {
5132                Err(SqlglotError::ParserError {
5133                    message: "Expected NULL or DEFAULT after SET".into(),
5134                })
5135            }
5136        } else if self.check_keyword("NO") {
5137            self.advance();
5138            self.expect(TokenType::Identifier)?; // ACTION
5139            Ok(ReferentialAction::NoAction)
5140        } else {
5141            Err(SqlglotError::ParserError {
5142                message: "Expected referential action (CASCADE, RESTRICT, SET NULL, SET DEFAULT, NO ACTION)".into(),
5143            })
5144        }
5145    }
5146
5147    fn parse_name_list(&mut self) -> Result<Vec<String>> {
5148        let mut names = vec![self.expect_name()?];
5149        while self.match_token(TokenType::Comma) {
5150            names.push(self.expect_name()?);
5151        }
5152        Ok(names)
5153    }
5154
5155    /// Parse a dotted column reference for INSERT column lists:
5156    /// `name` or `parent.child` (ClickHouse nested columns).
5157    fn parse_dotted_name(&mut self) -> Result<String> {
5158        let mut name = self.expect_name()?;
5159        while self.peek_type() == &TokenType::Dot {
5160            let next = self.peek_offset(1).map(|t| t.token_type.clone());
5161            let next_is_namelike = matches!(
5162                next,
5163                Some(TokenType::Identifier)
5164                    | Some(TokenType::Star)
5165                    | Some(TokenType::Int)
5166                    | Some(TokenType::BigInt)
5167                    | Some(TokenType::Text)
5168                    | Some(TokenType::Date)
5169                    | Some(TokenType::Timestamp)
5170            );
5171            if !next_is_namelike {
5172                break;
5173            }
5174            self.advance(); // .
5175            if self.peek_type() == &TokenType::Star {
5176                name.push('.');
5177                name.push('*');
5178                self.advance();
5179                break;
5180            }
5181            let part = self.expect_name()?;
5182            name.push('.');
5183            name.push_str(&part);
5184        }
5185        Ok(name)
5186    }
5187
5188    fn parse_column_def(&mut self) -> Result<ColumnDef> {
5189        let name = self.expect_name()?;
5190        let data_type = self.parse_data_type()?;
5191
5192        let mut nullable = None;
5193        let mut default = None;
5194        let mut primary_key = false;
5195        let mut unique = false;
5196        let mut auto_increment = false;
5197        let mut collation = None;
5198        let mut comment = None;
5199
5200        loop {
5201            if self.match_token(TokenType::Not) {
5202                self.expect(TokenType::Null)?;
5203                nullable = Some(false);
5204            } else if self.peek_type() == &TokenType::Null {
5205                self.advance();
5206                nullable = Some(true);
5207            } else if self.peek_type() == &TokenType::As
5208                && matches!(
5209                    self.peek_offset(1).map(|t| &t.token_type),
5210                    Some(TokenType::LParen)
5211                )
5212            {
5213                // SQLite / MySQL generated-column shorthand:
5214                //   `col TYPE AS (expr) [STORED|VIRTUAL|PERSISTENT]`.
5215                // Swallow AS, the parenthesised expression (depth-balanced),
5216                // and the optional storage-kind keyword.
5217                self.advance(); // AS
5218                self.advance(); // (
5219                let mut depth: i32 = 1;
5220                while depth > 0 {
5221                    match self.peek_type() {
5222                        TokenType::LParen => {
5223                            depth += 1;
5224                            self.advance();
5225                        }
5226                        TokenType::RParen => {
5227                            depth -= 1;
5228                            self.advance();
5229                        }
5230                        TokenType::Eof => break,
5231                        _ => {
5232                            self.advance();
5233                        }
5234                    }
5235                }
5236                if self.is_name_token()
5237                    && matches!(
5238                        self.peek().value.to_uppercase().as_str(),
5239                        "STORED" | "VIRTUAL" | "PERSISTENT" | "PERSISTED"
5240                    )
5241                {
5242                    self.advance();
5243                }
5244            } else if self.match_token(TokenType::Default) {
5245                // SQL Server / IBM `DEFAULT NEXT VALUE FOR seq[.qual]`.
5246                if self.is_name_token()
5247                    && self.peek().value.eq_ignore_ascii_case("NEXT")
5248                    && self
5249                        .peek_offset(1)
5250                        .map(|t| t.value.eq_ignore_ascii_case("VALUE"))
5251                        .unwrap_or(false)
5252                    && self
5253                        .peek_offset(2)
5254                        .map(|t| t.value.eq_ignore_ascii_case("FOR"))
5255                        .unwrap_or(false)
5256                {
5257                    self.advance();
5258                    self.advance();
5259                    self.advance();
5260                    let mut seq = self.expect_name()?;
5261                    while self.match_token(TokenType::Dot) {
5262                        seq.push('.');
5263                        seq.push_str(&self.expect_name()?);
5264                    }
5265                    default = Some(Expr::Function {
5266                        name: "NEXT_VALUE_FOR".to_string(),
5267                        args: vec![Expr::Column {
5268                            table: None,
5269                            name: seq,
5270                            quote_style: QuoteStyle::None,
5271                            table_quote_style: QuoteStyle::None,
5272                        }],
5273                        distinct: false,
5274                        filter: None,
5275                        over: None,
5276                        order_by: Vec::new(),
5277                        within_group: false,
5278                    });
5279                } else {
5280                    default = Some(self.parse_expr()?);
5281                }
5282            } else if self.match_token(TokenType::Primary) {
5283                self.expect(TokenType::Key)?;
5284                primary_key = true;
5285            } else if self.match_token(TokenType::Unique) {
5286                unique = true;
5287            } else if self.match_token(TokenType::AutoIncrement) {
5288                auto_increment = true;
5289            } else if self.match_token(TokenType::Collate) {
5290                collation = Some(self.expect_name()?);
5291            } else if self.match_token(TokenType::Comment) {
5292                let tok = self.expect(TokenType::String)?;
5293                comment = Some(tok.value);
5294            } else if self.match_token(TokenType::References) {
5295                // Inline foreign key — skip for now
5296                let _ = self.parse_table_ref()?;
5297                if self.match_token(TokenType::LParen) {
5298                    while !self.match_token(TokenType::RParen) {
5299                        self.advance();
5300                    }
5301                }
5302            } else if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("GENERATED") {
5303                // SQL:2003 / MySQL / PG / SQL Server identity / computed
5304                // column: `GENERATED ALWAYS AS (expr) [VIRTUAL|STORED]`,
5305                // `GENERATED ALWAYS AS IDENTITY [(...)]`,
5306                // `GENERATED BY DEFAULT AS IDENTITY [(...)]`. Swallow up
5307                // through the trailing parenthesised body if present and
5308                // let the next loop iteration pick up VIRTUAL/STORED.
5309                self.advance();
5310                if self.is_name_token()
5311                    && (self.peek().value.eq_ignore_ascii_case("ALWAYS")
5312                        || self.peek().value.eq_ignore_ascii_case("BY"))
5313                {
5314                    self.advance();
5315                    if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("DEFAULT") {
5316                        self.advance();
5317                    }
5318                }
5319                if self.match_token(TokenType::As) {
5320                    if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("IDENTITY") {
5321                        self.advance();
5322                    } else if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("ROW")
5323                    {
5324                        // SQL Server `GENERATED AS ROW START | END`.
5325                        self.advance();
5326                        if self.is_name_token() {
5327                            self.advance();
5328                        }
5329                    }
5330                }
5331                if self.peek_type() == &TokenType::LParen {
5332                    let mut depth = 0_i32;
5333                    self.advance();
5334                    depth += 1;
5335                    while depth > 0 {
5336                        match self.peek_type() {
5337                            TokenType::LParen => depth += 1,
5338                            TokenType::RParen => {
5339                                depth -= 1;
5340                                if depth == 0 {
5341                                    self.advance();
5342                                    break;
5343                                }
5344                            }
5345                            TokenType::Eof => break,
5346                            _ => {}
5347                        }
5348                        self.advance();
5349                    }
5350                }
5351            } else if self.is_name_token()
5352                && matches!(
5353                    self.peek().value.to_uppercase().as_str(),
5354                    "CODEC"
5355                        | "TTL"
5356                        | "MATERIALIZED"
5357                        | "ALIAS"
5358                        | "EPHEMERAL"
5359                        | "PERSISTED"
5360                        | "PERSISTENT"
5361                        | "VIRTUAL"
5362                        | "STORED"
5363                        | "ENCODE"
5364                        | "ENCRYPT"
5365                        | "MASKED"
5366                        | "INVISIBLE"
5367                        | "VISIBLE"
5368                        | "ENFORCED"
5369                        | "OPTIONS"
5370                        | "COMPRESSION"
5371                        | "SORTKEY"
5372                        | "DISTKEY"
5373                        | "CHARSET"
5374                        | "CHARACTER"
5375                        | "SRID"
5376                        | "FORMAT"
5377                        | "TAG"
5378                        | "MASKING"
5379                )
5380            {
5381                // ClickHouse / Snowflake / Redshift column modifiers. Consume
5382                // the keyword and the optional parenthesised body (`CODEC(...)`,
5383                // `TTL expr`, etc.) so the rest of the column def parses.
5384                self.advance();
5385                if self.peek_type() == &TokenType::LParen {
5386                    let mut depth = 0_i32;
5387                    self.advance();
5388                    depth += 1;
5389                    while depth > 0 {
5390                        match self.peek_type() {
5391                            TokenType::LParen => depth += 1,
5392                            TokenType::RParen => {
5393                                depth -= 1;
5394                                if depth == 0 {
5395                                    self.advance();
5396                                    break;
5397                                }
5398                            }
5399                            TokenType::Eof => break,
5400                            _ => {}
5401                        }
5402                        self.advance();
5403                    }
5404                } else {
5405                    // Best-effort: swallow an expression up to comma /
5406                    // top-level RParen / column-def boundary, balancing
5407                    // nested parens (e.g. `TTL toDate('2000-01-02')`,
5408                    // `ALIAS arrayResize(emptyArrayUInt32(), length(\`Arr.C2\`))`).
5409                    let mut depth: i32 = 0;
5410                    loop {
5411                        match self.peek_type() {
5412                            TokenType::LParen => {
5413                                depth += 1;
5414                                self.advance();
5415                            }
5416                            TokenType::RParen => {
5417                                if depth == 0 {
5418                                    break;
5419                                }
5420                                depth -= 1;
5421                                self.advance();
5422                            }
5423                            TokenType::Comma if depth == 0 => break,
5424                            TokenType::Eof => break,
5425                            _ => {
5426                                self.advance();
5427                            }
5428                        }
5429                    }
5430                }
5431            } else {
5432                break;
5433            }
5434        }
5435
5436        Ok(ColumnDef {
5437            name,
5438            data_type,
5439            nullable,
5440            default,
5441            primary_key,
5442            unique,
5443            auto_increment,
5444            collation,
5445            comment,
5446        })
5447    }
5448
5449    fn parse_data_type(&mut self) -> Result<DataType> {
5450        let token = self.peek().clone();
5451        // DuckDB / Spark template syntax: `${var}` (or `?` placeholder) used
5452        // where a data type is expected. Lower to `Unknown(name)` so the
5453        // surrounding expression parses.
5454        if matches!(token.token_type, TokenType::Parameter) {
5455            self.advance();
5456            return Ok(DataType::Unknown(token.value));
5457        }
5458        let type_result = match &token.token_type {
5459            TokenType::Int | TokenType::Integer => {
5460                self.advance();
5461                Ok(DataType::Int)
5462            }
5463            TokenType::BigInt => {
5464                self.advance();
5465                Ok(DataType::BigInt)
5466            }
5467            TokenType::SmallInt => {
5468                self.advance();
5469                Ok(DataType::SmallInt)
5470            }
5471            TokenType::TinyInt => {
5472                self.advance();
5473                Ok(DataType::TinyInt)
5474            }
5475            TokenType::Float => {
5476                self.advance();
5477                Ok(DataType::Float)
5478            }
5479            TokenType::Double => {
5480                self.advance();
5481                let _ = self.match_keyword("PRECISION");
5482                Ok(DataType::Double)
5483            }
5484            TokenType::Real => {
5485                self.advance();
5486                Ok(DataType::Real)
5487            }
5488            TokenType::Decimal | TokenType::Numeric => {
5489                let is_numeric = token.token_type == TokenType::Numeric;
5490                self.advance();
5491                let (precision, scale) = self.parse_type_params()?;
5492                if is_numeric {
5493                    Ok(DataType::Numeric { precision, scale })
5494                } else {
5495                    Ok(DataType::Decimal { precision, scale })
5496                }
5497            }
5498            TokenType::Varchar => {
5499                self.advance();
5500                let len = self.parse_single_type_param()?;
5501                Ok(DataType::Varchar(len))
5502            }
5503            TokenType::Char => {
5504                self.advance();
5505                let len = self.parse_single_type_param()?;
5506                Ok(DataType::Char(len))
5507            }
5508            TokenType::Text => {
5509                self.advance();
5510                Ok(DataType::Text)
5511            }
5512            TokenType::Boolean => {
5513                self.advance();
5514                Ok(DataType::Boolean)
5515            }
5516            TokenType::Date => {
5517                self.advance();
5518                Ok(DataType::Date)
5519            }
5520            TokenType::Timestamp => {
5521                self.advance();
5522                let precision = self.parse_single_type_param()?;
5523                let with_tz = if self.match_keyword("WITH") {
5524                    let _ = self.match_keyword("LOCAL");
5525                    let _ = self.match_keyword("TIME");
5526                    let _ = self.match_keyword("ZONE");
5527                    true
5528                } else if self.match_keyword("WITHOUT") {
5529                    let _ = self.match_keyword("TIME");
5530                    let _ = self.match_keyword("ZONE");
5531                    false
5532                } else {
5533                    false
5534                };
5535                Ok(DataType::Timestamp { precision, with_tz })
5536            }
5537            TokenType::TimestampTz => {
5538                self.advance();
5539                let precision = self.parse_single_type_param()?;
5540                Ok(DataType::Timestamp {
5541                    precision,
5542                    with_tz: true,
5543                })
5544            }
5545            TokenType::Time => {
5546                self.advance();
5547                let precision = self.parse_single_type_param()?;
5548                Ok(DataType::Time { precision })
5549            }
5550            TokenType::Interval => {
5551                self.advance();
5552                Ok(DataType::Interval)
5553            }
5554            TokenType::Blob => {
5555                self.advance();
5556                Ok(DataType::Blob)
5557            }
5558            TokenType::Bytea => {
5559                self.advance();
5560                Ok(DataType::Bytea)
5561            }
5562            TokenType::Json => {
5563                self.advance();
5564                Ok(DataType::Json)
5565            }
5566            TokenType::Jsonb => {
5567                self.advance();
5568                Ok(DataType::Jsonb)
5569            }
5570            TokenType::Uuid => {
5571                self.advance();
5572                Ok(DataType::Uuid)
5573            }
5574            TokenType::Array => {
5575                self.advance();
5576                if self.match_token(TokenType::Lt) {
5577                    let inner = self.parse_data_type()?;
5578                    self.expect(TokenType::Gt)?;
5579                    Ok(DataType::Array(Some(Box::new(inner))))
5580                } else {
5581                    Ok(DataType::Array(None))
5582                }
5583            }
5584            TokenType::Struct => {
5585                self.advance();
5586                // STRUCT<a INT, b STRING> (Hive/Spark) or STRUCT(a INT, b INT) (DuckDB).
5587                // Swallow the body — we don't model named struct fields in the AST.
5588                let close = if self.match_token(TokenType::Lt) {
5589                    Some(TokenType::Gt)
5590                } else if self.match_token(TokenType::LParen) {
5591                    Some(TokenType::RParen)
5592                } else {
5593                    None
5594                };
5595                if let Some(close_tok) = close {
5596                    let mut depth = 1_i32;
5597                    while depth > 0 {
5598                        if self.peek_type() == &TokenType::Eof {
5599                            break;
5600                        }
5601                        if self.peek_type() == &close_tok {
5602                            depth -= 1;
5603                            if depth == 0 {
5604                                self.advance();
5605                                break;
5606                            }
5607                        } else if matches!(self.peek_type(), TokenType::Lt | TokenType::LParen)
5608                            && (self.peek_type() == &TokenType::Lt && close_tok == TokenType::Gt
5609                                || self.peek_type() == &TokenType::LParen
5610                                    && close_tok == TokenType::RParen)
5611                        {
5612                            depth += 1;
5613                        }
5614                        self.advance();
5615                    }
5616                }
5617                Ok(DataType::Unknown("STRUCT".to_string()))
5618            }
5619            TokenType::Map => {
5620                self.advance();
5621                let close = if self.match_token(TokenType::Lt) {
5622                    Some(TokenType::Gt)
5623                } else if self.match_token(TokenType::LParen) {
5624                    Some(TokenType::RParen)
5625                } else {
5626                    None
5627                };
5628                if let Some(close_tok) = close {
5629                    let mut depth = 1_i32;
5630                    while depth > 0 {
5631                        if self.peek_type() == &TokenType::Eof {
5632                            break;
5633                        }
5634                        if self.peek_type() == &close_tok {
5635                            depth -= 1;
5636                            if depth == 0 {
5637                                self.advance();
5638                                break;
5639                            }
5640                        } else if (self.peek_type() == &TokenType::Lt && close_tok == TokenType::Gt)
5641                            || (self.peek_type() == &TokenType::LParen
5642                                && close_tok == TokenType::RParen)
5643                        {
5644                            depth += 1;
5645                        }
5646                        self.advance();
5647                    }
5648                }
5649                Ok(DataType::Unknown("MAP".to_string()))
5650            }
5651            TokenType::Identifier => {
5652                let name = token.value.to_uppercase();
5653                self.advance();
5654                match name.as_str() {
5655                    "STRING" => Ok(DataType::String),
5656                    "BINARY" => {
5657                        let len = self.parse_single_type_param()?;
5658                        Ok(DataType::Binary(len))
5659                    }
5660                    "VARBINARY" => {
5661                        let len = self.parse_single_type_param()?;
5662                        Ok(DataType::Varbinary(len))
5663                    }
5664                    "DATETIME" => Ok(DataType::DateTime),
5665                    "BYTES" => Ok(DataType::Bytes),
5666                    "VARIANT" => Ok(DataType::Variant),
5667                    "OBJECT" => Ok(DataType::Object),
5668                    "XML" => Ok(DataType::Xml),
5669                    "INET" => Ok(DataType::Inet),
5670                    "CIDR" => Ok(DataType::Cidr),
5671                    "MACADDR" => Ok(DataType::Macaddr),
5672                    "BIT" => {
5673                        // Postgres `BIT VARYING(n)` is the same as VARBIT.
5674                        // Swallow the VARYING keyword if present and parse
5675                        // the length normally.
5676                        if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("VARYING")
5677                        {
5678                            self.advance();
5679                            let len = self.parse_single_type_param()?;
5680                            return Ok(DataType::Varbinary(len));
5681                        }
5682                        let len = self.parse_single_type_param()?;
5683                        Ok(DataType::Bit(len))
5684                    }
5685                    "MONEY" => Ok(DataType::Money),
5686                    "SERIAL" => Ok(DataType::Serial),
5687                    "BIGSERIAL" => Ok(DataType::BigSerial),
5688                    "SMALLSERIAL" => Ok(DataType::SmallSerial),
5689                    "REGCLASS" => Ok(DataType::Regclass),
5690                    "REGTYPE" => Ok(DataType::Regtype),
5691                    "HSTORE" => Ok(DataType::Hstore),
5692                    "GEOGRAPHY" => Ok(DataType::Geography),
5693                    "GEOMETRY" => Ok(DataType::Geometry),
5694                    "SUPER" => Ok(DataType::Super),
5695                    _ => Ok(DataType::Unknown(name)),
5696                }
5697            }
5698            _ => {
5699                // Fallback: accept any keyword-like token as an unknown
5700                // data type by its textual value. Covers PostgreSQL `cube`,
5701                // `lseg`, `path`, `polygon`, and any vendor-specific type
5702                // name that happens to collide with a TokenType variant.
5703                let v = token.value.clone();
5704                if !v.is_empty() && v.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
5705                    self.advance();
5706                    Ok(DataType::Unknown(v.to_uppercase()))
5707                } else {
5708                    Err(SqlglotError::ParserError {
5709                        message: format!("Expected data type, got {:?}", token.token_type),
5710                    })
5711                }
5712            }
5713        };
5714
5715        // PostgreSQL opt_array_bounds: typename[], typename[N], typename[][]...
5716        let mut dt = type_result?;
5717        while self.match_token(TokenType::LBracket) {
5718            // Consume optional integer bound (PostgreSQL ignores it but accepts it)
5719            let _ = self.match_token(TokenType::Number);
5720            self.expect(TokenType::RBracket)?;
5721            dt = DataType::Array(Some(Box::new(dt)));
5722        }
5723        // ClickHouse parameterized types: `DateTime('Asia/Dubai')`,
5724        // `Nullable(String)`, `Array(Int32)`, `Enum8('a' = 1, 'b' = 2)`,
5725        // `Decimal(9, 2)`, etc. The base type was already produced — swallow
5726        // the parenthesized parameter list so the surrounding expression
5727        // continues to parse.
5728        if self.peek_type() == &TokenType::LParen {
5729            let saved = self.pos;
5730            self.advance();
5731            let mut depth = 1;
5732            let mut ok = true;
5733            while depth > 0 {
5734                match self.peek_type() {
5735                    TokenType::LParen => depth += 1,
5736                    TokenType::RParen => {
5737                        depth -= 1;
5738                        if depth == 0 {
5739                            self.advance();
5740                            break;
5741                        }
5742                    }
5743                    TokenType::Eof => {
5744                        ok = false;
5745                        break;
5746                    }
5747                    _ => {}
5748                }
5749                self.advance();
5750            }
5751            if !ok {
5752                self.pos = saved;
5753            }
5754        }
5755        Ok(dt)
5756    }
5757
5758    fn parse_type_params(&mut self) -> Result<(Option<u32>, Option<u32>)> {
5759        if self.match_token(TokenType::LParen) {
5760            let p: Option<u32> = self.expect(TokenType::Number)?.value.parse().ok();
5761            let s = if self.match_token(TokenType::Comma) {
5762                self.expect(TokenType::Number)?.value.parse().ok()
5763            } else {
5764                None
5765            };
5766            self.expect(TokenType::RParen)?;
5767            Ok((p, s))
5768        } else {
5769            Ok((None, None))
5770        }
5771    }
5772
5773    fn parse_single_type_param(&mut self) -> Result<Option<u32>> {
5774        if self.match_token(TokenType::LParen) {
5775            // Handle TSQL MAX keyword (e.g. VARBINARY(MAX), VARCHAR(MAX))
5776            if self.check_keyword("MAX") {
5777                self.advance(); // consume MAX
5778                self.expect(TokenType::RParen)?;
5779                return Ok(None);
5780            }
5781            let n: Option<u32> = self.expect(TokenType::Number)?.value.parse().ok();
5782            self.expect(TokenType::RParen)?;
5783            Ok(n)
5784        } else {
5785            Ok(None)
5786        }
5787    }
5788
5789    // ── DROP ────────────────────────────────────────────────────────
5790
5791    fn parse_drop(&mut self) -> Result<Statement> {
5792        self.expect(TokenType::Drop)?;
5793
5794        if self.match_token(TokenType::Materialized) {
5795            self.expect(TokenType::View)?;
5796            let if_exists = if self.match_token(TokenType::If) {
5797                self.expect(TokenType::Exists)?;
5798                true
5799            } else {
5800                false
5801            };
5802            let name = self.parse_table_ref()?;
5803            // MySQL/MariaDB allow comma-list — swallow the rest.
5804            while self.match_token(TokenType::Comma) {
5805                let _ = self.parse_table_ref()?;
5806            }
5807            // Trailing CASCADE / RESTRICT.
5808            let _ = self.match_token(TokenType::Cascade) || self.match_token(TokenType::Restrict);
5809            return Ok(Statement::DropView(DropViewStatement {
5810                comments: vec![],
5811                name,
5812                if_exists,
5813                materialized: true,
5814            }));
5815        }
5816
5817        if self.match_token(TokenType::View) {
5818            let if_exists = if self.match_token(TokenType::If) {
5819                self.expect(TokenType::Exists)?;
5820                true
5821            } else {
5822                false
5823            };
5824            let name = self.parse_table_ref()?;
5825            while self.match_token(TokenType::Comma) {
5826                let _ = self.parse_table_ref()?;
5827            }
5828            let _ = self.match_token(TokenType::Cascade) || self.match_token(TokenType::Restrict);
5829            return Ok(Statement::DropView(DropViewStatement {
5830                comments: vec![],
5831                name,
5832                if_exists,
5833                materialized: false,
5834            }));
5835        }
5836
5837        // DROP <kind> ... — preserve as a Command for non-TABLE/VIEW drops
5838        // (FUNCTION, PROCEDURE, SCHEMA, DATABASE, INDEX, ROLE, USER, …).
5839        if self.peek_type() != &TokenType::Table {
5840            // Already consumed DROP; capture the remainder.
5841            let body = self.consume_raw_to_statement_end();
5842            return Ok(Statement::Command(CommandStatement {
5843                comments: vec![],
5844                kind: "DROP".to_string(),
5845                body,
5846            }));
5847        }
5848
5849        self.expect(TokenType::Table)?;
5850
5851        let if_exists = if self.match_token(TokenType::If) {
5852            self.expect(TokenType::Exists)?;
5853            true
5854        } else {
5855            false
5856        };
5857
5858        let table = self.parse_table_ref()?;
5859        // MySQL / MariaDB: `DROP TABLE [IF EXISTS] t1, t2, …`. Swallow the
5860        // extra table names so the statement parses.
5861        while self.match_token(TokenType::Comma) {
5862            let _ = self.parse_table_ref()?;
5863        }
5864        let cascade = self.match_token(TokenType::Cascade);
5865        // Tolerate Doris / StarRocks / Oracle trailing modifiers on DROP TABLE
5866        // (`FORCE`, `PURGE`, `RESTRICT`).
5867        while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon) {
5868            if self.is_name_token()
5869                && matches!(
5870                    self.peek().value.to_uppercase().as_str(),
5871                    "FORCE" | "PURGE" | "RESTRICT"
5872                )
5873            {
5874                self.advance();
5875            } else if matches!(self.peek_type(), TokenType::Restrict) {
5876                self.advance();
5877            } else {
5878                break;
5879            }
5880        }
5881
5882        Ok(Statement::DropTable(DropTableStatement {
5883            comments: vec![],
5884            if_exists,
5885            table,
5886            cascade,
5887        }))
5888    }
5889
5890    // ── ALTER TABLE ─────────────────────────────────────────────────
5891
5892    fn parse_alter_table(&mut self) -> Result<AlterTableStatement> {
5893        self.expect(TokenType::Alter)?;
5894        self.expect(TokenType::Table)?;
5895        let table = self.parse_table_ref_no_alias()?;
5896
5897        let mut actions = Vec::new();
5898        loop {
5899            let action = self.parse_alter_action()?;
5900            actions.push(action);
5901            if !self.match_token(TokenType::Comma) {
5902                break;
5903            }
5904        }
5905
5906        Ok(AlterTableStatement {
5907            comments: vec![],
5908            table,
5909            actions,
5910        })
5911    }
5912
5913    fn parse_alter_action(&mut self) -> Result<AlterTableAction> {
5914        // Hive multi-partition continuation after a comma:
5915        // `ALTER TABLE t DROP PARTITION (a), PARTITION (b)`. Swallow the
5916        // bare PARTITION clause.
5917        if self.peek_type() == &TokenType::Partition {
5918            self.advance();
5919            let mut depth: i32 = 0;
5920            while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
5921                && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
5922            {
5923                match self.peek_type() {
5924                    TokenType::LParen => depth += 1,
5925                    TokenType::RParen => depth = depth.saturating_sub(1),
5926                    _ => {}
5927                }
5928                self.advance();
5929            }
5930            return Ok(AlterTableAction::DropColumn {
5931                name: String::new(),
5932                if_exists: false,
5933            });
5934        }
5935        if self.match_keyword("ADD") {
5936            if matches!(
5937                self.peek_type(),
5938                TokenType::Constraint
5939                    | TokenType::Primary
5940                    | TokenType::Unique
5941                    | TokenType::Foreign
5942                    | TokenType::Check
5943            ) {
5944                let constraint = self.parse_table_constraint()?;
5945                self.swallow_constraint_modifiers();
5946                Ok(AlterTableAction::AddConstraint(constraint))
5947            } else if self.check_keyword("EXCLUDE") {
5948                // PG `ADD EXCLUDE [USING method] (col WITH op [, ...]) [WHERE
5949                // (predicate)] [DEFERRABLE …]` — swallow opaquely until we
5950                // hit a top-level statement boundary or comma.
5951                let mut depth: i32 = 0;
5952                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
5953                    && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
5954                {
5955                    match self.peek_type() {
5956                        TokenType::LParen => depth += 1,
5957                        TokenType::RParen => depth = depth.saturating_sub(1),
5958                        _ => {}
5959                    }
5960                    self.advance();
5961                }
5962                Ok(AlterTableAction::DropColumn {
5963                    name: String::new(),
5964                    if_exists: false,
5965                })
5966            } else if self.check_keyword("INDEX")
5967                || self.check_keyword("KEY")
5968                || self.check_keyword("PROJECTION")
5969                || self.check_keyword("STATISTICS")
5970            {
5971                // ClickHouse / MySQL `ADD INDEX [name] expr TYPE x GRANULARITY n
5972                // [AFTER y]`, `ADD KEY ...`, `ADD PROJECTION ...`. The body
5973                // is heterogeneous; swallow it opaquely up to the next
5974                // top-level Comma / Semicolon / EOF.
5975                let mut depth: i32 = 0;
5976                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
5977                    && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
5978                {
5979                    match self.peek_type() {
5980                        TokenType::LParen => depth += 1,
5981                        TokenType::RParen => depth = depth.saturating_sub(1),
5982                        _ => {}
5983                    }
5984                    self.advance();
5985                }
5986                Ok(AlterTableAction::DropColumn {
5987                    name: String::new(),
5988                    if_exists: false,
5989                })
5990            } else if self.check_keyword("COLUMNS") {
5991                // Hive / Spark / Databricks `ALTER TABLE … ADD COLUMNS
5992                // (col type [, col type]*)` or the comma-list form
5993                // `ADD COLUMNS col type, col type`. Swallow opaquely.
5994                self.advance();
5995                let mut depth: i32 = 0;
5996                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
5997                    && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
5998                {
5999                    match self.peek_type() {
6000                        TokenType::LParen => depth += 1,
6001                        TokenType::RParen => depth = depth.saturating_sub(1),
6002                        _ => {}
6003                    }
6004                    self.advance();
6005                    if depth == 0
6006                        && matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
6007                    {
6008                        break;
6009                    }
6010                }
6011                Ok(AlterTableAction::DropColumn {
6012                    name: String::new(),
6013                    if_exists: false,
6014                })
6015            } else {
6016                let _ = self.match_keyword("COLUMN");
6017                let col = self.parse_column_def()?;
6018                // ClickHouse: `ADD COLUMN name type AFTER other` / `FIRST` —
6019                // consume the placement modifier so the rest of the action
6020                // list parses.
6021                if self.check_keyword("AFTER") {
6022                    self.advance();
6023                    if self.is_name_token() {
6024                        self.advance();
6025                    }
6026                } else if self.check_keyword("FIRST") {
6027                    self.advance();
6028                }
6029                Ok(AlterTableAction::AddColumn(col))
6030            }
6031        } else if self.match_token(TokenType::Drop) {
6032            // Hive: `DROP IF EXISTS PARTITION (…), PARTITION (…)`. The
6033            // optional `IF EXISTS` precedes PARTITION.
6034            if self.peek_type() == &TokenType::If
6035                && self
6036                    .peek_offset(1)
6037                    .map(|t| matches!(t.token_type, TokenType::Exists))
6038                    .unwrap_or(false)
6039                && self
6040                    .peek_offset(2)
6041                    .map(|t| matches!(t.token_type, TokenType::Partition))
6042                    .unwrap_or(false)
6043            {
6044                self.advance(); // IF
6045                self.advance(); // EXISTS
6046            }
6047            // MySQL / TiDB: `DROP INDEX|KEY name`, `DROP PRIMARY KEY`,
6048            // `DROP FOREIGN KEY name`, `DROP CONSTRAINT name`,
6049            // `DROP PARTITION (...)`, `DROP CHECK name`. We don't have a
6050            // dedicated AST node for these, so swallow them to end-of-action.
6051            if matches!(
6052                self.peek_type(),
6053                TokenType::Index
6054                    | TokenType::Primary
6055                    | TokenType::Foreign
6056                    | TokenType::Constraint
6057                    | TokenType::Check
6058                    | TokenType::Partition
6059                    | TokenType::Unique
6060            ) || self.check_keyword("KEY")
6061                || self.check_keyword("FEATURE")
6062                || self.check_keyword("PROJECTION")
6063                || self.check_keyword("STATISTICS")
6064                || self.check_keyword("INDEX")
6065                || self.check_keyword("DISTRIBUTION")
6066            {
6067                let mut depth: i32 = 0;
6068                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
6069                    && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
6070                {
6071                    match self.peek_type() {
6072                        TokenType::LParen => depth += 1,
6073                        TokenType::RParen => depth = depth.saturating_sub(1),
6074                        _ => {}
6075                    }
6076                    self.advance();
6077                }
6078                return Ok(AlterTableAction::DropColumn {
6079                    name: String::new(),
6080                    if_exists: false,
6081                });
6082            }
6083            let _ = self.match_keyword("COLUMN");
6084            let if_exists = if self.match_token(TokenType::If) {
6085                self.expect(TokenType::Exists)?;
6086                true
6087            } else {
6088                false
6089            };
6090            let mut name = self.expect_name()?;
6091            // ClickHouse `DROP COLUMN nested.col` — accept dotted suffixes;
6092            // we collapse them into the column name string for now.
6093            while self.peek_type() == &TokenType::Dot {
6094                self.advance();
6095                if !self.is_name_token() {
6096                    break;
6097                }
6098                name.push('.');
6099                name.push_str(&self.peek().value);
6100                self.advance();
6101            }
6102            Ok(AlterTableAction::DropColumn { name, if_exists })
6103        } else if self.match_keyword("RENAME") {
6104            if self.match_keyword("COLUMN") {
6105                let old_name = self.expect_name()?;
6106                self.expect(TokenType::Identifier)?; // TO
6107                let new_name = self.expect_name()?;
6108                Ok(AlterTableAction::RenameColumn { old_name, new_name })
6109            } else if self.match_keyword("TO") {
6110                let mut new_name = self.expect_name()?;
6111                while self.match_token(TokenType::Dot) {
6112                    new_name.push('.');
6113                    new_name.push_str(&self.expect_name()?);
6114                }
6115                Ok(AlterTableAction::RenameTable { new_name })
6116            } else {
6117                Err(SqlglotError::ParserError {
6118                    message: "Expected COLUMN or TO after RENAME".into(),
6119                })
6120            }
6121        } else {
6122            Err(SqlglotError::ParserError {
6123                message: "Expected ADD, DROP, or RENAME in ALTER TABLE".into(),
6124            })
6125        }
6126    }
6127
6128    /// Try [`parse_alter_table`]; on failure, rewind and capture the entire
6129    /// `ALTER …` statement verbatim as a [`Statement::Command`]. This covers
6130    /// the long tail of vendor-specific ALTER forms — MySQL `ALTER TABLE …
6131    /// CONVERT TO CHARACTER SET … COLLATE …`, Hive `ALTER TABLE … PARTITION
6132    /// (…) COMPACT 'major'`, T-SQL `ALTER TABLE … WITH (…) CHECK CONSTRAINT
6133    /// …`, etc. (Gap 5)
6134    fn parse_alter_or_command(&mut self) -> Result<Statement> {
6135        let saved = self.pos;
6136        let saved_comments = self.pending_comments.clone();
6137        match self.parse_alter_table() {
6138            Ok(stmt) => Ok(Statement::AlterTable(stmt)),
6139            Err(_) => {
6140                self.pos = saved;
6141                self.pending_comments = saved_comments;
6142                self.parse_command_kind("ALTER")
6143            }
6144        }
6145    }
6146
6147    /// Try [`parse_create`]; on failure, rewind and capture the entire
6148    /// `CREATE …` statement verbatim as a [`Statement::Command`]. Also
6149    /// handles the `CREATE TABLE t AS VALUES (…)` form (Gap 7) and rarer
6150    /// `CREATE OPERATOR / AGGREGATE / SEQUENCE / FUNCTION / TEXT SEARCH
6151    /// CONFIGURATION / …` (Gap 4).
6152    fn parse_create_or_command(&mut self) -> Result<Statement> {
6153        let saved = self.pos;
6154        let saved_comments = self.pending_comments.clone();
6155        match self.parse_create() {
6156            Ok(stmt) => Ok(stmt),
6157            Err(_) => {
6158                self.pos = saved;
6159                self.pending_comments = saved_comments;
6160                self.parse_command_kind("CREATE")
6161            }
6162        }
6163    }
6164
6165    // ── TRUNCATE ────────────────────────────────────────────────────
6166
6167    fn parse_truncate(&mut self) -> Result<TruncateStatement> {
6168        self.expect(TokenType::Truncate)?;
6169        let _ = self.match_token(TokenType::Table);
6170        let table = self.parse_table_ref()?;
6171        Ok(TruncateStatement {
6172            comments: vec![],
6173            table,
6174        })
6175    }
6176
6177    // ── Transaction ─────────────────────────────────────────────────
6178
6179    fn parse_transaction(&mut self) -> Result<TransactionStatement> {
6180        match self.peek_type() {
6181            TokenType::Begin => {
6182                self.advance();
6183                let _ = self.match_token(TokenType::Transaction);
6184                let _ = self.match_keyword("WORK");
6185                Ok(TransactionStatement::Begin)
6186            }
6187            TokenType::Commit => {
6188                self.advance();
6189                let _ = self.match_token(TokenType::Transaction);
6190                let _ = self.match_keyword("WORK");
6191                // SQL-standard COMMIT [WORK] [AND [NO] CHAIN]
6192                if self.match_token(TokenType::And) {
6193                    let _ = self.match_token(TokenType::Not);
6194                    let _ = self.match_keyword("NO");
6195                    let _ = self.match_keyword("CHAIN");
6196                }
6197                Ok(TransactionStatement::Commit)
6198            }
6199            TokenType::Rollback => {
6200                self.advance();
6201                let _ = self.match_token(TokenType::Transaction);
6202                let _ = self.match_keyword("WORK");
6203                if self.match_keyword("TO") {
6204                    let _ = self.match_token(TokenType::Savepoint);
6205                    let name = self.expect_name()?;
6206                    Ok(TransactionStatement::RollbackTo(name))
6207                } else {
6208                    // ROLLBACK [WORK] [AND [NO] CHAIN]
6209                    if self.match_token(TokenType::And) {
6210                        let _ = self.match_token(TokenType::Not);
6211                        let _ = self.match_keyword("NO");
6212                        let _ = self.match_keyword("CHAIN");
6213                    }
6214                    Ok(TransactionStatement::Rollback)
6215                }
6216            }
6217            TokenType::Savepoint => {
6218                self.advance();
6219                let name = self.expect_name()?;
6220                Ok(TransactionStatement::Savepoint(name))
6221            }
6222            _ => Err(SqlglotError::ParserError {
6223                message: "Expected transaction statement".into(),
6224            }),
6225        }
6226    }
6227
6228    // ── EXPLAIN ─────────────────────────────────────────────────────
6229
6230    fn parse_explain(&mut self) -> Result<ExplainStatement> {
6231        self.expect(TokenType::Explain)?;
6232        let analyze = self.match_token(TokenType::Analyze);
6233        // PostgreSQL `EXPLAIN (VERBOSE, COSTS OFF, ...)` option block, plus
6234        // unparenthesized `VERBOSE` / `FORMAT TEXT|JSON|YAML`.
6235        if self.match_token(TokenType::LParen) {
6236            let mut depth = 1;
6237            while depth > 0 {
6238                match self.peek_type() {
6239                    TokenType::Eof => break,
6240                    TokenType::LParen => depth += 1,
6241                    TokenType::RParen => {
6242                        depth -= 1;
6243                        if depth == 0 {
6244                            self.advance();
6245                            break;
6246                        }
6247                    }
6248                    _ => {}
6249                }
6250                self.advance();
6251            }
6252        } else {
6253            // Optional bare keywords: VERBOSE / FORMAT [=] <name|string>
6254            loop {
6255                if self.check_keyword("VERBOSE") {
6256                    self.advance();
6257                    continue;
6258                }
6259                if self.check_keyword("FORMAT") {
6260                    self.advance();
6261                    let _ = self.match_token(TokenType::Eq);
6262                    // Format name can be an identifier (TEXT/JSON/YAML/XML/...)
6263                    // or a string literal (`'plan_tree'`).
6264                    if matches!(self.peek_type(), TokenType::String | TokenType::Identifier)
6265                        || self.is_name_token()
6266                    {
6267                        self.advance();
6268                    }
6269                    continue;
6270                }
6271                break;
6272            }
6273            // Hive / Spark EXPLAIN modifiers: EXTENDED, LOCKS, AUTHORIZATION,
6274            // DEPENDENCY, VECTORIZATION [ONLY] [SUMMARY|OPERATOR|EXPRESSION|DETAIL],
6275            // CBO, AST, REWRITE, FORMATTED, LOGICAL, NODE. Also ClickHouse
6276            // `EXPLAIN indexes=1 actions=1 …` bare options. Consume any
6277            // identifier-like tokens (and optional `= value`) until we hit a
6278            // statement-starting keyword.
6279            loop {
6280                match self.peek_type() {
6281                    TokenType::Select
6282                    | TokenType::With
6283                    | TokenType::Insert
6284                    | TokenType::Update
6285                    | TokenType::Delete
6286                    | TokenType::Merge
6287                    | TokenType::Create
6288                    | TokenType::Drop
6289                    | TokenType::Alter
6290                    | TokenType::Truncate
6291                    | TokenType::LParen
6292                    | TokenType::Eof
6293                    | TokenType::Semicolon => break,
6294                    TokenType::Identifier => {
6295                        self.advance();
6296                        if self.match_token(TokenType::Eq) {
6297                            // value: number, string, or identifier
6298                            if matches!(self.peek_type(), TokenType::Number | TokenType::String)
6299                                || self.is_name_token()
6300                            {
6301                                self.advance();
6302                            }
6303                        }
6304                        // Optional comma between options
6305                        // (ClickHouse `dump_tree = 1, dump_ast = 1 …`).
6306                        let _ = self.match_token(TokenType::Comma);
6307                    }
6308                    _ => {
6309                        // Also accept unreserved keyword-style modifiers
6310                        // (ONLY, FORMATTED, EXTENDED, etc. that tokenize as
6311                        // their own variants). Bail when we hit anything
6312                        // that isn't a plain name token.
6313                        if self.is_name_token() {
6314                            self.advance();
6315                        } else {
6316                            break;
6317                        }
6318                    }
6319                }
6320            }
6321        }
6322        let statement = self.parse_statement_inner()?;
6323        Ok(ExplainStatement {
6324            comments: vec![],
6325            analyze,
6326            statement: Box::new(statement),
6327        })
6328    }
6329
6330    // ── USE ─────────────────────────────────────────────────────────
6331
6332    fn parse_use(&mut self) -> Result<UseStatement> {
6333        self.expect(TokenType::Use)?;
6334        // Optional kind: USE DATABASE / SCHEMA / CATALOG / WAREHOUSE / ROLE
6335        // (DuckDB / Snowflake / Spark). Swallow the leading keyword.
6336        let _ = matches!(self.peek_type(), TokenType::Database | TokenType::Schema) && {
6337            self.advance();
6338            true
6339        } || (self.is_name_token()
6340            && matches!(
6341                self.peek().value.to_uppercase().as_str(),
6342                "CATALOG" | "WAREHOUSE" | "ROLE"
6343            )
6344            && {
6345                self.advance();
6346                true
6347            });
6348        // `USE default` (Hive): `default` is a keyword, accept it as a name.
6349        let mut name = if matches!(self.peek_type(), TokenType::Default) {
6350            let v = self.peek().value.clone();
6351            self.advance();
6352            v
6353        } else if self.is_name_token()
6354            && self.peek().value.eq_ignore_ascii_case("IDENTIFIER")
6355            && matches!(
6356                self.peek_offset(1).map(|t| &t.token_type),
6357                Some(TokenType::LParen)
6358            )
6359        {
6360            // Snowflake / Databricks IDENTIFIER('name') indirection —
6361            // swallow the call and use a synthetic name.
6362            self.advance(); // IDENTIFIER
6363            self.advance(); // (
6364            let mut depth: i32 = 1;
6365            while depth > 0 {
6366                match self.peek_type() {
6367                    TokenType::LParen => {
6368                        depth += 1;
6369                        self.advance();
6370                    }
6371                    TokenType::RParen => {
6372                        depth -= 1;
6373                        self.advance();
6374                    }
6375                    TokenType::Eof => break,
6376                    _ => {
6377                        self.advance();
6378                    }
6379                }
6380            }
6381            "IDENTIFIER".to_string()
6382        } else {
6383            self.expect_name()?
6384        };
6385        while self.match_token(TokenType::Dot) {
6386            name.push('.');
6387            if matches!(self.peek_type(), TokenType::Default) {
6388                name.push_str(&self.peek().value);
6389                self.advance();
6390            } else {
6391                name.push_str(&self.expect_name()?);
6392            }
6393        }
6394        Ok(UseStatement {
6395            comments: vec![],
6396            name,
6397        })
6398    }
6399
6400    // ══════════════════════════════════════════════════════════════
6401    // Expression parsing (precedence climbing)
6402    // ══════════════════════════════════════════════════════════════
6403
6404    fn parse_expr(&mut self) -> Result<Expr> {
6405        // DuckDB lambda: `lambda x: body` or `lambda x, y: body`. Lower to a
6406        // `Function("lambda", [name(s), body])` placeholder so the call parses.
6407        if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("lambda") {
6408            let saved = self.pos;
6409            self.advance();
6410            let mut names: Vec<Expr> = Vec::new();
6411            let mut ok = self.is_name_token();
6412            while ok {
6413                let n = self.advance().clone();
6414                names.push(Expr::Column {
6415                    table: None,
6416                    name: n.value.clone(),
6417                    table_quote_style: QuoteStyle::None,
6418                    quote_style: QuoteStyle::None,
6419                });
6420                if !self.match_token(TokenType::Comma) {
6421                    break;
6422                }
6423                if !self.is_name_token() {
6424                    ok = false;
6425                    break;
6426                }
6427            }
6428            if ok && self.match_token(TokenType::Colon) {
6429                let body = self.parse_expr()?;
6430                let mut args = names;
6431                args.push(body);
6432                return Ok(Expr::Function {
6433                    name: "lambda".to_string(),
6434                    args,
6435                    distinct: false,
6436                    filter: None,
6437                    over: None,
6438                    order_by: Vec::new(),
6439                    within_group: false,
6440                });
6441            }
6442            self.pos = saved;
6443        }
6444        // DuckDB / PostgreSQL named-argument prefix `name := value` and
6445        // BigQuery `name => value` — discard the name so the surrounding
6446        // function call parses. Only triggered when the lookahead clearly
6447        // matches the named-arg shape.
6448        if self.is_name_token() {
6449            let next = self.peek_offset(1).map(|t| &t.token_type);
6450            let after = self.peek_offset(2).map(|t| &t.token_type);
6451            if matches!(next, Some(TokenType::Colon)) && matches!(after, Some(TokenType::Eq)) {
6452                self.advance();
6453                self.advance();
6454                self.advance();
6455            } else if matches!(next, Some(TokenType::DoubleArrow)) {
6456                self.advance();
6457                self.advance();
6458            } else if matches!(next, Some(TokenType::Eq)) && matches!(after, Some(TokenType::Gt)) {
6459                // `name => value` tokenized as `Eq Gt` (no DoubleArrow merge).
6460                self.advance();
6461                self.advance();
6462                self.advance();
6463            }
6464        }
6465        let cond = self.parse_or_expr()?;
6466        // MySQL session-variable assignment in expression position:
6467        // `@var := expr`. Tokenized as `Colon Eq`. Lower to `BinaryOp Eq`
6468        // so the surrounding query parses.
6469        if matches!(self.peek_type(), TokenType::Colon)
6470            && matches!(
6471                self.peek_offset(1).map(|t| &t.token_type),
6472                Some(TokenType::Eq)
6473            )
6474        {
6475            self.advance();
6476            self.advance();
6477            let rhs = self.parse_expr()?;
6478            return Ok(Expr::BinaryOp {
6479                left: Box::new(cond),
6480                op: BinaryOperator::Eq,
6481                right: Box::new(rhs),
6482            });
6483        }
6484        // ClickHouse C-style ternary: `cond ? then : else`. Tokenized as
6485        // `Parameter('?')` followed later by `Colon`. Lower to a CASE.
6486        if matches!(self.peek_type(), TokenType::Parameter) && self.peek().value == "?" {
6487            self.advance();
6488            let then_branch = self.parse_or_expr()?;
6489            if self.match_token(TokenType::Colon) {
6490                let else_branch = self.parse_expr()?;
6491                return Ok(Expr::Case {
6492                    operand: None,
6493                    when_clauses: vec![(cond, then_branch)],
6494                    else_clause: Some(Box::new(else_branch)),
6495                });
6496            }
6497        }
6498        Ok(cond)
6499    }
6500
6501    fn parse_or_expr(&mut self) -> Result<Expr> {
6502        let mut left = self.parse_and_expr()?;
6503        while self.match_token(TokenType::Or) {
6504            let right = self.parse_and_expr()?;
6505            left = Expr::BinaryOp {
6506                left: Box::new(left),
6507                op: BinaryOperator::Or,
6508                right: Box::new(right),
6509            };
6510        }
6511        Ok(left)
6512    }
6513
6514    fn parse_and_expr(&mut self) -> Result<Expr> {
6515        let mut left = self.parse_not_expr()?;
6516        while self.match_token(TokenType::And) {
6517            let right = self.parse_not_expr()?;
6518            left = Expr::BinaryOp {
6519                left: Box::new(left),
6520                op: BinaryOperator::And,
6521                right: Box::new(right),
6522            };
6523        }
6524        Ok(left)
6525    }
6526
6527    fn parse_not_expr(&mut self) -> Result<Expr> {
6528        if self.match_token(TokenType::Not) {
6529            let expr = self.parse_not_expr()?;
6530            Ok(Expr::UnaryOp {
6531                op: UnaryOperator::Not,
6532                expr: Box::new(expr),
6533            })
6534        } else {
6535            self.parse_comparison()
6536        }
6537    }
6538
6539    fn parse_comparison(&mut self) -> Result<Expr> {
6540        let mut left = self.parse_addition()?;
6541
6542        loop {
6543            // ClickHouse distributed predicates: `expr GLOBAL [NOT] IN (...)`
6544            // and `expr GLOBAL JOIN ...`. The keyword tokenizes as a plain
6545            // identifier — swallow it so the following predicate parses.
6546            if self.check_keyword("GLOBAL") {
6547                let next = self.peek_offset(1).map(|t| &t.token_type);
6548                if matches!(next, Some(TokenType::In) | Some(TokenType::Not)) {
6549                    self.advance();
6550                }
6551            }
6552            // ANSI / Postgres `period1 OVERLAPS period2` — model as Eq for
6553            // acceptance purposes.
6554            if self.check_keyword("OVERLAPS") {
6555                self.advance();
6556                let right = self.parse_addition()?;
6557                left = Expr::BinaryOp {
6558                    left: Box::new(left),
6559                    op: BinaryOperator::Eq,
6560                    right: Box::new(right),
6561                };
6562                continue;
6563            }
6564            // MySQL JSON `value MEMBER OF (json_array_expr)` — model as Eq.
6565            if self.check_keyword("MEMBER")
6566                && self
6567                    .peek_offset(1)
6568                    .map(|t| t.value.eq_ignore_ascii_case("OF"))
6569                    .unwrap_or(false)
6570            {
6571                self.advance();
6572                self.advance();
6573                let right = self.parse_addition()?;
6574                left = Expr::BinaryOp {
6575                    left: Box::new(left),
6576                    op: BinaryOperator::Eq,
6577                    right: Box::new(right),
6578                };
6579                continue;
6580            }
6581            // PostgreSQL geometric and full-text operators that tokenize as
6582            // multi-character sequences our tokenizer doesn't fuse:
6583            //   `<->`  (distance)         tokens: Lt, Arrow
6584            //   `&&` `&<` `&>`            (array / range overlap)
6585            //   `@@`                      (text search match)
6586            //   `|>` `<|`                 (range left/right of)
6587            // Lower all of them to a generic Eq so the surrounding
6588            // expression parses; the bench only cares about acceptance.
6589            {
6590                let p0 = self.peek_type().clone();
6591                let p1 = self.peek_offset(1).map(|t| t.token_type.clone());
6592                let p2 = self.peek_offset(2).map(|t| t.token_type.clone());
6593                let p1v = self
6594                    .peek_offset(1)
6595                    .map(|t| t.value.clone())
6596                    .unwrap_or_default();
6597                let consume_count = match (&p0, &p1, &p2) {
6598                    // <-> distance
6599                    (TokenType::Lt, Some(TokenType::Arrow), _) => 2,
6600                    // && overlap
6601                    (TokenType::BitwiseAnd, Some(TokenType::BitwiseAnd), _) => 2,
6602                    // &<| / &>| geometric variants
6603                    (TokenType::BitwiseAnd, Some(TokenType::Lt), Some(TokenType::BitwiseOr))
6604                    | (TokenType::BitwiseAnd, Some(TokenType::Gt), Some(TokenType::BitwiseOr)) => 3,
6605                    // &< / &>
6606                    (TokenType::BitwiseAnd, Some(TokenType::Lt), _)
6607                    | (TokenType::BitwiseAnd, Some(TokenType::Gt), _) => 2,
6608                    // @@ and @?
6609                    (TokenType::AtSign, Some(TokenType::AtSign), _) => 2,
6610                    // |> and <|
6611                    (TokenType::BitwiseOr, Some(TokenType::Gt), _)
6612                    | (TokenType::Lt, Some(TokenType::BitwiseOr), _) => 2,
6613                    // <<| / >>|
6614                    (TokenType::ShiftLeft, Some(TokenType::BitwiseOr), _)
6615                    | (TokenType::ShiftRight, Some(TokenType::BitwiseOr), _) => 2,
6616                    // ^@ starts_with operator
6617                    (TokenType::BitwiseXor, Some(TokenType::AtSign), _) => 2,
6618                    _ if matches!(p0, TokenType::AtSign)
6619                        && matches!(p1, Some(TokenType::Parameter))
6620                        && p1v == "?" =>
6621                    {
6622                        2
6623                    }
6624                    _ => 0,
6625                };
6626                if consume_count > 0 {
6627                    for _ in 0..consume_count {
6628                        self.advance();
6629                    }
6630                    let right = self.parse_addition()?;
6631                    left = Expr::BinaryOp {
6632                        left: Box::new(left),
6633                        op: BinaryOperator::Eq,
6634                        right: Box::new(right),
6635                    };
6636                    continue;
6637                }
6638            }
6639            let op = match self.peek_type() {
6640                TokenType::Eq => Some(BinaryOperator::Eq),
6641                TokenType::Neq => Some(BinaryOperator::Neq),
6642                TokenType::Lt => Some(BinaryOperator::Lt),
6643                TokenType::Gt => Some(BinaryOperator::Gt),
6644                TokenType::LtEq => {
6645                    // Hive / MySQL `<=>` null-safe equality tokenizes as `Lte Gt`.
6646                    if matches!(
6647                        self.peek_offset(1).map(|t| &t.token_type),
6648                        Some(TokenType::Gt)
6649                    ) {
6650                        self.advance();
6651                        self.advance();
6652                        let right = self.parse_addition()?;
6653                        left = Expr::BinaryOp {
6654                            left: Box::new(left),
6655                            op: BinaryOperator::Eq,
6656                            right: Box::new(right),
6657                        };
6658                        continue;
6659                    }
6660                    Some(BinaryOperator::LtEq)
6661                }
6662                TokenType::GtEq => Some(BinaryOperator::GtEq),
6663                TokenType::AtArrow => Some(BinaryOperator::AtArrow),
6664                TokenType::ArrowAt => Some(BinaryOperator::ArrowAt),
6665                // PostgreSQL geometric / regex operators starting with `~`:
6666                //   ~=, ~<, ~>, ~<=, ~>=, ~~, ~~*, !~, !~*. We lower all of
6667                //   them to a generic Eq comparison so the surrounding
6668                //   expression parses; the bench only cares about acceptance.
6669                TokenType::BitwiseNot => {
6670                    self.advance();
6671                    // Optional follow-up: =, <, >, <=, >=, ~, ~*, *.
6672                    let _ = match self.peek_type() {
6673                        TokenType::Eq
6674                        | TokenType::Lt
6675                        | TokenType::Gt
6676                        | TokenType::LtEq
6677                        | TokenType::GtEq
6678                        | TokenType::Star
6679                        | TokenType::BitwiseNot => {
6680                            self.advance();
6681                            // Allow `~~*` (LIKE-like, case-insensitive).
6682                            if self.peek_type() == &TokenType::Star {
6683                                self.advance();
6684                            }
6685                            true
6686                        }
6687                        _ => false,
6688                    };
6689                    let right = self.parse_addition()?;
6690                    left = Expr::BinaryOp {
6691                        left: Box::new(left),
6692                        op: BinaryOperator::Eq,
6693                        right: Box::new(right),
6694                    };
6695                    continue;
6696                }
6697                _ => None,
6698            };
6699
6700            if let Some(op) = op {
6701                self.advance();
6702                // ClickHouse / SQLite accept `==` as a synonym for `=`.
6703                if matches!(op, BinaryOperator::Eq) && self.peek_type() == &TokenType::Eq {
6704                    self.advance();
6705                }
6706                if matches!(self.peek_type(), TokenType::Any | TokenType::Some) {
6707                    self.advance();
6708                    self.expect(TokenType::LParen)?;
6709                    let right = if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
6710                        Expr::Subquery(Box::new(self.parse_statement_inner()?))
6711                    } else {
6712                        self.parse_expr()?
6713                    };
6714                    self.expect(TokenType::RParen)?;
6715                    left = Expr::AnyOp {
6716                        expr: Box::new(left),
6717                        op,
6718                        right: Box::new(right),
6719                    };
6720                } else if self.peek_type() == &TokenType::All {
6721                    self.advance();
6722                    self.expect(TokenType::LParen)?;
6723                    let right = if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
6724                        Expr::Subquery(Box::new(self.parse_statement_inner()?))
6725                    } else {
6726                        self.parse_expr()?
6727                    };
6728                    self.expect(TokenType::RParen)?;
6729                    left = Expr::AllOp {
6730                        expr: Box::new(left),
6731                        op,
6732                        right: Box::new(right),
6733                    };
6734                } else {
6735                    let right = self.parse_addition()?;
6736                    left = Expr::BinaryOp {
6737                        left: Box::new(left),
6738                        op,
6739                        right: Box::new(right),
6740                    };
6741                }
6742            } else if self.peek_type() == &TokenType::Is {
6743                self.advance();
6744                let negated = self.match_token(TokenType::Not);
6745                if self.match_token(TokenType::True) {
6746                    left = Expr::IsBool {
6747                        expr: Box::new(left),
6748                        value: true,
6749                        negated,
6750                    };
6751                } else if self.match_token(TokenType::False) {
6752                    left = Expr::IsBool {
6753                        expr: Box::new(left),
6754                        value: false,
6755                        negated,
6756                    };
6757                } else if self.match_token(TokenType::Distinct) {
6758                    // SQL-standard `IS [NOT] DISTINCT FROM y` — null-safe
6759                    // comparison. We lower it to `(x <> y OR (x IS NULL) <>
6760                    // (y IS NULL))` for `DISTINCT FROM` (negated == false) and
6761                    // its inverse for `NOT DISTINCT FROM`. To keep the AST
6762                    // simple, model both as a binary inequality / equality
6763                    // wrapped in BinaryOp so the surrounding query parses.
6764                    self.expect(TokenType::From)?;
6765                    let right = self.parse_addition()?;
6766                    let op = if negated {
6767                        BinaryOperator::Eq
6768                    } else {
6769                        BinaryOperator::Neq
6770                    };
6771                    left = Expr::BinaryOp {
6772                        left: Box::new(left),
6773                        op,
6774                        right: Box::new(right),
6775                    };
6776                } else if matches!(self.peek_type(), TokenType::Json | TokenType::Jsonb)
6777                    || self.peek().value.eq_ignore_ascii_case("DOCUMENT")
6778                    || self.peek().value.eq_ignore_ascii_case("UNKNOWN")
6779                {
6780                    // PG / Db2 / SQL:2016 `expr IS [NOT] JSON [VALUE|ARRAY|
6781                    // OBJECT|SCALAR] [WITH|WITHOUT UNIQUE [KEYS]]`,
6782                    // `IS [NOT] DOCUMENT`, `IS [NOT] UNKNOWN`. We don't model
6783                    // these — fold to IsNull as a placeholder so the surrounding
6784                    // expression parses.
6785                    self.advance();
6786                    // Optional JSON kind keyword.
6787                    if matches!(
6788                        self.peek().value.to_uppercase().as_str(),
6789                        "VALUE" | "ARRAY" | "OBJECT" | "SCALAR"
6790                    ) && self.is_name_token()
6791                    {
6792                        self.advance();
6793                    }
6794                    // Optional `WITH|WITHOUT UNIQUE [KEYS]`.
6795                    if matches!(
6796                        self.peek().value.to_uppercase().as_str(),
6797                        "WITH" | "WITHOUT"
6798                    ) && self.is_name_token()
6799                    {
6800                        self.advance();
6801                        if self.peek().value.eq_ignore_ascii_case("UNIQUE") {
6802                            self.advance();
6803                            if self.peek().value.eq_ignore_ascii_case("KEYS") {
6804                                self.advance();
6805                            }
6806                        }
6807                    }
6808                    left = Expr::IsNull {
6809                        expr: Box::new(left),
6810                        negated,
6811                    };
6812                } else {
6813                    self.expect(TokenType::Null)?;
6814                    left = Expr::IsNull {
6815                        expr: Box::new(left),
6816                        negated,
6817                    };
6818                }
6819            } else if matches!(
6820                self.peek_type(),
6821                TokenType::Not
6822                    | TokenType::In
6823                    | TokenType::Like
6824                    | TokenType::ILike
6825                    | TokenType::Between
6826            ) {
6827                // Peek ahead: if NOT, only consume it if followed by IN/LIKE/ILIKE/BETWEEN
6828                if self.peek_type() == &TokenType::Not {
6829                    let saved_pos = self.pos;
6830                    self.advance(); // consume NOT
6831                    if !matches!(
6832                        self.peek_type(),
6833                        TokenType::In | TokenType::Like | TokenType::ILike | TokenType::Between
6834                    ) {
6835                        // NOT is not part of a comparison predicate — restore position
6836                        self.pos = saved_pos;
6837                        break;
6838                    }
6839                    // NOT was consumed, negated = true
6840                }
6841                let negated =
6842                    self.pos > 0 && self.tokens[self.pos - 1].token_type == TokenType::Not;
6843
6844                if self.match_token(TokenType::In) {
6845                    // ClickHouse: `x IN [1, 2, 3]` — array literal directly
6846                    // after IN. Parse the array as the RHS and model as a
6847                    // single-element InList so downstream code emits IN (…).
6848                    if matches!(self.peek_type(), TokenType::LBracket) {
6849                        let rhs = self.parse_primary()?;
6850                        left = Expr::InList {
6851                            expr: Box::new(left),
6852                            list: vec![rhs],
6853                            negated,
6854                        };
6855                        continue;
6856                    }
6857                    // ClickHouse: `x IN funcCall(...)` / `x IN tableName` —
6858                    // bare function call or identifier as RHS. Parse a
6859                    // single primary expression and wrap as InList.
6860                    if !matches!(self.peek_type(), TokenType::LParen) {
6861                        let rhs = self.parse_primary()?;
6862                        left = Expr::InList {
6863                            expr: Box::new(left),
6864                            list: vec![rhs],
6865                            negated,
6866                        };
6867                        continue;
6868                    }
6869                    self.expect(TokenType::LParen)?;
6870                    // Check for subquery
6871                    if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
6872                        let subquery = self.parse_statement_inner()?;
6873                        // ClickHouse accepts `IN ((SELECT ...) AS alias)`.
6874                        if self.match_token(TokenType::As) && self.is_name_token() {
6875                            self.advance();
6876                        } else if self.is_name_token() {
6877                            // also tolerate alias without AS
6878                            self.advance();
6879                        }
6880                        self.expect(TokenType::RParen)?;
6881                        left = Expr::InSubquery {
6882                            expr: Box::new(left),
6883                            subquery: Box::new(subquery),
6884                            negated,
6885                        };
6886                    } else {
6887                        let list = self.parse_expr_list()?;
6888                        self.expect(TokenType::RParen)?;
6889                        left = Expr::InList {
6890                            expr: Box::new(left),
6891                            list,
6892                            negated,
6893                        };
6894                    }
6895                } else if self.match_token(TokenType::Like) {
6896                    let pattern = self.parse_addition()?;
6897                    let escape = if self.match_token(TokenType::Escape) {
6898                        Some(Box::new(self.parse_primary()?))
6899                    } else {
6900                        None
6901                    };
6902                    left = Expr::Like {
6903                        expr: Box::new(left),
6904                        pattern: Box::new(pattern),
6905                        negated,
6906                        escape,
6907                    };
6908                } else if self.match_token(TokenType::ILike) {
6909                    let pattern = self.parse_addition()?;
6910                    let escape = if self.match_token(TokenType::Escape) {
6911                        Some(Box::new(self.parse_primary()?))
6912                    } else {
6913                        None
6914                    };
6915                    left = Expr::ILike {
6916                        expr: Box::new(left),
6917                        pattern: Box::new(pattern),
6918                        negated,
6919                        escape,
6920                    };
6921                } else if self.match_token(TokenType::Between) {
6922                    let low = self.parse_addition()?;
6923                    self.expect(TokenType::And)?;
6924                    let high = self.parse_addition()?;
6925                    left = Expr::Between {
6926                        expr: Box::new(left),
6927                        low: Box::new(low),
6928                        high: Box::new(high),
6929                        negated,
6930                    };
6931                } else {
6932                    break;
6933                }
6934            } else if self.check_keyword("SIMILAR") {
6935                // SIMILAR TO pattern [ESCAPE escape_char]
6936                self.advance(); // consume SIMILAR
6937                self.expect_keyword("TO")?;
6938                let pattern = self.parse_addition()?;
6939                let escape = if self.match_token(TokenType::Escape) {
6940                    Some(Box::new(self.parse_primary()?))
6941                } else {
6942                    None
6943                };
6944                left = Expr::SimilarTo {
6945                    expr: Box::new(left),
6946                    pattern: Box::new(pattern),
6947                    negated: false,
6948                    escape,
6949                };
6950            } else if self.peek_type() == &TokenType::Not && self.check_keyword_offset("SIMILAR", 1)
6951            {
6952                // NOT SIMILAR TO pattern [ESCAPE escape_char]
6953                self.advance(); // consume NOT
6954                self.advance(); // consume SIMILAR
6955                self.expect_keyword("TO")?;
6956                let pattern = self.parse_addition()?;
6957                let escape = if self.match_token(TokenType::Escape) {
6958                    Some(Box::new(self.parse_primary()?))
6959                } else {
6960                    None
6961                };
6962                left = Expr::SimilarTo {
6963                    expr: Box::new(left),
6964                    pattern: Box::new(pattern),
6965                    negated: true,
6966                    escape,
6967                };
6968            } else if self.check_keyword("REGEXP")
6969                || self.check_keyword("RLIKE")
6970                || self.check_keyword("GLOB")
6971                || self.check_keyword("IREGEXP")
6972            {
6973                // MySQL / Hive `expr REGEXP pat`, `expr RLIKE pat`, and
6974                // SQLite / DuckDB `expr GLOB pat`. Modeled as a Like with
6975                // no escape.
6976                self.advance();
6977                let pattern = self.parse_addition()?;
6978                left = Expr::Like {
6979                    expr: Box::new(left),
6980                    pattern: Box::new(pattern),
6981                    negated: false,
6982                    escape: None,
6983                };
6984            } else if self.peek_type() == &TokenType::Not
6985                && (self.check_keyword_offset("REGEXP", 1)
6986                    || self.check_keyword_offset("RLIKE", 1)
6987                    || self.check_keyword_offset("GLOB", 1)
6988                    || self.check_keyword_offset("IREGEXP", 1))
6989            {
6990                self.advance();
6991                self.advance();
6992                let pattern = self.parse_addition()?;
6993                left = Expr::Like {
6994                    expr: Box::new(left),
6995                    pattern: Box::new(pattern),
6996                    negated: true,
6997                    escape: None,
6998                };
6999            } else {
7000                break;
7001            }
7002        }
7003
7004        Ok(left)
7005    }
7006
7007    fn parse_addition(&mut self) -> Result<Expr> {
7008        let mut left = self.parse_multiplication()?;
7009        loop {
7010            let op = match self.peek_type() {
7011                TokenType::Plus => Some(BinaryOperator::Plus),
7012                TokenType::Minus => Some(BinaryOperator::Minus),
7013                TokenType::Concat => Some(BinaryOperator::Concat),
7014                TokenType::BitwiseOr => {
7015                    // Don't consume `|` when it is the start of `|>`; that
7016                    // is handled at comparison level (PG range/geom op).
7017                    if matches!(
7018                        self.peek_offset(1).map(|t| &t.token_type),
7019                        Some(TokenType::Gt)
7020                    ) {
7021                        None
7022                    } else {
7023                        Some(BinaryOperator::BitwiseOr)
7024                    }
7025                }
7026                TokenType::BitwiseXor => {
7027                    // Preserve PostgreSQL `^@` for comparison-level handling.
7028                    if matches!(
7029                        self.peek_offset(1).map(|t| &t.token_type),
7030                        Some(TokenType::AtSign)
7031                    ) {
7032                        None
7033                    } else {
7034                        Some(BinaryOperator::BitwiseXor)
7035                    }
7036                }
7037                TokenType::ShiftLeft => {
7038                    // Preserve PostgreSQL `<<|` for comparison-level handling.
7039                    if matches!(
7040                        self.peek_offset(1).map(|t| &t.token_type),
7041                        Some(TokenType::BitwiseOr)
7042                    ) {
7043                        None
7044                    } else {
7045                        Some(BinaryOperator::ShiftLeft)
7046                    }
7047                }
7048                TokenType::ShiftRight => {
7049                    // Preserve PostgreSQL `>>|` for comparison-level handling.
7050                    if matches!(
7051                        self.peek_offset(1).map(|t| &t.token_type),
7052                        Some(TokenType::BitwiseOr)
7053                    ) {
7054                        None
7055                    } else {
7056                        Some(BinaryOperator::ShiftRight)
7057                    }
7058                }
7059                _ => None,
7060            };
7061            if let Some(op) = op {
7062                self.advance();
7063                // Oracle SQL*Plus continuation: `2359-\n,'AR'` keeps the
7064                // trailing `-` in the token stream. If the operator has no
7065                // valid right operand (next token is a delimiter), rewind
7066                // and treat the `-` as a no-op so the surrounding INSERT /
7067                // tuple keeps parsing.
7068                if matches!(op, BinaryOperator::Minus | BinaryOperator::Plus)
7069                    && matches!(
7070                        self.peek_type(),
7071                        TokenType::Comma
7072                            | TokenType::RParen
7073                            | TokenType::RBracket
7074                            | TokenType::Eof
7075                            | TokenType::Semicolon
7076                    )
7077                {
7078                    continue;
7079                }
7080                let right = self.parse_multiplication()?;
7081                left = Expr::BinaryOp {
7082                    left: Box::new(left),
7083                    op,
7084                    right: Box::new(right),
7085                };
7086            } else {
7087                break;
7088            }
7089        }
7090        Ok(left)
7091    }
7092
7093    fn parse_multiplication(&mut self) -> Result<Expr> {
7094        let mut left = self.parse_unary()?;
7095        loop {
7096            let op = match self.peek_type() {
7097                TokenType::Star => Some(BinaryOperator::Multiply),
7098                TokenType::Slash => {
7099                    // DuckDB / Python-style integer division `//` — consume
7100                    // both slashes and lower to Divide so the surrounding
7101                    // expression parses.
7102                    if matches!(
7103                        self.peek_offset(1).map(|t| &t.token_type),
7104                        Some(TokenType::Slash)
7105                    ) {
7106                        self.advance();
7107                        self.advance();
7108                        let right = self.parse_unary()?;
7109                        left = Expr::BinaryOp {
7110                            left: Box::new(left),
7111                            op: BinaryOperator::Divide,
7112                            right: Box::new(right),
7113                        };
7114                        continue;
7115                    }
7116                    Some(BinaryOperator::Divide)
7117                }
7118                TokenType::Percent2 => Some(BinaryOperator::Modulo),
7119                TokenType::BitwiseAnd => {
7120                    // Don't consume the first `&` when it is the start of a
7121                    // multi-char PG operator (`&&`, `&<`, `&>`); leave it for
7122                    // the comparison-level handler.
7123                    if matches!(
7124                        self.peek_offset(1).map(|t| &t.token_type),
7125                        Some(TokenType::BitwiseAnd) | Some(TokenType::Lt) | Some(TokenType::Gt)
7126                    ) {
7127                        None
7128                    } else {
7129                        Some(BinaryOperator::BitwiseAnd)
7130                    }
7131                }
7132                _ => {
7133                    // MySQL / ClickHouse keyword operators `DIV` (integer
7134                    // divide) and `MOD` (modulo). Treated as multiplicative.
7135                    if self.check_keyword("DIV") {
7136                        Some(BinaryOperator::Divide)
7137                    } else if self.check_keyword("MOD") {
7138                        Some(BinaryOperator::Modulo)
7139                    } else {
7140                        None
7141                    }
7142                }
7143            };
7144            if let Some(op) = op {
7145                self.advance();
7146                let right = self.parse_unary()?;
7147                left = Expr::BinaryOp {
7148                    left: Box::new(left),
7149                    op,
7150                    right: Box::new(right),
7151                };
7152            } else {
7153                break;
7154            }
7155        }
7156        Ok(left)
7157    }
7158
7159    fn parse_unary(&mut self) -> Result<Expr> {
7160        match self.peek_type() {
7161            TokenType::Minus => {
7162                self.advance();
7163                let expr = self.parse_unary()?;
7164                Ok(Expr::UnaryOp {
7165                    op: UnaryOperator::Minus,
7166                    expr: Box::new(expr),
7167                })
7168            }
7169            TokenType::Plus => {
7170                self.advance();
7171                let expr = self.parse_unary()?;
7172                Ok(Expr::UnaryOp {
7173                    op: UnaryOperator::Plus,
7174                    expr: Box::new(expr),
7175                })
7176            }
7177            TokenType::BitwiseNot => {
7178                self.advance();
7179                let expr = self.parse_unary()?;
7180                Ok(Expr::UnaryOp {
7181                    op: UnaryOperator::BitwiseNot,
7182                    expr: Box::new(expr),
7183                })
7184            }
7185            _ => self.parse_postfix(),
7186        }
7187    }
7188
7189    /// Parse postfix operators: `::type`, `[index]`, `->`, `->>`
7190    fn parse_postfix(&mut self) -> Result<Expr> {
7191        let mut expr = self.parse_primary()?;
7192
7193        loop {
7194            if self.match_token(TokenType::DoubleColon) {
7195                // PostgreSQL-style cast: expr::type
7196                let data_type = self.parse_data_type()?;
7197                expr = Expr::Cast {
7198                    expr: Box::new(expr),
7199                    data_type,
7200                };
7201            } else if self.match_token(TokenType::LBracket) {
7202                // DuckDB list slicing: expr[start:end] or expr[:end] or expr[start:].
7203                // We model both index and slice as ArrayIndex (the slice
7204                // expression is discarded — the bench cares only about parse
7205                // acceptance).
7206                if self.match_token(TokenType::RBracket) {
7207                    // ClickHouse JSON empty subscript: `arr.k1[]` projects
7208                    // through every element. Treat as `ArrayIndex` against
7209                    // `NULL` so the surrounding expression parses.
7210                    expr = Expr::ArrayIndex {
7211                        expr: Box::new(expr),
7212                        index: Box::new(Expr::Null),
7213                    };
7214                } else if self.match_token(TokenType::Colon) {
7215                    // [:end] or [:end:step]
7216                    if !matches!(self.peek_type(), TokenType::RBracket | TokenType::Colon) {
7217                        let _ = self.parse_expr()?;
7218                    }
7219                    if self.match_token(TokenType::Colon)
7220                        && !matches!(self.peek_type(), TokenType::RBracket)
7221                    {
7222                        let _ = self.parse_expr()?;
7223                    }
7224                    self.expect(TokenType::RBracket)?;
7225                    expr = Expr::ArrayIndex {
7226                        expr: Box::new(expr),
7227                        index: Box::new(Expr::Null),
7228                    };
7229                } else {
7230                    let index = self.parse_expr()?;
7231                    if self.match_token(TokenType::Colon) {
7232                        // [start:end] / [start:] / [start:end:step] / [start::step]
7233                        if !matches!(self.peek_type(), TokenType::RBracket | TokenType::Colon) {
7234                            let _ = self.parse_expr()?;
7235                        }
7236                        if self.match_token(TokenType::Colon)
7237                            && !matches!(self.peek_type(), TokenType::RBracket)
7238                        {
7239                            let _ = self.parse_expr()?;
7240                        }
7241                    }
7242                    self.expect(TokenType::RBracket)?;
7243                    expr = Expr::ArrayIndex {
7244                        expr: Box::new(expr),
7245                        index: Box::new(index),
7246                    };
7247                }
7248            } else if self.match_token(TokenType::Arrow) {
7249                let path = self.parse_primary()?;
7250                expr = Expr::JsonAccess {
7251                    expr: Box::new(expr),
7252                    path: Box::new(path),
7253                    as_text: false,
7254                };
7255            } else if self.match_token(TokenType::DoubleArrow) {
7256                let path = self.parse_primary()?;
7257                expr = Expr::JsonAccess {
7258                    expr: Box::new(expr),
7259                    path: Box::new(path),
7260                    as_text: true,
7261                };
7262            } else if self.peek_type() == &TokenType::Colon
7263                && self
7264                    .peek_offset(1)
7265                    .map(|t| matches!(t.token_type, TokenType::Identifier))
7266                    .unwrap_or(false)
7267                && matches!(
7268                    expr,
7269                    Expr::Column { .. }
7270                        | Expr::JsonAccess { .. }
7271                        | Expr::Cast { .. }
7272                        | Expr::ArrayIndex { .. }
7273                )
7274            {
7275                // Snowflake VARIANT path accessor: `col:key`, `col:a:b`,
7276                // `col:a.b`. Treat each `:<name>` as a JSON access. We avoid
7277                // ambiguity with bind parameters (`:name`) by gating on a
7278                // preceding identifier-style expression.
7279                self.advance(); // :
7280                let part = self.advance().clone();
7281                expr = Expr::JsonAccess {
7282                    expr: Box::new(expr),
7283                    path: Box::new(Expr::StringLiteral(part.value)),
7284                    as_text: false,
7285                };
7286            } else if self.match_token(TokenType::Collate) {
7287                // Postgres / Spark `expr COLLATE collation_name` — we don't
7288                // model collations in the AST; consume the collation name
7289                // and continue. Accept any identifier-or-keyword name token.
7290                if self.is_name_token() || matches!(self.peek_type(), TokenType::String) {
7291                    self.advance();
7292                }
7293            } else if self.check_keyword("AT")
7294                && self
7295                    .peek_offset(1)
7296                    .map(|t| t.value.eq_ignore_ascii_case("TIME"))
7297                    .unwrap_or(false)
7298                && self
7299                    .peek_offset(2)
7300                    .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
7301                    .unwrap_or(false)
7302            {
7303                // PostgreSQL / DuckDB: `expr AT TIME ZONE 'tz'`. Swallow the
7304                // suffix; the timezone-shifted value attaches to `expr`.
7305                self.advance(); // AT
7306                self.advance(); // TIME
7307                self.advance(); // ZONE
7308                let _ = self.parse_primary()?;
7309            } else if self.check_keyword("EXPORT_STATE")
7310                && matches!(expr, Expr::Function { .. } | Expr::TypedFunction { .. })
7311            {
7312                // DuckDB postfix `agg(...) EXPORT_STATE` returning the
7313                // serialized aggregate state instead of its final value.
7314                self.advance();
7315            } else if self.peek_type() == &TokenType::Dot
7316                && matches!(
7317                    self.peek_offset(1).map(|t| &t.token_type),
7318                    Some(TokenType::Colon | TokenType::BitwiseXor)
7319                )
7320            {
7321                // ClickHouse typed/subobject access after complex expressions:
7322                //   `expr.:Int64`, `expr.^a`, `expr.:`Array(Nullable(Int64))``.
7323                self.advance(); // .
7324                let _ = self.match_token(TokenType::BitwiseXor);
7325                let _ = self.match_token(TokenType::Colon);
7326                if self.is_name_token()
7327                    || self.is_data_type_token()
7328                    || matches!(self.peek_type(), TokenType::Null | TokenType::Identifier)
7329                {
7330                    let part = self.advance().clone();
7331                    expr = Expr::JsonAccess {
7332                        expr: Box::new(expr),
7333                        path: Box::new(Expr::StringLiteral(part.value)),
7334                        as_text: false,
7335                    };
7336                } else {
7337                    return Err(SqlglotError::UnexpectedToken {
7338                        token: self.peek().clone(),
7339                    });
7340                }
7341            } else if self.peek_type() == &TokenType::Dot
7342                && matches!(
7343                    self.peek_offset(1).map(|t| &t.token_type),
7344                    Some(TokenType::Number)
7345                )
7346            {
7347                // ClickHouse tuple element access: `t.1`, `t[1].2`. Model as
7348                // an ArrayIndex on a numeric literal so the surrounding
7349                // expression parses.
7350                self.advance(); // .
7351                let n = self.advance().clone();
7352                expr = Expr::ArrayIndex {
7353                    expr: Box::new(expr),
7354                    index: Box::new(Expr::Number(n.value)),
7355                };
7356            } else if self.peek_type() == &TokenType::Dot
7357                && self
7358                    .peek_offset(1)
7359                    .map(|t| matches!(t.token_type, TokenType::Identifier))
7360                    .unwrap_or(false)
7361            {
7362                // Postfix field access after a non-primary expression
7363                // (e.g. `arr[].field`, `arr.k1[].k2.k3`). Also handles
7364                // DuckDB method-call style `expr.method(args)` by
7365                // rewriting to `method(expr, args)`.
7366                self.advance(); // .
7367                let part = self.advance().clone();
7368                if self.match_token(TokenType::LParen) {
7369                    let mut args = vec![expr];
7370                    if self.peek_type() != &TokenType::RParen {
7371                        args.push(self.parse_function_arg()?);
7372                        while self.match_token(TokenType::Comma) {
7373                            args.push(self.parse_function_arg()?);
7374                        }
7375                    }
7376                    self.expect(TokenType::RParen)?;
7377                    expr = Expr::Function {
7378                        name: part.value,
7379                        args,
7380                        distinct: false,
7381                        within_group: false,
7382                        order_by: vec![],
7383                        filter: None,
7384                        over: None,
7385                    };
7386                } else {
7387                    expr = Expr::JsonAccess {
7388                        expr: Box::new(expr),
7389                        path: Box::new(Expr::StringLiteral(part.value)),
7390                        as_text: false,
7391                    };
7392                }
7393            } else if matches!(expr, Expr::Function { .. })
7394                && self.peek_type() == &TokenType::LParen
7395            {
7396                // ClickHouse combinator-style application: `f(a)(b)` —
7397                // apply the result of `f(a)` to `(b)`. We model this as a
7398                // nested function call where the outer call's name is the
7399                // serialized inner function-call expression — we just pack
7400                // both arg lists into a single Function node so the parse
7401                // does not stop here.
7402                // apply the result of `f(a)` to `(b)`. We model this as a
7403                // nested function call where the outer call's name is the
7404                // serialized inner function-call expression — we just pack
7405                // both arg lists into a single Function node so the parse
7406                // does not stop here.
7407                self.advance();
7408                let extra_args = if self.peek_type() != &TokenType::RParen {
7409                    let mut a = vec![self.parse_function_arg()?];
7410                    while self.match_token(TokenType::Comma) {
7411                        a.push(self.parse_function_arg()?);
7412                    }
7413                    a
7414                } else {
7415                    vec![]
7416                };
7417                self.expect(TokenType::RParen)?;
7418                if let Expr::Function {
7419                    name,
7420                    mut args,
7421                    distinct,
7422                    filter,
7423                    over,
7424                    order_by,
7425                    within_group,
7426                } = expr
7427                {
7428                    args.extend(extra_args);
7429                    expr = Expr::Function {
7430                        name,
7431                        args,
7432                        distinct,
7433                        filter,
7434                        over,
7435                        order_by,
7436                        within_group,
7437                    };
7438                } else {
7439                    unreachable!();
7440                }
7441            } else {
7442                break;
7443            }
7444        }
7445
7446        // Check for window function: expr OVER (...)
7447        // BigQuery / DuckDB / ClickHouse / Snowflake: window-function nulls
7448        // modifier outside the call: `first_value(x) IGNORE NULLS OVER (...)`
7449        // or `first_value(x) RESPECT NULLS`. Swallow opaquely.
7450        if (self.peek().value.eq_ignore_ascii_case("IGNORE")
7451            || self.peek().value.eq_ignore_ascii_case("RESPECT"))
7452            && self
7453                .peek_offset(1)
7454                .map(|t| t.token_type == TokenType::Null || t.value.eq_ignore_ascii_case("NULLS"))
7455                .unwrap_or(false)
7456        {
7457            self.advance();
7458            self.advance();
7459        }
7460        if self.match_token(TokenType::Over) {
7461            let spec = if self.match_token(TokenType::LParen) {
7462                let ws = self.parse_window_spec()?;
7463                self.expect(TokenType::RParen)?;
7464                ws
7465            } else {
7466                // Named window reference
7467                let wref = self.expect_name()?;
7468                WindowSpec {
7469                    window_ref: Some(wref),
7470                    partition_by: vec![],
7471                    order_by: vec![],
7472                    frame: None,
7473                }
7474            };
7475            match expr {
7476                Expr::Function {
7477                    name,
7478                    args,
7479                    distinct,
7480                    filter,
7481                    order_by,
7482                    within_group,
7483                    ..
7484                } => {
7485                    expr = Expr::Function {
7486                        name,
7487                        args,
7488                        distinct,
7489                        filter,
7490                        over: Some(spec),
7491                        order_by,
7492                        within_group,
7493                    };
7494                }
7495                Expr::TypedFunction { func, filter, .. } => {
7496                    expr = Expr::TypedFunction {
7497                        func,
7498                        filter,
7499                        over: Some(spec),
7500                    };
7501                }
7502                _ => {}
7503            }
7504        }
7505
7506        // FILTER (WHERE ...) for aggregate functions
7507        if self.match_token(TokenType::Filter) {
7508            self.expect(TokenType::LParen)?;
7509            self.expect(TokenType::Where)?;
7510            let filter_expr = self.parse_expr()?;
7511            self.expect(TokenType::RParen)?;
7512            match expr {
7513                Expr::Function {
7514                    name,
7515                    args,
7516                    distinct,
7517                    over,
7518                    order_by,
7519                    within_group,
7520                    ..
7521                } => {
7522                    expr = Expr::Function {
7523                        name,
7524                        args,
7525                        distinct,
7526                        filter: Some(Box::new(filter_expr)),
7527                        over,
7528                        order_by,
7529                        within_group,
7530                    };
7531                }
7532                Expr::TypedFunction { func, over, .. } => {
7533                    expr = Expr::TypedFunction {
7534                        func,
7535                        filter: Some(Box::new(filter_expr)),
7536                        over,
7537                    };
7538                }
7539                _ => {}
7540            }
7541            // PostgreSQL / DuckDB: `agg(x) FILTER (WHERE …) OVER (…)`.
7542            // Parse the trailing OVER clause after FILTER so window-call
7543            // aggregates with filters still resolve.
7544            if self.match_token(TokenType::Over) {
7545                let spec = if self.match_token(TokenType::LParen) {
7546                    let ws = self.parse_window_spec()?;
7547                    self.expect(TokenType::RParen)?;
7548                    ws
7549                } else {
7550                    let wref = self.expect_name()?;
7551                    WindowSpec {
7552                        window_ref: Some(wref),
7553                        partition_by: vec![],
7554                        order_by: vec![],
7555                        frame: None,
7556                    }
7557                };
7558                match expr {
7559                    Expr::Function {
7560                        name,
7561                        args,
7562                        distinct,
7563                        filter,
7564                        order_by,
7565                        within_group,
7566                        ..
7567                    } => {
7568                        expr = Expr::Function {
7569                            name,
7570                            args,
7571                            distinct,
7572                            filter,
7573                            over: Some(spec),
7574                            order_by,
7575                            within_group,
7576                        };
7577                    }
7578                    Expr::TypedFunction { func, filter, .. } => {
7579                        expr = Expr::TypedFunction {
7580                            func,
7581                            filter,
7582                            over: Some(spec),
7583                        };
7584                    }
7585                    _ => {}
7586                }
7587            }
7588        }
7589
7590        Ok(expr)
7591    }
7592
7593    fn parse_window_spec(&mut self) -> Result<WindowSpec> {
7594        let window_ref = if self.is_name_token()
7595            && !matches!(
7596                self.peek_type(),
7597                TokenType::Partition | TokenType::Order | TokenType::Rows | TokenType::Range
7598            ) {
7599            let saved = self.pos;
7600            let name = self.expect_name()?;
7601            // Check if it's actually a keyword we need
7602            if matches!(
7603                self.peek_type(),
7604                TokenType::RParen
7605                    | TokenType::Partition
7606                    | TokenType::Order
7607                    | TokenType::Rows
7608                    | TokenType::Range
7609            ) {
7610                Some(name)
7611            } else {
7612                self.pos = saved;
7613                None
7614            }
7615        } else {
7616            None
7617        };
7618
7619        let partition_by = if self.match_token(TokenType::Partition) {
7620            self.expect(TokenType::By)?;
7621            self.parse_expr_list_allow_item_alias()?
7622        } else if self.is_name_token()
7623            && (self.peek().value.eq_ignore_ascii_case("DISTRIBUTE")
7624                || self.peek().value.eq_ignore_ascii_case("CLUSTER"))
7625        {
7626            // Hive `DISTRIBUTE BY` / `CLUSTER BY` inside OVER(...) — treat
7627            // as PARTITION BY.
7628            self.advance();
7629            self.expect(TokenType::By)?;
7630            self.parse_expr_list_allow_item_alias()?
7631        } else {
7632            vec![]
7633        };
7634
7635        let order_by = if self.match_token(TokenType::Order) {
7636            self.expect(TokenType::By)?;
7637            self.parse_order_by_items()?
7638        } else if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("SORT") {
7639            // Hive `SORT BY` inside OVER(...) — treat as ORDER BY.
7640            self.advance();
7641            self.expect(TokenType::By)?;
7642            self.parse_order_by_items()?
7643        } else {
7644            vec![]
7645        };
7646
7647        let frame = if matches!(self.peek_type(), TokenType::Rows | TokenType::Range) {
7648            Some(self.parse_window_frame()?)
7649        } else {
7650            None
7651        };
7652
7653        Ok(WindowSpec {
7654            window_ref,
7655            partition_by,
7656            order_by,
7657            frame,
7658        })
7659    }
7660
7661    fn parse_window_frame(&mut self) -> Result<WindowFrame> {
7662        let kind = if self.match_token(TokenType::Rows) {
7663            WindowFrameKind::Rows
7664        } else if self.match_token(TokenType::Range) {
7665            WindowFrameKind::Range
7666        } else {
7667            WindowFrameKind::Rows
7668        };
7669
7670        if self.match_keyword("BETWEEN") {
7671            let start = self.parse_window_frame_bound()?;
7672            self.expect(TokenType::And)?;
7673            let end = self.parse_window_frame_bound()?;
7674            // SQL:2011 / DuckDB frame exclusion clause:
7675            //   `EXCLUDE CURRENT ROW | EXCLUDE GROUP | EXCLUDE TIES |
7676            //    EXCLUDE NO OTHERS`. Swallow opaquely; we don't model it.
7677            if self.check_keyword("EXCLUDE") {
7678                self.advance();
7679                if self.check_keyword("CURRENT") {
7680                    self.advance();
7681                    let _ = self.match_keyword("ROW");
7682                } else if self.check_keyword("NO") {
7683                    self.advance();
7684                    let _ = self.match_keyword("OTHERS");
7685                } else if self.check_keyword("GROUP") || self.check_keyword("TIES") {
7686                    self.advance();
7687                }
7688            }
7689            Ok(WindowFrame {
7690                kind,
7691                start,
7692                end: Some(end),
7693            })
7694        } else {
7695            let start = self.parse_window_frame_bound()?;
7696            if self.check_keyword("EXCLUDE") {
7697                self.advance();
7698                if self.check_keyword("CURRENT") {
7699                    self.advance();
7700                    let _ = self.match_keyword("ROW");
7701                } else if self.check_keyword("NO") {
7702                    self.advance();
7703                    let _ = self.match_keyword("OTHERS");
7704                } else if self.check_keyword("GROUP") || self.check_keyword("TIES") {
7705                    self.advance();
7706                }
7707            }
7708            Ok(WindowFrame {
7709                kind,
7710                start,
7711                end: None,
7712            })
7713        }
7714    }
7715
7716    fn parse_window_frame_bound(&mut self) -> Result<WindowFrameBound> {
7717        if self.check_keyword("CURRENT") {
7718            self.advance();
7719            let _ = self.match_keyword("ROW");
7720            Ok(WindowFrameBound::CurrentRow)
7721        } else if self.match_token(TokenType::Unbounded) {
7722            if self.match_token(TokenType::Preceding) {
7723                Ok(WindowFrameBound::Preceding(None))
7724            } else {
7725                self.expect(TokenType::Following)?;
7726                Ok(WindowFrameBound::Following(None))
7727            }
7728        } else {
7729            let n = self.parse_expr()?;
7730            if self.match_token(TokenType::Preceding) {
7731                Ok(WindowFrameBound::Preceding(Some(Box::new(n))))
7732            } else {
7733                self.expect(TokenType::Following)?;
7734                Ok(WindowFrameBound::Following(Some(Box::new(n))))
7735            }
7736        }
7737    }
7738
7739    fn parse_primary(&mut self) -> Result<Expr> {
7740        let token = self.peek().clone();
7741
7742        // DuckDB / Spark leading-dot float literal: `.5`, `.25`. The
7743        // tokenizer emits `Dot` then `Number`; glue them back together.
7744        if matches!(token.token_type, TokenType::Dot)
7745            && matches!(
7746                self.peek_offset(1).map(|t| &t.token_type),
7747                Some(TokenType::Number)
7748            )
7749        {
7750            self.advance();
7751            let n = self.peek().value.clone();
7752            self.advance();
7753            return Ok(Expr::Number(format!("0.{}", n)));
7754        }
7755
7756        match &token.token_type {
7757            TokenType::Number => {
7758                self.advance();
7759                // Trailing-dot fractional literal: `10.` — accept the dot as
7760                // part of the number when it isn't followed by something that
7761                // would be a member access (column reference like `t.col` or
7762                // tuple element access).
7763                let mut value = token.value;
7764                if self.peek_type() == &TokenType::Dot {
7765                    let after = self.peek_offset(1).map(|t| &t.token_type);
7766                    let looks_like_member = matches!(
7767                        after,
7768                        Some(TokenType::Identifier)
7769                            | Some(TokenType::Number)
7770                            | Some(TokenType::Star)
7771                    );
7772                    if !looks_like_member {
7773                        self.advance();
7774                        value.push('.');
7775                    }
7776                }
7777                // Spark / Hive float suffixes: `10.0F`, `20L`, `3.14D`, `5BD`.
7778                // Swallow the suffix identifier so the literal parses.
7779                if self.is_name_token() {
7780                    let v = self.peek().value.as_str();
7781                    if matches!(v, "F" | "f" | "L" | "l" | "D" | "d" | "BD" | "bd") {
7782                        self.advance();
7783                    }
7784                }
7785                Ok(Expr::Number(value))
7786            }
7787            TokenType::HexString => {
7788                self.advance();
7789                Ok(Expr::Number(token.value))
7790            }
7791            TokenType::String => {
7792                self.advance();
7793                // ANSI / Oracle interval literal: `'1-2' YEAR TO MONTH`,
7794                // `'12 03:04:05.6' DAY TO SECOND(2)`. After a bare string,
7795                // accept an optional interval qualifier and swallow it so
7796                // the surrounding expression parses. Skip this when the
7797                // previous token was `INTERVAL` — that has its own path.
7798                let prev_was_interval = self
7799                    .pos
7800                    .checked_sub(2)
7801                    .and_then(|i| self.tokens.get(i))
7802                    .map(|t| matches!(t.token_type, TokenType::Interval))
7803                    .unwrap_or(false);
7804                if !prev_was_interval
7805                    && matches!(
7806                        self.peek_type(),
7807                        TokenType::Year
7808                            | TokenType::Month
7809                            | TokenType::Day
7810                            | TokenType::Hour
7811                            | TokenType::Minute
7812                            | TokenType::Second
7813                    )
7814                {
7815                    self.advance();
7816                    if self.match_token(TokenType::LParen) {
7817                        // qualifier precision: `SECOND(2)`
7818                        if matches!(self.peek_type(), TokenType::Number) {
7819                            self.advance();
7820                            if self.match_token(TokenType::Comma) {
7821                                if matches!(self.peek_type(), TokenType::Number) {
7822                                    self.advance();
7823                                }
7824                            }
7825                        }
7826                        let _ = self.match_token(TokenType::RParen);
7827                    }
7828                    if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("TO") {
7829                        self.advance();
7830                        if matches!(
7831                            self.peek_type(),
7832                            TokenType::Year
7833                                | TokenType::Month
7834                                | TokenType::Day
7835                                | TokenType::Hour
7836                                | TokenType::Minute
7837                                | TokenType::Second
7838                        ) {
7839                            self.advance();
7840                            if self.match_token(TokenType::LParen) {
7841                                if matches!(self.peek_type(), TokenType::Number) {
7842                                    self.advance();
7843                                }
7844                                let _ = self.match_token(TokenType::RParen);
7845                            }
7846                        }
7847                    }
7848                    return Ok(Expr::Cast {
7849                        expr: Box::new(Expr::StringLiteral(token.value)),
7850                        data_type: DataType::Interval,
7851                    });
7852                }
7853                // SQL-92 / MySQL: adjacent string literals concatenate
7854                // (`'a' 'b'` → `'ab'`). Also fold in identifier-quoted
7855                // strings the lexer surfaces when MySQL ANSI_QUOTES is off
7856                // (`"a" "b" "c"` reaches us as a String followed by quoted
7857                // identifiers). Greedily consume any run of immediately
7858                // following String / quoted-Identifier tokens.
7859                let mut combined = token.value;
7860                loop {
7861                    let next = self.peek();
7862                    if matches!(next.token_type, TokenType::String) {
7863                        combined.push_str(&next.value);
7864                        self.advance();
7865                        continue;
7866                    }
7867                    if matches!(next.token_type, TokenType::Identifier)
7868                        && (next.quote_char == '"' || next.quote_char == '\'')
7869                    {
7870                        combined.push_str(&next.value);
7871                        self.advance();
7872                        continue;
7873                    }
7874                    break;
7875                }
7876                Ok(Expr::StringLiteral(combined))
7877            }
7878            TokenType::NationalString => {
7879                self.advance();
7880                Ok(Expr::NationalStringLiteral(token.value))
7881            }
7882            TokenType::True => {
7883                self.advance();
7884                Ok(Expr::Boolean(true))
7885            }
7886            TokenType::False => {
7887                self.advance();
7888                Ok(Expr::Boolean(false))
7889            }
7890            TokenType::Null => {
7891                self.advance();
7892                Ok(Expr::Null)
7893            }
7894            TokenType::Default => {
7895                self.advance();
7896                // MySQL `DEFAULT(col)` — emit as function call so the
7897                // surrounding tuple parses.
7898                if self.peek_type() == &TokenType::LParen {
7899                    self.advance();
7900                    let args = if self.peek_type() != &TokenType::RParen {
7901                        let mut a = vec![self.parse_function_arg()?];
7902                        while self.match_token(TokenType::Comma) {
7903                            a.push(self.parse_function_arg()?);
7904                        }
7905                        a
7906                    } else {
7907                        vec![]
7908                    };
7909                    self.expect(TokenType::RParen)?;
7910                    return Ok(Expr::Function {
7911                        name: "DEFAULT".to_string(),
7912                        args,
7913                        distinct: false,
7914                        filter: None,
7915                        over: None,
7916                        order_by: Vec::new(),
7917                        within_group: false,
7918                    });
7919                }
7920                Ok(Expr::Default)
7921            }
7922            TokenType::Star => {
7923                self.advance();
7924                Ok(Expr::Wildcard)
7925            }
7926            // ClickHouse / various: `values` used as a column name inside
7927            // expressions (e.g. `arrayExists(x -> x > 5, values)`). Accept
7928            // it as a bare column reference when it isn't followed by `(`.
7929            TokenType::Values
7930                if self.peek_offset(1).map(|t| &t.token_type) != Some(&TokenType::LParen) =>
7931            {
7932                self.advance();
7933                Ok(Expr::Column {
7934                    table: None,
7935                    name: token.value,
7936                    quote_style: QuoteStyle::None,
7937                    table_quote_style: QuoteStyle::None,
7938                })
7939            }
7940            TokenType::Parameter => {
7941                self.advance();
7942                Ok(Expr::Parameter(token.value))
7943            }
7944
7945            // ── `@var`, `@@global_var`, `:var` style placeholders ──
7946            //
7947            // MySQL/T-SQL session and global variables tokenize as a bare
7948            // `@` (or `:`) followed by an identifier. We glue the prefix and
7949            // following name into a single `Parameter` expression so the
7950            // surrounding query parses.
7951            TokenType::AtSign | TokenType::Colon => {
7952                self.advance();
7953                let mut name = match token.token_type {
7954                    TokenType::AtSign => String::from("@"),
7955                    TokenType::Colon => String::from(":"),
7956                    _ => unreachable!(),
7957                };
7958                // T-SQL `@@global` — second `@`.
7959                if matches!(token.token_type, TokenType::AtSign)
7960                    && self.peek_type() == &TokenType::AtSign
7961                {
7962                    name.push('@');
7963                    self.advance();
7964                }
7965                // Name part: identifier-or-keyword, number, or none.
7966                // T-SQL accepts reserved keywords after `@` (e.g. `@limit`,
7967                // `@order`). Accept any token that "looks like" a name.
7968                if self.is_name_token()
7969                    || matches!(
7970                        self.peek_type(),
7971                        TokenType::Limit
7972                            | TokenType::Offset
7973                            | TokenType::Order
7974                            | TokenType::Group
7975                            | TokenType::Having
7976                            | TokenType::Where
7977                            | TokenType::From
7978                            | TokenType::Select
7979                            | TokenType::Insert
7980                            | TokenType::Update
7981                            | TokenType::Delete
7982                            | TokenType::Union
7983                            | TokenType::Intersect
7984                            | TokenType::Except
7985                            | TokenType::Join
7986                            | TokenType::Inner
7987                            | TokenType::Cross
7988                            | TokenType::On
7989                            | TokenType::As
7990                            | TokenType::Distinct
7991                            | TokenType::Default
7992                            | TokenType::Null
7993                            | TokenType::True
7994                            | TokenType::False
7995                            | TokenType::Date
7996                            | TokenType::Time
7997                            | TokenType::Timestamp
7998                            | TokenType::Year
7999                            | TokenType::Month
8000                            | TokenType::Day
8001                            | TokenType::Hour
8002                            | TokenType::Minute
8003                            | TokenType::Second
8004                    )
8005                {
8006                    let nt = self.advance().clone();
8007                    name.push_str(&nt.value);
8008                } else if matches!(self.peek_type(), TokenType::Number | TokenType::Int) {
8009                    let nt = self.advance().clone();
8010                    name.push_str(&nt.value);
8011                }
8012                Ok(Expr::Parameter(name))
8013            }
8014
8015            // ── DuckDB / BigQuery struct literal: `{ key: expr, ... }` ──
8016            //
8017            // We capture the values as positional `STRUCT(...)` arguments
8018            // (keys are syntactically optional). This keeps surrounding
8019            // expressions parseable; the original AST shape is not preserved
8020            // because there is no dedicated struct-literal variant yet.
8021            TokenType::LBrace => {
8022                self.advance();
8023                let mut args = Vec::new();
8024                if self.peek_type() != &TokenType::RBrace {
8025                    loop {
8026                        // Optional `key:` prefix — discard the key, keep value.
8027                        if self.is_name_token()
8028                            && self
8029                                .peek_offset(1)
8030                                .is_some_and(|t| t.token_type == TokenType::Colon)
8031                        {
8032                            self.advance(); // key
8033                            self.advance(); // colon
8034                        } else if self.peek_type() == &TokenType::String
8035                            && self
8036                                .peek_offset(1)
8037                                .is_some_and(|t| t.token_type == TokenType::Colon)
8038                        {
8039                            self.advance(); // string key
8040                            self.advance(); // colon
8041                        }
8042                        let value = self.parse_expr()?;
8043                        args.push(value);
8044                        if !self.match_token(TokenType::Comma) {
8045                            break;
8046                        }
8047                    }
8048                }
8049                self.expect(TokenType::RBrace)?;
8050                Ok(Expr::Function {
8051                    name: "STRUCT".to_string(),
8052                    args,
8053                    distinct: false,
8054                    filter: None,
8055                    over: None,
8056                    order_by: Vec::new(),
8057                    within_group: false,
8058                })
8059            }
8060
8061            // ── CAST ────────────────────────────────────────────────
8062            TokenType::Cast
8063                if self
8064                    .peek_offset(1)
8065                    .is_some_and(|t| t.token_type == TokenType::LParen) =>
8066            {
8067                self.advance();
8068                self.expect(TokenType::LParen)?;
8069                let expr = self.parse_expr()?;
8070                // Standard form: `CAST(expr AS type)`. ClickHouse also accepts
8071                // `CAST(expr, 'TypeName')` with a string literal type.
8072                let data_type = if self.match_token(TokenType::As) {
8073                    self.parse_data_type()?
8074                } else if self.match_token(TokenType::Comma) {
8075                    if matches!(self.peek_type(), TokenType::String) {
8076                        let s = self.peek().value.clone();
8077                        self.advance();
8078                        DataType::Unknown(s)
8079                    } else {
8080                        self.parse_data_type()?
8081                    }
8082                } else {
8083                    self.expect(TokenType::As)?; // produce the canonical error
8084                    self.parse_data_type()?
8085                };
8086                // BigQuery: `CAST(expr AS type FORMAT 'fmt' [AT TIME ZONE …])`.
8087                if self.check_keyword("FORMAT") {
8088                    self.advance();
8089                    let _ = self.parse_expr();
8090                    if self.check_keyword("AT")
8091                        && self
8092                            .peek_offset(1)
8093                            .map(|t| t.value.eq_ignore_ascii_case("TIME"))
8094                            .unwrap_or(false)
8095                        && self
8096                            .peek_offset(2)
8097                            .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8098                            .unwrap_or(false)
8099                    {
8100                        self.advance();
8101                        self.advance();
8102                        self.advance();
8103                        let _ = self.parse_expr();
8104                    }
8105                }
8106                self.expect(TokenType::RParen)?;
8107                Ok(Expr::Cast {
8108                    expr: Box::new(expr),
8109                    data_type,
8110                })
8111            }
8112
8113            // ── EXTRACT ─────────────────────────────────────────────
8114            TokenType::Extract => {
8115                self.advance();
8116                self.expect(TokenType::LParen)?;
8117                let field = self.parse_datetime_field()?;
8118                self.expect(TokenType::From)?;
8119                let expr = self.parse_expr()?;
8120                // BigQuery: `EXTRACT(field FROM ts AT TIME ZONE 'tz')`.
8121                // Swallow the trailing timezone clause so the function
8122                // parses; we lose the explicit zone but keep the AST.
8123                if self.check_keyword("AT")
8124                    && self
8125                        .peek_offset(1)
8126                        .map(|t| t.value.eq_ignore_ascii_case("TIME"))
8127                        .unwrap_or(false)
8128                    && self
8129                        .peek_offset(2)
8130                        .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8131                        .unwrap_or(false)
8132                {
8133                    self.advance(); // AT
8134                    self.advance(); // TIME
8135                    self.advance(); // ZONE
8136                    let _ = self.parse_expr();
8137                }
8138                self.expect(TokenType::RParen)?;
8139                Ok(Expr::Extract {
8140                    field,
8141                    expr: Box::new(expr),
8142                })
8143            }
8144
8145            // ── CASE ────────────────────────────────────────────────
8146            TokenType::Case => self.parse_case_expr(),
8147
8148            // ── EXISTS ──────────────────────────────────────────────
8149            TokenType::Exists => {
8150                self.advance();
8151                self.expect(TokenType::LParen)?;
8152                let subquery = self.parse_statement_inner()?;
8153                self.expect(TokenType::RParen)?;
8154                Ok(Expr::Exists {
8155                    subquery: Box::new(subquery),
8156                    negated: false,
8157                })
8158            }
8159
8160            // ── NOT EXISTS ──────────────────────────────────────────
8161            TokenType::Not
8162                if {
8163                    let next_pos = self.pos + 1;
8164                    next_pos < self.tokens.len()
8165                        && self.tokens[next_pos].token_type == TokenType::Exists
8166                } =>
8167            {
8168                self.advance(); // NOT
8169                self.advance(); // EXISTS
8170                self.expect(TokenType::LParen)?;
8171                let subquery = self.parse_statement_inner()?;
8172                self.expect(TokenType::RParen)?;
8173                Ok(Expr::Exists {
8174                    subquery: Box::new(subquery),
8175                    negated: true,
8176                })
8177            }
8178
8179            // ── INTERVAL ────────────────────────────────────────────
8180            TokenType::Interval => {
8181                self.advance();
8182                // ClickHouse accepts arithmetic in the value position
8183                // (e.g. `INTERVAL number - 15 MONTH`). Parse an additive
8184                // expression instead of a single primary so the trailing
8185                // unit keyword is reached cleanly.
8186                let value = self.parse_addition()?;
8187                let unit = self.try_parse_datetime_field();
8188                // ANSI / Spark composite ranges: `INTERVAL '0-0' YEAR TO MONTH`,
8189                // `INTERVAL '15:40' HOUR TO MINUTE` etc. Swallow the trailing
8190                // `TO <unit>` clause; we keep only the leading unit.
8191                if self.check_keyword("TO") {
8192                    let saved = self.pos;
8193                    self.advance();
8194                    if self.try_parse_datetime_field().is_none() {
8195                        self.pos = saved;
8196                    }
8197                }
8198                // PostgreSQL fractional precision on the trailing unit:
8199                //   `INTERVAL '1.234' SECOND(2)`, `INTERVAL '…' MINUTE TO SECOND(2)`.
8200                // Swallow the `(N)` after the unit.
8201                if self.peek_type() == &TokenType::LParen
8202                    && self
8203                        .peek_offset(1)
8204                        .map(|t| matches!(t.token_type, TokenType::Number))
8205                        .unwrap_or(false)
8206                    && self
8207                        .peek_offset(2)
8208                        .map(|t| matches!(t.token_type, TokenType::RParen))
8209                        .unwrap_or(false)
8210                {
8211                    self.advance();
8212                    self.advance();
8213                    self.advance();
8214                }
8215                Ok(Expr::Interval {
8216                    value: Box::new(value),
8217                    unit,
8218                })
8219            }
8220
8221            // ── Parenthesized expression or subquery ────────────────
8222            TokenType::LParen => {
8223                self.advance();
8224                // Check for subquery
8225                if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
8226                    let subquery = self.parse_statement_inner()?;
8227                    self.expect(TokenType::RParen)?;
8228                    Ok(Expr::Subquery(Box::new(subquery)))
8229                } else {
8230                    let expr = self.parse_expr()?;
8231                    // ClickHouse: `(expr AS alias)` — swallow the alias.
8232                    if self.match_token(TokenType::As) && self.is_name_token() {
8233                        self.advance();
8234                    }
8235                    // Tuple: (a, b, c) — also accept ClickHouse trailing
8236                    // comma `(a,)`, `(a, b,)`.
8237                    if self.match_token(TokenType::Comma) {
8238                        let mut items = vec![expr];
8239                        if self.peek_type() == &TokenType::RParen {
8240                            self.advance();
8241                            return Ok(Expr::Tuple(items));
8242                        }
8243                        let next = self.parse_expr()?;
8244                        if self.match_token(TokenType::As) && self.is_name_token() {
8245                            self.advance();
8246                        }
8247                        items.push(next);
8248                        while self.match_token(TokenType::Comma) {
8249                            if self.peek_type() == &TokenType::RParen {
8250                                break;
8251                            }
8252                            let n = self.parse_expr()?;
8253                            if self.match_token(TokenType::As) && self.is_name_token() {
8254                                self.advance();
8255                            }
8256                            items.push(n);
8257                        }
8258                        self.expect(TokenType::RParen)?;
8259                        Ok(Expr::Tuple(items))
8260                    } else {
8261                        self.expect(TokenType::RParen)?;
8262                        Ok(Expr::Nested(Box::new(expr)))
8263                    }
8264                }
8265            }
8266
8267            // ── DuckDB MAP literal: `MAP { 'k': v, ... }` ──────────
8268            // Captured as a `MAP(...)` function call with the values as
8269            // positional arguments; keys are discarded for now.
8270            TokenType::Map
8271                if self
8272                    .peek_offset(1)
8273                    .map(|t| matches!(t.token_type, TokenType::LBrace))
8274                    .unwrap_or(false) =>
8275            {
8276                self.advance(); // MAP
8277                self.advance(); // {
8278                let mut args = Vec::new();
8279                if self.peek_type() != &TokenType::RBrace {
8280                    loop {
8281                        // Optional `key:` prefix — keep the value only.
8282                        let saved = self.pos;
8283                        let _ = self.parse_expr()?;
8284                        if self.match_token(TokenType::Colon) {
8285                            let v = self.parse_expr()?;
8286                            args.push(v);
8287                        } else {
8288                            self.pos = saved;
8289                            let v = self.parse_expr()?;
8290                            args.push(v);
8291                        }
8292                        if !self.match_token(TokenType::Comma) {
8293                            break;
8294                        }
8295                    }
8296                }
8297                self.expect(TokenType::RBrace)?;
8298                Ok(Expr::Function {
8299                    name: "MAP".to_string(),
8300                    args,
8301                    distinct: false,
8302                    filter: None,
8303                    over: None,
8304                    order_by: Vec::new(),
8305                    within_group: false,
8306                })
8307            }
8308
8309            // ── Array literal: ARRAY[...] ──────────────────────────
8310            TokenType::Array => {
8311                self.advance();
8312                if self.match_token(TokenType::LBracket) {
8313                    let items = self.parse_array_items(TokenType::RBracket)?;
8314                    self.expect(TokenType::RBracket)?;
8315                    Ok(Expr::ArrayLiteral(items))
8316                } else if self.match_token(TokenType::LParen) {
8317                    // ARRAY(SELECT ...) for subqueries, or Hive
8318                    // `ARRAY(expr, expr, ...)` for inline array literals.
8319                    if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
8320                        let subquery = self.parse_statement_inner()?;
8321                        self.expect(TokenType::RParen)?;
8322                        Ok(Expr::Subquery(Box::new(subquery)))
8323                    } else {
8324                        let items = self.parse_array_items(TokenType::RParen)?;
8325                        self.expect(TokenType::RParen)?;
8326                        Ok(Expr::ArrayLiteral(items))
8327                    }
8328                } else {
8329                    Ok(Expr::Column {
8330                        table: None,
8331                        name: "ARRAY".to_string(),
8332                        quote_style: QuoteStyle::None,
8333                        table_quote_style: QuoteStyle::None,
8334                    })
8335                }
8336            }
8337
8338            // ── Bracket array literal: [...] ────────────────────────
8339            TokenType::LBracket => {
8340                self.advance();
8341                let items = self.parse_array_items(TokenType::RBracket)?;
8342                // DuckDB list comprehension: `[expr FOR x IN list [IF cond]]`.
8343                // Swallow the comprehension tail opaquely; we keep the
8344                // initial expression as the AST representation.
8345                if self.peek().value.eq_ignore_ascii_case("FOR") {
8346                    let mut depth = 1_i32;
8347                    while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
8348                        match self.peek_type() {
8349                            TokenType::LBracket | TokenType::LParen => depth += 1,
8350                            TokenType::RBracket => {
8351                                depth -= 1;
8352                                if depth == 0 {
8353                                    break;
8354                                }
8355                            }
8356                            TokenType::RParen => depth -= 1,
8357                            _ => {}
8358                        }
8359                        self.advance();
8360                    }
8361                }
8362                self.expect(TokenType::RBracket)?;
8363                Ok(Expr::ArrayLiteral(items))
8364            }
8365
8366            // ── Identifier: column ref, function call, or qualified name ─
8367            _ if self.is_name_token() || self.is_data_type_token() => {
8368                let name_token = self.advance().clone();
8369                let name = name_token.value.clone();
8370                let name_qs = quote_style_from_char(name_token.quote_char);
8371
8372                // ── ANSI typed string literals: DATE 'x', TIMESTAMP 'x', TIME 'x' ──
8373                if matches!(
8374                    name_token.token_type,
8375                    TokenType::Date
8376                        | TokenType::Timestamp
8377                        | TokenType::TimestampTz
8378                        | TokenType::Time
8379                ) {
8380                    // PG / ANSI `TIMESTAMP [WITH [LOCAL] TIME ZONE] 'lit'`
8381                    // and `TIMESTAMP WITHOUT TIME ZONE 'lit'`. Swallow the
8382                    // optional timezone modifier so the string literal
8383                    // attaches to the right typed-literal form.
8384                    let mut explicit_tz: Option<bool> = None;
8385                    if matches!(
8386                        name_token.token_type,
8387                        TokenType::Timestamp | TokenType::Time
8388                    ) && self.peek_type() == &TokenType::With
8389                    {
8390                        let saved = self.pos;
8391                        self.advance(); // WITH
8392                        let _ = self.match_keyword("LOCAL");
8393                        if self.check_keyword("TIME")
8394                            && self
8395                                .peek_offset(1)
8396                                .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8397                                .unwrap_or(false)
8398                        {
8399                            self.advance(); // TIME
8400                            self.advance(); // ZONE
8401                            explicit_tz = Some(true);
8402                        } else {
8403                            self.pos = saved;
8404                        }
8405                    } else if matches!(
8406                        name_token.token_type,
8407                        TokenType::Timestamp | TokenType::Time
8408                    ) && self.check_keyword("WITHOUT")
8409                    {
8410                        let saved = self.pos;
8411                        self.advance(); // WITHOUT
8412                        if self.check_keyword("TIME")
8413                            && self
8414                                .peek_offset(1)
8415                                .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8416                                .unwrap_or(false)
8417                        {
8418                            self.advance();
8419                            self.advance();
8420                            explicit_tz = Some(false);
8421                        } else {
8422                            self.pos = saved;
8423                        }
8424                    }
8425
8426                    if self.peek_type() == &TokenType::String {
8427                        let value_token = self.advance().clone();
8428                        let data_type = match name_token.token_type {
8429                            TokenType::Date => DataType::Date,
8430                            TokenType::Timestamp => DataType::Timestamp {
8431                                precision: None,
8432                                with_tz: explicit_tz.unwrap_or(false),
8433                            },
8434                            TokenType::TimestampTz => DataType::Timestamp {
8435                                precision: None,
8436                                with_tz: true,
8437                            },
8438                            TokenType::Time => DataType::Time { precision: None },
8439                            _ => unreachable!(),
8440                        };
8441                        return Ok(Expr::Cast {
8442                            expr: Box::new(Expr::StringLiteral(value_token.value)),
8443                            data_type,
8444                        });
8445                    }
8446                }
8447
8448                // ── ANSI / PG generic typed string literal: `TYPE 'lit'` ──
8449                // (e.g. `bool 'true'`, `int4 '42'`, `varchar 'x'`). When the
8450                // current token is a data-type keyword (not already handled
8451                // above) and a String literal follows, fold the pair into a
8452                // Cast so the surrounding expression parses.
8453                if self.is_data_type_token_kind(&name_token.token_type)
8454                    && self.peek_type() == &TokenType::String
8455                {
8456                    let value_token = self.advance().clone();
8457                    let data_type = match name_token.token_type {
8458                        TokenType::Boolean => DataType::Boolean,
8459                        TokenType::Int | TokenType::Integer => DataType::Int,
8460                        TokenType::BigInt => DataType::BigInt,
8461                        TokenType::SmallInt => DataType::SmallInt,
8462                        TokenType::TinyInt => DataType::TinyInt,
8463                        TokenType::Float => DataType::Float,
8464                        TokenType::Double => DataType::Double,
8465                        TokenType::Real => DataType::Real,
8466                        TokenType::Decimal => DataType::Decimal {
8467                            precision: None,
8468                            scale: None,
8469                        },
8470                        TokenType::Numeric => DataType::Numeric {
8471                            precision: None,
8472                            scale: None,
8473                        },
8474                        TokenType::Varchar => DataType::Varchar(None),
8475                        TokenType::Char => DataType::Char(None),
8476                        TokenType::Text => DataType::Text,
8477                        TokenType::Json => DataType::Json,
8478                        TokenType::Jsonb => DataType::Jsonb,
8479                        TokenType::Uuid => DataType::Uuid,
8480                        TokenType::Bytea => DataType::Bytea,
8481                        TokenType::Blob => DataType::Blob,
8482                        _ => DataType::Unknown(name.clone()),
8483                    };
8484                    return Ok(Expr::Cast {
8485                        expr: Box::new(Expr::StringLiteral(value_token.value)),
8486                        data_type,
8487                    });
8488                }
8489
8490                // PostgreSQL geometric / network / OID type aliases used as
8491                // typed-literal prefixes (e.g. `box '(1,2,3,4)'`,
8492                // `point '(1,2)'`, `inet '127.0.0.1'`). Recognize a curated
8493                // list of bare identifiers followed by a String literal and
8494                // fold the pair into a Cast(Unknown(name)).
8495                if name_qs == QuoteStyle::None
8496                    && self.peek_type() == &TokenType::String
8497                    && matches!(
8498                        name.to_ascii_lowercase().as_str(),
8499                        "box"
8500                            | "point"
8501                            | "circle"
8502                            | "line"
8503                            | "lseg"
8504                            | "path"
8505                            | "polygon"
8506                            | "inet"
8507                            | "cidr"
8508                            | "macaddr"
8509                            | "macaddr8"
8510                            | "money"
8511                            | "regclass"
8512                            | "regtype"
8513                            | "regproc"
8514                            | "regprocedure"
8515                            | "regrole"
8516                            | "regnamespace"
8517                            | "regoperator"
8518                            | "regoper"
8519                            | "oid"
8520                            | "xml"
8521                            | "tsvector"
8522                            | "tsquery"
8523                            | "jsonpath"
8524                            | "name"
8525                            | "bit"
8526                            | "varbit"
8527                            | "interval"
8528                            | "bool"
8529                            | "int2"
8530                            | "int4"
8531                            | "int8"
8532                            | "float4"
8533                            | "float8"
8534                    )
8535                {
8536                    let value_token = self.advance().clone();
8537                    return Ok(Expr::Cast {
8538                        expr: Box::new(Expr::StringLiteral(value_token.value)),
8539                        data_type: DataType::Unknown(name.clone()),
8540                    });
8541                }
8542
8543                // ── Bare niladic temporal keywords: CURRENT_TIME, CURRENT_DATE,
8544                //    CURRENT_TIMESTAMP, LOCALTIMESTAMP (no parens) ──
8545                // ANSI SQL allows these without parentheses. Materialize them
8546                // as typed functions so the generator can emit dialect-specific
8547                // forms (e.g. TSQL requires CAST(GETDATE() AS TIME) rather than
8548                // a bare CURRENT_TIME reserved word).
8549                if name_qs == QuoteStyle::None && self.peek_type() != &TokenType::LParen {
8550                    let upper = name.to_ascii_uppercase();
8551                    let typed = match upper.as_str() {
8552                        "CURRENT_DATE" => Some(TypedFunction::CurrentDate),
8553                        "CURRENT_TIME" => Some(TypedFunction::CurrentTime),
8554                        "CURRENT_TIMESTAMP" | "LOCALTIMESTAMP" => {
8555                            Some(TypedFunction::CurrentTimestamp)
8556                        }
8557                        _ => None,
8558                    };
8559                    if let Some(tf) = typed {
8560                        return Ok(Expr::TypedFunction {
8561                            func: tf,
8562                            filter: None,
8563                            over: None,
8564                        });
8565                    }
8566                }
8567
8568                // Function call: name(...)
8569                if self.peek_type() == &TokenType::LParen {
8570                    self.advance();
8571
8572                    // TRY_CAST / SAFE_CAST / TRY_TO_TIMESTAMP / … — same shape
8573                    // as `CAST(expr AS type)`. Lower to `Expr::Cast` when the
8574                    // body matches; fall back to ordinary function call when
8575                    // it does not (e.g. comma-separated args).
8576                    if matches!(name.to_ascii_uppercase().as_str(), "TRY_CAST" | "SAFE_CAST") {
8577                        let save = self.pos;
8578                        let inner = self.parse_expr()?;
8579                        if self.match_token(TokenType::As) {
8580                            let dt = self.parse_data_type()?;
8581                            self.expect(TokenType::RParen)?;
8582                            return Ok(Expr::Cast {
8583                                expr: Box::new(inner),
8584                                data_type: dt,
8585                            });
8586                        }
8587                        self.pos = save;
8588                    }
8589
8590                    // Special: COUNT(*), COUNT(DISTINCT x)
8591                    let distinct = self.match_token(TokenType::Distinct);
8592                    // ANSI / ClickHouse `agg(ALL …)` — `ALL` is the opposite
8593                    // of DISTINCT and the default. Swallow so the args parse.
8594                    if !distinct {
8595                        let _ = self.match_token(TokenType::All);
8596                    }
8597
8598                    // Standard SQL syntactic forms for string functions:
8599                    //   SUBSTRING(expr FROM start [FOR len])
8600                    //   SUBSTRING(expr FOR len)
8601                    //   TRIM([LEADING|TRAILING|BOTH] [chars] FROM expr)
8602                    //   POSITION(needle IN haystack)
8603                    //   OVERLAY(expr PLACING str FROM start [FOR len])
8604                    let upper_name = name.to_ascii_uppercase();
8605                    if !distinct && self.peek_type() != &TokenType::RParen {
8606                        match upper_name.as_str() {
8607                            "SUBSTRING" | "SUBSTR" => {
8608                                let saved = self.pos;
8609                                let first = self.parse_expr()?;
8610                                if self.match_token(TokenType::From) {
8611                                    let start = self.parse_expr()?;
8612                                    let length = if self.check_keyword("FOR") {
8613                                        self.advance();
8614                                        Some(self.parse_expr()?)
8615                                    } else {
8616                                        None
8617                                    };
8618                                    self.expect(TokenType::RParen)?;
8619                                    let mut a = vec![first, start];
8620                                    if let Some(l) = length {
8621                                        a.push(l);
8622                                    }
8623                                    return Ok(Expr::Function {
8624                                        name: name.clone(),
8625                                        args: a,
8626                                        distinct: false,
8627                                        filter: None,
8628                                        over: None,
8629                                        order_by: Vec::new(),
8630                                        within_group: false,
8631                                    });
8632                                } else if self.check_keyword("FOR") {
8633                                    self.advance();
8634                                    let len = self.parse_expr()?;
8635                                    self.expect(TokenType::RParen)?;
8636                                    return Ok(Expr::Function {
8637                                        name: name.clone(),
8638                                        args: vec![first, len],
8639                                        distinct: false,
8640                                        filter: None,
8641                                        over: None,
8642                                        order_by: Vec::new(),
8643                                        within_group: false,
8644                                    });
8645                                }
8646                                self.pos = saved;
8647                            }
8648                            "TRIM" => {
8649                                let saved = self.pos;
8650                                if self.check_keyword("LEADING")
8651                                    || self.check_keyword("TRAILING")
8652                                    || self.check_keyword("BOTH")
8653                                {
8654                                    self.advance();
8655                                }
8656                                if self.peek_type() == &TokenType::From {
8657                                    self.advance();
8658                                    let expr = self.parse_expr()?;
8659                                    self.expect(TokenType::RParen)?;
8660                                    return Ok(Expr::Function {
8661                                        name: name.clone(),
8662                                        args: vec![expr],
8663                                        distinct: false,
8664                                        filter: None,
8665                                        over: None,
8666                                        order_by: Vec::new(),
8667                                        within_group: false,
8668                                    });
8669                                }
8670                                let chars = self.parse_expr()?;
8671                                if self.match_token(TokenType::From) {
8672                                    let expr = self.parse_expr()?;
8673                                    self.expect(TokenType::RParen)?;
8674                                    return Ok(Expr::Function {
8675                                        name: name.clone(),
8676                                        args: vec![expr, chars],
8677                                        distinct: false,
8678                                        filter: None,
8679                                        over: None,
8680                                        order_by: Vec::new(),
8681                                        within_group: false,
8682                                    });
8683                                }
8684                                self.pos = saved;
8685                            }
8686                            "POSITION" => {
8687                                let saved = self.pos;
8688                                let needle = self.parse_expr()?;
8689                                if self.match_token(TokenType::In) {
8690                                    let haystack = self.parse_expr()?;
8691                                    self.expect(TokenType::RParen)?;
8692                                    return Ok(Expr::Function {
8693                                        name: name.clone(),
8694                                        args: vec![needle, haystack],
8695                                        distinct: false,
8696                                        filter: None,
8697                                        over: None,
8698                                        order_by: Vec::new(),
8699                                        within_group: false,
8700                                    });
8701                                }
8702                                self.pos = saved;
8703                            }
8704                            "OVERLAY" => {
8705                                let saved = self.pos;
8706                                let target = self.parse_expr()?;
8707                                if self.check_keyword("PLACING") {
8708                                    self.advance();
8709                                    let placing = self.parse_expr()?;
8710                                    if self.match_token(TokenType::From) {
8711                                        let from = self.parse_expr()?;
8712                                        let len = if self.check_keyword("FOR") {
8713                                            self.advance();
8714                                            Some(self.parse_expr()?)
8715                                        } else {
8716                                            None
8717                                        };
8718                                        self.expect(TokenType::RParen)?;
8719                                        let mut a = vec![target, placing, from];
8720                                        if let Some(l) = len {
8721                                            a.push(l);
8722                                        }
8723                                        return Ok(Expr::Function {
8724                                            name: name.clone(),
8725                                            args: a,
8726                                            distinct: false,
8727                                            filter: None,
8728                                            over: None,
8729                                            order_by: Vec::new(),
8730                                            within_group: false,
8731                                        });
8732                                    }
8733                                }
8734                                self.pos = saved;
8735                            }
8736                            _ => {}
8737                        }
8738                    }
8739
8740                    // MySQL's GROUP_CONCAT has bespoke grammar
8741                    // (ORDER BY ..., SEPARATOR ...) — parse it into a typed
8742                    // expression so the structure is preserved across dialects.
8743                    if name.eq_ignore_ascii_case("GROUP_CONCAT") {
8744                        let expr = self.parse_group_concat_call(distinct)?;
8745                        self.expect(TokenType::RParen)?;
8746                        return Ok(expr);
8747                    }
8748
8749                    let args = if self.peek_type() == &TokenType::RParen {
8750                        vec![]
8751                    } else if self.peek_type() == &TokenType::Star {
8752                        self.advance();
8753                        vec![Expr::Wildcard]
8754                    } else {
8755                        let mut a = vec![self.parse_function_arg()?];
8756                        while self.match_token(TokenType::Comma) {
8757                            a.push(self.parse_function_arg()?);
8758                        }
8759                        a
8760                    };
8761
8762                    // Optional aggregate ORDER BY inside arg list (Postgres / Spark):
8763                    //   array_agg(x ORDER BY y DESC)
8764                    //   string_agg(x, ',' ORDER BY y)
8765                    let mut agg_order_by: Vec<OrderByItem> = vec![];
8766                    if self.peek_type() == &TokenType::Order {
8767                        self.advance();
8768                        self.expect(TokenType::By)?;
8769                        agg_order_by = self.parse_order_by_items()?;
8770                    }
8771                    // BigQuery / Snowflake: `ARRAY_AGG(x [ORDER BY y] LIMIT n)`.
8772                    // Swallow the trailing LIMIT clause inside the function call.
8773                    if self.peek_type() == &TokenType::Limit {
8774                        self.advance();
8775                        let _ = self.parse_expr();
8776                    }
8777                    // DuckDB aggregate-state modifier:
8778                    //   `count(1) EXPORT_STATE` returns the aggregate state
8779                    //   rather than its final value. We don't model it.
8780                    if self.check_keyword("EXPORT_STATE") {
8781                        self.advance();
8782                    }
8783                    self.expect(TokenType::RParen)?;
8784
8785                    // Optional WITHIN GROUP (ORDER BY ...) — ordered-set aggregates
8786                    //   percentile_cont(0.5) WITHIN GROUP (ORDER BY x)
8787                    //   listagg(x, ',') WITHIN GROUP (ORDER BY x)
8788                    let mut within_group = false;
8789                    let mut wg_order_by: Vec<OrderByItem> = vec![];
8790                    if self.check_keyword("WITHIN") {
8791                        self.advance();
8792                        self.expect_keyword("GROUP")?;
8793                        self.expect(TokenType::LParen)?;
8794                        self.expect(TokenType::Order)?;
8795                        self.expect(TokenType::By)?;
8796                        wg_order_by = self.parse_order_by_items()?;
8797                        self.expect(TokenType::RParen)?;
8798                        within_group = true;
8799                    }
8800
8801                    let final_order_by = if within_group {
8802                        wg_order_by
8803                    } else {
8804                        agg_order_by
8805                    };
8806
8807                    // Try to construct a typed function variant only when there are no
8808                    // aggregate-specific clauses (otherwise we lose them).
8809                    if final_order_by.is_empty()
8810                        && !within_group
8811                        && let Some(typed) = Self::try_typed_function(&name, args.clone(), distinct)
8812                    {
8813                        return Ok(typed);
8814                    }
8815
8816                    Ok(Expr::Function {
8817                        name,
8818                        args,
8819                        distinct,
8820                        filter: None,
8821                        over: None,
8822                        order_by: final_order_by,
8823                        within_group,
8824                    })
8825                }
8826                // Qualified column: table.column or table.*
8827                else if self.match_token(TokenType::Dot) {
8828                    if self.peek_type() == &TokenType::Star {
8829                        self.advance();
8830                        Ok(Expr::QualifiedWildcard { table: name })
8831                    } else {
8832                        // ClickHouse JSON subobject and typed access at the
8833                        // first dot: `json.^a`, `json.:Int64`.
8834                        let _ = self.match_token(TokenType::BitwiseXor);
8835                        let _ = self.match_token(TokenType::Colon);
8836                        let (mut col, mut col_qs) = if matches!(self.peek_type(), TokenType::Number)
8837                        {
8838                            // ClickHouse tuple index `x.1`.
8839                            let v = self.peek().value.clone();
8840                            self.advance();
8841                            (v, QuoteStyle::None)
8842                        } else if matches!(self.peek_type(), TokenType::Null) {
8843                            // ClickHouse JSON subcolumn `.null` (e.g.
8844                            // `arr.null`, `t.s.null`). Accept the keyword as
8845                            // a field name in dotted-access position.
8846                            let v = self.peek().value.clone();
8847                            self.advance();
8848                            (v, QuoteStyle::None)
8849                        } else {
8850                            self.expect_name_with_quote()?
8851                        };
8852                        // Handle 3+ part qualified names like `db.schema.table.column`
8853                        // (DuckDB, ClickHouse). We collapse everything except the
8854                        // final segment into the `table` field as a dotted string.
8855                        let mut table = name;
8856                        let mut table_qs = name_qs;
8857                        while self.match_token(TokenType::Dot) {
8858                            if self.peek_type() == &TokenType::Star {
8859                                self.advance();
8860                                let mut full = table;
8861                                full.push('.');
8862                                full.push_str(&col);
8863                                return Ok(Expr::QualifiedWildcard { table: full });
8864                            }
8865                            // ClickHouse JSON subobject (`json.^a`) and typed
8866                            // access (`json.a.:Int64`) — swallow the operator
8867                            // so the following name can be consumed normally.
8868                            let _ = self.match_token(TokenType::BitwiseXor);
8869                            let _ = self.match_token(TokenType::Colon);
8870                            // ClickHouse tuple index (`t.1`): treat number as
8871                            // a synthetic field name.
8872                            let (next_col, next_qs) =
8873                                if matches!(self.peek_type(), TokenType::Number) {
8874                                    let v = self.peek().value.clone();
8875                                    self.advance();
8876                                    (v, QuoteStyle::None)
8877                                } else if matches!(self.peek_type(), TokenType::Null) {
8878                                    let v = self.peek().value.clone();
8879                                    self.advance();
8880                                    (v, QuoteStyle::None)
8881                                } else {
8882                                    self.expect_name_with_quote()?
8883                                };
8884                            table.push('.');
8885                            table.push_str(&col);
8886                            table_qs = col_qs;
8887                            col = next_col;
8888                            col_qs = next_qs;
8889                        }
8890                        // Function call on dotted name: db.schema.func(args).
8891                        if self.peek_type() == &TokenType::LParen {
8892                            self.advance();
8893                            let mut full = table;
8894                            full.push('.');
8895                            full.push_str(&col);
8896                            let args = if self.peek_type() != &TokenType::RParen {
8897                                let mut a = vec![self.parse_function_arg()?];
8898                                while self.match_token(TokenType::Comma) {
8899                                    a.push(self.parse_function_arg()?);
8900                                }
8901                                a
8902                            } else {
8903                                vec![]
8904                            };
8905                            self.expect(TokenType::RParen)?;
8906                            return Ok(Expr::Function {
8907                                name: full,
8908                                args,
8909                                distinct: false,
8910                                filter: None,
8911                                over: None,
8912                                order_by: Vec::new(),
8913                                within_group: false,
8914                            });
8915                        }
8916                        Ok(Expr::Column {
8917                            table: Some(table),
8918                            name: col,
8919                            quote_style: col_qs,
8920                            table_quote_style: table_qs,
8921                        })
8922                    }
8923                } else {
8924                    Ok(Expr::Column {
8925                        table: None,
8926                        name,
8927                        quote_style: name_qs,
8928                        table_quote_style: QuoteStyle::None,
8929                    })
8930                }
8931            }
8932
8933            _ => {
8934                // Fallback: any other token whose value is a valid identifier
8935                // and is immediately followed by `(` is treated as a function
8936                // call. This handles reserved keywords used as Spark/Hive
8937                // built-ins (IF, ALL, ANY, EXISTS, MOD, etc.) and dialect
8938                // functions that happen to collide with token types.
8939                let v = token.value.clone();
8940                let is_word =
8941                    !v.is_empty() && v.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
8942                if is_word
8943                    && matches!(
8944                        self.peek_offset(1).map(|t| &t.token_type),
8945                        Some(TokenType::LParen)
8946                    )
8947                {
8948                    // TRY_CAST / SAFE_CAST / TRY_TO_TIMESTAMP / … — same
8949                    // shape as `CAST(expr AS type)`. Lower to `Expr::Cast`
8950                    // (or back to a function call when the form doesn't
8951                    // match).
8952                    let upper = v.to_ascii_uppercase();
8953                    if matches!(upper.as_str(), "TRY_CAST" | "SAFE_CAST") {
8954                        self.advance();
8955                        self.advance(); // consume '('
8956                        let inner = self.parse_expr()?;
8957                        if self.match_token(TokenType::As) {
8958                            let data_type = self.parse_data_type()?;
8959                            self.expect(TokenType::RParen)?;
8960                            return Ok(Expr::Cast {
8961                                expr: Box::new(inner),
8962                                data_type,
8963                            });
8964                        }
8965                        // Fall back: treat as ordinary function call.
8966                        let mut args = vec![inner];
8967                        while self.match_token(TokenType::Comma) {
8968                            args.push(self.parse_expr()?);
8969                        }
8970                        self.expect(TokenType::RParen)?;
8971                        return Ok(Expr::Function {
8972                            name: v,
8973                            args,
8974                            distinct: false,
8975                            filter: None,
8976                            over: None,
8977                            order_by: Vec::new(),
8978                            within_group: false,
8979                        });
8980                    }
8981                    self.advance();
8982                    self.advance(); // consume '('
8983                    let upper = v.to_ascii_uppercase();
8984                    // Standard SQL `SUBSTRING(expr FROM start [FOR length])`
8985                    // and MySQL `SUBSTRING(expr FROM start)` / `…FOR length`.
8986                    if matches!(upper.as_str(), "SUBSTRING" | "SUBSTR")
8987                        && self.peek_type() != &TokenType::RParen
8988                    {
8989                        let saved = self.pos;
8990                        let first = self.parse_expr()?;
8991                        if self.match_token(TokenType::From) {
8992                            let start = self.parse_expr()?;
8993                            let length = if self.check_keyword("FOR") {
8994                                self.advance();
8995                                Some(self.parse_expr()?)
8996                            } else {
8997                                None
8998                            };
8999                            self.expect(TokenType::RParen)?;
9000                            let mut args = vec![first, start];
9001                            if let Some(len) = length {
9002                                args.push(len);
9003                            }
9004                            return Ok(Expr::Function {
9005                                name: v,
9006                                args,
9007                                distinct: false,
9008                                filter: None,
9009                                over: None,
9010                                order_by: Vec::new(),
9011                                within_group: false,
9012                            });
9013                        }
9014                        if self.check_keyword("FOR") {
9015                            self.advance();
9016                            let length = self.parse_expr()?;
9017                            self.expect(TokenType::RParen)?;
9018                            return Ok(Expr::Function {
9019                                name: v,
9020                                args: vec![first, length],
9021                                distinct: false,
9022                                filter: None,
9023                                over: None,
9024                                order_by: Vec::new(),
9025                                within_group: false,
9026                            });
9027                        }
9028                        // Fall back: re-parse as comma list.
9029                        self.pos = saved;
9030                    }
9031                    // Standard `TRIM([LEADING|TRAILING|BOTH] [chars] FROM expr)`
9032                    // and `TRIM(expr [, chars])` (already covered by comma).
9033                    if upper == "TRIM" && self.peek_type() != &TokenType::RParen {
9034                        let saved = self.pos;
9035                        if self.check_keyword("LEADING")
9036                            || self.check_keyword("TRAILING")
9037                            || self.check_keyword("BOTH")
9038                        {
9039                            self.advance();
9040                        }
9041                        if self.peek_type() == &TokenType::From {
9042                            self.advance();
9043                            let expr = self.parse_expr()?;
9044                            self.expect(TokenType::RParen)?;
9045                            return Ok(Expr::Function {
9046                                name: v,
9047                                args: vec![expr],
9048                                distinct: false,
9049                                filter: None,
9050                                over: None,
9051                                order_by: Vec::new(),
9052                                within_group: false,
9053                            });
9054                        }
9055                        // chars FROM expr
9056                        let chars = self.parse_expr()?;
9057                        if self.match_token(TokenType::From) {
9058                            let expr = self.parse_expr()?;
9059                            self.expect(TokenType::RParen)?;
9060                            return Ok(Expr::Function {
9061                                name: v,
9062                                args: vec![expr, chars],
9063                                distinct: false,
9064                                filter: None,
9065                                over: None,
9066                                order_by: Vec::new(),
9067                                within_group: false,
9068                            });
9069                        }
9070                        // Plain comma list — fall back.
9071                        self.pos = saved;
9072                    }
9073                    // Standard `OVERLAY(expr PLACING str FROM start [FOR len])`.
9074                    if upper == "OVERLAY" && self.peek_type() != &TokenType::RParen {
9075                        let saved = self.pos;
9076                        let target = self.parse_expr()?;
9077                        if self.check_keyword("PLACING") {
9078                            self.advance();
9079                            let placing = self.parse_expr()?;
9080                            self.expect(TokenType::From)?;
9081                            let from = self.parse_expr()?;
9082                            let len = if self.check_keyword("FOR") {
9083                                self.advance();
9084                                Some(self.parse_expr()?)
9085                            } else {
9086                                None
9087                            };
9088                            self.expect(TokenType::RParen)?;
9089                            let mut args = vec![target, placing, from];
9090                            if let Some(l) = len {
9091                                args.push(l);
9092                            }
9093                            return Ok(Expr::Function {
9094                                name: v,
9095                                args,
9096                                distinct: false,
9097                                filter: None,
9098                                over: None,
9099                                order_by: Vec::new(),
9100                                within_group: false,
9101                            });
9102                        }
9103                        self.pos = saved;
9104                    }
9105                    // Standard `POSITION(needle IN haystack)`.
9106                    if upper == "POSITION" && self.peek_type() != &TokenType::RParen {
9107                        let saved = self.pos;
9108                        let needle = self.parse_expr()?;
9109                        if self.check_keyword("IN") {
9110                            self.advance();
9111                            let haystack = self.parse_expr()?;
9112                            self.expect(TokenType::RParen)?;
9113                            return Ok(Expr::Function {
9114                                name: v,
9115                                args: vec![needle, haystack],
9116                                distinct: false,
9117                                filter: None,
9118                                over: None,
9119                                order_by: Vec::new(),
9120                                within_group: false,
9121                            });
9122                        }
9123                        self.pos = saved;
9124                    }
9125                    let mut args = Vec::new();
9126                    if self.peek_type() != &TokenType::RParen {
9127                        args.push(self.parse_function_arg()?);
9128                        while self.match_token(TokenType::Comma) {
9129                            args.push(self.parse_function_arg()?);
9130                        }
9131                    }
9132                    self.expect(TokenType::RParen)?;
9133                    return Ok(Expr::Function {
9134                        name: v,
9135                        args,
9136                        distinct: false,
9137                        filter: None,
9138                        over: None,
9139                        order_by: Vec::new(),
9140                        within_group: false,
9141                    });
9142                }
9143                Err(SqlglotError::UnexpectedToken { token })
9144            }
9145        }
9146    }
9147
9148    /// Parse a single function-call argument. Accepts the DuckDB / PostgreSQL
9149    /// named-argument syntaxes `name := value` and `name => value` and falls
9150    /// back to a plain expression for positional arguments. The argument
9151    /// name is discarded — we don't model it in the AST.
9152    fn parse_function_arg(&mut self) -> Result<Expr> {
9153        // Hive table-valued function clause: `noop(on tbl partition by p
9154        // order by q distribute by r cluster by s sort by t)`. The arg
9155        // list begins with the `ON` keyword and is followed by a series
9156        // of windowing-style clauses we don't model. Swallow it as an
9157        // opaque payload so we don't reject the call.
9158        if matches!(self.peek_type(), TokenType::On) {
9159            let mut depth = 0usize;
9160            while !matches!(self.peek_type(), TokenType::Eof) {
9161                match self.peek_type() {
9162                    TokenType::LParen => depth += 1,
9163                    TokenType::RParen => {
9164                        if depth == 0 {
9165                            break;
9166                        }
9167                        depth -= 1;
9168                    }
9169                    TokenType::Comma if depth == 0 => break,
9170                    _ => {}
9171                }
9172                self.advance();
9173            }
9174            return Ok(Expr::Null);
9175        }
9176        if self.is_name_token()
9177            || self.is_data_type_token()
9178            || matches!(self.peek_type(), TokenType::Recursive)
9179        {
9180            let next = self.peek_offset(1).map(|t| &t.token_type);
9181            if matches!(next, Some(TokenType::Colon)) {
9182                let after = self.peek_offset(2).map(|t| &t.token_type);
9183                if matches!(after, Some(TokenType::Eq)) {
9184                    self.advance();
9185                    self.advance();
9186                    self.advance();
9187                    return self.parse_expr();
9188                }
9189            }
9190            if matches!(next, Some(TokenType::DoubleArrow)) {
9191                self.advance();
9192                self.advance();
9193                return self.parse_expr();
9194            }
9195        }
9196        // ClickHouse table functions: `view(SELECT …)`, `cluster(…)` etc.
9197        // accept a full SELECT / WITH / UNION inside the arg list. Parse
9198        // it as a Subquery so the surrounding call closes properly.
9199        if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
9200            let stmt = self.parse_statement_inner()?;
9201            return Ok(Expr::Subquery(Box::new(stmt)));
9202        }
9203        let mut expr = self.parse_expr()?;
9204        // Oracle / Snowflake / MySQL `JSON_OBJECT('k' : value, ...)` and the
9205        // `JSON_OBJECTAGG(k : v)` family use `:` as a key-value separator
9206        // inside function args. After parsing the first expression, swallow
9207        // a bare `:` and parse the value side; emit the value as the arg
9208        // (we don't model JSON key-value pairs in the AST). Only fire when
9209        // the next-after-colon is not another `:` (`::` cast) and not `=`
9210        // (`:=` named arg, already handled above).
9211        if matches!(self.peek_type(), TokenType::Colon)
9212            && !matches!(
9213                self.peek_offset(1).map(|t| &t.token_type),
9214                Some(TokenType::Colon) | Some(TokenType::Eq)
9215            )
9216        {
9217            self.advance(); // :
9218            expr = self.parse_expr()?;
9219            // Optional `FORMAT JSON` suffix (Oracle).
9220            if self.peek().value.eq_ignore_ascii_case("FORMAT")
9221                && self
9222                    .peek_offset(1)
9223                    .map(|t| t.value.eq_ignore_ascii_case("JSON"))
9224                    .unwrap_or(false)
9225            {
9226                self.advance();
9227                self.advance();
9228            }
9229        }
9230        // ClickHouse: `func(expr AS alias)` — swallow the alias.
9231        if self.match_token(TokenType::As) && self.is_name_token() {
9232            self.advance();
9233        }
9234        // Spark / DataBricks UDTF call: `UDTF(TABLE(t) [PARTITION BY cols]
9235        // [ORDER BY cols])`. Swallow the table-argument modifiers opaquely.
9236        if self.peek_type() == &TokenType::Partition
9237            && self
9238                .peek_offset(1)
9239                .map(|t| matches!(t.token_type, TokenType::By))
9240                .unwrap_or(false)
9241        {
9242            self.advance(); // PARTITION
9243            self.advance(); // BY
9244            // Comma-separated expression list (column refs / exprs).
9245            let _ = self.parse_expr()?;
9246            while self.match_token(TokenType::Comma) {
9247                let _ = self.parse_expr()?;
9248            }
9249        }
9250        if self.peek_type() == &TokenType::Order
9251            && self
9252                .peek_offset(1)
9253                .map(|t| matches!(t.token_type, TokenType::By))
9254                .unwrap_or(false)
9255        {
9256            self.advance(); // ORDER
9257            self.advance(); // BY
9258            let _ = self.parse_order_by_items()?;
9259        }
9260        // BigQuery / DuckDB / Snowflake / Oracle window-function nulls
9261        // modifier: `LAST_VALUE(arg IGNORE NULLS)`, `... RESPECT NULLS`.
9262        // Swallow opaquely; we don't model it in the AST.
9263        if (self.peek().value.eq_ignore_ascii_case("IGNORE")
9264            || self.peek().value.eq_ignore_ascii_case("RESPECT"))
9265            && self
9266                .peek_offset(1)
9267                .map(|t| t.token_type == TokenType::Null || t.value.eq_ignore_ascii_case("NULLS"))
9268                .unwrap_or(false)
9269        {
9270            self.advance();
9271            self.advance();
9272        }
9273        // Postgres JSON helpers: `JSON_SERIALIZE(expr RETURNING type)`,
9274        // `JSON_QUERY(... RETURNING jsonb FORMAT JSON)`,
9275        // `JSON_VALUE(... RETURNING type DEFAULT v ON EMPTY|ERROR …)`. After
9276        // any RETURNING clause, swallow the optional FORMAT, DEFAULT, ON
9277        // EMPTY/ERROR tail so the call parses cleanly.
9278        if self.match_token(TokenType::Returning) {
9279            if self.is_data_type_token() || self.is_name_token() {
9280                let _ = self.parse_data_type();
9281            }
9282        }
9283        // SQL/JSON `PASSING v AS name [, v AS name]*` clause inside
9284        // JSON_EXISTS / JSON_VALUE / JSON_QUERY argument lists.
9285        if self.check_keyword("PASSING") {
9286            self.advance();
9287            loop {
9288                let _ = self.parse_expr()?;
9289                if self.match_token(TokenType::As) && self.is_name_token() {
9290                    self.advance();
9291                }
9292                if !self.match_token(TokenType::Comma) {
9293                    break;
9294                }
9295            }
9296        }
9297        // SQL/JSON behavior clauses: `NULL|ERROR|EMPTY [ARRAY|OBJECT]|
9298        // DEFAULT expr ON EMPTY|ERROR`. Swallow them opaquely; the
9299        // surrounding call still resolves to its primary expression.
9300        loop {
9301            let is_default = self.peek_type() == &TokenType::Default;
9302            let is_behavior_kw = self.check_keyword("ERROR")
9303                || self.check_keyword("NULL")
9304                || self.peek_type() == &TokenType::Null
9305                || self.check_keyword("EMPTY")
9306                || self.check_keyword("TRUE")
9307                || self.check_keyword("FALSE")
9308                || self.check_keyword("UNKNOWN");
9309            if !is_default && !is_behavior_kw {
9310                break;
9311            }
9312            // Look ahead: behavior keyword must be followed (possibly via
9313            // optional ARRAY/OBJECT/expr) by `ON ERROR|EMPTY` to qualify.
9314            let saved = self.pos;
9315            if is_default {
9316                self.advance();
9317                let _ = self.parse_expr();
9318            } else {
9319                self.advance();
9320                if self.check_keyword("ARRAY") || self.check_keyword("OBJECT") {
9321                    self.advance();
9322                }
9323            }
9324            if self.peek_type() == &TokenType::On
9325                && self
9326                    .peek_offset(1)
9327                    .map(|t| {
9328                        t.value.eq_ignore_ascii_case("ERROR")
9329                            || t.value.eq_ignore_ascii_case("EMPTY")
9330                    })
9331                    .unwrap_or(false)
9332            {
9333                self.advance(); // ON
9334                self.advance(); // ERROR / EMPTY
9335            } else {
9336                // Not actually a behavior clause — rewind.
9337                self.pos = saved;
9338                break;
9339            }
9340        }
9341        // MySQL `CONVERT(expr USING charset)` — swallow USING + name.
9342        if self.match_token(TokenType::Using) {
9343            if self.is_name_token() {
9344                self.advance();
9345            }
9346        }
9347        // ON EMPTY / ON ERROR / DEFAULT … ON EMPTY|ERROR / FORMAT … —
9348        // tolerated tail clauses common to JSON_VALUE / JSON_QUERY /
9349        // JSON_EXISTS. Loop while one of the recognized starters appears.
9350        loop {
9351            let starts = self.peek_type() == &TokenType::Default
9352                || self.match_keyword_clone("FORMAT")
9353                || (self.peek_type() == &TokenType::On
9354                    && self
9355                        .peek_offset(1)
9356                        .map(|t| {
9357                            t.value.eq_ignore_ascii_case("EMPTY")
9358                                || t.value.eq_ignore_ascii_case("ERROR")
9359                        })
9360                        .unwrap_or(false));
9361            if !starts {
9362                break;
9363            }
9364            // Consume up to the next top-level `,` / `)` / EOF, tracking
9365            // nesting so embedded parens (e.g. `DEFAULT ('C' COLLATE "C")`)
9366            // don't terminate prematurely.
9367            let mut depth = 0i32;
9368            while !matches!(self.peek_type(), TokenType::Eof) {
9369                match self.peek_type() {
9370                    TokenType::LParen | TokenType::LBracket => depth += 1,
9371                    TokenType::RParen | TokenType::RBracket => {
9372                        if depth == 0 {
9373                            break;
9374                        }
9375                        depth -= 1;
9376                    }
9377                    TokenType::Comma if depth == 0 => break,
9378                    _ => {}
9379                }
9380                self.advance();
9381            }
9382        }
9383        Ok(expr)
9384    }
9385
9386    /// True when the current token is a name token whose uppercase value
9387    /// equals `kw`. Does NOT advance the token cursor.
9388    fn match_keyword_clone(&self, kw: &str) -> bool {
9389        self.check_keyword(kw)
9390    }
9391
9392    fn is_data_type_token(&self) -> bool {
9393        self.is_data_type_token_kind(self.peek_type())
9394    }
9395
9396    fn is_data_type_token_kind(&self, tt: &TokenType) -> bool {
9397        matches!(
9398            tt,
9399            TokenType::Int
9400                | TokenType::Integer
9401                | TokenType::BigInt
9402                | TokenType::SmallInt
9403                | TokenType::TinyInt
9404                | TokenType::Float
9405                | TokenType::Double
9406                | TokenType::Decimal
9407                | TokenType::Numeric
9408                | TokenType::Real
9409                | TokenType::Varchar
9410                | TokenType::Char
9411                | TokenType::Text
9412                | TokenType::Boolean
9413                | TokenType::Date
9414                | TokenType::Timestamp
9415                | TokenType::TimestampTz
9416                | TokenType::Time
9417                | TokenType::Interval
9418                | TokenType::Blob
9419                | TokenType::Bytea
9420                | TokenType::Json
9421                | TokenType::Jsonb
9422                | TokenType::Uuid
9423                | TokenType::Array
9424                | TokenType::Map
9425                | TokenType::Struct
9426        )
9427    }
9428
9429    fn parse_datetime_field(&mut self) -> Result<DateTimeField> {
9430        let token = self.peek().clone();
9431        let field = match &token.token_type {
9432            TokenType::Year => DateTimeField::Year,
9433            TokenType::Month => DateTimeField::Month,
9434            TokenType::Day => DateTimeField::Day,
9435            TokenType::Hour => DateTimeField::Hour,
9436            TokenType::Minute => DateTimeField::Minute,
9437            TokenType::Second => DateTimeField::Second,
9438            TokenType::Epoch => DateTimeField::Epoch,
9439            _ => {
9440                let name = token.value.to_uppercase();
9441                match name.as_str() {
9442                    "YEAR" => DateTimeField::Year,
9443                    "QUARTER" => DateTimeField::Quarter,
9444                    "MONTH" => DateTimeField::Month,
9445                    "WEEK" => DateTimeField::Week,
9446                    "DAY" => DateTimeField::Day,
9447                    "DOW" | "DAYOFWEEK" => DateTimeField::DayOfWeek,
9448                    "DOY" | "DAYOFYEAR" => DateTimeField::DayOfYear,
9449                    "HOUR" => DateTimeField::Hour,
9450                    "MINUTE" => DateTimeField::Minute,
9451                    "SECOND" => DateTimeField::Second,
9452                    "MILLISECOND" | "MILLISECONDS" | "MS" => DateTimeField::Millisecond,
9453                    "MICROSECOND" | "MICROSECONDS" | "US" => DateTimeField::Microsecond,
9454                    "NANOSECOND" | "NANOSECONDS" | "NS" => DateTimeField::Nanosecond,
9455                    "YEARS" => DateTimeField::Year,
9456                    "QUARTERS" => DateTimeField::Quarter,
9457                    "MONTHS" => DateTimeField::Month,
9458                    "WEEKS" => DateTimeField::Week,
9459                    "DAYS" => DateTimeField::Day,
9460                    "HOURS" => DateTimeField::Hour,
9461                    "MINUTES" => DateTimeField::Minute,
9462                    "SECONDS" => DateTimeField::Second,
9463                    "EPOCH" => DateTimeField::Epoch,
9464                    "TIMEZONE" => DateTimeField::Timezone,
9465                    "TIMEZONE_HOUR" => DateTimeField::TimezoneHour,
9466                    "TIMEZONE_MINUTE" => DateTimeField::TimezoneMinute,
9467                    // MySQL composite interval units. We don't model them
9468                    // distinctly; lower to the dominant component so the
9469                    // surrounding parse completes.
9470                    "DAY_HOUR" | "DAY_MINUTE" | "DAY_SECOND" | "DAY_MICROSECOND" => {
9471                        DateTimeField::Day
9472                    }
9473                    "HOUR_MINUTE" | "HOUR_SECOND" | "HOUR_MICROSECOND" => DateTimeField::Hour,
9474                    "MINUTE_SECOND" | "MINUTE_MICROSECOND" => DateTimeField::Minute,
9475                    "SECOND_MICROSECOND" => DateTimeField::Second,
9476                    "YEAR_MONTH" => DateTimeField::Year,
9477                    _ => {
9478                        return Err(SqlglotError::ParserError {
9479                            message: format!("Unknown datetime field: {name}"),
9480                        });
9481                    }
9482                }
9483            }
9484        };
9485        self.advance();
9486        Ok(field)
9487    }
9488
9489    fn try_parse_datetime_field(&mut self) -> Option<DateTimeField> {
9490        let saved = self.pos;
9491        match self.parse_datetime_field() {
9492            Ok(field) => Some(field),
9493            Err(_) => {
9494                self.pos = saved;
9495                None
9496            }
9497        }
9498    }
9499
9500    /// Parse the inside of `GROUP_CONCAT(...)` (caller has already consumed
9501    /// the `(` and optional `DISTINCT`). Returns a typed `GroupConcat`
9502    /// expression. Does NOT consume the trailing `)`.
9503    fn parse_group_concat_call(&mut self, distinct: bool) -> Result<Expr> {
9504        let mut exprs: Vec<Expr> = Vec::new();
9505        let mut order_by: Vec<OrderByItem> = Vec::new();
9506        let mut separator: Option<Box<Expr>> = None;
9507
9508        if self.peek_type() != &TokenType::RParen {
9509            exprs.push(self.parse_expr()?);
9510            while self.peek_type() == &TokenType::Comma {
9511                // ORDER BY / SEPARATOR are alternative terminators, not args.
9512                // Peek one past the comma to disambiguate `f(a, b)` from
9513                // `f(a, b ORDER BY ...)` — but comma here always introduces
9514                // another positional arg, so just keep consuming.
9515                self.advance();
9516                exprs.push(self.parse_expr()?);
9517            }
9518
9519            if self.match_token(TokenType::Order) {
9520                self.expect(TokenType::By)?;
9521                order_by = self.parse_order_by_items()?;
9522            }
9523
9524            if self.match_keyword("SEPARATOR") {
9525                separator = Some(Box::new(self.parse_expr()?));
9526            }
9527        }
9528
9529        Ok(Expr::TypedFunction {
9530            func: TypedFunction::GroupConcat {
9531                exprs,
9532                separator,
9533                order_by,
9534                distinct,
9535            },
9536            filter: None,
9537            over: None,
9538        })
9539    }
9540
9541    /// Try to construct a typed function expression from a parsed function call.
9542    /// Returns `None` if the function name is not recognized, falling back to
9543    /// the generic `Expr::Function`.
9544    fn try_typed_function(name: &str, args: Vec<Expr>, distinct: bool) -> Option<Expr> {
9545        let upper = name.to_uppercase();
9546        let tf = match upper.as_str() {
9547            // ── Date/Time ──────────────────────────────────────────
9548            "DATE_ADD" | "DATEADD" | "TIMESTAMPADD" => {
9549                let mut it = args.into_iter();
9550                let first = it.next()?;
9551                let second = it.next()?;
9552                let third = it.next();
9553                // Handle DATEADD(unit, interval, expr) — TSQL/Snowflake arg order
9554                if upper == "DATEADD" {
9555                    if let Some(third_arg) = third {
9556                        // 3-arg: DATEADD(unit, interval, expr)
9557                        let unit = Self::expr_to_datetime_field(&first);
9558                        TypedFunction::DateAdd {
9559                            expr: Box::new(third_arg),
9560                            interval: Box::new(second),
9561                            unit,
9562                        }
9563                    } else {
9564                        TypedFunction::DateAdd {
9565                            expr: Box::new(first),
9566                            interval: Box::new(second),
9567                            unit: None,
9568                        }
9569                    }
9570                } else {
9571                    // DATE_ADD(expr, interval [, unit])
9572                    let unit = third.as_ref().and_then(Self::expr_to_datetime_field);
9573                    TypedFunction::DateAdd {
9574                        expr: Box::new(first),
9575                        interval: Box::new(second),
9576                        unit,
9577                    }
9578                }
9579            }
9580            "DATE_DIFF" | "DATEDIFF" | "TIMESTAMPDIFF" => {
9581                let mut it = args.into_iter();
9582                let first = it.next()?;
9583                let second = it.next()?;
9584                let third = it.next();
9585                if let Some(third_arg) = third {
9586                    if upper == "DATEDIFF" {
9587                        // DATEDIFF(unit, start, end) — TSQL/Snowflake
9588                        let unit = Self::expr_to_datetime_field(&first);
9589                        TypedFunction::DateDiff {
9590                            start: Box::new(second),
9591                            end: Box::new(third_arg),
9592                            unit,
9593                        }
9594                    } else {
9595                        let unit = Self::expr_to_datetime_field(&third_arg);
9596                        TypedFunction::DateDiff {
9597                            start: Box::new(first),
9598                            end: Box::new(second),
9599                            unit,
9600                        }
9601                    }
9602                } else {
9603                    TypedFunction::DateDiff {
9604                        start: Box::new(first),
9605                        end: Box::new(second),
9606                        unit: None,
9607                    }
9608                }
9609            }
9610            "DATE_TRUNC" | "DATETRUNC" => {
9611                let mut it = args.into_iter();
9612                let first = it.next()?;
9613                let second = it.next()?;
9614                // DATE_TRUNC('unit', expr) or DATE_TRUNC(unit, expr)
9615                let (unit, expr) = if let Some(u) = Self::expr_to_datetime_field(&first) {
9616                    (u, second)
9617                } else if let Some(u) = Self::expr_to_datetime_field(&second) {
9618                    (u, first)
9619                } else {
9620                    // Default: first = unit string, second = expr
9621                    return None;
9622                };
9623                TypedFunction::DateTrunc {
9624                    unit,
9625                    expr: Box::new(expr),
9626                }
9627            }
9628            "DATE_SUB" | "DATESUB" => {
9629                let mut it = args.into_iter();
9630                let first = it.next()?;
9631                let second = it.next()?;
9632                let third = it.next();
9633                let unit = third.as_ref().and_then(Self::expr_to_datetime_field);
9634                TypedFunction::DateSub {
9635                    expr: Box::new(first),
9636                    interval: Box::new(second),
9637                    unit,
9638                }
9639            }
9640            "CURRENT_DATE" => TypedFunction::CurrentDate,
9641            "CURRENT_TIME" | "CURTIME" => TypedFunction::CurrentTime,
9642            "CURRENT_TIMESTAMP" | "NOW" | "GETDATE" | "SYSDATE" => TypedFunction::CurrentTimestamp,
9643            "STR_TO_TIME" | "STR_TO_DATE" | "TO_TIMESTAMP" | "PARSE_TIMESTAMP"
9644            | "PARSE_DATETIME" => {
9645                let mut it = args.into_iter();
9646                let expr = it.next()?;
9647                let format = it.next()?;
9648                TypedFunction::StrToTime {
9649                    expr: Box::new(expr),
9650                    format: Box::new(format),
9651                }
9652            }
9653            "TIME_TO_STR" | "DATE_FORMAT" | "FORMAT_TIMESTAMP" | "FORMAT_DATETIME" | "TO_CHAR" => {
9654                let mut it = args.into_iter();
9655                let expr = it.next()?;
9656                let format = it.next()?;
9657                TypedFunction::TimeToStr {
9658                    expr: Box::new(expr),
9659                    format: Box::new(format),
9660                }
9661            }
9662            "TS_OR_DS_TO_DATE" => {
9663                let mut it = args.into_iter();
9664                TypedFunction::TsOrDsToDate {
9665                    expr: Box::new(it.next()?),
9666                }
9667            }
9668            "YEAR" => {
9669                let mut it = args.into_iter();
9670                TypedFunction::Year {
9671                    expr: Box::new(it.next()?),
9672                }
9673            }
9674            "MONTH" => {
9675                let mut it = args.into_iter();
9676                TypedFunction::Month {
9677                    expr: Box::new(it.next()?),
9678                }
9679            }
9680            "DAY" | "DAYOFMONTH" => {
9681                let mut it = args.into_iter();
9682                TypedFunction::Day {
9683                    expr: Box::new(it.next()?),
9684                }
9685            }
9686
9687            // ── String ─────────────────────────────────────────────
9688            "TRIM" => {
9689                let mut it = args.into_iter();
9690                let expr = it.next()?;
9691                TypedFunction::Trim {
9692                    expr: Box::new(expr),
9693                    trim_type: TrimType::Both,
9694                    trim_chars: None,
9695                }
9696            }
9697            "LTRIM" => {
9698                let mut it = args.into_iter();
9699                let expr = it.next()?;
9700                TypedFunction::Trim {
9701                    expr: Box::new(expr),
9702                    trim_type: TrimType::Leading,
9703                    trim_chars: None,
9704                }
9705            }
9706            "RTRIM" => {
9707                let mut it = args.into_iter();
9708                let expr = it.next()?;
9709                TypedFunction::Trim {
9710                    expr: Box::new(expr),
9711                    trim_type: TrimType::Trailing,
9712                    trim_chars: None,
9713                }
9714            }
9715            "SUBSTRING" | "SUBSTR" => {
9716                let mut it = args.into_iter();
9717                let expr = it.next()?;
9718                let start = it.next()?;
9719                let length = it.next();
9720                TypedFunction::Substring {
9721                    expr: Box::new(expr),
9722                    start: Box::new(start),
9723                    length: length.map(Box::new),
9724                }
9725            }
9726            "UPPER" | "UCASE" => {
9727                let mut it = args.into_iter();
9728                TypedFunction::Upper {
9729                    expr: Box::new(it.next()?),
9730                }
9731            }
9732            "LOWER" | "LCASE" => {
9733                let mut it = args.into_iter();
9734                TypedFunction::Lower {
9735                    expr: Box::new(it.next()?),
9736                }
9737            }
9738            "REGEXP_LIKE" | "RLIKE" => {
9739                let mut it = args.into_iter();
9740                let expr = it.next()?;
9741                let pattern = it.next()?;
9742                let flags = it.next();
9743                TypedFunction::RegexpLike {
9744                    expr: Box::new(expr),
9745                    pattern: Box::new(pattern),
9746                    flags: flags.map(Box::new),
9747                }
9748            }
9749            "REGEXP_EXTRACT" | "REGEXP_SUBSTR" => {
9750                let mut it = args.into_iter();
9751                let expr = it.next()?;
9752                let pattern = it.next()?;
9753                let group_index = it.next();
9754                TypedFunction::RegexpExtract {
9755                    expr: Box::new(expr),
9756                    pattern: Box::new(pattern),
9757                    group_index: group_index.map(Box::new),
9758                }
9759            }
9760            "REGEXP_REPLACE" => {
9761                let mut it = args.into_iter();
9762                let expr = it.next()?;
9763                let pattern = it.next()?;
9764                let replacement = it.next()?;
9765                let flags = it.next();
9766                TypedFunction::RegexpReplace {
9767                    expr: Box::new(expr),
9768                    pattern: Box::new(pattern),
9769                    replacement: Box::new(replacement),
9770                    flags: flags.map(Box::new),
9771                }
9772            }
9773            "CONCAT_WS" => {
9774                let mut it = args.into_iter();
9775                let separator = it.next()?;
9776                let exprs: Vec<Expr> = it.collect();
9777                TypedFunction::ConcatWs {
9778                    separator: Box::new(separator),
9779                    exprs,
9780                }
9781            }
9782            "SPLIT" | "STRING_SPLIT" => {
9783                let mut it = args.into_iter();
9784                let expr = it.next()?;
9785                let delimiter = it.next()?;
9786                TypedFunction::Split {
9787                    expr: Box::new(expr),
9788                    delimiter: Box::new(delimiter),
9789                }
9790            }
9791            "INITCAP" => {
9792                let mut it = args.into_iter();
9793                TypedFunction::Initcap {
9794                    expr: Box::new(it.next()?),
9795                }
9796            }
9797            "LENGTH" | "LEN" | "CHAR_LENGTH" | "CHARACTER_LENGTH" => {
9798                let mut it = args.into_iter();
9799                TypedFunction::Length {
9800                    expr: Box::new(it.next()?),
9801                }
9802            }
9803            "REPLACE" => {
9804                let mut it = args.into_iter();
9805                let expr = it.next()?;
9806                let from = it.next()?;
9807                let to = it.next()?;
9808                TypedFunction::Replace {
9809                    expr: Box::new(expr),
9810                    from: Box::new(from),
9811                    to: Box::new(to),
9812                }
9813            }
9814            "REVERSE" => {
9815                let mut it = args.into_iter();
9816                TypedFunction::Reverse {
9817                    expr: Box::new(it.next()?),
9818                }
9819            }
9820            "LEFT" => {
9821                let mut it = args.into_iter();
9822                let expr = it.next()?;
9823                let n = it.next()?;
9824                TypedFunction::Left {
9825                    expr: Box::new(expr),
9826                    n: Box::new(n),
9827                }
9828            }
9829            "RIGHT" => {
9830                let mut it = args.into_iter();
9831                let expr = it.next()?;
9832                let n = it.next()?;
9833                TypedFunction::Right {
9834                    expr: Box::new(expr),
9835                    n: Box::new(n),
9836                }
9837            }
9838            "LPAD" => {
9839                let mut it = args.into_iter();
9840                let expr = it.next()?;
9841                let length = it.next()?;
9842                let pad = it.next();
9843                TypedFunction::Lpad {
9844                    expr: Box::new(expr),
9845                    length: Box::new(length),
9846                    pad: pad.map(Box::new),
9847                }
9848            }
9849            "RPAD" => {
9850                let mut it = args.into_iter();
9851                let expr = it.next()?;
9852                let length = it.next()?;
9853                let pad = it.next();
9854                TypedFunction::Rpad {
9855                    expr: Box::new(expr),
9856                    length: Box::new(length),
9857                    pad: pad.map(Box::new),
9858                }
9859            }
9860
9861            // ── Aggregate ──────────────────────────────────────────
9862            "COUNT" => {
9863                let mut it = args.into_iter();
9864                let expr = it.next().unwrap_or(Expr::Wildcard);
9865                TypedFunction::Count {
9866                    expr: Box::new(expr),
9867                    distinct,
9868                }
9869            }
9870            "SUM" => {
9871                let mut it = args.into_iter();
9872                TypedFunction::Sum {
9873                    expr: Box::new(it.next()?),
9874                    distinct,
9875                }
9876            }
9877            "AVG" => {
9878                let mut it = args.into_iter();
9879                TypedFunction::Avg {
9880                    expr: Box::new(it.next()?),
9881                    distinct,
9882                }
9883            }
9884            "MIN" => {
9885                let mut it = args.into_iter();
9886                TypedFunction::Min {
9887                    expr: Box::new(it.next()?),
9888                }
9889            }
9890            "MAX" => {
9891                let mut it = args.into_iter();
9892                TypedFunction::Max {
9893                    expr: Box::new(it.next()?),
9894                }
9895            }
9896            "ARRAY_AGG" | "LIST" | "COLLECT_LIST" => {
9897                let mut it = args.into_iter();
9898                TypedFunction::ArrayAgg {
9899                    expr: Box::new(it.next()?),
9900                    distinct,
9901                }
9902            }
9903            "APPROX_DISTINCT" | "APPROX_COUNT_DISTINCT" => {
9904                let mut it = args.into_iter();
9905                TypedFunction::ApproxDistinct {
9906                    expr: Box::new(it.next()?),
9907                }
9908            }
9909            "VARIANCE" | "VAR_SAMP" | "VAR" => {
9910                let mut it = args.into_iter();
9911                TypedFunction::Variance {
9912                    expr: Box::new(it.next()?),
9913                }
9914            }
9915            "VAR_POP" => {
9916                let mut it = args.into_iter();
9917                TypedFunction::VariancePop {
9918                    expr: Box::new(it.next()?),
9919                }
9920            }
9921            "STDDEV" | "STDDEV_SAMP" => {
9922                let mut it = args.into_iter();
9923                TypedFunction::Stddev {
9924                    expr: Box::new(it.next()?),
9925                }
9926            }
9927            "STDDEV_POP" => {
9928                let mut it = args.into_iter();
9929                TypedFunction::StddevPop {
9930                    expr: Box::new(it.next()?),
9931                }
9932            }
9933
9934            // ── Array ──────────────────────────────────────────────
9935            "ARRAY_CONCAT" | "ARRAY_CAT" => TypedFunction::ArrayConcat { arrays: args },
9936            "ARRAY_CONTAINS" => {
9937                let mut it = args.into_iter();
9938                let array = it.next()?;
9939                let element = it.next()?;
9940                TypedFunction::ArrayContains {
9941                    array: Box::new(array),
9942                    element: Box::new(element),
9943                }
9944            }
9945            "ARRAY_SIZE" | "ARRAY_LENGTH" | "CARDINALITY" => {
9946                let mut it = args.into_iter();
9947                TypedFunction::ArraySize {
9948                    expr: Box::new(it.next()?),
9949                }
9950            }
9951            "EXPLODE" => {
9952                let mut it = args.into_iter();
9953                TypedFunction::Explode {
9954                    expr: Box::new(it.next()?),
9955                }
9956            }
9957            "GENERATE_SERIES" | "SEQUENCE" => {
9958                let mut it = args.into_iter();
9959                let start = it.next()?;
9960                let stop = it.next()?;
9961                let step = it.next();
9962                TypedFunction::GenerateSeries {
9963                    start: Box::new(start),
9964                    stop: Box::new(stop),
9965                    step: step.map(Box::new),
9966                }
9967            }
9968            "FLATTEN" => {
9969                let mut it = args.into_iter();
9970                TypedFunction::Flatten {
9971                    expr: Box::new(it.next()?),
9972                }
9973            }
9974
9975            // ── JSON ───────────────────────────────────────────────
9976            "JSON_EXTRACT" | "JSON_VALUE" => {
9977                let mut it = args.into_iter();
9978                let expr = it.next()?;
9979                let path = it.next()?;
9980                TypedFunction::JSONExtract {
9981                    expr: Box::new(expr),
9982                    path: Box::new(path),
9983                }
9984            }
9985            "JSON_EXTRACT_SCALAR" => {
9986                let mut it = args.into_iter();
9987                let expr = it.next()?;
9988                let path = it.next()?;
9989                TypedFunction::JSONExtractScalar {
9990                    expr: Box::new(expr),
9991                    path: Box::new(path),
9992                }
9993            }
9994            "PARSE_JSON" | "JSON_PARSE" => {
9995                let mut it = args.into_iter();
9996                TypedFunction::ParseJSON {
9997                    expr: Box::new(it.next()?),
9998                }
9999            }
10000            "JSON_FORMAT" | "TO_JSON" | "TO_JSON_STRING" => {
10001                let mut it = args.into_iter();
10002                TypedFunction::JSONFormat {
10003                    expr: Box::new(it.next()?),
10004                }
10005            }
10006
10007            // ── Window ─────────────────────────────────────────────
10008            "ROW_NUMBER" => TypedFunction::RowNumber,
10009            "RANK" => TypedFunction::Rank,
10010            "DENSE_RANK" => TypedFunction::DenseRank,
10011            "NTILE" => {
10012                let mut it = args.into_iter();
10013                TypedFunction::NTile {
10014                    n: Box::new(it.next()?),
10015                }
10016            }
10017            "LEAD" => {
10018                let mut it = args.into_iter();
10019                let expr = it.next()?;
10020                let offset = it.next();
10021                let default = it.next();
10022                TypedFunction::Lead {
10023                    expr: Box::new(expr),
10024                    offset: offset.map(Box::new),
10025                    default: default.map(Box::new),
10026                }
10027            }
10028            "LAG" => {
10029                let mut it = args.into_iter();
10030                let expr = it.next()?;
10031                let offset = it.next();
10032                let default = it.next();
10033                TypedFunction::Lag {
10034                    expr: Box::new(expr),
10035                    offset: offset.map(Box::new),
10036                    default: default.map(Box::new),
10037                }
10038            }
10039            "FIRST_VALUE" => {
10040                let mut it = args.into_iter();
10041                TypedFunction::FirstValue {
10042                    expr: Box::new(it.next()?),
10043                }
10044            }
10045            "LAST_VALUE" => {
10046                let mut it = args.into_iter();
10047                TypedFunction::LastValue {
10048                    expr: Box::new(it.next()?),
10049                }
10050            }
10051
10052            // ── Math ───────────────────────────────────────────────
10053            "ABS" => {
10054                let mut it = args.into_iter();
10055                TypedFunction::Abs {
10056                    expr: Box::new(it.next()?),
10057                }
10058            }
10059            "CEIL" | "CEILING" => {
10060                let mut it = args.into_iter();
10061                TypedFunction::Ceil {
10062                    expr: Box::new(it.next()?),
10063                }
10064            }
10065            "FLOOR" => {
10066                let mut it = args.into_iter();
10067                TypedFunction::Floor {
10068                    expr: Box::new(it.next()?),
10069                }
10070            }
10071            "ROUND" => {
10072                let mut it = args.into_iter();
10073                let expr = it.next()?;
10074                let decimals = it.next();
10075                TypedFunction::Round {
10076                    expr: Box::new(expr),
10077                    decimals: decimals.map(Box::new),
10078                }
10079            }
10080            "LOG" => {
10081                let mut it = args.into_iter();
10082                let expr = it.next()?;
10083                let base = it.next();
10084                TypedFunction::Log {
10085                    expr: Box::new(expr),
10086                    base: base.map(Box::new),
10087                }
10088            }
10089            "LN" => {
10090                let mut it = args.into_iter();
10091                TypedFunction::Ln {
10092                    expr: Box::new(it.next()?),
10093                }
10094            }
10095            "POW" | "POWER" => {
10096                let mut it = args.into_iter();
10097                let base = it.next()?;
10098                let exponent = it.next()?;
10099                TypedFunction::Pow {
10100                    base: Box::new(base),
10101                    exponent: Box::new(exponent),
10102                }
10103            }
10104            "SQRT" => {
10105                let mut it = args.into_iter();
10106                TypedFunction::Sqrt {
10107                    expr: Box::new(it.next()?),
10108                }
10109            }
10110            "GREATEST" => TypedFunction::Greatest { exprs: args },
10111            "LEAST" => TypedFunction::Least { exprs: args },
10112            "MOD" => {
10113                let mut it = args.into_iter();
10114                let left = it.next()?;
10115                let right = it.next()?;
10116                TypedFunction::Mod {
10117                    left: Box::new(left),
10118                    right: Box::new(right),
10119                }
10120            }
10121
10122            // ── Conversion ─────────────────────────────────────────
10123            "HEX" | "TO_HEX" => {
10124                let mut it = args.into_iter();
10125                TypedFunction::Hex {
10126                    expr: Box::new(it.next()?),
10127                }
10128            }
10129            "UNHEX" | "FROM_HEX" => {
10130                let mut it = args.into_iter();
10131                TypedFunction::Unhex {
10132                    expr: Box::new(it.next()?),
10133                }
10134            }
10135            "MD5" => {
10136                let mut it = args.into_iter();
10137                TypedFunction::Md5 {
10138                    expr: Box::new(it.next()?),
10139                }
10140            }
10141            "SHA" | "SHA1" => {
10142                let mut it = args.into_iter();
10143                TypedFunction::Sha {
10144                    expr: Box::new(it.next()?),
10145                }
10146            }
10147            "SHA2" | "SHA256" | "SHA512" => {
10148                let mut it = args.into_iter();
10149                let expr = it.next()?;
10150                let bit_length = it.next().unwrap_or(Expr::Number("256".to_string()));
10151                TypedFunction::Sha2 {
10152                    expr: Box::new(expr),
10153                    bit_length: Box::new(bit_length),
10154                }
10155            }
10156
10157            // Not a recognized typed function
10158            _ => return None,
10159        };
10160
10161        Some(Expr::TypedFunction {
10162            func: tf,
10163            filter: None,
10164            over: None,
10165        })
10166    }
10167
10168    /// Try to extract a DateTimeField from a column-name expression.
10169    fn expr_to_datetime_field(expr: &Expr) -> Option<DateTimeField> {
10170        match expr {
10171            Expr::Column {
10172                name, table: None, ..
10173            } => match name.to_uppercase().as_str() {
10174                "YEAR" => Some(DateTimeField::Year),
10175                "QUARTER" => Some(DateTimeField::Quarter),
10176                "MONTH" => Some(DateTimeField::Month),
10177                "WEEK" => Some(DateTimeField::Week),
10178                "DAY" => Some(DateTimeField::Day),
10179                "HOUR" => Some(DateTimeField::Hour),
10180                "MINUTE" => Some(DateTimeField::Minute),
10181                "SECOND" => Some(DateTimeField::Second),
10182                "MILLISECOND" => Some(DateTimeField::Millisecond),
10183                "MICROSECOND" => Some(DateTimeField::Microsecond),
10184                _ => None,
10185            },
10186            Expr::StringLiteral(s) | Expr::NationalStringLiteral(s) => {
10187                match s.to_uppercase().as_str() {
10188                    "YEAR" => Some(DateTimeField::Year),
10189                    "QUARTER" => Some(DateTimeField::Quarter),
10190                    "MONTH" => Some(DateTimeField::Month),
10191                    "WEEK" => Some(DateTimeField::Week),
10192                    "DAY" => Some(DateTimeField::Day),
10193                    "HOUR" => Some(DateTimeField::Hour),
10194                    "MINUTE" => Some(DateTimeField::Minute),
10195                    "SECOND" => Some(DateTimeField::Second),
10196                    "MILLISECOND" => Some(DateTimeField::Millisecond),
10197                    "MICROSECOND" => Some(DateTimeField::Microsecond),
10198                    _ => None,
10199                }
10200            }
10201            _ => None,
10202        }
10203    }
10204
10205    fn parse_case_expr(&mut self) -> Result<Expr> {
10206        self.expect(TokenType::Case)?;
10207
10208        let operand = if self.peek_type() != &TokenType::When {
10209            Some(Box::new(self.parse_expr()?))
10210        } else {
10211            None
10212        };
10213
10214        let mut when_clauses = Vec::new();
10215        while self.match_token(TokenType::When) {
10216            let condition = self.parse_expr()?;
10217            self.expect(TokenType::Then)?;
10218            let result = self.parse_expr()?;
10219            when_clauses.push((condition, result));
10220        }
10221
10222        let else_clause = if self.match_token(TokenType::Else) {
10223            Some(Box::new(self.parse_expr()?))
10224        } else {
10225            None
10226        };
10227
10228        self.expect(TokenType::End)?;
10229
10230        Ok(Expr::Case {
10231            operand,
10232            when_clauses,
10233            else_clause,
10234        })
10235    }
10236}
10237
10238#[cfg(test)]
10239mod tests {
10240    use super::*;
10241
10242    #[test]
10243    fn test_parse_simple_select() {
10244        let stmt = Parser::new("SELECT a, b FROM t")
10245            .unwrap()
10246            .parse_statement()
10247            .unwrap();
10248        match stmt {
10249            Statement::Select(sel) => {
10250                assert_eq!(sel.columns.len(), 2);
10251                assert!(sel.from.is_some());
10252            }
10253            _ => panic!("Expected SELECT"),
10254        }
10255    }
10256
10257    #[test]
10258    fn test_parse_select_with_where() {
10259        let stmt = Parser::new("SELECT x FROM t WHERE x > 10")
10260            .unwrap()
10261            .parse_statement()
10262            .unwrap();
10263        match stmt {
10264            Statement::Select(sel) => assert!(sel.where_clause.is_some()),
10265            _ => panic!("Expected SELECT"),
10266        }
10267    }
10268
10269    #[test]
10270    fn test_parse_select_wildcard() {
10271        let stmt = Parser::new("SELECT * FROM users")
10272            .unwrap()
10273            .parse_statement()
10274            .unwrap();
10275        match stmt {
10276            Statement::Select(sel) => {
10277                assert_eq!(sel.columns.len(), 1);
10278                assert!(matches!(sel.columns[0], SelectItem::Wildcard));
10279            }
10280            _ => panic!("Expected SELECT"),
10281        }
10282    }
10283
10284    #[test]
10285    fn test_parse_insert() {
10286        let stmt = Parser::new("INSERT INTO t (a, b) VALUES (1, 'hello')")
10287            .unwrap()
10288            .parse_statement()
10289            .unwrap();
10290        match stmt {
10291            Statement::Insert(ins) => {
10292                assert_eq!(ins.table.name, "t");
10293                assert_eq!(ins.columns, vec!["a", "b"]);
10294                match &ins.source {
10295                    InsertSource::Values(rows) => {
10296                        assert_eq!(rows.len(), 1);
10297                        assert_eq!(rows[0].len(), 2);
10298                    }
10299                    _ => panic!("Expected VALUES"),
10300                }
10301            }
10302            _ => panic!("Expected INSERT"),
10303        }
10304    }
10305
10306    #[test]
10307    fn test_parse_delete() {
10308        let stmt = Parser::new("DELETE FROM users WHERE id = 1")
10309            .unwrap()
10310            .parse_statement()
10311            .unwrap();
10312        match stmt {
10313            Statement::Delete(del) => {
10314                assert_eq!(del.table.name, "users");
10315                assert!(del.where_clause.is_some());
10316            }
10317            _ => panic!("Expected DELETE"),
10318        }
10319    }
10320
10321    #[test]
10322    fn test_parse_join() {
10323        let stmt = Parser::new("SELECT a.id, b.name FROM a INNER JOIN b ON a.id = b.a_id")
10324            .unwrap()
10325            .parse_statement()
10326            .unwrap();
10327        match stmt {
10328            Statement::Select(sel) => {
10329                assert_eq!(sel.joins.len(), 1);
10330                assert_eq!(sel.joins[0].join_type, JoinType::Inner);
10331            }
10332            _ => panic!("Expected SELECT"),
10333        }
10334    }
10335
10336    #[test]
10337    fn test_parse_cte() {
10338        let stmt = Parser::new("WITH cte AS (SELECT 1 AS x) SELECT x FROM cte")
10339            .unwrap()
10340            .parse_statement()
10341            .unwrap();
10342        match stmt {
10343            Statement::Select(sel) => {
10344                assert_eq!(sel.ctes.len(), 1);
10345                assert_eq!(sel.ctes[0].name, "cte");
10346            }
10347            _ => panic!("Expected SELECT"),
10348        }
10349    }
10350
10351    #[test]
10352    fn test_parse_union() {
10353        let stmt = Parser::new("SELECT 1 UNION ALL SELECT 2")
10354            .unwrap()
10355            .parse_statement()
10356            .unwrap();
10357        match stmt {
10358            Statement::SetOperation(sop) => {
10359                assert_eq!(sop.op, SetOperationType::Union);
10360                assert!(sop.all);
10361            }
10362            _ => panic!("Expected SetOperation"),
10363        }
10364    }
10365
10366    #[test]
10367    fn test_parse_cast() {
10368        let stmt = Parser::new("SELECT CAST(x AS INT) FROM t")
10369            .unwrap()
10370            .parse_statement()
10371            .unwrap();
10372        match stmt {
10373            Statement::Select(sel) => {
10374                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10375                    assert!(matches!(expr, Expr::Cast { .. }));
10376                }
10377            }
10378            _ => panic!("Expected SELECT"),
10379        }
10380    }
10381
10382    #[test]
10383    fn test_parse_subquery() {
10384        let stmt = Parser::new("SELECT * FROM (SELECT 1 AS x) AS sub")
10385            .unwrap()
10386            .parse_statement()
10387            .unwrap();
10388        match stmt {
10389            Statement::Select(sel) => {
10390                if let Some(from) = &sel.from {
10391                    assert!(matches!(from.source, TableSource::Subquery { .. }));
10392                }
10393            }
10394            _ => panic!("Expected SELECT"),
10395        }
10396    }
10397
10398    #[test]
10399    fn cr014_paren_setop_derived_table_parses() {
10400        // CR-014: a parenthesised set operation used as a derived table, where
10401        // each branch is itself parenthesised, must parse. Previously failed
10402        // with `Expected RParen, got Except/Union/Intersect`.
10403        for op in ["EXCEPT", "UNION", "UNION ALL", "INTERSECT"] {
10404            let sql = format!("SELECT count(*) FROM ((SELECT 1) {op} (SELECT 2)) x");
10405            assert!(
10406                Parser::new(&sql).unwrap().parse_statements().is_ok(),
10407                "must parse: {sql}"
10408            );
10409        }
10410    }
10411
10412    #[test]
10413    fn cr014_chained_except_derived_table_parses() {
10414        // TPC-DS q87 shape: chained EXCEPT of parenthesised branches.
10415        let sql = "SELECT count(*) FROM ((SELECT 1 AS a) EXCEPT (SELECT 2 AS a) \
10416                   EXCEPT (SELECT 3 AS a)) cool_cust";
10417        let stmt = Parser::new(sql).unwrap().parse_statement().unwrap();
10418        match stmt {
10419            Statement::Select(sel) => {
10420                let from = sel.from.expect("FROM clause present");
10421                match from.source {
10422                    TableSource::Subquery { query, alias, .. } => {
10423                        assert_eq!(alias.as_deref(), Some("cool_cust"));
10424                        assert!(matches!(*query, Statement::SetOperation(_)));
10425                    }
10426                    _ => panic!("Expected subquery derived table"),
10427                }
10428            }
10429            _ => panic!("Expected SELECT"),
10430        }
10431    }
10432
10433    #[test]
10434    fn cr014_controls_still_parse() {
10435        // Redundant nesting and no-branch-parens set-op were already OK; keep
10436        // them green. The parenthesised-join derived table must also still
10437        // parse (no regression from removing the paren-counting heuristic).
10438        for sql in [
10439            "SELECT count(*) FROM ((SELECT 1)) x",
10440            "SELECT count(*) FROM (SELECT 1 EXCEPT SELECT 2) x",
10441            "SELECT * FROM (a JOIN b ON a.id = b.id) x",
10442        ] {
10443            assert!(
10444                Parser::new(sql).unwrap().parse_statements().is_ok(),
10445                "must parse: {sql}"
10446            );
10447        }
10448    }
10449
10450    #[test]
10451    fn test_parse_exists() {
10452        let stmt = Parser::new("SELECT * FROM t WHERE EXISTS (SELECT 1 FROM t2)")
10453            .unwrap()
10454            .parse_statement()
10455            .unwrap();
10456        match stmt {
10457            Statement::Select(sel) => {
10458                assert!(sel.where_clause.is_some());
10459            }
10460            _ => panic!("Expected SELECT"),
10461        }
10462    }
10463
10464    #[test]
10465    fn test_parse_window_function() {
10466        let stmt = Parser::new(
10467            "SELECT ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) FROM emp",
10468        )
10469        .unwrap()
10470        .parse_statement()
10471        .unwrap();
10472        match stmt {
10473            Statement::Select(sel) => {
10474                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10475                    match expr {
10476                        Expr::TypedFunction { over, .. } => {
10477                            assert!(over.is_some());
10478                        }
10479                        Expr::Function { over, .. } => {
10480                            assert!(over.is_some());
10481                        }
10482                        _ => panic!("Expected function"),
10483                    }
10484                }
10485            }
10486            _ => panic!("Expected SELECT"),
10487        }
10488    }
10489
10490    #[test]
10491    fn test_parse_multiple_statements() {
10492        let stmts = Parser::new("SELECT 1; SELECT 2;")
10493            .unwrap()
10494            .parse_statements()
10495            .unwrap();
10496        assert_eq!(stmts.len(), 2);
10497    }
10498
10499    #[test]
10500    fn test_parse_insert_select() {
10501        let stmt = Parser::new("INSERT INTO t SELECT * FROM s")
10502            .unwrap()
10503            .parse_statement()
10504            .unwrap();
10505        match stmt {
10506            Statement::Insert(ins) => {
10507                assert!(matches!(ins.source, InsertSource::Query(_)));
10508            }
10509            _ => panic!("Expected INSERT"),
10510        }
10511    }
10512
10513    #[test]
10514    fn test_parse_create_table_constraints() {
10515        let stmt =
10516            Parser::new("CREATE TABLE t (id INT PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE)")
10517                .unwrap()
10518                .parse_statement()
10519                .unwrap();
10520        match stmt {
10521            Statement::CreateTable(ct) => {
10522                assert_eq!(ct.columns.len(), 2);
10523                assert!(ct.columns[0].primary_key);
10524                assert!(ct.columns[1].unique);
10525            }
10526            _ => panic!("Expected CREATE TABLE"),
10527        }
10528    }
10529
10530    #[test]
10531    fn test_parse_extract() {
10532        let stmt = Parser::new("SELECT EXTRACT(YEAR FROM created_at) FROM t")
10533            .unwrap()
10534            .parse_statement()
10535            .unwrap();
10536        match stmt {
10537            Statement::Select(sel) => {
10538                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10539                    assert!(matches!(expr, Expr::Extract { .. }));
10540                }
10541            }
10542            _ => panic!("Expected SELECT"),
10543        }
10544    }
10545
10546    #[test]
10547    fn test_parse_postgres_cast() {
10548        let stmt = Parser::new("SELECT x::int FROM t")
10549            .unwrap()
10550            .parse_statement()
10551            .unwrap();
10552        match stmt {
10553            Statement::Select(sel) => {
10554                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10555                    assert!(matches!(expr, Expr::Cast { .. }));
10556                }
10557            }
10558            _ => panic!("Expected SELECT"),
10559        }
10560    }
10561
10562    #[test]
10563    fn test_parse_on_conflict_expression_targets() {
10564        let stmt = Parser::new(
10565            "INSERT INTO t VALUES (1, 'Crowberry') ON CONFLICT (lower(fruit) collate \"C\" text_pattern_ops, key) DO NOTHING",
10566        )
10567        .unwrap()
10568        .parse_statement()
10569        .unwrap();
10570
10571        match stmt {
10572            Statement::Insert(ins) => {
10573                let on_conflict = ins.on_conflict.expect("Expected ON CONFLICT");
10574                assert_eq!(on_conflict.columns.len(), 2);
10575                assert!(on_conflict.columns[0].starts_with("lower"));
10576                assert!(on_conflict.columns[0].contains("text_pattern_ops"));
10577                assert_eq!(on_conflict.columns[1], "key");
10578            }
10579            _ => panic!("Expected INSERT"),
10580        }
10581    }
10582
10583    #[test]
10584    fn test_parse_postgres_operator_sequences() {
10585        let cases = [
10586            "SELECT * FROM box_temp WHERE f1 <<| '(10,4.33334),(5,100)'",
10587            "SELECT * FROM box_temp WHERE f1 &<| '(10,4.3333334),(5,1)'",
10588            "SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth'",
10589        ];
10590
10591        for sql in &cases {
10592            let stmt = Parser::new(sql).unwrap().parse_statement().unwrap();
10593            assert!(matches!(stmt, Statement::Select(_)));
10594        }
10595    }
10596}
10597
10598/// Attach comments to the appropriate field on a parsed statement.
10599fn attach_comments_to_statement(stmt: &mut Statement, comments: Vec<String>) {
10600    match stmt {
10601        Statement::Select(s) => s.comments = comments,
10602        Statement::Insert(s) => s.comments = comments,
10603        Statement::Update(s) => s.comments = comments,
10604        Statement::Delete(s) => s.comments = comments,
10605        Statement::CreateTable(s) => s.comments = comments,
10606        Statement::DropTable(s) => s.comments = comments,
10607        Statement::SetOperation(s) => s.comments = comments,
10608        Statement::AlterTable(s) => s.comments = comments,
10609        Statement::CreateView(s) => s.comments = comments,
10610        Statement::DropView(s) => s.comments = comments,
10611        Statement::Truncate(s) => s.comments = comments,
10612        Statement::Explain(s) => s.comments = comments,
10613        Statement::Use(s) => s.comments = comments,
10614        Statement::Merge(s) => s.comments = comments,
10615        Statement::Command(s) => s.comments = comments,
10616        // Transaction and Expression don't have comment fields
10617        Statement::Transaction(_) | Statement::Expression(_) => {}
10618    }
10619}