Skip to main content

sqlglot_rust/parser/
sql_parser.rs

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