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            return Ok(Some(self.expect_name_with_quote()?));
2238        }
2239        // Implicit alias
2240        if self.is_name_token() {
2241            let peeked_upper = self.peek().value.to_uppercase();
2242            if !matches!(
2243                peeked_upper.as_str(),
2244                "FROM"
2245                    | "WHERE"
2246                    | "GROUP"
2247                    | "ORDER"
2248                    | "LIMIT"
2249                    | "HAVING"
2250                    | "UNION"
2251                    | "INTERSECT"
2252                    | "EXCEPT"
2253                    | "JOIN"
2254                    | "INNER"
2255                    | "LEFT"
2256                    | "RIGHT"
2257                    | "FULL"
2258                    | "CROSS"
2259                    | "ON"
2260                    | "WINDOW"
2261                    | "QUALIFY"
2262                    | "INTO"
2263                    | "SET"
2264                    | "RETURNING"
2265                    | "PIVOT"
2266                    | "UNPIVOT"
2267                    | "PREWHERE"
2268                    | "SETTINGS"
2269                    | "FORMAT"
2270                    | "SAMPLE"
2271                    | "TABLESAMPLE"
2272                    | "LATERAL"
2273                    | "USING"
2274                    | "OFFSET"
2275                    | "FETCH"
2276                    | "FOR"
2277                    | "WITH"
2278                    | "OPTION"
2279                    | "MATCH_RECOGNIZE"
2280                    | "SORT"
2281                    | "DISTRIBUTE"
2282                    | "CLUSTER"
2283                    | "GLOBAL"
2284                    | "PREFERRING"
2285                    | "FORCE"
2286                    | "USE"
2287                    | "IGNORE"
2288                    | "STRAIGHT_JOIN"
2289                    | "DISTRIBUTED"
2290                    | "VALUE"
2291                    | "VALUES"
2292                    | "DEFAULT"
2293                    | "PARTITION"
2294            ) {
2295                let token = self.advance().clone();
2296                let qs = quote_style_from_char(token.quote_char);
2297                return Ok(Some((token.value.clone(), qs)));
2298            }
2299        }
2300        Ok(None)
2301    }
2302
2303    fn parse_table_source(&mut self) -> Result<TableSource> {
2304        let mut source = self.parse_base_table_source()?;
2305        // PostgreSQL table-inheritance star: `FROM parent*` includes all
2306        // child tables. Swallow the trailing `*` so the table alias /
2307        // joins continue to parse.
2308        let _ = self.match_token(TokenType::Star);
2309        // BigQuery / Snowflake / MySQL TiDB time-travel:
2310        //   `<tbl> [FOR SYSTEM_TIME] AS OF [TIMESTAMP] <expr>` or
2311        //   `<tbl> AS OF VERSION <expr>` / `AS OF TIMESTAMP <expr>`.
2312        // We don't model the time-travel clause in the AST; swallow the
2313        // keywords and the expression so the surrounding query parses.
2314        if self.is_name_token()
2315            && self.peek().value.eq_ignore_ascii_case("FOR")
2316            && self
2317                .peek_offset(1)
2318                .map(|t| t.value.eq_ignore_ascii_case("SYSTEM_TIME"))
2319                .unwrap_or(false)
2320        {
2321            self.advance(); // FOR
2322            self.advance(); // SYSTEM_TIME
2323        }
2324        if self.peek_type() == &TokenType::As
2325            && self
2326                .peek_offset(1)
2327                .map(|t| t.value.eq_ignore_ascii_case("OF"))
2328                .unwrap_or(false)
2329        {
2330            self.advance(); // AS
2331            self.advance(); // OF
2332            // Optional TIMESTAMP / VERSION qualifier.
2333            if matches!(self.peek_type(), TokenType::Timestamp)
2334                || (self.is_name_token()
2335                    && matches!(
2336                        self.peek().value.to_uppercase().as_str(),
2337                        "VERSION" | "SCN" | "SEQUENCE"
2338                    ))
2339            {
2340                self.advance();
2341            }
2342            let _ = self.parse_expr()?;
2343        }
2344        // Hive / Spark / Trino `TABLESAMPLE [method] (...)` after a table
2345        // reference. We don't model the sample clause in the AST; just
2346        // consume the optional method identifier (BERNOULLI / SYSTEM /
2347        // RESERVOIR) and the parenthesized body so the surrounding query
2348        // parses. Also accept an optional `REPEATABLE (n)` trailer.
2349        if self.match_token(TokenType::Tablesample) {
2350            // Optional sampling method identifier.
2351            if matches!(self.peek_type(), TokenType::Identifier) {
2352                self.advance();
2353            }
2354            if self.match_token(TokenType::LParen) {
2355                let mut depth = 1;
2356                while depth > 0 {
2357                    match self.peek_type() {
2358                        TokenType::LParen => depth += 1,
2359                        TokenType::RParen => {
2360                            depth -= 1;
2361                            if depth == 0 {
2362                                self.advance();
2363                                break;
2364                            }
2365                        }
2366                        TokenType::Eof => break,
2367                        _ => {}
2368                    }
2369                    self.advance();
2370                }
2371            }
2372            if self.check_keyword("REPEATABLE") {
2373                self.advance();
2374                if self.match_token(TokenType::LParen) {
2375                    let mut depth = 1;
2376                    while depth > 0 {
2377                        match self.peek_type() {
2378                            TokenType::LParen => depth += 1,
2379                            TokenType::RParen => {
2380                                depth -= 1;
2381                                if depth == 0 {
2382                                    self.advance();
2383                                    break;
2384                                }
2385                            }
2386                            TokenType::Eof => break,
2387                            _ => {}
2388                        }
2389                        self.advance();
2390                    }
2391                }
2392            }
2393            // Optional trailing alias on the sampled table — `… TABLESAMPLE
2394            // (…) s`. We attach it to the underlying table reference when
2395            // possible, otherwise just consume the identifier.
2396            if let TableSource::Table(ref mut tr) = source {
2397                if tr.alias.is_none() {
2398                    if let Some((name, qs)) = self.parse_optional_alias()? {
2399                        tr.alias = Some(name);
2400                        tr.alias_quote_style = qs;
2401                    }
2402                }
2403            }
2404        }
2405        // Check for trailing PIVOT / UNPIVOT
2406        let source = self.parse_pivot_or_unpivot(source)?;
2407        // ClickHouse: `SELECT * FROM t SAMPLE 0.1` (no parens) — and the
2408        // optional `OFFSET m` modifier. The keyword tokenizes as a plain
2409        // identifier so this also handles dialects that don't reserve it.
2410        if self.check_keyword("SAMPLE") {
2411            self.advance();
2412            // Accept a number, identifier, or parenthesized expression.
2413            if matches!(self.peek_type(), TokenType::Number) {
2414                self.advance();
2415                // Optional `/ N` ratio.
2416                if self.peek_type() == &TokenType::Slash {
2417                    self.advance();
2418                    if matches!(self.peek_type(), TokenType::Number) {
2419                        self.advance();
2420                    }
2421                }
2422            }
2423            if self.check_keyword("OFFSET") {
2424                self.advance();
2425                if matches!(self.peek_type(), TokenType::Number) {
2426                    self.advance();
2427                }
2428            }
2429        }
2430        Ok(source)
2431    }
2432
2433    fn parse_base_table_source(&mut self) -> Result<TableSource> {
2434        // LATERAL
2435        if self.match_token(TokenType::Lateral) {
2436            let source = self.parse_table_source()?;
2437            return Ok(TableSource::Lateral {
2438                source: Box::new(source),
2439            });
2440        }
2441
2442        // Spark / DuckDB / Postgres `FROM VALUES (...) [, (...)]+ [alias[(cols)]]`
2443        // (un-parenthesised VALUES list). Swallow the rows.
2444        if self.match_token(TokenType::Values) {
2445            // First row.
2446            if self.match_token(TokenType::LParen) {
2447                let mut depth = 1;
2448                while depth > 0 {
2449                    match self.peek_type() {
2450                        TokenType::LParen => depth += 1,
2451                        TokenType::RParen => {
2452                            depth -= 1;
2453                            if depth == 0 {
2454                                self.advance();
2455                                break;
2456                            }
2457                        }
2458                        TokenType::Eof => break,
2459                        _ => {}
2460                    }
2461                    self.advance();
2462                }
2463            }
2464            // Additional rows.
2465            while self.peek_type() == &TokenType::Comma {
2466                let saved = self.pos;
2467                self.advance();
2468                if !self.match_token(TokenType::LParen) {
2469                    // Not a row — restore comma for the outer parser.
2470                    self.pos = saved;
2471                    break;
2472                }
2473                let mut depth = 1;
2474                while depth > 0 {
2475                    match self.peek_type() {
2476                        TokenType::LParen => depth += 1,
2477                        TokenType::RParen => {
2478                            depth -= 1;
2479                            if depth == 0 {
2480                                self.advance();
2481                                break;
2482                            }
2483                        }
2484                        TokenType::Eof => break,
2485                        _ => {}
2486                    }
2487                    self.advance();
2488                }
2489            }
2490            let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2491                Some((name, qs)) => (Some(name), qs),
2492                None => (None, QuoteStyle::None),
2493            };
2494            if alias.is_some() && self.peek_type() == &TokenType::LParen {
2495                let saved = self.pos;
2496                self.advance();
2497                let mut ok = true;
2498                loop {
2499                    if !self.is_name_token() {
2500                        ok = false;
2501                        break;
2502                    }
2503                    self.advance();
2504                    if self.match_token(TokenType::RParen) {
2505                        break;
2506                    }
2507                    if !self.match_token(TokenType::Comma) {
2508                        ok = false;
2509                        break;
2510                    }
2511                }
2512                if !ok {
2513                    self.pos = saved;
2514                }
2515            }
2516            return Ok(TableSource::TableFunction {
2517                name: "VALUES".to_string(),
2518                args: vec![],
2519                alias,
2520                alias_quote_style,
2521            });
2522        }
2523
2524        // UNNEST(expr)
2525        if self.match_token(TokenType::Unnest) {
2526            self.expect(TokenType::LParen)?;
2527            let expr = self.parse_expr()?;
2528            // Multi-arg form (Trino): UNNEST(a, b, c). Drop extras.
2529            while self.match_token(TokenType::Comma) {
2530                let _ = self.parse_expr()?;
2531            }
2532            self.expect(TokenType::RParen)?;
2533            let (mut alias, mut alias_quote_style) = match self.parse_optional_alias()? {
2534                Some((name, qs)) => (Some(name), qs),
2535                None => (None, QuoteStyle::None),
2536            };
2537            // BigQuery `WITH OFFSET [AS name]` / Postgres `WITH ORDINALITY`.
2538            let mut with_offset = false;
2539            if self.check_keyword("WITH") {
2540                let saved = self.pos;
2541                self.advance();
2542                if self.check_keyword("OFFSET") || self.check_keyword("ORDINALITY") {
2543                    self.advance();
2544                    with_offset = true;
2545                    // Optional alias after OFFSET / ORDINALITY.
2546                    if alias.is_none() {
2547                        if let Some((n, qs)) = self.parse_optional_alias()? {
2548                            alias = Some(n);
2549                            alias_quote_style = qs;
2550                        }
2551                    } else if self.is_name_token() {
2552                        // `UNNEST(a) id WITH OFFSET pos` — extra trailing
2553                        // name; absorb so we don't trip the join parser.
2554                        self.advance();
2555                    }
2556                } else {
2557                    self.pos = saved;
2558                }
2559            }
2560            // Optional positional column list: `AS t (n, a)`.
2561            if alias.is_some() && self.peek_type() == &TokenType::LParen {
2562                let saved = self.pos;
2563                self.advance();
2564                let mut ok = true;
2565                loop {
2566                    if !self.is_name_token() {
2567                        ok = false;
2568                        break;
2569                    }
2570                    self.advance();
2571                    if self.match_token(TokenType::RParen) {
2572                        break;
2573                    }
2574                    if !self.match_token(TokenType::Comma) {
2575                        ok = false;
2576                        break;
2577                    }
2578                }
2579                if !ok {
2580                    self.pos = saved;
2581                }
2582            }
2583            return Ok(TableSource::Unnest {
2584                expr: Box::new(expr),
2585                alias,
2586                alias_quote_style,
2587                with_offset,
2588            });
2589        }
2590
2591        // Subquery: (SELECT ...)
2592        if self.peek_type() == &TokenType::LParen {
2593            let saved = self.pos;
2594            self.advance();
2595            // A derived-table body is a subquery when it begins with a statement
2596            // keyword, or with another `(` when the body is itself a
2597            // parenthesised (possibly set-operation) query — e.g. redundant
2598            // nesting `((SELECT …))` or a set operation whose branches are each
2599            // parenthesised `((SELECT …) EXCEPT (SELECT …))`. Delegate to the
2600            // recursive statement parser (the same path the top-level set-op
2601            // parser uses) rather than hand-counting parens, which cannot tell
2602            // redundant wrapping apart from a parenthesised first branch
2603            // (CR-014).
2604            let direct_subquery = matches!(
2605                self.peek_type(),
2606                TokenType::Select
2607                    | TokenType::With
2608                    | TokenType::Explain
2609                    | TokenType::From
2610                    | TokenType::Describe
2611                    | TokenType::Show
2612                    | TokenType::Table
2613            );
2614            // A `(`-led body may instead be a parenthesised join / table list
2615            // (`((t1 JOIN t2)) alias`). Attempt the subquery interpretation and,
2616            // on failure, restore to `saved` so the parenthesised-join handling
2617            // further below still runs.
2618            let paren_body = self.peek_type() == &TokenType::LParen;
2619            let mut subquery: Option<Statement> = None;
2620            if direct_subquery {
2621                let query = self.parse_statement_inner()?;
2622                // Set operations across parenthesised subqueries: `(SELECT …)
2623                // UNION ALL (SELECT …) [ORDER BY …] [LIMIT …]`.
2624                let query = self.maybe_parse_set_operation(query)?;
2625                self.expect(TokenType::RParen)?;
2626                subquery = Some(query);
2627            } else if paren_body {
2628                let attempt = self
2629                    .parse_statement_inner()
2630                    .and_then(|q| self.maybe_parse_set_operation(q));
2631                match attempt {
2632                    Ok(query) if self.peek_type() == &TokenType::RParen => {
2633                        self.advance();
2634                        subquery = Some(query);
2635                    }
2636                    _ => self.pos = saved,
2637                }
2638            }
2639            if let Some(query) = subquery {
2640                let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2641                    Some((name, qs)) => (Some(name), qs),
2642                    None => (None, QuoteStyle::None),
2643                };
2644                // Positional column-list alias: `(SELECT ...) t(c1, c2)`
2645                if alias.is_some() && self.peek_type() == &TokenType::LParen {
2646                    let saved2 = self.pos;
2647                    self.advance();
2648                    let mut ok = true;
2649                    loop {
2650                        if !self.is_name_token() {
2651                            ok = false;
2652                            break;
2653                        }
2654                        self.advance();
2655                        if self.match_token(TokenType::RParen) {
2656                            break;
2657                        }
2658                        if !self.match_token(TokenType::Comma) {
2659                            ok = false;
2660                            break;
2661                        }
2662                    }
2663                    if !ok {
2664                        self.pos = saved2;
2665                    }
2666                }
2667                return Ok(TableSource::Subquery {
2668                    query: Box::new(query),
2669                    alias,
2670                    alias_quote_style,
2671                });
2672            }
2673            // `(VALUES (...), (...)) alias[(cols)]` — common in DuckDB /
2674            // Postgres derived tables. We don't model the VALUES rows in the
2675            // AST as a table source; swallow the parenthesized body and
2676            // synthesise an empty subquery placeholder.
2677            if self.peek_type() == &TokenType::Values {
2678                // Re-advance past the values list, balancing parens (we are
2679                // inside the outer LParen at depth 1).
2680                let mut depth = 1;
2681                while depth > 0 {
2682                    match self.peek_type() {
2683                        TokenType::LParen => depth += 1,
2684                        TokenType::RParen => {
2685                            depth -= 1;
2686                            if depth == 0 {
2687                                self.advance();
2688                                break;
2689                            }
2690                        }
2691                        TokenType::Eof => break,
2692                        _ => {}
2693                    }
2694                    self.advance();
2695                }
2696                let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2697                    Some((name, qs)) => (Some(name), qs),
2698                    None => (None, QuoteStyle::None),
2699                };
2700                if alias.is_some() && self.peek_type() == &TokenType::LParen {
2701                    let saved2 = self.pos;
2702                    self.advance();
2703                    let mut ok = true;
2704                    loop {
2705                        if !self.is_name_token() {
2706                            ok = false;
2707                            break;
2708                        }
2709                        self.advance();
2710                        if self.match_token(TokenType::RParen) {
2711                            break;
2712                        }
2713                        if !self.match_token(TokenType::Comma) {
2714                            ok = false;
2715                            break;
2716                        }
2717                    }
2718                    if !ok {
2719                        self.pos = saved2;
2720                    }
2721                }
2722                // Synthesise an empty values placeholder. Reuse Subquery
2723                // with a single-row Insert wrapper is awkward; instead,
2724                // wrap as a TableFunction("VALUES") with empty args.
2725                return Ok(TableSource::TableFunction {
2726                    name: "VALUES".to_string(),
2727                    args: vec![],
2728                    alias,
2729                    alias_quote_style,
2730                });
2731            }
2732            self.pos = saved;
2733
2734            // MySQL / SQLite / others permit parenthesized join expressions
2735            // as a table source: `(t1 LEFT JOIN t2 ON …)` or comma-list
2736            // `(t1, t2)`. Recurse into the parens, then consume joins /
2737            // commas until the matching `)`. Emit the first source so the
2738            // surrounding query parses; trailing tables are discarded
2739            // (their predicates were already parsed into the JOIN node we
2740            // throw away — acceptance only).
2741            if self.peek_type() == &TokenType::LParen {
2742                let inner_saved = self.pos;
2743                self.advance();
2744                let after_lparen = self.pos;
2745                if let Ok(inner) = self.parse_table_source() {
2746                    let _ = self.parse_joins();
2747                    while self.match_token(TokenType::Comma) {
2748                        if self.parse_table_source().is_err() {
2749                            self.pos = inner_saved;
2750                            // Fall through to the generic parse_table_ref
2751                            // path below, which will surface the original
2752                            // error message.
2753                            break;
2754                        }
2755                        let _ = self.parse_joins();
2756                    }
2757                    if self.pos != inner_saved && self.match_token(TokenType::RParen) {
2758                        let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2759                            Some((name, qs)) => (Some(name), qs),
2760                            None => (None, QuoteStyle::None),
2761                        };
2762                        if let Some(name) = alias.clone() {
2763                            if let TableSource::Table(mut tr) = inner {
2764                                tr.alias = Some(name);
2765                                tr.alias_quote_style = alias_quote_style;
2766                                return Ok(TableSource::Table(tr));
2767                            }
2768                        }
2769                        return Ok(inner);
2770                    }
2771                }
2772                // Restore so the caller sees the LParen and emits a useful
2773                // error rather than silently misparsing partial state.
2774                self.pos = inner_saved;
2775                let _ = after_lparen; // suppress unused warning when build optimises
2776            }
2777        }
2778
2779        // Regular table reference (possibly with function syntax)
2780        let table_ref = self.parse_table_ref()?;
2781
2782        // MySQL / TiDB partition selector: `tbl PARTITION (p0, p1)`. Swallow
2783        // it so the table reference parses cleanly.
2784        if matches!(self.peek_type(), TokenType::Partition)
2785            && matches!(
2786                self.peek_offset(1).map(|t| &t.token_type),
2787                Some(TokenType::LParen)
2788            )
2789        {
2790            self.advance();
2791            self.advance();
2792            while !matches!(self.peek_type(), TokenType::RParen | TokenType::Eof) {
2793                self.advance();
2794            }
2795            let _ = self.match_token(TokenType::RParen);
2796        }
2797
2798        // Check if it's actually a table function: name(args...). Also
2799        // accept dotted qualifiers so DuckDB `schema.func(...)` /
2800        // `catalog.schema.func(...)` parse.
2801        if self.peek_type() == &TokenType::LParen {
2802            // SQL/PGQ `GRAPH_TABLE(graph MATCH … COLUMNS (…))`,
2803            // SQL/XML `XMLTABLE('xpath' PASSING expr COLUMNS …)`,
2804            // SQL/JSON `JSON_TABLE(expr, '$' COLUMNS (…))`. Swallow the
2805            // body opaquely so the rest of the query parses.
2806            let fname = table_ref.name.to_uppercase();
2807            if matches!(
2808                fname.as_str(),
2809                "GRAPH_TABLE" | "XMLTABLE" | "JSON_TABLE" | "OPENJSON" | "OPENROWSET" | "OPENXML"
2810            ) {
2811                self.advance();
2812                let mut depth = 1usize;
2813                while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
2814                    match self.peek_type() {
2815                        TokenType::LParen => depth += 1,
2816                        TokenType::RParen => {
2817                            depth -= 1;
2818                            if depth == 0 {
2819                                self.advance();
2820                                break;
2821                            }
2822                        }
2823                        _ => {}
2824                    }
2825                    self.advance();
2826                }
2827                let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2828                    Some((name, qs)) => (Some(name), qs),
2829                    None => (None, QuoteStyle::None),
2830                };
2831                if alias.is_some() && self.peek_type() == &TokenType::LParen {
2832                    let saved = self.pos;
2833                    self.advance();
2834                    let mut ok = true;
2835                    loop {
2836                        if !self.is_name_token() {
2837                            ok = false;
2838                            break;
2839                        }
2840                        self.advance();
2841                        if self.match_token(TokenType::RParen) {
2842                            break;
2843                        }
2844                        if !self.match_token(TokenType::Comma) {
2845                            ok = false;
2846                            break;
2847                        }
2848                    }
2849                    if !ok {
2850                        self.pos = saved;
2851                    }
2852                }
2853                return Ok(TableSource::TableFunction {
2854                    name: match (&table_ref.catalog, &table_ref.schema) {
2855                        (Some(c), Some(s)) => format!("{}.{}.{}", c, s, table_ref.name),
2856                        (None, Some(s)) => format!("{}.{}", s, table_ref.name),
2857                        _ => table_ref.name,
2858                    },
2859                    args: vec![],
2860                    alias,
2861                    alias_quote_style,
2862                });
2863            }
2864            self.advance();
2865            // Hive `noop(on tbl partition by ... order by ... )` table-valued
2866            // function. Arguments start with the `ON` keyword and include
2867            // PARTITION/ORDER/CLUSTER/DISTRIBUTE/SORT BY clauses we don't
2868            // model. Swallow the body opaquely.
2869            let args = if matches!(self.peek_type(), TokenType::On) {
2870                let mut depth = 0usize;
2871                while !matches!(self.peek_type(), TokenType::Eof) {
2872                    match self.peek_type() {
2873                        TokenType::LParen => depth += 1,
2874                        TokenType::RParen => {
2875                            if depth == 0 {
2876                                break;
2877                            }
2878                            depth -= 1;
2879                        }
2880                        _ => {}
2881                    }
2882                    self.advance();
2883                }
2884                vec![]
2885            } else if self.peek_type() != &TokenType::RParen {
2886                self.parse_expr_list()?
2887            } else {
2888                vec![]
2889            };
2890            self.expect(TokenType::RParen)?;
2891            let (alias, alias_quote_style) = match self.parse_optional_alias()? {
2892                Some((name, qs)) => (Some(name), qs),
2893                None => (None, QuoteStyle::None),
2894            };
2895            // DuckDB / Postgres positional column-list alias:
2896            //   range(10) t(i)   →   alias = "t", columns = (i)
2897            // We consume the parenthesized list but do not model it in the AST.
2898            if alias.is_some() && self.peek_type() == &TokenType::LParen {
2899                let saved = self.pos;
2900                self.advance();
2901                let mut ok = true;
2902                loop {
2903                    if !self.is_name_token() {
2904                        ok = false;
2905                        break;
2906                    }
2907                    self.advance();
2908                    if self.match_token(TokenType::RParen) {
2909                        break;
2910                    }
2911                    if !self.match_token(TokenType::Comma) {
2912                        ok = false;
2913                        break;
2914                    }
2915                }
2916                if !ok {
2917                    self.pos = saved;
2918                }
2919            }
2920            return Ok(TableSource::TableFunction {
2921                name: match (&table_ref.catalog, &table_ref.schema) {
2922                    (Some(c), Some(s)) => format!("{}.{}.{}", c, s, table_ref.name),
2923                    (None, Some(s)) => format!("{}.{}", s, table_ref.name),
2924                    _ => table_ref.name,
2925                },
2926                args,
2927                alias,
2928                alias_quote_style,
2929            });
2930        }
2931
2932        // Also support positional column-list alias on a plain table reference:
2933        //   FROM tbl t(c1, c2)
2934        if self.peek_type() == &TokenType::LParen && table_ref.alias.is_some() {
2935            let saved = self.pos;
2936            self.advance();
2937            let mut ok = true;
2938            loop {
2939                if !self.is_name_token() {
2940                    ok = false;
2941                    break;
2942                }
2943                self.advance();
2944                if self.match_token(TokenType::RParen) {
2945                    break;
2946                }
2947                if !self.match_token(TokenType::Comma) {
2948                    ok = false;
2949                    break;
2950                }
2951            }
2952            if !ok {
2953                self.pos = saved;
2954            }
2955        }
2956
2957        // MySQL / MariaDB index hints — `USE INDEX (idx)`, `FORCE INDEX (idx)`,
2958        // `IGNORE INDEX (idx)`, optionally with `FOR JOIN|ORDER BY|GROUP BY`.
2959        // Swallow any sequence of these so the rest of the query parses.
2960        loop {
2961            let saved = self.pos;
2962            let is_hint = matches!(self.peek_type(), TokenType::Use | TokenType::Ignore)
2963                || self.check_keyword("FORCE");
2964            if !is_hint {
2965                break;
2966            }
2967            self.advance();
2968            if !self.check_keyword("INDEX") && !self.check_keyword("KEY") {
2969                self.pos = saved;
2970                break;
2971            }
2972            self.advance();
2973            // Optional `FOR JOIN | FOR ORDER BY | FOR GROUP BY`.
2974            if self.match_keyword("FOR") {
2975                if matches!(
2976                    self.peek_type(),
2977                    TokenType::Join | TokenType::Order | TokenType::Group
2978                ) {
2979                    self.advance();
2980                    let _ = self.match_token(TokenType::By);
2981                }
2982            }
2983            if self.match_token(TokenType::LParen) {
2984                let mut depth = 1;
2985                while depth > 0 {
2986                    match self.peek_type() {
2987                        TokenType::LParen => depth += 1,
2988                        TokenType::RParen => {
2989                            depth -= 1;
2990                            if depth == 0 {
2991                                self.advance();
2992                                break;
2993                            }
2994                        }
2995                        TokenType::Eof => break,
2996                        _ => {}
2997                    }
2998                    self.advance();
2999                }
3000            }
3001        }
3002
3003        // ClickHouse `FROM tbl [AS alias] FINAL` — swallow the FINAL modifier.
3004        // The token tokenizes as Identifier so check_keyword is enough.
3005        if self.check_keyword("FINAL") {
3006            self.advance();
3007        }
3008
3009        // MySQL: `FROM t PARTITION (p0[, p1, ...])` — swallow partition
3010        // selector. May appear before or after the alias; we accept it
3011        // here (i.e., before parse_optional_alias has run).
3012        if matches!(self.peek_type(), TokenType::Partition)
3013            && matches!(
3014                self.peek_offset(1).map(|t| &t.token_type),
3015                Some(TokenType::LParen)
3016            )
3017        {
3018            self.advance();
3019            self.advance();
3020            let mut depth = 1;
3021            while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
3022                match self.peek_type() {
3023                    TokenType::LParen => depth += 1,
3024                    TokenType::RParen => {
3025                        depth -= 1;
3026                        if depth == 0 {
3027                            self.advance();
3028                            break;
3029                        }
3030                    }
3031                    _ => {}
3032                }
3033                self.advance();
3034            }
3035        }
3036
3037        Ok(TableSource::Table(table_ref))
3038    }
3039
3040    /// After parsing a base table source, check if PIVOT or UNPIVOT follows.
3041    fn parse_pivot_or_unpivot(&mut self, source: TableSource) -> Result<TableSource> {
3042        if self.match_token(TokenType::Pivot) {
3043            self.expect(TokenType::LParen)?;
3044            let aggregate = self.parse_expr()?;
3045            // Snowflake / Databricks: optional `AS <alias>` on the aggregate
3046            // expression: `PIVOT (sum(sales) AS sales FOR …)`.
3047            if self.peek_type() == &TokenType::As
3048                && self
3049                    .peek_offset(1)
3050                    .map(|t| {
3051                        matches!(
3052                            t.token_type,
3053                            TokenType::Identifier | TokenType::String | TokenType::Number
3054                        )
3055                    })
3056                    .unwrap_or(false)
3057            {
3058                self.advance();
3059                self.advance();
3060            }
3061            // Multi-aggregate PIVOT: `PIVOT (SUM(x), COUNT(x) FOR …)`. Drop
3062            // the extra aggregates — we only keep the first one in the AST.
3063            while self.match_token(TokenType::Comma) {
3064                let _ = self.parse_expr()?;
3065                if self.peek_type() == &TokenType::As
3066                    && self
3067                        .peek_offset(1)
3068                        .map(|t| {
3069                            matches!(
3070                                t.token_type,
3071                                TokenType::Identifier | TokenType::String | TokenType::Number
3072                            )
3073                        })
3074                        .unwrap_or(false)
3075                {
3076                    self.advance();
3077                    self.advance();
3078                }
3079            }
3080            self.expect_keyword("FOR")?;
3081            // Snowflake `FOR (col1, col2) IN …` — grouped pivot key. Use the
3082            // first column name as the AST's for_column.
3083            let for_column = if self.peek_type() == &TokenType::LParen {
3084                self.advance();
3085                let first = self.expect_name()?;
3086                while self.match_token(TokenType::Comma) {
3087                    let _ = self.expect_name()?;
3088                }
3089                self.expect(TokenType::RParen)?;
3090                first
3091            } else {
3092                self.expect_name()?
3093            };
3094            self.expect(TokenType::In)?;
3095            self.expect(TokenType::LParen)?;
3096            let in_values = self.parse_pivot_values()?;
3097            self.expect(TokenType::RParen)?;
3098            self.expect(TokenType::RParen)?;
3099            let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3100                Some((name, qs)) => (Some(name), qs),
3101                None => (None, QuoteStyle::None),
3102            };
3103            return Ok(TableSource::Pivot {
3104                source: Box::new(source),
3105                aggregate: Box::new(aggregate),
3106                for_column,
3107                in_values,
3108                alias,
3109                alias_quote_style,
3110            });
3111        }
3112        if self.match_token(TokenType::Unpivot) {
3113            // BigQuery: `UNPIVOT INCLUDE|EXCLUDE NULLS (...)`.
3114            if self.check_keyword("INCLUDE") || self.check_keyword("EXCLUDE") {
3115                let saved = self.pos;
3116                self.advance();
3117                if !self.match_keyword("NULLS") {
3118                    self.pos = saved;
3119                }
3120            }
3121            self.expect(TokenType::LParen)?;
3122            // Snowflake/DuckDB allow a grouped value-column tuple:
3123            // `UNPIVOT ((col1, col2) FOR period IN (...))`. Swallow the
3124            // grouping parens — we only model a single value-column name.
3125            let value_column = if self.peek_type() == &TokenType::LParen {
3126                self.advance();
3127                let first = self.expect_name()?;
3128                while self.match_token(TokenType::Comma) {
3129                    let _ = self.expect_name()?;
3130                }
3131                self.expect(TokenType::RParen)?;
3132                first
3133            } else {
3134                self.expect_name()?
3135            };
3136            self.expect_keyword("FOR")?;
3137            let for_column = self.expect_name()?;
3138            self.expect(TokenType::In)?;
3139            self.expect(TokenType::LParen)?;
3140            let in_columns = self.parse_pivot_values()?;
3141            self.expect(TokenType::RParen)?;
3142            self.expect(TokenType::RParen)?;
3143            let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3144                Some((name, qs)) => (Some(name), qs),
3145                None => (None, QuoteStyle::None),
3146            };
3147            return Ok(TableSource::Unpivot {
3148                source: Box::new(source),
3149                value_column,
3150                for_column,
3151                in_columns,
3152                alias,
3153                alias_quote_style,
3154            });
3155        }
3156        Ok(source)
3157    }
3158
3159    /// Parse comma-separated pivot values, each optionally aliased.
3160    fn parse_pivot_values(&mut self) -> Result<Vec<PivotValue>> {
3161        let mut values = Vec::new();
3162        loop {
3163            let value = self.parse_expr()?;
3164            // Snowflake / BigQuery permit string or numeric aliases on pivot
3165            // values: `(a, b) AS 'semester_1'` / `(a, b) AS 1`. Accept those
3166            // alongside the regular identifier alias.
3167            let (alias, alias_quote_style) = if self.match_token(TokenType::As)
3168                && matches!(self.peek_type(), TokenType::String | TokenType::Number)
3169            {
3170                let tok = self.advance().clone();
3171                (Some(tok.value), QuoteStyle::None)
3172            } else {
3173                match self.parse_optional_alias()? {
3174                    Some((name, qs)) => (Some(name), qs),
3175                    None => (None, QuoteStyle::None),
3176                }
3177            };
3178            values.push(PivotValue {
3179                value,
3180                alias,
3181                alias_quote_style,
3182            });
3183            if !self.match_token(TokenType::Comma) {
3184                break;
3185            }
3186        }
3187        Ok(values)
3188    }
3189
3190    fn parse_table_ref(&mut self) -> Result<TableRef> {
3191        // T-SQL table variable: `FROM @t` / `INTO @t` etc. The @ is its own
3192        // token; fuse with the following name into a single identifier.
3193        if matches!(self.peek_type(), TokenType::AtSign)
3194            && self
3195                .peek_offset(1)
3196                .map(|t| {
3197                    matches!(t.token_type, TokenType::Identifier)
3198                        || matches!(t.token_type, TokenType::AtSign)
3199                })
3200                .unwrap_or(false)
3201        {
3202            let mut name = String::from("@");
3203            self.advance();
3204            if matches!(self.peek_type(), TokenType::AtSign) {
3205                name.push('@');
3206                self.advance();
3207            }
3208            let n = self.advance().clone();
3209            name.push_str(&n.value);
3210            let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3211                Some((a, qs)) => (Some(a), qs),
3212                None => (None, QuoteStyle::None),
3213            };
3214            return Ok(TableRef {
3215                catalog: None,
3216                schema: None,
3217                name,
3218                alias,
3219                name_quote_style: QuoteStyle::None,
3220                alias_quote_style,
3221            });
3222        }
3223        let (first, first_qs) = self.expect_name_with_quote()?;
3224
3225        // Check for schema.table or catalog.schema.table. We also tolerate 4+
3226        // part qualified names (DuckDB / SQL Server `srv.db.sch.tbl`) by
3227        // folding additional segments into the catalog field.
3228        let (catalog, schema, name, name_qs) = if self.match_token(TokenType::Dot) {
3229            let (second, second_qs) = self.expect_name_with_quote()?;
3230            if self.match_token(TokenType::Dot) {
3231                let (mut third, mut third_qs) = self.expect_name_with_quote()?;
3232                let mut catalog = first;
3233                let mut schema = second;
3234                while self.match_token(TokenType::Dot) {
3235                    let (next, next_qs) = self.expect_name_with_quote()?;
3236                    catalog.push('.');
3237                    catalog.push_str(&schema);
3238                    schema = third;
3239                    third = next;
3240                    third_qs = next_qs;
3241                }
3242                (Some(catalog), Some(schema), third, third_qs)
3243            } else {
3244                (None, Some(first), second, second_qs)
3245            }
3246        } else {
3247            (None, None, first, first_qs)
3248        };
3249
3250        let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3251            Some((name, qs)) => (Some(name), qs),
3252            None => (None, QuoteStyle::None),
3253        };
3254
3255        Ok(TableRef {
3256            catalog,
3257            schema,
3258            name,
3259            alias,
3260            name_quote_style: name_qs,
3261            alias_quote_style,
3262        })
3263    }
3264
3265    /// Like `parse_table_ref` but does not consume an alias.
3266    fn parse_table_ref_no_alias(&mut self) -> Result<TableRef> {
3267        let (first, first_qs) = self.expect_name_with_quote()?;
3268
3269        let (catalog, schema, name, name_qs) = if self.match_token(TokenType::Dot) {
3270            let (second, second_qs) = self.expect_name_with_quote()?;
3271            if self.match_token(TokenType::Dot) {
3272                let (mut third, mut third_qs) = self.expect_name_with_quote()?;
3273                let mut catalog = first;
3274                let mut schema = second;
3275                while self.match_token(TokenType::Dot) {
3276                    let (next, next_qs) = self.expect_name_with_quote()?;
3277                    catalog.push('.');
3278                    catalog.push_str(&schema);
3279                    schema = third;
3280                    third = next;
3281                    third_qs = next_qs;
3282                }
3283                (Some(catalog), Some(schema), third, third_qs)
3284            } else {
3285                (None, Some(first), second, second_qs)
3286            }
3287        } else {
3288            (None, None, first, first_qs)
3289        };
3290
3291        Ok(TableRef {
3292            catalog,
3293            schema,
3294            name,
3295            alias: None,
3296            name_quote_style: name_qs,
3297            alias_quote_style: QuoteStyle::None,
3298        })
3299    }
3300
3301    fn parse_joins(&mut self) -> Result<Vec<JoinClause>> {
3302        let mut joins = Vec::new();
3303        loop {
3304            // Hive `LATERAL VIEW [OUTER] func(args) tbl_alias [AS col, ...]`.
3305            // Model as a CROSS JOIN over a table-function so the rest of the
3306            // query parses; the AS column list is dropped.
3307            if self.peek_type() == &TokenType::Lateral
3308                && self
3309                    .peek_offset(1)
3310                    .map(|t| t.value.eq_ignore_ascii_case("VIEW"))
3311                    .unwrap_or(false)
3312            {
3313                self.advance(); // LATERAL
3314                self.advance(); // VIEW
3315                let _outer = self.check_keyword("OUTER") && {
3316                    self.advance();
3317                    true
3318                };
3319                // func(args) — parse name and arg list
3320                let fname = self.expect_name().unwrap_or_default();
3321                let mut fargs = Vec::new();
3322                if self.match_token(TokenType::LParen) {
3323                    if self.peek_type() != &TokenType::RParen {
3324                        fargs.push(self.parse_expr()?);
3325                        while self.match_token(TokenType::Comma) {
3326                            fargs.push(self.parse_expr()?);
3327                        }
3328                    }
3329                    self.expect(TokenType::RParen)?;
3330                }
3331                let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3332                    Some((name, qs)) => (Some(name), qs),
3333                    None => (None, QuoteStyle::None),
3334                };
3335                // Optional `[AS] col1[, col2, ...]` column list. Hive
3336                // allows the AS to be omitted entirely; Spark sometimes
3337                // emits `tbl_name col`. Consume names while we keep seeing
3338                // identifier-then-comma pairs.
3339                let _ = self.match_token(TokenType::As);
3340                if self.is_name_token() {
3341                    self.advance();
3342                    while self.match_token(TokenType::Comma) {
3343                        if !self.is_name_token() {
3344                            break;
3345                        }
3346                        self.advance();
3347                    }
3348                }
3349                joins.push(JoinClause {
3350                    join_type: JoinType::Cross,
3351                    table: TableSource::TableFunction {
3352                        name: fname,
3353                        args: fargs,
3354                        alias,
3355                        alias_quote_style,
3356                    },
3357                    on: None,
3358                    using: Vec::new(),
3359                });
3360                continue;
3361            }
3362            // ClickHouse: ARRAY JOIN / LEFT ARRAY JOIN — flatten arrays as join source.
3363            // We model it as a CROSS JOIN over the array expression.
3364            let saved_array = self.pos;
3365            let _left_array = self.match_token(TokenType::Left);
3366            if self.match_token(TokenType::Array) && self.match_token(TokenType::Join) {
3367                // parse the array expression(s) — comma-separated
3368                let mut sources = Vec::new();
3369                loop {
3370                    // ClickHouse permits inline array literals as the source:
3371                    //   ARRAY JOIN [1,2,3] AS x, [(...), (...)] AS y
3372                    // Wrap as Unnest so we don't reject the syntax.
3373                    let src = if matches!(self.peek_type(), TokenType::LBracket) {
3374                        let arr = self.parse_primary()?;
3375                        let (alias, alias_quote_style) = match self.parse_optional_alias()? {
3376                            Some((name, qs)) => (Some(name), qs),
3377                            None => (None, QuoteStyle::None),
3378                        };
3379                        TableSource::Unnest {
3380                            expr: Box::new(arr),
3381                            alias,
3382                            alias_quote_style,
3383                            with_offset: false,
3384                        }
3385                    } else {
3386                        self.parse_table_source()?
3387                    };
3388                    sources.push(src);
3389                    if !self.match_token(TokenType::Comma) {
3390                        break;
3391                    }
3392                }
3393                for src in sources {
3394                    joins.push(JoinClause {
3395                        join_type: JoinType::Cross,
3396                        table: src,
3397                        on: None,
3398                        using: Vec::new(),
3399                    });
3400                }
3401                continue;
3402            } else {
3403                self.pos = saved_array;
3404            }
3405            // ClickHouse / Hive join strictness modifiers — consume and drop:
3406            //   GLOBAL? ALL | ANY | SEMI | ANTI | ASOF [LEFT|RIGHT|INNER|OUTER] JOIN
3407            let saved_strictness = self.pos;
3408            let _global_prefix = self.check_keyword("GLOBAL") && {
3409                self.advance();
3410                true
3411            };
3412            let consumed_strictness = if self.match_token(TokenType::All) {
3413                true
3414            } else if self.match_token(TokenType::Any) {
3415                true
3416            } else if self.check_keyword("SEMI")
3417                || self.check_keyword("ANTI")
3418                || self.check_keyword("ASOF")
3419                || self.check_keyword("PASTE")
3420            {
3421                self.advance();
3422                // DuckDB / ClickHouse allow compound forms like
3423                // `ASOF ANTI JOIN` / `ASOF SEMI JOIN` — absorb a
3424                // following second strictness keyword too.
3425                if self.check_keyword("SEMI")
3426                    || self.check_keyword("ANTI")
3427                    || self.check_keyword("ASOF")
3428                {
3429                    self.advance();
3430                }
3431                true
3432            } else {
3433                _global_prefix
3434            };
3435            // If the strictness modifier wasn't followed by a join keyword,
3436            // rewind so we don't accidentally consume a stray ALL/ANY (e.g.
3437            // `ORDER BY ALL`).
3438            if consumed_strictness
3439                && !matches!(
3440                    self.peek_type(),
3441                    TokenType::Join
3442                        | TokenType::Inner
3443                        | TokenType::Left
3444                        | TokenType::Right
3445                        | TokenType::Full
3446                        | TokenType::Cross
3447                )
3448            {
3449                self.pos = saved_strictness;
3450            }
3451            let join_type = match self.peek_type() {
3452                // `FROM a, b` is treated as `FROM a CROSS JOIN b`. Note the
3453                // SQL standard gives comma a lower precedence than explicit
3454                // JOIN operators (so `FROM a, b JOIN c ON ...` should be
3455                // `a CROSS JOIN (b JOIN c ...)`), but we flatten everything
3456                // into a left-deep chain. Column resolution still works for
3457                // the common cases since the join order is associative when
3458                // ON-clauses only reference adjacent tables.
3459                TokenType::Comma => {
3460                    self.advance();
3461                    JoinType::Cross
3462                }
3463                // `NATURAL [LEFT|RIGHT|FULL [OUTER]] JOIN tbl` — auto-equi-join
3464                // on shared column names. We don't model NATURAL semantics yet;
3465                // promote to the corresponding non-natural join type and treat
3466                // the implicit USING clause as empty.
3467                t if matches!(t, TokenType::Identifier)
3468                    && self.peek().value.eq_ignore_ascii_case("NATURAL") =>
3469                {
3470                    self.advance(); // NATURAL
3471                    let jt = match self.peek_type() {
3472                        TokenType::Left => {
3473                            self.advance();
3474                            let _ = self.match_token(TokenType::Outer);
3475                            JoinType::Left
3476                        }
3477                        TokenType::Right => {
3478                            self.advance();
3479                            let _ = self.match_token(TokenType::Outer);
3480                            JoinType::Right
3481                        }
3482                        TokenType::Full => {
3483                            self.advance();
3484                            let _ = self.match_token(TokenType::Outer);
3485                            JoinType::Full
3486                        }
3487                        TokenType::Inner => {
3488                            self.advance();
3489                            JoinType::Inner
3490                        }
3491                        _ => JoinType::Inner,
3492                    };
3493                    self.expect(TokenType::Join)?;
3494                    jt
3495                }
3496                // MySQL `STRAIGHT_JOIN` — non-reordered INNER JOIN.
3497                t if matches!(t, TokenType::Identifier)
3498                    && self.peek().value.eq_ignore_ascii_case("STRAIGHT_JOIN") =>
3499                {
3500                    self.advance();
3501                    JoinType::Inner
3502                }
3503                TokenType::Join => {
3504                    self.advance();
3505                    JoinType::Inner
3506                }
3507                TokenType::Inner => {
3508                    self.advance();
3509                    self.expect(TokenType::Join)?;
3510                    JoinType::Inner
3511                }
3512                TokenType::Left => {
3513                    self.advance();
3514                    let _ = self.match_token(TokenType::Outer);
3515                    // Hive / Spark: LEFT SEMI JOIN / LEFT ANTI JOIN
3516                    let _ = self.check_keyword("SEMI") && {
3517                        self.advance();
3518                        true
3519                    } || self.check_keyword("ANTI") && {
3520                        self.advance();
3521                        true
3522                    };
3523                    // ClickHouse: LEFT ANY|ALL JOIN
3524                    let _ = self.match_token(TokenType::Any) || self.match_token(TokenType::All);
3525                    // Some dialects (Spark/Hive variants) allow a trailing
3526                    // OUTER after the strictness modifier.
3527                    let _ = self.match_token(TokenType::Outer);
3528                    self.expect(TokenType::Join)?;
3529                    JoinType::Left
3530                }
3531                TokenType::Right => {
3532                    self.advance();
3533                    let _ = self.match_token(TokenType::Outer);
3534                    let _ = self.check_keyword("SEMI") && {
3535                        self.advance();
3536                        true
3537                    } || self.check_keyword("ANTI") && {
3538                        self.advance();
3539                        true
3540                    };
3541                    let _ = self.match_token(TokenType::Any) || self.match_token(TokenType::All);
3542                    let _ = self.match_token(TokenType::Outer);
3543                    self.expect(TokenType::Join)?;
3544                    JoinType::Right
3545                }
3546                TokenType::Full => {
3547                    self.advance();
3548                    let _ = self.match_token(TokenType::Outer);
3549                    self.expect(TokenType::Join)?;
3550                    JoinType::Full
3551                }
3552                TokenType::Cross => {
3553                    self.advance();
3554                    // T-SQL `CROSS APPLY <source>` ≈ `CROSS JOIN LATERAL ...`.
3555                    if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("APPLY") {
3556                        self.advance();
3557                        JoinType::Cross
3558                    } else {
3559                        self.expect(TokenType::Join)?;
3560                        JoinType::Cross
3561                    }
3562                }
3563                TokenType::Outer => {
3564                    // T-SQL `OUTER APPLY <source>` ≈ `LEFT JOIN LATERAL ... ON TRUE`.
3565                    self.advance();
3566                    if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("APPLY") {
3567                        self.advance();
3568                        JoinType::Left
3569                    } else {
3570                        break;
3571                    }
3572                }
3573                _ => break,
3574            };
3575
3576            let table = self.parse_table_source()?;
3577            let mut on = None;
3578            let mut using = vec![];
3579
3580            if self.match_token(TokenType::On) {
3581                on = Some(self.parse_expr()?);
3582            } else if self.match_token(TokenType::Using) {
3583                // ClickHouse permits a bare column name without parens:
3584                // `JOIN t USING k`.
3585                if self.match_token(TokenType::LParen) {
3586                    using = vec![self.expect_name()?];
3587                    while self.match_token(TokenType::Comma) {
3588                        using.push(self.expect_name()?);
3589                    }
3590                    self.expect(TokenType::RParen)?;
3591                } else {
3592                    using = vec![self.expect_name()?];
3593                    while self.match_token(TokenType::Comma) {
3594                        if !self.is_name_token() {
3595                            break;
3596                        }
3597                        using.push(self.expect_name()?);
3598                    }
3599                }
3600            }
3601
3602            joins.push(JoinClause {
3603                join_type,
3604                table,
3605                on,
3606                using,
3607            });
3608        }
3609        Ok(joins)
3610    }
3611
3612    fn parse_order_by_items(&mut self) -> Result<Vec<OrderByItem>> {
3613        let mut items = Vec::new();
3614        // DuckDB / Snowflake `ORDER BY ALL` shortcut.
3615        if self.match_token(TokenType::All) {
3616            let ascending = if self.match_token(TokenType::Desc) {
3617                false
3618            } else {
3619                let _ = self.match_token(TokenType::Asc);
3620                true
3621            };
3622            items.push(OrderByItem {
3623                expr: Expr::Wildcard,
3624                ascending,
3625                nulls_first: None,
3626            });
3627            return Ok(items);
3628        }
3629        loop {
3630            // MySQL: `ORDER BY BINARY col [ASC|DESC]` — BINARY here is a
3631            // collation modifier on the sort key. Swallow it; the rest of
3632            // the expression parses normally.
3633            if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("BINARY") {
3634                let saved = self.pos;
3635                self.advance();
3636                // Only consume BINARY when followed by something that can
3637                // start an order-by key (name, literal, paren, etc.); if it
3638                // looks like the end of the list, rewind.
3639                if matches!(
3640                    self.peek_type(),
3641                    TokenType::Comma | TokenType::Semicolon | TokenType::Eof | TokenType::RParen
3642                ) {
3643                    self.pos = saved;
3644                }
3645            }
3646            let expr = self.parse_expr()?;
3647            // ClickHouse: `ORDER BY expr AS alias`. Swallow the alias.
3648            if self.match_token(TokenType::As) && self.is_name_token() {
3649                self.advance();
3650            }
3651            let ascending = if self.match_token(TokenType::Desc) {
3652                false
3653            } else {
3654                let _ = self.match_token(TokenType::Asc);
3655                true
3656            };
3657
3658            let nulls_first = if self.match_token(TokenType::Nulls) {
3659                if self.match_token(TokenType::First) {
3660                    Some(true)
3661                } else {
3662                    self.expect(TokenType::Identifier)?; // LAST
3663                    Some(false)
3664                }
3665            } else {
3666                None
3667            };
3668
3669            items.push(OrderByItem {
3670                expr,
3671                ascending,
3672                nulls_first,
3673            });
3674            if !self.match_token(TokenType::Comma) {
3675                break;
3676            }
3677        }
3678        Ok(items)
3679    }
3680
3681    fn parse_expr_list(&mut self) -> Result<Vec<Expr>> {
3682        let mut exprs = vec![self.parse_expr()?];
3683        while self.match_token(TokenType::Comma) {
3684            // Tolerate a trailing comma — DuckDB / PostgreSQL accept
3685            // `IN ('a', 'b', )` and similar list shapes.
3686            if matches!(self.peek_type(), TokenType::RParen | TokenType::RBracket) {
3687                break;
3688            }
3689            exprs.push(self.parse_expr()?);
3690        }
3691        Ok(exprs)
3692    }
3693
3694    /// Parse a comma-separated expression list where each item may carry an
3695    /// inline alias (`expr AS name` or `expr name`). Used for dialects (notably
3696    /// ClickHouse) that permit aliases inside partition/grouping lists.
3697    fn parse_expr_list_allow_item_alias(&mut self) -> Result<Vec<Expr>> {
3698        let mut exprs = Vec::new();
3699        loop {
3700            exprs.push(self.parse_expr()?);
3701            if self.match_token(TokenType::As) && self.is_name_token() {
3702                self.advance();
3703            }
3704            if !self.match_token(TokenType::Comma) {
3705                break;
3706            }
3707            if matches!(self.peek_type(), TokenType::RParen | TokenType::RBracket) {
3708                break;
3709            }
3710        }
3711        Ok(exprs)
3712    }
3713
3714    /// Parse array-literal elements: comma-separated expressions, each
3715    /// optionally followed by `AS alias` (ClickHouse lets bindings
3716    /// appear inside `[…]`). The closing token is the caller's
3717    /// responsibility.
3718    fn parse_array_items(&mut self, close: TokenType) -> Result<Vec<Expr>> {
3719        if self.peek_type() == &close {
3720            return Ok(vec![]);
3721        }
3722        let mut items = Vec::new();
3723        loop {
3724            let expr = self.parse_expr()?;
3725            if self.match_token(TokenType::As) {
3726                let _ = self.parse_optional_alias();
3727            }
3728            items.push(expr);
3729            if !self.match_token(TokenType::Comma) {
3730                break;
3731            }
3732        }
3733        Ok(items)
3734    }
3735
3736    /// Parse a GROUP BY list, which may contain regular expressions,
3737    /// CUBE(...), ROLLUP(...), and GROUPING SETS(...).
3738    fn parse_group_by_list(&mut self) -> Result<Vec<Expr>> {
3739        // DuckDB / Snowflake `GROUP BY ALL` shortcut — emit a wildcard
3740        // marker so downstream code can recognise it. PostgreSQL also
3741        // allows `GROUP BY ALL <col>, <col>` (treated identically to a
3742        // regular GROUP BY list); fall through to the normal parser when
3743        // the next token is a column expression rather than a clause
3744        // terminator.
3745        if self.match_token(TokenType::All) {
3746            let terminates = matches!(
3747                self.peek_type(),
3748                TokenType::Comma
3749                    | TokenType::Semicolon
3750                    | TokenType::Eof
3751                    | TokenType::RParen
3752                    | TokenType::Having
3753                    | TokenType::Order
3754                    | TokenType::Limit
3755                    | TokenType::Offset
3756                    | TokenType::Window
3757                    | TokenType::Union
3758                    | TokenType::Intersect
3759                    | TokenType::Except
3760                    | TokenType::Qualify
3761            );
3762            if terminates {
3763                return Ok(vec![Expr::Wildcard]);
3764            }
3765            // Followed by a real grouping expression — fall through.
3766        }
3767        let mut items = vec![self.parse_group_by_item()?];
3768        // ClickHouse: `GROUP BY col AS alias [, …]` — swallow alias.
3769        if self.match_token(TokenType::As) && self.is_name_token() {
3770            self.advance();
3771        }
3772        // MySQL: `GROUP BY col ASC|DESC [, …]` — swallow direction.
3773        let _ = self.match_token(TokenType::Asc) || self.match_token(TokenType::Desc);
3774        while self.match_token(TokenType::Comma) {
3775            items.push(self.parse_group_by_item()?);
3776            if self.match_token(TokenType::As) && self.is_name_token() {
3777                self.advance();
3778            }
3779            let _ = self.match_token(TokenType::Asc) || self.match_token(TokenType::Desc);
3780        }
3781        Ok(items)
3782    }
3783
3784    /// Parse a single GROUP BY item: a CUBE, ROLLUP, GROUPING SETS, or regular expression.
3785    fn parse_group_by_item(&mut self) -> Result<Expr> {
3786        match self.peek_type() {
3787            TokenType::Cube => {
3788                self.advance();
3789                self.expect(TokenType::LParen)?;
3790                let exprs = if self.peek_type() == &TokenType::RParen {
3791                    vec![]
3792                } else {
3793                    self.parse_group_by_element_list()?
3794                };
3795                self.expect(TokenType::RParen)?;
3796                Ok(Expr::Cube { exprs })
3797            }
3798            TokenType::Rollup => {
3799                self.advance();
3800                self.expect(TokenType::LParen)?;
3801                let exprs = if self.peek_type() == &TokenType::RParen {
3802                    vec![]
3803                } else {
3804                    self.parse_group_by_element_list()?
3805                };
3806                self.expect(TokenType::RParen)?;
3807                Ok(Expr::Rollup { exprs })
3808            }
3809            TokenType::Grouping => {
3810                // Could be GROUPING SETS or GROUPING() function
3811                let saved = self.pos;
3812                self.advance();
3813                if self.peek_type() == &TokenType::Sets {
3814                    // GROUPING SETS (...)
3815                    self.advance();
3816                    self.expect(TokenType::LParen)?;
3817                    let sets = self.parse_grouping_sets_elements()?;
3818                    self.expect(TokenType::RParen)?;
3819                    Ok(Expr::GroupingSets { sets })
3820                } else {
3821                    // It's the GROUPING() function, backtrack and parse as expression
3822                    self.pos = saved;
3823                    self.parse_expr()
3824                }
3825            }
3826            _ => self.parse_expr(),
3827        }
3828    }
3829
3830    /// Parse elements inside CUBE(...) or ROLLUP(...).
3831    /// Each element can be a single expression or a parenthesized tuple of expressions.
3832    fn parse_group_by_element_list(&mut self) -> Result<Vec<Expr>> {
3833        let mut items = vec![self.parse_group_by_element()?];
3834        while self.match_token(TokenType::Comma) {
3835            items.push(self.parse_group_by_element()?);
3836        }
3837        Ok(items)
3838    }
3839
3840    /// Parse a single element inside CUBE/ROLLUP: either `expr` or `(expr, expr, ...)`.
3841    fn parse_group_by_element(&mut self) -> Result<Expr> {
3842        if self.peek_type() == &TokenType::LParen {
3843            self.advance();
3844            let exprs = self.parse_expr_list()?;
3845            self.expect(TokenType::RParen)?;
3846            if exprs.len() == 1 {
3847                Ok(Expr::Nested(Box::new(exprs.into_iter().next().unwrap())))
3848            } else {
3849                Ok(Expr::Tuple(exprs))
3850            }
3851        } else {
3852            let e = self.parse_expr()?;
3853            // ClickHouse: `GROUP BY expr AS alias`. Swallow the alias.
3854            if self.match_token(TokenType::As) && self.is_name_token() {
3855                self.advance();
3856            }
3857            Ok(e)
3858        }
3859    }
3860
3861    /// Parse elements inside GROUPING SETS (...).
3862    /// Each element can be: (), (expr, ...), CUBE(...), ROLLUP(...), or a single expr.
3863    fn parse_grouping_sets_elements(&mut self) -> Result<Vec<Expr>> {
3864        let mut items = vec![self.parse_grouping_sets_element()?];
3865        while self.match_token(TokenType::Comma) {
3866            items.push(self.parse_grouping_sets_element()?);
3867        }
3868        Ok(items)
3869    }
3870
3871    /// Parse a single GROUPING SETS element.
3872    fn parse_grouping_sets_element(&mut self) -> Result<Expr> {
3873        match self.peek_type() {
3874            TokenType::Cube => {
3875                self.advance();
3876                self.expect(TokenType::LParen)?;
3877                let exprs = if self.peek_type() == &TokenType::RParen {
3878                    vec![]
3879                } else {
3880                    self.parse_group_by_element_list()?
3881                };
3882                self.expect(TokenType::RParen)?;
3883                Ok(Expr::Cube { exprs })
3884            }
3885            TokenType::Rollup => {
3886                self.advance();
3887                self.expect(TokenType::LParen)?;
3888                let exprs = if self.peek_type() == &TokenType::RParen {
3889                    vec![]
3890                } else {
3891                    self.parse_group_by_element_list()?
3892                };
3893                self.expect(TokenType::RParen)?;
3894                Ok(Expr::Rollup { exprs })
3895            }
3896            TokenType::LParen => {
3897                self.advance();
3898                if self.peek_type() == &TokenType::RParen {
3899                    // Empty grouping set: ()
3900                    self.advance();
3901                    Ok(Expr::Tuple(vec![]))
3902                } else {
3903                    let exprs = self.parse_expr_list()?;
3904                    self.expect(TokenType::RParen)?;
3905                    if exprs.len() == 1 {
3906                        Ok(Expr::Nested(Box::new(exprs.into_iter().next().unwrap())))
3907                    } else {
3908                        Ok(Expr::Tuple(exprs))
3909                    }
3910                }
3911            }
3912            _ => self.parse_expr(),
3913        }
3914    }
3915
3916    // ── INSERT ──────────────────────────────────────────────────────
3917
3918    fn parse_insert(&mut self) -> Result<InsertStatement> {
3919        // Accept MySQL `REPLACE INTO ...` as a synonym for `INSERT INTO ...`.
3920        if !self.match_token(TokenType::Insert) {
3921            self.expect(TokenType::Replace)?;
3922        }
3923        // SQLite / DuckDB conflict-resolution prefix:
3924        //   `INSERT OR REPLACE|IGNORE|FAIL|ABORT|ROLLBACK INTO ...`.
3925        // Swallow opaquely; we don't model conflict resolution at the
3926        // statement level (ON CONFLICT covers most cases downstream).
3927        if self.match_token(TokenType::Or) {
3928            if self.match_token(TokenType::Replace) {
3929                // matched
3930            } else if self.match_token(TokenType::Ignore) {
3931                // matched
3932            } else if self.is_name_token() {
3933                let v = self.peek().value.to_uppercase();
3934                if matches!(v.as_str(), "FAIL" | "ABORT" | "ROLLBACK") {
3935                    self.advance();
3936                }
3937            }
3938        }
3939        // MySQL modifiers between INSERT/REPLACE and INTO:
3940        //   `INSERT LOW_PRIORITY|DELAYED|HIGH_PRIORITY [IGNORE] INTO ...`,
3941        //   `INSERT IGNORE INTO ...`. Swallow them so the rest parses.
3942        loop {
3943            if self.match_token(TokenType::Ignore) {
3944                continue;
3945            }
3946            if self.is_name_token() {
3947                let v = self.peek().value.to_uppercase();
3948                if matches!(v.as_str(), "LOW_PRIORITY" | "DELAYED" | "HIGH_PRIORITY") {
3949                    self.advance();
3950                    continue;
3951                }
3952            }
3953            break;
3954        }
3955        let _ = self.match_token(TokenType::Into);
3956        // Hive: `INSERT OVERWRITE [LOCAL] DIRECTORY '/path'` or
3957        // `INSERT OVERWRITE TABLE tbl ...`. Consume OVERWRITE (tokenized as
3958        // an identifier) and any DIRECTORY clause that follows.
3959        if self.check_keyword("OVERWRITE") {
3960            self.advance();
3961            if self.check_keyword("LOCAL") {
3962                self.advance();
3963            }
3964            if self.check_keyword("DIRECTORY") {
3965                self.advance();
3966                // Consume `'path'` (string) and any STORED AS / ROW FORMAT
3967                // clauses until we hit SELECT/WITH/LParen/VALUES/EOF.
3968                if matches!(self.peek_type(), TokenType::String) {
3969                    self.advance();
3970                }
3971                while !matches!(
3972                    self.peek_type(),
3973                    TokenType::Select
3974                        | TokenType::With
3975                        | TokenType::LParen
3976                        | TokenType::Values
3977                        | TokenType::Eof
3978                        | TokenType::Semicolon
3979                ) {
3980                    self.advance();
3981                }
3982            }
3983        }
3984        // Hive: `INSERT INTO TABLE tbl ...` and `INSERT OVERWRITE TABLE tbl ...`.
3985        let _ = self.match_token(TokenType::Table);
3986        let table = self.parse_table_ref()?;
3987
3988        // Hive `PARTITION (k=v, ...)` between table and column list / source.
3989        if self.peek_type() == &TokenType::Partition {
3990            self.advance();
3991            if self.match_token(TokenType::LParen) {
3992                let mut depth = 1;
3993                while depth > 0 {
3994                    match self.peek_type() {
3995                        TokenType::LParen => depth += 1,
3996                        TokenType::RParen => depth -= 1,
3997                        TokenType::Eof => break,
3998                        _ => {}
3999                    }
4000                    if depth == 0 {
4001                        self.advance();
4002                        break;
4003                    }
4004                    self.advance();
4005                }
4006            }
4007        }
4008
4009        let columns = if self.match_token(TokenType::LParen) {
4010            // BigQuery / SQLFluff fixture: `INSERT INTO t (SELECT ... )` —
4011            // no column list, the parenthesized SELECT is the source.
4012            // Rewind to the `(` and let the source dispatch handle it.
4013            if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
4014                self.pos -= 1;
4015                Vec::new()
4016            } else {
4017                // ClickHouse `INSERT INTO t (COLUMNS('.*') EXCEPT (...))` — when
4018                // the list contains a function call or anything other than plain
4019                // identifiers, fall back to a balanced-paren swallow.
4020                let saved = self.pos;
4021                let try_simple: Result<Vec<String>> = (|| {
4022                    let mut cols = vec![self.parse_dotted_name()?];
4023                    while self.match_token(TokenType::Comma) {
4024                        cols.push(self.parse_dotted_name()?);
4025                    }
4026                    self.expect(TokenType::RParen)?;
4027                    Ok(cols)
4028                })();
4029                match try_simple {
4030                    Ok(c) => c,
4031                    Err(_) => {
4032                        self.pos = saved;
4033                        let mut depth = 1_i32;
4034                        while depth > 0 && self.peek_type() != &TokenType::Eof {
4035                            match self.peek_type() {
4036                                TokenType::LParen => depth += 1,
4037                                TokenType::RParen => depth -= 1,
4038                                _ => {}
4039                            }
4040                            self.advance();
4041                        }
4042                        Vec::new()
4043                    }
4044                }
4045            }
4046        } else {
4047            vec![]
4048        };
4049
4050        // ClickHouse `INSERT INTO t [(cols)] SETTINGS k=v[, …] VALUES …`.
4051        // Swallow the SETTINGS clause before the source clause so the
4052        // surrounding parse completes.
4053        if self.check_keyword("SETTINGS") {
4054            self.advance();
4055            loop {
4056                if !self.is_name_token() {
4057                    break;
4058                }
4059                self.advance(); // key
4060                if !self.match_token(TokenType::Eq) {
4061                    break;
4062                }
4063                // value: number / string / identifier / unary-signed number
4064                let _ = self.match_token(TokenType::Minus) || self.match_token(TokenType::Plus);
4065                if matches!(self.peek_type(), TokenType::Number | TokenType::String)
4066                    || self.is_name_token()
4067                {
4068                    self.advance();
4069                }
4070                if !self.match_token(TokenType::Comma) {
4071                    break;
4072                }
4073            }
4074        }
4075
4076        let source = if self.match_token(TokenType::Values) || self.match_keyword("VALUE") {
4077            let mut rows = Vec::new();
4078            loop {
4079                self.expect(TokenType::LParen)?;
4080                // MySQL allows `VALUES ()` as an empty row to insert all
4081                // defaults — accept and emit as an empty row.
4082                let row = if self.peek_type() == &TokenType::RParen {
4083                    Vec::new()
4084                } else {
4085                    self.parse_expr_list()?
4086                };
4087                self.expect(TokenType::RParen)?;
4088                rows.push(row);
4089                // ClickHouse permits comma-less rows: `VALUES (1)(2)(3)`.
4090                if self.peek_type() == &TokenType::LParen {
4091                    continue;
4092                }
4093                if !self.match_token(TokenType::Comma) {
4094                    break;
4095                }
4096                // Trailing comma: `VALUES (1,2), (3,4),` — DuckDB / sqlfluff
4097                // fixture truncation. Accept and stop the row loop.
4098                if !matches!(self.peek_type(), TokenType::LParen) {
4099                    break;
4100                }
4101            }
4102            InsertSource::Values(rows)
4103        } else if matches!(
4104            self.peek_type(),
4105            TokenType::Select | TokenType::With | TokenType::LParen
4106        ) {
4107            InsertSource::Query(Box::new(self.parse_statement_inner()?))
4108        } else if self.match_token(TokenType::Default) {
4109            self.expect(TokenType::Values)?;
4110            InsertSource::Default
4111        } else if self.match_token(TokenType::Set) {
4112            // MySQL `INSERT INTO t SET col = val, col = val, ...`.
4113            // Collapse into a single-row VALUES placeholder by collecting
4114            // the right-hand expressions; column names are dropped.
4115            let mut row = Vec::new();
4116            loop {
4117                let _ = self.expect_name()?;
4118                self.expect(TokenType::Eq)?;
4119                row.push(self.parse_expr()?);
4120                if !self.match_token(TokenType::Comma) {
4121                    break;
4122                }
4123            }
4124            InsertSource::Values(vec![row])
4125        } else if self.peek_type() == &TokenType::From {
4126            // DuckDB `INSERT INTO t FROM source` shorthand for
4127            // `INSERT INTO t SELECT * FROM source`. Synthesize a SELECT *
4128            // statement so the existing query path handles it.
4129            self.advance();
4130            let from = Some(FromClause {
4131                source: self.parse_table_source()?,
4132            });
4133            let joins = self.parse_joins()?;
4134            let stmt = Statement::Select(SelectStatement {
4135                comments: vec![],
4136                ctes: vec![],
4137                distinct: false,
4138                top: None,
4139                columns: vec![SelectItem::Wildcard],
4140                from,
4141                joins,
4142                where_clause: None,
4143                group_by: vec![],
4144                having: None,
4145                order_by: vec![],
4146                limit: None,
4147                offset: None,
4148                fetch_first: None,
4149                qualify: None,
4150                window_definitions: vec![],
4151            });
4152            InsertSource::Query(Box::new(stmt))
4153        } else if self.peek().value.eq_ignore_ascii_case("FORMAT") {
4154            // ClickHouse `INSERT INTO t FORMAT name <raw payload>`.
4155            // Swallow the format name and the remainder of the statement
4156            // as opaque bytes; we cannot parse JSONEachRow / TabSeparated
4157            // payloads, but we should not reject the statement.
4158            self.advance();
4159            let _ = self.expect_name();
4160            while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon) {
4161                self.advance();
4162            }
4163            InsertSource::Default
4164        } else {
4165            return Err(SqlglotError::ParserError {
4166                message: "Expected VALUES, SELECT, or DEFAULT VALUES after INSERT".into(),
4167            });
4168        };
4169
4170        // MySQL 8.0.19+ row alias: `INSERT INTO t (cols) VALUES (...) AS
4171        // alias [(col_alias, ...)] ON DUPLICATE KEY UPDATE ...`. Swallow
4172        // the alias so the ON DUPLICATE clause parses.
4173        if self.peek_type() == &TokenType::As
4174            && self
4175                .peek_offset(1)
4176                .map(|t| {
4177                    matches!(
4178                        t.token_type,
4179                        TokenType::Identifier
4180                            | TokenType::Key
4181                            | TokenType::Year
4182                            | TokenType::Month
4183                            | TokenType::Day
4184                            | TokenType::Hour
4185                            | TokenType::Minute
4186                            | TokenType::Second
4187                    ) || t
4188                        .value
4189                        .chars()
4190                        .next()
4191                        .is_some_and(|c| c.is_alphabetic() || c == '_')
4192                })
4193                .unwrap_or(false)
4194        {
4195            self.advance(); // AS
4196            self.advance(); // alias name
4197            if self.match_token(TokenType::LParen) {
4198                let mut depth = 1_i32;
4199                while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
4200                    match self.peek_type() {
4201                        TokenType::LParen => depth += 1,
4202                        TokenType::RParen => depth -= 1,
4203                        _ => {}
4204                    }
4205                    self.advance();
4206                }
4207            }
4208        }
4209
4210        // MySQL `ON DUPLICATE KEY UPDATE col=val, ...`. Swallow the clause.
4211        if self.peek_type() == &TokenType::On
4212            && self
4213                .peek_offset(1)
4214                .map(|t| t.value.eq_ignore_ascii_case("DUPLICATE"))
4215                .unwrap_or(false)
4216        {
4217            self.advance();
4218            self.advance();
4219            // KEY UPDATE
4220            if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("KEY") {
4221                self.advance();
4222            }
4223            if self.match_token(TokenType::Update) {
4224                // assignments until end-of-statement
4225                loop {
4226                    let _ = self.expect_name();
4227                    if !self.match_token(TokenType::Eq) {
4228                        break;
4229                    }
4230                    let _ = self.parse_expr();
4231                    if !self.match_token(TokenType::Comma) {
4232                        break;
4233                    }
4234                }
4235            }
4236        }
4237
4238        // ON CONFLICT
4239        let on_conflict = if self.match_token(TokenType::On) {
4240            if self.match_token(TokenType::Conflict) {
4241                let columns = if self.match_token(TokenType::LParen) {
4242                    self.parse_parenthesized_raw_items()?
4243                } else {
4244                    vec![]
4245                };
4246                self.expect(TokenType::Do)?;
4247                let action = if self.match_token(TokenType::Nothing) {
4248                    ConflictAction::DoNothing
4249                } else {
4250                    self.expect(TokenType::Update)?;
4251                    self.expect(TokenType::Set)?;
4252                    let mut assignments = Vec::new();
4253                    loop {
4254                        let col = self.expect_name()?;
4255                        self.expect(TokenType::Eq)?;
4256                        let val = self.parse_expr()?;
4257                        assignments.push((col, val));
4258                        if !self.match_token(TokenType::Comma) {
4259                            break;
4260                        }
4261                    }
4262                    ConflictAction::DoUpdate(assignments)
4263                };
4264                // Postgres / DuckDB allow `ON CONFLICT (...) DO UPDATE SET
4265                // ... WHERE predicate` to limit the update. Swallow the
4266                // predicate opaquely.
4267                if self.match_token(TokenType::Where) {
4268                    let _ = self.parse_expr()?;
4269                }
4270                Some(OnConflict { columns, action })
4271            } else {
4272                None
4273            }
4274        } else {
4275            None
4276        };
4277
4278        let returning = if self.match_token(TokenType::Returning) {
4279            self.parse_select_items()?
4280        } else {
4281            vec![]
4282        };
4283
4284        Ok(InsertStatement {
4285            comments: vec![],
4286            table,
4287            columns,
4288            source,
4289            on_conflict,
4290            returning,
4291        })
4292    }
4293
4294    // ── UPDATE ──────────────────────────────────────────────────────
4295
4296    fn parse_update(&mut self) -> Result<UpdateStatement> {
4297        self.expect(TokenType::Update)?;
4298        let table = self.parse_table_ref()?;
4299        // MySQL multi-table UPDATE: `UPDATE t1, t2 [, ...] SET ...`.
4300        // Swallow the additional table refs (we keep only the first as
4301        // the primary target).
4302        while self.match_token(TokenType::Comma) {
4303            let _ = self.parse_table_ref()?;
4304        }
4305        // PG SQL:2011 temporal `UPDATE t FOR PORTION OF col FROM a TO b
4306        // [AS alias] SET ...`. Swallow the qualifier verbatim.
4307        if self.check_keyword("FOR")
4308            && self
4309                .peek_offset(1)
4310                .map(|t| t.value.eq_ignore_ascii_case("PORTION"))
4311                .unwrap_or(false)
4312        {
4313            while !matches!(
4314                self.peek_type(),
4315                TokenType::Set | TokenType::Eof | TokenType::Semicolon
4316            ) {
4317                self.advance();
4318            }
4319        }
4320        // MySQL `UPDATE t PARTITION (p0[, p1]) SET ...` — swallow.
4321        if matches!(self.peek_type(), TokenType::Partition)
4322            && matches!(
4323                self.peek_offset(1).map(|t| &t.token_type),
4324                Some(TokenType::LParen)
4325            )
4326        {
4327            self.advance();
4328            self.advance();
4329            let mut depth = 1;
4330            while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
4331                match self.peek_type() {
4332                    TokenType::LParen => depth += 1,
4333                    TokenType::RParen => {
4334                        depth -= 1;
4335                        if depth == 0 {
4336                            self.advance();
4337                            break;
4338                        }
4339                    }
4340                    _ => {}
4341                }
4342                self.advance();
4343            }
4344        }
4345        // MySQL multi-table UPDATE: `UPDATE t1 [LEFT|RIGHT|INNER|CROSS] JOIN
4346        // t2 ON ... SET ...`. Swallow the joins so the existing single-target
4347        // update parses; the joined tables are dropped from the AST.
4348        let _ = self.parse_joins();
4349        self.expect(TokenType::Set)?;
4350
4351        let mut assignments = Vec::new();
4352        loop {
4353            // Accept qualified LHS like `alias.col` (Oracle, T-SQL idiom),
4354            // and PG/Snowflake subscripts/field access on the LHS such as
4355            // `arr[1] = …`, `arr[1:3] = …`, `obj['k']`, `(a,b) = …`.
4356            // Accept LHS row-tuple `(a, b, c) = (rhs)` (PostgreSQL).
4357            if self.peek_type() == &TokenType::LParen {
4358                let saved = self.pos;
4359                self.advance();
4360                let mut depth = 1;
4361                while depth > 0 && self.peek_type() != &TokenType::Eof {
4362                    match self.peek_type() {
4363                        TokenType::LParen => depth += 1,
4364                        TokenType::RParen => depth -= 1,
4365                        _ => {}
4366                    }
4367                    self.advance();
4368                }
4369                if self.peek_type() == &TokenType::Eq {
4370                    self.advance();
4371                    let val = self.parse_expr()?;
4372                    assignments.push(("__tuple__".to_string(), val));
4373                    if !self.match_token(TokenType::Comma) {
4374                        break;
4375                    }
4376                    continue;
4377                }
4378                self.pos = saved;
4379            }
4380            let mut col = self.expect_name()?;
4381            while self.match_token(TokenType::Dot) {
4382                col.push('.');
4383                col.push_str(&self.expect_name()?);
4384            }
4385            // Swallow `[index]` / `[a:b]` subscripts in the LHS — we don't
4386            // model array-element assignment in the AST.
4387            while self.peek_type() == &TokenType::LBracket {
4388                self.advance();
4389                let mut depth = 1;
4390                while depth > 0 && self.peek_type() != &TokenType::Eof {
4391                    match self.peek_type() {
4392                        TokenType::LBracket => depth += 1,
4393                        TokenType::RBracket => depth -= 1,
4394                        _ => {}
4395                    }
4396                    self.advance();
4397                }
4398            }
4399            self.expect(TokenType::Eq)?;
4400            let val = self.parse_expr()?;
4401            assignments.push((col, val));
4402            if !self.match_token(TokenType::Comma) {
4403                break;
4404            }
4405        }
4406
4407        let from = if self.match_token(TokenType::From) {
4408            Some(FromClause {
4409                source: self.parse_table_source()?,
4410            })
4411        } else {
4412            None
4413        };
4414
4415        let where_clause = if self.match_token(TokenType::Where) {
4416            Some(self.parse_expr()?)
4417        } else {
4418            None
4419        };
4420
4421        // Teradata `PREFERRING <expr> [PARTITION BY <list>]` skyline
4422        // clause on UPDATE. Swallow up to a known terminator.
4423        if self.check_keyword("PREFERRING") {
4424            self.advance();
4425            loop {
4426                match self.peek_type() {
4427                    TokenType::Eof
4428                    | TokenType::Semicolon
4429                    | TokenType::RParen
4430                    | TokenType::Returning => break,
4431                    _ => self.advance(),
4432                };
4433            }
4434        }
4435
4436        // MySQL: `UPDATE … [ORDER BY …] [LIMIT N]`. Swallow.
4437        if self.match_token(TokenType::Order) {
4438            self.expect(TokenType::By)?;
4439            let _ = self.parse_order_by_items()?;
4440        }
4441        if self.match_token(TokenType::Limit) {
4442            let _ = self.parse_expr()?;
4443        }
4444
4445        let returning = if self.match_token(TokenType::Returning) {
4446            self.parse_select_items()?
4447        } else {
4448            vec![]
4449        };
4450
4451        Ok(UpdateStatement {
4452            comments: vec![],
4453            table,
4454            assignments,
4455            from,
4456            where_clause,
4457            returning,
4458        })
4459    }
4460
4461    // ── DELETE ──────────────────────────────────────────────────────
4462
4463    fn parse_delete(&mut self) -> Result<DeleteStatement> {
4464        self.expect(TokenType::Delete)?;
4465        // MySQL multi-table form: `DELETE t1[, t2, ...] FROM <join expr>`.
4466        // Swallow the leading table-alias list (we don't model it) before
4467        // the mandatory FROM.
4468        let mut multi_table = false;
4469        if !matches!(self.peek_type(), TokenType::From) {
4470            let saved = self.pos;
4471            if self.is_name_token() {
4472                self.advance();
4473                let _ = self.match_token(TokenType::Dot);
4474                if self.is_name_token() {
4475                    self.advance();
4476                }
4477                while self.match_token(TokenType::Comma) {
4478                    if !self.is_name_token() {
4479                        break;
4480                    }
4481                    self.advance();
4482                    let _ = self.match_token(TokenType::Dot);
4483                    if self.is_name_token() {
4484                        self.advance();
4485                    }
4486                }
4487                if matches!(self.peek_type(), TokenType::From) {
4488                    multi_table = true;
4489                } else {
4490                    self.pos = saved;
4491                }
4492            }
4493        }
4494        // BigQuery / some Snowflake forms allow `DELETE <table> WHERE …`
4495        // (FROM optional). If FROM is missing but the next token starts a
4496        // table-ref, treat it as the implicit FROM target.
4497        let from_optional = !matches!(self.peek_type(), TokenType::From);
4498        if !from_optional {
4499            self.expect(TokenType::From)?;
4500        }
4501        let table = self.parse_table_ref()?;
4502        // MySQL: `DELETE FROM t PARTITION (p0[, p1, ...])` — swallow
4503        // partition selector.
4504        if matches!(self.peek_type(), TokenType::Partition)
4505            && matches!(
4506                self.peek_offset(1).map(|t| &t.token_type),
4507                Some(TokenType::LParen)
4508            )
4509        {
4510            self.advance();
4511            self.advance();
4512            let mut depth = 1;
4513            while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
4514                match self.peek_type() {
4515                    TokenType::LParen => depth += 1,
4516                    TokenType::RParen => {
4517                        depth -= 1;
4518                        if depth == 0 {
4519                            self.advance();
4520                            break;
4521                        }
4522                    }
4523                    _ => {}
4524                }
4525                self.advance();
4526            }
4527        }
4528        if multi_table {
4529            // Swallow JOIN clauses, additional comma-joined tables, and
4530            // any opaque tail up to USING / WHERE / RETURNING / ; / EOF.
4531            loop {
4532                if matches!(
4533                    self.peek_type(),
4534                    TokenType::Where
4535                        | TokenType::Using
4536                        | TokenType::Returning
4537                        | TokenType::Semicolon
4538                        | TokenType::Eof
4539                ) {
4540                    break;
4541                }
4542                self.advance();
4543            }
4544        }
4545
4546        let using = if self.match_token(TokenType::Using) {
4547            Some(FromClause {
4548                source: self.parse_table_source()?,
4549            })
4550        } else {
4551            None
4552        };
4553
4554        // Teradata `PREFERRING <expr> [PARTITION BY <list>]` skyline
4555        // clause on DELETE.
4556        if self.check_keyword("PREFERRING") {
4557            self.advance();
4558            loop {
4559                match self.peek_type() {
4560                    TokenType::Eof
4561                    | TokenType::Semicolon
4562                    | TokenType::Where
4563                    | TokenType::Returning
4564                    | TokenType::RParen => break,
4565                    _ => self.advance(),
4566                };
4567            }
4568        }
4569
4570        let where_clause = if self.match_token(TokenType::Where) {
4571            Some(self.parse_expr()?)
4572        } else {
4573            None
4574        };
4575
4576        // MySQL: `DELETE FROM tbl [WHERE ...] [ORDER BY ...] [LIMIT N]`.
4577        // Swallow ORDER BY and LIMIT modifiers — we don't model them on
4578        // DeleteStatement yet.
4579        if self.match_token(TokenType::Order) {
4580            self.expect(TokenType::By)?;
4581            let _ = self.parse_order_by_items()?;
4582        }
4583        if self.match_token(TokenType::Limit) {
4584            let _ = self.parse_expr()?;
4585        }
4586
4587        let returning = if self.match_token(TokenType::Returning) {
4588            self.parse_select_items()?
4589        } else {
4590            vec![]
4591        };
4592
4593        Ok(DeleteStatement {
4594            comments: vec![],
4595            table,
4596            using,
4597            where_clause,
4598            returning,
4599        })
4600    }
4601
4602    // ── MERGE ───────────────────────────────────────────────────────
4603
4604    fn parse_merge(&mut self) -> Result<MergeStatement> {
4605        self.expect(TokenType::Merge)?;
4606        let _ = self.match_token(TokenType::Into);
4607        let target = self.parse_table_ref()?;
4608
4609        self.expect(TokenType::Using)?;
4610        let source = self.parse_table_source()?;
4611
4612        // DuckDB supports `MERGE INTO t USING src USING (cols)` as a
4613        // shorthand for the ON condition (column-equality join, akin to
4614        // SQL USING for JOINs). Swallow the column list opaquely and
4615        // synthesize a trivial truthy ON expression so downstream parsing
4616        // continues. We don't model USING-style MERGE in the AST yet.
4617        let on = if self.match_token(TokenType::Using) {
4618            self.expect(TokenType::LParen)?;
4619            let _ = self.expect_name()?;
4620            while self.match_token(TokenType::Comma) {
4621                let _ = self.expect_name()?;
4622            }
4623            self.expect(TokenType::RParen)?;
4624            Expr::Boolean(true)
4625        } else {
4626            self.expect(TokenType::On)?;
4627            self.parse_expr()?
4628        };
4629
4630        let mut clauses = Vec::new();
4631        while self.match_token(TokenType::When) {
4632            clauses.push(self.parse_merge_clause()?);
4633        }
4634
4635        if clauses.is_empty() {
4636            return Err(SqlglotError::ParserError {
4637                message: "MERGE requires at least one WHEN clause".into(),
4638            });
4639        }
4640
4641        // OUTPUT clause (T-SQL extension)
4642        let output = if self.match_keyword("OUTPUT") {
4643            self.parse_select_items()?
4644        } else {
4645            vec![]
4646        };
4647
4648        // PostgreSQL: `MERGE … RETURNING <select_list>`. We don't yet model
4649        // RETURNING for MERGE, so swallow the items and discard them.
4650        if self.match_token(TokenType::Returning) {
4651            let _ = self.parse_select_items()?;
4652        }
4653
4654        Ok(MergeStatement {
4655            comments: vec![],
4656            target,
4657            source,
4658            on,
4659            clauses,
4660            output,
4661        })
4662    }
4663
4664    fn parse_merge_clause(&mut self) -> Result<MergeClause> {
4665        let kind = if self.match_token(TokenType::Not) {
4666            self.expect(TokenType::Matched)?;
4667            if self.match_keyword("BY") {
4668                if self.match_keyword("SOURCE") {
4669                    MergeClauseKind::NotMatchedBySource
4670                } else {
4671                    // BY TARGET is the default / explicit form
4672                    let _ = self.match_keyword("TARGET");
4673                    MergeClauseKind::NotMatched
4674                }
4675            } else {
4676                MergeClauseKind::NotMatched
4677            }
4678        } else {
4679            self.expect(TokenType::Matched)?;
4680            MergeClauseKind::Matched
4681        };
4682
4683        let condition = if self.match_token(TokenType::And) {
4684            Some(self.parse_expr()?)
4685        } else {
4686            None
4687        };
4688
4689        self.expect(TokenType::Then)?;
4690
4691        let action = self.parse_merge_action(&kind)?;
4692
4693        Ok(MergeClause {
4694            kind,
4695            condition,
4696            action,
4697        })
4698    }
4699
4700    fn parse_merge_action(&mut self, kind: &MergeClauseKind) -> Result<MergeAction> {
4701        if self.match_token(TokenType::Update) {
4702            self.expect(TokenType::Set)?;
4703            let mut assignments = Vec::new();
4704            loop {
4705                let mut col = self.expect_name()?;
4706                // Support dotted column names like target.col
4707                while self.match_token(TokenType::Dot) {
4708                    col.push('.');
4709                    col.push_str(&self.expect_name()?);
4710                }
4711                self.expect(TokenType::Eq)?;
4712                let val = self.parse_expr()?;
4713                assignments.push((col, val));
4714                if !self.match_token(TokenType::Comma) {
4715                    break;
4716                }
4717            }
4718            Ok(MergeAction::Update(assignments))
4719        } else if self.match_token(TokenType::Insert) {
4720            // INSERT ROW (BigQuery)
4721            if self.match_keyword("ROW") {
4722                return Ok(MergeAction::InsertRow);
4723            }
4724
4725            let columns = if self.match_token(TokenType::LParen) {
4726                let mut cols = vec![self.expect_name()?];
4727                while self.match_token(TokenType::Comma) {
4728                    cols.push(self.expect_name()?);
4729                }
4730                self.expect(TokenType::RParen)?;
4731                cols
4732            } else {
4733                vec![]
4734            };
4735
4736            self.expect(TokenType::Values)?;
4737            self.expect(TokenType::LParen)?;
4738            let values = self.parse_expr_list()?;
4739            self.expect(TokenType::RParen)?;
4740
4741            Ok(MergeAction::Insert { columns, values })
4742        } else if self.match_token(TokenType::Delete) {
4743            Ok(MergeAction::Delete)
4744        } else {
4745            Err(SqlglotError::ParserError {
4746                message: format!(
4747                    "Expected UPDATE, INSERT, or DELETE after WHEN {} THEN",
4748                    match kind {
4749                        MergeClauseKind::Matched => "MATCHED",
4750                        MergeClauseKind::NotMatched => "NOT MATCHED",
4751                        MergeClauseKind::NotMatchedBySource => "NOT MATCHED BY SOURCE",
4752                    }
4753                ),
4754            })
4755        }
4756    }
4757
4758    // ── CREATE ──────────────────────────────────────────────────────
4759
4760    fn parse_create(&mut self) -> Result<Statement> {
4761        self.expect(TokenType::Create)?;
4762
4763        let or_replace = if self.check_keyword("OR") {
4764            self.advance();
4765            self.expect(TokenType::Replace)?;
4766            true
4767        } else {
4768            false
4769        };
4770
4771        let temporary = self.match_token(TokenType::Temporary) || self.match_token(TokenType::Temp);
4772
4773        let materialized = self.match_token(TokenType::Materialized);
4774
4775        if self.match_token(TokenType::View) {
4776            return self
4777                .parse_create_view(or_replace, materialized)
4778                .map(Statement::CreateView);
4779        }
4780
4781        self.expect(TokenType::Table)?;
4782
4783        let if_not_exists = if self.match_token(TokenType::If) {
4784            self.expect(TokenType::Not)?;
4785            self.expect(TokenType::Exists)?;
4786            true
4787        } else {
4788            false
4789        };
4790
4791        let table = self.parse_table_ref_no_alias()?;
4792
4793        // CREATE TABLE ... AS SELECT ...
4794        if self.match_token(TokenType::As) {
4795            let query = self.parse_statement_inner()?;
4796            // Greenplum / Citus / etc. trailing `DISTRIBUTED BY (...)` /
4797            // `DISTRIBUTED RANDOMLY` / `DISTRIBUTED REPLICATED`. Swallow.
4798            if self.check_keyword("DISTRIBUTED") {
4799                self.advance();
4800                if self.check_keyword("BY") || matches!(self.peek_type(), TokenType::By) {
4801                    self.advance();
4802                    if self.match_token(TokenType::LParen) {
4803                        let mut depth = 1;
4804                        while depth > 0 {
4805                            match self.peek_type() {
4806                                TokenType::LParen => depth += 1,
4807                                TokenType::RParen => {
4808                                    depth -= 1;
4809                                    if depth == 0 {
4810                                        self.advance();
4811                                        break;
4812                                    }
4813                                }
4814                                TokenType::Eof => break,
4815                                _ => {}
4816                            }
4817                            self.advance();
4818                        }
4819                    }
4820                } else if self.is_name_token() {
4821                    // RANDOMLY / REPLICATED — single keyword
4822                    self.advance();
4823                }
4824            }
4825            return Ok(Statement::CreateTable(CreateTableStatement {
4826                comments: vec![],
4827                if_not_exists,
4828                temporary,
4829                table,
4830                columns: vec![],
4831                constraints: vec![],
4832                as_select: Some(Box::new(query)),
4833            }));
4834        }
4835
4836        self.expect(TokenType::LParen)?;
4837
4838        let mut columns = Vec::new();
4839        let mut constraints = Vec::new();
4840
4841        loop {
4842            // Check for table-level constraints
4843            if matches!(
4844                self.peek_type(),
4845                TokenType::Primary
4846                    | TokenType::Unique
4847                    | TokenType::Foreign
4848                    | TokenType::Check
4849                    | TokenType::Constraint
4850            ) {
4851                constraints.push(self.parse_table_constraint()?);
4852            } else if self.peek_type() != &TokenType::RParen {
4853                columns.push(self.parse_column_def()?);
4854            }
4855
4856            if !self.match_token(TokenType::Comma) {
4857                break;
4858            }
4859        }
4860        self.expect(TokenType::RParen)?;
4861
4862        // Tolerate dialect-specific trailing clauses (ClickHouse `ENGINE = X`,
4863        // `ORDER BY (...)`, `PARTITION BY ...`, `SETTINGS ...`, MySQL
4864        // `ENGINE=InnoDB DEFAULT CHARSET=utf8`, etc.) by consuming tokens
4865        // until the next statement boundary. Respects paren depth so a
4866        // top-level `;` inside `ORDER BY (a, b)` is not mistaken for end.
4867        self.skip_trailing_options();
4868
4869        Ok(Statement::CreateTable(CreateTableStatement {
4870            comments: vec![],
4871            if_not_exists,
4872            temporary,
4873            table,
4874            columns,
4875            constraints,
4876            as_select: None,
4877        }))
4878    }
4879
4880    /// Discard tokens up to (but not including) a top-level `;` or EOF.
4881    /// Used to skip dialect-specific tail clauses we don't model in the AST
4882    /// (CREATE TABLE engines, options, etc.).
4883    fn skip_trailing_options(&mut self) {
4884        let mut depth: i32 = 0;
4885        loop {
4886            match self.peek_type() {
4887                TokenType::Eof => break,
4888                TokenType::Semicolon if depth == 0 => break,
4889                TokenType::LParen => {
4890                    depth += 1;
4891                    self.advance();
4892                }
4893                TokenType::RParen => {
4894                    depth -= 1;
4895                    if depth < 0 {
4896                        break;
4897                    }
4898                    self.advance();
4899                }
4900                _ => {
4901                    self.advance();
4902                }
4903            }
4904        }
4905    }
4906
4907    fn parse_create_view(
4908        &mut self,
4909        or_replace: bool,
4910        materialized: bool,
4911    ) -> Result<CreateViewStatement> {
4912        let if_not_exists = if self.match_token(TokenType::If) {
4913            self.expect(TokenType::Not)?;
4914            self.expect(TokenType::Exists)?;
4915            true
4916        } else {
4917            false
4918        };
4919
4920        // Parse name without alias (so AS is not consumed as an alias)
4921        let name = self.parse_table_ref_no_alias()?;
4922
4923        let columns = if self.match_token(TokenType::LParen) {
4924            let mut cols = vec![self.expect_name()?];
4925            while self.match_token(TokenType::Comma) {
4926                cols.push(self.expect_name()?);
4927            }
4928            self.expect(TokenType::RParen)?;
4929            cols
4930        } else {
4931            vec![]
4932        };
4933
4934        self.expect(TokenType::As)?;
4935        let query = self.parse_statement_inner()?;
4936
4937        Ok(CreateViewStatement {
4938            comments: vec![],
4939            name,
4940            columns,
4941            query: Box::new(query),
4942            or_replace,
4943            materialized,
4944            if_not_exists,
4945        })
4946    }
4947
4948    fn parse_table_constraint(&mut self) -> Result<TableConstraint> {
4949        let name = if self.match_token(TokenType::Constraint) {
4950            Some(self.expect_name()?)
4951        } else {
4952            None
4953        };
4954
4955        if self.match_token(TokenType::Primary) {
4956            self.expect(TokenType::Key)?;
4957            self.expect(TokenType::LParen)?;
4958            let columns = self.parse_name_list()?;
4959            self.expect(TokenType::RParen)?;
4960            // TiDB / MySQL: `PRIMARY KEY (cols) GLOBAL|LOCAL` index scope
4961            // modifier and `USING BTREE|HASH` index-type modifier.
4962            if self.is_name_token()
4963                && matches!(
4964                    self.peek().value.to_uppercase().as_str(),
4965                    "GLOBAL" | "LOCAL"
4966                )
4967            {
4968                self.advance();
4969            }
4970            if self.match_token(TokenType::Using) && self.is_name_token() {
4971                self.advance();
4972            }
4973            self.swallow_constraint_modifiers();
4974            Ok(TableConstraint::PrimaryKey { name, columns })
4975        } else if self.match_token(TokenType::Unique) {
4976            let _ = self.match_token(TokenType::Index) || self.match_token(TokenType::Key);
4977            // Optional index name before `(`.
4978            if !matches!(self.peek_type(), TokenType::LParen) && self.is_name_token() {
4979                self.advance();
4980            }
4981            self.expect(TokenType::LParen)?;
4982            let columns = self.parse_name_list()?;
4983            self.expect(TokenType::RParen)?;
4984            if self.is_name_token()
4985                && matches!(
4986                    self.peek().value.to_uppercase().as_str(),
4987                    "GLOBAL" | "LOCAL"
4988                )
4989            {
4990                self.advance();
4991            }
4992            if self.match_token(TokenType::Using) && self.is_name_token() {
4993                self.advance();
4994            }
4995            self.swallow_constraint_modifiers();
4996            Ok(TableConstraint::Unique { name, columns })
4997        } else if self.match_token(TokenType::Foreign) {
4998            self.expect(TokenType::Key)?;
4999            self.expect(TokenType::LParen)?;
5000            let columns = self.parse_name_list()?;
5001            self.expect(TokenType::RParen)?;
5002            self.expect(TokenType::References)?;
5003            let ref_table = self.parse_table_ref()?;
5004            self.expect(TokenType::LParen)?;
5005            let ref_columns = self.parse_name_list()?;
5006            self.expect(TokenType::RParen)?;
5007
5008            // PG / ANSI `MATCH FULL | PARTIAL | SIMPLE` clause — swallow.
5009            if self.check_keyword("MATCH") {
5010                self.advance();
5011                if self.is_name_token() {
5012                    self.advance();
5013                }
5014            }
5015
5016            let mut on_delete = None;
5017            let mut on_update = None;
5018            // Accept ON DELETE / ON UPDATE clauses in any order. Match the
5019            // ON keyword only when the following token is DELETE / UPDATE
5020            // so a misplaced ON UPDATE doesn't consume the bare ON token
5021            // and orphan the rest of the action list.
5022            while self.peek_type() == &TokenType::On {
5023                let next = self.peek_offset(1).map(|t| &t.token_type);
5024                if matches!(next, Some(TokenType::Delete)) {
5025                    self.advance();
5026                    self.advance();
5027                    on_delete = Some(self.parse_referential_action()?);
5028                } else if matches!(next, Some(TokenType::Update)) {
5029                    self.advance();
5030                    self.advance();
5031                    on_update = Some(self.parse_referential_action()?);
5032                } else {
5033                    break;
5034                }
5035            }
5036
5037            self.swallow_constraint_modifiers();
5038            Ok(TableConstraint::ForeignKey {
5039                name,
5040                columns,
5041                ref_table,
5042                ref_columns,
5043                on_delete,
5044                on_update,
5045            })
5046        } else if self.match_token(TokenType::Check) {
5047            self.expect(TokenType::LParen)?;
5048            let expr = self.parse_expr()?;
5049            self.expect(TokenType::RParen)?;
5050            self.swallow_constraint_modifiers();
5051            Ok(TableConstraint::Check { name, expr })
5052        } else {
5053            Err(SqlglotError::ParserError {
5054                message: "Expected constraint type".into(),
5055            })
5056        }
5057    }
5058
5059    /// Swallow trailing constraint modifiers shared by FK / CHECK / PK /
5060    /// UNIQUE: `NOT VALID`, `[NOT] ENFORCED`, `DEFERRABLE`, `NOT DEFERRABLE`,
5061    /// `INITIALLY DEFERRED | IMMEDIATE`, `NO INHERIT`. Best-effort — we
5062    /// don't model them in the AST.
5063    fn swallow_constraint_modifiers(&mut self) {
5064        loop {
5065            if self.check_keyword("NOT")
5066                && self
5067                    .peek_offset(1)
5068                    .map(|t| t.value.to_uppercase())
5069                    .as_deref()
5070                    .is_some_and(|v| matches!(v, "VALID" | "ENFORCED" | "DEFERRABLE"))
5071            {
5072                self.advance();
5073                self.advance();
5074                continue;
5075            }
5076            if self.check_keyword("ENFORCED")
5077                || self.check_keyword("DEFERRABLE")
5078                || self.check_keyword("CLUSTERED")
5079                || self.check_keyword("NONCLUSTERED")
5080                || self.check_keyword("INVISIBLE")
5081                || self.check_keyword("VISIBLE")
5082            {
5083                self.advance();
5084                continue;
5085            }
5086            if self.check_keyword("INITIALLY") {
5087                self.advance();
5088                if self.is_name_token() {
5089                    self.advance();
5090                }
5091                continue;
5092            }
5093            if self.check_keyword("NO")
5094                && self
5095                    .peek_offset(1)
5096                    .map(|t| t.value.eq_ignore_ascii_case("INHERIT"))
5097                    .unwrap_or(false)
5098            {
5099                self.advance();
5100                self.advance();
5101                continue;
5102            }
5103            break;
5104        }
5105    }
5106
5107    fn parse_referential_action(&mut self) -> Result<ReferentialAction> {
5108        if self.match_token(TokenType::Cascade) {
5109            Ok(ReferentialAction::Cascade)
5110        } else if self.match_token(TokenType::Restrict) {
5111            Ok(ReferentialAction::Restrict)
5112        } else if self.match_token(TokenType::Set) {
5113            if self.match_token(TokenType::Null) {
5114                Ok(ReferentialAction::SetNull)
5115            } else if self.match_token(TokenType::Default) {
5116                Ok(ReferentialAction::SetDefault)
5117            } else {
5118                Err(SqlglotError::ParserError {
5119                    message: "Expected NULL or DEFAULT after SET".into(),
5120                })
5121            }
5122        } else if self.check_keyword("NO") {
5123            self.advance();
5124            self.expect(TokenType::Identifier)?; // ACTION
5125            Ok(ReferentialAction::NoAction)
5126        } else {
5127            Err(SqlglotError::ParserError {
5128                message: "Expected referential action (CASCADE, RESTRICT, SET NULL, SET DEFAULT, NO ACTION)".into(),
5129            })
5130        }
5131    }
5132
5133    fn parse_name_list(&mut self) -> Result<Vec<String>> {
5134        let mut names = vec![self.expect_name()?];
5135        while self.match_token(TokenType::Comma) {
5136            names.push(self.expect_name()?);
5137        }
5138        Ok(names)
5139    }
5140
5141    /// Parse a dotted column reference for INSERT column lists:
5142    /// `name` or `parent.child` (ClickHouse nested columns).
5143    fn parse_dotted_name(&mut self) -> Result<String> {
5144        let mut name = self.expect_name()?;
5145        while self.peek_type() == &TokenType::Dot {
5146            let next = self.peek_offset(1).map(|t| t.token_type.clone());
5147            let next_is_namelike = matches!(
5148                next,
5149                Some(TokenType::Identifier)
5150                    | Some(TokenType::Star)
5151                    | Some(TokenType::Int)
5152                    | Some(TokenType::BigInt)
5153                    | Some(TokenType::Text)
5154                    | Some(TokenType::Date)
5155                    | Some(TokenType::Timestamp)
5156            );
5157            if !next_is_namelike {
5158                break;
5159            }
5160            self.advance(); // .
5161            if self.peek_type() == &TokenType::Star {
5162                name.push('.');
5163                name.push('*');
5164                self.advance();
5165                break;
5166            }
5167            let part = self.expect_name()?;
5168            name.push('.');
5169            name.push_str(&part);
5170        }
5171        Ok(name)
5172    }
5173
5174    fn parse_column_def(&mut self) -> Result<ColumnDef> {
5175        let name = self.expect_name()?;
5176        let data_type = self.parse_data_type()?;
5177
5178        let mut nullable = None;
5179        let mut default = None;
5180        let mut primary_key = false;
5181        let mut unique = false;
5182        let mut auto_increment = false;
5183        let mut collation = None;
5184        let mut comment = None;
5185
5186        loop {
5187            if self.match_token(TokenType::Not) {
5188                self.expect(TokenType::Null)?;
5189                nullable = Some(false);
5190            } else if self.peek_type() == &TokenType::Null {
5191                self.advance();
5192                nullable = Some(true);
5193            } else if self.peek_type() == &TokenType::As
5194                && matches!(
5195                    self.peek_offset(1).map(|t| &t.token_type),
5196                    Some(TokenType::LParen)
5197                )
5198            {
5199                // SQLite / MySQL generated-column shorthand:
5200                //   `col TYPE AS (expr) [STORED|VIRTUAL|PERSISTENT]`.
5201                // Swallow AS, the parenthesised expression (depth-balanced),
5202                // and the optional storage-kind keyword.
5203                self.advance(); // AS
5204                self.advance(); // (
5205                let mut depth: i32 = 1;
5206                while depth > 0 {
5207                    match self.peek_type() {
5208                        TokenType::LParen => {
5209                            depth += 1;
5210                            self.advance();
5211                        }
5212                        TokenType::RParen => {
5213                            depth -= 1;
5214                            self.advance();
5215                        }
5216                        TokenType::Eof => break,
5217                        _ => {
5218                            self.advance();
5219                        }
5220                    }
5221                }
5222                if self.is_name_token()
5223                    && matches!(
5224                        self.peek().value.to_uppercase().as_str(),
5225                        "STORED" | "VIRTUAL" | "PERSISTENT" | "PERSISTED"
5226                    )
5227                {
5228                    self.advance();
5229                }
5230            } else if self.match_token(TokenType::Default) {
5231                // SQL Server / IBM `DEFAULT NEXT VALUE FOR seq[.qual]`.
5232                if self.is_name_token()
5233                    && self.peek().value.eq_ignore_ascii_case("NEXT")
5234                    && self
5235                        .peek_offset(1)
5236                        .map(|t| t.value.eq_ignore_ascii_case("VALUE"))
5237                        .unwrap_or(false)
5238                    && self
5239                        .peek_offset(2)
5240                        .map(|t| t.value.eq_ignore_ascii_case("FOR"))
5241                        .unwrap_or(false)
5242                {
5243                    self.advance();
5244                    self.advance();
5245                    self.advance();
5246                    let mut seq = self.expect_name()?;
5247                    while self.match_token(TokenType::Dot) {
5248                        seq.push('.');
5249                        seq.push_str(&self.expect_name()?);
5250                    }
5251                    default = Some(Expr::Function {
5252                        name: "NEXT_VALUE_FOR".to_string(),
5253                        args: vec![Expr::Column {
5254                            table: None,
5255                            name: seq,
5256                            quote_style: QuoteStyle::None,
5257                            table_quote_style: QuoteStyle::None,
5258                        }],
5259                        distinct: false,
5260                        filter: None,
5261                        over: None,
5262                        order_by: Vec::new(),
5263                        within_group: false,
5264                    });
5265                } else {
5266                    default = Some(self.parse_expr()?);
5267                }
5268            } else if self.match_token(TokenType::Primary) {
5269                self.expect(TokenType::Key)?;
5270                primary_key = true;
5271            } else if self.match_token(TokenType::Unique) {
5272                unique = true;
5273            } else if self.match_token(TokenType::AutoIncrement) {
5274                auto_increment = true;
5275            } else if self.match_token(TokenType::Collate) {
5276                collation = Some(self.expect_name()?);
5277            } else if self.match_token(TokenType::Comment) {
5278                let tok = self.expect(TokenType::String)?;
5279                comment = Some(tok.value);
5280            } else if self.match_token(TokenType::References) {
5281                // Inline foreign key — skip for now
5282                let _ = self.parse_table_ref()?;
5283                if self.match_token(TokenType::LParen) {
5284                    while !self.match_token(TokenType::RParen) {
5285                        self.advance();
5286                    }
5287                }
5288            } else if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("GENERATED") {
5289                // SQL:2003 / MySQL / PG / SQL Server identity / computed
5290                // column: `GENERATED ALWAYS AS (expr) [VIRTUAL|STORED]`,
5291                // `GENERATED ALWAYS AS IDENTITY [(...)]`,
5292                // `GENERATED BY DEFAULT AS IDENTITY [(...)]`. Swallow up
5293                // through the trailing parenthesised body if present and
5294                // let the next loop iteration pick up VIRTUAL/STORED.
5295                self.advance();
5296                if self.is_name_token()
5297                    && (self.peek().value.eq_ignore_ascii_case("ALWAYS")
5298                        || self.peek().value.eq_ignore_ascii_case("BY"))
5299                {
5300                    self.advance();
5301                    if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("DEFAULT") {
5302                        self.advance();
5303                    }
5304                }
5305                if self.match_token(TokenType::As) {
5306                    if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("IDENTITY") {
5307                        self.advance();
5308                    } else if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("ROW")
5309                    {
5310                        // SQL Server `GENERATED AS ROW START | END`.
5311                        self.advance();
5312                        if self.is_name_token() {
5313                            self.advance();
5314                        }
5315                    }
5316                }
5317                if self.peek_type() == &TokenType::LParen {
5318                    let mut depth = 0_i32;
5319                    self.advance();
5320                    depth += 1;
5321                    while depth > 0 {
5322                        match self.peek_type() {
5323                            TokenType::LParen => depth += 1,
5324                            TokenType::RParen => {
5325                                depth -= 1;
5326                                if depth == 0 {
5327                                    self.advance();
5328                                    break;
5329                                }
5330                            }
5331                            TokenType::Eof => break,
5332                            _ => {}
5333                        }
5334                        self.advance();
5335                    }
5336                }
5337            } else if self.is_name_token()
5338                && matches!(
5339                    self.peek().value.to_uppercase().as_str(),
5340                    "CODEC"
5341                        | "TTL"
5342                        | "MATERIALIZED"
5343                        | "ALIAS"
5344                        | "EPHEMERAL"
5345                        | "PERSISTED"
5346                        | "PERSISTENT"
5347                        | "VIRTUAL"
5348                        | "STORED"
5349                        | "ENCODE"
5350                        | "ENCRYPT"
5351                        | "MASKED"
5352                        | "INVISIBLE"
5353                        | "VISIBLE"
5354                        | "ENFORCED"
5355                        | "OPTIONS"
5356                        | "COMPRESSION"
5357                        | "SORTKEY"
5358                        | "DISTKEY"
5359                        | "CHARSET"
5360                        | "CHARACTER"
5361                        | "SRID"
5362                        | "FORMAT"
5363                        | "TAG"
5364                        | "MASKING"
5365                )
5366            {
5367                // ClickHouse / Snowflake / Redshift column modifiers. Consume
5368                // the keyword and the optional parenthesised body (`CODEC(...)`,
5369                // `TTL expr`, etc.) so the rest of the column def parses.
5370                self.advance();
5371                if self.peek_type() == &TokenType::LParen {
5372                    let mut depth = 0_i32;
5373                    self.advance();
5374                    depth += 1;
5375                    while depth > 0 {
5376                        match self.peek_type() {
5377                            TokenType::LParen => depth += 1,
5378                            TokenType::RParen => {
5379                                depth -= 1;
5380                                if depth == 0 {
5381                                    self.advance();
5382                                    break;
5383                                }
5384                            }
5385                            TokenType::Eof => break,
5386                            _ => {}
5387                        }
5388                        self.advance();
5389                    }
5390                } else {
5391                    // Best-effort: swallow an expression up to comma /
5392                    // top-level RParen / column-def boundary, balancing
5393                    // nested parens (e.g. `TTL toDate('2000-01-02')`,
5394                    // `ALIAS arrayResize(emptyArrayUInt32(), length(\`Arr.C2\`))`).
5395                    let mut depth: i32 = 0;
5396                    loop {
5397                        match self.peek_type() {
5398                            TokenType::LParen => {
5399                                depth += 1;
5400                                self.advance();
5401                            }
5402                            TokenType::RParen => {
5403                                if depth == 0 {
5404                                    break;
5405                                }
5406                                depth -= 1;
5407                                self.advance();
5408                            }
5409                            TokenType::Comma if depth == 0 => break,
5410                            TokenType::Eof => break,
5411                            _ => {
5412                                self.advance();
5413                            }
5414                        }
5415                    }
5416                }
5417            } else {
5418                break;
5419            }
5420        }
5421
5422        Ok(ColumnDef {
5423            name,
5424            data_type,
5425            nullable,
5426            default,
5427            primary_key,
5428            unique,
5429            auto_increment,
5430            collation,
5431            comment,
5432        })
5433    }
5434
5435    fn parse_data_type(&mut self) -> Result<DataType> {
5436        let token = self.peek().clone();
5437        // DuckDB / Spark template syntax: `${var}` (or `?` placeholder) used
5438        // where a data type is expected. Lower to `Unknown(name)` so the
5439        // surrounding expression parses.
5440        if matches!(token.token_type, TokenType::Parameter) {
5441            self.advance();
5442            return Ok(DataType::Unknown(token.value));
5443        }
5444        let type_result = match &token.token_type {
5445            TokenType::Int | TokenType::Integer => {
5446                self.advance();
5447                Ok(DataType::Int)
5448            }
5449            TokenType::BigInt => {
5450                self.advance();
5451                Ok(DataType::BigInt)
5452            }
5453            TokenType::SmallInt => {
5454                self.advance();
5455                Ok(DataType::SmallInt)
5456            }
5457            TokenType::TinyInt => {
5458                self.advance();
5459                Ok(DataType::TinyInt)
5460            }
5461            TokenType::Float => {
5462                self.advance();
5463                Ok(DataType::Float)
5464            }
5465            TokenType::Double => {
5466                self.advance();
5467                let _ = self.match_keyword("PRECISION");
5468                Ok(DataType::Double)
5469            }
5470            TokenType::Real => {
5471                self.advance();
5472                Ok(DataType::Real)
5473            }
5474            TokenType::Decimal | TokenType::Numeric => {
5475                let is_numeric = token.token_type == TokenType::Numeric;
5476                self.advance();
5477                let (precision, scale) = self.parse_type_params()?;
5478                if is_numeric {
5479                    Ok(DataType::Numeric { precision, scale })
5480                } else {
5481                    Ok(DataType::Decimal { precision, scale })
5482                }
5483            }
5484            TokenType::Varchar => {
5485                self.advance();
5486                let len = self.parse_single_type_param()?;
5487                Ok(DataType::Varchar(len))
5488            }
5489            TokenType::Char => {
5490                self.advance();
5491                let len = self.parse_single_type_param()?;
5492                Ok(DataType::Char(len))
5493            }
5494            TokenType::Text => {
5495                self.advance();
5496                Ok(DataType::Text)
5497            }
5498            TokenType::Boolean => {
5499                self.advance();
5500                Ok(DataType::Boolean)
5501            }
5502            TokenType::Date => {
5503                self.advance();
5504                Ok(DataType::Date)
5505            }
5506            TokenType::Timestamp => {
5507                self.advance();
5508                let precision = self.parse_single_type_param()?;
5509                let with_tz = if self.match_keyword("WITH") {
5510                    let _ = self.match_keyword("LOCAL");
5511                    let _ = self.match_keyword("TIME");
5512                    let _ = self.match_keyword("ZONE");
5513                    true
5514                } else if self.match_keyword("WITHOUT") {
5515                    let _ = self.match_keyword("TIME");
5516                    let _ = self.match_keyword("ZONE");
5517                    false
5518                } else {
5519                    false
5520                };
5521                Ok(DataType::Timestamp { precision, with_tz })
5522            }
5523            TokenType::TimestampTz => {
5524                self.advance();
5525                let precision = self.parse_single_type_param()?;
5526                Ok(DataType::Timestamp {
5527                    precision,
5528                    with_tz: true,
5529                })
5530            }
5531            TokenType::Time => {
5532                self.advance();
5533                let precision = self.parse_single_type_param()?;
5534                Ok(DataType::Time { precision })
5535            }
5536            TokenType::Interval => {
5537                self.advance();
5538                Ok(DataType::Interval)
5539            }
5540            TokenType::Blob => {
5541                self.advance();
5542                Ok(DataType::Blob)
5543            }
5544            TokenType::Bytea => {
5545                self.advance();
5546                Ok(DataType::Bytea)
5547            }
5548            TokenType::Json => {
5549                self.advance();
5550                Ok(DataType::Json)
5551            }
5552            TokenType::Jsonb => {
5553                self.advance();
5554                Ok(DataType::Jsonb)
5555            }
5556            TokenType::Uuid => {
5557                self.advance();
5558                Ok(DataType::Uuid)
5559            }
5560            TokenType::Array => {
5561                self.advance();
5562                if self.match_token(TokenType::Lt) {
5563                    let inner = self.parse_data_type()?;
5564                    self.expect(TokenType::Gt)?;
5565                    Ok(DataType::Array(Some(Box::new(inner))))
5566                } else {
5567                    Ok(DataType::Array(None))
5568                }
5569            }
5570            TokenType::Struct => {
5571                self.advance();
5572                // STRUCT<a INT, b STRING> (Hive/Spark) or STRUCT(a INT, b INT) (DuckDB).
5573                // Swallow the body — we don't model named struct fields in the AST.
5574                let close = if self.match_token(TokenType::Lt) {
5575                    Some(TokenType::Gt)
5576                } else if self.match_token(TokenType::LParen) {
5577                    Some(TokenType::RParen)
5578                } else {
5579                    None
5580                };
5581                if let Some(close_tok) = close {
5582                    let mut depth = 1_i32;
5583                    while depth > 0 {
5584                        if self.peek_type() == &TokenType::Eof {
5585                            break;
5586                        }
5587                        if self.peek_type() == &close_tok {
5588                            depth -= 1;
5589                            if depth == 0 {
5590                                self.advance();
5591                                break;
5592                            }
5593                        } else if matches!(self.peek_type(), TokenType::Lt | TokenType::LParen)
5594                            && (self.peek_type() == &TokenType::Lt && close_tok == TokenType::Gt
5595                                || self.peek_type() == &TokenType::LParen
5596                                    && close_tok == TokenType::RParen)
5597                        {
5598                            depth += 1;
5599                        }
5600                        self.advance();
5601                    }
5602                }
5603                Ok(DataType::Unknown("STRUCT".to_string()))
5604            }
5605            TokenType::Map => {
5606                self.advance();
5607                let close = if self.match_token(TokenType::Lt) {
5608                    Some(TokenType::Gt)
5609                } else if self.match_token(TokenType::LParen) {
5610                    Some(TokenType::RParen)
5611                } else {
5612                    None
5613                };
5614                if let Some(close_tok) = close {
5615                    let mut depth = 1_i32;
5616                    while depth > 0 {
5617                        if self.peek_type() == &TokenType::Eof {
5618                            break;
5619                        }
5620                        if self.peek_type() == &close_tok {
5621                            depth -= 1;
5622                            if depth == 0 {
5623                                self.advance();
5624                                break;
5625                            }
5626                        } else if (self.peek_type() == &TokenType::Lt && close_tok == TokenType::Gt)
5627                            || (self.peek_type() == &TokenType::LParen
5628                                && close_tok == TokenType::RParen)
5629                        {
5630                            depth += 1;
5631                        }
5632                        self.advance();
5633                    }
5634                }
5635                Ok(DataType::Unknown("MAP".to_string()))
5636            }
5637            TokenType::Identifier => {
5638                let name = token.value.to_uppercase();
5639                self.advance();
5640                match name.as_str() {
5641                    "STRING" => Ok(DataType::String),
5642                    "BINARY" => {
5643                        let len = self.parse_single_type_param()?;
5644                        Ok(DataType::Binary(len))
5645                    }
5646                    "VARBINARY" => {
5647                        let len = self.parse_single_type_param()?;
5648                        Ok(DataType::Varbinary(len))
5649                    }
5650                    "DATETIME" => Ok(DataType::DateTime),
5651                    "BYTES" => Ok(DataType::Bytes),
5652                    "VARIANT" => Ok(DataType::Variant),
5653                    "OBJECT" => Ok(DataType::Object),
5654                    "XML" => Ok(DataType::Xml),
5655                    "INET" => Ok(DataType::Inet),
5656                    "CIDR" => Ok(DataType::Cidr),
5657                    "MACADDR" => Ok(DataType::Macaddr),
5658                    "BIT" => {
5659                        // Postgres `BIT VARYING(n)` is the same as VARBIT.
5660                        // Swallow the VARYING keyword if present and parse
5661                        // the length normally.
5662                        if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("VARYING")
5663                        {
5664                            self.advance();
5665                            let len = self.parse_single_type_param()?;
5666                            return Ok(DataType::Varbinary(len));
5667                        }
5668                        let len = self.parse_single_type_param()?;
5669                        Ok(DataType::Bit(len))
5670                    }
5671                    "MONEY" => Ok(DataType::Money),
5672                    "SERIAL" => Ok(DataType::Serial),
5673                    "BIGSERIAL" => Ok(DataType::BigSerial),
5674                    "SMALLSERIAL" => Ok(DataType::SmallSerial),
5675                    "REGCLASS" => Ok(DataType::Regclass),
5676                    "REGTYPE" => Ok(DataType::Regtype),
5677                    "HSTORE" => Ok(DataType::Hstore),
5678                    "GEOGRAPHY" => Ok(DataType::Geography),
5679                    "GEOMETRY" => Ok(DataType::Geometry),
5680                    "SUPER" => Ok(DataType::Super),
5681                    _ => Ok(DataType::Unknown(name)),
5682                }
5683            }
5684            _ => {
5685                // Fallback: accept any keyword-like token as an unknown
5686                // data type by its textual value. Covers PostgreSQL `cube`,
5687                // `lseg`, `path`, `polygon`, and any vendor-specific type
5688                // name that happens to collide with a TokenType variant.
5689                let v = token.value.clone();
5690                if !v.is_empty() && v.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
5691                    self.advance();
5692                    Ok(DataType::Unknown(v.to_uppercase()))
5693                } else {
5694                    Err(SqlglotError::ParserError {
5695                        message: format!("Expected data type, got {:?}", token.token_type),
5696                    })
5697                }
5698            }
5699        };
5700
5701        // PostgreSQL opt_array_bounds: typename[], typename[N], typename[][]...
5702        let mut dt = type_result?;
5703        while self.match_token(TokenType::LBracket) {
5704            // Consume optional integer bound (PostgreSQL ignores it but accepts it)
5705            let _ = self.match_token(TokenType::Number);
5706            self.expect(TokenType::RBracket)?;
5707            dt = DataType::Array(Some(Box::new(dt)));
5708        }
5709        // ClickHouse parameterized types: `DateTime('Asia/Dubai')`,
5710        // `Nullable(String)`, `Array(Int32)`, `Enum8('a' = 1, 'b' = 2)`,
5711        // `Decimal(9, 2)`, etc. The base type was already produced — swallow
5712        // the parenthesized parameter list so the surrounding expression
5713        // continues to parse.
5714        if self.peek_type() == &TokenType::LParen {
5715            let saved = self.pos;
5716            self.advance();
5717            let mut depth = 1;
5718            let mut ok = true;
5719            while depth > 0 {
5720                match self.peek_type() {
5721                    TokenType::LParen => depth += 1,
5722                    TokenType::RParen => {
5723                        depth -= 1;
5724                        if depth == 0 {
5725                            self.advance();
5726                            break;
5727                        }
5728                    }
5729                    TokenType::Eof => {
5730                        ok = false;
5731                        break;
5732                    }
5733                    _ => {}
5734                }
5735                self.advance();
5736            }
5737            if !ok {
5738                self.pos = saved;
5739            }
5740        }
5741        Ok(dt)
5742    }
5743
5744    fn parse_type_params(&mut self) -> Result<(Option<u32>, Option<u32>)> {
5745        if self.match_token(TokenType::LParen) {
5746            let p: Option<u32> = self.expect(TokenType::Number)?.value.parse().ok();
5747            let s = if self.match_token(TokenType::Comma) {
5748                self.expect(TokenType::Number)?.value.parse().ok()
5749            } else {
5750                None
5751            };
5752            self.expect(TokenType::RParen)?;
5753            Ok((p, s))
5754        } else {
5755            Ok((None, None))
5756        }
5757    }
5758
5759    fn parse_single_type_param(&mut self) -> Result<Option<u32>> {
5760        if self.match_token(TokenType::LParen) {
5761            // Handle TSQL MAX keyword (e.g. VARBINARY(MAX), VARCHAR(MAX))
5762            if self.check_keyword("MAX") {
5763                self.advance(); // consume MAX
5764                self.expect(TokenType::RParen)?;
5765                return Ok(None);
5766            }
5767            let n: Option<u32> = self.expect(TokenType::Number)?.value.parse().ok();
5768            self.expect(TokenType::RParen)?;
5769            Ok(n)
5770        } else {
5771            Ok(None)
5772        }
5773    }
5774
5775    // ── DROP ────────────────────────────────────────────────────────
5776
5777    fn parse_drop(&mut self) -> Result<Statement> {
5778        self.expect(TokenType::Drop)?;
5779
5780        if self.match_token(TokenType::Materialized) {
5781            self.expect(TokenType::View)?;
5782            let if_exists = if self.match_token(TokenType::If) {
5783                self.expect(TokenType::Exists)?;
5784                true
5785            } else {
5786                false
5787            };
5788            let name = self.parse_table_ref()?;
5789            // MySQL/MariaDB allow comma-list — swallow the rest.
5790            while self.match_token(TokenType::Comma) {
5791                let _ = self.parse_table_ref()?;
5792            }
5793            // Trailing CASCADE / RESTRICT.
5794            let _ = self.match_token(TokenType::Cascade) || self.match_token(TokenType::Restrict);
5795            return Ok(Statement::DropView(DropViewStatement {
5796                comments: vec![],
5797                name,
5798                if_exists,
5799                materialized: true,
5800            }));
5801        }
5802
5803        if self.match_token(TokenType::View) {
5804            let if_exists = if self.match_token(TokenType::If) {
5805                self.expect(TokenType::Exists)?;
5806                true
5807            } else {
5808                false
5809            };
5810            let name = self.parse_table_ref()?;
5811            while self.match_token(TokenType::Comma) {
5812                let _ = self.parse_table_ref()?;
5813            }
5814            let _ = self.match_token(TokenType::Cascade) || self.match_token(TokenType::Restrict);
5815            return Ok(Statement::DropView(DropViewStatement {
5816                comments: vec![],
5817                name,
5818                if_exists,
5819                materialized: false,
5820            }));
5821        }
5822
5823        // DROP <kind> ... — preserve as a Command for non-TABLE/VIEW drops
5824        // (FUNCTION, PROCEDURE, SCHEMA, DATABASE, INDEX, ROLE, USER, …).
5825        if self.peek_type() != &TokenType::Table {
5826            // Already consumed DROP; capture the remainder.
5827            let body = self.consume_raw_to_statement_end();
5828            return Ok(Statement::Command(CommandStatement {
5829                comments: vec![],
5830                kind: "DROP".to_string(),
5831                body,
5832            }));
5833        }
5834
5835        self.expect(TokenType::Table)?;
5836
5837        let if_exists = if self.match_token(TokenType::If) {
5838            self.expect(TokenType::Exists)?;
5839            true
5840        } else {
5841            false
5842        };
5843
5844        let table = self.parse_table_ref()?;
5845        // MySQL / MariaDB: `DROP TABLE [IF EXISTS] t1, t2, …`. Swallow the
5846        // extra table names so the statement parses.
5847        while self.match_token(TokenType::Comma) {
5848            let _ = self.parse_table_ref()?;
5849        }
5850        let cascade = self.match_token(TokenType::Cascade);
5851        // Tolerate Doris / StarRocks / Oracle trailing modifiers on DROP TABLE
5852        // (`FORCE`, `PURGE`, `RESTRICT`).
5853        while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon) {
5854            if self.is_name_token()
5855                && matches!(
5856                    self.peek().value.to_uppercase().as_str(),
5857                    "FORCE" | "PURGE" | "RESTRICT"
5858                )
5859            {
5860                self.advance();
5861            } else if matches!(self.peek_type(), TokenType::Restrict) {
5862                self.advance();
5863            } else {
5864                break;
5865            }
5866        }
5867
5868        Ok(Statement::DropTable(DropTableStatement {
5869            comments: vec![],
5870            if_exists,
5871            table,
5872            cascade,
5873        }))
5874    }
5875
5876    // ── ALTER TABLE ─────────────────────────────────────────────────
5877
5878    fn parse_alter_table(&mut self) -> Result<AlterTableStatement> {
5879        self.expect(TokenType::Alter)?;
5880        self.expect(TokenType::Table)?;
5881        let table = self.parse_table_ref_no_alias()?;
5882
5883        let mut actions = Vec::new();
5884        loop {
5885            let action = self.parse_alter_action()?;
5886            actions.push(action);
5887            if !self.match_token(TokenType::Comma) {
5888                break;
5889            }
5890        }
5891
5892        Ok(AlterTableStatement {
5893            comments: vec![],
5894            table,
5895            actions,
5896        })
5897    }
5898
5899    fn parse_alter_action(&mut self) -> Result<AlterTableAction> {
5900        // Hive multi-partition continuation after a comma:
5901        // `ALTER TABLE t DROP PARTITION (a), PARTITION (b)`. Swallow the
5902        // bare PARTITION clause.
5903        if self.peek_type() == &TokenType::Partition {
5904            self.advance();
5905            let mut depth: i32 = 0;
5906            while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
5907                && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
5908            {
5909                match self.peek_type() {
5910                    TokenType::LParen => depth += 1,
5911                    TokenType::RParen => depth = depth.saturating_sub(1),
5912                    _ => {}
5913                }
5914                self.advance();
5915            }
5916            return Ok(AlterTableAction::DropColumn {
5917                name: String::new(),
5918                if_exists: false,
5919            });
5920        }
5921        if self.match_keyword("ADD") {
5922            if matches!(
5923                self.peek_type(),
5924                TokenType::Constraint
5925                    | TokenType::Primary
5926                    | TokenType::Unique
5927                    | TokenType::Foreign
5928                    | TokenType::Check
5929            ) {
5930                let constraint = self.parse_table_constraint()?;
5931                self.swallow_constraint_modifiers();
5932                Ok(AlterTableAction::AddConstraint(constraint))
5933            } else if self.check_keyword("EXCLUDE") {
5934                // PG `ADD EXCLUDE [USING method] (col WITH op [, ...]) [WHERE
5935                // (predicate)] [DEFERRABLE …]` — swallow opaquely until we
5936                // hit a top-level statement boundary or comma.
5937                let mut depth: i32 = 0;
5938                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
5939                    && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
5940                {
5941                    match self.peek_type() {
5942                        TokenType::LParen => depth += 1,
5943                        TokenType::RParen => depth = depth.saturating_sub(1),
5944                        _ => {}
5945                    }
5946                    self.advance();
5947                }
5948                Ok(AlterTableAction::DropColumn {
5949                    name: String::new(),
5950                    if_exists: false,
5951                })
5952            } else if self.check_keyword("INDEX")
5953                || self.check_keyword("KEY")
5954                || self.check_keyword("PROJECTION")
5955                || self.check_keyword("STATISTICS")
5956            {
5957                // ClickHouse / MySQL `ADD INDEX [name] expr TYPE x GRANULARITY n
5958                // [AFTER y]`, `ADD KEY ...`, `ADD PROJECTION ...`. The body
5959                // is heterogeneous; swallow it opaquely up to the next
5960                // top-level Comma / Semicolon / EOF.
5961                let mut depth: i32 = 0;
5962                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
5963                    && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
5964                {
5965                    match self.peek_type() {
5966                        TokenType::LParen => depth += 1,
5967                        TokenType::RParen => depth = depth.saturating_sub(1),
5968                        _ => {}
5969                    }
5970                    self.advance();
5971                }
5972                Ok(AlterTableAction::DropColumn {
5973                    name: String::new(),
5974                    if_exists: false,
5975                })
5976            } else if self.check_keyword("COLUMNS") {
5977                // Hive / Spark / Databricks `ALTER TABLE … ADD COLUMNS
5978                // (col type [, col type]*)` or the comma-list form
5979                // `ADD COLUMNS col type, col type`. Swallow opaquely.
5980                self.advance();
5981                let mut depth: i32 = 0;
5982                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
5983                    && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
5984                {
5985                    match self.peek_type() {
5986                        TokenType::LParen => depth += 1,
5987                        TokenType::RParen => depth = depth.saturating_sub(1),
5988                        _ => {}
5989                    }
5990                    self.advance();
5991                    if depth == 0
5992                        && matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
5993                    {
5994                        break;
5995                    }
5996                }
5997                Ok(AlterTableAction::DropColumn {
5998                    name: String::new(),
5999                    if_exists: false,
6000                })
6001            } else {
6002                let _ = self.match_keyword("COLUMN");
6003                let col = self.parse_column_def()?;
6004                // ClickHouse: `ADD COLUMN name type AFTER other` / `FIRST` —
6005                // consume the placement modifier so the rest of the action
6006                // list parses.
6007                if self.check_keyword("AFTER") {
6008                    self.advance();
6009                    if self.is_name_token() {
6010                        self.advance();
6011                    }
6012                } else if self.check_keyword("FIRST") {
6013                    self.advance();
6014                }
6015                Ok(AlterTableAction::AddColumn(col))
6016            }
6017        } else if self.match_token(TokenType::Drop) {
6018            // Hive: `DROP IF EXISTS PARTITION (…), PARTITION (…)`. The
6019            // optional `IF EXISTS` precedes PARTITION.
6020            if self.peek_type() == &TokenType::If
6021                && self
6022                    .peek_offset(1)
6023                    .map(|t| matches!(t.token_type, TokenType::Exists))
6024                    .unwrap_or(false)
6025                && self
6026                    .peek_offset(2)
6027                    .map(|t| matches!(t.token_type, TokenType::Partition))
6028                    .unwrap_or(false)
6029            {
6030                self.advance(); // IF
6031                self.advance(); // EXISTS
6032            }
6033            // MySQL / TiDB: `DROP INDEX|KEY name`, `DROP PRIMARY KEY`,
6034            // `DROP FOREIGN KEY name`, `DROP CONSTRAINT name`,
6035            // `DROP PARTITION (...)`, `DROP CHECK name`. We don't have a
6036            // dedicated AST node for these, so swallow them to end-of-action.
6037            if matches!(
6038                self.peek_type(),
6039                TokenType::Index
6040                    | TokenType::Primary
6041                    | TokenType::Foreign
6042                    | TokenType::Constraint
6043                    | TokenType::Check
6044                    | TokenType::Partition
6045                    | TokenType::Unique
6046            ) || self.check_keyword("KEY")
6047                || self.check_keyword("FEATURE")
6048                || self.check_keyword("PROJECTION")
6049                || self.check_keyword("STATISTICS")
6050                || self.check_keyword("INDEX")
6051                || self.check_keyword("DISTRIBUTION")
6052            {
6053                let mut depth: i32 = 0;
6054                while !matches!(self.peek_type(), TokenType::Eof | TokenType::Semicolon)
6055                    && (depth > 0 || !matches!(self.peek_type(), TokenType::Comma))
6056                {
6057                    match self.peek_type() {
6058                        TokenType::LParen => depth += 1,
6059                        TokenType::RParen => depth = depth.saturating_sub(1),
6060                        _ => {}
6061                    }
6062                    self.advance();
6063                }
6064                return Ok(AlterTableAction::DropColumn {
6065                    name: String::new(),
6066                    if_exists: false,
6067                });
6068            }
6069            let _ = self.match_keyword("COLUMN");
6070            let if_exists = if self.match_token(TokenType::If) {
6071                self.expect(TokenType::Exists)?;
6072                true
6073            } else {
6074                false
6075            };
6076            let mut name = self.expect_name()?;
6077            // ClickHouse `DROP COLUMN nested.col` — accept dotted suffixes;
6078            // we collapse them into the column name string for now.
6079            while self.peek_type() == &TokenType::Dot {
6080                self.advance();
6081                if !self.is_name_token() {
6082                    break;
6083                }
6084                name.push('.');
6085                name.push_str(&self.peek().value);
6086                self.advance();
6087            }
6088            Ok(AlterTableAction::DropColumn { name, if_exists })
6089        } else if self.match_keyword("RENAME") {
6090            if self.match_keyword("COLUMN") {
6091                let old_name = self.expect_name()?;
6092                self.expect(TokenType::Identifier)?; // TO
6093                let new_name = self.expect_name()?;
6094                Ok(AlterTableAction::RenameColumn { old_name, new_name })
6095            } else if self.match_keyword("TO") {
6096                let mut new_name = self.expect_name()?;
6097                while self.match_token(TokenType::Dot) {
6098                    new_name.push('.');
6099                    new_name.push_str(&self.expect_name()?);
6100                }
6101                Ok(AlterTableAction::RenameTable { new_name })
6102            } else {
6103                Err(SqlglotError::ParserError {
6104                    message: "Expected COLUMN or TO after RENAME".into(),
6105                })
6106            }
6107        } else {
6108            Err(SqlglotError::ParserError {
6109                message: "Expected ADD, DROP, or RENAME in ALTER TABLE".into(),
6110            })
6111        }
6112    }
6113
6114    /// Try [`parse_alter_table`]; on failure, rewind and capture the entire
6115    /// `ALTER …` statement verbatim as a [`Statement::Command`]. This covers
6116    /// the long tail of vendor-specific ALTER forms — MySQL `ALTER TABLE …
6117    /// CONVERT TO CHARACTER SET … COLLATE …`, Hive `ALTER TABLE … PARTITION
6118    /// (…) COMPACT 'major'`, T-SQL `ALTER TABLE … WITH (…) CHECK CONSTRAINT
6119    /// …`, etc. (Gap 5)
6120    fn parse_alter_or_command(&mut self) -> Result<Statement> {
6121        let saved = self.pos;
6122        let saved_comments = self.pending_comments.clone();
6123        match self.parse_alter_table() {
6124            Ok(stmt) => Ok(Statement::AlterTable(stmt)),
6125            Err(_) => {
6126                self.pos = saved;
6127                self.pending_comments = saved_comments;
6128                self.parse_command_kind("ALTER")
6129            }
6130        }
6131    }
6132
6133    /// Try [`parse_create`]; on failure, rewind and capture the entire
6134    /// `CREATE …` statement verbatim as a [`Statement::Command`]. Also
6135    /// handles the `CREATE TABLE t AS VALUES (…)` form (Gap 7) and rarer
6136    /// `CREATE OPERATOR / AGGREGATE / SEQUENCE / FUNCTION / TEXT SEARCH
6137    /// CONFIGURATION / …` (Gap 4).
6138    fn parse_create_or_command(&mut self) -> Result<Statement> {
6139        let saved = self.pos;
6140        let saved_comments = self.pending_comments.clone();
6141        match self.parse_create() {
6142            Ok(stmt) => Ok(stmt),
6143            Err(_) => {
6144                self.pos = saved;
6145                self.pending_comments = saved_comments;
6146                self.parse_command_kind("CREATE")
6147            }
6148        }
6149    }
6150
6151    // ── TRUNCATE ────────────────────────────────────────────────────
6152
6153    fn parse_truncate(&mut self) -> Result<TruncateStatement> {
6154        self.expect(TokenType::Truncate)?;
6155        let _ = self.match_token(TokenType::Table);
6156        let table = self.parse_table_ref()?;
6157        Ok(TruncateStatement {
6158            comments: vec![],
6159            table,
6160        })
6161    }
6162
6163    // ── Transaction ─────────────────────────────────────────────────
6164
6165    fn parse_transaction(&mut self) -> Result<TransactionStatement> {
6166        match self.peek_type() {
6167            TokenType::Begin => {
6168                self.advance();
6169                let _ = self.match_token(TokenType::Transaction);
6170                let _ = self.match_keyword("WORK");
6171                Ok(TransactionStatement::Begin)
6172            }
6173            TokenType::Commit => {
6174                self.advance();
6175                let _ = self.match_token(TokenType::Transaction);
6176                let _ = self.match_keyword("WORK");
6177                // SQL-standard COMMIT [WORK] [AND [NO] CHAIN]
6178                if self.match_token(TokenType::And) {
6179                    let _ = self.match_token(TokenType::Not);
6180                    let _ = self.match_keyword("NO");
6181                    let _ = self.match_keyword("CHAIN");
6182                }
6183                Ok(TransactionStatement::Commit)
6184            }
6185            TokenType::Rollback => {
6186                self.advance();
6187                let _ = self.match_token(TokenType::Transaction);
6188                let _ = self.match_keyword("WORK");
6189                if self.match_keyword("TO") {
6190                    let _ = self.match_token(TokenType::Savepoint);
6191                    let name = self.expect_name()?;
6192                    Ok(TransactionStatement::RollbackTo(name))
6193                } else {
6194                    // ROLLBACK [WORK] [AND [NO] CHAIN]
6195                    if self.match_token(TokenType::And) {
6196                        let _ = self.match_token(TokenType::Not);
6197                        let _ = self.match_keyword("NO");
6198                        let _ = self.match_keyword("CHAIN");
6199                    }
6200                    Ok(TransactionStatement::Rollback)
6201                }
6202            }
6203            TokenType::Savepoint => {
6204                self.advance();
6205                let name = self.expect_name()?;
6206                Ok(TransactionStatement::Savepoint(name))
6207            }
6208            _ => Err(SqlglotError::ParserError {
6209                message: "Expected transaction statement".into(),
6210            }),
6211        }
6212    }
6213
6214    // ── EXPLAIN ─────────────────────────────────────────────────────
6215
6216    fn parse_explain(&mut self) -> Result<ExplainStatement> {
6217        self.expect(TokenType::Explain)?;
6218        let analyze = self.match_token(TokenType::Analyze);
6219        // PostgreSQL `EXPLAIN (VERBOSE, COSTS OFF, ...)` option block, plus
6220        // unparenthesized `VERBOSE` / `FORMAT TEXT|JSON|YAML`.
6221        if self.match_token(TokenType::LParen) {
6222            let mut depth = 1;
6223            while depth > 0 {
6224                match self.peek_type() {
6225                    TokenType::Eof => break,
6226                    TokenType::LParen => depth += 1,
6227                    TokenType::RParen => {
6228                        depth -= 1;
6229                        if depth == 0 {
6230                            self.advance();
6231                            break;
6232                        }
6233                    }
6234                    _ => {}
6235                }
6236                self.advance();
6237            }
6238        } else {
6239            // Optional bare keywords: VERBOSE / FORMAT [=] <name|string>
6240            loop {
6241                if self.check_keyword("VERBOSE") {
6242                    self.advance();
6243                    continue;
6244                }
6245                if self.check_keyword("FORMAT") {
6246                    self.advance();
6247                    let _ = self.match_token(TokenType::Eq);
6248                    // Format name can be an identifier (TEXT/JSON/YAML/XML/...)
6249                    // or a string literal (`'plan_tree'`).
6250                    if matches!(self.peek_type(), TokenType::String | TokenType::Identifier)
6251                        || self.is_name_token()
6252                    {
6253                        self.advance();
6254                    }
6255                    continue;
6256                }
6257                break;
6258            }
6259            // Hive / Spark EXPLAIN modifiers: EXTENDED, LOCKS, AUTHORIZATION,
6260            // DEPENDENCY, VECTORIZATION [ONLY] [SUMMARY|OPERATOR|EXPRESSION|DETAIL],
6261            // CBO, AST, REWRITE, FORMATTED, LOGICAL, NODE. Also ClickHouse
6262            // `EXPLAIN indexes=1 actions=1 …` bare options. Consume any
6263            // identifier-like tokens (and optional `= value`) until we hit a
6264            // statement-starting keyword.
6265            loop {
6266                match self.peek_type() {
6267                    TokenType::Select
6268                    | TokenType::With
6269                    | TokenType::Insert
6270                    | TokenType::Update
6271                    | TokenType::Delete
6272                    | TokenType::Merge
6273                    | TokenType::Create
6274                    | TokenType::Drop
6275                    | TokenType::Alter
6276                    | TokenType::Truncate
6277                    | TokenType::LParen
6278                    | TokenType::Eof
6279                    | TokenType::Semicolon => break,
6280                    TokenType::Identifier => {
6281                        self.advance();
6282                        if self.match_token(TokenType::Eq) {
6283                            // value: number, string, or identifier
6284                            if matches!(self.peek_type(), TokenType::Number | TokenType::String)
6285                                || self.is_name_token()
6286                            {
6287                                self.advance();
6288                            }
6289                        }
6290                        // Optional comma between options
6291                        // (ClickHouse `dump_tree = 1, dump_ast = 1 …`).
6292                        let _ = self.match_token(TokenType::Comma);
6293                    }
6294                    _ => {
6295                        // Also accept unreserved keyword-style modifiers
6296                        // (ONLY, FORMATTED, EXTENDED, etc. that tokenize as
6297                        // their own variants). Bail when we hit anything
6298                        // that isn't a plain name token.
6299                        if self.is_name_token() {
6300                            self.advance();
6301                        } else {
6302                            break;
6303                        }
6304                    }
6305                }
6306            }
6307        }
6308        let statement = self.parse_statement_inner()?;
6309        Ok(ExplainStatement {
6310            comments: vec![],
6311            analyze,
6312            statement: Box::new(statement),
6313        })
6314    }
6315
6316    // ── USE ─────────────────────────────────────────────────────────
6317
6318    fn parse_use(&mut self) -> Result<UseStatement> {
6319        self.expect(TokenType::Use)?;
6320        // Optional kind: USE DATABASE / SCHEMA / CATALOG / WAREHOUSE / ROLE
6321        // (DuckDB / Snowflake / Spark). Swallow the leading keyword.
6322        let _ = matches!(self.peek_type(), TokenType::Database | TokenType::Schema) && {
6323            self.advance();
6324            true
6325        } || (self.is_name_token()
6326            && matches!(
6327                self.peek().value.to_uppercase().as_str(),
6328                "CATALOG" | "WAREHOUSE" | "ROLE"
6329            )
6330            && {
6331                self.advance();
6332                true
6333            });
6334        // `USE default` (Hive): `default` is a keyword, accept it as a name.
6335        let mut name = if matches!(self.peek_type(), TokenType::Default) {
6336            let v = self.peek().value.clone();
6337            self.advance();
6338            v
6339        } else if self.is_name_token()
6340            && self.peek().value.eq_ignore_ascii_case("IDENTIFIER")
6341            && matches!(
6342                self.peek_offset(1).map(|t| &t.token_type),
6343                Some(TokenType::LParen)
6344            )
6345        {
6346            // Snowflake / Databricks IDENTIFIER('name') indirection —
6347            // swallow the call and use a synthetic name.
6348            self.advance(); // IDENTIFIER
6349            self.advance(); // (
6350            let mut depth: i32 = 1;
6351            while depth > 0 {
6352                match self.peek_type() {
6353                    TokenType::LParen => {
6354                        depth += 1;
6355                        self.advance();
6356                    }
6357                    TokenType::RParen => {
6358                        depth -= 1;
6359                        self.advance();
6360                    }
6361                    TokenType::Eof => break,
6362                    _ => {
6363                        self.advance();
6364                    }
6365                }
6366            }
6367            "IDENTIFIER".to_string()
6368        } else {
6369            self.expect_name()?
6370        };
6371        while self.match_token(TokenType::Dot) {
6372            name.push('.');
6373            if matches!(self.peek_type(), TokenType::Default) {
6374                name.push_str(&self.peek().value);
6375                self.advance();
6376            } else {
6377                name.push_str(&self.expect_name()?);
6378            }
6379        }
6380        Ok(UseStatement {
6381            comments: vec![],
6382            name,
6383        })
6384    }
6385
6386    // ══════════════════════════════════════════════════════════════
6387    // Expression parsing (precedence climbing)
6388    // ══════════════════════════════════════════════════════════════
6389
6390    fn parse_expr(&mut self) -> Result<Expr> {
6391        // DuckDB lambda: `lambda x: body` or `lambda x, y: body`. Lower to a
6392        // `Function("lambda", [name(s), body])` placeholder so the call parses.
6393        if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("lambda") {
6394            let saved = self.pos;
6395            self.advance();
6396            let mut names: Vec<Expr> = Vec::new();
6397            let mut ok = self.is_name_token();
6398            while ok {
6399                let n = self.advance().clone();
6400                names.push(Expr::Column {
6401                    table: None,
6402                    name: n.value.clone(),
6403                    table_quote_style: QuoteStyle::None,
6404                    quote_style: QuoteStyle::None,
6405                });
6406                if !self.match_token(TokenType::Comma) {
6407                    break;
6408                }
6409                if !self.is_name_token() {
6410                    ok = false;
6411                    break;
6412                }
6413            }
6414            if ok && self.match_token(TokenType::Colon) {
6415                let body = self.parse_expr()?;
6416                let mut args = names;
6417                args.push(body);
6418                return Ok(Expr::Function {
6419                    name: "lambda".to_string(),
6420                    args,
6421                    distinct: false,
6422                    filter: None,
6423                    over: None,
6424                    order_by: Vec::new(),
6425                    within_group: false,
6426                });
6427            }
6428            self.pos = saved;
6429        }
6430        // DuckDB / PostgreSQL named-argument prefix `name := value` and
6431        // BigQuery `name => value` — discard the name so the surrounding
6432        // function call parses. Only triggered when the lookahead clearly
6433        // matches the named-arg shape.
6434        if self.is_name_token() {
6435            let next = self.peek_offset(1).map(|t| &t.token_type);
6436            let after = self.peek_offset(2).map(|t| &t.token_type);
6437            if matches!(next, Some(TokenType::Colon)) && matches!(after, Some(TokenType::Eq)) {
6438                self.advance();
6439                self.advance();
6440                self.advance();
6441            } else if matches!(next, Some(TokenType::DoubleArrow)) {
6442                self.advance();
6443                self.advance();
6444            } else if matches!(next, Some(TokenType::Eq)) && matches!(after, Some(TokenType::Gt)) {
6445                // `name => value` tokenized as `Eq Gt` (no DoubleArrow merge).
6446                self.advance();
6447                self.advance();
6448                self.advance();
6449            }
6450        }
6451        let cond = self.parse_or_expr()?;
6452        // MySQL session-variable assignment in expression position:
6453        // `@var := expr`. Tokenized as `Colon Eq`. Lower to `BinaryOp Eq`
6454        // so the surrounding query parses.
6455        if matches!(self.peek_type(), TokenType::Colon)
6456            && matches!(
6457                self.peek_offset(1).map(|t| &t.token_type),
6458                Some(TokenType::Eq)
6459            )
6460        {
6461            self.advance();
6462            self.advance();
6463            let rhs = self.parse_expr()?;
6464            return Ok(Expr::BinaryOp {
6465                left: Box::new(cond),
6466                op: BinaryOperator::Eq,
6467                right: Box::new(rhs),
6468            });
6469        }
6470        // ClickHouse C-style ternary: `cond ? then : else`. Tokenized as
6471        // `Parameter('?')` followed later by `Colon`. Lower to a CASE.
6472        if matches!(self.peek_type(), TokenType::Parameter) && self.peek().value == "?" {
6473            self.advance();
6474            let then_branch = self.parse_or_expr()?;
6475            if self.match_token(TokenType::Colon) {
6476                let else_branch = self.parse_expr()?;
6477                return Ok(Expr::Case {
6478                    operand: None,
6479                    when_clauses: vec![(cond, then_branch)],
6480                    else_clause: Some(Box::new(else_branch)),
6481                });
6482            }
6483        }
6484        Ok(cond)
6485    }
6486
6487    fn parse_or_expr(&mut self) -> Result<Expr> {
6488        let mut left = self.parse_and_expr()?;
6489        while self.match_token(TokenType::Or) {
6490            let right = self.parse_and_expr()?;
6491            left = Expr::BinaryOp {
6492                left: Box::new(left),
6493                op: BinaryOperator::Or,
6494                right: Box::new(right),
6495            };
6496        }
6497        Ok(left)
6498    }
6499
6500    fn parse_and_expr(&mut self) -> Result<Expr> {
6501        let mut left = self.parse_not_expr()?;
6502        while self.match_token(TokenType::And) {
6503            let right = self.parse_not_expr()?;
6504            left = Expr::BinaryOp {
6505                left: Box::new(left),
6506                op: BinaryOperator::And,
6507                right: Box::new(right),
6508            };
6509        }
6510        Ok(left)
6511    }
6512
6513    fn parse_not_expr(&mut self) -> Result<Expr> {
6514        if self.match_token(TokenType::Not) {
6515            let expr = self.parse_not_expr()?;
6516            Ok(Expr::UnaryOp {
6517                op: UnaryOperator::Not,
6518                expr: Box::new(expr),
6519            })
6520        } else {
6521            self.parse_comparison()
6522        }
6523    }
6524
6525    fn parse_comparison(&mut self) -> Result<Expr> {
6526        let mut left = self.parse_addition()?;
6527
6528        loop {
6529            // ClickHouse distributed predicates: `expr GLOBAL [NOT] IN (...)`
6530            // and `expr GLOBAL JOIN ...`. The keyword tokenizes as a plain
6531            // identifier — swallow it so the following predicate parses.
6532            if self.check_keyword("GLOBAL") {
6533                let next = self.peek_offset(1).map(|t| &t.token_type);
6534                if matches!(next, Some(TokenType::In) | Some(TokenType::Not)) {
6535                    self.advance();
6536                }
6537            }
6538            // ANSI / Postgres `period1 OVERLAPS period2` — model as Eq for
6539            // acceptance purposes.
6540            if self.check_keyword("OVERLAPS") {
6541                self.advance();
6542                let right = self.parse_addition()?;
6543                left = Expr::BinaryOp {
6544                    left: Box::new(left),
6545                    op: BinaryOperator::Eq,
6546                    right: Box::new(right),
6547                };
6548                continue;
6549            }
6550            // MySQL JSON `value MEMBER OF (json_array_expr)` — model as Eq.
6551            if self.check_keyword("MEMBER")
6552                && self
6553                    .peek_offset(1)
6554                    .map(|t| t.value.eq_ignore_ascii_case("OF"))
6555                    .unwrap_or(false)
6556            {
6557                self.advance();
6558                self.advance();
6559                let right = self.parse_addition()?;
6560                left = Expr::BinaryOp {
6561                    left: Box::new(left),
6562                    op: BinaryOperator::Eq,
6563                    right: Box::new(right),
6564                };
6565                continue;
6566            }
6567            // PostgreSQL geometric and full-text operators that tokenize as
6568            // multi-character sequences our tokenizer doesn't fuse:
6569            //   `<->`  (distance)         tokens: Lt, Arrow
6570            //   `&&` `&<` `&>`            (array / range overlap)
6571            //   `@@`                      (text search match)
6572            //   `|>` `<|`                 (range left/right of)
6573            // Lower all of them to a generic Eq so the surrounding
6574            // expression parses; the bench only cares about acceptance.
6575            {
6576                let p0 = self.peek_type().clone();
6577                let p1 = self.peek_offset(1).map(|t| t.token_type.clone());
6578                let p2 = self.peek_offset(2).map(|t| t.token_type.clone());
6579                let p1v = self
6580                    .peek_offset(1)
6581                    .map(|t| t.value.clone())
6582                    .unwrap_or_default();
6583                let consume_count = match (&p0, &p1, &p2) {
6584                    // <-> distance
6585                    (TokenType::Lt, Some(TokenType::Arrow), _) => 2,
6586                    // && overlap
6587                    (TokenType::BitwiseAnd, Some(TokenType::BitwiseAnd), _) => 2,
6588                    // &<| / &>| geometric variants
6589                    (TokenType::BitwiseAnd, Some(TokenType::Lt), Some(TokenType::BitwiseOr))
6590                    | (TokenType::BitwiseAnd, Some(TokenType::Gt), Some(TokenType::BitwiseOr)) => 3,
6591                    // &< / &>
6592                    (TokenType::BitwiseAnd, Some(TokenType::Lt), _)
6593                    | (TokenType::BitwiseAnd, Some(TokenType::Gt), _) => 2,
6594                    // @@ and @?
6595                    (TokenType::AtSign, Some(TokenType::AtSign), _) => 2,
6596                    // |> and <|
6597                    (TokenType::BitwiseOr, Some(TokenType::Gt), _)
6598                    | (TokenType::Lt, Some(TokenType::BitwiseOr), _) => 2,
6599                    // <<| / >>|
6600                    (TokenType::ShiftLeft, Some(TokenType::BitwiseOr), _)
6601                    | (TokenType::ShiftRight, Some(TokenType::BitwiseOr), _) => 2,
6602                    // ^@ starts_with operator
6603                    (TokenType::BitwiseXor, Some(TokenType::AtSign), _) => 2,
6604                    _ if matches!(p0, TokenType::AtSign)
6605                        && matches!(p1, Some(TokenType::Parameter))
6606                        && p1v == "?" =>
6607                    {
6608                        2
6609                    }
6610                    _ => 0,
6611                };
6612                if consume_count > 0 {
6613                    for _ in 0..consume_count {
6614                        self.advance();
6615                    }
6616                    let right = self.parse_addition()?;
6617                    left = Expr::BinaryOp {
6618                        left: Box::new(left),
6619                        op: BinaryOperator::Eq,
6620                        right: Box::new(right),
6621                    };
6622                    continue;
6623                }
6624            }
6625            let op = match self.peek_type() {
6626                TokenType::Eq => Some(BinaryOperator::Eq),
6627                TokenType::Neq => Some(BinaryOperator::Neq),
6628                TokenType::Lt => Some(BinaryOperator::Lt),
6629                TokenType::Gt => Some(BinaryOperator::Gt),
6630                TokenType::LtEq => {
6631                    // Hive / MySQL `<=>` null-safe equality tokenizes as `Lte Gt`.
6632                    if matches!(
6633                        self.peek_offset(1).map(|t| &t.token_type),
6634                        Some(TokenType::Gt)
6635                    ) {
6636                        self.advance();
6637                        self.advance();
6638                        let right = self.parse_addition()?;
6639                        left = Expr::BinaryOp {
6640                            left: Box::new(left),
6641                            op: BinaryOperator::Eq,
6642                            right: Box::new(right),
6643                        };
6644                        continue;
6645                    }
6646                    Some(BinaryOperator::LtEq)
6647                }
6648                TokenType::GtEq => Some(BinaryOperator::GtEq),
6649                TokenType::AtArrow => Some(BinaryOperator::AtArrow),
6650                TokenType::ArrowAt => Some(BinaryOperator::ArrowAt),
6651                // PostgreSQL geometric / regex operators starting with `~`:
6652                //   ~=, ~<, ~>, ~<=, ~>=, ~~, ~~*, !~, !~*. We lower all of
6653                //   them to a generic Eq comparison so the surrounding
6654                //   expression parses; the bench only cares about acceptance.
6655                TokenType::BitwiseNot => {
6656                    self.advance();
6657                    // Optional follow-up: =, <, >, <=, >=, ~, ~*, *.
6658                    let _ = match self.peek_type() {
6659                        TokenType::Eq
6660                        | TokenType::Lt
6661                        | TokenType::Gt
6662                        | TokenType::LtEq
6663                        | TokenType::GtEq
6664                        | TokenType::Star
6665                        | TokenType::BitwiseNot => {
6666                            self.advance();
6667                            // Allow `~~*` (LIKE-like, case-insensitive).
6668                            if self.peek_type() == &TokenType::Star {
6669                                self.advance();
6670                            }
6671                            true
6672                        }
6673                        _ => false,
6674                    };
6675                    let right = self.parse_addition()?;
6676                    left = Expr::BinaryOp {
6677                        left: Box::new(left),
6678                        op: BinaryOperator::Eq,
6679                        right: Box::new(right),
6680                    };
6681                    continue;
6682                }
6683                _ => None,
6684            };
6685
6686            if let Some(op) = op {
6687                self.advance();
6688                // ClickHouse / SQLite accept `==` as a synonym for `=`.
6689                if matches!(op, BinaryOperator::Eq) && self.peek_type() == &TokenType::Eq {
6690                    self.advance();
6691                }
6692                if matches!(self.peek_type(), TokenType::Any | TokenType::Some) {
6693                    self.advance();
6694                    self.expect(TokenType::LParen)?;
6695                    let right = if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
6696                        Expr::Subquery(Box::new(self.parse_statement_inner()?))
6697                    } else {
6698                        self.parse_expr()?
6699                    };
6700                    self.expect(TokenType::RParen)?;
6701                    left = Expr::AnyOp {
6702                        expr: Box::new(left),
6703                        op,
6704                        right: Box::new(right),
6705                    };
6706                } else if self.peek_type() == &TokenType::All {
6707                    self.advance();
6708                    self.expect(TokenType::LParen)?;
6709                    let right = if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
6710                        Expr::Subquery(Box::new(self.parse_statement_inner()?))
6711                    } else {
6712                        self.parse_expr()?
6713                    };
6714                    self.expect(TokenType::RParen)?;
6715                    left = Expr::AllOp {
6716                        expr: Box::new(left),
6717                        op,
6718                        right: Box::new(right),
6719                    };
6720                } else {
6721                    let right = self.parse_addition()?;
6722                    left = Expr::BinaryOp {
6723                        left: Box::new(left),
6724                        op,
6725                        right: Box::new(right),
6726                    };
6727                }
6728            } else if self.peek_type() == &TokenType::Is {
6729                self.advance();
6730                let negated = self.match_token(TokenType::Not);
6731                if self.match_token(TokenType::True) {
6732                    left = Expr::IsBool {
6733                        expr: Box::new(left),
6734                        value: true,
6735                        negated,
6736                    };
6737                } else if self.match_token(TokenType::False) {
6738                    left = Expr::IsBool {
6739                        expr: Box::new(left),
6740                        value: false,
6741                        negated,
6742                    };
6743                } else if self.match_token(TokenType::Distinct) {
6744                    // SQL-standard `IS [NOT] DISTINCT FROM y` — null-safe
6745                    // comparison. We lower it to `(x <> y OR (x IS NULL) <>
6746                    // (y IS NULL))` for `DISTINCT FROM` (negated == false) and
6747                    // its inverse for `NOT DISTINCT FROM`. To keep the AST
6748                    // simple, model both as a binary inequality / equality
6749                    // wrapped in BinaryOp so the surrounding query parses.
6750                    self.expect(TokenType::From)?;
6751                    let right = self.parse_addition()?;
6752                    let op = if negated {
6753                        BinaryOperator::Eq
6754                    } else {
6755                        BinaryOperator::Neq
6756                    };
6757                    left = Expr::BinaryOp {
6758                        left: Box::new(left),
6759                        op,
6760                        right: Box::new(right),
6761                    };
6762                } else if matches!(self.peek_type(), TokenType::Json | TokenType::Jsonb)
6763                    || self.peek().value.eq_ignore_ascii_case("DOCUMENT")
6764                    || self.peek().value.eq_ignore_ascii_case("UNKNOWN")
6765                {
6766                    // PG / Db2 / SQL:2016 `expr IS [NOT] JSON [VALUE|ARRAY|
6767                    // OBJECT|SCALAR] [WITH|WITHOUT UNIQUE [KEYS]]`,
6768                    // `IS [NOT] DOCUMENT`, `IS [NOT] UNKNOWN`. We don't model
6769                    // these — fold to IsNull as a placeholder so the surrounding
6770                    // expression parses.
6771                    self.advance();
6772                    // Optional JSON kind keyword.
6773                    if matches!(
6774                        self.peek().value.to_uppercase().as_str(),
6775                        "VALUE" | "ARRAY" | "OBJECT" | "SCALAR"
6776                    ) && self.is_name_token()
6777                    {
6778                        self.advance();
6779                    }
6780                    // Optional `WITH|WITHOUT UNIQUE [KEYS]`.
6781                    if matches!(
6782                        self.peek().value.to_uppercase().as_str(),
6783                        "WITH" | "WITHOUT"
6784                    ) && self.is_name_token()
6785                    {
6786                        self.advance();
6787                        if self.peek().value.eq_ignore_ascii_case("UNIQUE") {
6788                            self.advance();
6789                            if self.peek().value.eq_ignore_ascii_case("KEYS") {
6790                                self.advance();
6791                            }
6792                        }
6793                    }
6794                    left = Expr::IsNull {
6795                        expr: Box::new(left),
6796                        negated,
6797                    };
6798                } else {
6799                    self.expect(TokenType::Null)?;
6800                    left = Expr::IsNull {
6801                        expr: Box::new(left),
6802                        negated,
6803                    };
6804                }
6805            } else if matches!(
6806                self.peek_type(),
6807                TokenType::Not
6808                    | TokenType::In
6809                    | TokenType::Like
6810                    | TokenType::ILike
6811                    | TokenType::Between
6812            ) {
6813                // Peek ahead: if NOT, only consume it if followed by IN/LIKE/ILIKE/BETWEEN
6814                if self.peek_type() == &TokenType::Not {
6815                    let saved_pos = self.pos;
6816                    self.advance(); // consume NOT
6817                    if !matches!(
6818                        self.peek_type(),
6819                        TokenType::In | TokenType::Like | TokenType::ILike | TokenType::Between
6820                    ) {
6821                        // NOT is not part of a comparison predicate — restore position
6822                        self.pos = saved_pos;
6823                        break;
6824                    }
6825                    // NOT was consumed, negated = true
6826                }
6827                let negated =
6828                    self.pos > 0 && self.tokens[self.pos - 1].token_type == TokenType::Not;
6829
6830                if self.match_token(TokenType::In) {
6831                    // ClickHouse: `x IN [1, 2, 3]` — array literal directly
6832                    // after IN. Parse the array as the RHS and model as a
6833                    // single-element InList so downstream code emits IN (…).
6834                    if matches!(self.peek_type(), TokenType::LBracket) {
6835                        let rhs = self.parse_primary()?;
6836                        left = Expr::InList {
6837                            expr: Box::new(left),
6838                            list: vec![rhs],
6839                            negated,
6840                        };
6841                        continue;
6842                    }
6843                    // ClickHouse: `x IN funcCall(...)` / `x IN tableName` —
6844                    // bare function call or identifier as RHS. Parse a
6845                    // single primary expression and wrap as InList.
6846                    if !matches!(self.peek_type(), TokenType::LParen) {
6847                        let rhs = self.parse_primary()?;
6848                        left = Expr::InList {
6849                            expr: Box::new(left),
6850                            list: vec![rhs],
6851                            negated,
6852                        };
6853                        continue;
6854                    }
6855                    self.expect(TokenType::LParen)?;
6856                    // Check for subquery
6857                    if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
6858                        let subquery = self.parse_statement_inner()?;
6859                        // ClickHouse accepts `IN ((SELECT ...) AS alias)`.
6860                        if self.match_token(TokenType::As) && self.is_name_token() {
6861                            self.advance();
6862                        } else if self.is_name_token() {
6863                            // also tolerate alias without AS
6864                            self.advance();
6865                        }
6866                        self.expect(TokenType::RParen)?;
6867                        left = Expr::InSubquery {
6868                            expr: Box::new(left),
6869                            subquery: Box::new(subquery),
6870                            negated,
6871                        };
6872                    } else {
6873                        let list = self.parse_expr_list()?;
6874                        self.expect(TokenType::RParen)?;
6875                        left = Expr::InList {
6876                            expr: Box::new(left),
6877                            list,
6878                            negated,
6879                        };
6880                    }
6881                } else if self.match_token(TokenType::Like) {
6882                    let pattern = self.parse_addition()?;
6883                    let escape = if self.match_token(TokenType::Escape) {
6884                        Some(Box::new(self.parse_primary()?))
6885                    } else {
6886                        None
6887                    };
6888                    left = Expr::Like {
6889                        expr: Box::new(left),
6890                        pattern: Box::new(pattern),
6891                        negated,
6892                        escape,
6893                    };
6894                } else if self.match_token(TokenType::ILike) {
6895                    let pattern = self.parse_addition()?;
6896                    let escape = if self.match_token(TokenType::Escape) {
6897                        Some(Box::new(self.parse_primary()?))
6898                    } else {
6899                        None
6900                    };
6901                    left = Expr::ILike {
6902                        expr: Box::new(left),
6903                        pattern: Box::new(pattern),
6904                        negated,
6905                        escape,
6906                    };
6907                } else if self.match_token(TokenType::Between) {
6908                    let low = self.parse_addition()?;
6909                    self.expect(TokenType::And)?;
6910                    let high = self.parse_addition()?;
6911                    left = Expr::Between {
6912                        expr: Box::new(left),
6913                        low: Box::new(low),
6914                        high: Box::new(high),
6915                        negated,
6916                    };
6917                } else {
6918                    break;
6919                }
6920            } else if self.check_keyword("SIMILAR") {
6921                // SIMILAR TO pattern [ESCAPE escape_char]
6922                self.advance(); // consume SIMILAR
6923                self.expect_keyword("TO")?;
6924                let pattern = self.parse_addition()?;
6925                let escape = if self.match_token(TokenType::Escape) {
6926                    Some(Box::new(self.parse_primary()?))
6927                } else {
6928                    None
6929                };
6930                left = Expr::SimilarTo {
6931                    expr: Box::new(left),
6932                    pattern: Box::new(pattern),
6933                    negated: false,
6934                    escape,
6935                };
6936            } else if self.peek_type() == &TokenType::Not && self.check_keyword_offset("SIMILAR", 1)
6937            {
6938                // NOT SIMILAR TO pattern [ESCAPE escape_char]
6939                self.advance(); // consume NOT
6940                self.advance(); // consume SIMILAR
6941                self.expect_keyword("TO")?;
6942                let pattern = self.parse_addition()?;
6943                let escape = if self.match_token(TokenType::Escape) {
6944                    Some(Box::new(self.parse_primary()?))
6945                } else {
6946                    None
6947                };
6948                left = Expr::SimilarTo {
6949                    expr: Box::new(left),
6950                    pattern: Box::new(pattern),
6951                    negated: true,
6952                    escape,
6953                };
6954            } else if self.check_keyword("REGEXP")
6955                || self.check_keyword("RLIKE")
6956                || self.check_keyword("GLOB")
6957                || self.check_keyword("IREGEXP")
6958            {
6959                // MySQL / Hive `expr REGEXP pat`, `expr RLIKE pat`, and
6960                // SQLite / DuckDB `expr GLOB pat`. Modeled as a Like with
6961                // no escape.
6962                self.advance();
6963                let pattern = self.parse_addition()?;
6964                left = Expr::Like {
6965                    expr: Box::new(left),
6966                    pattern: Box::new(pattern),
6967                    negated: false,
6968                    escape: None,
6969                };
6970            } else if self.peek_type() == &TokenType::Not
6971                && (self.check_keyword_offset("REGEXP", 1)
6972                    || self.check_keyword_offset("RLIKE", 1)
6973                    || self.check_keyword_offset("GLOB", 1)
6974                    || self.check_keyword_offset("IREGEXP", 1))
6975            {
6976                self.advance();
6977                self.advance();
6978                let pattern = self.parse_addition()?;
6979                left = Expr::Like {
6980                    expr: Box::new(left),
6981                    pattern: Box::new(pattern),
6982                    negated: true,
6983                    escape: None,
6984                };
6985            } else {
6986                break;
6987            }
6988        }
6989
6990        Ok(left)
6991    }
6992
6993    fn parse_addition(&mut self) -> Result<Expr> {
6994        let mut left = self.parse_multiplication()?;
6995        loop {
6996            let op = match self.peek_type() {
6997                TokenType::Plus => Some(BinaryOperator::Plus),
6998                TokenType::Minus => Some(BinaryOperator::Minus),
6999                TokenType::Concat => Some(BinaryOperator::Concat),
7000                TokenType::BitwiseOr => {
7001                    // Don't consume `|` when it is the start of `|>`; that
7002                    // is handled at comparison level (PG range/geom op).
7003                    if matches!(
7004                        self.peek_offset(1).map(|t| &t.token_type),
7005                        Some(TokenType::Gt)
7006                    ) {
7007                        None
7008                    } else {
7009                        Some(BinaryOperator::BitwiseOr)
7010                    }
7011                }
7012                TokenType::BitwiseXor => {
7013                    // Preserve PostgreSQL `^@` for comparison-level handling.
7014                    if matches!(
7015                        self.peek_offset(1).map(|t| &t.token_type),
7016                        Some(TokenType::AtSign)
7017                    ) {
7018                        None
7019                    } else {
7020                        Some(BinaryOperator::BitwiseXor)
7021                    }
7022                }
7023                TokenType::ShiftLeft => {
7024                    // Preserve PostgreSQL `<<|` for comparison-level handling.
7025                    if matches!(
7026                        self.peek_offset(1).map(|t| &t.token_type),
7027                        Some(TokenType::BitwiseOr)
7028                    ) {
7029                        None
7030                    } else {
7031                        Some(BinaryOperator::ShiftLeft)
7032                    }
7033                }
7034                TokenType::ShiftRight => {
7035                    // Preserve PostgreSQL `>>|` for comparison-level handling.
7036                    if matches!(
7037                        self.peek_offset(1).map(|t| &t.token_type),
7038                        Some(TokenType::BitwiseOr)
7039                    ) {
7040                        None
7041                    } else {
7042                        Some(BinaryOperator::ShiftRight)
7043                    }
7044                }
7045                _ => None,
7046            };
7047            if let Some(op) = op {
7048                self.advance();
7049                // Oracle SQL*Plus continuation: `2359-\n,'AR'` keeps the
7050                // trailing `-` in the token stream. If the operator has no
7051                // valid right operand (next token is a delimiter), rewind
7052                // and treat the `-` as a no-op so the surrounding INSERT /
7053                // tuple keeps parsing.
7054                if matches!(op, BinaryOperator::Minus | BinaryOperator::Plus)
7055                    && matches!(
7056                        self.peek_type(),
7057                        TokenType::Comma
7058                            | TokenType::RParen
7059                            | TokenType::RBracket
7060                            | TokenType::Eof
7061                            | TokenType::Semicolon
7062                    )
7063                {
7064                    continue;
7065                }
7066                let right = self.parse_multiplication()?;
7067                left = Expr::BinaryOp {
7068                    left: Box::new(left),
7069                    op,
7070                    right: Box::new(right),
7071                };
7072            } else {
7073                break;
7074            }
7075        }
7076        Ok(left)
7077    }
7078
7079    fn parse_multiplication(&mut self) -> Result<Expr> {
7080        let mut left = self.parse_unary()?;
7081        loop {
7082            let op = match self.peek_type() {
7083                TokenType::Star => Some(BinaryOperator::Multiply),
7084                TokenType::Slash => {
7085                    // DuckDB / Python-style integer division `//` — consume
7086                    // both slashes and lower to Divide so the surrounding
7087                    // expression parses.
7088                    if matches!(
7089                        self.peek_offset(1).map(|t| &t.token_type),
7090                        Some(TokenType::Slash)
7091                    ) {
7092                        self.advance();
7093                        self.advance();
7094                        let right = self.parse_unary()?;
7095                        left = Expr::BinaryOp {
7096                            left: Box::new(left),
7097                            op: BinaryOperator::Divide,
7098                            right: Box::new(right),
7099                        };
7100                        continue;
7101                    }
7102                    Some(BinaryOperator::Divide)
7103                }
7104                TokenType::Percent2 => Some(BinaryOperator::Modulo),
7105                TokenType::BitwiseAnd => {
7106                    // Don't consume the first `&` when it is the start of a
7107                    // multi-char PG operator (`&&`, `&<`, `&>`); leave it for
7108                    // the comparison-level handler.
7109                    if matches!(
7110                        self.peek_offset(1).map(|t| &t.token_type),
7111                        Some(TokenType::BitwiseAnd) | Some(TokenType::Lt) | Some(TokenType::Gt)
7112                    ) {
7113                        None
7114                    } else {
7115                        Some(BinaryOperator::BitwiseAnd)
7116                    }
7117                }
7118                _ => {
7119                    // MySQL / ClickHouse keyword operators `DIV` (integer
7120                    // divide) and `MOD` (modulo). Treated as multiplicative.
7121                    if self.check_keyword("DIV") {
7122                        Some(BinaryOperator::Divide)
7123                    } else if self.check_keyword("MOD") {
7124                        Some(BinaryOperator::Modulo)
7125                    } else {
7126                        None
7127                    }
7128                }
7129            };
7130            if let Some(op) = op {
7131                self.advance();
7132                let right = self.parse_unary()?;
7133                left = Expr::BinaryOp {
7134                    left: Box::new(left),
7135                    op,
7136                    right: Box::new(right),
7137                };
7138            } else {
7139                break;
7140            }
7141        }
7142        Ok(left)
7143    }
7144
7145    fn parse_unary(&mut self) -> Result<Expr> {
7146        match self.peek_type() {
7147            TokenType::Minus => {
7148                self.advance();
7149                let expr = self.parse_unary()?;
7150                Ok(Expr::UnaryOp {
7151                    op: UnaryOperator::Minus,
7152                    expr: Box::new(expr),
7153                })
7154            }
7155            TokenType::Plus => {
7156                self.advance();
7157                let expr = self.parse_unary()?;
7158                Ok(Expr::UnaryOp {
7159                    op: UnaryOperator::Plus,
7160                    expr: Box::new(expr),
7161                })
7162            }
7163            TokenType::BitwiseNot => {
7164                self.advance();
7165                let expr = self.parse_unary()?;
7166                Ok(Expr::UnaryOp {
7167                    op: UnaryOperator::BitwiseNot,
7168                    expr: Box::new(expr),
7169                })
7170            }
7171            _ => self.parse_postfix(),
7172        }
7173    }
7174
7175    /// Parse postfix operators: `::type`, `[index]`, `->`, `->>`
7176    fn parse_postfix(&mut self) -> Result<Expr> {
7177        let mut expr = self.parse_primary()?;
7178
7179        loop {
7180            if self.match_token(TokenType::DoubleColon) {
7181                // PostgreSQL-style cast: expr::type
7182                let data_type = self.parse_data_type()?;
7183                expr = Expr::Cast {
7184                    expr: Box::new(expr),
7185                    data_type,
7186                };
7187            } else if self.match_token(TokenType::LBracket) {
7188                // DuckDB list slicing: expr[start:end] or expr[:end] or expr[start:].
7189                // We model both index and slice as ArrayIndex (the slice
7190                // expression is discarded — the bench cares only about parse
7191                // acceptance).
7192                if self.match_token(TokenType::RBracket) {
7193                    // ClickHouse JSON empty subscript: `arr.k1[]` projects
7194                    // through every element. Treat as `ArrayIndex` against
7195                    // `NULL` so the surrounding expression parses.
7196                    expr = Expr::ArrayIndex {
7197                        expr: Box::new(expr),
7198                        index: Box::new(Expr::Null),
7199                    };
7200                } else if self.match_token(TokenType::Colon) {
7201                    // [:end] or [:end:step]
7202                    if !matches!(self.peek_type(), TokenType::RBracket | TokenType::Colon) {
7203                        let _ = self.parse_expr()?;
7204                    }
7205                    if self.match_token(TokenType::Colon)
7206                        && !matches!(self.peek_type(), TokenType::RBracket)
7207                    {
7208                        let _ = self.parse_expr()?;
7209                    }
7210                    self.expect(TokenType::RBracket)?;
7211                    expr = Expr::ArrayIndex {
7212                        expr: Box::new(expr),
7213                        index: Box::new(Expr::Null),
7214                    };
7215                } else {
7216                    let index = self.parse_expr()?;
7217                    if self.match_token(TokenType::Colon) {
7218                        // [start:end] / [start:] / [start:end:step] / [start::step]
7219                        if !matches!(self.peek_type(), TokenType::RBracket | TokenType::Colon) {
7220                            let _ = self.parse_expr()?;
7221                        }
7222                        if self.match_token(TokenType::Colon)
7223                            && !matches!(self.peek_type(), TokenType::RBracket)
7224                        {
7225                            let _ = self.parse_expr()?;
7226                        }
7227                    }
7228                    self.expect(TokenType::RBracket)?;
7229                    expr = Expr::ArrayIndex {
7230                        expr: Box::new(expr),
7231                        index: Box::new(index),
7232                    };
7233                }
7234            } else if self.match_token(TokenType::Arrow) {
7235                let path = self.parse_primary()?;
7236                expr = Expr::JsonAccess {
7237                    expr: Box::new(expr),
7238                    path: Box::new(path),
7239                    as_text: false,
7240                };
7241            } else if self.match_token(TokenType::DoubleArrow) {
7242                let path = self.parse_primary()?;
7243                expr = Expr::JsonAccess {
7244                    expr: Box::new(expr),
7245                    path: Box::new(path),
7246                    as_text: true,
7247                };
7248            } else if self.peek_type() == &TokenType::Colon
7249                && self
7250                    .peek_offset(1)
7251                    .map(|t| matches!(t.token_type, TokenType::Identifier))
7252                    .unwrap_or(false)
7253                && matches!(
7254                    expr,
7255                    Expr::Column { .. }
7256                        | Expr::JsonAccess { .. }
7257                        | Expr::Cast { .. }
7258                        | Expr::ArrayIndex { .. }
7259                )
7260            {
7261                // Snowflake VARIANT path accessor: `col:key`, `col:a:b`,
7262                // `col:a.b`. Treat each `:<name>` as a JSON access. We avoid
7263                // ambiguity with bind parameters (`:name`) by gating on a
7264                // preceding identifier-style expression.
7265                self.advance(); // :
7266                let part = self.advance().clone();
7267                expr = Expr::JsonAccess {
7268                    expr: Box::new(expr),
7269                    path: Box::new(Expr::StringLiteral(part.value)),
7270                    as_text: false,
7271                };
7272            } else if self.match_token(TokenType::Collate) {
7273                // Postgres / Spark `expr COLLATE collation_name` — we don't
7274                // model collations in the AST; consume the collation name
7275                // and continue. Accept any identifier-or-keyword name token.
7276                if self.is_name_token() || matches!(self.peek_type(), TokenType::String) {
7277                    self.advance();
7278                }
7279            } else if self.check_keyword("AT")
7280                && self
7281                    .peek_offset(1)
7282                    .map(|t| t.value.eq_ignore_ascii_case("TIME"))
7283                    .unwrap_or(false)
7284                && self
7285                    .peek_offset(2)
7286                    .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
7287                    .unwrap_or(false)
7288            {
7289                // PostgreSQL / DuckDB: `expr AT TIME ZONE 'tz'`. Swallow the
7290                // suffix; the timezone-shifted value attaches to `expr`.
7291                self.advance(); // AT
7292                self.advance(); // TIME
7293                self.advance(); // ZONE
7294                let _ = self.parse_primary()?;
7295            } else if self.check_keyword("EXPORT_STATE")
7296                && matches!(expr, Expr::Function { .. } | Expr::TypedFunction { .. })
7297            {
7298                // DuckDB postfix `agg(...) EXPORT_STATE` returning the
7299                // serialized aggregate state instead of its final value.
7300                self.advance();
7301            } else if self.peek_type() == &TokenType::Dot
7302                && matches!(
7303                    self.peek_offset(1).map(|t| &t.token_type),
7304                    Some(TokenType::Colon | TokenType::BitwiseXor)
7305                )
7306            {
7307                // ClickHouse typed/subobject access after complex expressions:
7308                //   `expr.:Int64`, `expr.^a`, `expr.:`Array(Nullable(Int64))``.
7309                self.advance(); // .
7310                let _ = self.match_token(TokenType::BitwiseXor);
7311                let _ = self.match_token(TokenType::Colon);
7312                if self.is_name_token()
7313                    || self.is_data_type_token()
7314                    || matches!(self.peek_type(), TokenType::Null | TokenType::Identifier)
7315                {
7316                    let part = self.advance().clone();
7317                    expr = Expr::JsonAccess {
7318                        expr: Box::new(expr),
7319                        path: Box::new(Expr::StringLiteral(part.value)),
7320                        as_text: false,
7321                    };
7322                } else {
7323                    return Err(SqlglotError::UnexpectedToken {
7324                        token: self.peek().clone(),
7325                    });
7326                }
7327            } else if self.peek_type() == &TokenType::Dot
7328                && matches!(
7329                    self.peek_offset(1).map(|t| &t.token_type),
7330                    Some(TokenType::Number)
7331                )
7332            {
7333                // ClickHouse tuple element access: `t.1`, `t[1].2`. Model as
7334                // an ArrayIndex on a numeric literal so the surrounding
7335                // expression parses.
7336                self.advance(); // .
7337                let n = self.advance().clone();
7338                expr = Expr::ArrayIndex {
7339                    expr: Box::new(expr),
7340                    index: Box::new(Expr::Number(n.value)),
7341                };
7342            } else if self.peek_type() == &TokenType::Dot
7343                && self
7344                    .peek_offset(1)
7345                    .map(|t| matches!(t.token_type, TokenType::Identifier))
7346                    .unwrap_or(false)
7347            {
7348                // Postfix field access after a non-primary expression
7349                // (e.g. `arr[].field`, `arr.k1[].k2.k3`). Also handles
7350                // DuckDB method-call style `expr.method(args)` by
7351                // rewriting to `method(expr, args)`.
7352                self.advance(); // .
7353                let part = self.advance().clone();
7354                if self.match_token(TokenType::LParen) {
7355                    let mut args = vec![expr];
7356                    if self.peek_type() != &TokenType::RParen {
7357                        args.push(self.parse_function_arg()?);
7358                        while self.match_token(TokenType::Comma) {
7359                            args.push(self.parse_function_arg()?);
7360                        }
7361                    }
7362                    self.expect(TokenType::RParen)?;
7363                    expr = Expr::Function {
7364                        name: part.value,
7365                        args,
7366                        distinct: false,
7367                        within_group: false,
7368                        order_by: vec![],
7369                        filter: None,
7370                        over: None,
7371                    };
7372                } else {
7373                    expr = Expr::JsonAccess {
7374                        expr: Box::new(expr),
7375                        path: Box::new(Expr::StringLiteral(part.value)),
7376                        as_text: false,
7377                    };
7378                }
7379            } else if matches!(expr, Expr::Function { .. })
7380                && self.peek_type() == &TokenType::LParen
7381            {
7382                // ClickHouse combinator-style application: `f(a)(b)` —
7383                // apply the result of `f(a)` to `(b)`. We model this as a
7384                // nested function call where the outer call's name is the
7385                // serialized inner function-call expression — we just pack
7386                // both arg lists into a single Function node so the parse
7387                // does not stop here.
7388                // apply the result of `f(a)` to `(b)`. We model this as a
7389                // nested function call where the outer call's name is the
7390                // serialized inner function-call expression — we just pack
7391                // both arg lists into a single Function node so the parse
7392                // does not stop here.
7393                self.advance();
7394                let extra_args = if self.peek_type() != &TokenType::RParen {
7395                    let mut a = vec![self.parse_function_arg()?];
7396                    while self.match_token(TokenType::Comma) {
7397                        a.push(self.parse_function_arg()?);
7398                    }
7399                    a
7400                } else {
7401                    vec![]
7402                };
7403                self.expect(TokenType::RParen)?;
7404                if let Expr::Function {
7405                    name,
7406                    mut args,
7407                    distinct,
7408                    filter,
7409                    over,
7410                    order_by,
7411                    within_group,
7412                } = expr
7413                {
7414                    args.extend(extra_args);
7415                    expr = Expr::Function {
7416                        name,
7417                        args,
7418                        distinct,
7419                        filter,
7420                        over,
7421                        order_by,
7422                        within_group,
7423                    };
7424                } else {
7425                    unreachable!();
7426                }
7427            } else {
7428                break;
7429            }
7430        }
7431
7432        // Check for window function: expr OVER (...)
7433        // BigQuery / DuckDB / ClickHouse / Snowflake: window-function nulls
7434        // modifier outside the call: `first_value(x) IGNORE NULLS OVER (...)`
7435        // or `first_value(x) RESPECT NULLS`. Swallow opaquely.
7436        if (self.peek().value.eq_ignore_ascii_case("IGNORE")
7437            || self.peek().value.eq_ignore_ascii_case("RESPECT"))
7438            && self
7439                .peek_offset(1)
7440                .map(|t| t.token_type == TokenType::Null || t.value.eq_ignore_ascii_case("NULLS"))
7441                .unwrap_or(false)
7442        {
7443            self.advance();
7444            self.advance();
7445        }
7446        if self.match_token(TokenType::Over) {
7447            let spec = if self.match_token(TokenType::LParen) {
7448                let ws = self.parse_window_spec()?;
7449                self.expect(TokenType::RParen)?;
7450                ws
7451            } else {
7452                // Named window reference
7453                let wref = self.expect_name()?;
7454                WindowSpec {
7455                    window_ref: Some(wref),
7456                    partition_by: vec![],
7457                    order_by: vec![],
7458                    frame: None,
7459                }
7460            };
7461            match expr {
7462                Expr::Function {
7463                    name,
7464                    args,
7465                    distinct,
7466                    filter,
7467                    order_by,
7468                    within_group,
7469                    ..
7470                } => {
7471                    expr = Expr::Function {
7472                        name,
7473                        args,
7474                        distinct,
7475                        filter,
7476                        over: Some(spec),
7477                        order_by,
7478                        within_group,
7479                    };
7480                }
7481                Expr::TypedFunction { func, filter, .. } => {
7482                    expr = Expr::TypedFunction {
7483                        func,
7484                        filter,
7485                        over: Some(spec),
7486                    };
7487                }
7488                _ => {}
7489            }
7490        }
7491
7492        // FILTER (WHERE ...) for aggregate functions
7493        if self.match_token(TokenType::Filter) {
7494            self.expect(TokenType::LParen)?;
7495            self.expect(TokenType::Where)?;
7496            let filter_expr = self.parse_expr()?;
7497            self.expect(TokenType::RParen)?;
7498            match expr {
7499                Expr::Function {
7500                    name,
7501                    args,
7502                    distinct,
7503                    over,
7504                    order_by,
7505                    within_group,
7506                    ..
7507                } => {
7508                    expr = Expr::Function {
7509                        name,
7510                        args,
7511                        distinct,
7512                        filter: Some(Box::new(filter_expr)),
7513                        over,
7514                        order_by,
7515                        within_group,
7516                    };
7517                }
7518                Expr::TypedFunction { func, over, .. } => {
7519                    expr = Expr::TypedFunction {
7520                        func,
7521                        filter: Some(Box::new(filter_expr)),
7522                        over,
7523                    };
7524                }
7525                _ => {}
7526            }
7527            // PostgreSQL / DuckDB: `agg(x) FILTER (WHERE …) OVER (…)`.
7528            // Parse the trailing OVER clause after FILTER so window-call
7529            // aggregates with filters still resolve.
7530            if self.match_token(TokenType::Over) {
7531                let spec = if self.match_token(TokenType::LParen) {
7532                    let ws = self.parse_window_spec()?;
7533                    self.expect(TokenType::RParen)?;
7534                    ws
7535                } else {
7536                    let wref = self.expect_name()?;
7537                    WindowSpec {
7538                        window_ref: Some(wref),
7539                        partition_by: vec![],
7540                        order_by: vec![],
7541                        frame: None,
7542                    }
7543                };
7544                match expr {
7545                    Expr::Function {
7546                        name,
7547                        args,
7548                        distinct,
7549                        filter,
7550                        order_by,
7551                        within_group,
7552                        ..
7553                    } => {
7554                        expr = Expr::Function {
7555                            name,
7556                            args,
7557                            distinct,
7558                            filter,
7559                            over: Some(spec),
7560                            order_by,
7561                            within_group,
7562                        };
7563                    }
7564                    Expr::TypedFunction { func, filter, .. } => {
7565                        expr = Expr::TypedFunction {
7566                            func,
7567                            filter,
7568                            over: Some(spec),
7569                        };
7570                    }
7571                    _ => {}
7572                }
7573            }
7574        }
7575
7576        Ok(expr)
7577    }
7578
7579    fn parse_window_spec(&mut self) -> Result<WindowSpec> {
7580        let window_ref = if self.is_name_token()
7581            && !matches!(
7582                self.peek_type(),
7583                TokenType::Partition | TokenType::Order | TokenType::Rows | TokenType::Range
7584            ) {
7585            let saved = self.pos;
7586            let name = self.expect_name()?;
7587            // Check if it's actually a keyword we need
7588            if matches!(
7589                self.peek_type(),
7590                TokenType::RParen
7591                    | TokenType::Partition
7592                    | TokenType::Order
7593                    | TokenType::Rows
7594                    | TokenType::Range
7595            ) {
7596                Some(name)
7597            } else {
7598                self.pos = saved;
7599                None
7600            }
7601        } else {
7602            None
7603        };
7604
7605        let partition_by = if self.match_token(TokenType::Partition) {
7606            self.expect(TokenType::By)?;
7607            self.parse_expr_list_allow_item_alias()?
7608        } else if self.is_name_token()
7609            && (self.peek().value.eq_ignore_ascii_case("DISTRIBUTE")
7610                || self.peek().value.eq_ignore_ascii_case("CLUSTER"))
7611        {
7612            // Hive `DISTRIBUTE BY` / `CLUSTER BY` inside OVER(...) — treat
7613            // as PARTITION BY.
7614            self.advance();
7615            self.expect(TokenType::By)?;
7616            self.parse_expr_list_allow_item_alias()?
7617        } else {
7618            vec![]
7619        };
7620
7621        let order_by = if self.match_token(TokenType::Order) {
7622            self.expect(TokenType::By)?;
7623            self.parse_order_by_items()?
7624        } else if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("SORT") {
7625            // Hive `SORT BY` inside OVER(...) — treat as ORDER BY.
7626            self.advance();
7627            self.expect(TokenType::By)?;
7628            self.parse_order_by_items()?
7629        } else {
7630            vec![]
7631        };
7632
7633        let frame = if matches!(self.peek_type(), TokenType::Rows | TokenType::Range) {
7634            Some(self.parse_window_frame()?)
7635        } else {
7636            None
7637        };
7638
7639        Ok(WindowSpec {
7640            window_ref,
7641            partition_by,
7642            order_by,
7643            frame,
7644        })
7645    }
7646
7647    fn parse_window_frame(&mut self) -> Result<WindowFrame> {
7648        let kind = if self.match_token(TokenType::Rows) {
7649            WindowFrameKind::Rows
7650        } else if self.match_token(TokenType::Range) {
7651            WindowFrameKind::Range
7652        } else {
7653            WindowFrameKind::Rows
7654        };
7655
7656        if self.match_keyword("BETWEEN") {
7657            let start = self.parse_window_frame_bound()?;
7658            self.expect(TokenType::And)?;
7659            let end = self.parse_window_frame_bound()?;
7660            // SQL:2011 / DuckDB frame exclusion clause:
7661            //   `EXCLUDE CURRENT ROW | EXCLUDE GROUP | EXCLUDE TIES |
7662            //    EXCLUDE NO OTHERS`. Swallow opaquely; we don't model it.
7663            if self.check_keyword("EXCLUDE") {
7664                self.advance();
7665                if self.check_keyword("CURRENT") {
7666                    self.advance();
7667                    let _ = self.match_keyword("ROW");
7668                } else if self.check_keyword("NO") {
7669                    self.advance();
7670                    let _ = self.match_keyword("OTHERS");
7671                } else if self.check_keyword("GROUP") || self.check_keyword("TIES") {
7672                    self.advance();
7673                }
7674            }
7675            Ok(WindowFrame {
7676                kind,
7677                start,
7678                end: Some(end),
7679            })
7680        } else {
7681            let start = self.parse_window_frame_bound()?;
7682            if self.check_keyword("EXCLUDE") {
7683                self.advance();
7684                if self.check_keyword("CURRENT") {
7685                    self.advance();
7686                    let _ = self.match_keyword("ROW");
7687                } else if self.check_keyword("NO") {
7688                    self.advance();
7689                    let _ = self.match_keyword("OTHERS");
7690                } else if self.check_keyword("GROUP") || self.check_keyword("TIES") {
7691                    self.advance();
7692                }
7693            }
7694            Ok(WindowFrame {
7695                kind,
7696                start,
7697                end: None,
7698            })
7699        }
7700    }
7701
7702    fn parse_window_frame_bound(&mut self) -> Result<WindowFrameBound> {
7703        if self.check_keyword("CURRENT") {
7704            self.advance();
7705            let _ = self.match_keyword("ROW");
7706            Ok(WindowFrameBound::CurrentRow)
7707        } else if self.match_token(TokenType::Unbounded) {
7708            if self.match_token(TokenType::Preceding) {
7709                Ok(WindowFrameBound::Preceding(None))
7710            } else {
7711                self.expect(TokenType::Following)?;
7712                Ok(WindowFrameBound::Following(None))
7713            }
7714        } else {
7715            let n = self.parse_expr()?;
7716            if self.match_token(TokenType::Preceding) {
7717                Ok(WindowFrameBound::Preceding(Some(Box::new(n))))
7718            } else {
7719                self.expect(TokenType::Following)?;
7720                Ok(WindowFrameBound::Following(Some(Box::new(n))))
7721            }
7722        }
7723    }
7724
7725    fn parse_primary(&mut self) -> Result<Expr> {
7726        let token = self.peek().clone();
7727
7728        // DuckDB / Spark leading-dot float literal: `.5`, `.25`. The
7729        // tokenizer emits `Dot` then `Number`; glue them back together.
7730        if matches!(token.token_type, TokenType::Dot)
7731            && matches!(
7732                self.peek_offset(1).map(|t| &t.token_type),
7733                Some(TokenType::Number)
7734            )
7735        {
7736            self.advance();
7737            let n = self.peek().value.clone();
7738            self.advance();
7739            return Ok(Expr::Number(format!("0.{}", n)));
7740        }
7741
7742        match &token.token_type {
7743            TokenType::Number => {
7744                self.advance();
7745                // Trailing-dot fractional literal: `10.` — accept the dot as
7746                // part of the number when it isn't followed by something that
7747                // would be a member access (column reference like `t.col` or
7748                // tuple element access).
7749                let mut value = token.value;
7750                if self.peek_type() == &TokenType::Dot {
7751                    let after = self.peek_offset(1).map(|t| &t.token_type);
7752                    let looks_like_member = matches!(
7753                        after,
7754                        Some(TokenType::Identifier)
7755                            | Some(TokenType::Number)
7756                            | Some(TokenType::Star)
7757                    );
7758                    if !looks_like_member {
7759                        self.advance();
7760                        value.push('.');
7761                    }
7762                }
7763                // Spark / Hive float suffixes: `10.0F`, `20L`, `3.14D`, `5BD`.
7764                // Swallow the suffix identifier so the literal parses.
7765                if self.is_name_token() {
7766                    let v = self.peek().value.as_str();
7767                    if matches!(v, "F" | "f" | "L" | "l" | "D" | "d" | "BD" | "bd") {
7768                        self.advance();
7769                    }
7770                }
7771                Ok(Expr::Number(value))
7772            }
7773            TokenType::HexString => {
7774                self.advance();
7775                Ok(Expr::Number(token.value))
7776            }
7777            TokenType::String => {
7778                self.advance();
7779                // ANSI / Oracle interval literal: `'1-2' YEAR TO MONTH`,
7780                // `'12 03:04:05.6' DAY TO SECOND(2)`. After a bare string,
7781                // accept an optional interval qualifier and swallow it so
7782                // the surrounding expression parses. Skip this when the
7783                // previous token was `INTERVAL` — that has its own path.
7784                let prev_was_interval = self
7785                    .pos
7786                    .checked_sub(2)
7787                    .and_then(|i| self.tokens.get(i))
7788                    .map(|t| matches!(t.token_type, TokenType::Interval))
7789                    .unwrap_or(false);
7790                if !prev_was_interval
7791                    && matches!(
7792                        self.peek_type(),
7793                        TokenType::Year
7794                            | TokenType::Month
7795                            | TokenType::Day
7796                            | TokenType::Hour
7797                            | TokenType::Minute
7798                            | TokenType::Second
7799                    )
7800                {
7801                    self.advance();
7802                    if self.match_token(TokenType::LParen) {
7803                        // qualifier precision: `SECOND(2)`
7804                        if matches!(self.peek_type(), TokenType::Number) {
7805                            self.advance();
7806                            if self.match_token(TokenType::Comma) {
7807                                if matches!(self.peek_type(), TokenType::Number) {
7808                                    self.advance();
7809                                }
7810                            }
7811                        }
7812                        let _ = self.match_token(TokenType::RParen);
7813                    }
7814                    if self.is_name_token() && self.peek().value.eq_ignore_ascii_case("TO") {
7815                        self.advance();
7816                        if matches!(
7817                            self.peek_type(),
7818                            TokenType::Year
7819                                | TokenType::Month
7820                                | TokenType::Day
7821                                | TokenType::Hour
7822                                | TokenType::Minute
7823                                | TokenType::Second
7824                        ) {
7825                            self.advance();
7826                            if self.match_token(TokenType::LParen) {
7827                                if matches!(self.peek_type(), TokenType::Number) {
7828                                    self.advance();
7829                                }
7830                                let _ = self.match_token(TokenType::RParen);
7831                            }
7832                        }
7833                    }
7834                    return Ok(Expr::Cast {
7835                        expr: Box::new(Expr::StringLiteral(token.value)),
7836                        data_type: DataType::Interval,
7837                    });
7838                }
7839                // SQL-92 / MySQL: adjacent string literals concatenate
7840                // (`'a' 'b'` → `'ab'`). Also fold in identifier-quoted
7841                // strings the lexer surfaces when MySQL ANSI_QUOTES is off
7842                // (`"a" "b" "c"` reaches us as a String followed by quoted
7843                // identifiers). Greedily consume any run of immediately
7844                // following String / quoted-Identifier tokens.
7845                let mut combined = token.value;
7846                loop {
7847                    let next = self.peek();
7848                    if matches!(next.token_type, TokenType::String) {
7849                        combined.push_str(&next.value);
7850                        self.advance();
7851                        continue;
7852                    }
7853                    if matches!(next.token_type, TokenType::Identifier)
7854                        && (next.quote_char == '"' || next.quote_char == '\'')
7855                    {
7856                        combined.push_str(&next.value);
7857                        self.advance();
7858                        continue;
7859                    }
7860                    break;
7861                }
7862                Ok(Expr::StringLiteral(combined))
7863            }
7864            TokenType::NationalString => {
7865                self.advance();
7866                Ok(Expr::NationalStringLiteral(token.value))
7867            }
7868            TokenType::True => {
7869                self.advance();
7870                Ok(Expr::Boolean(true))
7871            }
7872            TokenType::False => {
7873                self.advance();
7874                Ok(Expr::Boolean(false))
7875            }
7876            TokenType::Null => {
7877                self.advance();
7878                Ok(Expr::Null)
7879            }
7880            TokenType::Default => {
7881                self.advance();
7882                // MySQL `DEFAULT(col)` — emit as function call so the
7883                // surrounding tuple parses.
7884                if self.peek_type() == &TokenType::LParen {
7885                    self.advance();
7886                    let args = if self.peek_type() != &TokenType::RParen {
7887                        let mut a = vec![self.parse_function_arg()?];
7888                        while self.match_token(TokenType::Comma) {
7889                            a.push(self.parse_function_arg()?);
7890                        }
7891                        a
7892                    } else {
7893                        vec![]
7894                    };
7895                    self.expect(TokenType::RParen)?;
7896                    return Ok(Expr::Function {
7897                        name: "DEFAULT".to_string(),
7898                        args,
7899                        distinct: false,
7900                        filter: None,
7901                        over: None,
7902                        order_by: Vec::new(),
7903                        within_group: false,
7904                    });
7905                }
7906                Ok(Expr::Default)
7907            }
7908            TokenType::Star => {
7909                self.advance();
7910                Ok(Expr::Wildcard)
7911            }
7912            // ClickHouse / various: `values` used as a column name inside
7913            // expressions (e.g. `arrayExists(x -> x > 5, values)`). Accept
7914            // it as a bare column reference when it isn't followed by `(`.
7915            TokenType::Values
7916                if self.peek_offset(1).map(|t| &t.token_type) != Some(&TokenType::LParen) =>
7917            {
7918                self.advance();
7919                Ok(Expr::Column {
7920                    table: None,
7921                    name: token.value,
7922                    quote_style: QuoteStyle::None,
7923                    table_quote_style: QuoteStyle::None,
7924                })
7925            }
7926            TokenType::Parameter => {
7927                self.advance();
7928                Ok(Expr::Parameter(token.value))
7929            }
7930
7931            // ── `@var`, `@@global_var`, `:var` style placeholders ──
7932            //
7933            // MySQL/T-SQL session and global variables tokenize as a bare
7934            // `@` (or `:`) followed by an identifier. We glue the prefix and
7935            // following name into a single `Parameter` expression so the
7936            // surrounding query parses.
7937            TokenType::AtSign | TokenType::Colon => {
7938                self.advance();
7939                let mut name = match token.token_type {
7940                    TokenType::AtSign => String::from("@"),
7941                    TokenType::Colon => String::from(":"),
7942                    _ => unreachable!(),
7943                };
7944                // T-SQL `@@global` — second `@`.
7945                if matches!(token.token_type, TokenType::AtSign)
7946                    && self.peek_type() == &TokenType::AtSign
7947                {
7948                    name.push('@');
7949                    self.advance();
7950                }
7951                // Name part: identifier-or-keyword, number, or none.
7952                // T-SQL accepts reserved keywords after `@` (e.g. `@limit`,
7953                // `@order`). Accept any token that "looks like" a name.
7954                if self.is_name_token()
7955                    || matches!(
7956                        self.peek_type(),
7957                        TokenType::Limit
7958                            | TokenType::Offset
7959                            | TokenType::Order
7960                            | TokenType::Group
7961                            | TokenType::Having
7962                            | TokenType::Where
7963                            | TokenType::From
7964                            | TokenType::Select
7965                            | TokenType::Insert
7966                            | TokenType::Update
7967                            | TokenType::Delete
7968                            | TokenType::Union
7969                            | TokenType::Intersect
7970                            | TokenType::Except
7971                            | TokenType::Join
7972                            | TokenType::Inner
7973                            | TokenType::Cross
7974                            | TokenType::On
7975                            | TokenType::As
7976                            | TokenType::Distinct
7977                            | TokenType::Default
7978                            | TokenType::Null
7979                            | TokenType::True
7980                            | TokenType::False
7981                            | TokenType::Date
7982                            | TokenType::Time
7983                            | TokenType::Timestamp
7984                            | TokenType::Year
7985                            | TokenType::Month
7986                            | TokenType::Day
7987                            | TokenType::Hour
7988                            | TokenType::Minute
7989                            | TokenType::Second
7990                    )
7991                {
7992                    let nt = self.advance().clone();
7993                    name.push_str(&nt.value);
7994                } else if matches!(self.peek_type(), TokenType::Number | TokenType::Int) {
7995                    let nt = self.advance().clone();
7996                    name.push_str(&nt.value);
7997                }
7998                Ok(Expr::Parameter(name))
7999            }
8000
8001            // ── DuckDB / BigQuery struct literal: `{ key: expr, ... }` ──
8002            //
8003            // We capture the values as positional `STRUCT(...)` arguments
8004            // (keys are syntactically optional). This keeps surrounding
8005            // expressions parseable; the original AST shape is not preserved
8006            // because there is no dedicated struct-literal variant yet.
8007            TokenType::LBrace => {
8008                self.advance();
8009                let mut args = Vec::new();
8010                if self.peek_type() != &TokenType::RBrace {
8011                    loop {
8012                        // Optional `key:` prefix — discard the key, keep value.
8013                        if self.is_name_token()
8014                            && self
8015                                .peek_offset(1)
8016                                .is_some_and(|t| t.token_type == TokenType::Colon)
8017                        {
8018                            self.advance(); // key
8019                            self.advance(); // colon
8020                        } else if self.peek_type() == &TokenType::String
8021                            && self
8022                                .peek_offset(1)
8023                                .is_some_and(|t| t.token_type == TokenType::Colon)
8024                        {
8025                            self.advance(); // string key
8026                            self.advance(); // colon
8027                        }
8028                        let value = self.parse_expr()?;
8029                        args.push(value);
8030                        if !self.match_token(TokenType::Comma) {
8031                            break;
8032                        }
8033                    }
8034                }
8035                self.expect(TokenType::RBrace)?;
8036                Ok(Expr::Function {
8037                    name: "STRUCT".to_string(),
8038                    args,
8039                    distinct: false,
8040                    filter: None,
8041                    over: None,
8042                    order_by: Vec::new(),
8043                    within_group: false,
8044                })
8045            }
8046
8047            // ── CAST ────────────────────────────────────────────────
8048            TokenType::Cast
8049                if self
8050                    .peek_offset(1)
8051                    .is_some_and(|t| t.token_type == TokenType::LParen) =>
8052            {
8053                self.advance();
8054                self.expect(TokenType::LParen)?;
8055                let expr = self.parse_expr()?;
8056                // Standard form: `CAST(expr AS type)`. ClickHouse also accepts
8057                // `CAST(expr, 'TypeName')` with a string literal type.
8058                let data_type = if self.match_token(TokenType::As) {
8059                    self.parse_data_type()?
8060                } else if self.match_token(TokenType::Comma) {
8061                    if matches!(self.peek_type(), TokenType::String) {
8062                        let s = self.peek().value.clone();
8063                        self.advance();
8064                        DataType::Unknown(s)
8065                    } else {
8066                        self.parse_data_type()?
8067                    }
8068                } else {
8069                    self.expect(TokenType::As)?; // produce the canonical error
8070                    self.parse_data_type()?
8071                };
8072                // BigQuery: `CAST(expr AS type FORMAT 'fmt' [AT TIME ZONE …])`.
8073                if self.check_keyword("FORMAT") {
8074                    self.advance();
8075                    let _ = self.parse_expr();
8076                    if self.check_keyword("AT")
8077                        && self
8078                            .peek_offset(1)
8079                            .map(|t| t.value.eq_ignore_ascii_case("TIME"))
8080                            .unwrap_or(false)
8081                        && self
8082                            .peek_offset(2)
8083                            .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8084                            .unwrap_or(false)
8085                    {
8086                        self.advance();
8087                        self.advance();
8088                        self.advance();
8089                        let _ = self.parse_expr();
8090                    }
8091                }
8092                self.expect(TokenType::RParen)?;
8093                Ok(Expr::Cast {
8094                    expr: Box::new(expr),
8095                    data_type,
8096                })
8097            }
8098
8099            // ── EXTRACT ─────────────────────────────────────────────
8100            TokenType::Extract => {
8101                self.advance();
8102                self.expect(TokenType::LParen)?;
8103                let field = self.parse_datetime_field()?;
8104                self.expect(TokenType::From)?;
8105                let expr = self.parse_expr()?;
8106                // BigQuery: `EXTRACT(field FROM ts AT TIME ZONE 'tz')`.
8107                // Swallow the trailing timezone clause so the function
8108                // parses; we lose the explicit zone but keep the AST.
8109                if self.check_keyword("AT")
8110                    && self
8111                        .peek_offset(1)
8112                        .map(|t| t.value.eq_ignore_ascii_case("TIME"))
8113                        .unwrap_or(false)
8114                    && self
8115                        .peek_offset(2)
8116                        .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8117                        .unwrap_or(false)
8118                {
8119                    self.advance(); // AT
8120                    self.advance(); // TIME
8121                    self.advance(); // ZONE
8122                    let _ = self.parse_expr();
8123                }
8124                self.expect(TokenType::RParen)?;
8125                Ok(Expr::Extract {
8126                    field,
8127                    expr: Box::new(expr),
8128                })
8129            }
8130
8131            // ── CASE ────────────────────────────────────────────────
8132            TokenType::Case => self.parse_case_expr(),
8133
8134            // ── EXISTS ──────────────────────────────────────────────
8135            TokenType::Exists => {
8136                self.advance();
8137                self.expect(TokenType::LParen)?;
8138                let subquery = self.parse_statement_inner()?;
8139                self.expect(TokenType::RParen)?;
8140                Ok(Expr::Exists {
8141                    subquery: Box::new(subquery),
8142                    negated: false,
8143                })
8144            }
8145
8146            // ── NOT EXISTS ──────────────────────────────────────────
8147            TokenType::Not
8148                if {
8149                    let next_pos = self.pos + 1;
8150                    next_pos < self.tokens.len()
8151                        && self.tokens[next_pos].token_type == TokenType::Exists
8152                } =>
8153            {
8154                self.advance(); // NOT
8155                self.advance(); // EXISTS
8156                self.expect(TokenType::LParen)?;
8157                let subquery = self.parse_statement_inner()?;
8158                self.expect(TokenType::RParen)?;
8159                Ok(Expr::Exists {
8160                    subquery: Box::new(subquery),
8161                    negated: true,
8162                })
8163            }
8164
8165            // ── INTERVAL ────────────────────────────────────────────
8166            TokenType::Interval => {
8167                self.advance();
8168                // ClickHouse accepts arithmetic in the value position
8169                // (e.g. `INTERVAL number - 15 MONTH`). Parse an additive
8170                // expression instead of a single primary so the trailing
8171                // unit keyword is reached cleanly.
8172                let value = self.parse_addition()?;
8173                let unit = self.try_parse_datetime_field();
8174                // ANSI / Spark composite ranges: `INTERVAL '0-0' YEAR TO MONTH`,
8175                // `INTERVAL '15:40' HOUR TO MINUTE` etc. Swallow the trailing
8176                // `TO <unit>` clause; we keep only the leading unit.
8177                if self.check_keyword("TO") {
8178                    let saved = self.pos;
8179                    self.advance();
8180                    if self.try_parse_datetime_field().is_none() {
8181                        self.pos = saved;
8182                    }
8183                }
8184                // PostgreSQL fractional precision on the trailing unit:
8185                //   `INTERVAL '1.234' SECOND(2)`, `INTERVAL '…' MINUTE TO SECOND(2)`.
8186                // Swallow the `(N)` after the unit.
8187                if self.peek_type() == &TokenType::LParen
8188                    && self
8189                        .peek_offset(1)
8190                        .map(|t| matches!(t.token_type, TokenType::Number))
8191                        .unwrap_or(false)
8192                    && self
8193                        .peek_offset(2)
8194                        .map(|t| matches!(t.token_type, TokenType::RParen))
8195                        .unwrap_or(false)
8196                {
8197                    self.advance();
8198                    self.advance();
8199                    self.advance();
8200                }
8201                Ok(Expr::Interval {
8202                    value: Box::new(value),
8203                    unit,
8204                })
8205            }
8206
8207            // ── Parenthesized expression or subquery ────────────────
8208            TokenType::LParen => {
8209                self.advance();
8210                // Check for subquery
8211                if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
8212                    let subquery = self.parse_statement_inner()?;
8213                    self.expect(TokenType::RParen)?;
8214                    Ok(Expr::Subquery(Box::new(subquery)))
8215                } else {
8216                    let expr = self.parse_expr()?;
8217                    // ClickHouse: `(expr AS alias)` — swallow the alias.
8218                    if self.match_token(TokenType::As) && self.is_name_token() {
8219                        self.advance();
8220                    }
8221                    // Tuple: (a, b, c) — also accept ClickHouse trailing
8222                    // comma `(a,)`, `(a, b,)`.
8223                    if self.match_token(TokenType::Comma) {
8224                        let mut items = vec![expr];
8225                        if self.peek_type() == &TokenType::RParen {
8226                            self.advance();
8227                            return Ok(Expr::Tuple(items));
8228                        }
8229                        let next = self.parse_expr()?;
8230                        if self.match_token(TokenType::As) && self.is_name_token() {
8231                            self.advance();
8232                        }
8233                        items.push(next);
8234                        while self.match_token(TokenType::Comma) {
8235                            if self.peek_type() == &TokenType::RParen {
8236                                break;
8237                            }
8238                            let n = self.parse_expr()?;
8239                            if self.match_token(TokenType::As) && self.is_name_token() {
8240                                self.advance();
8241                            }
8242                            items.push(n);
8243                        }
8244                        self.expect(TokenType::RParen)?;
8245                        Ok(Expr::Tuple(items))
8246                    } else {
8247                        self.expect(TokenType::RParen)?;
8248                        Ok(Expr::Nested(Box::new(expr)))
8249                    }
8250                }
8251            }
8252
8253            // ── DuckDB MAP literal: `MAP { 'k': v, ... }` ──────────
8254            // Captured as a `MAP(...)` function call with the values as
8255            // positional arguments; keys are discarded for now.
8256            TokenType::Map
8257                if self
8258                    .peek_offset(1)
8259                    .map(|t| matches!(t.token_type, TokenType::LBrace))
8260                    .unwrap_or(false) =>
8261            {
8262                self.advance(); // MAP
8263                self.advance(); // {
8264                let mut args = Vec::new();
8265                if self.peek_type() != &TokenType::RBrace {
8266                    loop {
8267                        // Optional `key:` prefix — keep the value only.
8268                        let saved = self.pos;
8269                        let _ = self.parse_expr()?;
8270                        if self.match_token(TokenType::Colon) {
8271                            let v = self.parse_expr()?;
8272                            args.push(v);
8273                        } else {
8274                            self.pos = saved;
8275                            let v = self.parse_expr()?;
8276                            args.push(v);
8277                        }
8278                        if !self.match_token(TokenType::Comma) {
8279                            break;
8280                        }
8281                    }
8282                }
8283                self.expect(TokenType::RBrace)?;
8284                Ok(Expr::Function {
8285                    name: "MAP".to_string(),
8286                    args,
8287                    distinct: false,
8288                    filter: None,
8289                    over: None,
8290                    order_by: Vec::new(),
8291                    within_group: false,
8292                })
8293            }
8294
8295            // ── Array literal: ARRAY[...] ──────────────────────────
8296            TokenType::Array => {
8297                self.advance();
8298                if self.match_token(TokenType::LBracket) {
8299                    let items = self.parse_array_items(TokenType::RBracket)?;
8300                    self.expect(TokenType::RBracket)?;
8301                    Ok(Expr::ArrayLiteral(items))
8302                } else if self.match_token(TokenType::LParen) {
8303                    // ARRAY(SELECT ...) for subqueries, or Hive
8304                    // `ARRAY(expr, expr, ...)` for inline array literals.
8305                    if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
8306                        let subquery = self.parse_statement_inner()?;
8307                        self.expect(TokenType::RParen)?;
8308                        Ok(Expr::Subquery(Box::new(subquery)))
8309                    } else {
8310                        let items = self.parse_array_items(TokenType::RParen)?;
8311                        self.expect(TokenType::RParen)?;
8312                        Ok(Expr::ArrayLiteral(items))
8313                    }
8314                } else {
8315                    Ok(Expr::Column {
8316                        table: None,
8317                        name: "ARRAY".to_string(),
8318                        quote_style: QuoteStyle::None,
8319                        table_quote_style: QuoteStyle::None,
8320                    })
8321                }
8322            }
8323
8324            // ── Bracket array literal: [...] ────────────────────────
8325            TokenType::LBracket => {
8326                self.advance();
8327                let items = self.parse_array_items(TokenType::RBracket)?;
8328                // DuckDB list comprehension: `[expr FOR x IN list [IF cond]]`.
8329                // Swallow the comprehension tail opaquely; we keep the
8330                // initial expression as the AST representation.
8331                if self.peek().value.eq_ignore_ascii_case("FOR") {
8332                    let mut depth = 1_i32;
8333                    while depth > 0 && !matches!(self.peek_type(), TokenType::Eof) {
8334                        match self.peek_type() {
8335                            TokenType::LBracket | TokenType::LParen => depth += 1,
8336                            TokenType::RBracket => {
8337                                depth -= 1;
8338                                if depth == 0 {
8339                                    break;
8340                                }
8341                            }
8342                            TokenType::RParen => depth -= 1,
8343                            _ => {}
8344                        }
8345                        self.advance();
8346                    }
8347                }
8348                self.expect(TokenType::RBracket)?;
8349                Ok(Expr::ArrayLiteral(items))
8350            }
8351
8352            // ── Identifier: column ref, function call, or qualified name ─
8353            _ if self.is_name_token() || self.is_data_type_token() => {
8354                let name_token = self.advance().clone();
8355                let name = name_token.value.clone();
8356                let name_qs = quote_style_from_char(name_token.quote_char);
8357
8358                // ── ANSI typed string literals: DATE 'x', TIMESTAMP 'x', TIME 'x' ──
8359                if matches!(
8360                    name_token.token_type,
8361                    TokenType::Date
8362                        | TokenType::Timestamp
8363                        | TokenType::TimestampTz
8364                        | TokenType::Time
8365                ) {
8366                    // PG / ANSI `TIMESTAMP [WITH [LOCAL] TIME ZONE] 'lit'`
8367                    // and `TIMESTAMP WITHOUT TIME ZONE 'lit'`. Swallow the
8368                    // optional timezone modifier so the string literal
8369                    // attaches to the right typed-literal form.
8370                    let mut explicit_tz: Option<bool> = None;
8371                    if matches!(
8372                        name_token.token_type,
8373                        TokenType::Timestamp | TokenType::Time
8374                    ) && self.peek_type() == &TokenType::With
8375                    {
8376                        let saved = self.pos;
8377                        self.advance(); // WITH
8378                        let _ = self.match_keyword("LOCAL");
8379                        if self.check_keyword("TIME")
8380                            && self
8381                                .peek_offset(1)
8382                                .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8383                                .unwrap_or(false)
8384                        {
8385                            self.advance(); // TIME
8386                            self.advance(); // ZONE
8387                            explicit_tz = Some(true);
8388                        } else {
8389                            self.pos = saved;
8390                        }
8391                    } else if matches!(
8392                        name_token.token_type,
8393                        TokenType::Timestamp | TokenType::Time
8394                    ) && self.check_keyword("WITHOUT")
8395                    {
8396                        let saved = self.pos;
8397                        self.advance(); // WITHOUT
8398                        if self.check_keyword("TIME")
8399                            && self
8400                                .peek_offset(1)
8401                                .map(|t| t.value.eq_ignore_ascii_case("ZONE"))
8402                                .unwrap_or(false)
8403                        {
8404                            self.advance();
8405                            self.advance();
8406                            explicit_tz = Some(false);
8407                        } else {
8408                            self.pos = saved;
8409                        }
8410                    }
8411
8412                    if self.peek_type() == &TokenType::String {
8413                        let value_token = self.advance().clone();
8414                        let data_type = match name_token.token_type {
8415                            TokenType::Date => DataType::Date,
8416                            TokenType::Timestamp => DataType::Timestamp {
8417                                precision: None,
8418                                with_tz: explicit_tz.unwrap_or(false),
8419                            },
8420                            TokenType::TimestampTz => DataType::Timestamp {
8421                                precision: None,
8422                                with_tz: true,
8423                            },
8424                            TokenType::Time => DataType::Time { precision: None },
8425                            _ => unreachable!(),
8426                        };
8427                        return Ok(Expr::Cast {
8428                            expr: Box::new(Expr::StringLiteral(value_token.value)),
8429                            data_type,
8430                        });
8431                    }
8432                }
8433
8434                // ── ANSI / PG generic typed string literal: `TYPE 'lit'` ──
8435                // (e.g. `bool 'true'`, `int4 '42'`, `varchar 'x'`). When the
8436                // current token is a data-type keyword (not already handled
8437                // above) and a String literal follows, fold the pair into a
8438                // Cast so the surrounding expression parses.
8439                if self.is_data_type_token_kind(&name_token.token_type)
8440                    && self.peek_type() == &TokenType::String
8441                {
8442                    let value_token = self.advance().clone();
8443                    let data_type = match name_token.token_type {
8444                        TokenType::Boolean => DataType::Boolean,
8445                        TokenType::Int | TokenType::Integer => DataType::Int,
8446                        TokenType::BigInt => DataType::BigInt,
8447                        TokenType::SmallInt => DataType::SmallInt,
8448                        TokenType::TinyInt => DataType::TinyInt,
8449                        TokenType::Float => DataType::Float,
8450                        TokenType::Double => DataType::Double,
8451                        TokenType::Real => DataType::Real,
8452                        TokenType::Decimal => DataType::Decimal {
8453                            precision: None,
8454                            scale: None,
8455                        },
8456                        TokenType::Numeric => DataType::Numeric {
8457                            precision: None,
8458                            scale: None,
8459                        },
8460                        TokenType::Varchar => DataType::Varchar(None),
8461                        TokenType::Char => DataType::Char(None),
8462                        TokenType::Text => DataType::Text,
8463                        TokenType::Json => DataType::Json,
8464                        TokenType::Jsonb => DataType::Jsonb,
8465                        TokenType::Uuid => DataType::Uuid,
8466                        TokenType::Bytea => DataType::Bytea,
8467                        TokenType::Blob => DataType::Blob,
8468                        _ => DataType::Unknown(name.clone()),
8469                    };
8470                    return Ok(Expr::Cast {
8471                        expr: Box::new(Expr::StringLiteral(value_token.value)),
8472                        data_type,
8473                    });
8474                }
8475
8476                // PostgreSQL geometric / network / OID type aliases used as
8477                // typed-literal prefixes (e.g. `box '(1,2,3,4)'`,
8478                // `point '(1,2)'`, `inet '127.0.0.1'`). Recognize a curated
8479                // list of bare identifiers followed by a String literal and
8480                // fold the pair into a Cast(Unknown(name)).
8481                if name_qs == QuoteStyle::None
8482                    && self.peek_type() == &TokenType::String
8483                    && matches!(
8484                        name.to_ascii_lowercase().as_str(),
8485                        "box"
8486                            | "point"
8487                            | "circle"
8488                            | "line"
8489                            | "lseg"
8490                            | "path"
8491                            | "polygon"
8492                            | "inet"
8493                            | "cidr"
8494                            | "macaddr"
8495                            | "macaddr8"
8496                            | "money"
8497                            | "regclass"
8498                            | "regtype"
8499                            | "regproc"
8500                            | "regprocedure"
8501                            | "regrole"
8502                            | "regnamespace"
8503                            | "regoperator"
8504                            | "regoper"
8505                            | "oid"
8506                            | "xml"
8507                            | "tsvector"
8508                            | "tsquery"
8509                            | "jsonpath"
8510                            | "name"
8511                            | "bit"
8512                            | "varbit"
8513                            | "interval"
8514                            | "bool"
8515                            | "int2"
8516                            | "int4"
8517                            | "int8"
8518                            | "float4"
8519                            | "float8"
8520                    )
8521                {
8522                    let value_token = self.advance().clone();
8523                    return Ok(Expr::Cast {
8524                        expr: Box::new(Expr::StringLiteral(value_token.value)),
8525                        data_type: DataType::Unknown(name.clone()),
8526                    });
8527                }
8528
8529                // ── Bare niladic temporal keywords: CURRENT_TIME, CURRENT_DATE,
8530                //    CURRENT_TIMESTAMP, LOCALTIMESTAMP (no parens) ──
8531                // ANSI SQL allows these without parentheses. Materialize them
8532                // as typed functions so the generator can emit dialect-specific
8533                // forms (e.g. TSQL requires CAST(GETDATE() AS TIME) rather than
8534                // a bare CURRENT_TIME reserved word).
8535                if name_qs == QuoteStyle::None && self.peek_type() != &TokenType::LParen {
8536                    let upper = name.to_ascii_uppercase();
8537                    let typed = match upper.as_str() {
8538                        "CURRENT_DATE" => Some(TypedFunction::CurrentDate),
8539                        "CURRENT_TIME" => Some(TypedFunction::CurrentTime),
8540                        "CURRENT_TIMESTAMP" | "LOCALTIMESTAMP" => {
8541                            Some(TypedFunction::CurrentTimestamp)
8542                        }
8543                        _ => None,
8544                    };
8545                    if let Some(tf) = typed {
8546                        return Ok(Expr::TypedFunction {
8547                            func: tf,
8548                            filter: None,
8549                            over: None,
8550                        });
8551                    }
8552                }
8553
8554                // Function call: name(...)
8555                if self.peek_type() == &TokenType::LParen {
8556                    self.advance();
8557
8558                    // TRY_CAST / SAFE_CAST / TRY_TO_TIMESTAMP / … — same shape
8559                    // as `CAST(expr AS type)`. Lower to `Expr::Cast` when the
8560                    // body matches; fall back to ordinary function call when
8561                    // it does not (e.g. comma-separated args).
8562                    if matches!(name.to_ascii_uppercase().as_str(), "TRY_CAST" | "SAFE_CAST") {
8563                        let save = self.pos;
8564                        let inner = self.parse_expr()?;
8565                        if self.match_token(TokenType::As) {
8566                            let dt = self.parse_data_type()?;
8567                            self.expect(TokenType::RParen)?;
8568                            return Ok(Expr::Cast {
8569                                expr: Box::new(inner),
8570                                data_type: dt,
8571                            });
8572                        }
8573                        self.pos = save;
8574                    }
8575
8576                    // Special: COUNT(*), COUNT(DISTINCT x)
8577                    let distinct = self.match_token(TokenType::Distinct);
8578                    // ANSI / ClickHouse `agg(ALL …)` — `ALL` is the opposite
8579                    // of DISTINCT and the default. Swallow so the args parse.
8580                    if !distinct {
8581                        let _ = self.match_token(TokenType::All);
8582                    }
8583
8584                    // Standard SQL syntactic forms for string functions:
8585                    //   SUBSTRING(expr FROM start [FOR len])
8586                    //   SUBSTRING(expr FOR len)
8587                    //   TRIM([LEADING|TRAILING|BOTH] [chars] FROM expr)
8588                    //   POSITION(needle IN haystack)
8589                    //   OVERLAY(expr PLACING str FROM start [FOR len])
8590                    let upper_name = name.to_ascii_uppercase();
8591                    if !distinct && self.peek_type() != &TokenType::RParen {
8592                        match upper_name.as_str() {
8593                            "SUBSTRING" | "SUBSTR" => {
8594                                let saved = self.pos;
8595                                let first = self.parse_expr()?;
8596                                if self.match_token(TokenType::From) {
8597                                    let start = self.parse_expr()?;
8598                                    let length = if self.check_keyword("FOR") {
8599                                        self.advance();
8600                                        Some(self.parse_expr()?)
8601                                    } else {
8602                                        None
8603                                    };
8604                                    self.expect(TokenType::RParen)?;
8605                                    let mut a = vec![first, start];
8606                                    if let Some(l) = length {
8607                                        a.push(l);
8608                                    }
8609                                    return Ok(Expr::Function {
8610                                        name: name.clone(),
8611                                        args: a,
8612                                        distinct: false,
8613                                        filter: None,
8614                                        over: None,
8615                                        order_by: Vec::new(),
8616                                        within_group: false,
8617                                    });
8618                                } else if self.check_keyword("FOR") {
8619                                    self.advance();
8620                                    let len = self.parse_expr()?;
8621                                    self.expect(TokenType::RParen)?;
8622                                    return Ok(Expr::Function {
8623                                        name: name.clone(),
8624                                        args: vec![first, len],
8625                                        distinct: false,
8626                                        filter: None,
8627                                        over: None,
8628                                        order_by: Vec::new(),
8629                                        within_group: false,
8630                                    });
8631                                }
8632                                self.pos = saved;
8633                            }
8634                            "TRIM" => {
8635                                let saved = self.pos;
8636                                if self.check_keyword("LEADING")
8637                                    || self.check_keyword("TRAILING")
8638                                    || self.check_keyword("BOTH")
8639                                {
8640                                    self.advance();
8641                                }
8642                                if self.peek_type() == &TokenType::From {
8643                                    self.advance();
8644                                    let expr = self.parse_expr()?;
8645                                    self.expect(TokenType::RParen)?;
8646                                    return Ok(Expr::Function {
8647                                        name: name.clone(),
8648                                        args: vec![expr],
8649                                        distinct: false,
8650                                        filter: None,
8651                                        over: None,
8652                                        order_by: Vec::new(),
8653                                        within_group: false,
8654                                    });
8655                                }
8656                                let chars = self.parse_expr()?;
8657                                if self.match_token(TokenType::From) {
8658                                    let expr = self.parse_expr()?;
8659                                    self.expect(TokenType::RParen)?;
8660                                    return Ok(Expr::Function {
8661                                        name: name.clone(),
8662                                        args: vec![expr, chars],
8663                                        distinct: false,
8664                                        filter: None,
8665                                        over: None,
8666                                        order_by: Vec::new(),
8667                                        within_group: false,
8668                                    });
8669                                }
8670                                self.pos = saved;
8671                            }
8672                            "POSITION" => {
8673                                let saved = self.pos;
8674                                let needle = self.parse_expr()?;
8675                                if self.match_token(TokenType::In) {
8676                                    let haystack = self.parse_expr()?;
8677                                    self.expect(TokenType::RParen)?;
8678                                    return Ok(Expr::Function {
8679                                        name: name.clone(),
8680                                        args: vec![needle, haystack],
8681                                        distinct: false,
8682                                        filter: None,
8683                                        over: None,
8684                                        order_by: Vec::new(),
8685                                        within_group: false,
8686                                    });
8687                                }
8688                                self.pos = saved;
8689                            }
8690                            "OVERLAY" => {
8691                                let saved = self.pos;
8692                                let target = self.parse_expr()?;
8693                                if self.check_keyword("PLACING") {
8694                                    self.advance();
8695                                    let placing = self.parse_expr()?;
8696                                    if self.match_token(TokenType::From) {
8697                                        let from = self.parse_expr()?;
8698                                        let len = if self.check_keyword("FOR") {
8699                                            self.advance();
8700                                            Some(self.parse_expr()?)
8701                                        } else {
8702                                            None
8703                                        };
8704                                        self.expect(TokenType::RParen)?;
8705                                        let mut a = vec![target, placing, from];
8706                                        if let Some(l) = len {
8707                                            a.push(l);
8708                                        }
8709                                        return Ok(Expr::Function {
8710                                            name: name.clone(),
8711                                            args: a,
8712                                            distinct: false,
8713                                            filter: None,
8714                                            over: None,
8715                                            order_by: Vec::new(),
8716                                            within_group: false,
8717                                        });
8718                                    }
8719                                }
8720                                self.pos = saved;
8721                            }
8722                            _ => {}
8723                        }
8724                    }
8725
8726                    // MySQL's GROUP_CONCAT has bespoke grammar
8727                    // (ORDER BY ..., SEPARATOR ...) — parse it into a typed
8728                    // expression so the structure is preserved across dialects.
8729                    if name.eq_ignore_ascii_case("GROUP_CONCAT") {
8730                        let expr = self.parse_group_concat_call(distinct)?;
8731                        self.expect(TokenType::RParen)?;
8732                        return Ok(expr);
8733                    }
8734
8735                    let args = if self.peek_type() == &TokenType::RParen {
8736                        vec![]
8737                    } else if self.peek_type() == &TokenType::Star {
8738                        self.advance();
8739                        vec![Expr::Wildcard]
8740                    } else {
8741                        let mut a = vec![self.parse_function_arg()?];
8742                        while self.match_token(TokenType::Comma) {
8743                            a.push(self.parse_function_arg()?);
8744                        }
8745                        a
8746                    };
8747
8748                    // Optional aggregate ORDER BY inside arg list (Postgres / Spark):
8749                    //   array_agg(x ORDER BY y DESC)
8750                    //   string_agg(x, ',' ORDER BY y)
8751                    let mut agg_order_by: Vec<OrderByItem> = vec![];
8752                    if self.peek_type() == &TokenType::Order {
8753                        self.advance();
8754                        self.expect(TokenType::By)?;
8755                        agg_order_by = self.parse_order_by_items()?;
8756                    }
8757                    // BigQuery / Snowflake: `ARRAY_AGG(x [ORDER BY y] LIMIT n)`.
8758                    // Swallow the trailing LIMIT clause inside the function call.
8759                    if self.peek_type() == &TokenType::Limit {
8760                        self.advance();
8761                        let _ = self.parse_expr();
8762                    }
8763                    // DuckDB aggregate-state modifier:
8764                    //   `count(1) EXPORT_STATE` returns the aggregate state
8765                    //   rather than its final value. We don't model it.
8766                    if self.check_keyword("EXPORT_STATE") {
8767                        self.advance();
8768                    }
8769                    self.expect(TokenType::RParen)?;
8770
8771                    // Optional WITHIN GROUP (ORDER BY ...) — ordered-set aggregates
8772                    //   percentile_cont(0.5) WITHIN GROUP (ORDER BY x)
8773                    //   listagg(x, ',') WITHIN GROUP (ORDER BY x)
8774                    let mut within_group = false;
8775                    let mut wg_order_by: Vec<OrderByItem> = vec![];
8776                    if self.check_keyword("WITHIN") {
8777                        self.advance();
8778                        self.expect_keyword("GROUP")?;
8779                        self.expect(TokenType::LParen)?;
8780                        self.expect(TokenType::Order)?;
8781                        self.expect(TokenType::By)?;
8782                        wg_order_by = self.parse_order_by_items()?;
8783                        self.expect(TokenType::RParen)?;
8784                        within_group = true;
8785                    }
8786
8787                    let final_order_by = if within_group {
8788                        wg_order_by
8789                    } else {
8790                        agg_order_by
8791                    };
8792
8793                    // Try to construct a typed function variant only when there are no
8794                    // aggregate-specific clauses (otherwise we lose them).
8795                    if final_order_by.is_empty()
8796                        && !within_group
8797                        && let Some(typed) = Self::try_typed_function(&name, args.clone(), distinct)
8798                    {
8799                        return Ok(typed);
8800                    }
8801
8802                    Ok(Expr::Function {
8803                        name,
8804                        args,
8805                        distinct,
8806                        filter: None,
8807                        over: None,
8808                        order_by: final_order_by,
8809                        within_group,
8810                    })
8811                }
8812                // Qualified column: table.column or table.*
8813                else if self.match_token(TokenType::Dot) {
8814                    if self.peek_type() == &TokenType::Star {
8815                        self.advance();
8816                        Ok(Expr::QualifiedWildcard { table: name })
8817                    } else {
8818                        // ClickHouse JSON subobject and typed access at the
8819                        // first dot: `json.^a`, `json.:Int64`.
8820                        let _ = self.match_token(TokenType::BitwiseXor);
8821                        let _ = self.match_token(TokenType::Colon);
8822                        let (mut col, mut col_qs) = if matches!(self.peek_type(), TokenType::Number)
8823                        {
8824                            // ClickHouse tuple index `x.1`.
8825                            let v = self.peek().value.clone();
8826                            self.advance();
8827                            (v, QuoteStyle::None)
8828                        } else if matches!(self.peek_type(), TokenType::Null) {
8829                            // ClickHouse JSON subcolumn `.null` (e.g.
8830                            // `arr.null`, `t.s.null`). Accept the keyword as
8831                            // a field name in dotted-access position.
8832                            let v = self.peek().value.clone();
8833                            self.advance();
8834                            (v, QuoteStyle::None)
8835                        } else {
8836                            self.expect_name_with_quote()?
8837                        };
8838                        // Handle 3+ part qualified names like `db.schema.table.column`
8839                        // (DuckDB, ClickHouse). We collapse everything except the
8840                        // final segment into the `table` field as a dotted string.
8841                        let mut table = name;
8842                        let mut table_qs = name_qs;
8843                        while self.match_token(TokenType::Dot) {
8844                            if self.peek_type() == &TokenType::Star {
8845                                self.advance();
8846                                let mut full = table;
8847                                full.push('.');
8848                                full.push_str(&col);
8849                                return Ok(Expr::QualifiedWildcard { table: full });
8850                            }
8851                            // ClickHouse JSON subobject (`json.^a`) and typed
8852                            // access (`json.a.:Int64`) — swallow the operator
8853                            // so the following name can be consumed normally.
8854                            let _ = self.match_token(TokenType::BitwiseXor);
8855                            let _ = self.match_token(TokenType::Colon);
8856                            // ClickHouse tuple index (`t.1`): treat number as
8857                            // a synthetic field name.
8858                            let (next_col, next_qs) =
8859                                if matches!(self.peek_type(), TokenType::Number) {
8860                                    let v = self.peek().value.clone();
8861                                    self.advance();
8862                                    (v, QuoteStyle::None)
8863                                } else if matches!(self.peek_type(), TokenType::Null) {
8864                                    let v = self.peek().value.clone();
8865                                    self.advance();
8866                                    (v, QuoteStyle::None)
8867                                } else {
8868                                    self.expect_name_with_quote()?
8869                                };
8870                            table.push('.');
8871                            table.push_str(&col);
8872                            table_qs = col_qs;
8873                            col = next_col;
8874                            col_qs = next_qs;
8875                        }
8876                        // Function call on dotted name: db.schema.func(args).
8877                        if self.peek_type() == &TokenType::LParen {
8878                            self.advance();
8879                            let mut full = table;
8880                            full.push('.');
8881                            full.push_str(&col);
8882                            let args = if self.peek_type() != &TokenType::RParen {
8883                                let mut a = vec![self.parse_function_arg()?];
8884                                while self.match_token(TokenType::Comma) {
8885                                    a.push(self.parse_function_arg()?);
8886                                }
8887                                a
8888                            } else {
8889                                vec![]
8890                            };
8891                            self.expect(TokenType::RParen)?;
8892                            return Ok(Expr::Function {
8893                                name: full,
8894                                args,
8895                                distinct: false,
8896                                filter: None,
8897                                over: None,
8898                                order_by: Vec::new(),
8899                                within_group: false,
8900                            });
8901                        }
8902                        Ok(Expr::Column {
8903                            table: Some(table),
8904                            name: col,
8905                            quote_style: col_qs,
8906                            table_quote_style: table_qs,
8907                        })
8908                    }
8909                } else {
8910                    Ok(Expr::Column {
8911                        table: None,
8912                        name,
8913                        quote_style: name_qs,
8914                        table_quote_style: QuoteStyle::None,
8915                    })
8916                }
8917            }
8918
8919            _ => {
8920                // Fallback: any other token whose value is a valid identifier
8921                // and is immediately followed by `(` is treated as a function
8922                // call. This handles reserved keywords used as Spark/Hive
8923                // built-ins (IF, ALL, ANY, EXISTS, MOD, etc.) and dialect
8924                // functions that happen to collide with token types.
8925                let v = token.value.clone();
8926                let is_word =
8927                    !v.is_empty() && v.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
8928                if is_word
8929                    && matches!(
8930                        self.peek_offset(1).map(|t| &t.token_type),
8931                        Some(TokenType::LParen)
8932                    )
8933                {
8934                    // TRY_CAST / SAFE_CAST / TRY_TO_TIMESTAMP / … — same
8935                    // shape as `CAST(expr AS type)`. Lower to `Expr::Cast`
8936                    // (or back to a function call when the form doesn't
8937                    // match).
8938                    let upper = v.to_ascii_uppercase();
8939                    if matches!(upper.as_str(), "TRY_CAST" | "SAFE_CAST") {
8940                        self.advance();
8941                        self.advance(); // consume '('
8942                        let inner = self.parse_expr()?;
8943                        if self.match_token(TokenType::As) {
8944                            let data_type = self.parse_data_type()?;
8945                            self.expect(TokenType::RParen)?;
8946                            return Ok(Expr::Cast {
8947                                expr: Box::new(inner),
8948                                data_type,
8949                            });
8950                        }
8951                        // Fall back: treat as ordinary function call.
8952                        let mut args = vec![inner];
8953                        while self.match_token(TokenType::Comma) {
8954                            args.push(self.parse_expr()?);
8955                        }
8956                        self.expect(TokenType::RParen)?;
8957                        return Ok(Expr::Function {
8958                            name: v,
8959                            args,
8960                            distinct: false,
8961                            filter: None,
8962                            over: None,
8963                            order_by: Vec::new(),
8964                            within_group: false,
8965                        });
8966                    }
8967                    self.advance();
8968                    self.advance(); // consume '('
8969                    let upper = v.to_ascii_uppercase();
8970                    // Standard SQL `SUBSTRING(expr FROM start [FOR length])`
8971                    // and MySQL `SUBSTRING(expr FROM start)` / `…FOR length`.
8972                    if matches!(upper.as_str(), "SUBSTRING" | "SUBSTR")
8973                        && self.peek_type() != &TokenType::RParen
8974                    {
8975                        let saved = self.pos;
8976                        let first = self.parse_expr()?;
8977                        if self.match_token(TokenType::From) {
8978                            let start = self.parse_expr()?;
8979                            let length = if self.check_keyword("FOR") {
8980                                self.advance();
8981                                Some(self.parse_expr()?)
8982                            } else {
8983                                None
8984                            };
8985                            self.expect(TokenType::RParen)?;
8986                            let mut args = vec![first, start];
8987                            if let Some(len) = length {
8988                                args.push(len);
8989                            }
8990                            return Ok(Expr::Function {
8991                                name: v,
8992                                args,
8993                                distinct: false,
8994                                filter: None,
8995                                over: None,
8996                                order_by: Vec::new(),
8997                                within_group: false,
8998                            });
8999                        }
9000                        if self.check_keyword("FOR") {
9001                            self.advance();
9002                            let length = self.parse_expr()?;
9003                            self.expect(TokenType::RParen)?;
9004                            return Ok(Expr::Function {
9005                                name: v,
9006                                args: vec![first, length],
9007                                distinct: false,
9008                                filter: None,
9009                                over: None,
9010                                order_by: Vec::new(),
9011                                within_group: false,
9012                            });
9013                        }
9014                        // Fall back: re-parse as comma list.
9015                        self.pos = saved;
9016                    }
9017                    // Standard `TRIM([LEADING|TRAILING|BOTH] [chars] FROM expr)`
9018                    // and `TRIM(expr [, chars])` (already covered by comma).
9019                    if upper == "TRIM" && self.peek_type() != &TokenType::RParen {
9020                        let saved = self.pos;
9021                        if self.check_keyword("LEADING")
9022                            || self.check_keyword("TRAILING")
9023                            || self.check_keyword("BOTH")
9024                        {
9025                            self.advance();
9026                        }
9027                        if self.peek_type() == &TokenType::From {
9028                            self.advance();
9029                            let expr = self.parse_expr()?;
9030                            self.expect(TokenType::RParen)?;
9031                            return Ok(Expr::Function {
9032                                name: v,
9033                                args: vec![expr],
9034                                distinct: false,
9035                                filter: None,
9036                                over: None,
9037                                order_by: Vec::new(),
9038                                within_group: false,
9039                            });
9040                        }
9041                        // chars FROM expr
9042                        let chars = self.parse_expr()?;
9043                        if self.match_token(TokenType::From) {
9044                            let expr = self.parse_expr()?;
9045                            self.expect(TokenType::RParen)?;
9046                            return Ok(Expr::Function {
9047                                name: v,
9048                                args: vec![expr, chars],
9049                                distinct: false,
9050                                filter: None,
9051                                over: None,
9052                                order_by: Vec::new(),
9053                                within_group: false,
9054                            });
9055                        }
9056                        // Plain comma list — fall back.
9057                        self.pos = saved;
9058                    }
9059                    // Standard `OVERLAY(expr PLACING str FROM start [FOR len])`.
9060                    if upper == "OVERLAY" && self.peek_type() != &TokenType::RParen {
9061                        let saved = self.pos;
9062                        let target = self.parse_expr()?;
9063                        if self.check_keyword("PLACING") {
9064                            self.advance();
9065                            let placing = self.parse_expr()?;
9066                            self.expect(TokenType::From)?;
9067                            let from = self.parse_expr()?;
9068                            let len = if self.check_keyword("FOR") {
9069                                self.advance();
9070                                Some(self.parse_expr()?)
9071                            } else {
9072                                None
9073                            };
9074                            self.expect(TokenType::RParen)?;
9075                            let mut args = vec![target, placing, from];
9076                            if let Some(l) = len {
9077                                args.push(l);
9078                            }
9079                            return Ok(Expr::Function {
9080                                name: v,
9081                                args,
9082                                distinct: false,
9083                                filter: None,
9084                                over: None,
9085                                order_by: Vec::new(),
9086                                within_group: false,
9087                            });
9088                        }
9089                        self.pos = saved;
9090                    }
9091                    // Standard `POSITION(needle IN haystack)`.
9092                    if upper == "POSITION" && self.peek_type() != &TokenType::RParen {
9093                        let saved = self.pos;
9094                        let needle = self.parse_expr()?;
9095                        if self.check_keyword("IN") {
9096                            self.advance();
9097                            let haystack = self.parse_expr()?;
9098                            self.expect(TokenType::RParen)?;
9099                            return Ok(Expr::Function {
9100                                name: v,
9101                                args: vec![needle, haystack],
9102                                distinct: false,
9103                                filter: None,
9104                                over: None,
9105                                order_by: Vec::new(),
9106                                within_group: false,
9107                            });
9108                        }
9109                        self.pos = saved;
9110                    }
9111                    let mut args = Vec::new();
9112                    if self.peek_type() != &TokenType::RParen {
9113                        args.push(self.parse_function_arg()?);
9114                        while self.match_token(TokenType::Comma) {
9115                            args.push(self.parse_function_arg()?);
9116                        }
9117                    }
9118                    self.expect(TokenType::RParen)?;
9119                    return Ok(Expr::Function {
9120                        name: v,
9121                        args,
9122                        distinct: false,
9123                        filter: None,
9124                        over: None,
9125                        order_by: Vec::new(),
9126                        within_group: false,
9127                    });
9128                }
9129                Err(SqlglotError::UnexpectedToken { token })
9130            }
9131        }
9132    }
9133
9134    /// Parse a single function-call argument. Accepts the DuckDB / PostgreSQL
9135    /// named-argument syntaxes `name := value` and `name => value` and falls
9136    /// back to a plain expression for positional arguments. The argument
9137    /// name is discarded — we don't model it in the AST.
9138    fn parse_function_arg(&mut self) -> Result<Expr> {
9139        // Hive table-valued function clause: `noop(on tbl partition by p
9140        // order by q distribute by r cluster by s sort by t)`. The arg
9141        // list begins with the `ON` keyword and is followed by a series
9142        // of windowing-style clauses we don't model. Swallow it as an
9143        // opaque payload so we don't reject the call.
9144        if matches!(self.peek_type(), TokenType::On) {
9145            let mut depth = 0usize;
9146            while !matches!(self.peek_type(), TokenType::Eof) {
9147                match self.peek_type() {
9148                    TokenType::LParen => depth += 1,
9149                    TokenType::RParen => {
9150                        if depth == 0 {
9151                            break;
9152                        }
9153                        depth -= 1;
9154                    }
9155                    TokenType::Comma if depth == 0 => break,
9156                    _ => {}
9157                }
9158                self.advance();
9159            }
9160            return Ok(Expr::Null);
9161        }
9162        if self.is_name_token()
9163            || self.is_data_type_token()
9164            || matches!(self.peek_type(), TokenType::Recursive)
9165        {
9166            let next = self.peek_offset(1).map(|t| &t.token_type);
9167            if matches!(next, Some(TokenType::Colon)) {
9168                let after = self.peek_offset(2).map(|t| &t.token_type);
9169                if matches!(after, Some(TokenType::Eq)) {
9170                    self.advance();
9171                    self.advance();
9172                    self.advance();
9173                    return self.parse_expr();
9174                }
9175            }
9176            if matches!(next, Some(TokenType::DoubleArrow)) {
9177                self.advance();
9178                self.advance();
9179                return self.parse_expr();
9180            }
9181        }
9182        // ClickHouse table functions: `view(SELECT …)`, `cluster(…)` etc.
9183        // accept a full SELECT / WITH / UNION inside the arg list. Parse
9184        // it as a Subquery so the surrounding call closes properly.
9185        if matches!(self.peek_type(), TokenType::Select | TokenType::With) {
9186            let stmt = self.parse_statement_inner()?;
9187            return Ok(Expr::Subquery(Box::new(stmt)));
9188        }
9189        let mut expr = self.parse_expr()?;
9190        // Oracle / Snowflake / MySQL `JSON_OBJECT('k' : value, ...)` and the
9191        // `JSON_OBJECTAGG(k : v)` family use `:` as a key-value separator
9192        // inside function args. After parsing the first expression, swallow
9193        // a bare `:` and parse the value side; emit the value as the arg
9194        // (we don't model JSON key-value pairs in the AST). Only fire when
9195        // the next-after-colon is not another `:` (`::` cast) and not `=`
9196        // (`:=` named arg, already handled above).
9197        if matches!(self.peek_type(), TokenType::Colon)
9198            && !matches!(
9199                self.peek_offset(1).map(|t| &t.token_type),
9200                Some(TokenType::Colon) | Some(TokenType::Eq)
9201            )
9202        {
9203            self.advance(); // :
9204            expr = self.parse_expr()?;
9205            // Optional `FORMAT JSON` suffix (Oracle).
9206            if self.peek().value.eq_ignore_ascii_case("FORMAT")
9207                && self
9208                    .peek_offset(1)
9209                    .map(|t| t.value.eq_ignore_ascii_case("JSON"))
9210                    .unwrap_or(false)
9211            {
9212                self.advance();
9213                self.advance();
9214            }
9215        }
9216        // ClickHouse: `func(expr AS alias)` — swallow the alias.
9217        if self.match_token(TokenType::As) && self.is_name_token() {
9218            self.advance();
9219        }
9220        // Spark / DataBricks UDTF call: `UDTF(TABLE(t) [PARTITION BY cols]
9221        // [ORDER BY cols])`. Swallow the table-argument modifiers opaquely.
9222        if self.peek_type() == &TokenType::Partition
9223            && self
9224                .peek_offset(1)
9225                .map(|t| matches!(t.token_type, TokenType::By))
9226                .unwrap_or(false)
9227        {
9228            self.advance(); // PARTITION
9229            self.advance(); // BY
9230            // Comma-separated expression list (column refs / exprs).
9231            let _ = self.parse_expr()?;
9232            while self.match_token(TokenType::Comma) {
9233                let _ = self.parse_expr()?;
9234            }
9235        }
9236        if self.peek_type() == &TokenType::Order
9237            && self
9238                .peek_offset(1)
9239                .map(|t| matches!(t.token_type, TokenType::By))
9240                .unwrap_or(false)
9241        {
9242            self.advance(); // ORDER
9243            self.advance(); // BY
9244            let _ = self.parse_order_by_items()?;
9245        }
9246        // BigQuery / DuckDB / Snowflake / Oracle window-function nulls
9247        // modifier: `LAST_VALUE(arg IGNORE NULLS)`, `... RESPECT NULLS`.
9248        // Swallow opaquely; we don't model it in the AST.
9249        if (self.peek().value.eq_ignore_ascii_case("IGNORE")
9250            || self.peek().value.eq_ignore_ascii_case("RESPECT"))
9251            && self
9252                .peek_offset(1)
9253                .map(|t| t.token_type == TokenType::Null || t.value.eq_ignore_ascii_case("NULLS"))
9254                .unwrap_or(false)
9255        {
9256            self.advance();
9257            self.advance();
9258        }
9259        // Postgres JSON helpers: `JSON_SERIALIZE(expr RETURNING type)`,
9260        // `JSON_QUERY(... RETURNING jsonb FORMAT JSON)`,
9261        // `JSON_VALUE(... RETURNING type DEFAULT v ON EMPTY|ERROR …)`. After
9262        // any RETURNING clause, swallow the optional FORMAT, DEFAULT, ON
9263        // EMPTY/ERROR tail so the call parses cleanly.
9264        if self.match_token(TokenType::Returning) {
9265            if self.is_data_type_token() || self.is_name_token() {
9266                let _ = self.parse_data_type();
9267            }
9268        }
9269        // SQL/JSON `PASSING v AS name [, v AS name]*` clause inside
9270        // JSON_EXISTS / JSON_VALUE / JSON_QUERY argument lists.
9271        if self.check_keyword("PASSING") {
9272            self.advance();
9273            loop {
9274                let _ = self.parse_expr()?;
9275                if self.match_token(TokenType::As) && self.is_name_token() {
9276                    self.advance();
9277                }
9278                if !self.match_token(TokenType::Comma) {
9279                    break;
9280                }
9281            }
9282        }
9283        // SQL/JSON behavior clauses: `NULL|ERROR|EMPTY [ARRAY|OBJECT]|
9284        // DEFAULT expr ON EMPTY|ERROR`. Swallow them opaquely; the
9285        // surrounding call still resolves to its primary expression.
9286        loop {
9287            let is_default = self.peek_type() == &TokenType::Default;
9288            let is_behavior_kw = self.check_keyword("ERROR")
9289                || self.check_keyword("NULL")
9290                || self.peek_type() == &TokenType::Null
9291                || self.check_keyword("EMPTY")
9292                || self.check_keyword("TRUE")
9293                || self.check_keyword("FALSE")
9294                || self.check_keyword("UNKNOWN");
9295            if !is_default && !is_behavior_kw {
9296                break;
9297            }
9298            // Look ahead: behavior keyword must be followed (possibly via
9299            // optional ARRAY/OBJECT/expr) by `ON ERROR|EMPTY` to qualify.
9300            let saved = self.pos;
9301            if is_default {
9302                self.advance();
9303                let _ = self.parse_expr();
9304            } else {
9305                self.advance();
9306                if self.check_keyword("ARRAY") || self.check_keyword("OBJECT") {
9307                    self.advance();
9308                }
9309            }
9310            if self.peek_type() == &TokenType::On
9311                && self
9312                    .peek_offset(1)
9313                    .map(|t| {
9314                        t.value.eq_ignore_ascii_case("ERROR")
9315                            || t.value.eq_ignore_ascii_case("EMPTY")
9316                    })
9317                    .unwrap_or(false)
9318            {
9319                self.advance(); // ON
9320                self.advance(); // ERROR / EMPTY
9321            } else {
9322                // Not actually a behavior clause — rewind.
9323                self.pos = saved;
9324                break;
9325            }
9326        }
9327        // MySQL `CONVERT(expr USING charset)` — swallow USING + name.
9328        if self.match_token(TokenType::Using) {
9329            if self.is_name_token() {
9330                self.advance();
9331            }
9332        }
9333        // ON EMPTY / ON ERROR / DEFAULT … ON EMPTY|ERROR / FORMAT … —
9334        // tolerated tail clauses common to JSON_VALUE / JSON_QUERY /
9335        // JSON_EXISTS. Loop while one of the recognized starters appears.
9336        loop {
9337            let starts = self.peek_type() == &TokenType::Default
9338                || self.match_keyword_clone("FORMAT")
9339                || (self.peek_type() == &TokenType::On
9340                    && self
9341                        .peek_offset(1)
9342                        .map(|t| {
9343                            t.value.eq_ignore_ascii_case("EMPTY")
9344                                || t.value.eq_ignore_ascii_case("ERROR")
9345                        })
9346                        .unwrap_or(false));
9347            if !starts {
9348                break;
9349            }
9350            // Consume up to the next top-level `,` / `)` / EOF, tracking
9351            // nesting so embedded parens (e.g. `DEFAULT ('C' COLLATE "C")`)
9352            // don't terminate prematurely.
9353            let mut depth = 0i32;
9354            while !matches!(self.peek_type(), TokenType::Eof) {
9355                match self.peek_type() {
9356                    TokenType::LParen | TokenType::LBracket => depth += 1,
9357                    TokenType::RParen | TokenType::RBracket => {
9358                        if depth == 0 {
9359                            break;
9360                        }
9361                        depth -= 1;
9362                    }
9363                    TokenType::Comma if depth == 0 => break,
9364                    _ => {}
9365                }
9366                self.advance();
9367            }
9368        }
9369        Ok(expr)
9370    }
9371
9372    /// True when the current token is a name token whose uppercase value
9373    /// equals `kw`. Does NOT advance the token cursor.
9374    fn match_keyword_clone(&self, kw: &str) -> bool {
9375        self.check_keyword(kw)
9376    }
9377
9378    fn is_data_type_token(&self) -> bool {
9379        self.is_data_type_token_kind(self.peek_type())
9380    }
9381
9382    fn is_data_type_token_kind(&self, tt: &TokenType) -> bool {
9383        matches!(
9384            tt,
9385            TokenType::Int
9386                | TokenType::Integer
9387                | TokenType::BigInt
9388                | TokenType::SmallInt
9389                | TokenType::TinyInt
9390                | TokenType::Float
9391                | TokenType::Double
9392                | TokenType::Decimal
9393                | TokenType::Numeric
9394                | TokenType::Real
9395                | TokenType::Varchar
9396                | TokenType::Char
9397                | TokenType::Text
9398                | TokenType::Boolean
9399                | TokenType::Date
9400                | TokenType::Timestamp
9401                | TokenType::TimestampTz
9402                | TokenType::Time
9403                | TokenType::Interval
9404                | TokenType::Blob
9405                | TokenType::Bytea
9406                | TokenType::Json
9407                | TokenType::Jsonb
9408                | TokenType::Uuid
9409                | TokenType::Array
9410                | TokenType::Map
9411                | TokenType::Struct
9412        )
9413    }
9414
9415    fn parse_datetime_field(&mut self) -> Result<DateTimeField> {
9416        let token = self.peek().clone();
9417        let field = match &token.token_type {
9418            TokenType::Year => DateTimeField::Year,
9419            TokenType::Month => DateTimeField::Month,
9420            TokenType::Day => DateTimeField::Day,
9421            TokenType::Hour => DateTimeField::Hour,
9422            TokenType::Minute => DateTimeField::Minute,
9423            TokenType::Second => DateTimeField::Second,
9424            TokenType::Epoch => DateTimeField::Epoch,
9425            _ => {
9426                let name = token.value.to_uppercase();
9427                match name.as_str() {
9428                    "YEAR" => DateTimeField::Year,
9429                    "QUARTER" => DateTimeField::Quarter,
9430                    "MONTH" => DateTimeField::Month,
9431                    "WEEK" => DateTimeField::Week,
9432                    "DAY" => DateTimeField::Day,
9433                    "DOW" | "DAYOFWEEK" => DateTimeField::DayOfWeek,
9434                    "DOY" | "DAYOFYEAR" => DateTimeField::DayOfYear,
9435                    "HOUR" => DateTimeField::Hour,
9436                    "MINUTE" => DateTimeField::Minute,
9437                    "SECOND" => DateTimeField::Second,
9438                    "MILLISECOND" | "MILLISECONDS" | "MS" => DateTimeField::Millisecond,
9439                    "MICROSECOND" | "MICROSECONDS" | "US" => DateTimeField::Microsecond,
9440                    "NANOSECOND" | "NANOSECONDS" | "NS" => DateTimeField::Nanosecond,
9441                    "YEARS" => DateTimeField::Year,
9442                    "QUARTERS" => DateTimeField::Quarter,
9443                    "MONTHS" => DateTimeField::Month,
9444                    "WEEKS" => DateTimeField::Week,
9445                    "DAYS" => DateTimeField::Day,
9446                    "HOURS" => DateTimeField::Hour,
9447                    "MINUTES" => DateTimeField::Minute,
9448                    "SECONDS" => DateTimeField::Second,
9449                    "EPOCH" => DateTimeField::Epoch,
9450                    "TIMEZONE" => DateTimeField::Timezone,
9451                    "TIMEZONE_HOUR" => DateTimeField::TimezoneHour,
9452                    "TIMEZONE_MINUTE" => DateTimeField::TimezoneMinute,
9453                    // MySQL composite interval units. We don't model them
9454                    // distinctly; lower to the dominant component so the
9455                    // surrounding parse completes.
9456                    "DAY_HOUR" | "DAY_MINUTE" | "DAY_SECOND" | "DAY_MICROSECOND" => {
9457                        DateTimeField::Day
9458                    }
9459                    "HOUR_MINUTE" | "HOUR_SECOND" | "HOUR_MICROSECOND" => DateTimeField::Hour,
9460                    "MINUTE_SECOND" | "MINUTE_MICROSECOND" => DateTimeField::Minute,
9461                    "SECOND_MICROSECOND" => DateTimeField::Second,
9462                    "YEAR_MONTH" => DateTimeField::Year,
9463                    _ => {
9464                        return Err(SqlglotError::ParserError {
9465                            message: format!("Unknown datetime field: {name}"),
9466                        });
9467                    }
9468                }
9469            }
9470        };
9471        self.advance();
9472        Ok(field)
9473    }
9474
9475    fn try_parse_datetime_field(&mut self) -> Option<DateTimeField> {
9476        let saved = self.pos;
9477        match self.parse_datetime_field() {
9478            Ok(field) => Some(field),
9479            Err(_) => {
9480                self.pos = saved;
9481                None
9482            }
9483        }
9484    }
9485
9486    /// Parse the inside of `GROUP_CONCAT(...)` (caller has already consumed
9487    /// the `(` and optional `DISTINCT`). Returns a typed `GroupConcat`
9488    /// expression. Does NOT consume the trailing `)`.
9489    fn parse_group_concat_call(&mut self, distinct: bool) -> Result<Expr> {
9490        let mut exprs: Vec<Expr> = Vec::new();
9491        let mut order_by: Vec<OrderByItem> = Vec::new();
9492        let mut separator: Option<Box<Expr>> = None;
9493
9494        if self.peek_type() != &TokenType::RParen {
9495            exprs.push(self.parse_expr()?);
9496            while self.peek_type() == &TokenType::Comma {
9497                // ORDER BY / SEPARATOR are alternative terminators, not args.
9498                // Peek one past the comma to disambiguate `f(a, b)` from
9499                // `f(a, b ORDER BY ...)` — but comma here always introduces
9500                // another positional arg, so just keep consuming.
9501                self.advance();
9502                exprs.push(self.parse_expr()?);
9503            }
9504
9505            if self.match_token(TokenType::Order) {
9506                self.expect(TokenType::By)?;
9507                order_by = self.parse_order_by_items()?;
9508            }
9509
9510            if self.match_keyword("SEPARATOR") {
9511                separator = Some(Box::new(self.parse_expr()?));
9512            }
9513        }
9514
9515        Ok(Expr::TypedFunction {
9516            func: TypedFunction::GroupConcat {
9517                exprs,
9518                separator,
9519                order_by,
9520                distinct,
9521            },
9522            filter: None,
9523            over: None,
9524        })
9525    }
9526
9527    /// Try to construct a typed function expression from a parsed function call.
9528    /// Returns `None` if the function name is not recognized, falling back to
9529    /// the generic `Expr::Function`.
9530    fn try_typed_function(name: &str, args: Vec<Expr>, distinct: bool) -> Option<Expr> {
9531        let upper = name.to_uppercase();
9532        let tf = match upper.as_str() {
9533            // ── Date/Time ──────────────────────────────────────────
9534            "DATE_ADD" | "DATEADD" | "TIMESTAMPADD" => {
9535                let mut it = args.into_iter();
9536                let first = it.next()?;
9537                let second = it.next()?;
9538                let third = it.next();
9539                // Handle DATEADD(unit, interval, expr) — TSQL/Snowflake arg order
9540                if upper == "DATEADD" {
9541                    if let Some(third_arg) = third {
9542                        // 3-arg: DATEADD(unit, interval, expr)
9543                        let unit = Self::expr_to_datetime_field(&first);
9544                        TypedFunction::DateAdd {
9545                            expr: Box::new(third_arg),
9546                            interval: Box::new(second),
9547                            unit,
9548                        }
9549                    } else {
9550                        TypedFunction::DateAdd {
9551                            expr: Box::new(first),
9552                            interval: Box::new(second),
9553                            unit: None,
9554                        }
9555                    }
9556                } else {
9557                    // DATE_ADD(expr, interval [, unit])
9558                    let unit = third.as_ref().and_then(Self::expr_to_datetime_field);
9559                    TypedFunction::DateAdd {
9560                        expr: Box::new(first),
9561                        interval: Box::new(second),
9562                        unit,
9563                    }
9564                }
9565            }
9566            "DATE_DIFF" | "DATEDIFF" | "TIMESTAMPDIFF" => {
9567                let mut it = args.into_iter();
9568                let first = it.next()?;
9569                let second = it.next()?;
9570                let third = it.next();
9571                if let Some(third_arg) = third {
9572                    if upper == "DATEDIFF" {
9573                        // DATEDIFF(unit, start, end) — TSQL/Snowflake
9574                        let unit = Self::expr_to_datetime_field(&first);
9575                        TypedFunction::DateDiff {
9576                            start: Box::new(second),
9577                            end: Box::new(third_arg),
9578                            unit,
9579                        }
9580                    } else {
9581                        let unit = Self::expr_to_datetime_field(&third_arg);
9582                        TypedFunction::DateDiff {
9583                            start: Box::new(first),
9584                            end: Box::new(second),
9585                            unit,
9586                        }
9587                    }
9588                } else {
9589                    TypedFunction::DateDiff {
9590                        start: Box::new(first),
9591                        end: Box::new(second),
9592                        unit: None,
9593                    }
9594                }
9595            }
9596            "DATE_TRUNC" | "DATETRUNC" => {
9597                let mut it = args.into_iter();
9598                let first = it.next()?;
9599                let second = it.next()?;
9600                // DATE_TRUNC('unit', expr) or DATE_TRUNC(unit, expr)
9601                let (unit, expr) = if let Some(u) = Self::expr_to_datetime_field(&first) {
9602                    (u, second)
9603                } else if let Some(u) = Self::expr_to_datetime_field(&second) {
9604                    (u, first)
9605                } else {
9606                    // Default: first = unit string, second = expr
9607                    return None;
9608                };
9609                TypedFunction::DateTrunc {
9610                    unit,
9611                    expr: Box::new(expr),
9612                }
9613            }
9614            "DATE_SUB" | "DATESUB" => {
9615                let mut it = args.into_iter();
9616                let first = it.next()?;
9617                let second = it.next()?;
9618                let third = it.next();
9619                let unit = third.as_ref().and_then(Self::expr_to_datetime_field);
9620                TypedFunction::DateSub {
9621                    expr: Box::new(first),
9622                    interval: Box::new(second),
9623                    unit,
9624                }
9625            }
9626            "CURRENT_DATE" => TypedFunction::CurrentDate,
9627            "CURRENT_TIME" | "CURTIME" => TypedFunction::CurrentTime,
9628            "CURRENT_TIMESTAMP" | "NOW" | "GETDATE" | "SYSDATE" => TypedFunction::CurrentTimestamp,
9629            "STR_TO_TIME" | "STR_TO_DATE" | "TO_TIMESTAMP" | "PARSE_TIMESTAMP"
9630            | "PARSE_DATETIME" => {
9631                let mut it = args.into_iter();
9632                let expr = it.next()?;
9633                let format = it.next()?;
9634                TypedFunction::StrToTime {
9635                    expr: Box::new(expr),
9636                    format: Box::new(format),
9637                }
9638            }
9639            "TIME_TO_STR" | "DATE_FORMAT" | "FORMAT_TIMESTAMP" | "FORMAT_DATETIME" | "TO_CHAR" => {
9640                let mut it = args.into_iter();
9641                let expr = it.next()?;
9642                let format = it.next()?;
9643                TypedFunction::TimeToStr {
9644                    expr: Box::new(expr),
9645                    format: Box::new(format),
9646                }
9647            }
9648            "TS_OR_DS_TO_DATE" => {
9649                let mut it = args.into_iter();
9650                TypedFunction::TsOrDsToDate {
9651                    expr: Box::new(it.next()?),
9652                }
9653            }
9654            "YEAR" => {
9655                let mut it = args.into_iter();
9656                TypedFunction::Year {
9657                    expr: Box::new(it.next()?),
9658                }
9659            }
9660            "MONTH" => {
9661                let mut it = args.into_iter();
9662                TypedFunction::Month {
9663                    expr: Box::new(it.next()?),
9664                }
9665            }
9666            "DAY" | "DAYOFMONTH" => {
9667                let mut it = args.into_iter();
9668                TypedFunction::Day {
9669                    expr: Box::new(it.next()?),
9670                }
9671            }
9672
9673            // ── String ─────────────────────────────────────────────
9674            "TRIM" => {
9675                let mut it = args.into_iter();
9676                let expr = it.next()?;
9677                TypedFunction::Trim {
9678                    expr: Box::new(expr),
9679                    trim_type: TrimType::Both,
9680                    trim_chars: None,
9681                }
9682            }
9683            "LTRIM" => {
9684                let mut it = args.into_iter();
9685                let expr = it.next()?;
9686                TypedFunction::Trim {
9687                    expr: Box::new(expr),
9688                    trim_type: TrimType::Leading,
9689                    trim_chars: None,
9690                }
9691            }
9692            "RTRIM" => {
9693                let mut it = args.into_iter();
9694                let expr = it.next()?;
9695                TypedFunction::Trim {
9696                    expr: Box::new(expr),
9697                    trim_type: TrimType::Trailing,
9698                    trim_chars: None,
9699                }
9700            }
9701            "SUBSTRING" | "SUBSTR" => {
9702                let mut it = args.into_iter();
9703                let expr = it.next()?;
9704                let start = it.next()?;
9705                let length = it.next();
9706                TypedFunction::Substring {
9707                    expr: Box::new(expr),
9708                    start: Box::new(start),
9709                    length: length.map(Box::new),
9710                }
9711            }
9712            "UPPER" | "UCASE" => {
9713                let mut it = args.into_iter();
9714                TypedFunction::Upper {
9715                    expr: Box::new(it.next()?),
9716                }
9717            }
9718            "LOWER" | "LCASE" => {
9719                let mut it = args.into_iter();
9720                TypedFunction::Lower {
9721                    expr: Box::new(it.next()?),
9722                }
9723            }
9724            "REGEXP_LIKE" | "RLIKE" => {
9725                let mut it = args.into_iter();
9726                let expr = it.next()?;
9727                let pattern = it.next()?;
9728                let flags = it.next();
9729                TypedFunction::RegexpLike {
9730                    expr: Box::new(expr),
9731                    pattern: Box::new(pattern),
9732                    flags: flags.map(Box::new),
9733                }
9734            }
9735            "REGEXP_EXTRACT" | "REGEXP_SUBSTR" => {
9736                let mut it = args.into_iter();
9737                let expr = it.next()?;
9738                let pattern = it.next()?;
9739                let group_index = it.next();
9740                TypedFunction::RegexpExtract {
9741                    expr: Box::new(expr),
9742                    pattern: Box::new(pattern),
9743                    group_index: group_index.map(Box::new),
9744                }
9745            }
9746            "REGEXP_REPLACE" => {
9747                let mut it = args.into_iter();
9748                let expr = it.next()?;
9749                let pattern = it.next()?;
9750                let replacement = it.next()?;
9751                let flags = it.next();
9752                TypedFunction::RegexpReplace {
9753                    expr: Box::new(expr),
9754                    pattern: Box::new(pattern),
9755                    replacement: Box::new(replacement),
9756                    flags: flags.map(Box::new),
9757                }
9758            }
9759            "CONCAT_WS" => {
9760                let mut it = args.into_iter();
9761                let separator = it.next()?;
9762                let exprs: Vec<Expr> = it.collect();
9763                TypedFunction::ConcatWs {
9764                    separator: Box::new(separator),
9765                    exprs,
9766                }
9767            }
9768            "SPLIT" | "STRING_SPLIT" => {
9769                let mut it = args.into_iter();
9770                let expr = it.next()?;
9771                let delimiter = it.next()?;
9772                TypedFunction::Split {
9773                    expr: Box::new(expr),
9774                    delimiter: Box::new(delimiter),
9775                }
9776            }
9777            "INITCAP" => {
9778                let mut it = args.into_iter();
9779                TypedFunction::Initcap {
9780                    expr: Box::new(it.next()?),
9781                }
9782            }
9783            "LENGTH" | "LEN" | "CHAR_LENGTH" | "CHARACTER_LENGTH" => {
9784                let mut it = args.into_iter();
9785                TypedFunction::Length {
9786                    expr: Box::new(it.next()?),
9787                }
9788            }
9789            "REPLACE" => {
9790                let mut it = args.into_iter();
9791                let expr = it.next()?;
9792                let from = it.next()?;
9793                let to = it.next()?;
9794                TypedFunction::Replace {
9795                    expr: Box::new(expr),
9796                    from: Box::new(from),
9797                    to: Box::new(to),
9798                }
9799            }
9800            "REVERSE" => {
9801                let mut it = args.into_iter();
9802                TypedFunction::Reverse {
9803                    expr: Box::new(it.next()?),
9804                }
9805            }
9806            "LEFT" => {
9807                let mut it = args.into_iter();
9808                let expr = it.next()?;
9809                let n = it.next()?;
9810                TypedFunction::Left {
9811                    expr: Box::new(expr),
9812                    n: Box::new(n),
9813                }
9814            }
9815            "RIGHT" => {
9816                let mut it = args.into_iter();
9817                let expr = it.next()?;
9818                let n = it.next()?;
9819                TypedFunction::Right {
9820                    expr: Box::new(expr),
9821                    n: Box::new(n),
9822                }
9823            }
9824            "LPAD" => {
9825                let mut it = args.into_iter();
9826                let expr = it.next()?;
9827                let length = it.next()?;
9828                let pad = it.next();
9829                TypedFunction::Lpad {
9830                    expr: Box::new(expr),
9831                    length: Box::new(length),
9832                    pad: pad.map(Box::new),
9833                }
9834            }
9835            "RPAD" => {
9836                let mut it = args.into_iter();
9837                let expr = it.next()?;
9838                let length = it.next()?;
9839                let pad = it.next();
9840                TypedFunction::Rpad {
9841                    expr: Box::new(expr),
9842                    length: Box::new(length),
9843                    pad: pad.map(Box::new),
9844                }
9845            }
9846
9847            // ── Aggregate ──────────────────────────────────────────
9848            "COUNT" => {
9849                let mut it = args.into_iter();
9850                let expr = it.next().unwrap_or(Expr::Wildcard);
9851                TypedFunction::Count {
9852                    expr: Box::new(expr),
9853                    distinct,
9854                }
9855            }
9856            "SUM" => {
9857                let mut it = args.into_iter();
9858                TypedFunction::Sum {
9859                    expr: Box::new(it.next()?),
9860                    distinct,
9861                }
9862            }
9863            "AVG" => {
9864                let mut it = args.into_iter();
9865                TypedFunction::Avg {
9866                    expr: Box::new(it.next()?),
9867                    distinct,
9868                }
9869            }
9870            "MIN" => {
9871                let mut it = args.into_iter();
9872                TypedFunction::Min {
9873                    expr: Box::new(it.next()?),
9874                }
9875            }
9876            "MAX" => {
9877                let mut it = args.into_iter();
9878                TypedFunction::Max {
9879                    expr: Box::new(it.next()?),
9880                }
9881            }
9882            "ARRAY_AGG" | "LIST" | "COLLECT_LIST" => {
9883                let mut it = args.into_iter();
9884                TypedFunction::ArrayAgg {
9885                    expr: Box::new(it.next()?),
9886                    distinct,
9887                }
9888            }
9889            "APPROX_DISTINCT" | "APPROX_COUNT_DISTINCT" => {
9890                let mut it = args.into_iter();
9891                TypedFunction::ApproxDistinct {
9892                    expr: Box::new(it.next()?),
9893                }
9894            }
9895            "VARIANCE" | "VAR_SAMP" | "VAR" => {
9896                let mut it = args.into_iter();
9897                TypedFunction::Variance {
9898                    expr: Box::new(it.next()?),
9899                }
9900            }
9901            "VAR_POP" => {
9902                let mut it = args.into_iter();
9903                TypedFunction::VariancePop {
9904                    expr: Box::new(it.next()?),
9905                }
9906            }
9907            "STDDEV" | "STDDEV_SAMP" => {
9908                let mut it = args.into_iter();
9909                TypedFunction::Stddev {
9910                    expr: Box::new(it.next()?),
9911                }
9912            }
9913            "STDDEV_POP" => {
9914                let mut it = args.into_iter();
9915                TypedFunction::StddevPop {
9916                    expr: Box::new(it.next()?),
9917                }
9918            }
9919
9920            // ── Array ──────────────────────────────────────────────
9921            "ARRAY_CONCAT" | "ARRAY_CAT" => TypedFunction::ArrayConcat { arrays: args },
9922            "ARRAY_CONTAINS" => {
9923                let mut it = args.into_iter();
9924                let array = it.next()?;
9925                let element = it.next()?;
9926                TypedFunction::ArrayContains {
9927                    array: Box::new(array),
9928                    element: Box::new(element),
9929                }
9930            }
9931            "ARRAY_SIZE" | "ARRAY_LENGTH" | "CARDINALITY" => {
9932                let mut it = args.into_iter();
9933                TypedFunction::ArraySize {
9934                    expr: Box::new(it.next()?),
9935                }
9936            }
9937            "EXPLODE" => {
9938                let mut it = args.into_iter();
9939                TypedFunction::Explode {
9940                    expr: Box::new(it.next()?),
9941                }
9942            }
9943            "GENERATE_SERIES" | "SEQUENCE" => {
9944                let mut it = args.into_iter();
9945                let start = it.next()?;
9946                let stop = it.next()?;
9947                let step = it.next();
9948                TypedFunction::GenerateSeries {
9949                    start: Box::new(start),
9950                    stop: Box::new(stop),
9951                    step: step.map(Box::new),
9952                }
9953            }
9954            "FLATTEN" => {
9955                let mut it = args.into_iter();
9956                TypedFunction::Flatten {
9957                    expr: Box::new(it.next()?),
9958                }
9959            }
9960
9961            // ── JSON ───────────────────────────────────────────────
9962            "JSON_EXTRACT" | "JSON_VALUE" => {
9963                let mut it = args.into_iter();
9964                let expr = it.next()?;
9965                let path = it.next()?;
9966                TypedFunction::JSONExtract {
9967                    expr: Box::new(expr),
9968                    path: Box::new(path),
9969                }
9970            }
9971            "JSON_EXTRACT_SCALAR" => {
9972                let mut it = args.into_iter();
9973                let expr = it.next()?;
9974                let path = it.next()?;
9975                TypedFunction::JSONExtractScalar {
9976                    expr: Box::new(expr),
9977                    path: Box::new(path),
9978                }
9979            }
9980            "PARSE_JSON" | "JSON_PARSE" => {
9981                let mut it = args.into_iter();
9982                TypedFunction::ParseJSON {
9983                    expr: Box::new(it.next()?),
9984                }
9985            }
9986            "JSON_FORMAT" | "TO_JSON" | "TO_JSON_STRING" => {
9987                let mut it = args.into_iter();
9988                TypedFunction::JSONFormat {
9989                    expr: Box::new(it.next()?),
9990                }
9991            }
9992
9993            // ── Window ─────────────────────────────────────────────
9994            "ROW_NUMBER" => TypedFunction::RowNumber,
9995            "RANK" => TypedFunction::Rank,
9996            "DENSE_RANK" => TypedFunction::DenseRank,
9997            "NTILE" => {
9998                let mut it = args.into_iter();
9999                TypedFunction::NTile {
10000                    n: Box::new(it.next()?),
10001                }
10002            }
10003            "LEAD" => {
10004                let mut it = args.into_iter();
10005                let expr = it.next()?;
10006                let offset = it.next();
10007                let default = it.next();
10008                TypedFunction::Lead {
10009                    expr: Box::new(expr),
10010                    offset: offset.map(Box::new),
10011                    default: default.map(Box::new),
10012                }
10013            }
10014            "LAG" => {
10015                let mut it = args.into_iter();
10016                let expr = it.next()?;
10017                let offset = it.next();
10018                let default = it.next();
10019                TypedFunction::Lag {
10020                    expr: Box::new(expr),
10021                    offset: offset.map(Box::new),
10022                    default: default.map(Box::new),
10023                }
10024            }
10025            "FIRST_VALUE" => {
10026                let mut it = args.into_iter();
10027                TypedFunction::FirstValue {
10028                    expr: Box::new(it.next()?),
10029                }
10030            }
10031            "LAST_VALUE" => {
10032                let mut it = args.into_iter();
10033                TypedFunction::LastValue {
10034                    expr: Box::new(it.next()?),
10035                }
10036            }
10037
10038            // ── Math ───────────────────────────────────────────────
10039            "ABS" => {
10040                let mut it = args.into_iter();
10041                TypedFunction::Abs {
10042                    expr: Box::new(it.next()?),
10043                }
10044            }
10045            "CEIL" | "CEILING" => {
10046                let mut it = args.into_iter();
10047                TypedFunction::Ceil {
10048                    expr: Box::new(it.next()?),
10049                }
10050            }
10051            "FLOOR" => {
10052                let mut it = args.into_iter();
10053                TypedFunction::Floor {
10054                    expr: Box::new(it.next()?),
10055                }
10056            }
10057            "ROUND" => {
10058                let mut it = args.into_iter();
10059                let expr = it.next()?;
10060                let decimals = it.next();
10061                TypedFunction::Round {
10062                    expr: Box::new(expr),
10063                    decimals: decimals.map(Box::new),
10064                }
10065            }
10066            "LOG" => {
10067                let mut it = args.into_iter();
10068                let expr = it.next()?;
10069                let base = it.next();
10070                TypedFunction::Log {
10071                    expr: Box::new(expr),
10072                    base: base.map(Box::new),
10073                }
10074            }
10075            "LN" => {
10076                let mut it = args.into_iter();
10077                TypedFunction::Ln {
10078                    expr: Box::new(it.next()?),
10079                }
10080            }
10081            "POW" | "POWER" => {
10082                let mut it = args.into_iter();
10083                let base = it.next()?;
10084                let exponent = it.next()?;
10085                TypedFunction::Pow {
10086                    base: Box::new(base),
10087                    exponent: Box::new(exponent),
10088                }
10089            }
10090            "SQRT" => {
10091                let mut it = args.into_iter();
10092                TypedFunction::Sqrt {
10093                    expr: Box::new(it.next()?),
10094                }
10095            }
10096            "GREATEST" => TypedFunction::Greatest { exprs: args },
10097            "LEAST" => TypedFunction::Least { exprs: args },
10098            "MOD" => {
10099                let mut it = args.into_iter();
10100                let left = it.next()?;
10101                let right = it.next()?;
10102                TypedFunction::Mod {
10103                    left: Box::new(left),
10104                    right: Box::new(right),
10105                }
10106            }
10107
10108            // ── Conversion ─────────────────────────────────────────
10109            "HEX" | "TO_HEX" => {
10110                let mut it = args.into_iter();
10111                TypedFunction::Hex {
10112                    expr: Box::new(it.next()?),
10113                }
10114            }
10115            "UNHEX" | "FROM_HEX" => {
10116                let mut it = args.into_iter();
10117                TypedFunction::Unhex {
10118                    expr: Box::new(it.next()?),
10119                }
10120            }
10121            "MD5" => {
10122                let mut it = args.into_iter();
10123                TypedFunction::Md5 {
10124                    expr: Box::new(it.next()?),
10125                }
10126            }
10127            "SHA" | "SHA1" => {
10128                let mut it = args.into_iter();
10129                TypedFunction::Sha {
10130                    expr: Box::new(it.next()?),
10131                }
10132            }
10133            "SHA2" | "SHA256" | "SHA512" => {
10134                let mut it = args.into_iter();
10135                let expr = it.next()?;
10136                let bit_length = it.next().unwrap_or(Expr::Number("256".to_string()));
10137                TypedFunction::Sha2 {
10138                    expr: Box::new(expr),
10139                    bit_length: Box::new(bit_length),
10140                }
10141            }
10142
10143            // Not a recognized typed function
10144            _ => return None,
10145        };
10146
10147        Some(Expr::TypedFunction {
10148            func: tf,
10149            filter: None,
10150            over: None,
10151        })
10152    }
10153
10154    /// Try to extract a DateTimeField from a column-name expression.
10155    fn expr_to_datetime_field(expr: &Expr) -> Option<DateTimeField> {
10156        match expr {
10157            Expr::Column {
10158                name, table: None, ..
10159            } => match name.to_uppercase().as_str() {
10160                "YEAR" => Some(DateTimeField::Year),
10161                "QUARTER" => Some(DateTimeField::Quarter),
10162                "MONTH" => Some(DateTimeField::Month),
10163                "WEEK" => Some(DateTimeField::Week),
10164                "DAY" => Some(DateTimeField::Day),
10165                "HOUR" => Some(DateTimeField::Hour),
10166                "MINUTE" => Some(DateTimeField::Minute),
10167                "SECOND" => Some(DateTimeField::Second),
10168                "MILLISECOND" => Some(DateTimeField::Millisecond),
10169                "MICROSECOND" => Some(DateTimeField::Microsecond),
10170                _ => None,
10171            },
10172            Expr::StringLiteral(s) | Expr::NationalStringLiteral(s) => {
10173                match s.to_uppercase().as_str() {
10174                    "YEAR" => Some(DateTimeField::Year),
10175                    "QUARTER" => Some(DateTimeField::Quarter),
10176                    "MONTH" => Some(DateTimeField::Month),
10177                    "WEEK" => Some(DateTimeField::Week),
10178                    "DAY" => Some(DateTimeField::Day),
10179                    "HOUR" => Some(DateTimeField::Hour),
10180                    "MINUTE" => Some(DateTimeField::Minute),
10181                    "SECOND" => Some(DateTimeField::Second),
10182                    "MILLISECOND" => Some(DateTimeField::Millisecond),
10183                    "MICROSECOND" => Some(DateTimeField::Microsecond),
10184                    _ => None,
10185                }
10186            }
10187            _ => None,
10188        }
10189    }
10190
10191    fn parse_case_expr(&mut self) -> Result<Expr> {
10192        self.expect(TokenType::Case)?;
10193
10194        let operand = if self.peek_type() != &TokenType::When {
10195            Some(Box::new(self.parse_expr()?))
10196        } else {
10197            None
10198        };
10199
10200        let mut when_clauses = Vec::new();
10201        while self.match_token(TokenType::When) {
10202            let condition = self.parse_expr()?;
10203            self.expect(TokenType::Then)?;
10204            let result = self.parse_expr()?;
10205            when_clauses.push((condition, result));
10206        }
10207
10208        let else_clause = if self.match_token(TokenType::Else) {
10209            Some(Box::new(self.parse_expr()?))
10210        } else {
10211            None
10212        };
10213
10214        self.expect(TokenType::End)?;
10215
10216        Ok(Expr::Case {
10217            operand,
10218            when_clauses,
10219            else_clause,
10220        })
10221    }
10222}
10223
10224#[cfg(test)]
10225mod tests {
10226    use super::*;
10227
10228    #[test]
10229    fn test_parse_simple_select() {
10230        let stmt = Parser::new("SELECT a, b FROM t")
10231            .unwrap()
10232            .parse_statement()
10233            .unwrap();
10234        match stmt {
10235            Statement::Select(sel) => {
10236                assert_eq!(sel.columns.len(), 2);
10237                assert!(sel.from.is_some());
10238            }
10239            _ => panic!("Expected SELECT"),
10240        }
10241    }
10242
10243    #[test]
10244    fn test_parse_select_with_where() {
10245        let stmt = Parser::new("SELECT x FROM t WHERE x > 10")
10246            .unwrap()
10247            .parse_statement()
10248            .unwrap();
10249        match stmt {
10250            Statement::Select(sel) => assert!(sel.where_clause.is_some()),
10251            _ => panic!("Expected SELECT"),
10252        }
10253    }
10254
10255    #[test]
10256    fn test_parse_select_wildcard() {
10257        let stmt = Parser::new("SELECT * FROM users")
10258            .unwrap()
10259            .parse_statement()
10260            .unwrap();
10261        match stmt {
10262            Statement::Select(sel) => {
10263                assert_eq!(sel.columns.len(), 1);
10264                assert!(matches!(sel.columns[0], SelectItem::Wildcard));
10265            }
10266            _ => panic!("Expected SELECT"),
10267        }
10268    }
10269
10270    #[test]
10271    fn test_parse_insert() {
10272        let stmt = Parser::new("INSERT INTO t (a, b) VALUES (1, 'hello')")
10273            .unwrap()
10274            .parse_statement()
10275            .unwrap();
10276        match stmt {
10277            Statement::Insert(ins) => {
10278                assert_eq!(ins.table.name, "t");
10279                assert_eq!(ins.columns, vec!["a", "b"]);
10280                match &ins.source {
10281                    InsertSource::Values(rows) => {
10282                        assert_eq!(rows.len(), 1);
10283                        assert_eq!(rows[0].len(), 2);
10284                    }
10285                    _ => panic!("Expected VALUES"),
10286                }
10287            }
10288            _ => panic!("Expected INSERT"),
10289        }
10290    }
10291
10292    #[test]
10293    fn test_parse_delete() {
10294        let stmt = Parser::new("DELETE FROM users WHERE id = 1")
10295            .unwrap()
10296            .parse_statement()
10297            .unwrap();
10298        match stmt {
10299            Statement::Delete(del) => {
10300                assert_eq!(del.table.name, "users");
10301                assert!(del.where_clause.is_some());
10302            }
10303            _ => panic!("Expected DELETE"),
10304        }
10305    }
10306
10307    #[test]
10308    fn test_parse_join() {
10309        let stmt = Parser::new("SELECT a.id, b.name FROM a INNER JOIN b ON a.id = b.a_id")
10310            .unwrap()
10311            .parse_statement()
10312            .unwrap();
10313        match stmt {
10314            Statement::Select(sel) => {
10315                assert_eq!(sel.joins.len(), 1);
10316                assert_eq!(sel.joins[0].join_type, JoinType::Inner);
10317            }
10318            _ => panic!("Expected SELECT"),
10319        }
10320    }
10321
10322    #[test]
10323    fn test_parse_cte() {
10324        let stmt = Parser::new("WITH cte AS (SELECT 1 AS x) SELECT x FROM cte")
10325            .unwrap()
10326            .parse_statement()
10327            .unwrap();
10328        match stmt {
10329            Statement::Select(sel) => {
10330                assert_eq!(sel.ctes.len(), 1);
10331                assert_eq!(sel.ctes[0].name, "cte");
10332            }
10333            _ => panic!("Expected SELECT"),
10334        }
10335    }
10336
10337    #[test]
10338    fn test_parse_union() {
10339        let stmt = Parser::new("SELECT 1 UNION ALL SELECT 2")
10340            .unwrap()
10341            .parse_statement()
10342            .unwrap();
10343        match stmt {
10344            Statement::SetOperation(sop) => {
10345                assert_eq!(sop.op, SetOperationType::Union);
10346                assert!(sop.all);
10347            }
10348            _ => panic!("Expected SetOperation"),
10349        }
10350    }
10351
10352    #[test]
10353    fn test_parse_cast() {
10354        let stmt = Parser::new("SELECT CAST(x AS INT) FROM t")
10355            .unwrap()
10356            .parse_statement()
10357            .unwrap();
10358        match stmt {
10359            Statement::Select(sel) => {
10360                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10361                    assert!(matches!(expr, Expr::Cast { .. }));
10362                }
10363            }
10364            _ => panic!("Expected SELECT"),
10365        }
10366    }
10367
10368    #[test]
10369    fn test_parse_subquery() {
10370        let stmt = Parser::new("SELECT * FROM (SELECT 1 AS x) AS sub")
10371            .unwrap()
10372            .parse_statement()
10373            .unwrap();
10374        match stmt {
10375            Statement::Select(sel) => {
10376                if let Some(from) = &sel.from {
10377                    assert!(matches!(from.source, TableSource::Subquery { .. }));
10378                }
10379            }
10380            _ => panic!("Expected SELECT"),
10381        }
10382    }
10383
10384    #[test]
10385    fn cr014_paren_setop_derived_table_parses() {
10386        // CR-014: a parenthesised set operation used as a derived table, where
10387        // each branch is itself parenthesised, must parse. Previously failed
10388        // with `Expected RParen, got Except/Union/Intersect`.
10389        for op in ["EXCEPT", "UNION", "UNION ALL", "INTERSECT"] {
10390            let sql = format!("SELECT count(*) FROM ((SELECT 1) {op} (SELECT 2)) x");
10391            assert!(
10392                Parser::new(&sql).unwrap().parse_statements().is_ok(),
10393                "must parse: {sql}"
10394            );
10395        }
10396    }
10397
10398    #[test]
10399    fn cr014_chained_except_derived_table_parses() {
10400        // TPC-DS q87 shape: chained EXCEPT of parenthesised branches.
10401        let sql = "SELECT count(*) FROM ((SELECT 1 AS a) EXCEPT (SELECT 2 AS a) \
10402                   EXCEPT (SELECT 3 AS a)) cool_cust";
10403        let stmt = Parser::new(sql).unwrap().parse_statement().unwrap();
10404        match stmt {
10405            Statement::Select(sel) => {
10406                let from = sel.from.expect("FROM clause present");
10407                match from.source {
10408                    TableSource::Subquery { query, alias, .. } => {
10409                        assert_eq!(alias.as_deref(), Some("cool_cust"));
10410                        assert!(matches!(*query, Statement::SetOperation(_)));
10411                    }
10412                    _ => panic!("Expected subquery derived table"),
10413                }
10414            }
10415            _ => panic!("Expected SELECT"),
10416        }
10417    }
10418
10419    #[test]
10420    fn cr014_controls_still_parse() {
10421        // Redundant nesting and no-branch-parens set-op were already OK; keep
10422        // them green. The parenthesised-join derived table must also still
10423        // parse (no regression from removing the paren-counting heuristic).
10424        for sql in [
10425            "SELECT count(*) FROM ((SELECT 1)) x",
10426            "SELECT count(*) FROM (SELECT 1 EXCEPT SELECT 2) x",
10427            "SELECT * FROM (a JOIN b ON a.id = b.id) x",
10428        ] {
10429            assert!(
10430                Parser::new(sql).unwrap().parse_statements().is_ok(),
10431                "must parse: {sql}"
10432            );
10433        }
10434    }
10435
10436    #[test]
10437    fn test_parse_exists() {
10438        let stmt = Parser::new("SELECT * FROM t WHERE EXISTS (SELECT 1 FROM t2)")
10439            .unwrap()
10440            .parse_statement()
10441            .unwrap();
10442        match stmt {
10443            Statement::Select(sel) => {
10444                assert!(sel.where_clause.is_some());
10445            }
10446            _ => panic!("Expected SELECT"),
10447        }
10448    }
10449
10450    #[test]
10451    fn test_parse_window_function() {
10452        let stmt = Parser::new(
10453            "SELECT ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) FROM emp",
10454        )
10455        .unwrap()
10456        .parse_statement()
10457        .unwrap();
10458        match stmt {
10459            Statement::Select(sel) => {
10460                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10461                    match expr {
10462                        Expr::TypedFunction { over, .. } => {
10463                            assert!(over.is_some());
10464                        }
10465                        Expr::Function { over, .. } => {
10466                            assert!(over.is_some());
10467                        }
10468                        _ => panic!("Expected function"),
10469                    }
10470                }
10471            }
10472            _ => panic!("Expected SELECT"),
10473        }
10474    }
10475
10476    #[test]
10477    fn test_parse_multiple_statements() {
10478        let stmts = Parser::new("SELECT 1; SELECT 2;")
10479            .unwrap()
10480            .parse_statements()
10481            .unwrap();
10482        assert_eq!(stmts.len(), 2);
10483    }
10484
10485    #[test]
10486    fn test_parse_insert_select() {
10487        let stmt = Parser::new("INSERT INTO t SELECT * FROM s")
10488            .unwrap()
10489            .parse_statement()
10490            .unwrap();
10491        match stmt {
10492            Statement::Insert(ins) => {
10493                assert!(matches!(ins.source, InsertSource::Query(_)));
10494            }
10495            _ => panic!("Expected INSERT"),
10496        }
10497    }
10498
10499    #[test]
10500    fn test_parse_create_table_constraints() {
10501        let stmt =
10502            Parser::new("CREATE TABLE t (id INT PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE)")
10503                .unwrap()
10504                .parse_statement()
10505                .unwrap();
10506        match stmt {
10507            Statement::CreateTable(ct) => {
10508                assert_eq!(ct.columns.len(), 2);
10509                assert!(ct.columns[0].primary_key);
10510                assert!(ct.columns[1].unique);
10511            }
10512            _ => panic!("Expected CREATE TABLE"),
10513        }
10514    }
10515
10516    #[test]
10517    fn test_parse_extract() {
10518        let stmt = Parser::new("SELECT EXTRACT(YEAR FROM created_at) FROM t")
10519            .unwrap()
10520            .parse_statement()
10521            .unwrap();
10522        match stmt {
10523            Statement::Select(sel) => {
10524                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10525                    assert!(matches!(expr, Expr::Extract { .. }));
10526                }
10527            }
10528            _ => panic!("Expected SELECT"),
10529        }
10530    }
10531
10532    #[test]
10533    fn test_parse_postgres_cast() {
10534        let stmt = Parser::new("SELECT x::int FROM t")
10535            .unwrap()
10536            .parse_statement()
10537            .unwrap();
10538        match stmt {
10539            Statement::Select(sel) => {
10540                if let SelectItem::Expr { expr, .. } = &sel.columns[0] {
10541                    assert!(matches!(expr, Expr::Cast { .. }));
10542                }
10543            }
10544            _ => panic!("Expected SELECT"),
10545        }
10546    }
10547
10548    #[test]
10549    fn test_parse_on_conflict_expression_targets() {
10550        let stmt = Parser::new(
10551            "INSERT INTO t VALUES (1, 'Crowberry') ON CONFLICT (lower(fruit) collate \"C\" text_pattern_ops, key) DO NOTHING",
10552        )
10553        .unwrap()
10554        .parse_statement()
10555        .unwrap();
10556
10557        match stmt {
10558            Statement::Insert(ins) => {
10559                let on_conflict = ins.on_conflict.expect("Expected ON CONFLICT");
10560                assert_eq!(on_conflict.columns.len(), 2);
10561                assert!(on_conflict.columns[0].starts_with("lower"));
10562                assert!(on_conflict.columns[0].contains("text_pattern_ops"));
10563                assert_eq!(on_conflict.columns[1], "key");
10564            }
10565            _ => panic!("Expected INSERT"),
10566        }
10567    }
10568
10569    #[test]
10570    fn test_parse_postgres_operator_sequences() {
10571        let cases = [
10572            "SELECT * FROM box_temp WHERE f1 <<| '(10,4.33334),(5,100)'",
10573            "SELECT * FROM box_temp WHERE f1 &<| '(10,4.3333334),(5,1)'",
10574            "SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth'",
10575        ];
10576
10577        for sql in &cases {
10578            let stmt = Parser::new(sql).unwrap().parse_statement().unwrap();
10579            assert!(matches!(stmt, Statement::Select(_)));
10580        }
10581    }
10582}
10583
10584/// Attach comments to the appropriate field on a parsed statement.
10585fn attach_comments_to_statement(stmt: &mut Statement, comments: Vec<String>) {
10586    match stmt {
10587        Statement::Select(s) => s.comments = comments,
10588        Statement::Insert(s) => s.comments = comments,
10589        Statement::Update(s) => s.comments = comments,
10590        Statement::Delete(s) => s.comments = comments,
10591        Statement::CreateTable(s) => s.comments = comments,
10592        Statement::DropTable(s) => s.comments = comments,
10593        Statement::SetOperation(s) => s.comments = comments,
10594        Statement::AlterTable(s) => s.comments = comments,
10595        Statement::CreateView(s) => s.comments = comments,
10596        Statement::DropView(s) => s.comments = comments,
10597        Statement::Truncate(s) => s.comments = comments,
10598        Statement::Explain(s) => s.comments = comments,
10599        Statement::Use(s) => s.comments = comments,
10600        Statement::Merge(s) => s.comments = comments,
10601        Statement::Command(s) => s.comments = comments,
10602        // Transaction and Expression don't have comment fields
10603        Statement::Transaction(_) | Statement::Expression(_) => {}
10604    }
10605}