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 (is_max, len) = self.parse_len_or_max()?;
5501                Ok(if is_max {
5502                    DataType::VarcharMax
5503                } else {
5504                    DataType::Varchar(len)
5505                })
5506            }
5507            TokenType::Char => {
5508                self.advance();
5509                let len = self.parse_single_type_param()?;
5510                Ok(DataType::Char(len))
5511            }
5512            TokenType::Text => {
5513                self.advance();
5514                Ok(DataType::Text)
5515            }
5516            TokenType::Boolean => {
5517                self.advance();
5518                Ok(DataType::Boolean)
5519            }
5520            TokenType::Date => {
5521                self.advance();
5522                Ok(DataType::Date)
5523            }
5524            TokenType::Timestamp => {
5525                self.advance();
5526                let precision = self.parse_single_type_param()?;
5527                let with_tz = if self.match_keyword("WITH") {
5528                    let _ = self.match_keyword("LOCAL");
5529                    let _ = self.match_keyword("TIME");
5530                    let _ = self.match_keyword("ZONE");
5531                    true
5532                } else if self.match_keyword("WITHOUT") {
5533                    let _ = self.match_keyword("TIME");
5534                    let _ = self.match_keyword("ZONE");
5535                    false
5536                } else {
5537                    false
5538                };
5539                Ok(DataType::Timestamp { precision, with_tz })
5540            }
5541            TokenType::TimestampTz => {
5542                self.advance();
5543                let precision = self.parse_single_type_param()?;
5544                Ok(DataType::Timestamp {
5545                    precision,
5546                    with_tz: true,
5547                })
5548            }
5549            TokenType::Time => {
5550                self.advance();
5551                let precision = self.parse_single_type_param()?;
5552                Ok(DataType::Time { precision })
5553            }
5554            TokenType::Interval => {
5555                self.advance();
5556                Ok(DataType::Interval)
5557            }
5558            TokenType::Blob => {
5559                self.advance();
5560                Ok(DataType::Blob)
5561            }
5562            TokenType::Bytea => {
5563                self.advance();
5564                Ok(DataType::Bytea)
5565            }
5566            TokenType::Json => {
5567                self.advance();
5568                Ok(DataType::Json)
5569            }
5570            TokenType::Jsonb => {
5571                self.advance();
5572                Ok(DataType::Jsonb)
5573            }
5574            TokenType::Uuid => {
5575                self.advance();
5576                Ok(DataType::Uuid)
5577            }
5578            TokenType::Array => {
5579                self.advance();
5580                if self.match_token(TokenType::Lt) {
5581                    let inner = self.parse_data_type()?;
5582                    self.expect(TokenType::Gt)?;
5583                    Ok(DataType::Array(Some(Box::new(inner))))
5584                } else {
5585                    Ok(DataType::Array(None))
5586                }
5587            }
5588            TokenType::Struct => {
5589                self.advance();
5590                // STRUCT<a INT, b STRING> (Hive/Spark) or STRUCT(a INT, b INT) (DuckDB).
5591                // Swallow the body — we don't model named struct fields in the AST.
5592                let close = if self.match_token(TokenType::Lt) {
5593                    Some(TokenType::Gt)
5594                } else if self.match_token(TokenType::LParen) {
5595                    Some(TokenType::RParen)
5596                } else {
5597                    None
5598                };
5599                if let Some(close_tok) = close {
5600                    let mut depth = 1_i32;
5601                    while depth > 0 {
5602                        if self.peek_type() == &TokenType::Eof {
5603                            break;
5604                        }
5605                        if self.peek_type() == &close_tok {
5606                            depth -= 1;
5607                            if depth == 0 {
5608                                self.advance();
5609                                break;
5610                            }
5611                        } else if matches!(self.peek_type(), TokenType::Lt | TokenType::LParen)
5612                            && (self.peek_type() == &TokenType::Lt && close_tok == TokenType::Gt
5613                                || self.peek_type() == &TokenType::LParen
5614                                    && close_tok == TokenType::RParen)
5615                        {
5616                            depth += 1;
5617                        }
5618                        self.advance();
5619                    }
5620                }
5621                Ok(DataType::Unknown("STRUCT".to_string()))
5622            }
5623            TokenType::Map => {
5624                self.advance();
5625                let close = if self.match_token(TokenType::Lt) {
5626                    Some(TokenType::Gt)
5627                } else if self.match_token(TokenType::LParen) {
5628                    Some(TokenType::RParen)
5629                } else {
5630                    None
5631                };
5632                if let Some(close_tok) = close {
5633                    let mut depth = 1_i32;
5634                    while depth > 0 {
5635                        if self.peek_type() == &TokenType::Eof {
5636                            break;
5637                        }
5638                        if self.peek_type() == &close_tok {
5639                            depth -= 1;
5640                            if depth == 0 {
5641                                self.advance();
5642                                break;
5643                            }
5644                        } else if (self.peek_type() == &TokenType::Lt && close_tok == TokenType::Gt)
5645                            || (self.peek_type() == &TokenType::LParen
5646                                && close_tok == TokenType::RParen)
5647                        {
5648                            depth += 1;
5649                        }
5650                        self.advance();
5651                    }
5652                }
5653                Ok(DataType::Unknown("MAP".to_string()))
5654            }
5655            TokenType::Identifier => {
5656                let name = token.value.to_uppercase();
5657                self.advance();
5658                match name.as_str() {
5659                    "STRING" => Ok(DataType::String),
5660                    "NCHAR" => {
5661                        // T-SQL national fixed-length char. NCHAR has no MAX
5662                        // form, so a plain length is exact.
5663                        let len = self.parse_single_type_param()?;
5664                        Ok(DataType::NChar(len))
5665                    }
5666                    "NVARCHAR" => {
5667                        let (is_max, len) = self.parse_len_or_max()?;
5668                        Ok(if is_max {
5669                            DataType::NvarcharMax
5670                        } else {
5671                            DataType::NVarchar(len)
5672                        })
5673                    }
5674                    "VARCHAR2" => {
5675                        // Oracle's canonical variable-length string. The
5676                        // length (and optional CHAR/BYTE qualifier) MUST be
5677                        // preserved — Oracle rejects a bare `VARCHAR2` in a
5678                        // CAST (ORA-00906), the PSQ-3201 failure.
5679                        let len = self.parse_oracle_str_len()?;
5680                        Ok(DataType::Varchar2(len))
5681                    }
5682                    "NVARCHAR2" => {
5683                        let len = self.parse_oracle_str_len()?;
5684                        Ok(DataType::NVarchar2(len))
5685                    }
5686                    "BINARY" => {
5687                        let len = self.parse_single_type_param()?;
5688                        Ok(DataType::Binary(len))
5689                    }
5690                    "VARBINARY" => {
5691                        let len = self.parse_single_type_param()?;
5692                        Ok(DataType::Varbinary(len))
5693                    }
5694                    "DATETIME" => Ok(DataType::DateTime),
5695                    "BYTES" => Ok(DataType::Bytes),
5696                    "VARIANT" => Ok(DataType::Variant),
5697                    "OBJECT" => Ok(DataType::Object),
5698                    "XML" => Ok(DataType::Xml),
5699                    "INET" => Ok(DataType::Inet),
5700                    "CIDR" => Ok(DataType::Cidr),
5701                    "MACADDR" => Ok(DataType::Macaddr),
5702                    "BIT" => {
5703                        // Postgres `BIT VARYING(n)` is the same as VARBIT.
5704                        // Swallow the VARYING keyword if present and parse
5705                        // the length normally.
5706                        if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("VARYING")
5707                        {
5708                            self.advance();
5709                            let len = self.parse_single_type_param()?;
5710                            return Ok(DataType::Varbinary(len));
5711                        }
5712                        let len = self.parse_single_type_param()?;
5713                        Ok(DataType::Bit(len))
5714                    }
5715                    "MONEY" => Ok(DataType::Money),
5716                    "SERIAL" => Ok(DataType::Serial),
5717                    "BIGSERIAL" => Ok(DataType::BigSerial),
5718                    "SMALLSERIAL" => Ok(DataType::SmallSerial),
5719                    "REGCLASS" => Ok(DataType::Regclass),
5720                    "REGTYPE" => Ok(DataType::Regtype),
5721                    "HSTORE" => Ok(DataType::Hstore),
5722                    "GEOGRAPHY" => Ok(DataType::Geography),
5723                    "GEOMETRY" => Ok(DataType::Geometry),
5724                    "SUPER" => Ok(DataType::Super),
5725                    _ => Ok(DataType::Unknown(name)),
5726                }
5727            }
5728            _ => {
5729                // Fallback: accept any keyword-like token as an unknown
5730                // data type by its textual value. Covers PostgreSQL `cube`,
5731                // `lseg`, `path`, `polygon`, and any vendor-specific type
5732                // name that happens to collide with a TokenType variant.
5733                let v = token.value.clone();
5734                if !v.is_empty() && v.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
5735                    self.advance();
5736                    Ok(DataType::Unknown(v.to_uppercase()))
5737                } else {
5738                    Err(SqlglotError::ParserError {
5739                        message: format!("Expected data type, got {:?}", token.token_type),
5740                    })
5741                }
5742            }
5743        };
5744
5745        // PostgreSQL opt_array_bounds: typename[], typename[N], typename[][]...
5746        let mut dt = type_result?;
5747        while self.match_token(TokenType::LBracket) {
5748            // Consume optional integer bound (PostgreSQL ignores it but accepts it)
5749            let _ = self.match_token(TokenType::Number);
5750            self.expect(TokenType::RBracket)?;
5751            dt = DataType::Array(Some(Box::new(dt)));
5752        }
5753        // ClickHouse parameterized types: `DateTime('Asia/Dubai')`,
5754        // `Nullable(String)`, `Array(Int32)`, `Enum8('a' = 1, 'b' = 2)`,
5755        // `Decimal(9, 2)`, etc. The base type was already produced — swallow
5756        // the parenthesized parameter list so the surrounding expression
5757        // continues to parse.
5758        if self.peek_type() == &TokenType::LParen {
5759            let saved = self.pos;
5760            self.advance();
5761            let mut depth = 1;
5762            let mut ok = true;
5763            while depth > 0 {
5764                match self.peek_type() {
5765                    TokenType::LParen => depth += 1,
5766                    TokenType::RParen => {
5767                        depth -= 1;
5768                        if depth == 0 {
5769                            self.advance();
5770                            break;
5771                        }
5772                    }
5773                    TokenType::Eof => {
5774                        ok = false;
5775                        break;
5776                    }
5777                    _ => {}
5778                }
5779                self.advance();
5780            }
5781            if !ok {
5782                self.pos = saved;
5783            }
5784        }
5785        Ok(dt)
5786    }
5787
5788    fn parse_type_params(&mut self) -> Result<(Option<u32>, Option<u32>)> {
5789        if self.match_token(TokenType::LParen) {
5790            let p: Option<u32> = self.expect(TokenType::Number)?.value.parse().ok();
5791            let s = if self.match_token(TokenType::Comma) {
5792                self.expect(TokenType::Number)?.value.parse().ok()
5793            } else {
5794                None
5795            };
5796            self.expect(TokenType::RParen)?;
5797            Ok((p, s))
5798        } else {
5799            Ok((None, None))
5800        }
5801    }
5802
5803    fn parse_single_type_param(&mut self) -> Result<Option<u32>> {
5804        if self.match_token(TokenType::LParen) {
5805            // Handle TSQL MAX keyword (e.g. VARBINARY(MAX), VARCHAR(MAX))
5806            if self.check_keyword("MAX") {
5807                self.advance(); // consume MAX
5808                self.expect(TokenType::RParen)?;
5809                return Ok(None);
5810            }
5811            let n: Option<u32> = self.expect(TokenType::Number)?.value.parse().ok();
5812            self.expect(TokenType::RParen)?;
5813            Ok(n)
5814        } else {
5815            Ok(None)
5816        }
5817    }
5818
5819    /// Parse an optional `(...)` length parameter, distinguishing the T-SQL
5820    /// `MAX` sentinel from a numeric length and from an absent parameter.
5821    /// Returns `(is_max, length)`: `(true, None)` for `(MAX)`, `(false, n)`
5822    /// for `(n)`, and `(false, None)` when no parameter is present. Unlike
5823    /// [`Self::parse_single_type_param`], this does not conflate `(MAX)` with
5824    /// the bare form, so callers can preserve `VARCHAR(MAX)` / `NVARCHAR(MAX)`.
5825    fn parse_len_or_max(&mut self) -> Result<(bool, Option<u32>)> {
5826        if self.match_token(TokenType::LParen) {
5827            if self.check_keyword("MAX") {
5828                self.advance(); // consume MAX
5829                self.expect(TokenType::RParen)?;
5830                return Ok((true, None));
5831            }
5832            let n: Option<u32> = self.expect(TokenType::Number)?.value.parse().ok();
5833            self.expect(TokenType::RParen)?;
5834            Ok((false, n))
5835        } else {
5836            Ok((false, None))
5837        }
5838    }
5839
5840    /// Parse an optional Oracle string-length parameter: `(n)` or
5841    /// `(n CHAR)` / `(n BYTE)`. The numeric length is preserved; the optional
5842    /// `CHAR`/`BYTE` length-semantics qualifier is tolerated (and dropped, as
5843    /// the AST models length as a plain count). Oracle requires the length on
5844    /// `VARCHAR2`/`NVARCHAR2` in a CAST, so — unlike the generic ClickHouse
5845    /// parameterized-type swallow that discards it — it must be captured
5846    /// (PSQ-3201).
5847    fn parse_oracle_str_len(&mut self) -> Result<Option<u32>> {
5848        if self.match_token(TokenType::LParen) {
5849            let n: Option<u32> = self.expect(TokenType::Number)?.value.parse().ok();
5850            // Optional Oracle CHAR / BYTE length-semantics qualifier.
5851            if matches!(self.peek().value.to_uppercase().as_str(), "CHAR" | "BYTE") {
5852                self.advance();
5853            }
5854            self.expect(TokenType::RParen)?;
5855            Ok(n)
5856        } else {
5857            Ok(None)
5858        }
5859    }
5860
5861    // ── DROP ────────────────────────────────────────────────────────
5862
5863    fn parse_drop(&mut self) -> Result<Statement> {
5864        self.expect(TokenType::Drop)?;
5865
5866        if self.match_token(TokenType::Materialized) {
5867            self.expect(TokenType::View)?;
5868            let if_exists = if self.match_token(TokenType::If) {
5869                self.expect(TokenType::Exists)?;
5870                true
5871            } else {
5872                false
5873            };
5874            let name = self.parse_table_ref()?;
5875            // MySQL/MariaDB allow comma-list — swallow the rest.
5876            while self.match_token(TokenType::Comma) {
5877                let _ = self.parse_table_ref()?;
5878            }
5879            // Trailing CASCADE / RESTRICT.
5880            let _ = self.match_token(TokenType::Cascade) || self.match_token(TokenType::Restrict);
5881            return Ok(Statement::DropView(DropViewStatement {
5882                comments: vec![],
5883                name,
5884                if_exists,
5885                materialized: true,
5886            }));
5887        }
5888
5889        if self.match_token(TokenType::View) {
5890            let if_exists = if self.match_token(TokenType::If) {
5891                self.expect(TokenType::Exists)?;
5892                true
5893            } else {
5894                false
5895            };
5896            let name = self.parse_table_ref()?;
5897            while self.match_token(TokenType::Comma) {
5898                let _ = self.parse_table_ref()?;
5899            }
5900            let _ = self.match_token(TokenType::Cascade) || self.match_token(TokenType::Restrict);
5901            return Ok(Statement::DropView(DropViewStatement {
5902                comments: vec![],
5903                name,
5904                if_exists,
5905                materialized: false,
5906            }));
5907        }
5908
5909        // DROP <kind> ... — preserve as a Command for non-TABLE/VIEW drops
5910        // (FUNCTION, PROCEDURE, SCHEMA, DATABASE, INDEX, ROLE, USER, …).
5911        if self.peek_type() != &TokenType::Table {
5912            // Already consumed DROP; capture the remainder.
5913            let body = self.consume_raw_to_statement_end();
5914            return Ok(Statement::Command(CommandStatement {
5915                comments: vec![],
5916                kind: "DROP".to_string(),
5917                body,
5918            }));
5919        }
5920
5921        self.expect(TokenType::Table)?;
5922
5923        let if_exists = if self.match_token(TokenType::If) {
5924            self.expect(TokenType::Exists)?;
5925            true
5926        } else {
5927            false
5928        };
5929
5930        let table = self.parse_table_ref()?;
5931        // MySQL / MariaDB: `DROP TABLE [IF EXISTS] t1, t2, …`. Swallow the
5932        // extra table names so the statement parses.
5933        while self.match_token(TokenType::Comma) {
5934            let _ = self.parse_table_ref()?;
5935        }
5936        let cascade = self.match_token(TokenType::Cascade);
5937        // Tolerate Doris / StarRocks / Oracle trailing modifiers on DROP TABLE
5938        // (`FORCE`, `PURGE`, `RESTRICT`).
5939        while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon) {
5940            if self.is_name_token()
5941                && matches!(
5942                    self.peek().value.to_uppercase().as_str(),
5943                    "FORCE" | "PURGE" | "RESTRICT"
5944                )
5945            {
5946                self.advance();
5947            } else if matches!(self.peek_type(), TokenType::Restrict) {
5948                self.advance();
5949            } else {
5950                break;
5951            }
5952        }
5953
5954        Ok(Statement::DropTable(DropTableStatement {
5955            comments: vec![],
5956            if_exists,
5957            table,
5958            cascade,
5959        }))
5960    }
5961
5962    // ── ALTER TABLE ─────────────────────────────────────────────────
5963
5964    fn parse_alter_table(&mut self) -> Result<AlterTableStatement> {
5965        self.expect(TokenType::Alter)?;
5966        self.expect(TokenType::Table)?;
5967        let table = self.parse_table_ref_no_alias()?;
5968
5969        let mut actions = Vec::new();
5970        loop {
5971            let action = self.parse_alter_action()?;
5972            actions.push(action);
5973            if !self.match_token(TokenType::Comma) {
5974                break;
5975            }
5976        }
5977
5978        Ok(AlterTableStatement {
5979            comments: vec![],
5980            table,
5981            actions,
5982        })
5983    }
5984
5985    fn parse_alter_action(&mut self) -> Result<AlterTableAction> {
5986        // Hive multi-partition continuation after a comma:
5987        // `ALTER TABLE t DROP PARTITION (a), PARTITION (b)`. Swallow the
5988        // bare PARTITION clause.
5989        if self.peek_type() == &TokenType::Partition {
5990            self.advance();
5991            let mut depth: i32 = 0;
5992            while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
5993                && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
5994            {
5995                match self.peek_type() {
5996                    TokenType::LParen => depth += 1,
5997                    TokenType::RParen => depth = depth.saturating_sub(1),
5998                    _ => {}
5999                }
6000                self.advance();
6001            }
6002            return Ok(AlterTableAction::DropColumn {
6003                name: String::new(),
6004                if_exists: false,
6005            });
6006        }
6007        if self.match_keyword("ADD") {
6008            if matches!(
6009                self.peek_type(),
6010                TokenType::Constraint
6011                    | TokenType::Primary
6012                    | TokenType::Unique
6013                    | TokenType::Foreign
6014                    | TokenType::Check
6015            ) {
6016                let constraint = self.parse_table_constraint()?;
6017                self.swallow_constraint_modifiers();
6018                Ok(AlterTableAction::AddConstraint(constraint))
6019            } else if self.check_keyword("EXCLUDE") {
6020                // PG `ADD EXCLUDE [USING method] (col WITH op [, ...]) [WHERE
6021                // (predicate)] [DEFERRABLE …]` — swallow opaquely until we
6022                // hit a top-level statement boundary or comma.
6023                let mut depth: i32 = 0;
6024                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
6025                    && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
6026                {
6027                    match self.peek_type() {
6028                        TokenType::LParen => depth += 1,
6029                        TokenType::RParen => depth = depth.saturating_sub(1),
6030                        _ => {}
6031                    }
6032                    self.advance();
6033                }
6034                Ok(AlterTableAction::DropColumn {
6035                    name: String::new(),
6036                    if_exists: false,
6037                })
6038            } else if self.check_keyword("INDEX")
6039                || self.check_keyword("KEY")
6040                || self.check_keyword("PROJECTION")
6041                || self.check_keyword("STATISTICS")
6042            {
6043                // ClickHouse / MySQL `ADD INDEX [name] expr TYPE x GRANULARITY n
6044                // [AFTER y]`, `ADD KEY ...`, `ADD PROJECTION ...`. The body
6045                // is heterogeneous; swallow it opaquely up to the next
6046                // top-level Comma / Semicolon / EOF.
6047                let mut depth: i32 = 0;
6048                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
6049                    && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
6050                {
6051                    match self.peek_type() {
6052                        TokenType::LParen => depth += 1,
6053                        TokenType::RParen => depth = depth.saturating_sub(1),
6054                        _ => {}
6055                    }
6056                    self.advance();
6057                }
6058                Ok(AlterTableAction::DropColumn {
6059                    name: String::new(),
6060                    if_exists: false,
6061                })
6062            } else if self.check_keyword("COLUMNS") {
6063                // Hive / Spark / Databricks `ALTER TABLE … ADD COLUMNS
6064                // (col type [, col type]*)` or the comma-list form
6065                // `ADD COLUMNS col type, col type`. Swallow opaquely.
6066                self.advance();
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                    if depth == 0
6078                        && matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
6079                    {
6080                        break;
6081                    }
6082                }
6083                Ok(AlterTableAction::DropColumn {
6084                    name: String::new(),
6085                    if_exists: false,
6086                })
6087            } else {
6088                let _ = self.match_keyword("COLUMN");
6089                let col = self.parse_column_def()?;
6090                // ClickHouse: `ADD COLUMN name type AFTER other` / `FIRST` —
6091                // consume the placement modifier so the rest of the action
6092                // list parses.
6093                if self.check_keyword("AFTER") {
6094                    self.advance();
6095                    if self.is_name_token() {
6096                        self.advance();
6097                    }
6098                } else if self.check_keyword("FIRST") {
6099                    self.advance();
6100                }
6101                Ok(AlterTableAction::AddColumn(col))
6102            }
6103        } else if self.match_token(TokenType::Drop) {
6104            // Hive: `DROP IF EXISTS PARTITION (…), PARTITION (…)`. The
6105            // optional `IF EXISTS` precedes PARTITION.
6106            if self.peek_type() == &TokenType::If
6107                && self
6108                    .peek_offset(1)
6109                    .map(|t| matches!(t.token_type, TokenType::Exists))
6110                    .unwrap_or(false)
6111                && self
6112                    .peek_offset(2)
6113                    .map(|t| matches!(t.token_type, TokenType::Partition))
6114                    .unwrap_or(false)
6115            {
6116                self.advance(); // IF
6117                self.advance(); // EXISTS
6118            }
6119            // MySQL / TiDB: `DROP INDEX|KEY name`, `DROP PRIMARY KEY`,
6120            // `DROP FOREIGN KEY name`, `DROP CONSTRAINT name`,
6121            // `DROP PARTITION (...)`, `DROP CHECK name`. We don't have a
6122            // dedicated AST node for these, so swallow them to end-of-action.
6123            if matches!(
6124                self.peek_type(),
6125                TokenType::Index
6126                    | TokenType::Primary
6127                    | TokenType::Foreign
6128                    | TokenType::Constraint
6129                    | TokenType::Check
6130                    | TokenType::Partition
6131                    | TokenType::Unique
6132            ) || self.check_keyword("KEY")
6133                || self.check_keyword("FEATURE")
6134                || self.check_keyword("PROJECTION")
6135                || self.check_keyword("STATISTICS")
6136                || self.check_keyword("INDEX")
6137                || self.check_keyword("DISTRIBUTION")
6138            {
6139                let mut depth: i32 = 0;
6140                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
6141                    && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
6142                {
6143                    match self.peek_type() {
6144                        TokenType::LParen => depth += 1,
6145                        TokenType::RParen => depth = depth.saturating_sub(1),
6146                        _ => {}
6147                    }
6148                    self.advance();
6149                }
6150                return Ok(AlterTableAction::DropColumn {
6151                    name: String::new(),
6152                    if_exists: false,
6153                });
6154            }
6155            let _ = self.match_keyword("COLUMN");
6156            let if_exists = if self.match_token(TokenType::If) {
6157                self.expect(TokenType::Exists)?;
6158                true
6159            } else {
6160                false
6161            };
6162            let mut name = self.expect_name()?;
6163            // ClickHouse `DROP COLUMN nested.col` — accept dotted suffixes;
6164            // we collapse them into the column name string for now.
6165            while self.peek_type() == &TokenType::Dot {
6166                self.advance();
6167                if !self.is_name_token() {
6168                    break;
6169                }
6170                name.push('.');
6171                name.push_str(&self.peek().value);
6172                self.advance();
6173            }
6174            Ok(AlterTableAction::DropColumn { name, if_exists })
6175        } else if self.match_keyword("RENAME") {
6176            if self.match_keyword("COLUMN") {
6177                let old_name = self.expect_name()?;
6178                self.expect(TokenType::Identifier)?; // TO
6179                let new_name = self.expect_name()?;
6180                Ok(AlterTableAction::RenameColumn { old_name, new_name })
6181            } else if self.match_keyword("TO") {
6182                let mut new_name = self.expect_name()?;
6183                while self.match_token(TokenType::Dot) {
6184                    new_name.push('.');
6185                    new_name.push_str(&self.expect_name()?);
6186                }
6187                Ok(AlterTableAction::RenameTable { new_name })
6188            } else {
6189                Err(SqlglotError::ParserError {
6190                    message: "Expected COLUMN or TO after RENAME".into(),
6191                })
6192            }
6193        } else {
6194            Err(SqlglotError::ParserError {
6195                message: "Expected ADD, DROP, or RENAME in ALTER TABLE".into(),
6196            })
6197        }
6198    }
6199
6200    /// Try [`parse_alter_table`]; on failure, rewind and capture the entire
6201    /// `ALTER …` statement verbatim as a [`Statement::Command`]. This covers
6202    /// the long tail of vendor-specific ALTER forms — MySQL `ALTER TABLE …
6203    /// CONVERT TO CHARACTER SET … COLLATE …`, Hive `ALTER TABLE … PARTITION
6204    /// (…) COMPACT 'major'`, T-SQL `ALTER TABLE … WITH (…) CHECK CONSTRAINT
6205    /// …`, etc. (Gap 5)
6206    fn parse_alter_or_command(&mut self) -> Result<Statement> {
6207        let saved = self.pos;
6208        let saved_comments = self.pending_comments.clone();
6209        match self.parse_alter_table() {
6210            Ok(stmt) => Ok(Statement::AlterTable(stmt)),
6211            Err(_) => {
6212                self.pos = saved;
6213                self.pending_comments = saved_comments;
6214                self.parse_command_kind("ALTER")
6215            }
6216        }
6217    }
6218
6219    /// Try [`parse_create`]; on failure, rewind and capture the entire
6220    /// `CREATE …` statement verbatim as a [`Statement::Command`]. Also
6221    /// handles the `CREATE TABLE t AS VALUES (…)` form (Gap 7) and rarer
6222    /// `CREATE OPERATOR / AGGREGATE / SEQUENCE / FUNCTION / TEXT SEARCH
6223    /// CONFIGURATION / …` (Gap 4).
6224    fn parse_create_or_command(&mut self) -> Result<Statement> {
6225        let saved = self.pos;
6226        let saved_comments = self.pending_comments.clone();
6227        match self.parse_create() {
6228            Ok(stmt) => Ok(stmt),
6229            Err(_) => {
6230                self.pos = saved;
6231                self.pending_comments = saved_comments;
6232                self.parse_command_kind("CREATE")
6233            }
6234        }
6235    }
6236
6237    // ── TRUNCATE ────────────────────────────────────────────────────
6238
6239    fn parse_truncate(&mut self) -> Result<TruncateStatement> {
6240        self.expect(TokenType::Truncate)?;
6241        let _ = self.match_token(TokenType::Table);
6242        let table = self.parse_table_ref()?;
6243        Ok(TruncateStatement {
6244            comments: vec![],
6245            table,
6246        })
6247    }
6248
6249    // ── Transaction ─────────────────────────────────────────────────
6250
6251    fn parse_transaction(&mut self) -> Result<TransactionStatement> {
6252        match self.peek_type() {
6253            TokenType::Begin => {
6254                self.advance();
6255                let _ = self.match_token(TokenType::Transaction);
6256                let _ = self.match_keyword("WORK");
6257                Ok(TransactionStatement::Begin)
6258            }
6259            TokenType::Commit => {
6260                self.advance();
6261                let _ = self.match_token(TokenType::Transaction);
6262                let _ = self.match_keyword("WORK");
6263                // SQL-standard COMMIT [WORK] [AND [NO] CHAIN]
6264                if self.match_token(TokenType::And) {
6265                    let _ = self.match_token(TokenType::Not);
6266                    let _ = self.match_keyword("NO");
6267                    let _ = self.match_keyword("CHAIN");
6268                }
6269                Ok(TransactionStatement::Commit)
6270            }
6271            TokenType::Rollback => {
6272                self.advance();
6273                let _ = self.match_token(TokenType::Transaction);
6274                let _ = self.match_keyword("WORK");
6275                if self.match_keyword("TO") {
6276                    let _ = self.match_token(TokenType::Savepoint);
6277                    let name = self.expect_name()?;
6278                    Ok(TransactionStatement::RollbackTo(name))
6279                } else {
6280                    // ROLLBACK [WORK] [AND [NO] CHAIN]
6281                    if self.match_token(TokenType::And) {
6282                        let _ = self.match_token(TokenType::Not);
6283                        let _ = self.match_keyword("NO");
6284                        let _ = self.match_keyword("CHAIN");
6285                    }
6286                    Ok(TransactionStatement::Rollback)
6287                }
6288            }
6289            TokenType::Savepoint => {
6290                self.advance();
6291                let name = self.expect_name()?;
6292                Ok(TransactionStatement::Savepoint(name))
6293            }
6294            _ => Err(SqlglotError::ParserError {
6295                message: "Expected transaction statement".into(),
6296            }),
6297        }
6298    }
6299
6300    // ── EXPLAIN ─────────────────────────────────────────────────────
6301
6302    fn parse_explain(&mut self) -> Result<ExplainStatement> {
6303        self.expect(TokenType::Explain)?;
6304        let analyze = self.match_token(TokenType::Analyze);
6305        // PostgreSQL `EXPLAIN (VERBOSE, COSTS OFF, ...)` option block, plus
6306        // unparenthesized `VERBOSE` / `FORMAT TEXT|JSON|YAML`.
6307        if self.match_token(TokenType::LParen) {
6308            let mut depth = 1;
6309            while depth > 0 {
6310                match self.peek_type() {
6311                    TokenType::Eof => break,
6312                    TokenType::LParen => depth += 1,
6313                    TokenType::RParen => {
6314                        depth -= 1;
6315                        if depth == 0 {
6316                            self.advance();
6317                            break;
6318                        }
6319                    }
6320                    _ => {}
6321                }
6322                self.advance();
6323            }
6324        } else {
6325            // Optional bare keywords: VERBOSE / FORMAT [=] <name|string>
6326            loop {
6327                if self.check_keyword("VERBOSE") {
6328                    self.advance();
6329                    continue;
6330                }
6331                if self.check_keyword("FORMAT") {
6332                    self.advance();
6333                    let _ = self.match_token(TokenType::Eq);
6334                    // Format name can be an identifier (TEXT/JSON/YAML/XML/...)
6335                    // or a string literal (`'plan_tree'`).
6336                    if matches!(self.peek_type(), TokenType::String | TokenType::Identifier)
6337                        || self.is_name_token()
6338                    {
6339                        self.advance();
6340                    }
6341                    continue;
6342                }
6343                break;
6344            }
6345            // Hive / Spark EXPLAIN modifiers: EXTENDED, LOCKS, AUTHORIZATION,
6346            // DEPENDENCY, VECTORIZATION [ONLY] [SUMMARY|OPERATOR|EXPRESSION|DETAIL],
6347            // CBO, AST, REWRITE, FORMATTED, LOGICAL, NODE. Also ClickHouse
6348            // `EXPLAIN indexes=1 actions=1 …` bare options. Consume any
6349            // identifier-like tokens (and optional `= value`) until we hit a
6350            // statement-starting keyword.
6351            loop {
6352                match self.peek_type() {
6353                    TokenType::Select
6354                    | TokenType::With
6355                    | TokenType::Insert
6356                    | TokenType::Update
6357                    | TokenType::Delete
6358                    | TokenType::Merge
6359                    | TokenType::Create
6360                    | TokenType::Drop
6361                    | TokenType::Alter
6362                    | TokenType::Truncate
6363                    | TokenType::LParen
6364                    | TokenType::Eof
6365                    | TokenType::Semicolon => break,
6366                    TokenType::Identifier => {
6367                        self.advance();
6368                        if self.match_token(TokenType::Eq) {
6369                            // value: number, string, or identifier
6370                            if matches!(self.peek_type(), TokenType::Number | TokenType::String)
6371                                || self.is_name_token()
6372                            {
6373                                self.advance();
6374                            }
6375                        }
6376                        // Optional comma between options
6377                        // (ClickHouse `dump_tree = 1, dump_ast = 1 …`).
6378                        let _ = self.match_token(TokenType::Comma);
6379                    }
6380                    _ => {
6381                        // Also accept unreserved keyword-style modifiers
6382                        // (ONLY, FORMATTED, EXTENDED, etc. that tokenize as
6383                        // their own variants). Bail when we hit anything
6384                        // that isn't a plain name token.
6385                        if self.is_name_token() {
6386                            self.advance();
6387                        } else {
6388                            break;
6389                        }
6390                    }
6391                }
6392            }
6393        }
6394        let statement = self.parse_statement_inner()?;
6395        Ok(ExplainStatement {
6396            comments: vec![],
6397            analyze,
6398            statement: Box::new(statement),
6399        })
6400    }
6401
6402    // ── USE ─────────────────────────────────────────────────────────
6403
6404    fn parse_use(&mut self) -> Result<UseStatement> {
6405        self.expect(TokenType::Use)?;
6406        // Optional kind: USE DATABASE / SCHEMA / CATALOG / WAREHOUSE / ROLE
6407        // (DuckDB / Snowflake / Spark). Swallow the leading keyword.
6408        let _ = matches!(self.peek_type(), TokenType::Database | TokenType::Schema) && {
6409            self.advance();
6410            true
6411        } || (self.is_name_token()
6412            && matches!(
6413                self.peek().value.to_uppercase().as_str(),
6414                "CATALOG" | "WAREHOUSE" | "ROLE"
6415            )
6416            && {
6417                self.advance();
6418                true
6419            });
6420        // `USE default` (Hive): `default` is a keyword, accept it as a name.
6421        let mut name = if matches!(self.peek_type(), TokenType::Default) {
6422            let v = self.peek().value.clone();
6423            self.advance();
6424            v
6425        } else if self.is_name_token()
6426            && self.peek().value.eq_ignore_ascii_case("IDENTIFIER")
6427            && matches!(
6428                self.peek_offset(1).map(|t| &t.token_type),
6429                Some(TokenType::LParen)
6430            )
6431        {
6432            // Snowflake / Databricks IDENTIFIER('name') indirection —
6433            // swallow the call and use a synthetic name.
6434            self.advance(); // IDENTIFIER
6435            self.advance(); // (
6436            let mut depth: i32 = 1;
6437            while depth > 0 {
6438                match self.peek_type() {
6439                    TokenType::LParen => {
6440                        depth += 1;
6441                        self.advance();
6442                    }
6443                    TokenType::RParen => {
6444                        depth -= 1;
6445                        self.advance();
6446                    }
6447                    TokenType::Eof => break,
6448                    _ => {
6449                        self.advance();
6450                    }
6451                }
6452            }
6453            "IDENTIFIER".to_string()
6454        } else {
6455            self.expect_name()?
6456        };
6457        while self.match_token(TokenType::Dot) {
6458            name.push('.');
6459            if matches!(self.peek_type(), TokenType::Default) {
6460                name.push_str(&self.peek().value);
6461                self.advance();
6462            } else {
6463                name.push_str(&self.expect_name()?);
6464            }
6465        }
6466        Ok(UseStatement {
6467            comments: vec![],
6468            name,
6469        })
6470    }
6471
6472    // ══════════════════════════════════════════════════════════════
6473    // Expression parsing (precedence climbing)
6474    // ══════════════════════════════════════════════════════════════
6475
6476    fn parse_expr(&mut self) -> Result<Expr> {
6477        // DuckDB lambda: `lambda x: body` or `lambda x, y: body`. Lower to a
6478        // `Function("lambda", [name(s), body])` placeholder so the call parses.
6479        if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("lambda") {
6480            let saved = self.pos;
6481            self.advance();
6482            let mut names: Vec<Expr> = Vec::new();
6483            let mut ok = self.is_name_token();
6484            while ok {
6485                let n = self.advance().clone();
6486                names.push(Expr::Column {
6487                    table: None,
6488                    name: n.value.clone(),
6489                    table_quote_style: QuoteStyle::None,
6490                    quote_style: QuoteStyle::None,
6491                });
6492                if !self.match_token(TokenType::Comma) {
6493                    break;
6494                }
6495                if !self.is_name_token() {
6496                    ok = false;
6497                    break;
6498                }
6499            }
6500            if ok && self.match_token(TokenType::Colon) {
6501                let body = self.parse_expr()?;
6502                let mut args = names;
6503                args.push(body);
6504                return Ok(Expr::Function {
6505                    name: "lambda".to_string(),
6506                    args,
6507                    distinct: false,
6508                    filter: None,
6509                    over: None,
6510                    order_by: Vec::new(),
6511                    within_group: false,
6512                });
6513            }
6514            self.pos = saved;
6515        }
6516        // DuckDB / PostgreSQL named-argument prefix `name := value` and
6517        // BigQuery `name => value` — discard the name so the surrounding
6518        // function call parses. Only triggered when the lookahead clearly
6519        // matches the named-arg shape.
6520        if self.is_name_token() {
6521            let next = self.peek_offset(1).map(|t| &t.token_type);
6522            let after = self.peek_offset(2).map(|t| &t.token_type);
6523            if matches!(next, Some(TokenType::Colon)) && matches!(after, Some(TokenType::Eq)) {
6524                self.advance();
6525                self.advance();
6526                self.advance();
6527            } else if matches!(next, Some(TokenType::DoubleArrow)) {
6528                self.advance();
6529                self.advance();
6530            } else if matches!(next, Some(TokenType::Eq)) && matches!(after, Some(TokenType::Gt)) {
6531                // `name => value` tokenized as `Eq Gt` (no DoubleArrow merge).
6532                self.advance();
6533                self.advance();
6534                self.advance();
6535            }
6536        }
6537        let cond = self.parse_or_expr()?;
6538        // MySQL session-variable assignment in expression position:
6539        // `@var := expr`. Tokenized as `Colon Eq`. Lower to `BinaryOp Eq`
6540        // so the surrounding query parses.
6541        if matches!(self.peek_type(), TokenType::Colon)
6542            && matches!(
6543                self.peek_offset(1).map(|t| &t.token_type),
6544                Some(TokenType::Eq)
6545            )
6546        {
6547            self.advance();
6548            self.advance();
6549            let rhs = self.parse_expr()?;
6550            return Ok(Expr::BinaryOp {
6551                left: Box::new(cond),
6552                op: BinaryOperator::Eq,
6553                right: Box::new(rhs),
6554            });
6555        }
6556        // ClickHouse C-style ternary: `cond ? then : else`. Tokenized as
6557        // `Parameter('?')` followed later by `Colon`. Lower to a CASE.
6558        if matches!(self.peek_type(), TokenType::Parameter) && self.peek().value == "?" {
6559            self.advance();
6560            let then_branch = self.parse_or_expr()?;
6561            if self.match_token(TokenType::Colon) {
6562                let else_branch = self.parse_expr()?;
6563                return Ok(Expr::Case {
6564                    operand: None,
6565                    when_clauses: vec![(cond, then_branch)],
6566                    else_clause: Some(Box::new(else_branch)),
6567                });
6568            }
6569        }
6570        Ok(cond)
6571    }
6572
6573    fn parse_or_expr(&mut self) -> Result<Expr> {
6574        let mut left = self.parse_and_expr()?;
6575        while self.match_token(TokenType::Or) {
6576            let right = self.parse_and_expr()?;
6577            left = Expr::BinaryOp {
6578                left: Box::new(left),
6579                op: BinaryOperator::Or,
6580                right: Box::new(right),
6581            };
6582        }
6583        Ok(left)
6584    }
6585
6586    fn parse_and_expr(&mut self) -> Result<Expr> {
6587        let mut left = self.parse_not_expr()?;
6588        while self.match_token(TokenType::And) {
6589            let right = self.parse_not_expr()?;
6590            left = Expr::BinaryOp {
6591                left: Box::new(left),
6592                op: BinaryOperator::And,
6593                right: Box::new(right),
6594            };
6595        }
6596        Ok(left)
6597    }
6598
6599    fn parse_not_expr(&mut self) -> Result<Expr> {
6600        if self.match_token(TokenType::Not) {
6601            let expr = self.parse_not_expr()?;
6602            Ok(Expr::UnaryOp {
6603                op: UnaryOperator::Not,
6604                expr: Box::new(expr),
6605            })
6606        } else {
6607            self.parse_comparison()
6608        }
6609    }
6610
6611    fn parse_comparison(&mut self) -> Result<Expr> {
6612        let mut left = self.parse_addition()?;
6613
6614        loop {
6615            // ClickHouse distributed predicates: `expr GLOBAL [NOT] IN (...)`
6616            // and `expr GLOBAL JOIN ...`. The keyword tokenizes as a plain
6617            // identifier — swallow it so the following predicate parses.
6618            if self.check_keyword("GLOBAL") {
6619                let next = self.peek_offset(1).map(|t| &t.token_type);
6620                if matches!(next, Some(TokenType::In) | Some(TokenType::Not)) {
6621                    self.advance();
6622                }
6623            }
6624            // ANSI / Postgres `period1 OVERLAPS period2` — model as Eq for
6625            // acceptance purposes.
6626            if self.check_keyword("OVERLAPS") {
6627                self.advance();
6628                let right = self.parse_addition()?;
6629                left = Expr::BinaryOp {
6630                    left: Box::new(left),
6631                    op: BinaryOperator::Eq,
6632                    right: Box::new(right),
6633                };
6634                continue;
6635            }
6636            // MySQL JSON `value MEMBER OF (json_array_expr)` — model as Eq.
6637            if self.check_keyword("MEMBER")
6638                && self
6639                    .peek_offset(1)
6640                    .map(|t| t.value.eq_ignore_ascii_case("OF"))
6641                    .unwrap_or(false)
6642            {
6643                self.advance();
6644                self.advance();
6645                let right = self.parse_addition()?;
6646                left = Expr::BinaryOp {
6647                    left: Box::new(left),
6648                    op: BinaryOperator::Eq,
6649                    right: Box::new(right),
6650                };
6651                continue;
6652            }
6653            // PostgreSQL geometric and full-text operators that tokenize as
6654            // multi-character sequences our tokenizer doesn't fuse:
6655            //   `<->`  (distance)         tokens: Lt, Arrow
6656            //   `&&` `&<` `&>`            (array / range overlap)
6657            //   `@@`                      (text search match)
6658            //   `|>` `<|`                 (range left/right of)
6659            // Lower all of them to a generic Eq so the surrounding
6660            // expression parses; the bench only cares about acceptance.
6661            {
6662                let p0 = self.peek_type().clone();
6663                let p1 = self.peek_offset(1).map(|t| t.token_type.clone());
6664                let p2 = self.peek_offset(2).map(|t| t.token_type.clone());
6665                let p1v = self
6666                    .peek_offset(1)
6667                    .map(|t| t.value.clone())
6668                    .unwrap_or_default();
6669                let consume_count = match (&p0, &p1, &p2) {
6670                    // <-> distance
6671                    (TokenType::Lt, Some(TokenType::Arrow), _) => 2,
6672                    // && overlap
6673                    (TokenType::BitwiseAnd, Some(TokenType::BitwiseAnd), _) => 2,
6674                    // &<| / &>| geometric variants
6675                    (TokenType::BitwiseAnd, Some(TokenType::Lt), Some(TokenType::BitwiseOr))
6676                    | (TokenType::BitwiseAnd, Some(TokenType::Gt), Some(TokenType::BitwiseOr)) => 3,
6677                    // &< / &>
6678                    (TokenType::BitwiseAnd, Some(TokenType::Lt), _)
6679                    | (TokenType::BitwiseAnd, Some(TokenType::Gt), _) => 2,
6680                    // @@ and @?
6681                    (TokenType::AtSign, Some(TokenType::AtSign), _) => 2,
6682                    // |> and <|
6683                    (TokenType::BitwiseOr, Some(TokenType::Gt), _)
6684                    | (TokenType::Lt, Some(TokenType::BitwiseOr), _) => 2,
6685                    // <<| / >>|
6686                    (TokenType::ShiftLeft, Some(TokenType::BitwiseOr), _)
6687                    | (TokenType::ShiftRight, Some(TokenType::BitwiseOr), _) => 2,
6688                    // ^@ starts_with operator
6689                    (TokenType::BitwiseXor, Some(TokenType::AtSign), _) => 2,
6690                    _ if matches!(p0, TokenType::AtSign)
6691                        && matches!(p1, Some(TokenType::Parameter))
6692                        && p1v == "?" =>
6693                    {
6694                        2
6695                    }
6696                    _ => 0,
6697                };
6698                if consume_count > 0 {
6699                    for _ in 0..consume_count {
6700                        self.advance();
6701                    }
6702                    let right = self.parse_addition()?;
6703                    left = Expr::BinaryOp {
6704                        left: Box::new(left),
6705                        op: BinaryOperator::Eq,
6706                        right: Box::new(right),
6707                    };
6708                    continue;
6709                }
6710            }
6711            let op = match self.peek_type() {
6712                TokenType::Eq => Some(BinaryOperator::Eq),
6713                TokenType::Neq => Some(BinaryOperator::Neq),
6714                TokenType::Lt => Some(BinaryOperator::Lt),
6715                TokenType::Gt => Some(BinaryOperator::Gt),
6716                TokenType::LtEq => {
6717                    // Hive / MySQL `<=>` null-safe equality tokenizes as `Lte Gt`.
6718                    if matches!(
6719                        self.peek_offset(1).map(|t| &t.token_type),
6720                        Some(TokenType::Gt)
6721                    ) {
6722                        self.advance();
6723                        self.advance();
6724                        let right = self.parse_addition()?;
6725                        left = Expr::BinaryOp {
6726                            left: Box::new(left),
6727                            op: BinaryOperator::Eq,
6728                            right: Box::new(right),
6729                        };
6730                        continue;
6731                    }
6732                    Some(BinaryOperator::LtEq)
6733                }
6734                TokenType::GtEq => Some(BinaryOperator::GtEq),
6735                TokenType::AtArrow => Some(BinaryOperator::AtArrow),
6736                TokenType::ArrowAt => Some(BinaryOperator::ArrowAt),
6737                // PostgreSQL geometric / regex operators starting with `~`:
6738                //   ~=, ~<, ~>, ~<=, ~>=, ~~, ~~*, !~, !~*. We lower all of
6739                //   them to a generic Eq comparison so the surrounding
6740                //   expression parses; the bench only cares about acceptance.
6741                TokenType::BitwiseNot => {
6742                    self.advance();
6743                    // Optional follow-up: =, <, >, <=, >=, ~, ~*, *.
6744                    let _ = match self.peek_type() {
6745                        TokenType::Eq
6746                        | TokenType::Lt
6747                        | TokenType::Gt
6748                        | TokenType::LtEq
6749                        | TokenType::GtEq
6750                        | TokenType::Star
6751                        | TokenType::BitwiseNot => {
6752                            self.advance();
6753                            // Allow `~~*` (LIKE-like, case-insensitive).
6754                            if self.peek_type() == &TokenType::Star {
6755                                self.advance();
6756                            }
6757                            true
6758                        }
6759                        _ => false,
6760                    };
6761                    let right = self.parse_addition()?;
6762                    left = Expr::BinaryOp {
6763                        left: Box::new(left),
6764                        op: BinaryOperator::Eq,
6765                        right: Box::new(right),
6766                    };
6767                    continue;
6768                }
6769                _ => None,
6770            };
6771
6772            if let Some(op) = op {
6773                self.advance();
6774                // ClickHouse / SQLite accept `==` as a synonym for `=`.
6775                if matches!(op, BinaryOperator::Eq) && self.peek_type() == &TokenType::Eq {
6776                    self.advance();
6777                }
6778                if matches!(self.peek_type(), TokenType::Any | TokenType::Some) {
6779                    self.advance();
6780                    self.expect(TokenType::LParen)?;
6781                    let right = if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
6782                        Expr::Subquery(Box::new(self.parse_statement_inner()?))
6783                    } else {
6784                        self.parse_expr()?
6785                    };
6786                    self.expect(TokenType::RParen)?;
6787                    left = Expr::AnyOp {
6788                        expr: Box::new(left),
6789                        op,
6790                        right: Box::new(right),
6791                    };
6792                } else if self.peek_type() == &TokenType::All {
6793                    self.advance();
6794                    self.expect(TokenType::LParen)?;
6795                    let right = if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
6796                        Expr::Subquery(Box::new(self.parse_statement_inner()?))
6797                    } else {
6798                        self.parse_expr()?
6799                    };
6800                    self.expect(TokenType::RParen)?;
6801                    left = Expr::AllOp {
6802                        expr: Box::new(left),
6803                        op,
6804                        right: Box::new(right),
6805                    };
6806                } else {
6807                    let right = self.parse_addition()?;
6808                    left = Expr::BinaryOp {
6809                        left: Box::new(left),
6810                        op,
6811                        right: Box::new(right),
6812                    };
6813                }
6814            } else if self.peek_type() == &TokenType::Is {
6815                self.advance();
6816                let negated = self.match_token(TokenType::Not);
6817                if self.match_token(TokenType::True) {
6818                    left = Expr::IsBool {
6819                        expr: Box::new(left),
6820                        value: true,
6821                        negated,
6822                    };
6823                } else if self.match_token(TokenType::False) {
6824                    left = Expr::IsBool {
6825                        expr: Box::new(left),
6826                        value: false,
6827                        negated,
6828                    };
6829                } else if self.match_token(TokenType::Distinct) {
6830                    // SQL-standard `IS [NOT] DISTINCT FROM y` — null-safe
6831                    // comparison. We lower it to `(x <> y OR (x IS NULL) <>
6832                    // (y IS NULL))` for `DISTINCT FROM` (negated == false) and
6833                    // its inverse for `NOT DISTINCT FROM`. To keep the AST
6834                    // simple, model both as a binary inequality / equality
6835                    // wrapped in BinaryOp so the surrounding query parses.
6836                    self.expect(TokenType::From)?;
6837                    let right = self.parse_addition()?;
6838                    let op = if negated {
6839                        BinaryOperator::Eq
6840                    } else {
6841                        BinaryOperator::Neq
6842                    };
6843                    left = Expr::BinaryOp {
6844                        left: Box::new(left),
6845                        op,
6846                        right: Box::new(right),
6847                    };
6848                } else if matches!(self.peek_type(), TokenType::Json | TokenType::Jsonb)
6849                    || self.peek().value.eq_ignore_ascii_case("DOCUMENT")
6850                    || self.peek().value.eq_ignore_ascii_case("UNKNOWN")
6851                {
6852                    // PG / Db2 / SQL:2016 `expr IS [NOT] JSON [VALUE|ARRAY|
6853                    // OBJECT|SCALAR] [WITH|WITHOUT UNIQUE [KEYS]]`,
6854                    // `IS [NOT] DOCUMENT`, `IS [NOT] UNKNOWN`. We don't model
6855                    // these — fold to IsNull as a placeholder so the surrounding
6856                    // expression parses.
6857                    self.advance();
6858                    // Optional JSON kind keyword.
6859                    if matches!(
6860                        self.peek().value.to_uppercase().as_str(),
6861                        "VALUE" | "ARRAY" | "OBJECT" | "SCALAR"
6862                    ) && self.is_name_token()
6863                    {
6864                        self.advance();
6865                    }
6866                    // Optional `WITH|WITHOUT UNIQUE [KEYS]`.
6867                    if matches!(
6868                        self.peek().value.to_uppercase().as_str(),
6869                        "WITH" | "WITHOUT"
6870                    ) && self.is_name_token()
6871                    {
6872                        self.advance();
6873                        if self.peek().value.eq_ignore_ascii_case("UNIQUE") {
6874                            self.advance();
6875                            if self.peek().value.eq_ignore_ascii_case("KEYS") {
6876                                self.advance();
6877                            }
6878                        }
6879                    }
6880                    left = Expr::IsNull {
6881                        expr: Box::new(left),
6882                        negated,
6883                    };
6884                } else {
6885                    self.expect(TokenType::Null)?;
6886                    left = Expr::IsNull {
6887                        expr: Box::new(left),
6888                        negated,
6889                    };
6890                }
6891            } else if matches!(
6892                self.peek_type(),
6893                TokenType::Not
6894                    | TokenType::In
6895                    | TokenType::Like
6896                    | TokenType::ILike
6897                    | TokenType::Between
6898            ) {
6899                // Peek ahead: if NOT, only consume it if followed by IN/LIKE/ILIKE/BETWEEN
6900                if self.peek_type() == &TokenType::Not {
6901                    let saved_pos = self.pos;
6902                    self.advance(); // consume NOT
6903                    if !matches!(
6904                        self.peek_type(),
6905                        TokenType::In | TokenType::Like | TokenType::ILike | TokenType::Between
6906                    ) {
6907                        // NOT is not part of a comparison predicate — restore position
6908                        self.pos = saved_pos;
6909                        break;
6910                    }
6911                    // NOT was consumed, negated = true
6912                }
6913                let negated =
6914                    self.pos > 0 && self.tokens[self.pos - 1].token_type == TokenType::Not;
6915
6916                if self.match_token(TokenType::In) {
6917                    // ClickHouse: `x IN [1, 2, 3]` — array literal directly
6918                    // after IN. Parse the array as the RHS and model as a
6919                    // single-element InList so downstream code emits IN (…).
6920                    if matches!(self.peek_type(), TokenType::LBracket) {
6921                        let rhs = self.parse_primary()?;
6922                        left = Expr::InList {
6923                            expr: Box::new(left),
6924                            list: vec![rhs],
6925                            negated,
6926                        };
6927                        continue;
6928                    }
6929                    // ClickHouse: `x IN funcCall(...)` / `x IN tableName` —
6930                    // bare function call or identifier as RHS. Parse a
6931                    // single primary expression and wrap as InList.
6932                    if !matches!(self.peek_type(), TokenType::LParen) {
6933                        let rhs = self.parse_primary()?;
6934                        left = Expr::InList {
6935                            expr: Box::new(left),
6936                            list: vec![rhs],
6937                            negated,
6938                        };
6939                        continue;
6940                    }
6941                    self.expect(TokenType::LParen)?;
6942                    // Check for subquery
6943                    if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
6944                        let subquery = self.parse_statement_inner()?;
6945                        // ClickHouse accepts `IN ((SELECT ...) AS alias)`.
6946                        if self.match_token(TokenType::As) && self.is_name_token() {
6947                            self.advance();
6948                        } else if self.is_name_token() {
6949                            // also tolerate alias without AS
6950                            self.advance();
6951                        }
6952                        self.expect(TokenType::RParen)?;
6953                        left = Expr::InSubquery {
6954                            expr: Box::new(left),
6955                            subquery: Box::new(subquery),
6956                            negated,
6957                        };
6958                    } else {
6959                        let list = self.parse_expr_list()?;
6960                        self.expect(TokenType::RParen)?;
6961                        left = Expr::InList {
6962                            expr: Box::new(left),
6963                            list,
6964                            negated,
6965                        };
6966                    }
6967                } else if self.match_token(TokenType::Like) {
6968                    let pattern = self.parse_addition()?;
6969                    let escape = if self.match_token(TokenType::Escape) {
6970                        Some(Box::new(self.parse_primary()?))
6971                    } else {
6972                        None
6973                    };
6974                    left = Expr::Like {
6975                        expr: Box::new(left),
6976                        pattern: Box::new(pattern),
6977                        negated,
6978                        escape,
6979                    };
6980                } else if self.match_token(TokenType::ILike) {
6981                    let pattern = self.parse_addition()?;
6982                    let escape = if self.match_token(TokenType::Escape) {
6983                        Some(Box::new(self.parse_primary()?))
6984                    } else {
6985                        None
6986                    };
6987                    left = Expr::ILike {
6988                        expr: Box::new(left),
6989                        pattern: Box::new(pattern),
6990                        negated,
6991                        escape,
6992                    };
6993                } else if self.match_token(TokenType::Between) {
6994                    let low = self.parse_addition()?;
6995                    self.expect(TokenType::And)?;
6996                    let high = self.parse_addition()?;
6997                    left = Expr::Between {
6998                        expr: Box::new(left),
6999                        low: Box::new(low),
7000                        high: Box::new(high),
7001                        negated,
7002                    };
7003                } else {
7004                    break;
7005                }
7006            } else if self.check_keyword("SIMILAR") {
7007                // SIMILAR TO pattern [ESCAPE escape_char]
7008                self.advance(); // consume SIMILAR
7009                self.expect_keyword("TO")?;
7010                let pattern = self.parse_addition()?;
7011                let escape = if self.match_token(TokenType::Escape) {
7012                    Some(Box::new(self.parse_primary()?))
7013                } else {
7014                    None
7015                };
7016                left = Expr::SimilarTo {
7017                    expr: Box::new(left),
7018                    pattern: Box::new(pattern),
7019                    negated: false,
7020                    escape,
7021                };
7022            } else if self.peek_type() == &TokenType::Not && self.check_keyword_offset("SIMILAR", 1)
7023            {
7024                // NOT SIMILAR TO pattern [ESCAPE escape_char]
7025                self.advance(); // consume NOT
7026                self.advance(); // consume SIMILAR
7027                self.expect_keyword("TO")?;
7028                let pattern = self.parse_addition()?;
7029                let escape = if self.match_token(TokenType::Escape) {
7030                    Some(Box::new(self.parse_primary()?))
7031                } else {
7032                    None
7033                };
7034                left = Expr::SimilarTo {
7035                    expr: Box::new(left),
7036                    pattern: Box::new(pattern),
7037                    negated: true,
7038                    escape,
7039                };
7040            } else if self.check_keyword("REGEXP")
7041                || self.check_keyword("RLIKE")
7042                || self.check_keyword("GLOB")
7043                || self.check_keyword("IREGEXP")
7044            {
7045                // MySQL / Hive `expr REGEXP pat`, `expr RLIKE pat`, and
7046                // SQLite / DuckDB `expr GLOB pat`. Modeled as a Like with
7047                // no escape.
7048                self.advance();
7049                let pattern = self.parse_addition()?;
7050                left = Expr::Like {
7051                    expr: Box::new(left),
7052                    pattern: Box::new(pattern),
7053                    negated: false,
7054                    escape: None,
7055                };
7056            } else if self.peek_type() == &TokenType::Not
7057                && (self.check_keyword_offset("REGEXP", 1)
7058                    || self.check_keyword_offset("RLIKE", 1)
7059                    || self.check_keyword_offset("GLOB", 1)
7060                    || self.check_keyword_offset("IREGEXP", 1))
7061            {
7062                self.advance();
7063                self.advance();
7064                let pattern = self.parse_addition()?;
7065                left = Expr::Like {
7066                    expr: Box::new(left),
7067                    pattern: Box::new(pattern),
7068                    negated: true,
7069                    escape: None,
7070                };
7071            } else {
7072                break;
7073            }
7074        }
7075
7076        Ok(left)
7077    }
7078
7079    fn parse_addition(&mut self) -> Result<Expr> {
7080        let mut left = self.parse_multiplication()?;
7081        loop {
7082            let op = match self.peek_type() {
7083                TokenType::Plus => Some(BinaryOperator::Plus),
7084                TokenType::Minus => Some(BinaryOperator::Minus),
7085                TokenType::Concat => Some(BinaryOperator::Concat),
7086                TokenType::BitwiseOr => {
7087                    // Don't consume `|` when it is the start of `|>`; that
7088                    // is handled at comparison level (PG range/geom op).
7089                    if matches!(
7090                        self.peek_offset(1).map(|t| &t.token_type),
7091                        Some(TokenType::Gt)
7092                    ) {
7093                        None
7094                    } else {
7095                        Some(BinaryOperator::BitwiseOr)
7096                    }
7097                }
7098                TokenType::BitwiseXor => {
7099                    // Preserve PostgreSQL `^@` for comparison-level handling.
7100                    if matches!(
7101                        self.peek_offset(1).map(|t| &t.token_type),
7102                        Some(TokenType::AtSign)
7103                    ) {
7104                        None
7105                    } else {
7106                        Some(BinaryOperator::BitwiseXor)
7107                    }
7108                }
7109                TokenType::ShiftLeft => {
7110                    // Preserve PostgreSQL `<<|` for comparison-level handling.
7111                    if matches!(
7112                        self.peek_offset(1).map(|t| &t.token_type),
7113                        Some(TokenType::BitwiseOr)
7114                    ) {
7115                        None
7116                    } else {
7117                        Some(BinaryOperator::ShiftLeft)
7118                    }
7119                }
7120                TokenType::ShiftRight => {
7121                    // Preserve PostgreSQL `>>|` for comparison-level handling.
7122                    if matches!(
7123                        self.peek_offset(1).map(|t| &t.token_type),
7124                        Some(TokenType::BitwiseOr)
7125                    ) {
7126                        None
7127                    } else {
7128                        Some(BinaryOperator::ShiftRight)
7129                    }
7130                }
7131                _ => None,
7132            };
7133            if let Some(op) = op {
7134                self.advance();
7135                // Oracle SQL*Plus continuation: `2359-\n,'AR'` keeps the
7136                // trailing `-` in the token stream. If the operator has no
7137                // valid right operand (next token is a delimiter), rewind
7138                // and treat the `-` as a no-op so the surrounding INSERT /
7139                // tuple keeps parsing.
7140                if matches!(op, BinaryOperator::Minus | BinaryOperator::Plus)
7141                    && matches!(
7142                        self.peek_type(),
7143                        TokenType::Comma
7144                            | TokenType::RParen
7145                            | TokenType::RBracket
7146                            | TokenType::Eof
7147                            | TokenType::Semicolon
7148                    )
7149                {
7150                    continue;
7151                }
7152                let right = self.parse_multiplication()?;
7153                left = Expr::BinaryOp {
7154                    left: Box::new(left),
7155                    op,
7156                    right: Box::new(right),
7157                };
7158            } else {
7159                break;
7160            }
7161        }
7162        Ok(left)
7163    }
7164
7165    fn parse_multiplication(&mut self) -> Result<Expr> {
7166        let mut left = self.parse_unary()?;
7167        loop {
7168            let op = match self.peek_type() {
7169                TokenType::Star => Some(BinaryOperator::Multiply),
7170                TokenType::Slash => {
7171                    // DuckDB / Python-style integer division `//` — consume
7172                    // both slashes and lower to Divide so the surrounding
7173                    // expression parses.
7174                    if matches!(
7175                        self.peek_offset(1).map(|t| &t.token_type),
7176                        Some(TokenType::Slash)
7177                    ) {
7178                        self.advance();
7179                        self.advance();
7180                        let right = self.parse_unary()?;
7181                        left = Expr::BinaryOp {
7182                            left: Box::new(left),
7183                            op: BinaryOperator::Divide,
7184                            right: Box::new(right),
7185                        };
7186                        continue;
7187                    }
7188                    Some(BinaryOperator::Divide)
7189                }
7190                TokenType::Percent2 => Some(BinaryOperator::Modulo),
7191                TokenType::BitwiseAnd => {
7192                    // Don't consume the first `&` when it is the start of a
7193                    // multi-char PG operator (`&&`, `&<`, `&>`); leave it for
7194                    // the comparison-level handler.
7195                    if matches!(
7196                        self.peek_offset(1).map(|t| &t.token_type),
7197                        Some(TokenType::BitwiseAnd) | Some(TokenType::Lt) | Some(TokenType::Gt)
7198                    ) {
7199                        None
7200                    } else {
7201                        Some(BinaryOperator::BitwiseAnd)
7202                    }
7203                }
7204                _ => {
7205                    // MySQL / ClickHouse keyword operators `DIV` (integer
7206                    // divide) and `MOD` (modulo). Treated as multiplicative.
7207                    if self.check_keyword("DIV") {
7208                        Some(BinaryOperator::Divide)
7209                    } else if self.check_keyword("MOD") {
7210                        Some(BinaryOperator::Modulo)
7211                    } else {
7212                        None
7213                    }
7214                }
7215            };
7216            if let Some(op) = op {
7217                self.advance();
7218                let right = self.parse_unary()?;
7219                left = Expr::BinaryOp {
7220                    left: Box::new(left),
7221                    op,
7222                    right: Box::new(right),
7223                };
7224            } else {
7225                break;
7226            }
7227        }
7228        Ok(left)
7229    }
7230
7231    fn parse_unary(&mut self) -> Result<Expr> {
7232        match self.peek_type() {
7233            TokenType::Minus => {
7234                self.advance();
7235                let expr = self.parse_unary()?;
7236                Ok(Expr::UnaryOp {
7237                    op: UnaryOperator::Minus,
7238                    expr: Box::new(expr),
7239                })
7240            }
7241            TokenType::Plus => {
7242                self.advance();
7243                let expr = self.parse_unary()?;
7244                Ok(Expr::UnaryOp {
7245                    op: UnaryOperator::Plus,
7246                    expr: Box::new(expr),
7247                })
7248            }
7249            TokenType::BitwiseNot => {
7250                self.advance();
7251                let expr = self.parse_unary()?;
7252                Ok(Expr::UnaryOp {
7253                    op: UnaryOperator::BitwiseNot,
7254                    expr: Box::new(expr),
7255                })
7256            }
7257            _ => self.parse_postfix(),
7258        }
7259    }
7260
7261    /// Parse postfix operators: `::type`, `[index]`, `->`, `->>`
7262    fn parse_postfix(&mut self) -> Result<Expr> {
7263        let mut expr = self.parse_primary()?;
7264
7265        loop {
7266            if self.match_token(TokenType::DoubleColon) {
7267                // PostgreSQL-style cast: expr::type
7268                let data_type = self.parse_data_type()?;
7269                expr = Expr::Cast {
7270                    expr: Box::new(expr),
7271                    data_type,
7272                };
7273            } else if self.match_token(TokenType::LBracket) {
7274                // DuckDB list slicing: expr[start:end] or expr[:end] or expr[start:].
7275                // We model both index and slice as ArrayIndex (the slice
7276                // expression is discarded — the bench cares only about parse
7277                // acceptance).
7278                if self.match_token(TokenType::RBracket) {
7279                    // ClickHouse JSON empty subscript: `arr.k1[]` projects
7280                    // through every element. Treat as `ArrayIndex` against
7281                    // `NULL` so the surrounding expression parses.
7282                    expr = Expr::ArrayIndex {
7283                        expr: Box::new(expr),
7284                        index: Box::new(Expr::Null),
7285                    };
7286                } else if self.match_token(TokenType::Colon) {
7287                    // [:end] or [:end:step]
7288                    if !matches!(self.peek_type(), TokenType::RBracket | TokenType::Colon) {
7289                        let _ = self.parse_expr()?;
7290                    }
7291                    if self.match_token(TokenType::Colon)
7292                        && !matches!(self.peek_type(), TokenType::RBracket)
7293                    {
7294                        let _ = self.parse_expr()?;
7295                    }
7296                    self.expect(TokenType::RBracket)?;
7297                    expr = Expr::ArrayIndex {
7298                        expr: Box::new(expr),
7299                        index: Box::new(Expr::Null),
7300                    };
7301                } else {
7302                    let index = self.parse_expr()?;
7303                    if self.match_token(TokenType::Colon) {
7304                        // [start:end] / [start:] / [start:end:step] / [start::step]
7305                        if !matches!(self.peek_type(), TokenType::RBracket | TokenType::Colon) {
7306                            let _ = self.parse_expr()?;
7307                        }
7308                        if self.match_token(TokenType::Colon)
7309                            && !matches!(self.peek_type(), TokenType::RBracket)
7310                        {
7311                            let _ = self.parse_expr()?;
7312                        }
7313                    }
7314                    self.expect(TokenType::RBracket)?;
7315                    expr = Expr::ArrayIndex {
7316                        expr: Box::new(expr),
7317                        index: Box::new(index),
7318                    };
7319                }
7320            } else if self.match_token(TokenType::Arrow) {
7321                let path = self.parse_primary()?;
7322                expr = Expr::JsonAccess {
7323                    expr: Box::new(expr),
7324                    path: Box::new(path),
7325                    as_text: false,
7326                };
7327            } else if self.match_token(TokenType::DoubleArrow) {
7328                let path = self.parse_primary()?;
7329                expr = Expr::JsonAccess {
7330                    expr: Box::new(expr),
7331                    path: Box::new(path),
7332                    as_text: true,
7333                };
7334            } else if self.peek_type() == &TokenType::Colon
7335                && self
7336                    .peek_offset(1)
7337                    .map(|t| matches!(t.token_type, TokenType::Identifier))
7338                    .unwrap_or(false)
7339                && matches!(
7340                    expr,
7341                    Expr::Column { .. }
7342                        | Expr::JsonAccess { .. }
7343                        | Expr::Cast { .. }
7344                        | Expr::ArrayIndex { .. }
7345                )
7346            {
7347                // Snowflake VARIANT path accessor: `col:key`, `col:a:b`,
7348                // `col:a.b`. Treat each `:<name>` as a JSON access. We avoid
7349                // ambiguity with bind parameters (`:name`) by gating on a
7350                // preceding identifier-style expression.
7351                self.advance(); // :
7352                let part = self.advance().clone();
7353                expr = Expr::JsonAccess {
7354                    expr: Box::new(expr),
7355                    path: Box::new(Expr::StringLiteral(part.value)),
7356                    as_text: false,
7357                };
7358            } else if self.match_token(TokenType::Collate) {
7359                // Postgres / Spark `expr COLLATE collation_name` — we don't
7360                // model collations in the AST; consume the collation name
7361                // and continue. Accept any identifier-or-keyword name token.
7362                if self.is_name_token() || matches!(self.peek_type(), TokenType::String) {
7363                    self.advance();
7364                }
7365            } else if self.check_keyword("AT")
7366                && self
7367                    .peek_offset(1)
7368                    .map(|t| t.value.eq_ignore_ascii_case("TIME"))
7369                    .unwrap_or(false)
7370                && self
7371                    .peek_offset(2)
7372                    .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
7373                    .unwrap_or(false)
7374            {
7375                // PostgreSQL / DuckDB: `expr AT TIME ZONE 'tz'`. Swallow the
7376                // suffix; the timezone-shifted value attaches to `expr`.
7377                self.advance(); // AT
7378                self.advance(); // TIME
7379                self.advance(); // ZONE
7380                let _ = self.parse_primary()?;
7381            } else if self.check_keyword("EXPORT_STATE")
7382                && matches!(expr, Expr::Function { .. } | Expr::TypedFunction { .. })
7383            {
7384                // DuckDB postfix `agg(...) EXPORT_STATE` returning the
7385                // serialized aggregate state instead of its final value.
7386                self.advance();
7387            } else if self.peek_type() == &TokenType::Dot
7388                && matches!(
7389                    self.peek_offset(1).map(|t| &t.token_type),
7390                    Some(TokenType::Colon | TokenType::BitwiseXor)
7391                )
7392            {
7393                // ClickHouse typed/subobject access after complex expressions:
7394                //   `expr.:Int64`, `expr.^a`, `expr.:`Array(Nullable(Int64))``.
7395                self.advance(); // .
7396                let _ = self.match_token(TokenType::BitwiseXor);
7397                let _ = self.match_token(TokenType::Colon);
7398                if self.is_name_token()
7399                    || self.is_data_type_token()
7400                    || matches!(self.peek_type(), TokenType::Null | TokenType::Identifier)
7401                {
7402                    let part = self.advance().clone();
7403                    expr = Expr::JsonAccess {
7404                        expr: Box::new(expr),
7405                        path: Box::new(Expr::StringLiteral(part.value)),
7406                        as_text: false,
7407                    };
7408                } else {
7409                    return Err(SqlglotError::UnexpectedToken {
7410                        token: self.peek().clone(),
7411                    });
7412                }
7413            } else if self.peek_type() == &TokenType::Dot
7414                && matches!(
7415                    self.peek_offset(1).map(|t| &t.token_type),
7416                    Some(TokenType::Number)
7417                )
7418            {
7419                // ClickHouse tuple element access: `t.1`, `t[1].2`. Model as
7420                // an ArrayIndex on a numeric literal so the surrounding
7421                // expression parses.
7422                self.advance(); // .
7423                let n = self.advance().clone();
7424                expr = Expr::ArrayIndex {
7425                    expr: Box::new(expr),
7426                    index: Box::new(Expr::Number(n.value)),
7427                };
7428            } else if self.peek_type() == &TokenType::Dot
7429                && self
7430                    .peek_offset(1)
7431                    .map(|t| matches!(t.token_type, TokenType::Identifier))
7432                    .unwrap_or(false)
7433            {
7434                // Postfix field access after a non-primary expression
7435                // (e.g. `arr[].field`, `arr.k1[].k2.k3`). Also handles
7436                // DuckDB method-call style `expr.method(args)` by
7437                // rewriting to `method(expr, args)`.
7438                self.advance(); // .
7439                let part = self.advance().clone();
7440                if self.match_token(TokenType::LParen) {
7441                    let mut args = vec![expr];
7442                    if self.peek_type() != &TokenType::RParen {
7443                        args.push(self.parse_function_arg()?);
7444                        while self.match_token(TokenType::Comma) {
7445                            args.push(self.parse_function_arg()?);
7446                        }
7447                    }
7448                    self.expect(TokenType::RParen)?;
7449                    expr = Expr::Function {
7450                        name: part.value,
7451                        args,
7452                        distinct: false,
7453                        within_group: false,
7454                        order_by: vec![],
7455                        filter: None,
7456                        over: None,
7457                    };
7458                } else {
7459                    expr = Expr::JsonAccess {
7460                        expr: Box::new(expr),
7461                        path: Box::new(Expr::StringLiteral(part.value)),
7462                        as_text: false,
7463                    };
7464                }
7465            } else if matches!(expr, Expr::Function { .. })
7466                && self.peek_type() == &TokenType::LParen
7467            {
7468                // ClickHouse combinator-style application: `f(a)(b)` —
7469                // apply the result of `f(a)` to `(b)`. We model this as a
7470                // nested function call where the outer call's name is the
7471                // serialized inner function-call expression — we just pack
7472                // both arg lists into a single Function node so the parse
7473                // does not stop here.
7474                // apply the result of `f(a)` to `(b)`. We model this as a
7475                // nested function call where the outer call's name is the
7476                // serialized inner function-call expression — we just pack
7477                // both arg lists into a single Function node so the parse
7478                // does not stop here.
7479                self.advance();
7480                let extra_args = if self.peek_type() != &TokenType::RParen {
7481                    let mut a = vec![self.parse_function_arg()?];
7482                    while self.match_token(TokenType::Comma) {
7483                        a.push(self.parse_function_arg()?);
7484                    }
7485                    a
7486                } else {
7487                    vec![]
7488                };
7489                self.expect(TokenType::RParen)?;
7490                if let Expr::Function {
7491                    name,
7492                    mut args,
7493                    distinct,
7494                    filter,
7495                    over,
7496                    order_by,
7497                    within_group,
7498                } = expr
7499                {
7500                    args.extend(extra_args);
7501                    expr = Expr::Function {
7502                        name,
7503                        args,
7504                        distinct,
7505                        filter,
7506                        over,
7507                        order_by,
7508                        within_group,
7509                    };
7510                } else {
7511                    unreachable!();
7512                }
7513            } else {
7514                break;
7515            }
7516        }
7517
7518        // Check for window function: expr OVER (...)
7519        // BigQuery / DuckDB / ClickHouse / Snowflake: window-function nulls
7520        // modifier outside the call: `first_value(x) IGNORE NULLS OVER (...)`
7521        // or `first_value(x) RESPECT NULLS`. Swallow opaquely.
7522        if (self.peek().value.eq_ignore_ascii_case("IGNORE")
7523            || self.peek().value.eq_ignore_ascii_case("RESPECT"))
7524            && self
7525                .peek_offset(1)
7526                .map(|t| t.token_type == TokenType::Null || t.value.eq_ignore_ascii_case("NULLS"))
7527                .unwrap_or(false)
7528        {
7529            self.advance();
7530            self.advance();
7531        }
7532        if self.match_token(TokenType::Over) {
7533            let spec = if self.match_token(TokenType::LParen) {
7534                let ws = self.parse_window_spec()?;
7535                self.expect(TokenType::RParen)?;
7536                ws
7537            } else {
7538                // Named window reference
7539                let wref = self.expect_name()?;
7540                WindowSpec {
7541                    window_ref: Some(wref),
7542                    partition_by: vec![],
7543                    order_by: vec![],
7544                    frame: None,
7545                }
7546            };
7547            match expr {
7548                Expr::Function {
7549                    name,
7550                    args,
7551                    distinct,
7552                    filter,
7553                    order_by,
7554                    within_group,
7555                    ..
7556                } => {
7557                    expr = Expr::Function {
7558                        name,
7559                        args,
7560                        distinct,
7561                        filter,
7562                        over: Some(spec),
7563                        order_by,
7564                        within_group,
7565                    };
7566                }
7567                Expr::TypedFunction { func, filter, .. } => {
7568                    expr = Expr::TypedFunction {
7569                        func,
7570                        filter,
7571                        over: Some(spec),
7572                    };
7573                }
7574                _ => {}
7575            }
7576        }
7577
7578        // FILTER (WHERE ...) for aggregate functions
7579        if self.match_token(TokenType::Filter) {
7580            self.expect(TokenType::LParen)?;
7581            self.expect(TokenType::Where)?;
7582            let filter_expr = self.parse_expr()?;
7583            self.expect(TokenType::RParen)?;
7584            match expr {
7585                Expr::Function {
7586                    name,
7587                    args,
7588                    distinct,
7589                    over,
7590                    order_by,
7591                    within_group,
7592                    ..
7593                } => {
7594                    expr = Expr::Function {
7595                        name,
7596                        args,
7597                        distinct,
7598                        filter: Some(Box::new(filter_expr)),
7599                        over,
7600                        order_by,
7601                        within_group,
7602                    };
7603                }
7604                Expr::TypedFunction { func, over, .. } => {
7605                    expr = Expr::TypedFunction {
7606                        func,
7607                        filter: Some(Box::new(filter_expr)),
7608                        over,
7609                    };
7610                }
7611                _ => {}
7612            }
7613            // PostgreSQL / DuckDB: `agg(x) FILTER (WHERE …) OVER (…)`.
7614            // Parse the trailing OVER clause after FILTER so window-call
7615            // aggregates with filters still resolve.
7616            if self.match_token(TokenType::Over) {
7617                let spec = if self.match_token(TokenType::LParen) {
7618                    let ws = self.parse_window_spec()?;
7619                    self.expect(TokenType::RParen)?;
7620                    ws
7621                } else {
7622                    let wref = self.expect_name()?;
7623                    WindowSpec {
7624                        window_ref: Some(wref),
7625                        partition_by: vec![],
7626                        order_by: vec![],
7627                        frame: None,
7628                    }
7629                };
7630                match expr {
7631                    Expr::Function {
7632                        name,
7633                        args,
7634                        distinct,
7635                        filter,
7636                        order_by,
7637                        within_group,
7638                        ..
7639                    } => {
7640                        expr = Expr::Function {
7641                            name,
7642                            args,
7643                            distinct,
7644                            filter,
7645                            over: Some(spec),
7646                            order_by,
7647                            within_group,
7648                        };
7649                    }
7650                    Expr::TypedFunction { func, filter, .. } => {
7651                        expr = Expr::TypedFunction {
7652                            func,
7653                            filter,
7654                            over: Some(spec),
7655                        };
7656                    }
7657                    _ => {}
7658                }
7659            }
7660        }
7661
7662        Ok(expr)
7663    }
7664
7665    fn parse_window_spec(&mut self) -> Result<WindowSpec> {
7666        let window_ref = if self.is_name_token()
7667            && !matches!(
7668                self.peek_type(),
7669                TokenType::Partition | TokenType::Order | TokenType::Rows | TokenType::Range
7670            ) {
7671            let saved = self.pos;
7672            let name = self.expect_name()?;
7673            // Check if it's actually a keyword we need
7674            if matches!(
7675                self.peek_type(),
7676                TokenType::RParen
7677                    | TokenType::Partition
7678                    | TokenType::Order
7679                    | TokenType::Rows
7680                    | TokenType::Range
7681            ) {
7682                Some(name)
7683            } else {
7684                self.pos = saved;
7685                None
7686            }
7687        } else {
7688            None
7689        };
7690
7691        let partition_by = if self.match_token(TokenType::Partition) {
7692            self.expect(TokenType::By)?;
7693            self.parse_expr_list_allow_item_alias()?
7694        } else if self.is_name_token()
7695            && (self.peek().value.eq_ignore_ascii_case("DISTRIBUTE")
7696                || self.peek().value.eq_ignore_ascii_case("CLUSTER"))
7697        {
7698            // Hive `DISTRIBUTE BY` / `CLUSTER BY` inside OVER(...) — treat
7699            // as PARTITION BY.
7700            self.advance();
7701            self.expect(TokenType::By)?;
7702            self.parse_expr_list_allow_item_alias()?
7703        } else {
7704            vec![]
7705        };
7706
7707        let order_by = if self.match_token(TokenType::Order) {
7708            self.expect(TokenType::By)?;
7709            self.parse_order_by_items()?
7710        } else if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("SORT") {
7711            // Hive `SORT BY` inside OVER(...) — treat as ORDER BY.
7712            self.advance();
7713            self.expect(TokenType::By)?;
7714            self.parse_order_by_items()?
7715        } else {
7716            vec![]
7717        };
7718
7719        let frame = if matches!(self.peek_type(), TokenType::Rows | TokenType::Range) {
7720            Some(self.parse_window_frame()?)
7721        } else {
7722            None
7723        };
7724
7725        Ok(WindowSpec {
7726            window_ref,
7727            partition_by,
7728            order_by,
7729            frame,
7730        })
7731    }
7732
7733    fn parse_window_frame(&mut self) -> Result<WindowFrame> {
7734        let kind = if self.match_token(TokenType::Rows) {
7735            WindowFrameKind::Rows
7736        } else if self.match_token(TokenType::Range) {
7737            WindowFrameKind::Range
7738        } else {
7739            WindowFrameKind::Rows
7740        };
7741
7742        if self.match_keyword("BETWEEN") {
7743            let start = self.parse_window_frame_bound()?;
7744            self.expect(TokenType::And)?;
7745            let end = self.parse_window_frame_bound()?;
7746            // SQL:2011 / DuckDB frame exclusion clause:
7747            //   `EXCLUDE CURRENT ROW | EXCLUDE GROUP | EXCLUDE TIES |
7748            //    EXCLUDE NO OTHERS`. Swallow opaquely; we don't model it.
7749            if self.check_keyword("EXCLUDE") {
7750                self.advance();
7751                if self.check_keyword("CURRENT") {
7752                    self.advance();
7753                    let _ = self.match_keyword("ROW");
7754                } else if self.check_keyword("NO") {
7755                    self.advance();
7756                    let _ = self.match_keyword("OTHERS");
7757                } else if self.check_keyword("GROUP") || self.check_keyword("TIES") {
7758                    self.advance();
7759                }
7760            }
7761            Ok(WindowFrame {
7762                kind,
7763                start,
7764                end: Some(end),
7765            })
7766        } else {
7767            let start = self.parse_window_frame_bound()?;
7768            if self.check_keyword("EXCLUDE") {
7769                self.advance();
7770                if self.check_keyword("CURRENT") {
7771                    self.advance();
7772                    let _ = self.match_keyword("ROW");
7773                } else if self.check_keyword("NO") {
7774                    self.advance();
7775                    let _ = self.match_keyword("OTHERS");
7776                } else if self.check_keyword("GROUP") || self.check_keyword("TIES") {
7777                    self.advance();
7778                }
7779            }
7780            Ok(WindowFrame {
7781                kind,
7782                start,
7783                end: None,
7784            })
7785        }
7786    }
7787
7788    fn parse_window_frame_bound(&mut self) -> Result<WindowFrameBound> {
7789        if self.check_keyword("CURRENT") {
7790            self.advance();
7791            let _ = self.match_keyword("ROW");
7792            Ok(WindowFrameBound::CurrentRow)
7793        } else if self.match_token(TokenType::Unbounded) {
7794            if self.match_token(TokenType::Preceding) {
7795                Ok(WindowFrameBound::Preceding(None))
7796            } else {
7797                self.expect(TokenType::Following)?;
7798                Ok(WindowFrameBound::Following(None))
7799            }
7800        } else {
7801            let n = self.parse_expr()?;
7802            if self.match_token(TokenType::Preceding) {
7803                Ok(WindowFrameBound::Preceding(Some(Box::new(n))))
7804            } else {
7805                self.expect(TokenType::Following)?;
7806                Ok(WindowFrameBound::Following(Some(Box::new(n))))
7807            }
7808        }
7809    }
7810
7811    fn parse_primary(&mut self) -> Result<Expr> {
7812        let token = self.peek().clone();
7813
7814        // DuckDB / Spark leading-dot float literal: `.5`, `.25`. The
7815        // tokenizer emits `Dot` then `Number`; glue them back together.
7816        if matches!(token.token_type, TokenType::Dot)
7817            && matches!(
7818                self.peek_offset(1).map(|t| &t.token_type),
7819                Some(TokenType::Number)
7820            )
7821        {
7822            self.advance();
7823            let n = self.peek().value.clone();
7824            self.advance();
7825            return Ok(Expr::Number(format!("0.{}", n)));
7826        }
7827
7828        match &token.token_type {
7829            TokenType::Number => {
7830                self.advance();
7831                // Trailing-dot fractional literal: `10.` — accept the dot as
7832                // part of the number when it isn't followed by something that
7833                // would be a member access (column reference like `t.col` or
7834                // tuple element access).
7835                let mut value = token.value;
7836                if self.peek_type() == &TokenType::Dot {
7837                    let after = self.peek_offset(1).map(|t| &t.token_type);
7838                    let looks_like_member = matches!(
7839                        after,
7840                        Some(TokenType::Identifier)
7841                            | Some(TokenType::Number)
7842                            | Some(TokenType::Star)
7843                    );
7844                    if !looks_like_member {
7845                        self.advance();
7846                        value.push('.');
7847                    }
7848                }
7849                // Spark / Hive float suffixes: `10.0F`, `20L`, `3.14D`, `5BD`.
7850                // Swallow the suffix identifier so the literal parses.
7851                if self.is_name_token() {
7852                    let v = self.peek().value.as_str();
7853                    if matches!(v, "F" | "f" | "L" | "l" | "D" | "d" | "BD" | "bd") {
7854                        self.advance();
7855                    }
7856                }
7857                Ok(Expr::Number(value))
7858            }
7859            TokenType::HexString => {
7860                self.advance();
7861                Ok(Expr::Number(token.value))
7862            }
7863            TokenType::String => {
7864                self.advance();
7865                // ANSI / Oracle interval literal: `'1-2' YEAR TO MONTH`,
7866                // `'12 03:04:05.6' DAY TO SECOND(2)`. After a bare string,
7867                // accept an optional interval qualifier and swallow it so
7868                // the surrounding expression parses. Skip this when the
7869                // previous token was `INTERVAL` — that has its own path.
7870                let prev_was_interval = self
7871                    .pos
7872                    .checked_sub(2)
7873                    .and_then(|i| self.tokens.get(i))
7874                    .map(|t| matches!(t.token_type, TokenType::Interval))
7875                    .unwrap_or(false);
7876                if !prev_was_interval
7877                    && matches!(
7878                        self.peek_type(),
7879                        TokenType::Year
7880                            | TokenType::Month
7881                            | TokenType::Day
7882                            | TokenType::Hour
7883                            | TokenType::Minute
7884                            | TokenType::Second
7885                    )
7886                {
7887                    self.advance();
7888                    if self.match_token(TokenType::LParen) {
7889                        // qualifier precision: `SECOND(2)`
7890                        if matches!(self.peek_type(), TokenType::Number) {
7891                            self.advance();
7892                            if self.match_token(TokenType::Comma) {
7893                                if matches!(self.peek_type(), TokenType::Number) {
7894                                    self.advance();
7895                                }
7896                            }
7897                        }
7898                        let _ = self.match_token(TokenType::RParen);
7899                    }
7900                    if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("TO") {
7901                        self.advance();
7902                        if matches!(
7903                            self.peek_type(),
7904                            TokenType::Year
7905                                | TokenType::Month
7906                                | TokenType::Day
7907                                | TokenType::Hour
7908                                | TokenType::Minute
7909                                | TokenType::Second
7910                        ) {
7911                            self.advance();
7912                            if self.match_token(TokenType::LParen) {
7913                                if matches!(self.peek_type(), TokenType::Number) {
7914                                    self.advance();
7915                                }
7916                                let _ = self.match_token(TokenType::RParen);
7917                            }
7918                        }
7919                    }
7920                    return Ok(Expr::Cast {
7921                        expr: Box::new(Expr::StringLiteral(token.value)),
7922                        data_type: DataType::Interval,
7923                    });
7924                }
7925                // SQL-92 / MySQL: adjacent string literals concatenate
7926                // (`'a' 'b'` → `'ab'`). Also fold in identifier-quoted
7927                // strings the lexer surfaces when MySQL ANSI_QUOTES is off
7928                // (`"a" "b" "c"` reaches us as a String followed by quoted
7929                // identifiers). Greedily consume any run of immediately
7930                // following String / quoted-Identifier tokens.
7931                let mut combined = token.value;
7932                loop {
7933                    let next = self.peek();
7934                    if matches!(next.token_type, TokenType::String) {
7935                        combined.push_str(&next.value);
7936                        self.advance();
7937                        continue;
7938                    }
7939                    if matches!(next.token_type, TokenType::Identifier)
7940                        && (next.quote_char == '"' || next.quote_char == '\'')
7941                    {
7942                        combined.push_str(&next.value);
7943                        self.advance();
7944                        continue;
7945                    }
7946                    break;
7947                }
7948                Ok(Expr::StringLiteral(combined))
7949            }
7950            TokenType::NationalString => {
7951                self.advance();
7952                Ok(Expr::NationalStringLiteral(token.value))
7953            }
7954            TokenType::True => {
7955                self.advance();
7956                Ok(Expr::Boolean(true))
7957            }
7958            TokenType::False => {
7959                self.advance();
7960                Ok(Expr::Boolean(false))
7961            }
7962            TokenType::Null => {
7963                self.advance();
7964                Ok(Expr::Null)
7965            }
7966            TokenType::Default => {
7967                self.advance();
7968                // MySQL `DEFAULT(col)` — emit as function call so the
7969                // surrounding tuple parses.
7970                if self.peek_type() == &TokenType::LParen {
7971                    self.advance();
7972                    let args = if self.peek_type() != &TokenType::RParen {
7973                        let mut a = vec![self.parse_function_arg()?];
7974                        while self.match_token(TokenType::Comma) {
7975                            a.push(self.parse_function_arg()?);
7976                        }
7977                        a
7978                    } else {
7979                        vec![]
7980                    };
7981                    self.expect(TokenType::RParen)?;
7982                    return Ok(Expr::Function {
7983                        name: "DEFAULT".to_string(),
7984                        args,
7985                        distinct: false,
7986                        filter: None,
7987                        over: None,
7988                        order_by: Vec::new(),
7989                        within_group: false,
7990                    });
7991                }
7992                Ok(Expr::Default)
7993            }
7994            TokenType::Star => {
7995                self.advance();
7996                Ok(Expr::Wildcard)
7997            }
7998            // ClickHouse / various: `values` used as a column name inside
7999            // expressions (e.g. `arrayExists(x -> x > 5, values)`). Accept
8000            // it as a bare column reference when it isn't followed by `(`.
8001            TokenType::Values
8002                if self.peek_offset(1).map(|t| &t.token_type) != Some(&TokenType::LParen) =>
8003            {
8004                self.advance();
8005                Ok(Expr::Column {
8006                    table: None,
8007                    name: token.value,
8008                    quote_style: QuoteStyle::None,
8009                    table_quote_style: QuoteStyle::None,
8010                })
8011            }
8012            TokenType::Parameter => {
8013                self.advance();
8014                Ok(Expr::Parameter(token.value))
8015            }
8016
8017            // ── `@var`, `@@global_var`, `:var` style placeholders ──
8018            //
8019            // MySQL/T-SQL session and global variables tokenize as a bare
8020            // `@` (or `:`) followed by an identifier. We glue the prefix and
8021            // following name into a single `Parameter` expression so the
8022            // surrounding query parses.
8023            TokenType::AtSign | TokenType::Colon => {
8024                self.advance();
8025                let mut name = match token.token_type {
8026                    TokenType::AtSign => String::from("@"),
8027                    TokenType::Colon => String::from(":"),
8028                    _ => unreachable!(),
8029                };
8030                // T-SQL `@@global` — second `@`.
8031                if matches!(token.token_type, TokenType::AtSign)
8032                    && self.peek_type() == &TokenType::AtSign
8033                {
8034                    name.push('@');
8035                    self.advance();
8036                }
8037                // Name part: identifier-or-keyword, number, or none.
8038                // T-SQL accepts reserved keywords after `@` (e.g. `@limit`,
8039                // `@order`). Accept any token that "looks like" a name.
8040                if self.is_name_token()
8041                    || matches!(
8042                        self.peek_type(),
8043                        TokenType::Limit
8044                            | TokenType::Offset
8045                            | TokenType::Order
8046                            | TokenType::Group
8047                            | TokenType::Having
8048                            | TokenType::Where
8049                            | TokenType::From
8050                            | TokenType::Select
8051                            | TokenType::Insert
8052                            | TokenType::Update
8053                            | TokenType::Delete
8054                            | TokenType::Union
8055                            | TokenType::Intersect
8056                            | TokenType::Except
8057                            | TokenType::Join
8058                            | TokenType::Inner
8059                            | TokenType::Cross
8060                            | TokenType::On
8061                            | TokenType::As
8062                            | TokenType::Distinct
8063                            | TokenType::Default
8064                            | TokenType::Null
8065                            | TokenType::True
8066                            | TokenType::False
8067                            | TokenType::Date
8068                            | TokenType::Time
8069                            | TokenType::Timestamp
8070                            | TokenType::Year
8071                            | TokenType::Month
8072                            | TokenType::Day
8073                            | TokenType::Hour
8074                            | TokenType::Minute
8075                            | TokenType::Second
8076                    )
8077                {
8078                    let nt = self.advance().clone();
8079                    name.push_str(&nt.value);
8080                } else if matches!(self.peek_type(), TokenType::Number | TokenType::Int) {
8081                    let nt = self.advance().clone();
8082                    name.push_str(&nt.value);
8083                }
8084                Ok(Expr::Parameter(name))
8085            }
8086
8087            // ── DuckDB / BigQuery struct literal: `{ key: expr, ... }` ──
8088            //
8089            // We capture the values as positional `STRUCT(...)` arguments
8090            // (keys are syntactically optional). This keeps surrounding
8091            // expressions parseable; the original AST shape is not preserved
8092            // because there is no dedicated struct-literal variant yet.
8093            TokenType::LBrace => {
8094                self.advance();
8095                let mut args = Vec::new();
8096                if self.peek_type() != &TokenType::RBrace {
8097                    loop {
8098                        // Optional `key:` prefix — discard the key, keep value.
8099                        if self.is_name_token()
8100                            && self
8101                                .peek_offset(1)
8102                                .is_some_and(|t| t.token_type == TokenType::Colon)
8103                        {
8104                            self.advance(); // key
8105                            self.advance(); // colon
8106                        } else if self.peek_type() == &TokenType::String
8107                            && self
8108                                .peek_offset(1)
8109                                .is_some_and(|t| t.token_type == TokenType::Colon)
8110                        {
8111                            self.advance(); // string key
8112                            self.advance(); // colon
8113                        }
8114                        let value = self.parse_expr()?;
8115                        args.push(value);
8116                        if !self.match_token(TokenType::Comma) {
8117                            break;
8118                        }
8119                    }
8120                }
8121                self.expect(TokenType::RBrace)?;
8122                Ok(Expr::Function {
8123                    name: "STRUCT".to_string(),
8124                    args,
8125                    distinct: false,
8126                    filter: None,
8127                    over: None,
8128                    order_by: Vec::new(),
8129                    within_group: false,
8130                })
8131            }
8132
8133            // ── CAST ────────────────────────────────────────────────
8134            TokenType::Cast
8135                if self
8136                    .peek_offset(1)
8137                    .is_some_and(|t| t.token_type == TokenType::LParen) =>
8138            {
8139                self.advance();
8140                self.expect(TokenType::LParen)?;
8141                let expr = self.parse_expr()?;
8142                // Standard form: `CAST(expr AS type)`. ClickHouse also accepts
8143                // `CAST(expr, 'TypeName')` with a string literal type.
8144                let data_type = if self.match_token(TokenType::As) {
8145                    self.parse_data_type()?
8146                } else if self.match_token(TokenType::Comma) {
8147                    if matches!(self.peek_type(), TokenType::String) {
8148                        let s = self.peek().value.clone();
8149                        self.advance();
8150                        DataType::Unknown(s)
8151                    } else {
8152                        self.parse_data_type()?
8153                    }
8154                } else {
8155                    self.expect(TokenType::As)?; // produce the canonical error
8156                    self.parse_data_type()?
8157                };
8158                // BigQuery: `CAST(expr AS type FORMAT 'fmt' [AT TIME ZONE …])`.
8159                if self.check_keyword("FORMAT") {
8160                    self.advance();
8161                    let _ = self.parse_expr();
8162                    if self.check_keyword("AT")
8163                        && self
8164                            .peek_offset(1)
8165                            .map(|t| t.value.eq_ignore_ascii_case("TIME"))
8166                            .unwrap_or(false)
8167                        && self
8168                            .peek_offset(2)
8169                            .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8170                            .unwrap_or(false)
8171                    {
8172                        self.advance();
8173                        self.advance();
8174                        self.advance();
8175                        let _ = self.parse_expr();
8176                    }
8177                }
8178                self.expect(TokenType::RParen)?;
8179                Ok(Expr::Cast {
8180                    expr: Box::new(expr),
8181                    data_type,
8182                })
8183            }
8184
8185            // ── EXTRACT ─────────────────────────────────────────────
8186            TokenType::Extract => {
8187                self.advance();
8188                self.expect(TokenType::LParen)?;
8189                let field = self.parse_datetime_field()?;
8190                self.expect(TokenType::From)?;
8191                let expr = self.parse_expr()?;
8192                // BigQuery: `EXTRACT(field FROM ts AT TIME ZONE 'tz')`.
8193                // Swallow the trailing timezone clause so the function
8194                // parses; we lose the explicit zone but keep the AST.
8195                if self.check_keyword("AT")
8196                    && self
8197                        .peek_offset(1)
8198                        .map(|t| t.value.eq_ignore_ascii_case("TIME"))
8199                        .unwrap_or(false)
8200                    && self
8201                        .peek_offset(2)
8202                        .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8203                        .unwrap_or(false)
8204                {
8205                    self.advance(); // AT
8206                    self.advance(); // TIME
8207                    self.advance(); // ZONE
8208                    let _ = self.parse_expr();
8209                }
8210                self.expect(TokenType::RParen)?;
8211                Ok(Expr::Extract {
8212                    field,
8213                    expr: Box::new(expr),
8214                })
8215            }
8216
8217            // ── CASE ────────────────────────────────────────────────
8218            TokenType::Case => self.parse_case_expr(),
8219
8220            // ── EXISTS ──────────────────────────────────────────────
8221            TokenType::Exists => {
8222                self.advance();
8223                self.expect(TokenType::LParen)?;
8224                let subquery = self.parse_statement_inner()?;
8225                self.expect(TokenType::RParen)?;
8226                Ok(Expr::Exists {
8227                    subquery: Box::new(subquery),
8228                    negated: false,
8229                })
8230            }
8231
8232            // ── NOT EXISTS ──────────────────────────────────────────
8233            TokenType::Not
8234                if {
8235                    let next_pos = self.pos + 1;
8236                    next_pos < self.tokens.len()
8237                        && self.tokens[next_pos].token_type == TokenType::Exists
8238                } =>
8239            {
8240                self.advance(); // NOT
8241                self.advance(); // EXISTS
8242                self.expect(TokenType::LParen)?;
8243                let subquery = self.parse_statement_inner()?;
8244                self.expect(TokenType::RParen)?;
8245                Ok(Expr::Exists {
8246                    subquery: Box::new(subquery),
8247                    negated: true,
8248                })
8249            }
8250
8251            // ── INTERVAL ────────────────────────────────────────────
8252            TokenType::Interval => {
8253                self.advance();
8254                // ClickHouse accepts arithmetic in the value position
8255                // (e.g. `INTERVAL number - 15 MONTH`). Parse an additive
8256                // expression instead of a single primary so the trailing
8257                // unit keyword is reached cleanly.
8258                let value = self.parse_addition()?;
8259                let unit = self.try_parse_datetime_field();
8260                // ANSI / Spark composite ranges: `INTERVAL '0-0' YEAR TO MONTH`,
8261                // `INTERVAL '15:40' HOUR TO MINUTE` etc. Swallow the trailing
8262                // `TO <unit>` clause; we keep only the leading unit.
8263                if self.check_keyword("TO") {
8264                    let saved = self.pos;
8265                    self.advance();
8266                    if self.try_parse_datetime_field().is_none() {
8267                        self.pos = saved;
8268                    }
8269                }
8270                // PostgreSQL fractional precision on the trailing unit:
8271                //   `INTERVAL '1.234' SECOND(2)`, `INTERVAL '…' MINUTE TO SECOND(2)`.
8272                // Swallow the `(N)` after the unit.
8273                if self.peek_type() == &TokenType::LParen
8274                    && self
8275                        .peek_offset(1)
8276                        .map(|t| matches!(t.token_type, TokenType::Number))
8277                        .unwrap_or(false)
8278                    && self
8279                        .peek_offset(2)
8280                        .map(|t| matches!(t.token_type, TokenType::RParen))
8281                        .unwrap_or(false)
8282                {
8283                    self.advance();
8284                    self.advance();
8285                    self.advance();
8286                }
8287                Ok(Expr::Interval {
8288                    value: Box::new(value),
8289                    unit,
8290                })
8291            }
8292
8293            // ── Parenthesized expression or subquery ────────────────
8294            TokenType::LParen => {
8295                self.advance();
8296                // Check for subquery
8297                if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
8298                    let subquery = self.parse_statement_inner()?;
8299                    self.expect(TokenType::RParen)?;
8300                    Ok(Expr::Subquery(Box::new(subquery)))
8301                } else {
8302                    let expr = self.parse_expr()?;
8303                    // ClickHouse: `(expr AS alias)` — swallow the alias.
8304                    if self.match_token(TokenType::As) && self.is_name_token() {
8305                        self.advance();
8306                    }
8307                    // Tuple: (a, b, c) — also accept ClickHouse trailing
8308                    // comma `(a,)`, `(a, b,)`.
8309                    if self.match_token(TokenType::Comma) {
8310                        let mut items = vec![expr];
8311                        if self.peek_type() == &TokenType::RParen {
8312                            self.advance();
8313                            return Ok(Expr::Tuple(items));
8314                        }
8315                        let next = self.parse_expr()?;
8316                        if self.match_token(TokenType::As) && self.is_name_token() {
8317                            self.advance();
8318                        }
8319                        items.push(next);
8320                        while self.match_token(TokenType::Comma) {
8321                            if self.peek_type() == &TokenType::RParen {
8322                                break;
8323                            }
8324                            let n = self.parse_expr()?;
8325                            if self.match_token(TokenType::As) && self.is_name_token() {
8326                                self.advance();
8327                            }
8328                            items.push(n);
8329                        }
8330                        self.expect(TokenType::RParen)?;
8331                        Ok(Expr::Tuple(items))
8332                    } else {
8333                        self.expect(TokenType::RParen)?;
8334                        Ok(Expr::Nested(Box::new(expr)))
8335                    }
8336                }
8337            }
8338
8339            // ── DuckDB MAP literal: `MAP { 'k': v, ... }` ──────────
8340            // Captured as a `MAP(...)` function call with the values as
8341            // positional arguments; keys are discarded for now.
8342            TokenType::Map
8343                if self
8344                    .peek_offset(1)
8345                    .map(|t| matches!(t.token_type, TokenType::LBrace))
8346                    .unwrap_or(false) =>
8347            {
8348                self.advance(); // MAP
8349                self.advance(); // {
8350                let mut args = Vec::new();
8351                if self.peek_type() != &TokenType::RBrace {
8352                    loop {
8353                        // Optional `key:` prefix — keep the value only.
8354                        let saved = self.pos;
8355                        let _ = self.parse_expr()?;
8356                        if self.match_token(TokenType::Colon) {
8357                            let v = self.parse_expr()?;
8358                            args.push(v);
8359                        } else {
8360                            self.pos = saved;
8361                            let v = self.parse_expr()?;
8362                            args.push(v);
8363                        }
8364                        if !self.match_token(TokenType::Comma) {
8365                            break;
8366                        }
8367                    }
8368                }
8369                self.expect(TokenType::RBrace)?;
8370                Ok(Expr::Function {
8371                    name: "MAP".to_string(),
8372                    args,
8373                    distinct: false,
8374                    filter: None,
8375                    over: None,
8376                    order_by: Vec::new(),
8377                    within_group: false,
8378                })
8379            }
8380
8381            // ── Array literal: ARRAY[...] ──────────────────────────
8382            TokenType::Array => {
8383                self.advance();
8384                if self.match_token(TokenType::LBracket) {
8385                    let items = self.parse_array_items(TokenType::RBracket)?;
8386                    self.expect(TokenType::RBracket)?;
8387                    Ok(Expr::ArrayLiteral(items))
8388                } else if self.match_token(TokenType::LParen) {
8389                    // ARRAY(SELECT ...) for subqueries, or Hive
8390                    // `ARRAY(expr, expr, ...)` for inline array literals.
8391                    if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
8392                        let subquery = self.parse_statement_inner()?;
8393                        self.expect(TokenType::RParen)?;
8394                        Ok(Expr::Subquery(Box::new(subquery)))
8395                    } else {
8396                        let items = self.parse_array_items(TokenType::RParen)?;
8397                        self.expect(TokenType::RParen)?;
8398                        Ok(Expr::ArrayLiteral(items))
8399                    }
8400                } else {
8401                    Ok(Expr::Column {
8402                        table: None,
8403                        name: "ARRAY".to_string(),
8404                        quote_style: QuoteStyle::None,
8405                        table_quote_style: QuoteStyle::None,
8406                    })
8407                }
8408            }
8409
8410            // ── Bracket array literal: [...] ────────────────────────
8411            TokenType::LBracket => {
8412                self.advance();
8413                let items = self.parse_array_items(TokenType::RBracket)?;
8414                // DuckDB list comprehension: `[expr FOR x IN list [IF cond]]`.
8415                // Swallow the comprehension tail opaquely; we keep the
8416                // initial expression as the AST representation.
8417                if self.peek().value.eq_ignore_ascii_case("FOR") {
8418                    let mut depth = 1_i32;
8419                    while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
8420                        match self.peek_type() {
8421                            TokenType::LBracket | TokenType::LParen => depth += 1,
8422                            TokenType::RBracket => {
8423                                depth -= 1;
8424                                if depth == 0 {
8425                                    break;
8426                                }
8427                            }
8428                            TokenType::RParen => depth -= 1,
8429                            _ => {}
8430                        }
8431                        self.advance();
8432                    }
8433                }
8434                self.expect(TokenType::RBracket)?;
8435                Ok(Expr::ArrayLiteral(items))
8436            }
8437
8438            // ── Identifier: column ref, function call, or qualified name ─
8439            _ if self.is_name_token() || self.is_data_type_token() => {
8440                let name_token = self.advance().clone();
8441                let name = name_token.value.clone();
8442                let name_qs = quote_style_from_char(name_token.quote_char);
8443
8444                // ── ANSI typed string literals: DATE 'x', TIMESTAMP 'x', TIME 'x' ──
8445                if matches!(
8446                    name_token.token_type,
8447                    TokenType::Date
8448                        | TokenType::Timestamp
8449                        | TokenType::TimestampTz
8450                        | TokenType::Time
8451                ) {
8452                    // PG / ANSI `TIMESTAMP [WITH [LOCAL] TIME ZONE] 'lit'`
8453                    // and `TIMESTAMP WITHOUT TIME ZONE 'lit'`. Swallow the
8454                    // optional timezone modifier so the string literal
8455                    // attaches to the right typed-literal form.
8456                    let mut explicit_tz: Option<bool> = None;
8457                    if matches!(
8458                        name_token.token_type,
8459                        TokenType::Timestamp | TokenType::Time
8460                    ) && self.peek_type() == &TokenType::With
8461                    {
8462                        let saved = self.pos;
8463                        self.advance(); // WITH
8464                        let _ = self.match_keyword("LOCAL");
8465                        if self.check_keyword("TIME")
8466                            && self
8467                                .peek_offset(1)
8468                                .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8469                                .unwrap_or(false)
8470                        {
8471                            self.advance(); // TIME
8472                            self.advance(); // ZONE
8473                            explicit_tz = Some(true);
8474                        } else {
8475                            self.pos = saved;
8476                        }
8477                    } else if matches!(
8478                        name_token.token_type,
8479                        TokenType::Timestamp | TokenType::Time
8480                    ) && self.check_keyword("WITHOUT")
8481                    {
8482                        let saved = self.pos;
8483                        self.advance(); // WITHOUT
8484                        if self.check_keyword("TIME")
8485                            && self
8486                                .peek_offset(1)
8487                                .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8488                                .unwrap_or(false)
8489                        {
8490                            self.advance();
8491                            self.advance();
8492                            explicit_tz = Some(false);
8493                        } else {
8494                            self.pos = saved;
8495                        }
8496                    }
8497
8498                    if self.peek_type() == &TokenType::String {
8499                        let value_token = self.advance().clone();
8500                        let data_type = match name_token.token_type {
8501                            TokenType::Date => DataType::Date,
8502                            TokenType::Timestamp => DataType::Timestamp {
8503                                precision: None,
8504                                with_tz: explicit_tz.unwrap_or(false),
8505                            },
8506                            TokenType::TimestampTz => DataType::Timestamp {
8507                                precision: None,
8508                                with_tz: true,
8509                            },
8510                            TokenType::Time => DataType::Time { precision: None },
8511                            _ => unreachable!(),
8512                        };
8513                        return Ok(Expr::Cast {
8514                            expr: Box::new(Expr::StringLiteral(value_token.value)),
8515                            data_type,
8516                        });
8517                    }
8518                }
8519
8520                // ── ANSI / PG generic typed string literal: `TYPE 'lit'` ──
8521                // (e.g. `bool 'true'`, `int4 '42'`, `varchar 'x'`). When the
8522                // current token is a data-type keyword (not already handled
8523                // above) and a String literal follows, fold the pair into a
8524                // Cast so the surrounding expression parses.
8525                if self.is_data_type_token_kind(&name_token.token_type)
8526                    && self.peek_type() == &TokenType::String
8527                {
8528                    let value_token = self.advance().clone();
8529                    let data_type = match name_token.token_type {
8530                        TokenType::Boolean => DataType::Boolean,
8531                        TokenType::Int | TokenType::Integer => DataType::Int,
8532                        TokenType::BigInt => DataType::BigInt,
8533                        TokenType::SmallInt => DataType::SmallInt,
8534                        TokenType::TinyInt => DataType::TinyInt,
8535                        TokenType::Float => DataType::Float,
8536                        TokenType::Double => DataType::Double,
8537                        TokenType::Real => DataType::Real,
8538                        TokenType::Decimal => DataType::Decimal {
8539                            precision: None,
8540                            scale: None,
8541                        },
8542                        TokenType::Numeric => DataType::Numeric {
8543                            precision: None,
8544                            scale: None,
8545                        },
8546                        TokenType::Varchar => DataType::Varchar(None),
8547                        TokenType::Char => DataType::Char(None),
8548                        TokenType::Text => DataType::Text,
8549                        TokenType::Json => DataType::Json,
8550                        TokenType::Jsonb => DataType::Jsonb,
8551                        TokenType::Uuid => DataType::Uuid,
8552                        TokenType::Bytea => DataType::Bytea,
8553                        TokenType::Blob => DataType::Blob,
8554                        _ => DataType::Unknown(name.clone()),
8555                    };
8556                    return Ok(Expr::Cast {
8557                        expr: Box::new(Expr::StringLiteral(value_token.value)),
8558                        data_type,
8559                    });
8560                }
8561
8562                // PostgreSQL geometric / network / OID type aliases used as
8563                // typed-literal prefixes (e.g. `box '(1,2,3,4)'`,
8564                // `point '(1,2)'`, `inet '127.0.0.1'`). Recognize a curated
8565                // list of bare identifiers followed by a String literal and
8566                // fold the pair into a Cast(Unknown(name)).
8567                if name_qs == QuoteStyle::None
8568                    && self.peek_type() == &TokenType::String
8569                    && matches!(
8570                        name.to_ascii_lowercase().as_str(),
8571                        "box"
8572                            | "point"
8573                            | "circle"
8574                            | "line"
8575                            | "lseg"
8576                            | "path"
8577                            | "polygon"
8578                            | "inet"
8579                            | "cidr"
8580                            | "macaddr"
8581                            | "macaddr8"
8582                            | "money"
8583                            | "regclass"
8584                            | "regtype"
8585                            | "regproc"
8586                            | "regprocedure"
8587                            | "regrole"
8588                            | "regnamespace"
8589                            | "regoperator"
8590                            | "regoper"
8591                            | "oid"
8592                            | "xml"
8593                            | "tsvector"
8594                            | "tsquery"
8595                            | "jsonpath"
8596                            | "name"
8597                            | "bit"
8598                            | "varbit"
8599                            | "interval"
8600                            | "bool"
8601                            | "int2"
8602                            | "int4"
8603                            | "int8"
8604                            | "float4"
8605                            | "float8"
8606                    )
8607                {
8608                    let value_token = self.advance().clone();
8609                    return Ok(Expr::Cast {
8610                        expr: Box::new(Expr::StringLiteral(value_token.value)),
8611                        data_type: DataType::Unknown(name.clone()),
8612                    });
8613                }
8614
8615                // ── Bare niladic temporal keywords: CURRENT_TIME, CURRENT_DATE,
8616                //    CURRENT_TIMESTAMP, LOCALTIMESTAMP (no parens) ──
8617                // ANSI SQL allows these without parentheses. Materialize them
8618                // as typed functions so the generator can emit dialect-specific
8619                // forms (e.g. TSQL requires CAST(GETDATE() AS TIME) rather than
8620                // a bare CURRENT_TIME reserved word).
8621                if name_qs == QuoteStyle::None && self.peek_type() != &TokenType::LParen {
8622                    let upper = name.to_ascii_uppercase();
8623                    let typed = match upper.as_str() {
8624                        "CURRENT_DATE" => Some(TypedFunction::CurrentDate),
8625                        "CURRENT_TIME" => Some(TypedFunction::CurrentTime),
8626                        "CURRENT_TIMESTAMP" | "LOCALTIMESTAMP" => {
8627                            Some(TypedFunction::CurrentTimestamp)
8628                        }
8629                        _ => None,
8630                    };
8631                    if let Some(tf) = typed {
8632                        return Ok(Expr::TypedFunction {
8633                            func: tf,
8634                            filter: None,
8635                            over: None,
8636                        });
8637                    }
8638                }
8639
8640                // Function call: name(...)
8641                if self.peek_type() == &TokenType::LParen {
8642                    self.advance();
8643
8644                    // TRY_CAST / SAFE_CAST / TRY_TO_TIMESTAMP / … — same shape
8645                    // as `CAST(expr AS type)`. Lower to `Expr::Cast` when the
8646                    // body matches; fall back to ordinary function call when
8647                    // it does not (e.g. comma-separated args).
8648                    if matches!(name.to_ascii_uppercase().as_str(), "TRY_CAST" | "SAFE_CAST") {
8649                        let save = self.pos;
8650                        let inner = self.parse_expr()?;
8651                        if self.match_token(TokenType::As) {
8652                            let dt = self.parse_data_type()?;
8653                            self.expect(TokenType::RParen)?;
8654                            return Ok(Expr::Cast {
8655                                expr: Box::new(inner),
8656                                data_type: dt,
8657                            });
8658                        }
8659                        self.pos = save;
8660                    }
8661
8662                    // Special: COUNT(*), COUNT(DISTINCT x)
8663                    let distinct = self.match_token(TokenType::Distinct);
8664                    // ANSI / ClickHouse `agg(ALL …)` — `ALL` is the opposite
8665                    // of DISTINCT and the default. Swallow so the args parse.
8666                    if !distinct {
8667                        let _ = self.match_token(TokenType::All);
8668                    }
8669
8670                    // Standard SQL syntactic forms for string functions:
8671                    //   SUBSTRING(expr FROM start [FOR len])
8672                    //   SUBSTRING(expr FOR len)
8673                    //   TRIM([LEADING|TRAILING|BOTH] [chars] FROM expr)
8674                    //   POSITION(needle IN haystack)
8675                    //   OVERLAY(expr PLACING str FROM start [FOR len])
8676                    let upper_name = name.to_ascii_uppercase();
8677                    if !distinct && self.peek_type() != &TokenType::RParen {
8678                        match upper_name.as_str() {
8679                            "SUBSTRING" | "SUBSTR" => {
8680                                let saved = self.pos;
8681                                let first = self.parse_expr()?;
8682                                if self.match_token(TokenType::From) {
8683                                    let start = self.parse_expr()?;
8684                                    let length = if self.check_keyword("FOR") {
8685                                        self.advance();
8686                                        Some(self.parse_expr()?)
8687                                    } else {
8688                                        None
8689                                    };
8690                                    self.expect(TokenType::RParen)?;
8691                                    let mut a = vec![first, start];
8692                                    if let Some(l) = length {
8693                                        a.push(l);
8694                                    }
8695                                    return Ok(Expr::Function {
8696                                        name: name.clone(),
8697                                        args: a,
8698                                        distinct: false,
8699                                        filter: None,
8700                                        over: None,
8701                                        order_by: Vec::new(),
8702                                        within_group: false,
8703                                    });
8704                                } else if self.check_keyword("FOR") {
8705                                    self.advance();
8706                                    let len = self.parse_expr()?;
8707                                    self.expect(TokenType::RParen)?;
8708                                    return Ok(Expr::Function {
8709                                        name: name.clone(),
8710                                        args: vec![first, len],
8711                                        distinct: false,
8712                                        filter: None,
8713                                        over: None,
8714                                        order_by: Vec::new(),
8715                                        within_group: false,
8716                                    });
8717                                }
8718                                self.pos = saved;
8719                            }
8720                            "TRIM" => {
8721                                let saved = self.pos;
8722                                let trim_type = if self.check_keyword("LEADING") {
8723                                    self.advance();
8724                                    TrimType::Leading
8725                                } else if self.check_keyword("TRAILING") {
8726                                    self.advance();
8727                                    TrimType::Trailing
8728                                } else if self.check_keyword("BOTH") {
8729                                    self.advance();
8730                                    TrimType::Both
8731                                } else {
8732                                    TrimType::Both
8733                                };
8734                                // TRIM([LEADING|TRAILING|BOTH] FROM expr)
8735                                if self.peek_type() == &TokenType::From {
8736                                    self.advance();
8737                                    let expr = self.parse_expr()?;
8738                                    self.expect(TokenType::RParen)?;
8739                                    return Ok(Expr::TypedFunction {
8740                                        func: TypedFunction::Trim {
8741                                            expr: Box::new(expr),
8742                                            trim_type,
8743                                            trim_chars: None,
8744                                        },
8745                                        filter: None,
8746                                        over: None,
8747                                    });
8748                                }
8749                                // TRIM([LEADING|TRAILING|BOTH] chars FROM expr)
8750                                let chars = self.parse_expr()?;
8751                                if self.match_token(TokenType::From) {
8752                                    let expr = self.parse_expr()?;
8753                                    self.expect(TokenType::RParen)?;
8754                                    return Ok(Expr::TypedFunction {
8755                                        func: TypedFunction::Trim {
8756                                            expr: Box::new(expr),
8757                                            trim_type,
8758                                            trim_chars: Some(Box::new(chars)),
8759                                        },
8760                                        filter: None,
8761                                        over: None,
8762                                    });
8763                                }
8764                                self.pos = saved;
8765                            }
8766                            "POSITION" => {
8767                                let saved = self.pos;
8768                                let needle = self.parse_expr()?;
8769                                if self.match_token(TokenType::In) {
8770                                    let haystack = self.parse_expr()?;
8771                                    self.expect(TokenType::RParen)?;
8772                                    return Ok(Expr::Function {
8773                                        name: name.clone(),
8774                                        args: vec![needle, haystack],
8775                                        distinct: false,
8776                                        filter: None,
8777                                        over: None,
8778                                        order_by: Vec::new(),
8779                                        within_group: false,
8780                                    });
8781                                }
8782                                self.pos = saved;
8783                            }
8784                            "OVERLAY" => {
8785                                let saved = self.pos;
8786                                let target = self.parse_expr()?;
8787                                if self.check_keyword("PLACING") {
8788                                    self.advance();
8789                                    let placing = self.parse_expr()?;
8790                                    if self.match_token(TokenType::From) {
8791                                        let from = self.parse_expr()?;
8792                                        let len = if self.check_keyword("FOR") {
8793                                            self.advance();
8794                                            Some(self.parse_expr()?)
8795                                        } else {
8796                                            None
8797                                        };
8798                                        self.expect(TokenType::RParen)?;
8799                                        let mut a = vec![target, placing, from];
8800                                        if let Some(l) = len {
8801                                            a.push(l);
8802                                        }
8803                                        return Ok(Expr::Function {
8804                                            name: name.clone(),
8805                                            args: a,
8806                                            distinct: false,
8807                                            filter: None,
8808                                            over: None,
8809                                            order_by: Vec::new(),
8810                                            within_group: false,
8811                                        });
8812                                    }
8813                                }
8814                                self.pos = saved;
8815                            }
8816                            _ => {}
8817                        }
8818                    }
8819
8820                    // MySQL's GROUP_CONCAT has bespoke grammar
8821                    // (ORDER BY ..., SEPARATOR ...) — parse it into a typed
8822                    // expression so the structure is preserved across dialects.
8823                    if name.eq_ignore_ascii_case("GROUP_CONCAT") {
8824                        let expr = self.parse_group_concat_call(distinct)?;
8825                        self.expect(TokenType::RParen)?;
8826                        return Ok(expr);
8827                    }
8828
8829                    let args = if self.peek_type() == &TokenType::RParen {
8830                        vec![]
8831                    } else if self.peek_type() == &TokenType::Star {
8832                        self.advance();
8833                        vec![Expr::Wildcard]
8834                    } else {
8835                        let mut a = vec![self.parse_function_arg()?];
8836                        while self.match_token(TokenType::Comma) {
8837                            a.push(self.parse_function_arg()?);
8838                        }
8839                        a
8840                    };
8841
8842                    // Optional aggregate ORDER BY inside arg list (Postgres / Spark):
8843                    //   array_agg(x ORDER BY y DESC)
8844                    //   string_agg(x, ',' ORDER BY y)
8845                    let mut agg_order_by: Vec<OrderByItem> = vec![];
8846                    if self.peek_type() == &TokenType::Order {
8847                        self.advance();
8848                        self.expect(TokenType::By)?;
8849                        agg_order_by = self.parse_order_by_items()?;
8850                    }
8851                    // BigQuery / Snowflake: `ARRAY_AGG(x [ORDER BY y] LIMIT n)`.
8852                    // Swallow the trailing LIMIT clause inside the function call.
8853                    if self.peek_type() == &TokenType::Limit {
8854                        self.advance();
8855                        let _ = self.parse_expr();
8856                    }
8857                    // DuckDB aggregate-state modifier:
8858                    //   `count(1) EXPORT_STATE` returns the aggregate state
8859                    //   rather than its final value. We don't model it.
8860                    if self.check_keyword("EXPORT_STATE") {
8861                        self.advance();
8862                    }
8863                    self.expect(TokenType::RParen)?;
8864
8865                    // Optional WITHIN GROUP (ORDER BY ...) — ordered-set aggregates
8866                    //   percentile_cont(0.5) WITHIN GROUP (ORDER BY x)
8867                    //   listagg(x, ',') WITHIN GROUP (ORDER BY x)
8868                    let mut within_group = false;
8869                    let mut wg_order_by: Vec<OrderByItem> = vec![];
8870                    if self.check_keyword("WITHIN") {
8871                        self.advance();
8872                        self.expect_keyword("GROUP")?;
8873                        self.expect(TokenType::LParen)?;
8874                        self.expect(TokenType::Order)?;
8875                        self.expect(TokenType::By)?;
8876                        wg_order_by = self.parse_order_by_items()?;
8877                        self.expect(TokenType::RParen)?;
8878                        within_group = true;
8879                    }
8880
8881                    let final_order_by = if within_group {
8882                        wg_order_by
8883                    } else {
8884                        agg_order_by
8885                    };
8886
8887                    // Try to construct a typed function variant only when there are no
8888                    // aggregate-specific clauses (otherwise we lose them).
8889                    if final_order_by.is_empty()
8890                        && !within_group
8891                        && let Some(typed) = Self::try_typed_function(&name, args.clone(), distinct)
8892                    {
8893                        return Ok(typed);
8894                    }
8895
8896                    Ok(Expr::Function {
8897                        name,
8898                        args,
8899                        distinct,
8900                        filter: None,
8901                        over: None,
8902                        order_by: final_order_by,
8903                        within_group,
8904                    })
8905                }
8906                // Qualified column: table.column or table.*
8907                else if self.match_token(TokenType::Dot) {
8908                    if self.peek_type() == &TokenType::Star {
8909                        self.advance();
8910                        Ok(Expr::QualifiedWildcard { table: name })
8911                    } else {
8912                        // ClickHouse JSON subobject and typed access at the
8913                        // first dot: `json.^a`, `json.:Int64`.
8914                        let _ = self.match_token(TokenType::BitwiseXor);
8915                        let _ = self.match_token(TokenType::Colon);
8916                        let (mut col, mut col_qs) = if matches!(self.peek_type(), TokenType::Number)
8917                        {
8918                            // ClickHouse tuple index `x.1`.
8919                            let v = self.peek().value.clone();
8920                            self.advance();
8921                            (v, QuoteStyle::None)
8922                        } else if matches!(self.peek_type(), TokenType::Null) {
8923                            // ClickHouse JSON subcolumn `.null` (e.g.
8924                            // `arr.null`, `t.s.null`). Accept the keyword as
8925                            // a field name in dotted-access position.
8926                            let v = self.peek().value.clone();
8927                            self.advance();
8928                            (v, QuoteStyle::None)
8929                        } else {
8930                            self.expect_name_with_quote()?
8931                        };
8932                        // Handle 3+ part qualified names like `db.schema.table.column`
8933                        // (DuckDB, ClickHouse). We collapse everything except the
8934                        // final segment into the `table` field as a dotted string.
8935                        let mut table = name;
8936                        let mut table_qs = name_qs;
8937                        while self.match_token(TokenType::Dot) {
8938                            if self.peek_type() == &TokenType::Star {
8939                                self.advance();
8940                                let mut full = table;
8941                                full.push('.');
8942                                full.push_str(&col);
8943                                return Ok(Expr::QualifiedWildcard { table: full });
8944                            }
8945                            // ClickHouse JSON subobject (`json.^a`) and typed
8946                            // access (`json.a.:Int64`) — swallow the operator
8947                            // so the following name can be consumed normally.
8948                            let _ = self.match_token(TokenType::BitwiseXor);
8949                            let _ = self.match_token(TokenType::Colon);
8950                            // ClickHouse tuple index (`t.1`): treat number as
8951                            // a synthetic field name.
8952                            let (next_col, next_qs) =
8953                                if matches!(self.peek_type(), TokenType::Number) {
8954                                    let v = self.peek().value.clone();
8955                                    self.advance();
8956                                    (v, QuoteStyle::None)
8957                                } else if matches!(self.peek_type(), TokenType::Null) {
8958                                    let v = self.peek().value.clone();
8959                                    self.advance();
8960                                    (v, QuoteStyle::None)
8961                                } else {
8962                                    self.expect_name_with_quote()?
8963                                };
8964                            table.push('.');
8965                            table.push_str(&col);
8966                            table_qs = col_qs;
8967                            col = next_col;
8968                            col_qs = next_qs;
8969                        }
8970                        // Function call on dotted name: db.schema.func(args).
8971                        if self.peek_type() == &TokenType::LParen {
8972                            self.advance();
8973                            let mut full = table;
8974                            full.push('.');
8975                            full.push_str(&col);
8976                            let args = if self.peek_type() != &TokenType::RParen {
8977                                let mut a = vec![self.parse_function_arg()?];
8978                                while self.match_token(TokenType::Comma) {
8979                                    a.push(self.parse_function_arg()?);
8980                                }
8981                                a
8982                            } else {
8983                                vec![]
8984                            };
8985                            self.expect(TokenType::RParen)?;
8986                            return Ok(Expr::Function {
8987                                name: full,
8988                                args,
8989                                distinct: false,
8990                                filter: None,
8991                                over: None,
8992                                order_by: Vec::new(),
8993                                within_group: false,
8994                            });
8995                        }
8996                        Ok(Expr::Column {
8997                            table: Some(table),
8998                            name: col,
8999                            quote_style: col_qs,
9000                            table_quote_style: table_qs,
9001                        })
9002                    }
9003                } else {
9004                    Ok(Expr::Column {
9005                        table: None,
9006                        name,
9007                        quote_style: name_qs,
9008                        table_quote_style: QuoteStyle::None,
9009                    })
9010                }
9011            }
9012
9013            _ => {
9014                // Fallback: any other token whose value is a valid identifier
9015                // and is immediately followed by `(` is treated as a function
9016                // call. This handles reserved keywords used as Spark/Hive
9017                // built-ins (IF, ALL, ANY, EXISTS, MOD, etc.) and dialect
9018                // functions that happen to collide with token types.
9019                let v = token.value.clone();
9020                let is_word =
9021                    !v.is_empty() && v.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
9022                if is_word
9023                    && matches!(
9024                        self.peek_offset(1).map(|t| &t.token_type),
9025                        Some(TokenType::LParen)
9026                    )
9027                {
9028                    // TRY_CAST / SAFE_CAST / TRY_TO_TIMESTAMP / … — same
9029                    // shape as `CAST(expr AS type)`. Lower to `Expr::Cast`
9030                    // (or back to a function call when the form doesn't
9031                    // match).
9032                    let upper = v.to_ascii_uppercase();
9033                    if matches!(upper.as_str(), "TRY_CAST" | "SAFE_CAST") {
9034                        self.advance();
9035                        self.advance(); // consume '('
9036                        let inner = self.parse_expr()?;
9037                        if self.match_token(TokenType::As) {
9038                            let data_type = self.parse_data_type()?;
9039                            self.expect(TokenType::RParen)?;
9040                            return Ok(Expr::Cast {
9041                                expr: Box::new(inner),
9042                                data_type,
9043                            });
9044                        }
9045                        // Fall back: treat as ordinary function call.
9046                        let mut args = vec![inner];
9047                        while self.match_token(TokenType::Comma) {
9048                            args.push(self.parse_expr()?);
9049                        }
9050                        self.expect(TokenType::RParen)?;
9051                        return Ok(Expr::Function {
9052                            name: v,
9053                            args,
9054                            distinct: false,
9055                            filter: None,
9056                            over: None,
9057                            order_by: Vec::new(),
9058                            within_group: false,
9059                        });
9060                    }
9061                    self.advance();
9062                    self.advance(); // consume '('
9063                    let upper = v.to_ascii_uppercase();
9064                    // Standard SQL `SUBSTRING(expr FROM start [FOR length])`
9065                    // and MySQL `SUBSTRING(expr FROM start)` / `…FOR length`.
9066                    if matches!(upper.as_str(), "SUBSTRING" | "SUBSTR")
9067                        && self.peek_type() != &TokenType::RParen
9068                    {
9069                        let saved = self.pos;
9070                        let first = self.parse_expr()?;
9071                        if self.match_token(TokenType::From) {
9072                            let start = self.parse_expr()?;
9073                            let length = if self.check_keyword("FOR") {
9074                                self.advance();
9075                                Some(self.parse_expr()?)
9076                            } else {
9077                                None
9078                            };
9079                            self.expect(TokenType::RParen)?;
9080                            let mut args = vec![first, start];
9081                            if let Some(len) = length {
9082                                args.push(len);
9083                            }
9084                            return Ok(Expr::Function {
9085                                name: v,
9086                                args,
9087                                distinct: false,
9088                                filter: None,
9089                                over: None,
9090                                order_by: Vec::new(),
9091                                within_group: false,
9092                            });
9093                        }
9094                        if self.check_keyword("FOR") {
9095                            self.advance();
9096                            let length = self.parse_expr()?;
9097                            self.expect(TokenType::RParen)?;
9098                            return Ok(Expr::Function {
9099                                name: v,
9100                                args: vec![first, length],
9101                                distinct: false,
9102                                filter: None,
9103                                over: None,
9104                                order_by: Vec::new(),
9105                                within_group: false,
9106                            });
9107                        }
9108                        // Fall back: re-parse as comma list.
9109                        self.pos = saved;
9110                    }
9111                    // Standard `TRIM([LEADING|TRAILING|BOTH] [chars] FROM expr)`
9112                    // and `TRIM(expr [, chars])` (already covered by comma).
9113                    if upper == "TRIM" && self.peek_type() != &TokenType::RParen {
9114                        let saved = self.pos;
9115                        let trim_type = if self.check_keyword("LEADING") {
9116                            self.advance();
9117                            TrimType::Leading
9118                        } else if self.check_keyword("TRAILING") {
9119                            self.advance();
9120                            TrimType::Trailing
9121                        } else if self.check_keyword("BOTH") {
9122                            self.advance();
9123                            TrimType::Both
9124                        } else {
9125                            TrimType::Both
9126                        };
9127                        if self.peek_type() == &TokenType::From {
9128                            self.advance();
9129                            let expr = self.parse_expr()?;
9130                            self.expect(TokenType::RParen)?;
9131                            return Ok(Expr::TypedFunction {
9132                                func: TypedFunction::Trim {
9133                                    expr: Box::new(expr),
9134                                    trim_type,
9135                                    trim_chars: None,
9136                                },
9137                                filter: None,
9138                                over: None,
9139                            });
9140                        }
9141                        // chars FROM expr
9142                        let chars = self.parse_expr()?;
9143                        if self.match_token(TokenType::From) {
9144                            let expr = self.parse_expr()?;
9145                            self.expect(TokenType::RParen)?;
9146                            return Ok(Expr::TypedFunction {
9147                                func: TypedFunction::Trim {
9148                                    expr: Box::new(expr),
9149                                    trim_type,
9150                                    trim_chars: Some(Box::new(chars)),
9151                                },
9152                                filter: None,
9153                                over: None,
9154                            });
9155                        }
9156                        // Plain comma list — fall back.
9157                        self.pos = saved;
9158                    }
9159                    // Standard `OVERLAY(expr PLACING str FROM start [FOR len])`.
9160                    if upper == "OVERLAY" && self.peek_type() != &TokenType::RParen {
9161                        let saved = self.pos;
9162                        let target = self.parse_expr()?;
9163                        if self.check_keyword("PLACING") {
9164                            self.advance();
9165                            let placing = self.parse_expr()?;
9166                            self.expect(TokenType::From)?;
9167                            let from = self.parse_expr()?;
9168                            let len = if self.check_keyword("FOR") {
9169                                self.advance();
9170                                Some(self.parse_expr()?)
9171                            } else {
9172                                None
9173                            };
9174                            self.expect(TokenType::RParen)?;
9175                            let mut args = vec![target, placing, from];
9176                            if let Some(l) = len {
9177                                args.push(l);
9178                            }
9179                            return Ok(Expr::Function {
9180                                name: v,
9181                                args,
9182                                distinct: false,
9183                                filter: None,
9184                                over: None,
9185                                order_by: Vec::new(),
9186                                within_group: false,
9187                            });
9188                        }
9189                        self.pos = saved;
9190                    }
9191                    // Standard `POSITION(needle IN haystack)`.
9192                    if upper == "POSITION" && self.peek_type() != &TokenType::RParen {
9193                        let saved = self.pos;
9194                        let needle = self.parse_expr()?;
9195                        if self.check_keyword("IN") {
9196                            self.advance();
9197                            let haystack = self.parse_expr()?;
9198                            self.expect(TokenType::RParen)?;
9199                            return Ok(Expr::Function {
9200                                name: v,
9201                                args: vec![needle, haystack],
9202                                distinct: false,
9203                                filter: None,
9204                                over: None,
9205                                order_by: Vec::new(),
9206                                within_group: false,
9207                            });
9208                        }
9209                        self.pos = saved;
9210                    }
9211                    let mut args = Vec::new();
9212                    if self.peek_type() != &TokenType::RParen {
9213                        args.push(self.parse_function_arg()?);
9214                        while self.match_token(TokenType::Comma) {
9215                            args.push(self.parse_function_arg()?);
9216                        }
9217                    }
9218                    self.expect(TokenType::RParen)?;
9219                    return Ok(Expr::Function {
9220                        name: v,
9221                        args,
9222                        distinct: false,
9223                        filter: None,
9224                        over: None,
9225                        order_by: Vec::new(),
9226                        within_group: false,
9227                    });
9228                }
9229                Err(SqlglotError::UnexpectedToken { token })
9230            }
9231        }
9232    }
9233
9234    /// Parse a single function-call argument. Accepts the DuckDB / PostgreSQL
9235    /// named-argument syntaxes `name := value` and `name => value` and falls
9236    /// back to a plain expression for positional arguments. The argument
9237    /// name is discarded — we don't model it in the AST.
9238    fn parse_function_arg(&mut self) -> Result<Expr> {
9239        // Hive table-valued function clause: `noop(on tbl partition by p
9240        // order by q distribute by r cluster by s sort by t)`. The arg
9241        // list begins with the `ON` keyword and is followed by a series
9242        // of windowing-style clauses we don't model. Swallow it as an
9243        // opaque payload so we don't reject the call.
9244        if matches!(self.peek_type(), TokenType::On) {
9245            let mut depth = 0usize;
9246            while !matches!(self.peek_type(), TokenType::Eof) {
9247                match self.peek_type() {
9248                    TokenType::LParen => depth += 1,
9249                    TokenType::RParen => {
9250                        if depth == 0 {
9251                            break;
9252                        }
9253                        depth -= 1;
9254                    }
9255                    TokenType::Comma if depth == 0 => break,
9256                    _ => {}
9257                }
9258                self.advance();
9259            }
9260            return Ok(Expr::Null);
9261        }
9262        if self.is_name_token()
9263            || self.is_data_type_token()
9264            || matches!(self.peek_type(), TokenType::Recursive)
9265        {
9266            let next = self.peek_offset(1).map(|t| &t.token_type);
9267            if matches!(next, Some(TokenType::Colon)) {
9268                let after = self.peek_offset(2).map(|t| &t.token_type);
9269                if matches!(after, Some(TokenType::Eq)) {
9270                    self.advance();
9271                    self.advance();
9272                    self.advance();
9273                    return self.parse_expr();
9274                }
9275            }
9276            if matches!(next, Some(TokenType::DoubleArrow)) {
9277                self.advance();
9278                self.advance();
9279                return self.parse_expr();
9280            }
9281        }
9282        // ClickHouse table functions: `view(SELECT …)`, `cluster(…)` etc.
9283        // accept a full SELECT / WITH / UNION inside the arg list. Parse
9284        // it as a Subquery so the surrounding call closes properly.
9285        if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
9286            let stmt = self.parse_statement_inner()?;
9287            return Ok(Expr::Subquery(Box::new(stmt)));
9288        }
9289        let mut expr = self.parse_expr()?;
9290        // Oracle / Snowflake / MySQL `JSON_OBJECT('k' : value, ...)` and the
9291        // `JSON_OBJECTAGG(k : v)` family use `:` as a key-value separator
9292        // inside function args. After parsing the first expression, swallow
9293        // a bare `:` and parse the value side; emit the value as the arg
9294        // (we don't model JSON key-value pairs in the AST). Only fire when
9295        // the next-after-colon is not another `:` (`::` cast) and not `=`
9296        // (`:=` named arg, already handled above).
9297        if matches!(self.peek_type(), TokenType::Colon)
9298            && !matches!(
9299                self.peek_offset(1).map(|t| &t.token_type),
9300                Some(TokenType::Colon) | Some(TokenType::Eq)
9301            )
9302        {
9303            self.advance(); // :
9304            expr = self.parse_expr()?;
9305            // Optional `FORMAT JSON` suffix (Oracle).
9306            if self.peek().value.eq_ignore_ascii_case("FORMAT")
9307                && self
9308                    .peek_offset(1)
9309                    .map(|t| t.value.eq_ignore_ascii_case("JSON"))
9310                    .unwrap_or(false)
9311            {
9312                self.advance();
9313                self.advance();
9314            }
9315        }
9316        // ClickHouse: `func(expr AS alias)` — swallow the alias.
9317        if self.match_token(TokenType::As) && self.is_name_token() {
9318            self.advance();
9319        }
9320        // Spark / DataBricks UDTF call: `UDTF(TABLE(t) [PARTITION BY cols]
9321        // [ORDER BY cols])`. Swallow the table-argument modifiers opaquely.
9322        if self.peek_type() == &TokenType::Partition
9323            && self
9324                .peek_offset(1)
9325                .map(|t| matches!(t.token_type, TokenType::By))
9326                .unwrap_or(false)
9327        {
9328            self.advance(); // PARTITION
9329            self.advance(); // BY
9330            // Comma-separated expression list (column refs / exprs).
9331            let _ = self.parse_expr()?;
9332            while self.match_token(TokenType::Comma) {
9333                let _ = self.parse_expr()?;
9334            }
9335        }
9336        if self.peek_type() == &TokenType::Order
9337            && self
9338                .peek_offset(1)
9339                .map(|t| matches!(t.token_type, TokenType::By))
9340                .unwrap_or(false)
9341        {
9342            self.advance(); // ORDER
9343            self.advance(); // BY
9344            let _ = self.parse_order_by_items()?;
9345        }
9346        // BigQuery / DuckDB / Snowflake / Oracle window-function nulls
9347        // modifier: `LAST_VALUE(arg IGNORE NULLS)`, `... RESPECT NULLS`.
9348        // Swallow opaquely; we don't model it in the AST.
9349        if (self.peek().value.eq_ignore_ascii_case("IGNORE")
9350            || self.peek().value.eq_ignore_ascii_case("RESPECT"))
9351            && self
9352                .peek_offset(1)
9353                .map(|t| t.token_type == TokenType::Null || t.value.eq_ignore_ascii_case("NULLS"))
9354                .unwrap_or(false)
9355        {
9356            self.advance();
9357            self.advance();
9358        }
9359        // Postgres JSON helpers: `JSON_SERIALIZE(expr RETURNING type)`,
9360        // `JSON_QUERY(... RETURNING jsonb FORMAT JSON)`,
9361        // `JSON_VALUE(... RETURNING type DEFAULT v ON EMPTY|ERROR …)`. After
9362        // any RETURNING clause, swallow the optional FORMAT, DEFAULT, ON
9363        // EMPTY/ERROR tail so the call parses cleanly.
9364        if self.match_token(TokenType::Returning) {
9365            if self.is_data_type_token() || self.is_name_token() {
9366                let _ = self.parse_data_type();
9367            }
9368        }
9369        // SQL/JSON `PASSING v AS name [, v AS name]*` clause inside
9370        // JSON_EXISTS / JSON_VALUE / JSON_QUERY argument lists.
9371        if self.check_keyword("PASSING") {
9372            self.advance();
9373            loop {
9374                let _ = self.parse_expr()?;
9375                if self.match_token(TokenType::As) && self.is_name_token() {
9376                    self.advance();
9377                }
9378                if !self.match_token(TokenType::Comma) {
9379                    break;
9380                }
9381            }
9382        }
9383        // SQL/JSON behavior clauses: `NULL|ERROR|EMPTY [ARRAY|OBJECT]|
9384        // DEFAULT expr ON EMPTY|ERROR`. Swallow them opaquely; the
9385        // surrounding call still resolves to its primary expression.
9386        loop {
9387            let is_default = self.peek_type() == &TokenType::Default;
9388            let is_behavior_kw = self.check_keyword("ERROR")
9389                || self.check_keyword("NULL")
9390                || self.peek_type() == &TokenType::Null
9391                || self.check_keyword("EMPTY")
9392                || self.check_keyword("TRUE")
9393                || self.check_keyword("FALSE")
9394                || self.check_keyword("UNKNOWN");
9395            if !is_default && !is_behavior_kw {
9396                break;
9397            }
9398            // Look ahead: behavior keyword must be followed (possibly via
9399            // optional ARRAY/OBJECT/expr) by `ON ERROR|EMPTY` to qualify.
9400            let saved = self.pos;
9401            if is_default {
9402                self.advance();
9403                let _ = self.parse_expr();
9404            } else {
9405                self.advance();
9406                if self.check_keyword("ARRAY") || self.check_keyword("OBJECT") {
9407                    self.advance();
9408                }
9409            }
9410            if self.peek_type() == &TokenType::On
9411                && self
9412                    .peek_offset(1)
9413                    .map(|t| {
9414                        t.value.eq_ignore_ascii_case("ERROR")
9415                            || t.value.eq_ignore_ascii_case("EMPTY")
9416                    })
9417                    .unwrap_or(false)
9418            {
9419                self.advance(); // ON
9420                self.advance(); // ERROR / EMPTY
9421            } else {
9422                // Not actually a behavior clause — rewind.
9423                self.pos = saved;
9424                break;
9425            }
9426        }
9427        // MySQL `CONVERT(expr USING charset)` — swallow USING + name.
9428        if self.match_token(TokenType::Using) {
9429            if self.is_name_token() {
9430                self.advance();
9431            }
9432        }
9433        // ON EMPTY / ON ERROR / DEFAULT … ON EMPTY|ERROR / FORMAT … —
9434        // tolerated tail clauses common to JSON_VALUE / JSON_QUERY /
9435        // JSON_EXISTS. Loop while one of the recognized starters appears.
9436        loop {
9437            let starts = self.peek_type() == &TokenType::Default
9438                || self.match_keyword_clone("FORMAT")
9439                || (self.peek_type() == &TokenType::On
9440                    && self
9441                        .peek_offset(1)
9442                        .map(|t| {
9443                            t.value.eq_ignore_ascii_case("EMPTY")
9444                                || t.value.eq_ignore_ascii_case("ERROR")
9445                        })
9446                        .unwrap_or(false));
9447            if !starts {
9448                break;
9449            }
9450            // Consume up to the next top-level `,` / `)` / EOF, tracking
9451            // nesting so embedded parens (e.g. `DEFAULT ('C' COLLATE "C")`)
9452            // don't terminate prematurely.
9453            let mut depth = 0i32;
9454            while !matches!(self.peek_type(), TokenType::Eof) {
9455                match self.peek_type() {
9456                    TokenType::LParen | TokenType::LBracket => depth += 1,
9457                    TokenType::RParen | TokenType::RBracket => {
9458                        if depth == 0 {
9459                            break;
9460                        }
9461                        depth -= 1;
9462                    }
9463                    TokenType::Comma if depth == 0 => break,
9464                    _ => {}
9465                }
9466                self.advance();
9467            }
9468        }
9469        Ok(expr)
9470    }
9471
9472    /// True when the current token is a name token whose uppercase value
9473    /// equals `kw`. Does NOT advance the token cursor.
9474    fn match_keyword_clone(&self, kw: &str) -> bool {
9475        self.check_keyword(kw)
9476    }
9477
9478    fn is_data_type_token(&self) -> bool {
9479        self.is_data_type_token_kind(self.peek_type())
9480    }
9481
9482    fn is_data_type_token_kind(&self, tt: &TokenType) -> bool {
9483        matches!(
9484            tt,
9485            TokenType::Int
9486                | TokenType::Integer
9487                | TokenType::BigInt
9488                | TokenType::SmallInt
9489                | TokenType::TinyInt
9490                | TokenType::Float
9491                | TokenType::Double
9492                | TokenType::Decimal
9493                | TokenType::Numeric
9494                | TokenType::Real
9495                | TokenType::Varchar
9496                | TokenType::Char
9497                | TokenType::Text
9498                | TokenType::Boolean
9499                | TokenType::Date
9500                | TokenType::Timestamp
9501                | TokenType::TimestampTz
9502                | TokenType::Time
9503                | TokenType::Interval
9504                | TokenType::Blob
9505                | TokenType::Bytea
9506                | TokenType::Json
9507                | TokenType::Jsonb
9508                | TokenType::Uuid
9509                | TokenType::Array
9510                | TokenType::Map
9511                | TokenType::Struct
9512        )
9513    }
9514
9515    fn parse_datetime_field(&mut self) -> Result<DateTimeField> {
9516        let token = self.peek().clone();
9517        let field = match &token.token_type {
9518            TokenType::Year => DateTimeField::Year,
9519            TokenType::Month => DateTimeField::Month,
9520            TokenType::Day => DateTimeField::Day,
9521            TokenType::Hour => DateTimeField::Hour,
9522            TokenType::Minute => DateTimeField::Minute,
9523            TokenType::Second => DateTimeField::Second,
9524            TokenType::Epoch => DateTimeField::Epoch,
9525            _ => {
9526                let name = token.value.to_uppercase();
9527                match name.as_str() {
9528                    "YEAR" => DateTimeField::Year,
9529                    "QUARTER" => DateTimeField::Quarter,
9530                    "MONTH" => DateTimeField::Month,
9531                    "WEEK" => DateTimeField::Week,
9532                    "DAY" => DateTimeField::Day,
9533                    "DOW" | "DAYOFWEEK" => DateTimeField::DayOfWeek,
9534                    "DOY" | "DAYOFYEAR" => DateTimeField::DayOfYear,
9535                    "HOUR" => DateTimeField::Hour,
9536                    "MINUTE" => DateTimeField::Minute,
9537                    "SECOND" => DateTimeField::Second,
9538                    "MILLISECOND" | "MILLISECONDS" | "MS" => DateTimeField::Millisecond,
9539                    "MICROSECOND" | "MICROSECONDS" | "US" => DateTimeField::Microsecond,
9540                    "NANOSECOND" | "NANOSECONDS" | "NS" => DateTimeField::Nanosecond,
9541                    "YEARS" => DateTimeField::Year,
9542                    "QUARTERS" => DateTimeField::Quarter,
9543                    "MONTHS" => DateTimeField::Month,
9544                    "WEEKS" => DateTimeField::Week,
9545                    "DAYS" => DateTimeField::Day,
9546                    "HOURS" => DateTimeField::Hour,
9547                    "MINUTES" => DateTimeField::Minute,
9548                    "SECONDS" => DateTimeField::Second,
9549                    "EPOCH" => DateTimeField::Epoch,
9550                    "TIMEZONE" => DateTimeField::Timezone,
9551                    "TIMEZONE_HOUR" => DateTimeField::TimezoneHour,
9552                    "TIMEZONE_MINUTE" => DateTimeField::TimezoneMinute,
9553                    // MySQL composite interval units. We don't model them
9554                    // distinctly; lower to the dominant component so the
9555                    // surrounding parse completes.
9556                    "DAY_HOUR" | "DAY_MINUTE" | "DAY_SECOND" | "DAY_MICROSECOND" => {
9557                        DateTimeField::Day
9558                    }
9559                    "HOUR_MINUTE" | "HOUR_SECOND" | "HOUR_MICROSECOND" => DateTimeField::Hour,
9560                    "MINUTE_SECOND" | "MINUTE_MICROSECOND" => DateTimeField::Minute,
9561                    "SECOND_MICROSECOND" => DateTimeField::Second,
9562                    "YEAR_MONTH" => DateTimeField::Year,
9563                    _ => {
9564                        return Err(SqlglotError::ParserError {
9565                            message: format!("Unknown datetime field: {name}"),
9566                        });
9567                    }
9568                }
9569            }
9570        };
9571        self.advance();
9572        Ok(field)
9573    }
9574
9575    fn try_parse_datetime_field(&mut self) -> Option<DateTimeField> {
9576        let saved = self.pos;
9577        match self.parse_datetime_field() {
9578            Ok(field) => Some(field),
9579            Err(_) => {
9580                self.pos = saved;
9581                None
9582            }
9583        }
9584    }
9585
9586    /// Parse the inside of `GROUP_CONCAT(...)` (caller has already consumed
9587    /// the `(` and optional `DISTINCT`). Returns a typed `GroupConcat`
9588    /// expression. Does NOT consume the trailing `)`.
9589    fn parse_group_concat_call(&mut self, distinct: bool) -> Result<Expr> {
9590        let mut exprs: Vec<Expr> = Vec::new();
9591        let mut order_by: Vec<OrderByItem> = Vec::new();
9592        let mut separator: Option<Box<Expr>> = None;
9593
9594        if self.peek_type() != &TokenType::RParen {
9595            exprs.push(self.parse_expr()?);
9596            while self.peek_type() == &TokenType::Comma {
9597                // ORDER BY / SEPARATOR are alternative terminators, not args.
9598                // Peek one past the comma to disambiguate `f(a, b)` from
9599                // `f(a, b ORDER BY ...)` — but comma here always introduces
9600                // another positional arg, so just keep consuming.
9601                self.advance();
9602                exprs.push(self.parse_expr()?);
9603            }
9604
9605            if self.match_token(TokenType::Order) {
9606                self.expect(TokenType::By)?;
9607                order_by = self.parse_order_by_items()?;
9608            }
9609
9610            if self.match_keyword("SEPARATOR") {
9611                separator = Some(Box::new(self.parse_expr()?));
9612            }
9613        }
9614
9615        Ok(Expr::TypedFunction {
9616            func: TypedFunction::GroupConcat {
9617                exprs,
9618                separator,
9619                order_by,
9620                distinct,
9621            },
9622            filter: None,
9623            over: None,
9624        })
9625    }
9626
9627    /// Try to construct a typed function expression from a parsed function call.
9628    /// Returns `None` if the function name is not recognized, falling back to
9629    /// the generic `Expr::Function`.
9630    fn try_typed_function(name: &str, args: Vec<Expr>, distinct: bool) -> Option<Expr> {
9631        let upper = name.to_uppercase();
9632        let tf = match upper.as_str() {
9633            // ── Date/Time ──────────────────────────────────────────
9634            "DATE_ADD" | "DATEADD" | "TIMESTAMPADD" => {
9635                let mut it = args.into_iter();
9636                let first = it.next()?;
9637                let second = it.next()?;
9638                let third = it.next();
9639                // Handle DATEADD(unit, interval, expr) — TSQL/Snowflake arg order
9640                if upper == "DATEADD" {
9641                    if let Some(third_arg) = third {
9642                        // 3-arg: DATEADD(unit, interval, expr)
9643                        let unit = Self::expr_to_datetime_field(&first);
9644                        TypedFunction::DateAdd {
9645                            expr: Box::new(third_arg),
9646                            interval: Box::new(second),
9647                            unit,
9648                        }
9649                    } else {
9650                        TypedFunction::DateAdd {
9651                            expr: Box::new(first),
9652                            interval: Box::new(second),
9653                            unit: None,
9654                        }
9655                    }
9656                } else {
9657                    // DATE_ADD(expr, interval [, unit])
9658                    let unit = third.as_ref().and_then(Self::expr_to_datetime_field);
9659                    TypedFunction::DateAdd {
9660                        expr: Box::new(first),
9661                        interval: Box::new(second),
9662                        unit,
9663                    }
9664                }
9665            }
9666            "DATE_DIFF" | "DATEDIFF" | "TIMESTAMPDIFF" => {
9667                let mut it = args.into_iter();
9668                let first = it.next()?;
9669                let second = it.next()?;
9670                let third = it.next();
9671                if let Some(third_arg) = third {
9672                    if upper == "DATEDIFF" {
9673                        // DATEDIFF(unit, start, end) — TSQL/Snowflake
9674                        let unit = Self::expr_to_datetime_field(&first);
9675                        TypedFunction::DateDiff {
9676                            start: Box::new(second),
9677                            end: Box::new(third_arg),
9678                            unit,
9679                        }
9680                    } else {
9681                        let unit = Self::expr_to_datetime_field(&third_arg);
9682                        TypedFunction::DateDiff {
9683                            start: Box::new(first),
9684                            end: Box::new(second),
9685                            unit,
9686                        }
9687                    }
9688                } else {
9689                    TypedFunction::DateDiff {
9690                        start: Box::new(first),
9691                        end: Box::new(second),
9692                        unit: None,
9693                    }
9694                }
9695            }
9696            "DATE_TRUNC" | "DATETRUNC" => {
9697                let mut it = args.into_iter();
9698                let first = it.next()?;
9699                let second = it.next()?;
9700                // DATE_TRUNC('unit', expr) or DATE_TRUNC(unit, expr)
9701                let (unit, expr) = if let Some(u) = Self::expr_to_datetime_field(&first) {
9702                    (u, second)
9703                } else if let Some(u) = Self::expr_to_datetime_field(&second) {
9704                    (u, first)
9705                } else {
9706                    // Default: first = unit string, second = expr
9707                    return None;
9708                };
9709                TypedFunction::DateTrunc {
9710                    unit,
9711                    expr: Box::new(expr),
9712                }
9713            }
9714            "DATE_SUB" | "DATESUB" => {
9715                let mut it = args.into_iter();
9716                let first = it.next()?;
9717                let second = it.next()?;
9718                let third = it.next();
9719                let unit = third.as_ref().and_then(Self::expr_to_datetime_field);
9720                TypedFunction::DateSub {
9721                    expr: Box::new(first),
9722                    interval: Box::new(second),
9723                    unit,
9724                }
9725            }
9726            "CURRENT_DATE" => TypedFunction::CurrentDate,
9727            "CURRENT_TIME" | "CURTIME" => TypedFunction::CurrentTime,
9728            "CURRENT_TIMESTAMP" | "NOW" | "GETDATE" | "SYSDATE" => TypedFunction::CurrentTimestamp,
9729            "STR_TO_TIME" | "STR_TO_DATE" | "TO_TIMESTAMP" | "PARSE_TIMESTAMP"
9730            | "PARSE_DATETIME" => {
9731                let mut it = args.into_iter();
9732                let expr = it.next()?;
9733                let format = it.next()?;
9734                TypedFunction::StrToTime {
9735                    expr: Box::new(expr),
9736                    format: Box::new(format),
9737                }
9738            }
9739            "TIME_TO_STR" | "DATE_FORMAT" | "FORMAT_TIMESTAMP" | "FORMAT_DATETIME" | "TO_CHAR" => {
9740                let mut it = args.into_iter();
9741                let expr = it.next()?;
9742                let format = it.next()?;
9743                TypedFunction::TimeToStr {
9744                    expr: Box::new(expr),
9745                    format: Box::new(format),
9746                }
9747            }
9748            "TS_OR_DS_TO_DATE" => {
9749                let mut it = args.into_iter();
9750                TypedFunction::TsOrDsToDate {
9751                    expr: Box::new(it.next()?),
9752                }
9753            }
9754            "YEAR" => {
9755                let mut it = args.into_iter();
9756                TypedFunction::Year {
9757                    expr: Box::new(it.next()?),
9758                }
9759            }
9760            "MONTH" => {
9761                let mut it = args.into_iter();
9762                TypedFunction::Month {
9763                    expr: Box::new(it.next()?),
9764                }
9765            }
9766            "DAY" | "DAYOFMONTH" => {
9767                let mut it = args.into_iter();
9768                TypedFunction::Day {
9769                    expr: Box::new(it.next()?),
9770                }
9771            }
9772
9773            // ── String ─────────────────────────────────────────────
9774            "TRIM" => {
9775                let mut it = args.into_iter();
9776                let expr = it.next()?;
9777                let trim_chars = it.next().map(Box::new);
9778                TypedFunction::Trim {
9779                    expr: Box::new(expr),
9780                    trim_type: TrimType::Both,
9781                    trim_chars,
9782                }
9783            }
9784            "LTRIM" => {
9785                let mut it = args.into_iter();
9786                let expr = it.next()?;
9787                TypedFunction::Trim {
9788                    expr: Box::new(expr),
9789                    trim_type: TrimType::Leading,
9790                    trim_chars: None,
9791                }
9792            }
9793            "RTRIM" => {
9794                let mut it = args.into_iter();
9795                let expr = it.next()?;
9796                TypedFunction::Trim {
9797                    expr: Box::new(expr),
9798                    trim_type: TrimType::Trailing,
9799                    trim_chars: None,
9800                }
9801            }
9802            "SUBSTRING" | "SUBSTR" => {
9803                let mut it = args.into_iter();
9804                let expr = it.next()?;
9805                let start = it.next()?;
9806                let length = it.next();
9807                TypedFunction::Substring {
9808                    expr: Box::new(expr),
9809                    start: Box::new(start),
9810                    length: length.map(Box::new),
9811                }
9812            }
9813            "UPPER" | "UCASE" => {
9814                let mut it = args.into_iter();
9815                TypedFunction::Upper {
9816                    expr: Box::new(it.next()?),
9817                }
9818            }
9819            "LOWER" | "LCASE" => {
9820                let mut it = args.into_iter();
9821                TypedFunction::Lower {
9822                    expr: Box::new(it.next()?),
9823                }
9824            }
9825            "REGEXP_LIKE" | "RLIKE" => {
9826                let mut it = args.into_iter();
9827                let expr = it.next()?;
9828                let pattern = it.next()?;
9829                let flags = it.next();
9830                TypedFunction::RegexpLike {
9831                    expr: Box::new(expr),
9832                    pattern: Box::new(pattern),
9833                    flags: flags.map(Box::new),
9834                }
9835            }
9836            "REGEXP_EXTRACT" | "REGEXP_SUBSTR" => {
9837                let mut it = args.into_iter();
9838                let expr = it.next()?;
9839                let pattern = it.next()?;
9840                let group_index = it.next();
9841                TypedFunction::RegexpExtract {
9842                    expr: Box::new(expr),
9843                    pattern: Box::new(pattern),
9844                    group_index: group_index.map(Box::new),
9845                }
9846            }
9847            "REGEXP_REPLACE" => {
9848                let mut it = args.into_iter();
9849                let expr = it.next()?;
9850                let pattern = it.next()?;
9851                let replacement = it.next()?;
9852                let flags = it.next();
9853                TypedFunction::RegexpReplace {
9854                    expr: Box::new(expr),
9855                    pattern: Box::new(pattern),
9856                    replacement: Box::new(replacement),
9857                    flags: flags.map(Box::new),
9858                }
9859            }
9860            "CONCAT_WS" => {
9861                let mut it = args.into_iter();
9862                let separator = it.next()?;
9863                let exprs: Vec<Expr> = it.collect();
9864                TypedFunction::ConcatWs {
9865                    separator: Box::new(separator),
9866                    exprs,
9867                }
9868            }
9869            "SPLIT" | "STRING_SPLIT" => {
9870                let mut it = args.into_iter();
9871                let expr = it.next()?;
9872                let delimiter = it.next()?;
9873                TypedFunction::Split {
9874                    expr: Box::new(expr),
9875                    delimiter: Box::new(delimiter),
9876                }
9877            }
9878            "INITCAP" => {
9879                let mut it = args.into_iter();
9880                TypedFunction::Initcap {
9881                    expr: Box::new(it.next()?),
9882                }
9883            }
9884            "LENGTH" | "LEN" | "CHAR_LENGTH" | "CHARACTER_LENGTH" => {
9885                let mut it = args.into_iter();
9886                TypedFunction::Length {
9887                    expr: Box::new(it.next()?),
9888                }
9889            }
9890            "REPLACE" => {
9891                let mut it = args.into_iter();
9892                let expr = it.next()?;
9893                let from = it.next()?;
9894                let to = it.next()?;
9895                TypedFunction::Replace {
9896                    expr: Box::new(expr),
9897                    from: Box::new(from),
9898                    to: Box::new(to),
9899                }
9900            }
9901            "REVERSE" => {
9902                let mut it = args.into_iter();
9903                TypedFunction::Reverse {
9904                    expr: Box::new(it.next()?),
9905                }
9906            }
9907            "LEFT" => {
9908                let mut it = args.into_iter();
9909                let expr = it.next()?;
9910                let n = it.next()?;
9911                TypedFunction::Left {
9912                    expr: Box::new(expr),
9913                    n: Box::new(n),
9914                }
9915            }
9916            "RIGHT" => {
9917                let mut it = args.into_iter();
9918                let expr = it.next()?;
9919                let n = it.next()?;
9920                TypedFunction::Right {
9921                    expr: Box::new(expr),
9922                    n: Box::new(n),
9923                }
9924            }
9925            "LPAD" => {
9926                let mut it = args.into_iter();
9927                let expr = it.next()?;
9928                let length = it.next()?;
9929                let pad = it.next();
9930                TypedFunction::Lpad {
9931                    expr: Box::new(expr),
9932                    length: Box::new(length),
9933                    pad: pad.map(Box::new),
9934                }
9935            }
9936            "RPAD" => {
9937                let mut it = args.into_iter();
9938                let expr = it.next()?;
9939                let length = it.next()?;
9940                let pad = it.next();
9941                TypedFunction::Rpad {
9942                    expr: Box::new(expr),
9943                    length: Box::new(length),
9944                    pad: pad.map(Box::new),
9945                }
9946            }
9947
9948            // ── Aggregate ──────────────────────────────────────────
9949            "COUNT" => {
9950                let mut it = args.into_iter();
9951                let expr = it.next().unwrap_or(Expr::Wildcard);
9952                TypedFunction::Count {
9953                    expr: Box::new(expr),
9954                    distinct,
9955                }
9956            }
9957            "SUM" => {
9958                let mut it = args.into_iter();
9959                TypedFunction::Sum {
9960                    expr: Box::new(it.next()?),
9961                    distinct,
9962                }
9963            }
9964            "AVG" => {
9965                let mut it = args.into_iter();
9966                TypedFunction::Avg {
9967                    expr: Box::new(it.next()?),
9968                    distinct,
9969                }
9970            }
9971            "MIN" => {
9972                let mut it = args.into_iter();
9973                TypedFunction::Min {
9974                    expr: Box::new(it.next()?),
9975                }
9976            }
9977            "MAX" => {
9978                let mut it = args.into_iter();
9979                TypedFunction::Max {
9980                    expr: Box::new(it.next()?),
9981                }
9982            }
9983            "ARRAY_AGG" | "LIST" | "COLLECT_LIST" => {
9984                let mut it = args.into_iter();
9985                TypedFunction::ArrayAgg {
9986                    expr: Box::new(it.next()?),
9987                    distinct,
9988                }
9989            }
9990            "APPROX_DISTINCT" | "APPROX_COUNT_DISTINCT" => {
9991                let mut it = args.into_iter();
9992                TypedFunction::ApproxDistinct {
9993                    expr: Box::new(it.next()?),
9994                }
9995            }
9996            "VARIANCE" | "VAR_SAMP" | "VAR" => {
9997                let mut it = args.into_iter();
9998                TypedFunction::Variance {
9999                    expr: Box::new(it.next()?),
10000                }
10001            }
10002            "VAR_POP" => {
10003                let mut it = args.into_iter();
10004                TypedFunction::VariancePop {
10005                    expr: Box::new(it.next()?),
10006                }
10007            }
10008            "STDDEV" | "STDDEV_SAMP" => {
10009                let mut it = args.into_iter();
10010                TypedFunction::Stddev {
10011                    expr: Box::new(it.next()?),
10012                }
10013            }
10014            "STDDEV_POP" => {
10015                let mut it = args.into_iter();
10016                TypedFunction::StddevPop {
10017                    expr: Box::new(it.next()?),
10018                }
10019            }
10020
10021            // ── Array ──────────────────────────────────────────────
10022            "ARRAY_CONCAT" | "ARRAY_CAT" => TypedFunction::ArrayConcat { arrays: args },
10023            "ARRAY_CONTAINS" => {
10024                let mut it = args.into_iter();
10025                let array = it.next()?;
10026                let element = it.next()?;
10027                TypedFunction::ArrayContains {
10028                    array: Box::new(array),
10029                    element: Box::new(element),
10030                }
10031            }
10032            "ARRAY_SIZE" | "ARRAY_LENGTH" | "CARDINALITY" => {
10033                let mut it = args.into_iter();
10034                TypedFunction::ArraySize {
10035                    expr: Box::new(it.next()?),
10036                }
10037            }
10038            "EXPLODE" => {
10039                let mut it = args.into_iter();
10040                TypedFunction::Explode {
10041                    expr: Box::new(it.next()?),
10042                }
10043            }
10044            "GENERATE_SERIES" | "SEQUENCE" => {
10045                let mut it = args.into_iter();
10046                let start = it.next()?;
10047                let stop = it.next()?;
10048                let step = it.next();
10049                TypedFunction::GenerateSeries {
10050                    start: Box::new(start),
10051                    stop: Box::new(stop),
10052                    step: step.map(Box::new),
10053                }
10054            }
10055            "FLATTEN" => {
10056                let mut it = args.into_iter();
10057                TypedFunction::Flatten {
10058                    expr: Box::new(it.next()?),
10059                }
10060            }
10061
10062            // ── JSON ───────────────────────────────────────────────
10063            "JSON_EXTRACT" | "JSON_VALUE" => {
10064                let mut it = args.into_iter();
10065                let expr = it.next()?;
10066                let path = it.next()?;
10067                TypedFunction::JSONExtract {
10068                    expr: Box::new(expr),
10069                    path: Box::new(path),
10070                }
10071            }
10072            "JSON_EXTRACT_SCALAR" => {
10073                let mut it = args.into_iter();
10074                let expr = it.next()?;
10075                let path = it.next()?;
10076                TypedFunction::JSONExtractScalar {
10077                    expr: Box::new(expr),
10078                    path: Box::new(path),
10079                }
10080            }
10081            "PARSE_JSON" | "JSON_PARSE" => {
10082                let mut it = args.into_iter();
10083                TypedFunction::ParseJSON {
10084                    expr: Box::new(it.next()?),
10085                }
10086            }
10087            "JSON_FORMAT" | "TO_JSON" | "TO_JSON_STRING" => {
10088                let mut it = args.into_iter();
10089                TypedFunction::JSONFormat {
10090                    expr: Box::new(it.next()?),
10091                }
10092            }
10093
10094            // ── Window ─────────────────────────────────────────────
10095            "ROW_NUMBER" => TypedFunction::RowNumber,
10096            "RANK" => TypedFunction::Rank,
10097            "DENSE_RANK" => TypedFunction::DenseRank,
10098            "NTILE" => {
10099                let mut it = args.into_iter();
10100                TypedFunction::NTile {
10101                    n: Box::new(it.next()?),
10102                }
10103            }
10104            "LEAD" => {
10105                let mut it = args.into_iter();
10106                let expr = it.next()?;
10107                let offset = it.next();
10108                let default = it.next();
10109                TypedFunction::Lead {
10110                    expr: Box::new(expr),
10111                    offset: offset.map(Box::new),
10112                    default: default.map(Box::new),
10113                }
10114            }
10115            "LAG" => {
10116                let mut it = args.into_iter();
10117                let expr = it.next()?;
10118                let offset = it.next();
10119                let default = it.next();
10120                TypedFunction::Lag {
10121                    expr: Box::new(expr),
10122                    offset: offset.map(Box::new),
10123                    default: default.map(Box::new),
10124                }
10125            }
10126            "FIRST_VALUE" => {
10127                let mut it = args.into_iter();
10128                TypedFunction::FirstValue {
10129                    expr: Box::new(it.next()?),
10130                }
10131            }
10132            "LAST_VALUE" => {
10133                let mut it = args.into_iter();
10134                TypedFunction::LastValue {
10135                    expr: Box::new(it.next()?),
10136                }
10137            }
10138
10139            // ── Math ───────────────────────────────────────────────
10140            "ABS" => {
10141                let mut it = args.into_iter();
10142                TypedFunction::Abs {
10143                    expr: Box::new(it.next()?),
10144                }
10145            }
10146            "CEIL" | "CEILING" => {
10147                let mut it = args.into_iter();
10148                TypedFunction::Ceil {
10149                    expr: Box::new(it.next()?),
10150                }
10151            }
10152            "FLOOR" => {
10153                let mut it = args.into_iter();
10154                TypedFunction::Floor {
10155                    expr: Box::new(it.next()?),
10156                }
10157            }
10158            "ROUND" => {
10159                let mut it = args.into_iter();
10160                let expr = it.next()?;
10161                let decimals = it.next();
10162                TypedFunction::Round {
10163                    expr: Box::new(expr),
10164                    decimals: decimals.map(Box::new),
10165                }
10166            }
10167            "LOG" => {
10168                let mut it = args.into_iter();
10169                let expr = it.next()?;
10170                let base = it.next();
10171                TypedFunction::Log {
10172                    expr: Box::new(expr),
10173                    base: base.map(Box::new),
10174                }
10175            }
10176            "LN" => {
10177                let mut it = args.into_iter();
10178                TypedFunction::Ln {
10179                    expr: Box::new(it.next()?),
10180                }
10181            }
10182            "POW" | "POWER" => {
10183                let mut it = args.into_iter();
10184                let base = it.next()?;
10185                let exponent = it.next()?;
10186                TypedFunction::Pow {
10187                    base: Box::new(base),
10188                    exponent: Box::new(exponent),
10189                }
10190            }
10191            "SQRT" => {
10192                let mut it = args.into_iter();
10193                TypedFunction::Sqrt {
10194                    expr: Box::new(it.next()?),
10195                }
10196            }
10197            "GREATEST" => TypedFunction::Greatest { exprs: args },
10198            "LEAST" => TypedFunction::Least { exprs: args },
10199            "MOD" => {
10200                let mut it = args.into_iter();
10201                let left = it.next()?;
10202                let right = it.next()?;
10203                TypedFunction::Mod {
10204                    left: Box::new(left),
10205                    right: Box::new(right),
10206                }
10207            }
10208
10209            // ── Conversion ─────────────────────────────────────────
10210            "HEX" | "TO_HEX" => {
10211                let mut it = args.into_iter();
10212                TypedFunction::Hex {
10213                    expr: Box::new(it.next()?),
10214                }
10215            }
10216            "UNHEX" | "FROM_HEX" => {
10217                let mut it = args.into_iter();
10218                TypedFunction::Unhex {
10219                    expr: Box::new(it.next()?),
10220                }
10221            }
10222            "MD5" => {
10223                let mut it = args.into_iter();
10224                TypedFunction::Md5 {
10225                    expr: Box::new(it.next()?),
10226                }
10227            }
10228            "SHA" | "SHA1" => {
10229                let mut it = args.into_iter();
10230                TypedFunction::Sha {
10231                    expr: Box::new(it.next()?),
10232                }
10233            }
10234            "SHA2" | "SHA256" | "SHA512" => {
10235                let mut it = args.into_iter();
10236                let expr = it.next()?;
10237                let bit_length = it.next().unwrap_or(Expr::Number("256".to_string()));
10238                TypedFunction::Sha2 {
10239                    expr: Box::new(expr),
10240                    bit_length: Box::new(bit_length),
10241                }
10242            }
10243
10244            // Not a recognized typed function
10245            _ => return None,
10246        };
10247
10248        Some(Expr::TypedFunction {
10249            func: tf,
10250            filter: None,
10251            over: None,
10252        })
10253    }
10254
10255    /// Try to extract a DateTimeField from a column-name expression.
10256    fn expr_to_datetime_field(expr: &Expr) -> Option<DateTimeField> {
10257        match expr {
10258            Expr::Column {
10259                name, table: None, ..
10260            } => match name.to_uppercase().as_str() {
10261                "YEAR" => Some(DateTimeField::Year),
10262                "QUARTER" => Some(DateTimeField::Quarter),
10263                "MONTH" => Some(DateTimeField::Month),
10264                "WEEK" => Some(DateTimeField::Week),
10265                "DAY" => Some(DateTimeField::Day),
10266                "HOUR" => Some(DateTimeField::Hour),
10267                "MINUTE" => Some(DateTimeField::Minute),
10268                "SECOND" => Some(DateTimeField::Second),
10269                "MILLISECOND" => Some(DateTimeField::Millisecond),
10270                "MICROSECOND" => Some(DateTimeField::Microsecond),
10271                _ => None,
10272            },
10273            Expr::StringLiteral(s) | Expr::NationalStringLiteral(s) => {
10274                match s.to_uppercase().as_str() {
10275                    "YEAR" => Some(DateTimeField::Year),
10276                    "QUARTER" => Some(DateTimeField::Quarter),
10277                    "MONTH" => Some(DateTimeField::Month),
10278                    "WEEK" => Some(DateTimeField::Week),
10279                    "DAY" => Some(DateTimeField::Day),
10280                    "HOUR" => Some(DateTimeField::Hour),
10281                    "MINUTE" => Some(DateTimeField::Minute),
10282                    "SECOND" => Some(DateTimeField::Second),
10283                    "MILLISECOND" => Some(DateTimeField::Millisecond),
10284                    "MICROSECOND" => Some(DateTimeField::Microsecond),
10285                    _ => None,
10286                }
10287            }
10288            _ => None,
10289        }
10290    }
10291
10292    fn parse_case_expr(&mut self) -> Result<Expr> {
10293        self.expect(TokenType::Case)?;
10294
10295        let operand = if self.peek_type() != &TokenType::When {
10296            Some(Box::new(self.parse_expr()?))
10297        } else {
10298            None
10299        };
10300
10301        let mut when_clauses = Vec::new();
10302        while self.match_token(TokenType::When) {
10303            let condition = self.parse_expr()?;
10304            self.expect(TokenType::Then)?;
10305            let result = self.parse_expr()?;
10306            when_clauses.push((condition, result));
10307        }
10308
10309        let else_clause = if self.match_token(TokenType::Else) {
10310            Some(Box::new(self.parse_expr()?))
10311        } else {
10312            None
10313        };
10314
10315        self.expect(TokenType::End)?;
10316
10317        Ok(Expr::Case {
10318            operand,
10319            when_clauses,
10320            else_clause,
10321        })
10322    }
10323}
10324
10325#[cfg(test)]
10326mod tests {
10327    use super::*;
10328
10329    #[test]
10330    fn test_parse_simple_select() {
10331        let stmt = Parser::new("SELECT a, b FROM t")
10332            .unwrap()
10333            .parse_statement()
10334            .unwrap();
10335        match stmt {
10336            Statement::Select(sel) => {
10337                assert_eq!(sel.columns.len(), 2);
10338                assert!(sel.from.is_some());
10339            }
10340            _ => panic!("Expected SELECT"),
10341        }
10342    }
10343
10344    #[test]
10345    fn test_parse_select_with_where() {
10346        let stmt = Parser::new("SELECT x FROM t WHERE x > 10")
10347            .unwrap()
10348            .parse_statement()
10349            .unwrap();
10350        match stmt {
10351            Statement::Select(sel) => assert!(sel.where_clause.is_some()),
10352            _ => panic!("Expected SELECT"),
10353        }
10354    }
10355
10356    #[test]
10357    fn test_parse_select_wildcard() {
10358        let stmt = Parser::new("SELECT * FROM users")
10359            .unwrap()
10360            .parse_statement()
10361            .unwrap();
10362        match stmt {
10363            Statement::Select(sel) => {
10364                assert_eq!(sel.columns.len(), 1);
10365                assert!(matches!(sel.columns[0], SelectItem::Wildcard));
10366            }
10367            _ => panic!("Expected SELECT"),
10368        }
10369    }
10370
10371    #[test]
10372    fn test_parse_insert() {
10373        let stmt = Parser::new("INSERT INTO t (a, b) VALUES (1, 'hello')")
10374            .unwrap()
10375            .parse_statement()
10376            .unwrap();
10377        match stmt {
10378            Statement::Insert(ins) => {
10379                assert_eq!(ins.table.name, "t");
10380                assert_eq!(ins.columns, vec!["a", "b"]);
10381                match &ins.source {
10382                    InsertSource::Values(rows) => {
10383                        assert_eq!(rows.len(), 1);
10384                        assert_eq!(rows[0].len(), 2);
10385                    }
10386                    _ => panic!("Expected VALUES"),
10387                }
10388            }
10389            _ => panic!("Expected INSERT"),
10390        }
10391    }
10392
10393    #[test]
10394    fn test_parse_delete() {
10395        let stmt = Parser::new("DELETE FROM users WHERE id = 1")
10396            .unwrap()
10397            .parse_statement()
10398            .unwrap();
10399        match stmt {
10400            Statement::Delete(del) => {
10401                assert_eq!(del.table.name, "users");
10402                assert!(del.where_clause.is_some());
10403            }
10404            _ => panic!("Expected DELETE"),
10405        }
10406    }
10407
10408    #[test]
10409    fn test_parse_join() {
10410        let stmt = Parser::new("SELECT a.id, b.name FROM a INNER JOIN b ON a.id = b.a_id")
10411            .unwrap()
10412            .parse_statement()
10413            .unwrap();
10414        match stmt {
10415            Statement::Select(sel) => {
10416                assert_eq!(sel.joins.len(), 1);
10417                assert_eq!(sel.joins[0].join_type, JoinType::Inner);
10418            }
10419            _ => panic!("Expected SELECT"),
10420        }
10421    }
10422
10423    #[test]
10424    fn test_parse_cte() {
10425        let stmt = Parser::new("WITH cte AS (SELECT 1 AS x) SELECT x FROM cte")
10426            .unwrap()
10427            .parse_statement()
10428            .unwrap();
10429        match stmt {
10430            Statement::Select(sel) => {
10431                assert_eq!(sel.ctes.len(), 1);
10432                assert_eq!(sel.ctes[0].name, "cte");
10433            }
10434            _ => panic!("Expected SELECT"),
10435        }
10436    }
10437
10438    #[test]
10439    fn test_parse_union() {
10440        let stmt = Parser::new("SELECT 1 UNION ALL SELECT 2")
10441            .unwrap()
10442            .parse_statement()
10443            .unwrap();
10444        match stmt {
10445            Statement::SetOperation(sop) => {
10446                assert_eq!(sop.op, SetOperationType::Union);
10447                assert!(sop.all);
10448            }
10449            _ => panic!("Expected SetOperation"),
10450        }
10451    }
10452
10453    #[test]
10454    fn test_parse_cast() {
10455        let stmt = Parser::new("SELECT CAST(x AS INT) FROM t")
10456            .unwrap()
10457            .parse_statement()
10458            .unwrap();
10459        match stmt {
10460            Statement::Select(sel) => {
10461                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10462                    assert!(matches!(expr, Expr::Cast { .. }));
10463                }
10464            }
10465            _ => panic!("Expected SELECT"),
10466        }
10467    }
10468
10469    #[test]
10470    fn test_parse_subquery() {
10471        let stmt = Parser::new("SELECT * FROM (SELECT 1 AS x) AS sub")
10472            .unwrap()
10473            .parse_statement()
10474            .unwrap();
10475        match stmt {
10476            Statement::Select(sel) => {
10477                if let Some(from) = &sel.from {
10478                    assert!(matches!(from.source, TableSource::Subquery { .. }));
10479                }
10480            }
10481            _ => panic!("Expected SELECT"),
10482        }
10483    }
10484
10485    #[test]
10486    fn cr014_paren_setop_derived_table_parses() {
10487        // CR-014: a parenthesised set operation used as a derived table, where
10488        // each branch is itself parenthesised, must parse. Previously failed
10489        // with `Expected RParen, got Except/Union/Intersect`.
10490        for op in ["EXCEPT", "UNION", "UNION ALL", "INTERSECT"] {
10491            let sql = format!("SELECT count(*) FROM ((SELECT 1) {op} (SELECT 2)) x");
10492            assert!(
10493                Parser::new(&sql).unwrap().parse_statements().is_ok(),
10494                "must parse: {sql}"
10495            );
10496        }
10497    }
10498
10499    #[test]
10500    fn cr014_chained_except_derived_table_parses() {
10501        // TPC-DS q87 shape: chained EXCEPT of parenthesised branches.
10502        let sql = "SELECT count(*) FROM ((SELECT 1 AS a) EXCEPT (SELECT 2 AS a) \
10503                   EXCEPT (SELECT 3 AS a)) cool_cust";
10504        let stmt = Parser::new(sql).unwrap().parse_statement().unwrap();
10505        match stmt {
10506            Statement::Select(sel) => {
10507                let from = sel.from.expect("FROM clause present");
10508                match from.source {
10509                    TableSource::Subquery { query, alias, .. } => {
10510                        assert_eq!(alias.as_deref(), Some("cool_cust"));
10511                        assert!(matches!(*query, Statement::SetOperation(_)));
10512                    }
10513                    _ => panic!("Expected subquery derived table"),
10514                }
10515            }
10516            _ => panic!("Expected SELECT"),
10517        }
10518    }
10519
10520    #[test]
10521    fn cr014_controls_still_parse() {
10522        // Redundant nesting and no-branch-parens set-op were already OK; keep
10523        // them green. The parenthesised-join derived table must also still
10524        // parse (no regression from removing the paren-counting heuristic).
10525        for sql in [
10526            "SELECT count(*) FROM ((SELECT 1)) x",
10527            "SELECT count(*) FROM (SELECT 1 EXCEPT SELECT 2) x",
10528            "SELECT * FROM (a JOIN b ON a.id = b.id) x",
10529        ] {
10530            assert!(
10531                Parser::new(sql).unwrap().parse_statements().is_ok(),
10532                "must parse: {sql}"
10533            );
10534        }
10535    }
10536
10537    #[test]
10538    fn test_parse_exists() {
10539        let stmt = Parser::new("SELECT * FROM t WHERE EXISTS (SELECT 1 FROM t2)")
10540            .unwrap()
10541            .parse_statement()
10542            .unwrap();
10543        match stmt {
10544            Statement::Select(sel) => {
10545                assert!(sel.where_clause.is_some());
10546            }
10547            _ => panic!("Expected SELECT"),
10548        }
10549    }
10550
10551    #[test]
10552    fn test_parse_window_function() {
10553        let stmt = Parser::new(
10554            "SELECT ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) FROM emp",
10555        )
10556        .unwrap()
10557        .parse_statement()
10558        .unwrap();
10559        match stmt {
10560            Statement::Select(sel) => {
10561                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10562                    match expr {
10563                        Expr::TypedFunction { over, .. } => {
10564                            assert!(over.is_some());
10565                        }
10566                        Expr::Function { over, .. } => {
10567                            assert!(over.is_some());
10568                        }
10569                        _ => panic!("Expected function"),
10570                    }
10571                }
10572            }
10573            _ => panic!("Expected SELECT"),
10574        }
10575    }
10576
10577    #[test]
10578    fn test_parse_multiple_statements() {
10579        let stmts = Parser::new("SELECT 1; SELECT 2;")
10580            .unwrap()
10581            .parse_statements()
10582            .unwrap();
10583        assert_eq!(stmts.len(), 2);
10584    }
10585
10586    #[test]
10587    fn test_parse_insert_select() {
10588        let stmt = Parser::new("INSERT INTO t SELECT * FROM s")
10589            .unwrap()
10590            .parse_statement()
10591            .unwrap();
10592        match stmt {
10593            Statement::Insert(ins) => {
10594                assert!(matches!(ins.source, InsertSource::Query(_)));
10595            }
10596            _ => panic!("Expected INSERT"),
10597        }
10598    }
10599
10600    #[test]
10601    fn test_parse_create_table_constraints() {
10602        let stmt =
10603            Parser::new("CREATE TABLE t (id INT PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE)")
10604                .unwrap()
10605                .parse_statement()
10606                .unwrap();
10607        match stmt {
10608            Statement::CreateTable(ct) => {
10609                assert_eq!(ct.columns.len(), 2);
10610                assert!(ct.columns[0].primary_key);
10611                assert!(ct.columns[1].unique);
10612            }
10613            _ => panic!("Expected CREATE TABLE"),
10614        }
10615    }
10616
10617    #[test]
10618    fn test_parse_extract() {
10619        let stmt = Parser::new("SELECT EXTRACT(YEAR FROM created_at) FROM t")
10620            .unwrap()
10621            .parse_statement()
10622            .unwrap();
10623        match stmt {
10624            Statement::Select(sel) => {
10625                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10626                    assert!(matches!(expr, Expr::Extract { .. }));
10627                }
10628            }
10629            _ => panic!("Expected SELECT"),
10630        }
10631    }
10632
10633    #[test]
10634    fn test_parse_postgres_cast() {
10635        let stmt = Parser::new("SELECT x::int FROM t")
10636            .unwrap()
10637            .parse_statement()
10638            .unwrap();
10639        match stmt {
10640            Statement::Select(sel) => {
10641                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10642                    assert!(matches!(expr, Expr::Cast { .. }));
10643                }
10644            }
10645            _ => panic!("Expected SELECT"),
10646        }
10647    }
10648
10649    #[test]
10650    fn test_parse_on_conflict_expression_targets() {
10651        let stmt = Parser::new(
10652            "INSERT INTO t VALUES (1, 'Crowberry') ON CONFLICT (lower(fruit) collate \"C\" text_pattern_ops, key) DO NOTHING",
10653        )
10654        .unwrap()
10655        .parse_statement()
10656        .unwrap();
10657
10658        match stmt {
10659            Statement::Insert(ins) => {
10660                let on_conflict = ins.on_conflict.expect("Expected ON CONFLICT");
10661                assert_eq!(on_conflict.columns.len(), 2);
10662                assert!(on_conflict.columns[0].starts_with("lower"));
10663                assert!(on_conflict.columns[0].contains("text_pattern_ops"));
10664                assert_eq!(on_conflict.columns[1], "key");
10665            }
10666            _ => panic!("Expected INSERT"),
10667        }
10668    }
10669
10670    #[test]
10671    fn test_parse_postgres_operator_sequences() {
10672        let cases = [
10673            "SELECT * FROM box_temp WHERE f1 <<| '(10,4.33334),(5,100)'",
10674            "SELECT * FROM box_temp WHERE f1 &<| '(10,4.3333334),(5,1)'",
10675            "SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth'",
10676        ];
10677
10678        for sql in &cases {
10679            let stmt = Parser::new(sql).unwrap().parse_statement().unwrap();
10680            assert!(matches!(stmt, Statement::Select(_)));
10681        }
10682    }
10683}
10684
10685/// Attach comments to the appropriate field on a parsed statement.
10686fn attach_comments_to_statement(stmt: &mut Statement, comments: Vec<String>) {
10687    match stmt {
10688        Statement::Select(s) => s.comments = comments,
10689        Statement::Insert(s) => s.comments = comments,
10690        Statement::Update(s) => s.comments = comments,
10691        Statement::Delete(s) => s.comments = comments,
10692        Statement::CreateTable(s) => s.comments = comments,
10693        Statement::DropTable(s) => s.comments = comments,
10694        Statement::SetOperation(s) => s.comments = comments,
10695        Statement::AlterTable(s) => s.comments = comments,
10696        Statement::CreateView(s) => s.comments = comments,
10697        Statement::DropView(s) => s.comments = comments,
10698        Statement::Truncate(s) => s.comments = comments,
10699        Statement::Explain(s) => s.comments = comments,
10700        Statement::Use(s) => s.comments = comments,
10701        Statement::Merge(s) => s.comments = comments,
10702        Statement::Command(s) => s.comments = comments,
10703        // Transaction and Expression don't have comment fields
10704        Statement::Transaction(_) | Statement::Expression(_) => {}
10705    }
10706}