Skip to main content

crabka_pgparser/
parser.rs

1//! Recursive-descent statement parser with Pratt expression parsing.
2
3use std::{cell::Cell, rc::Rc};
4
5use crate::{
6    ast::{BinaryOp, Expr, UnaryOp},
7    error::ParseError,
8    lexer::lex,
9    token::{Keyword, Token},
10};
11
12/// Maximum nesting depth the parser will build before returning `54001`
13/// (`statement_too_complex`). This bounds BOTH crash modes:
14///   * mode 1 — deep parse recursion (nested parens / subqueries / CASE / NOT /
15///     unary minus, all of which funnel through `expr`/`query_expr`), and
16///   * mode 2 — a flat left-associative chain (`1+1+1+…`) whose Pratt loop is
17///     iterative but builds an N-deep left-nested AST that would overflow later
18///     in eval AND on recursive `Box` `Drop`; capping the loop iteration count
19///     stops the over-deep tree from ever being built.
20///
21/// Chosen empirically (see the `at_limit_*` crash-safety tests): the server runs
22/// on tokio's default ~2 MiB worker stack, and a query nested at `MAX_DEPTH` must
23/// parse AND evaluate without overflowing while a deeper one returns a clean
24/// error. Measured on that 2 MiB stack (both plain-debug AND llvm-cov-
25/// instrumented builds, since CI runs `cargo llvm-cov nextest`), a deeply-nested
26/// `(((…)))` paren parse — the heaviest recursion, an `expr`→`prefix`→`expr`
27/// round-trip per level — overflows the stack at a nesting depth of ~133. `50`
28/// leaves ~2.6x headroom below that ceiling; the executor's eval recursion
29/// (ceiling >12 000 on the same stack) and the AST's recursive `Box` `Drop` are
30/// nowhere near it. Real queries nest well under ~50 levels. This cap is
31/// deliberately MUCH more conservative than `PostgreSQL`'s own (far higher)
32/// `max_stack_depth` — both return `54001` for sufficiently deep input, which is
33/// what matters for closing the `DoS`.
34pub(crate) const MAX_DEPTH: usize = 50;
35
36type QueryTailAndLocking = (
37    Vec<crate::ast::OrderItem>,
38    Option<i64>,
39    Option<i64>,
40    Option<crate::ast::RowLockStrength>,
41);
42
43type SetTail = (Vec<crate::ast::OrderItem>, Option<i64>, Option<i64>);
44
45pub(crate) struct Parser {
46    toks: Vec<(Token, usize)>,
47    source: String,
48    pos: usize,
49    /// Current recursion depth of the recursive productions (`expr`,
50    /// `select_core`). Held behind an `Rc<Cell<…>>` so the RAII [`DepthGuard`]
51    /// can hold an OWNED clone of the handle rather than a borrow of `self` —
52    /// that lets the guarded method keep calling `&mut self` methods freely while
53    /// the guard is alive (a `&self.depth` borrow would conflict with `&mut self`
54    /// for the guard's whole lifetime). The guard's `Drop` decrements on EVERY
55    /// exit path, including a `?` early-return, so the depth is always restored.
56    depth: Rc<Cell<usize>>,
57}
58
59#[derive(Debug, Clone, PartialEq)]
60struct ParsedStatement {
61    statement: crate::ast::Statement,
62    command_identity: crate::command::CommandIdentity,
63}
64
65fn emitted(
66    command_identity: crate::command::CommandIdentity,
67    statement: Result<crate::ast::Statement, ParseError>,
68) -> Result<ParsedStatement, ParseError> {
69    statement.map(|statement| ParsedStatement {
70        statement,
71        command_identity,
72    })
73}
74
75/// RAII depth counter: increments the shared depth `Cell` on construction and
76/// decrements it on `Drop` (so a `?` early-return still restores the count).
77/// Holds an owned `Rc` clone, so it does not borrow the `Parser` and never fights
78/// the borrow checker with the `&mut self` method calls in the guarded body.
79struct DepthGuard {
80    depth: Rc<Cell<usize>>,
81}
82
83impl DepthGuard {
84    /// Enter one recursion level, erroring with `54001` if it would exceed
85    /// `MAX_DEPTH`. On error the guard is NOT created (the count is not bumped for
86    /// a frame that never ran); the caller returns the error immediately.
87    fn enter(depth: &Rc<Cell<usize>>, position: usize) -> Result<Self, ParseError> {
88        let next = depth.get() + 1;
89        if next > MAX_DEPTH {
90            return Err(ParseError::too_deep(position));
91        }
92        depth.set(next);
93        Ok(Self {
94            depth: Rc::clone(depth),
95        })
96    }
97}
98
99impl Drop for DepthGuard {
100    fn drop(&mut self) {
101        self.depth.set(self.depth.get() - 1);
102    }
103}
104
105impl Parser {
106    pub(crate) fn new(toks: Vec<(Token, usize)>, source: String) -> Self {
107        Self {
108            toks,
109            source,
110            pos: 0,
111            depth: Rc::new(Cell::new(0)),
112        }
113    }
114
115    fn peek(&self) -> &Token {
116        &self.toks[self.pos].0
117    }
118
119    /// The token *after* the current one (saturates at EOF). Used for the SP28
120    /// two-token lookahead that disambiguates infix `NOT IN`/`NOT BETWEEN`/
121    /// `NOT LIKE` from the prefix `NOT` operator.
122    fn peek2(&self) -> &Token {
123        let i = (self.pos + 1).min(self.toks.len() - 1);
124        &self.toks[i].0
125    }
126
127    /// The token `n` positions ahead of the current one (saturates at EOF).
128    fn peek_n(&self, n: usize) -> &Token {
129        let i = (self.pos + n).min(self.toks.len() - 1);
130        &self.toks[i].0
131    }
132
133    /// The token two positions after the current one (saturates at EOF). Used by
134    /// the SP37 `AT TIME ZONE` postfix, whose three-token lead-in (`at time zone`)
135    /// needs a three-token lookahead so a bare column named `at` is never mistaken
136    /// for the operator.
137    fn peek3(&self) -> &Token {
138        let i = (self.pos + 2).min(self.toks.len() - 1);
139        &self.toks[i].0
140    }
141
142    fn peek_pos(&self) -> usize {
143        self.toks[self.pos].1
144    }
145
146    fn bump(&mut self) -> Token {
147        let t = self.toks[self.pos].0.clone();
148        if self.pos + 1 < self.toks.len() {
149            self.pos += 1;
150        }
151        t
152    }
153
154    fn eat_keyword(&mut self, kw: Keyword) -> bool {
155        if *self.peek() == Token::Keyword(kw) {
156            self.bump();
157            true
158        } else {
159            false
160        }
161    }
162
163    fn expect(&mut self, want: &Token) -> Result<(), ParseError> {
164        if self.peek() == want {
165            self.bump();
166            Ok(())
167        } else {
168            Err(ParseError::new(
169                format!("expected {want:?}, found {:?}", self.peek()),
170                self.peek_pos(),
171            ))
172        }
173    }
174
175    fn expect_ident(&mut self) -> Result<String, ParseError> {
176        match self.bump() {
177            Token::Ident(s) => Ok(s),
178            other => Err(ParseError::new(
179                format!("expected identifier, found {other:?}"),
180                self.peek_pos(),
181            )),
182        }
183    }
184
185    /// Consume an identifier that must equal `want` (case-insensitively). Used by
186    /// the SP37 keyword-free multi-word type fold (`time`/`zone`) so those words
187    /// stay non-reserved (still usable as identifiers elsewhere).
188    fn expect_ident_eq(&mut self, want: &str) -> Result<(), ParseError> {
189        let pos = self.peek_pos();
190        match self.bump() {
191            Token::Ident(s) if s.eq_ignore_ascii_case(want) => Ok(()),
192            other => Err(ParseError::new(
193                format!("expected `{want}`, found {other:?}"),
194                pos,
195            )),
196        }
197    }
198
199    fn eat_ident_eq(&mut self, want: &str) -> bool {
200        if matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case(want)) {
201            self.bump();
202            true
203        } else {
204            false
205        }
206    }
207
208    /// Parse a SQL type name into a [`ColumnType`] — shared by `CREATE TABLE`
209    /// column definitions and the SP31 cast target (`CAST(_ AS ty)` / `_::ty`).
210    /// Folds the two-word `double precision` (SP30) into one normalized name; an
211    /// unknown type name is a 42601 parse error (`PostgreSQL`: 42704 — a documented
212    /// deviation, consistent with the column-type path).
213    fn parse_type_name(&mut self) -> Result<crabka_pgtypes::ColumnType, ParseError> {
214        let type_pos = self.peek_pos();
215        let mut type_word = self.expect_ident()?;
216        if type_word.eq_ignore_ascii_case("double")
217            && matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("precision"))
218        {
219            self.bump();
220            type_word = "double precision".to_string();
221        }
222        if type_word.eq_ignore_ascii_case("character")
223            && matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("varying"))
224        {
225            self.bump();
226            type_word = "character varying".to_string();
227        }
228        // SP37: fold the multi-word `timestamp`/`time` { with | without } `time zone`
229        // spellings into one normalized name (keyword-free — the lexer lowercases
230        // idents, so the three trailing words are matched as plain `Token::Ident`s).
231        // `timestamp with time zone` / `timestamp without time zone` /
232        // `time with time zone` / `time without time zone`.
233        if (type_word.eq_ignore_ascii_case("timestamp") || type_word.eq_ignore_ascii_case("time"))
234            && (matches!(self.peek(), Token::Keyword(Keyword::With))
235                || matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("without")))
236            && matches!(self.peek2(), Token::Ident(w) if w.eq_ignore_ascii_case("time"))
237        {
238            // Consume `{with|without}`; require `time` `zone` to follow.
239            let with_zone = matches!(self.bump(), Token::Keyword(Keyword::With));
240            self.expect_ident_eq("time")?;
241            self.expect_ident_eq("zone")?;
242            let qualifier = if with_zone { "with" } else { "without" };
243            type_word = format!("{} {qualifier} time zone", type_word.to_ascii_lowercase());
244        }
245        let ty = crabka_pgtypes::ColumnType::from_sql_name(&type_word)
246            .ok_or_else(|| ParseError::new(format!("unknown type \"{type_word}\""), type_pos))?;
247        // SP32: `numeric`/`decimal` may carry a `(precision[, scale])` modifier.
248        if ty.is_numeric() && *self.peek() == Token::LParen {
249            return self.parse_numeric_typmod();
250        }
251        if matches!(
252            ty,
253            crabka_pgtypes::ColumnType::Varchar(_) | crabka_pgtypes::ColumnType::Char(_)
254        ) && *self.peek() == Token::LParen
255        {
256            return self.parse_string_typmod(ty);
257        }
258        Ok(ty)
259    }
260
261    fn parse_string_typmod(
262        &mut self,
263        ty: crabka_pgtypes::ColumnType,
264    ) -> Result<crabka_pgtypes::ColumnType, ParseError> {
265        self.expect(&Token::LParen)?;
266        let limit = self.expect_u16("string length")?;
267        self.expect(&Token::RParen)?;
268        match ty {
269            crabka_pgtypes::ColumnType::Varchar(_) => {
270                Ok(crabka_pgtypes::ColumnType::Varchar(Some(limit)))
271            }
272            crabka_pgtypes::ColumnType::Char(_) => {
273                Ok(crabka_pgtypes::ColumnType::Char(Some(limit)))
274            }
275            _ => unreachable!("parse_string_typmod called for non-string typmod type"),
276        }
277    }
278
279    /// Parse a `numeric(precision[, scale])` modifier, positioned at `(`. `scale`
280    /// defaults to 0 (`PostgreSQL` `numeric(p)` ≡ `numeric(p, 0)`).
281    fn parse_numeric_typmod(&mut self) -> Result<crabka_pgtypes::ColumnType, ParseError> {
282        self.expect(&Token::LParen)?;
283        let precision = self.expect_u16("numeric precision")?;
284        let scale = if self.eat_comma() {
285            self.expect_u16("numeric scale")?
286        } else {
287            0
288        };
289        self.expect(&Token::RParen)?;
290        Ok(crabka_pgtypes::ColumnType::Numeric(Some(
291            crabka_pgtypes::numeric::Typmod { precision, scale },
292        )))
293    }
294
295    /// Parse a small unsigned integer literal (a `numeric` precision/scale).
296    fn expect_u16(&mut self, what: &str) -> Result<u16, ParseError> {
297        let pos = self.peek_pos();
298        match self.bump() {
299            Token::IntLit(s) => s
300                .parse::<u16>()
301                .map_err(|_| ParseError::new(format!("invalid {what}"), pos)),
302            other => Err(ParseError::new(
303                format!("expected {what}, found {other:?}"),
304                pos,
305            )),
306        }
307    }
308
309    /// Pratt expression parser. `min_bp` is the minimum left binding power.
310    pub(crate) fn expr(&mut self, min_bp: u8) -> Result<Expr, ParseError> {
311        // Mode-1 guard: every recursive expression production (parens, NOT, unary
312        // minus, CASE, CAST, IN-list, BETWEEN, LIKE, function args, subqueries)
313        // funnels back through `expr`, so bounding the recursion depth here caps
314        // all of them. The RAII guard decrements on every exit path, `?` included.
315        let _guard = DepthGuard::enter(&self.depth, self.peek_pos())?;
316        let mut lhs = self.prefix()?;
317        // Mode-2 guard: the Pratt loop is iterative, but each iteration adds one
318        // level of left-nesting to the result tree (`1+1+1+…`). Capping the
319        // iteration count caps the built tree's depth, so it can never grow deep
320        // enough to overflow eval/fold/router-walk or recursive `Box` `Drop`.
321        let mut iterations: usize = 0;
322        loop {
323            iterations += 1;
324            if iterations > MAX_DEPTH {
325                return Err(ParseError::too_deep(self.peek_pos()));
326            }
327            // SP31: `::` is the tightest-binding operator (tighter than unary
328            // minus and every arithmetic/comparison operator), so it is consumed
329            // unconditionally here — no `min_bp` gate — and left-associatively
330            // (`a::int::text` == `(a::int)::text`). `-2::int` still parses as
331            // `-(2::int)` because the unary-minus prefix recurses into `expr`,
332            // whose innermost frame grabs the `::` before the minus is applied.
333            if *self.peek() == Token::TypeCast {
334                self.bump();
335                let ty = self.parse_type_name()?;
336                lhs = Expr::Cast {
337                    expr: Box::new(lhs),
338                    ty,
339                };
340                continue;
341            }
342            // SP37: `x AT TIME ZONE z` — a postfix operator that lowers onto PG's
343            // internal `timezone(z, x)` form (note arg ORDER: zone first, value
344            // second). It binds TIGHTER than every binary operator (so
345            // `ts AT TIME ZONE 'UTC' = y` groups as `(ts AT TIME ZONE 'UTC') = y`),
346            // so — like `::` — it is consumed unconditionally (no `min_bp` gate).
347            // Keyword-free: `at`/`time`/`zone` are matched as lowercased idents via
348            // a three-token lookahead, so a bare column named `at` is never the
349            // operator. The zone operand is parsed at bp 11 (a high-precedence
350            // operand, like the `*`/`/` level), and recursion terminates because
351            // each iteration consumes the `at time zone` lead-in before recursing.
352            if matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("at"))
353                && matches!(self.peek2(), Token::Ident(w) if w.eq_ignore_ascii_case("time"))
354                && matches!(self.peek3(), Token::Ident(w) if w.eq_ignore_ascii_case("zone"))
355            {
356                self.bump(); // at
357                self.bump(); // time
358                self.bump(); // zone
359                let zone = self.expr(11)?;
360                lhs = Expr::Func(crate::ast::FuncCall {
361                    name: "timezone".into(),
362                    distinct: false,
363                    args: crate::ast::FuncArgs::Exprs(vec![zone, lhs]),
364                });
365                continue;
366            }
367            // SP28: postfix predicates (IS [NOT] NULL, [NOT] IN, [NOT] BETWEEN,
368            // [NOT] LIKE/ILIKE) bind at the comparison level (l_bp = 5). They are
369            // handled before the binary-operator match so `a = 1 AND b IN (1,2)`
370            // groups as `(a=1) AND (b IN (1,2))`.
371            if 5 >= min_bp {
372                match self.peek() {
373                    Token::Keyword(Keyword::Is) => {
374                        lhs = self.parse_is_null(lhs)?;
375                        continue;
376                    }
377                    Token::Keyword(Keyword::In) => {
378                        lhs = self.parse_in(lhs, false)?;
379                        continue;
380                    }
381                    Token::Keyword(Keyword::Between) => {
382                        lhs = self.parse_between(lhs, false)?;
383                        continue;
384                    }
385                    Token::Keyword(Keyword::Like) => {
386                        lhs = self.parse_like(lhs, false, false)?;
387                        continue;
388                    }
389                    Token::Keyword(Keyword::Ilike) => {
390                        lhs = self.parse_like(lhs, false, true)?;
391                        continue;
392                    }
393                    // Infix `NOT` only when it leads a negated predicate
394                    // (`x NOT IN/BETWEEN/LIKE/ILIKE …`); otherwise `NOT` is the
395                    // prefix operator handled in `prefix`. Two-token lookahead.
396                    Token::Keyword(Keyword::Not)
397                        if matches!(
398                            self.peek2(),
399                            Token::Keyword(
400                                Keyword::In | Keyword::Between | Keyword::Like | Keyword::Ilike
401                            )
402                        ) =>
403                    {
404                        self.bump(); // NOT
405                        lhs = match self.peek() {
406                            Token::Keyword(Keyword::In) => self.parse_in(lhs, true)?,
407                            Token::Keyword(Keyword::Between) => self.parse_between(lhs, true)?,
408                            Token::Keyword(Keyword::Like) => self.parse_like(lhs, true, false)?,
409                            Token::Keyword(Keyword::Ilike) => self.parse_like(lhs, true, true)?,
410                            _ => unreachable!("lookahead guaranteed a negated predicate"),
411                        };
412                        continue;
413                    }
414                    _ => {}
415                }
416            }
417            // SP29 inserts `||` (BinaryOp::Concat) between the comparison level
418            // (5/6) and the additive level: like PostgreSQL, `||` binds TIGHTER
419            // than `< > = <= >= <>`, `BETWEEN/IN/LIKE`, `AND`/`OR` but LOOSER than
420            // `+ - * /`. So `+ - * /` and the unary-minus operand power shift up by
421            // two to make room (odd l_bp / even r_bp preserved).
422            let (op, l_bp, r_bp) = match self.peek() {
423                Token::Keyword(Keyword::Or) => (BinaryOp::Or, 1, 2),
424                Token::Keyword(Keyword::And) => (BinaryOp::And, 3, 4),
425                Token::Eq => (BinaryOp::Eq, 5, 6),
426                Token::Ne => (BinaryOp::Ne, 5, 6),
427                Token::Lt => (BinaryOp::Lt, 5, 6),
428                Token::Le => (BinaryOp::Le, 5, 6),
429                Token::Gt => (BinaryOp::Gt, 5, 6),
430                Token::Ge => (BinaryOp::Ge, 5, 6),
431                Token::Concat => (BinaryOp::Concat, 7, 8),
432                Token::Plus => (BinaryOp::Add, 9, 10),
433                Token::Minus => (BinaryOp::Sub, 9, 10),
434                Token::Star => (BinaryOp::Mul, 11, 12),
435                Token::Slash => (BinaryOp::Div, 11, 12),
436                _ => break,
437            };
438            if l_bp < min_bp {
439                break;
440            }
441            self.bump();
442            // SP34: `op ANY|SOME|ALL ( SELECT … )` — a quantified comparison. Only
443            // the comparison operators take a quantifier (PostgreSQL).
444            if matches!(
445                op,
446                BinaryOp::Eq
447                    | BinaryOp::Ne
448                    | BinaryOp::Lt
449                    | BinaryOp::Le
450                    | BinaryOp::Gt
451                    | BinaryOp::Ge
452            ) && matches!(
453                self.peek(),
454                Token::Keyword(Keyword::Any | Keyword::Some | Keyword::All)
455            ) {
456                let all = matches!(self.peek(), Token::Keyword(Keyword::All));
457                self.bump(); // ANY / SOME / ALL
458                self.expect(&Token::LParen)?;
459                let subquery = Box::new(self.query_expr_after_open_paren()?);
460                lhs = Expr::Quantified {
461                    expr: Box::new(lhs),
462                    op,
463                    all,
464                    subquery,
465                };
466                continue;
467            }
468            let rhs = self.expr(r_bp)?;
469            lhs = Expr::Binary {
470                op,
471                left: Box::new(lhs),
472                right: Box::new(rhs),
473            };
474        }
475        Ok(lhs)
476    }
477
478    fn prefix(&mut self) -> Result<Expr, ParseError> {
479        match self.peek().clone() {
480            Token::Keyword(Keyword::Not) => {
481                self.bump();
482                Ok(Expr::Unary {
483                    op: UnaryOp::Not,
484                    expr: Box::new(self.expr(4)?),
485                })
486            }
487            Token::Minus => {
488                self.bump();
489                // Unary minus binds tighter than `* /` (now 11/12), so its operand
490                // is parsed above that level (13) — `-a * b` stays `(-a) * b`.
491                Ok(Expr::Unary {
492                    op: UnaryOp::Neg,
493                    expr: Box::new(self.expr(13)?),
494                })
495            }
496            Token::LParen => {
497                // SP34: `( SELECT … )` is a scalar subquery; anything else is a
498                // parenthesised (grouping) expression.
499                if matches!(
500                    self.peek2(),
501                    Token::Keyword(Keyword::Select | Keyword::Values | Keyword::With)
502                ) {
503                    self.bump();
504                    let sub = self.query_expr_after_open_paren()?;
505                    Ok(Expr::ScalarSubquery(Box::new(sub)))
506                } else {
507                    self.bump();
508                    let e = self.expr(0)?;
509                    self.expect(&Token::RParen)?;
510                    Ok(e)
511                }
512            }
513            Token::Keyword(Keyword::Exists) => {
514                self.bump(); // EXISTS
515                self.expect(&Token::LParen)?;
516                let sub = self.query_expr_after_open_paren()?;
517                Ok(Expr::Exists(Box::new(sub)))
518            }
519            Token::IntLit(s) => {
520                self.bump();
521                Ok(Expr::IntLiteral(s))
522            }
523            Token::FloatLit(s) => {
524                self.bump();
525                Ok(Expr::NumericLiteral(s))
526            }
527            Token::StringLit(s) => {
528                self.bump();
529                Ok(Expr::StringLiteral(s))
530            }
531            Token::Keyword(Keyword::True) => {
532                self.bump();
533                Ok(Expr::BoolLiteral(true))
534            }
535            Token::Keyword(Keyword::False) => {
536                self.bump();
537                Ok(Expr::BoolLiteral(false))
538            }
539            Token::Keyword(Keyword::Null) => {
540                self.bump();
541                Ok(Expr::NullLiteral)
542            }
543            Token::Keyword(Keyword::Case) => self.case_expr(),
544            Token::Keyword(Keyword::Cast) => self.cast_expr(),
545            Token::Keyword(Keyword::CurrentUser) => {
546                self.bump();
547                Ok(Expr::Func(crate::ast::FuncCall {
548                    name: "current_user".into(),
549                    distinct: false,
550                    args: crate::ast::FuncArgs::Exprs(vec![]),
551                }))
552            }
553            // `left`/`right` are PostgreSQL scalar functions AND (LEFT/RIGHT) join
554            // keywords. In expression position they are valid only as a function
555            // call — `left(s, n)` / `right(s, n)` — so route them to `func_call`.
556            Token::Keyword(Keyword::Left) => self.keyword_func_call("left"),
557            Token::Keyword(Keyword::Right) => self.keyword_func_call("right"),
558            Token::Param(n) => {
559                self.bump();
560                Ok(Expr::Param(n))
561            }
562            Token::Ident(s) => {
563                // SP37: a typed datetime literal — `DATE '…'` / `TIME '…'` /
564                // `TIMESTAMP '…'` / `TIMESTAMPTZ '…'` / `INTERVAL '…'` — lowers onto
565                // an explicit cast of a string literal. Only the single-word
566                // spellings are typed-literal prefixes (multi-word `TIMESTAMP WITH
567                // TIME ZONE '…'` is out of scope — use `'…'::timestamptz`). This is
568                // checked BEFORE the function-call path so `date('…')` is not
569                // shadowed (a typed literal has NO parenthesis — `peek2` is the
570                // string literal, not `(`).
571                let lower = s.to_ascii_lowercase();
572                if matches!(
573                    lower.as_str(),
574                    "date" | "time" | "timestamp" | "timestamptz" | "interval"
575                ) && matches!(self.peek2(), Token::StringLit(_))
576                {
577                    self.bump(); // the type-name ident
578                    let ty = crabka_pgtypes::ColumnType::from_sql_name(&lower)
579                        .expect("single-word datetime type name resolves");
580                    let Token::StringLit(string) = self.bump() else {
581                        unreachable!("peek2 guaranteed a string literal");
582                    };
583                    return Ok(Expr::Cast {
584                        expr: Box::new(Expr::StringLiteral(string)),
585                        ty,
586                    });
587                }
588                self.bump();
589                // SP37: niladic keyword functions — `current_date`, `current_time`,
590                // `localtimestamp`, `localtime`, `current_timestamp` — have NO
591                // parentheses. When one of these names is NOT followed by `(`, build
592                // a zero-arg `Func` call (the executor resolves it against the session
593                // clock/zone). These names are effectively reserved in PostgreSQL, so
594                // shadowing a column of the same name is acceptable. The paren forms
595                // (`now()`, `current_timestamp(0)`, etc.) fall through to `func_call`.
596                if matches!(
597                    lower.as_str(),
598                    "current_date"
599                        | "current_time"
600                        | "session_user"
601                        | "localtimestamp"
602                        | "localtime"
603                        | "current_timestamp"
604                ) && *self.peek() != Token::LParen
605                {
606                    return Ok(Expr::Func(crate::ast::FuncCall {
607                        name: lower,
608                        distinct: false,
609                        args: crate::ast::FuncArgs::Exprs(vec![]),
610                    }));
611                }
612                // SP37: `EXTRACT(field FROM source)` — a special call form that
613                // lowers onto `extract('<field>', source)` (field lowercased to a
614                // string literal). Checked before the generic comma-arg `func_call`
615                // so the `FROM` keyword inside the parens is not mis-parsed.
616                if lower == "extract" && *self.peek() == Token::LParen {
617                    return self.extract_expr();
618                }
619                // SP27: `ident (` is a function call; a bare ident is a column.
620                // SP33/F-2: `ident . ident` is either a schema-qualified function
621                // call (`pg_catalog.format_type(...)`) or a table-qualified column.
622                if *self.peek() == Token::LParen {
623                    self.func_call(s)
624                } else if *self.peek() == Token::Dot {
625                    self.bump();
626                    let name = self.expect_ident()?;
627                    if *self.peek() == Token::LParen {
628                        self.func_call(name)
629                    } else {
630                        Ok(Expr::Column {
631                            table: Some(s),
632                            name,
633                        })
634                    }
635                } else {
636                    Ok(Expr::Column {
637                        table: None,
638                        name: s,
639                    })
640                }
641            }
642            other => Err(ParseError::new(
643                format!("unexpected token {other:?}"),
644                self.peek_pos(),
645            )),
646        }
647    }
648
649    /// Parse a function call after its name `ident`, positioned at `(`.
650    /// `f(*)` yields [`FuncArgs::Star`]; `DISTINCT`/`ALL` may lead the argument
651    /// list; otherwise a (possibly empty) comma-separated expression list.
652    fn func_call(&mut self, name: String) -> Result<Expr, ParseError> {
653        use crate::ast::{FuncArgs, FuncCall};
654        self.expect(&Token::LParen)?;
655        // `f(*)` — the star form (no DISTINCT, no other args).
656        if *self.peek() == Token::Star {
657            self.bump();
658            self.expect(&Token::RParen)?;
659            return Ok(Expr::Func(FuncCall {
660                name,
661                distinct: false,
662                args: FuncArgs::Star,
663            }));
664        }
665        let distinct = if self.eat_keyword(Keyword::Distinct) {
666            true
667        } else {
668            // ALL is the default modifier; accept and ignore it.
669            self.eat_keyword(Keyword::All);
670            false
671        };
672        let mut args = Vec::new();
673        if *self.peek() != Token::RParen {
674            loop {
675                args.push(self.expr(0)?);
676                if self.eat_comma() {
677                    continue;
678                }
679                break;
680            }
681        }
682        self.expect(&Token::RParen)?;
683        Ok(Expr::Func(FuncCall {
684            name,
685            distinct,
686            args: FuncArgs::Exprs(args),
687        }))
688    }
689
690    /// A keyword that doubles as a scalar function name (`left`/`right`, which are
691    /// also join keywords) used in expression position: positioned at the keyword,
692    /// it is valid only as a function call `kw (`.
693    fn keyword_func_call(&mut self, name: &str) -> Result<Expr, ParseError> {
694        self.bump();
695        if *self.peek() == Token::LParen {
696            self.func_call(name.to_string())
697        } else {
698            Err(ParseError::new(
699                format!("`{name}` is reserved here; use it as a function call `{name}(...)`"),
700                self.peek_pos(),
701            ))
702        }
703    }
704
705    /// `EXTRACT(field FROM source)` — positioned at `(`, after the `extract` ident.
706    /// Lowers onto `PostgreSQL`'s internal `extract('<field>', source)` form: the
707    /// field is an identifier (lowercased to a string literal), the source is a
708    /// full expression. The executor resolves the field at runtime.
709    fn extract_expr(&mut self) -> Result<Expr, ParseError> {
710        use crate::ast::{FuncArgs, FuncCall};
711        self.expect(&Token::LParen)?;
712        let field = self.expect_ident()?.to_ascii_lowercase();
713        self.expect(&Token::Keyword(Keyword::From))?;
714        let source = self.expr(0)?;
715        self.expect(&Token::RParen)?;
716        Ok(Expr::Func(FuncCall {
717            name: "extract".into(),
718            distinct: false,
719            args: FuncArgs::Exprs(vec![Expr::StringLiteral(field), source]),
720        }))
721    }
722
723    /// `expr IS [NOT] NULL`, positioned at `IS`. (`IS TRUE`/`IS DISTINCT FROM`
724    /// are out of scope — anything but `NULL` after `IS`/`IS NOT` is a 42601.)
725    fn parse_is_null(&mut self, lhs: Expr) -> Result<Expr, ParseError> {
726        self.expect(&Token::Keyword(Keyword::Is))?;
727        let negated = self.eat_keyword(Keyword::Not);
728        self.expect(&Token::Keyword(Keyword::Null))?;
729        Ok(Expr::IsNull {
730            expr: Box::new(lhs),
731            negated,
732        })
733    }
734
735    /// `expr [NOT] IN (e1, e2, …)` or `expr [NOT] IN (SELECT …)`, positioned at
736    /// `IN`. The value-list form has ≥1 element (`IN ()` is a 42601, matching
737    /// `PostgreSQL`); the `SELECT` form (SP34) is a single-column subquery.
738    fn parse_in(&mut self, lhs: Expr, negated: bool) -> Result<Expr, ParseError> {
739        self.expect(&Token::Keyword(Keyword::In))?;
740        self.expect(&Token::LParen)?;
741        // SP34: `IN ( SELECT … )` is a subquery; otherwise a value list.
742        if matches!(
743            self.peek(),
744            Token::Keyword(Keyword::Select | Keyword::Values | Keyword::With)
745        ) {
746            let subquery = self.query_expr_after_open_paren()?;
747            return Ok(Expr::InSubquery {
748                expr: Box::new(lhs),
749                subquery: Box::new(subquery),
750                negated,
751            });
752        }
753        let mut list = Vec::new();
754        loop {
755            list.push(self.expr(0)?);
756            if self.eat_comma() {
757                continue;
758            }
759            break;
760        }
761        self.expect(&Token::RParen)?;
762        Ok(Expr::InList {
763            expr: Box::new(lhs),
764            list,
765            negated,
766        })
767    }
768
769    /// `expr [NOT] BETWEEN low AND high`, positioned at `BETWEEN`. The bounds are
770    /// parsed at `min_bp = 4` so the separating `AND` (left bp 3) is NOT consumed
771    /// as a boolean `AND`; thus `a BETWEEN 1 AND 2 AND b` → `(a BETWEEN 1 AND 2) AND b`.
772    fn parse_between(&mut self, lhs: Expr, negated: bool) -> Result<Expr, ParseError> {
773        self.expect(&Token::Keyword(Keyword::Between))?;
774        let low = self.expr(4)?;
775        self.expect(&Token::Keyword(Keyword::And))?;
776        let high = self.expr(4)?;
777        Ok(Expr::Between {
778            expr: Box::new(lhs),
779            low: Box::new(low),
780            high: Box::new(high),
781            negated,
782        })
783    }
784
785    /// `expr [NOT] LIKE pat` / `[NOT] ILIKE pat`, positioned at `LIKE`/`ILIKE`.
786    /// The pattern is parsed at `min_bp = 6` (the right bp of the comparison
787    /// level) so it stays a single comparand and does not swallow a trailing
788    /// `AND`/`OR`.
789    fn parse_like(
790        &mut self,
791        lhs: Expr,
792        negated: bool,
793        case_insensitive: bool,
794    ) -> Result<Expr, ParseError> {
795        self.bump(); // LIKE or ILIKE
796        let pattern = self.expr(6)?;
797        Ok(Expr::Like {
798            expr: Box::new(lhs),
799            pattern: Box::new(pattern),
800            negated,
801            case_insensitive,
802        })
803    }
804
805    /// A `CASE` expression. Simple form (`CASE x WHEN v THEN r …`) carries an
806    /// operand; searched form (`CASE WHEN cond THEN r …`) does not. At least one
807    /// `WHEN` is required; `ELSE` is optional.
808    fn case_expr(&mut self) -> Result<Expr, ParseError> {
809        self.expect(&Token::Keyword(Keyword::Case))?;
810        let operand = if *self.peek() == Token::Keyword(Keyword::When) {
811            None
812        } else {
813            Some(Box::new(self.expr(0)?))
814        };
815        let mut whens = Vec::new();
816        while self.eat_keyword(Keyword::When) {
817            let cond = self.expr(0)?;
818            self.expect(&Token::Keyword(Keyword::Then))?;
819            let result = self.expr(0)?;
820            whens.push((cond, result));
821        }
822        if whens.is_empty() {
823            return Err(ParseError::new(
824                "CASE requires at least one WHEN clause",
825                self.peek_pos(),
826            ));
827        }
828        let else_result = if self.eat_keyword(Keyword::Else) {
829            Some(Box::new(self.expr(0)?))
830        } else {
831            None
832        };
833        self.expect(&Token::Keyword(Keyword::End))?;
834        Ok(Expr::Case {
835            operand,
836            whens,
837            else_result,
838        })
839    }
840
841    /// `CAST(expr AS type)` — positioned at `CAST`. The functional spelling of
842    /// the `::` operator; the inner expression is parsed at the lowest precedence
843    /// (it is delimited by the surrounding parens).
844    fn cast_expr(&mut self) -> Result<Expr, ParseError> {
845        self.expect(&Token::Keyword(Keyword::Cast))?;
846        self.expect(&Token::LParen)?;
847        let expr = self.expr(0)?;
848        self.expect(&Token::Keyword(Keyword::As))?;
849        let ty = self.parse_type_name()?;
850        self.expect(&Token::RParen)?;
851        Ok(Expr::Cast {
852            expr: Box::new(expr),
853            ty,
854        })
855    }
856
857    /// Pairs each statement with the byte range of its source in
858    /// the original input — from its first token's offset up to the trailing `;`
859    /// (or end of input). Powers [`parse_with_source`].
860    fn program_spanned(
861        &mut self,
862    ) -> Result<Vec<(ParsedStatement, std::ops::Range<usize>)>, ParseError> {
863        let mut stmts = Vec::new();
864        loop {
865            while *self.peek() == Token::Semicolon {
866                self.bump();
867            }
868            if *self.peek() == Token::Eof {
869                break;
870            }
871            let start = self.peek_pos();
872            let s = self.statement()?;
873            let end = self.peek_pos();
874            stmts.push((s, start..end));
875            match self.peek() {
876                Token::Semicolon => {
877                    self.bump();
878                }
879                Token::Eof => break,
880                other => {
881                    return Err(ParseError::new(
882                        format!("expected ; or end of input, found {other:?}"),
883                        self.peek_pos(),
884                    ));
885                }
886            }
887        }
888        Ok(stmts)
889    }
890
891    fn statement(&mut self) -> Result<ParsedStatement, ParseError> {
892        use crate::command::CommandIdentity as I;
893
894        if self.starts_query_expr() {
895            let identity = self.query_command_identity();
896            return emitted(identity, self.query_statement());
897        }
898        match self.peek() {
899            Token::Keyword(Keyword::Create) => {
900                // SP40: lookahead dispatch for FDW DDL vs CREATE TABLE
901                match self.peek2() {
902                    Token::Keyword(
903                        Keyword::Index | Keyword::Unique | Keyword::Global | Keyword::Local,
904                    ) => emitted(I::CreateIndex, self.create_index()),
905                    Token::Keyword(Keyword::View) => emitted(I::CreateView, self.create_view()),
906                    Token::Keyword(Keyword::Foreign) => {
907                        // CREATE FOREIGN ... — look at the third token
908                        match self.peek3() {
909                            Token::Keyword(Keyword::Table) => {
910                                emitted(I::CreateForeignTable, self.create_foreign_table())
911                            }
912                            Token::Keyword(Keyword::Data) => {
913                                emitted(I::CreateForeignDataWrapper, self.create_fdw())
914                            }
915                            _ => Err(ParseError::new(
916                                format!(
917                                    "unexpected token after CREATE FOREIGN: {:?}",
918                                    self.peek3()
919                                ),
920                                self.peek_pos(),
921                            )),
922                        }
923                    }
924                    Token::Keyword(Keyword::Server) => {
925                        emitted(I::CreateServer, self.create_server())
926                    }
927                    Token::Keyword(Keyword::User) => {
928                        if matches!(self.peek3(), Token::Keyword(Keyword::Mapping)) {
929                            emitted(I::CreateUserMapping, self.create_user_mapping())
930                        } else {
931                            emitted(I::CreateUser, self.create_role(true))
932                        }
933                    }
934                    Token::Ident(s) if s == "role" => {
935                        emitted(I::CreateRole, self.create_role(false))
936                    }
937                    Token::Ident(s) if s == "sequence" => {
938                        emitted(I::CreateSequence, self.create_sequence())
939                    }
940                    Token::Ident(s) if s == "database" => {
941                        emitted(I::CreateDatabase, self.create_database_refusal())
942                    }
943                    _ => emitted(I::CreateTable, self.create_table()),
944                }
945            }
946            Token::Keyword(Keyword::Drop) => {
947                // SP40: lookahead dispatch for FDW DDL vs DROP TABLE.
948                // Each drop function handles its own `IF EXISTS` via `eat_if_exists`.
949                match self.peek2() {
950                    Token::Keyword(Keyword::Foreign) => {
951                        // DROP FOREIGN ... — look at the third token to distinguish
952                        // DROP FOREIGN DATA WRAPPER from DROP FOREIGN TABLE.
953                        match self.peek3() {
954                            Token::Keyword(Keyword::Table) => {
955                                emitted(I::DropForeignTable, self.drop_foreign_table())
956                            }
957                            Token::Keyword(Keyword::Data) => {
958                                emitted(I::DropForeignDataWrapper, self.drop_fdw())
959                            }
960                            _ => Err(ParseError::new(
961                                format!("unexpected token after DROP FOREIGN: {:?}", self.peek3()),
962                                self.peek_pos(),
963                            )),
964                        }
965                    }
966                    Token::Keyword(Keyword::Server) => emitted(I::DropServer, self.drop_server()),
967                    Token::Keyword(Keyword::View) => emitted(I::DropView, self.drop_view()),
968                    Token::Keyword(Keyword::Index) => emitted(I::DropIndex, self.drop_index()),
969                    Token::Keyword(Keyword::User) => {
970                        if matches!(self.peek3(), Token::Keyword(Keyword::Mapping)) {
971                            emitted(I::DropUserMapping, self.drop_user_mapping())
972                        } else {
973                            emitted(I::DropUser, self.drop_role())
974                        }
975                    }
976                    Token::Ident(s) if s == "role" => emitted(I::DropRole, self.drop_role()),
977                    Token::Ident(s) if s == "sequence" => {
978                        emitted(I::DropSequence, self.drop_sequence())
979                    }
980                    Token::Ident(s) if s == "database" => {
981                        emitted(I::DropDatabase, self.drop_database_refusal())
982                    }
983                    Token::Ident(s) if s == "extension" => {
984                        emitted(I::DropExtension, self.drop_extension_refusal())
985                    }
986                    _ => emitted(I::DropTable, self.drop_table()),
987                }
988            }
989            Token::Ident(s) if s == "grant" => emitted(I::Grant, self.grant_table_privileges()),
990            Token::Ident(s) if s == "revoke" => emitted(I::Revoke, self.revoke_table_privileges()),
991            Token::Keyword(Keyword::Import) => {
992                emitted(I::ImportForeignSchema, self.import_foreign_schema())
993            }
994            Token::Keyword(Keyword::Insert) => emitted(I::Insert, self.insert()),
995            Token::Keyword(Keyword::Copy) => emitted(I::Copy, self.copy_stmt()),
996            // SP4: transaction control
997            Token::Keyword(Keyword::Begin) => emitted(I::Begin, self.begin()),
998            Token::Keyword(Keyword::Start) => emitted(I::StartTransaction, self.begin()),
999            Token::Keyword(Keyword::Commit) if matches!(self.peek2(), Token::Ident(s) if s == "prepared") => {
1000                emitted(
1001                    I::CommitPrepared,
1002                    self.prepared_transaction_refusal(crate::ast::RefusalCommand::CommitPrepared),
1003                )
1004            }
1005            Token::Keyword(Keyword::Rollback) if matches!(self.peek2(), Token::Ident(s) if s == "prepared") => {
1006                emitted(
1007                    I::RollbackPrepared,
1008                    self.prepared_transaction_refusal(crate::ast::RefusalCommand::RollbackPrepared),
1009                )
1010            }
1011            Token::Keyword(keyword @ (Keyword::Commit | Keyword::End)) => {
1012                let identity = if *keyword == Keyword::Commit {
1013                    I::Commit
1014                } else {
1015                    I::End
1016                };
1017                self.bump();
1018                emitted(identity, Ok(crate::ast::Statement::Commit))
1019            }
1020            Token::Keyword(keyword @ (Keyword::Rollback | Keyword::Abort)) => {
1021                let identity = if *keyword == Keyword::Rollback {
1022                    I::Rollback
1023                } else {
1024                    I::Abort
1025                };
1026                self.bump();
1027                emitted(identity, Ok(crate::ast::Statement::Rollback))
1028            }
1029            // SP4: DML
1030            Token::Keyword(Keyword::Update) => emitted(I::Update, self.update()),
1031            Token::Keyword(Keyword::Delete) => emitted(I::Delete, self.delete()),
1032            // SP37: GUC control. `SET` is a keyword; `SHOW`/`RESET`/`ALTER` are matched as
1033            // plain (lowercased) idents — keyword-free so they stay usable as names.
1034            Token::Keyword(Keyword::Set) => {
1035                if matches!(self.peek2(), Token::Ident(s) if s == "role") {
1036                    emitted(I::SetRole, self.set_role_stmt())
1037                } else if matches!(self.peek2(), Token::Keyword(Keyword::Transaction)) {
1038                    emitted(I::SetTransaction, self.set_stmt())
1039                } else {
1040                    emitted(I::Set, self.set_stmt())
1041                }
1042            }
1043            Token::Ident(s) if s == "show" => emitted(I::Show, self.show_stmt()),
1044            Token::Ident(s) if s == "reset" => emitted(I::Reset, self.reset_stmt()),
1045            Token::Ident(s) if s == "discard" => emitted(I::Discard, self.discard_stmt()),
1046            Token::Ident(s)
1047                if s == "prepare"
1048                    && matches!(self.peek2(), Token::Keyword(Keyword::Transaction)) =>
1049            {
1050                emitted(
1051                    I::PrepareTransaction,
1052                    self.prepared_transaction_refusal(
1053                        crate::ast::RefusalCommand::PrepareTransaction,
1054                    ),
1055                )
1056            }
1057            // SP40: ALTER SERVER / ALTER USER MAPPING; bounded ALTER TABLE rename.
1058            Token::Ident(s) if s == "alter" => match self.peek2() {
1059                Token::Keyword(Keyword::Table) => emitted(I::AlterTable, self.alter_table()),
1060                Token::Keyword(Keyword::Server) => emitted(I::AlterServer, self.alter_server()),
1061                Token::Keyword(Keyword::User) => {
1062                    emitted(I::AlterUserMapping, self.alter_user_mapping())
1063                }
1064                Token::Ident(s) if s == "database" => {
1065                    emitted(I::AlterDatabase, self.alter_database_refusal())
1066                }
1067                Token::Ident(s) if s == "extension" => {
1068                    emitted(I::AlterExtension, self.alter_extension_refusal())
1069                }
1070                _ => Err(ParseError::new(
1071                    format!("unexpected token after ALTER: {:?}", self.peek2()),
1072                    self.peek_pos(),
1073                )),
1074            },
1075            other => Err(ParseError::new(
1076                format!("unexpected statement start {other:?}"),
1077                self.peek_pos(),
1078            )),
1079        }
1080    }
1081
1082    fn query_command_identity(&self) -> crate::command::CommandIdentity {
1083        use crate::command::CommandIdentity;
1084
1085        let mut offset = 0;
1086        while matches!(self.peek_n(offset), Token::LParen) {
1087            offset += 1;
1088        }
1089        if matches!(self.peek_n(offset), Token::Keyword(Keyword::Values)) {
1090            CommandIdentity::Values
1091        } else {
1092            CommandIdentity::Select
1093        }
1094    }
1095
1096    fn create_database_refusal(&mut self) -> Result<crate::ast::Statement, ParseError> {
1097        self.expect(&Token::Keyword(Keyword::Create))?;
1098        self.expect_ident_eq("database")?;
1099        self.expect_ident()?;
1100        Ok(crate::ast::Statement::CompatibilityRefusal(
1101            crate::ast::RefusalCommand::CreateDatabase,
1102        ))
1103    }
1104
1105    fn drop_database_refusal(&mut self) -> Result<crate::ast::Statement, ParseError> {
1106        self.expect(&Token::Keyword(Keyword::Drop))?;
1107        self.expect_ident_eq("database")?;
1108        self.expect_ident()?;
1109        Ok(crate::ast::Statement::CompatibilityRefusal(
1110            crate::ast::RefusalCommand::DropDatabase,
1111        ))
1112    }
1113
1114    fn drop_extension_refusal(&mut self) -> Result<crate::ast::Statement, ParseError> {
1115        self.expect(&Token::Keyword(Keyword::Drop))?;
1116        self.expect_ident_eq("extension")?;
1117        self.expect_ident()?;
1118        Ok(crate::ast::Statement::CompatibilityRefusal(
1119            crate::ast::RefusalCommand::DropExtension,
1120        ))
1121    }
1122
1123    fn alter_database_refusal(&mut self) -> Result<crate::ast::Statement, ParseError> {
1124        self.expect_ident_eq("alter")?;
1125        self.expect_ident_eq("database")?;
1126        self.expect_ident()?;
1127        self.expect_ident_eq("rename")?;
1128        self.expect_keyword_or_ident(Keyword::To, "to")?;
1129        self.expect_ident()?;
1130        Ok(crate::ast::Statement::CompatibilityRefusal(
1131            crate::ast::RefusalCommand::AlterDatabase,
1132        ))
1133    }
1134
1135    fn alter_extension_refusal(&mut self) -> Result<crate::ast::Statement, ParseError> {
1136        self.expect_ident_eq("alter")?;
1137        self.expect_ident_eq("extension")?;
1138        self.expect_ident()?;
1139        self.expect_keyword_or_ident(Keyword::Update, "update")?;
1140        Ok(crate::ast::Statement::CompatibilityRefusal(
1141            crate::ast::RefusalCommand::AlterExtension,
1142        ))
1143    }
1144
1145    fn prepared_transaction_refusal(
1146        &mut self,
1147        command: crate::ast::RefusalCommand,
1148    ) -> Result<crate::ast::Statement, ParseError> {
1149        match command {
1150            crate::ast::RefusalCommand::PrepareTransaction => {
1151                self.expect_ident_eq("prepare")?;
1152                self.expect(&Token::Keyword(Keyword::Transaction))?;
1153            }
1154            crate::ast::RefusalCommand::CommitPrepared => {
1155                self.expect(&Token::Keyword(Keyword::Commit))?;
1156                self.expect_ident_eq("prepared")?;
1157            }
1158            crate::ast::RefusalCommand::RollbackPrepared => {
1159                self.expect(&Token::Keyword(Keyword::Rollback))?;
1160                self.expect_ident_eq("prepared")?;
1161            }
1162            _ => unreachable!("only SQL-level prepared transaction commands use this parser"),
1163        }
1164        match self.bump() {
1165            Token::StringLit(_) => {}
1166            other => {
1167                return Err(ParseError::new(
1168                    format!("expected transaction identifier string, found {other:?}"),
1169                    self.peek_pos(),
1170                ));
1171            }
1172        }
1173        Ok(crate::ast::Statement::CompatibilityRefusal(command))
1174    }
1175
1176    fn alter_table(&mut self) -> Result<crate::ast::Statement, ParseError> {
1177        use crate::ast::{AlterTableRename, Statement};
1178
1179        self.expect_ident_eq("alter")?;
1180        self.expect(&Token::Keyword(Keyword::Table))?;
1181        let table = self.expect_ident()?;
1182        self.expect_ident_eq("rename")?;
1183        let rename = if self.eat_ident_eq("column") {
1184            let column = self.expect_ident()?;
1185            self.expect_keyword_or_ident(Keyword::To, "to")?;
1186            AlterTableRename::Column {
1187                column,
1188                new_name: self.expect_ident()?,
1189            }
1190        } else if self.eat_keyword(Keyword::To) || self.eat_ident_eq("to") {
1191            AlterTableRename::Table {
1192                new_name: self.expect_ident()?,
1193            }
1194        } else {
1195            let column = self.expect_ident()?;
1196            self.expect_keyword_or_ident(Keyword::To, "to")?;
1197            AlterTableRename::Column {
1198                column,
1199                new_name: self.expect_ident()?,
1200            }
1201        };
1202        Ok(Statement::AlterTableRename { table, rename })
1203    }
1204
1205    fn create_role(&mut self, can_login: bool) -> Result<crate::ast::Statement, ParseError> {
1206        self.expect(&Token::Keyword(Keyword::Create))?;
1207        if can_login {
1208            self.expect(&Token::Keyword(Keyword::User))?;
1209        } else {
1210            self.expect_ident_eq("role")?;
1211        }
1212        Ok(crate::ast::Statement::CreateRole {
1213            name: self.expect_object_name()?,
1214            can_login,
1215        })
1216    }
1217
1218    fn drop_role(&mut self) -> Result<crate::ast::Statement, ParseError> {
1219        self.expect(&Token::Keyword(Keyword::Drop))?;
1220        if self.eat_keyword(Keyword::User) {
1221            // DROP USER name
1222        } else {
1223            self.expect_ident_eq("role")?;
1224        }
1225        Ok(crate::ast::Statement::DropRole {
1226            name: self.expect_object_name()?,
1227        })
1228    }
1229
1230    fn grant_table_privileges(&mut self) -> Result<crate::ast::Statement, ParseError> {
1231        self.expect_ident_eq("grant")?;
1232        let privileges = self.privilege_list_until_on()?;
1233        self.expect(&Token::Keyword(Keyword::On))?;
1234        self.expect(&Token::Keyword(Keyword::Table))?;
1235        let table = self.expect_object_name()?;
1236        self.expect(&Token::Keyword(Keyword::To))?;
1237        let grantees = self.object_name_list()?;
1238        Ok(crate::ast::Statement::GrantTablePrivileges {
1239            privileges,
1240            table,
1241            grantees,
1242        })
1243    }
1244
1245    fn revoke_table_privileges(&mut self) -> Result<crate::ast::Statement, ParseError> {
1246        self.expect_ident_eq("revoke")?;
1247        let privileges = self.privilege_list_until_on()?;
1248        self.expect(&Token::Keyword(Keyword::On))?;
1249        self.expect(&Token::Keyword(Keyword::Table))?;
1250        let table = self.expect_object_name()?;
1251        self.expect(&Token::Keyword(Keyword::From))?;
1252        let grantees = self.object_name_list()?;
1253        Ok(crate::ast::Statement::RevokeTablePrivileges {
1254            privileges,
1255            table,
1256            grantees,
1257        })
1258    }
1259
1260    fn set_role_stmt(&mut self) -> Result<crate::ast::Statement, ParseError> {
1261        self.expect(&Token::Keyword(Keyword::Set))?;
1262        self.expect_ident_eq("role")?;
1263        let role = if matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("none")) {
1264            self.bump();
1265            None
1266        } else {
1267            Some(self.expect_object_name()?)
1268        };
1269        Ok(crate::ast::Statement::SetRole { role })
1270    }
1271
1272    fn privilege_list_until_on(&mut self) -> Result<Vec<String>, ParseError> {
1273        let mut privileges = Vec::new();
1274        loop {
1275            if matches!(self.peek(), Token::Keyword(Keyword::On)) {
1276                break;
1277            }
1278            privileges.push(self.expect_privilege_name()?);
1279            if !self.eat_comma() {
1280                if matches!(self.peek(), Token::Keyword(Keyword::On)) {
1281                    break;
1282                }
1283                return Err(ParseError::new(
1284                    "expected `,` or `ON` in privilege list",
1285                    self.peek_pos(),
1286                ));
1287            }
1288        }
1289        if privileges.is_empty() {
1290            return Err(ParseError::new(
1291                "expected at least one privilege",
1292                self.peek_pos(),
1293            ));
1294        }
1295        Ok(privileges)
1296    }
1297
1298    fn object_name_list(&mut self) -> Result<Vec<String>, ParseError> {
1299        let mut names = vec![self.expect_object_name()?];
1300        while self.eat_comma() {
1301            names.push(self.expect_object_name()?);
1302        }
1303        Ok(names)
1304    }
1305
1306    fn expect_object_name(&mut self) -> Result<String, ParseError> {
1307        match self.bump() {
1308            Token::Ident(s) => Ok(s),
1309            Token::Keyword(Keyword::Public) => Ok("public".into()),
1310            Token::Keyword(Keyword::CurrentUser) => Ok("current_user".into()),
1311            Token::Keyword(Keyword::User) => Ok("user".into()),
1312            other => Err(ParseError::new(
1313                format!("expected object name, found {other:?}"),
1314                self.peek_pos(),
1315            )),
1316        }
1317    }
1318
1319    fn expect_privilege_name(&mut self) -> Result<String, ParseError> {
1320        match self.bump() {
1321            Token::Ident(s) => Ok(s.to_ascii_uppercase()),
1322            Token::Keyword(Keyword::Select) => Ok("SELECT".into()),
1323            Token::Keyword(Keyword::Insert) => Ok("INSERT".into()),
1324            Token::Keyword(Keyword::Update) => Ok("UPDATE".into()),
1325            Token::Keyword(Keyword::Delete) => Ok("DELETE".into()),
1326            Token::Keyword(Keyword::All) => Ok("ALL".into()),
1327            other => Err(ParseError::new(
1328                format!("expected privilege name, found {other:?}"),
1329                self.peek_pos(),
1330            )),
1331        }
1332    }
1333
1334    /// SP37: `SET [LOCAL] <name> (= | TO) <value>` / `SET [LOCAL] TIME ZONE <value>`.
1335    /// Keyword-free for `LOCAL`/`TO`/`TIME ZONE`/`DEFAULT`/`LOCAL` (the value) —
1336    /// they are matched as lowercased idents, so none becomes a reserved keyword.
1337    /// The GUC name is normalized to lowercase; `TIME ZONE` normalizes to
1338    /// `"timezone"`.
1339    fn set_stmt(&mut self) -> Result<crate::ast::Statement, ParseError> {
1340        use crate::ast::Statement;
1341        self.expect(&Token::Keyword(Keyword::Set))?;
1342        if self.eat_keyword(Keyword::Transaction)
1343            || (matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("session"))
1344                && matches!(self.peek2(), Token::Ident(w) if w.eq_ignore_ascii_case("characteristics")))
1345        {
1346            if matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("session")) {
1347                self.bump(); // session
1348                self.bump(); // characteristics
1349                if matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("as")) {
1350                    self.bump();
1351                }
1352                self.expect(&Token::Keyword(Keyword::Transaction))?;
1353            }
1354            return self.set_transaction_tail();
1355        }
1356        // `SESSION` is the explicit spelling of the default SET scope.
1357        if matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("session")) {
1358            self.bump();
1359        }
1360        // `LOCAL` is the flag only when it leads and is followed by a parameter
1361        // name (an ident or `TIME ZONE`). It is NEVER a flag after `TIME ZONE`
1362        // (there it is the value `LOCAL`), and the `set_stmt` entry is before any
1363        // `TIME ZONE` is consumed, so a leading `LOCAL` here is unambiguous.
1364        let local = (matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("local"))
1365            || *self.peek() == Token::Keyword(Keyword::Local))
1366            && !matches!(self.peek2(), Token::Eq);
1367        if local {
1368            self.bump(); // LOCAL
1369        }
1370        // The `TIME ZONE` special spelling: `SET [LOCAL] TIME ZONE <value>`.
1371        if matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("time"))
1372            && matches!(self.peek2(), Token::Ident(w) if w.eq_ignore_ascii_case("zone"))
1373        {
1374            self.bump(); // time
1375            self.bump(); // zone
1376            let value = self.set_time_zone_value()?;
1377            return Ok(Statement::Set {
1378                local,
1379                name: "timezone".into(),
1380                value,
1381            });
1382        }
1383        // `SET [LOCAL] <name> (= | TO) <value>`.
1384        let name = self.expect_ident()?.to_ascii_lowercase();
1385        // `=` is a token; `TO` is now a keyword (Keyword::To) — either separates name from value.
1386        let sep = *self.peek() == Token::Eq
1387            || *self.peek() == Token::Keyword(Keyword::To)
1388            || matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("to"));
1389        if !sep {
1390            return Err(ParseError::new(
1391                "expected `=` or `TO` in SET",
1392                self.peek_pos(),
1393            ));
1394        }
1395        self.bump(); // = or TO
1396        let value = self.set_value()?;
1397        Ok(Statement::Set { local, name, value })
1398    }
1399
1400    /// The value after `=`/`TO`: a string literal, a `DEFAULT` ident (→ Default),
1401    /// or any other identifier (→ that ident verbatim).
1402    fn set_value(&mut self) -> Result<crate::ast::SetValue, ParseError> {
1403        use crate::ast::SetValue;
1404        let mut rendered = String::new();
1405        let mut separator = "";
1406        loop {
1407            let part = match self.peek().clone() {
1408                Token::Plus | Token::Minus => {
1409                    let sign = if self.bump() == Token::Minus {
1410                        "-"
1411                    } else {
1412                        "+"
1413                    };
1414                    let Token::IntLit(number) = self.bump() else {
1415                        return Err(ParseError::new(
1416                            "expected a number after SET value sign",
1417                            self.peek_pos(),
1418                        ));
1419                    };
1420                    format!("{sign}{number}")
1421                }
1422                Token::StringLit(s) | Token::IntLit(s) | Token::FloatLit(s) => {
1423                    self.bump();
1424                    s
1425                }
1426                Token::Ident(w) if w.eq_ignore_ascii_case("default") => {
1427                    self.bump();
1428                    if rendered.is_empty() && *self.peek() != Token::Comma {
1429                        return Ok(SetValue::Default);
1430                    }
1431                    "default".into()
1432                }
1433                Token::Ident(w) => {
1434                    self.bump();
1435                    w
1436                }
1437                Token::Keyword(Keyword::True | Keyword::On) => {
1438                    self.bump();
1439                    "on".into()
1440                }
1441                Token::Keyword(Keyword::False) => {
1442                    self.bump();
1443                    "off".into()
1444                }
1445                Token::Keyword(Keyword::Local) => {
1446                    self.bump();
1447                    "local".into()
1448                }
1449                Token::Keyword(Keyword::Public) => {
1450                    self.bump();
1451                    "public".into()
1452                }
1453                other => Err(ParseError::new(
1454                    format!("expected a SET value, found {other:?}"),
1455                    self.peek_pos(),
1456                ))?,
1457            };
1458            rendered.push_str(separator);
1459            rendered.push_str(&part);
1460            if self.eat_comma() {
1461                separator = ", ";
1462                continue;
1463            }
1464            if matches!(
1465                self.peek(),
1466                Token::Ident(_)
1467                    | Token::StringLit(_)
1468                    | Token::IntLit(_)
1469                    | Token::FloatLit(_)
1470                    | Token::Plus
1471                    | Token::Minus
1472                    | Token::Keyword(
1473                        Keyword::True
1474                            | Keyword::On
1475                            | Keyword::False
1476                            | Keyword::Local
1477                            | Keyword::Public
1478                    )
1479            ) {
1480                separator = " ";
1481            } else {
1482                break;
1483            }
1484        }
1485        Ok(SetValue::Value(rendered))
1486    }
1487
1488    fn set_transaction_tail(&mut self) -> Result<crate::ast::Statement, ParseError> {
1489        use crate::ast::{IsolationLevel, SetValue, Statement};
1490        let isolation = if self.eat_keyword(Keyword::Isolation) {
1491            self.expect(&Token::Keyword(Keyword::Level))?;
1492            if self.eat_keyword(Keyword::Repeatable) {
1493                self.expect(&Token::Keyword(Keyword::Read))?;
1494                Some(IsolationLevel::RepeatableRead)
1495            } else if self.eat_keyword(Keyword::Read) {
1496                self.expect(&Token::Keyword(Keyword::Committed))?;
1497                Some(IsolationLevel::ReadCommitted)
1498            } else {
1499                return Err(ParseError::new(
1500                    "expected REPEATABLE READ or READ COMMITTED",
1501                    self.peek_pos(),
1502                ));
1503            }
1504        } else {
1505            None
1506        };
1507        let value = match isolation {
1508            Some(IsolationLevel::RepeatableRead) => SetValue::Value("repeatable read".into()),
1509            Some(IsolationLevel::ReadCommitted) => SetValue::Value("read committed".into()),
1510            None => SetValue::Default,
1511        };
1512        Ok(Statement::Set {
1513            local: false,
1514            name: "__set_transaction".into(),
1515            value,
1516        })
1517    }
1518
1519    /// The value after `SET [LOCAL] TIME ZONE`: like [`set_value`], but the bare
1520    /// idents `LOCAL` and `DEFAULT` both mean "reset to default" (`PostgreSQL`).
1521    fn set_time_zone_value(&mut self) -> Result<crate::ast::SetValue, ParseError> {
1522        use crate::ast::SetValue;
1523        match self.peek().clone() {
1524            Token::StringLit(s) => {
1525                self.bump();
1526                Ok(SetValue::Value(s))
1527            }
1528            Token::Ident(w)
1529                if w.eq_ignore_ascii_case("default") || w.eq_ignore_ascii_case("local") =>
1530            {
1531                self.bump();
1532                Ok(SetValue::Default)
1533            }
1534            Token::Keyword(Keyword::Local) => {
1535                self.bump();
1536                Ok(SetValue::Default)
1537            }
1538            Token::Ident(w) => {
1539                self.bump();
1540                Ok(SetValue::Value(w))
1541            }
1542            other => Err(ParseError::new(
1543                format!("expected a TIME ZONE value, found {other:?}"),
1544                self.peek_pos(),
1545            )),
1546        }
1547    }
1548
1549    /// SP37: `SHOW <name>` / `SHOW TIME ZONE`. Positioned at the `show` ident.
1550    fn show_stmt(&mut self) -> Result<crate::ast::Statement, ParseError> {
1551        use crate::ast::Statement;
1552        self.bump(); // show
1553        if self.eat_keyword(Keyword::All) {
1554            return Ok(Statement::Show { name: "all".into() });
1555        }
1556        // `SHOW TIME ZONE` → name `"timezone"`.
1557        if matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("time"))
1558            && matches!(self.peek2(), Token::Ident(w) if w.eq_ignore_ascii_case("zone"))
1559        {
1560            self.bump(); // time
1561            self.bump(); // zone
1562            return Ok(Statement::Show {
1563                name: "timezone".into(),
1564            });
1565        }
1566        let name = self.expect_ident()?.to_ascii_lowercase();
1567        Ok(Statement::Show { name })
1568    }
1569
1570    /// SP37: `RESET <name>`. Positioned at the `reset` ident.
1571    fn reset_stmt(&mut self) -> Result<crate::ast::Statement, ParseError> {
1572        use crate::ast::Statement;
1573        self.bump(); // reset
1574        if matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("role")) {
1575            self.bump();
1576            return Ok(Statement::SetRole { role: None });
1577        }
1578        if self.eat_keyword(Keyword::All) {
1579            return Ok(Statement::Reset {
1580                target: crate::ast::ResetTarget::All,
1581            });
1582        }
1583        // `RESET TIME ZONE` → name `"timezone"` (symmetry with SHOW; PG accepts it).
1584        if matches!(self.peek(), Token::Ident(w) if w.eq_ignore_ascii_case("time"))
1585            && matches!(self.peek2(), Token::Ident(w) if w.eq_ignore_ascii_case("zone"))
1586        {
1587            self.bump(); // time
1588            self.bump(); // zone
1589            return Ok(Statement::Reset {
1590                target: crate::ast::ResetTarget::Name("timezone".into()),
1591            });
1592        }
1593        let name = self.expect_ident()?.to_ascii_lowercase();
1594        Ok(Statement::Reset {
1595            target: crate::ast::ResetTarget::Name(name),
1596        })
1597    }
1598
1599    fn discard_stmt(&mut self) -> Result<crate::ast::Statement, ParseError> {
1600        self.bump(); // discard
1601        self.expect(&Token::Keyword(Keyword::All))?;
1602        Ok(crate::ast::Statement::Set {
1603            local: false,
1604            name: "__discard_all".into(),
1605            value: crate::ast::SetValue::Default,
1606        })
1607    }
1608
1609    fn begin(&mut self) -> Result<crate::ast::Statement, ParseError> {
1610        use crate::ast::{IsolationLevel, Statement};
1611        let leading = self.bump(); // BEGIN or START
1612        if leading == Token::Keyword(Keyword::Start) {
1613            // START TRANSACTION is valid; bare START is not a statement.
1614            self.expect(&Token::Keyword(Keyword::Transaction))?;
1615        } else {
1616            // TRANSACTION is optional after BEGIN.
1617            self.eat_keyword(Keyword::Transaction);
1618        }
1619        let isolation = if self.eat_keyword(Keyword::Isolation) {
1620            self.expect(&Token::Keyword(Keyword::Level))?;
1621            if self.eat_keyword(Keyword::Repeatable) {
1622                self.expect(&Token::Keyword(Keyword::Read))?;
1623                Some(IsolationLevel::RepeatableRead)
1624            } else if self.eat_keyword(Keyword::Read) {
1625                self.expect(&Token::Keyword(Keyword::Committed))?;
1626                Some(IsolationLevel::ReadCommitted)
1627            } else {
1628                return Err(ParseError::new(
1629                    "expected REPEATABLE READ or READ COMMITTED",
1630                    self.peek_pos(),
1631                ));
1632            }
1633        } else {
1634            None
1635        };
1636        Ok(Statement::Begin { isolation })
1637    }
1638
1639    fn update(&mut self) -> Result<crate::ast::Statement, ParseError> {
1640        use crate::ast::Statement;
1641        self.expect(&Token::Keyword(Keyword::Update))?;
1642        let table = self.expect_ident()?;
1643        self.expect(&Token::Keyword(Keyword::Set))?;
1644        let mut assignments = Vec::new();
1645        loop {
1646            let col = self.expect_ident()?;
1647            self.expect(&Token::Eq)?;
1648            let value = self.expr(0)?;
1649            assignments.push((col, value));
1650            if self.eat_comma() {
1651                continue;
1652            }
1653            break;
1654        }
1655        let filter = if self.eat_keyword(Keyword::Where) {
1656            Some(self.expr(0)?)
1657        } else {
1658            None
1659        };
1660        let returning = self.returning_clause()?;
1661        Ok(Statement::Update {
1662            table,
1663            assignments,
1664            filter,
1665            returning,
1666        })
1667    }
1668
1669    fn delete(&mut self) -> Result<crate::ast::Statement, ParseError> {
1670        use crate::ast::Statement;
1671        self.expect(&Token::Keyword(Keyword::Delete))?;
1672        self.expect(&Token::Keyword(Keyword::From))?;
1673        let table = self.expect_ident()?;
1674        let filter = if self.eat_keyword(Keyword::Where) {
1675            Some(self.expr(0)?)
1676        } else {
1677            None
1678        };
1679        let returning = self.returning_clause()?;
1680        Ok(Statement::Delete {
1681            table,
1682            filter,
1683            returning,
1684        })
1685    }
1686
1687    fn create_table(&mut self) -> Result<crate::ast::Statement, ParseError> {
1688        use crate::ast::{ColumnDef, HashShardingSpec, ShardingSpec, Statement};
1689        self.expect(&Token::Keyword(Keyword::Create))?;
1690        self.expect(&Token::Keyword(Keyword::Table))?;
1691        let name = self.expect_ident()?;
1692        self.expect(&Token::LParen)?;
1693        let mut columns = Vec::new();
1694        let mut constraints = Vec::new();
1695        loop {
1696            if self.eat_ident_eq("primary") {
1697                self.expect_ident_eq("key")?;
1698                constraints.push(crate::ast::TableConstraint::PrimaryKey(
1699                    self.parse_ident_list()?,
1700                ));
1701                if self.eat_comma() {
1702                    continue;
1703                }
1704                break;
1705            }
1706            if self.eat_keyword(Keyword::Unique) {
1707                constraints.push(crate::ast::TableConstraint::Unique(
1708                    self.parse_ident_list()?,
1709                ));
1710                if self.eat_comma() {
1711                    continue;
1712                }
1713                break;
1714            }
1715            if self.eat_ident_eq("check") {
1716                self.expect(&Token::LParen)?;
1717                let expr = self.expr(0)?;
1718                self.expect(&Token::RParen)?;
1719                constraints.push(crate::ast::TableConstraint::Check(expr));
1720                if self.eat_comma() {
1721                    continue;
1722                }
1723                break;
1724            }
1725            let col_name = self.expect_ident()?;
1726            let (ty, serial) = self.parse_column_type()?;
1727            let constraints = self.column_constraints()?;
1728            columns.push(ColumnDef {
1729                name: col_name,
1730                ty,
1731                serial,
1732                constraints,
1733            });
1734            if self.eat_comma() {
1735                continue;
1736            }
1737            break;
1738        }
1739        self.expect(&Token::RParen)?;
1740        let saw_sharded = self.eat_ident_eq("sharded");
1741        let sharding = if saw_sharded && self.eat_keyword(Keyword::By) {
1742            self.expect_ident_eq("hash")?;
1743            self.expect(&Token::LParen)?;
1744            let hash_columns = vec![self.expect_ident()?];
1745            if self.eat_comma() {
1746                return Err(ParseError::new(
1747                    "hash sharding requires exactly one column",
1748                    self.peek_pos(),
1749                ));
1750            }
1751            self.expect(&Token::RParen)?;
1752            self.expect_ident_eq("buckets")?;
1753            let buckets = self.expect_hash_bucket_count()?;
1754            let co_location_group = if self.eat_ident_eq("colocated") {
1755                self.expect_keyword_or_ident(Keyword::With, "with")?;
1756                Some(self.expect_ident()?)
1757            } else {
1758                None
1759            };
1760            Some(ShardingSpec::Hash(HashShardingSpec {
1761                columns: hash_columns,
1762                buckets,
1763                co_location_group,
1764            }))
1765        } else {
1766            None
1767        };
1768        let sharded = saw_sharded;
1769        Ok(Statement::CreateTable {
1770            name,
1771            columns,
1772            constraints,
1773            sharded,
1774            sharding,
1775        })
1776    }
1777
1778    fn parse_column_type(
1779        &mut self,
1780    ) -> Result<(crabka_pgtypes::ColumnType, Option<crate::ast::SerialKind>), ParseError> {
1781        let type_pos = self.peek_pos();
1782        let type_name = self.expect_ident()?;
1783        match type_name.as_str() {
1784            "serial" => Ok((
1785                crabka_pgtypes::ColumnType::Int4,
1786                Some(crate::ast::SerialKind::Serial),
1787            )),
1788            "bigserial" => Ok((
1789                crabka_pgtypes::ColumnType::Int8,
1790                Some(crate::ast::SerialKind::BigSerial),
1791            )),
1792            _ => {
1793                self.pos -= 1;
1794                self.parse_type_name()
1795                    .map(|ty| (ty, None))
1796                    .map_err(|mut err| {
1797                        err.position = type_pos;
1798                        err
1799                    })
1800            }
1801        }
1802    }
1803
1804    fn column_constraints(&mut self) -> Result<Vec<crate::ast::ColumnConstraint>, ParseError> {
1805        use crate::ast::ColumnConstraint;
1806        let mut constraints = Vec::new();
1807        loop {
1808            if self.eat_keyword(Keyword::Not) {
1809                self.expect(&Token::Keyword(Keyword::Null))?;
1810                constraints.push(ColumnConstraint::NotNull);
1811                continue;
1812            }
1813            if self.eat_ident_eq("default") {
1814                constraints.push(ColumnConstraint::Default(self.expr(0)?));
1815                continue;
1816            }
1817            if self.eat_ident_eq("primary") {
1818                self.expect_ident_eq("key")?;
1819                constraints.push(ColumnConstraint::PrimaryKey);
1820                continue;
1821            }
1822            if self.eat_keyword(Keyword::Unique) {
1823                constraints.push(ColumnConstraint::Unique);
1824                continue;
1825            }
1826            if self.eat_ident_eq("check") {
1827                self.expect(&Token::LParen)?;
1828                let expr = self.expr(0)?;
1829                self.expect(&Token::RParen)?;
1830                constraints.push(ColumnConstraint::Check(expr));
1831                continue;
1832            }
1833            break;
1834        }
1835        Ok(constraints)
1836    }
1837
1838    fn create_index(&mut self) -> Result<crate::ast::Statement, ParseError> {
1839        use crate::ast::{IndexPlacement, Statement};
1840
1841        self.expect(&Token::Keyword(Keyword::Create))?;
1842        let mut unique = false;
1843        let mut placement = IndexPlacement::Local;
1844        loop {
1845            if !unique && self.eat_keyword(Keyword::Unique) {
1846                unique = true;
1847                continue;
1848            }
1849            if self.eat_keyword(Keyword::Global) {
1850                placement = IndexPlacement::Global;
1851                continue;
1852            }
1853            if self.eat_keyword(Keyword::Local) {
1854                placement = IndexPlacement::Local;
1855                continue;
1856            }
1857            break;
1858        }
1859        self.expect(&Token::Keyword(Keyword::Index))?;
1860        let name = self.expect_ident()?;
1861        self.expect(&Token::Keyword(Keyword::On))?;
1862        let table = self.expect_ident()?;
1863        let columns = self.parse_ident_list()?;
1864        if columns.is_empty() {
1865            return Err(ParseError::new(
1866                "CREATE INDEX requires at least one column",
1867                self.peek_pos(),
1868            ));
1869        }
1870        Ok(Statement::CreateIndex {
1871            name,
1872            table,
1873            columns,
1874            unique,
1875            placement,
1876        })
1877    }
1878
1879    fn create_view(&mut self) -> Result<crate::ast::Statement, ParseError> {
1880        use crate::ast::Statement;
1881
1882        self.expect(&Token::Keyword(Keyword::Create))?;
1883        self.expect(&Token::Keyword(Keyword::View))?;
1884        let name = self.expect_object_name()?;
1885        self.expect(&Token::Keyword(Keyword::As))?;
1886        let definition_start = self.peek_pos();
1887        let query = self.query_expr()?;
1888        let definition_end = self.peek_pos();
1889        let definition = self.source[definition_start..definition_end]
1890            .trim()
1891            .to_string();
1892        Ok(Statement::CreateView {
1893            name,
1894            definition,
1895            query,
1896        })
1897    }
1898
1899    fn create_sequence(&mut self) -> Result<crate::ast::Statement, ParseError> {
1900        use crate::ast::{SequenceOptions, Statement};
1901        self.expect(&Token::Keyword(Keyword::Create))?;
1902        self.expect_ident_eq("sequence")?;
1903        let name = self.expect_ident()?;
1904        let mut options = SequenceOptions::default();
1905        while !matches!(self.peek(), Token::Semicolon | Token::Eof) {
1906            if self.eat_ident_eq("start") || self.eat_keyword(Keyword::Start) {
1907                self.eat_keyword(Keyword::With);
1908                options.start = Some(self.expect_i64("START value")?);
1909            } else if self.eat_ident_eq("increment") {
1910                self.expect_keyword_or_ident(Keyword::By, "by")?;
1911                options.increment = Some(self.expect_i64("INCREMENT value")?);
1912            } else if self.eat_ident_eq("minvalue") {
1913                options.min = Some(self.expect_i64("MINVALUE")?);
1914            } else if self.eat_ident_eq("maxvalue") {
1915                options.max = Some(self.expect_i64("MAXVALUE")?);
1916            } else if self.eat_ident_eq("no") {
1917                if self.eat_ident_eq("minvalue") {
1918                    options.min = None;
1919                } else if self.eat_ident_eq("maxvalue") {
1920                    options.max = None;
1921                } else if self.eat_ident_eq("cycle") {
1922                    options.cycle = Some(false);
1923                } else {
1924                    return Err(ParseError::new(
1925                        "expected MINVALUE, MAXVALUE, or CYCLE after NO",
1926                        self.peek_pos(),
1927                    ));
1928                }
1929            } else if self.eat_ident_eq("cache") {
1930                options.cache = Some(self.expect_i64("CACHE")?);
1931            } else if self.eat_ident_eq("cycle") {
1932                options.cycle = Some(true);
1933            } else {
1934                return Err(ParseError::new(
1935                    format!("unexpected CREATE SEQUENCE option {:?}", self.peek()),
1936                    self.peek_pos(),
1937                ));
1938            }
1939        }
1940        Ok(Statement::CreateIndex {
1941            name,
1942            table: "__crabka_sequence__".into(),
1943            columns: encode_sequence_options(&options),
1944            unique: false,
1945            placement: crate::ast::IndexPlacement::Local,
1946        })
1947    }
1948
1949    fn drop_sequence(&mut self) -> Result<crate::ast::Statement, ParseError> {
1950        self.expect(&Token::Keyword(Keyword::Drop))?;
1951        self.expect_ident_eq("sequence")?;
1952        self.eat_if_exists()?;
1953        Ok(crate::ast::Statement::DropTable {
1954            name: format!("__crabka_sequence__:{}", self.expect_ident()?),
1955        })
1956    }
1957
1958    fn expect_i64(&mut self, what: &str) -> Result<i64, ParseError> {
1959        let pos = self.peek_pos();
1960        let negative = *self.peek() == Token::Minus;
1961        if negative {
1962            self.bump();
1963        }
1964        let Token::IntLit(raw) = self.bump() else {
1965            return Err(ParseError::new(format!("expected {what}"), pos));
1966        };
1967        let signed = if negative { format!("-{raw}") } else { raw };
1968        signed
1969            .parse::<i64>()
1970            .map_err(|_| ParseError::new(format!("{what} out of range"), pos))
1971    }
1972
1973    fn expect_keyword_or_ident(&mut self, keyword: Keyword, ident: &str) -> Result<(), ParseError> {
1974        if self.eat_keyword(keyword) || self.eat_ident_eq(ident) {
1975            return Ok(());
1976        }
1977
1978        Err(ParseError::new(
1979            format!("expected `{ident}`, found {:?}", self.peek()),
1980            self.peek_pos(),
1981        ))
1982    }
1983
1984    fn expect_hash_bucket_count(&mut self) -> Result<u32, ParseError> {
1985        let pos = self.peek_pos();
1986        let Token::IntLit(raw) = self.bump() else {
1987            return Err(ParseError::new("expected hash bucket count", pos));
1988        };
1989        let buckets = raw
1990            .parse::<u32>()
1991            .map_err(|_| ParseError::new("hash bucket count out of range", pos))?;
1992        if buckets == 0 || !buckets.is_power_of_two() {
1993            return Err(ParseError::new(
1994                "hash bucket count must be a power of two",
1995                pos,
1996            ));
1997        }
1998        Ok(buckets)
1999    }
2000
2001    fn drop_table(&mut self) -> Result<crate::ast::Statement, ParseError> {
2002        use crate::ast::Statement;
2003        self.expect(&Token::Keyword(Keyword::Drop))?;
2004        self.expect(&Token::Keyword(Keyword::Table))?;
2005        // SP40: accept IF EXISTS (consistent with DROP SERVER / FOREIGN TABLE).
2006        self.eat_if_exists()?;
2007        Ok(Statement::DropTable {
2008            name: self.expect_ident()?,
2009        })
2010    }
2011
2012    fn drop_index(&mut self) -> Result<crate::ast::Statement, ParseError> {
2013        use crate::ast::Statement;
2014
2015        self.expect(&Token::Keyword(Keyword::Drop))?;
2016        self.expect(&Token::Keyword(Keyword::Index))?;
2017        let if_exists = self.eat_if_exists()?;
2018        Ok(Statement::DropIndex {
2019            name: self.expect_ident()?,
2020            if_exists,
2021        })
2022    }
2023
2024    fn drop_view(&mut self) -> Result<crate::ast::Statement, ParseError> {
2025        use crate::ast::Statement;
2026
2027        self.expect(&Token::Keyword(Keyword::Drop))?;
2028        self.expect(&Token::Keyword(Keyword::View))?;
2029        let if_exists = self.eat_if_exists()?;
2030        Ok(Statement::DropView {
2031            name: self.expect_object_name()?,
2032            if_exists,
2033        })
2034    }
2035
2036    fn insert(&mut self) -> Result<crate::ast::Statement, ParseError> {
2037        use crate::ast::Statement;
2038        self.expect(&Token::Keyword(Keyword::Insert))?;
2039        self.expect(&Token::Keyword(Keyword::Into))?;
2040        let table = self.expect_ident()?;
2041        let columns = if *self.peek() == Token::LParen {
2042            self.bump();
2043            let mut cols = Vec::new();
2044            loop {
2045                cols.push(self.expect_ident()?);
2046                if self.eat_comma() {
2047                    continue;
2048                }
2049                break;
2050            }
2051            self.expect(&Token::RParen)?;
2052            Some(cols)
2053        } else {
2054            None
2055        };
2056        self.expect(&Token::Keyword(Keyword::Values))?;
2057        let mut rows = Vec::new();
2058        loop {
2059            self.expect(&Token::LParen)?;
2060            let mut row = Vec::new();
2061            loop {
2062                row.push(self.insert_value_expr()?);
2063                if self.eat_comma() {
2064                    continue;
2065                }
2066                break;
2067            }
2068            self.expect(&Token::RParen)?;
2069            rows.push(row);
2070            if self.eat_comma() {
2071                continue;
2072            }
2073            break;
2074        }
2075        Ok(Statement::Insert {
2076            table,
2077            columns,
2078            rows,
2079            returning: self.returning_clause()?,
2080        })
2081    }
2082
2083    fn returning_clause(&mut self) -> Result<Option<Vec<crate::ast::SelectItem>>, ParseError> {
2084        if !self.eat_keyword(Keyword::Returning) {
2085            return Ok(None);
2086        }
2087
2088        Ok(Some(self.projection_list()?))
2089    }
2090
2091    fn projection_list(&mut self) -> Result<Vec<crate::ast::SelectItem>, ParseError> {
2092        use crate::ast::SelectItem;
2093
2094        let mut projection = Vec::new();
2095        loop {
2096            if *self.peek() == Token::Star {
2097                self.bump();
2098                projection.push(SelectItem::Wildcard);
2099            } else if let Token::Ident(_) = self.peek()
2100                && *self.peek_n(1) == Token::Dot
2101                && *self.peek_n(2) == Token::Star
2102            {
2103                let qualifier = self.expect_ident()?;
2104                self.bump();
2105                self.bump();
2106                projection.push(SelectItem::QualifiedWildcard(qualifier));
2107            } else {
2108                let expr = self.expr(0)?;
2109                let alias = if self.eat_keyword(Keyword::As) {
2110                    Some(self.expect_ident()?)
2111                } else if let Token::Ident(_) = self.peek() {
2112                    Some(self.expect_ident()?)
2113                } else {
2114                    None
2115                };
2116                projection.push(SelectItem::Expr { expr, alias });
2117            }
2118
2119            if self.eat_comma() {
2120                continue;
2121            }
2122            break;
2123        }
2124        Ok(projection)
2125    }
2126
2127    fn insert_value_expr(&mut self) -> Result<crate::ast::Expr, ParseError> {
2128        if self.eat_ident_eq("default") {
2129            return Ok(crate::ast::Expr::Default);
2130        }
2131        self.expr(0)
2132    }
2133
2134    fn copy_stmt(&mut self) -> Result<crate::ast::Statement, ParseError> {
2135        use crate::ast::{CopyFormat, CopyStmt, Statement};
2136
2137        self.expect(&Token::Keyword(Keyword::Copy))?;
2138        let table = self.expect_ident()?;
2139        let columns = if *self.peek() == Token::LParen {
2140            Some(self.parse_parenthesized_ident_list()?)
2141        } else {
2142            None
2143        };
2144
2145        if self.eat_keyword(Keyword::To) {
2146            return Err(ParseError::new_sqlstate(
2147                "0A000",
2148                "COPY TO is not supported",
2149                self.peek_pos(),
2150            ));
2151        }
2152        self.expect(&Token::Keyword(Keyword::From))?;
2153        if !self.eat_ident_eq("stdin") {
2154            return Err(ParseError::new_sqlstate(
2155                "0A000",
2156                "only COPY FROM STDIN is supported",
2157                self.peek_pos(),
2158            ));
2159        }
2160
2161        let mut format = CopyFormat::Text;
2162        if self.eat_keyword(Keyword::With) {
2163            self.expect(&Token::LParen)?;
2164            loop {
2165                let option = self.expect_ident()?.to_ascii_lowercase();
2166                match option.as_str() {
2167                    "format" => {
2168                        let value = self.expect_ident()?.to_ascii_lowercase();
2169                        format = match value.as_str() {
2170                            "text" => CopyFormat::Text,
2171                            "csv" => CopyFormat::Csv,
2172                            "binary" => {
2173                                return Err(ParseError::new_sqlstate(
2174                                    "0A000",
2175                                    "COPY BINARY is not supported",
2176                                    self.peek_pos(),
2177                                ));
2178                            }
2179                            _ => {
2180                                return Err(ParseError::new(
2181                                    format!("unsupported COPY format `{value}`"),
2182                                    self.peek_pos(),
2183                                ));
2184                            }
2185                        };
2186                    }
2187                    "binary" => {
2188                        return Err(ParseError::new_sqlstate(
2189                            "0A000",
2190                            "COPY BINARY is not supported",
2191                            self.peek_pos(),
2192                        ));
2193                    }
2194                    _ => {
2195                        return Err(ParseError::new_sqlstate(
2196                            "0A000",
2197                            format!("COPY option `{option}` is not supported"),
2198                            self.peek_pos(),
2199                        ));
2200                    }
2201                }
2202                if self.eat_comma() {
2203                    continue;
2204                }
2205                break;
2206            }
2207            self.expect(&Token::RParen)?;
2208        } else if self.eat_ident_eq("binary") {
2209            return Err(ParseError::new_sqlstate(
2210                "0A000",
2211                "COPY BINARY is not supported",
2212                self.peek_pos(),
2213            ));
2214        }
2215
2216        Ok(Statement::Set {
2217            local: false,
2218            name: crate::ast::COPY_FROM_STDIN_SENTINEL.into(),
2219            value: crate::ast::SetValue::Value(Self::encode_copy_stmt(&CopyStmt {
2220                table,
2221                columns,
2222                format,
2223            })),
2224        })
2225    }
2226
2227    fn parse_parenthesized_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
2228        self.expect(&Token::LParen)?;
2229        let mut cols = Vec::new();
2230        loop {
2231            cols.push(self.expect_ident()?);
2232            if self.eat_comma() {
2233                continue;
2234            }
2235            break;
2236        }
2237        self.expect(&Token::RParen)?;
2238        Ok(cols)
2239    }
2240
2241    fn encode_copy_stmt(copy: &crate::ast::CopyStmt) -> String {
2242        let format = match copy.format {
2243            crate::ast::CopyFormat::Text => "text",
2244            crate::ast::CopyFormat::Csv => "csv",
2245        };
2246        let columns = copy
2247            .columns
2248            .as_ref()
2249            .map(|columns| {
2250                columns
2251                    .iter()
2252                    .map(|column| Self::encode_copy_part(column))
2253                    .collect::<Vec<_>>()
2254                    .join(",")
2255            })
2256            .unwrap_or_default();
2257        [format, &Self::encode_copy_part(&copy.table), &columns].join("\t")
2258    }
2259
2260    fn encode_copy_part(value: &str) -> String {
2261        let mut out = String::with_capacity(value.len());
2262        for ch in value.chars() {
2263            match ch {
2264                '\\' => out.push_str(r"\\"),
2265                '\t' => out.push_str(r"\t"),
2266                ',' => out.push_str(r"\,"),
2267                other => out.push(other),
2268            }
2269        }
2270        out
2271    }
2272
2273    /// Parse projection → HAVING. Leaves `order_by` / `limit` / `offset` / `locking` empty;
2274    /// the caller (single SELECT or set-op query) owns the tail.
2275    fn select_core(&mut self) -> Result<crate::ast::SelectStmt, ParseError> {
2276        use crate::ast::SelectStmt;
2277        // Mode-1 depth guard: EVERY SELECT body funnels through `select_core` — a
2278        // top-level set-op branch (`set_primary → select_core`), a derived table,
2279        // or a scalar/IN/EXISTS subquery (`query_expr → set_primary → select_core`) — so
2280        // guarding here bounds all nested-SELECT recursion (e.g. a derived-table
2281        // chain `( SELECT … FROM ( SELECT … ) )`). Subqueries also pass through
2282        // `expr` first; guarding both is belt-and-braces.
2283        let _guard = DepthGuard::enter(&self.depth, self.peek_pos())?;
2284        self.expect(&Token::Keyword(Keyword::Select))?;
2285        // SP28: SELECT DISTINCT (ALL is the default modifier — accept and ignore).
2286        let distinct = self.eat_keyword(Keyword::Distinct);
2287        if !distinct {
2288            self.eat_keyword(Keyword::All);
2289        }
2290        let projection = self.projection_list()?;
2291        let from = if self.eat_keyword(Keyword::From) {
2292            self.parse_from()?
2293        } else {
2294            Vec::new()
2295        };
2296        let filter = if self.eat_keyword(Keyword::Where) {
2297            Some(self.expr(0)?)
2298        } else {
2299            None
2300        };
2301        // SP27: GROUP BY <expr-list> then HAVING <expr>, between WHERE and ORDER BY.
2302        let mut group_by = Vec::new();
2303        if self.eat_keyword(Keyword::Group) {
2304            self.expect(&Token::Keyword(Keyword::By))?;
2305            loop {
2306                group_by.push(self.expr(0)?);
2307                if self.eat_comma() {
2308                    continue;
2309                }
2310                break;
2311            }
2312        }
2313        let having = if self.eat_keyword(Keyword::Having) {
2314            Some(self.expr(0)?)
2315        } else {
2316            None
2317        };
2318        Ok(SelectStmt {
2319            projection,
2320            from,
2321            filter,
2322            distinct,
2323            group_by,
2324            having,
2325            order_by: Vec::new(),
2326            limit: None,
2327            offset: None,
2328            locking: None,
2329        })
2330    }
2331
2332    fn values_stmt(&mut self) -> Result<crate::ast::ValuesStmt, ParseError> {
2333        self.expect(&Token::Keyword(Keyword::Values))?;
2334        let mut rows = Vec::new();
2335        loop {
2336            self.expect(&Token::LParen)?;
2337            if *self.peek() == Token::RParen {
2338                return Err(ParseError::new(
2339                    "VALUES row must have at least one expression",
2340                    self.peek_pos(),
2341                ));
2342            }
2343            let mut row = vec![self.expr(0)?];
2344            while self.eat_comma() {
2345                row.push(self.expr(0)?);
2346            }
2347            self.expect(&Token::RParen)?;
2348            rows.push(row);
2349            if !self.eat_comma() {
2350                break;
2351            }
2352        }
2353        Ok(crate::ast::ValuesStmt { rows })
2354    }
2355
2356    /// Parse an optional `ORDER BY …`, then `LIMIT`/`OFFSET` in either order.
2357    /// The tuple is the three result-level tail components (`order_by`, `limit`, `offset`);
2358    /// a named struct would not read more clearly than the positional triple.
2359    fn parse_set_tail(&mut self) -> Result<SetTail, ParseError> {
2360        use crate::ast::OrderItem;
2361        let mut order_by = Vec::new();
2362        if self.eat_keyword(Keyword::Order) {
2363            self.expect(&Token::Keyword(Keyword::By))?;
2364            loop {
2365                let expr = self.expr(0)?;
2366                let asc = if self.eat_keyword(Keyword::Desc) {
2367                    false
2368                } else {
2369                    self.eat_keyword(Keyword::Asc);
2370                    true
2371                };
2372                order_by.push(OrderItem { expr, asc });
2373                if self.eat_comma() {
2374                    continue;
2375                }
2376                break;
2377            }
2378        }
2379        // SP28: LIMIT and OFFSET in either order (PostgreSQL accepts both).
2380        let mut limit = None;
2381        let mut offset = None;
2382        loop {
2383            if limit.is_none() && self.eat_keyword(Keyword::Limit) {
2384                limit = Some(self.expect_int_count("LIMIT")?);
2385            } else if offset.is_none() && self.eat_keyword(Keyword::Offset) {
2386                offset = Some(self.expect_int_count("OFFSET")?);
2387            } else {
2388                break;
2389            }
2390        }
2391        Ok((order_by, limit, offset))
2392    }
2393
2394    /// Parse an optional `FOR UPDATE` / `FOR SHARE` row-locking clause.
2395    fn parse_locking(&mut self) -> Result<Option<crate::ast::RowLockStrength>, ParseError> {
2396        if self.eat_keyword(Keyword::For) {
2397            if self.eat_keyword(Keyword::Update) {
2398                Ok(Some(crate::ast::RowLockStrength::ForUpdate))
2399            } else if self.eat_keyword(Keyword::Share) {
2400                Ok(Some(crate::ast::RowLockStrength::ForShare))
2401            } else {
2402                Err(ParseError::new(
2403                    "expected UPDATE or SHARE after FOR",
2404                    self.peek_pos(),
2405                ))
2406            }
2407        } else {
2408            Ok(None)
2409        }
2410    }
2411
2412    fn query_statement(&mut self) -> Result<crate::ast::Statement, ParseError> {
2413        Ok(crate::ast::Statement::Query(self.query_expr()?))
2414    }
2415
2416    fn query_expr(&mut self) -> Result<crate::ast::QueryExpr, ParseError> {
2417        let with = self.parse_with_clause()?;
2418        if *self.peek() == Token::LParen {
2419            let q = self.parenthesized_query_expr()?;
2420            if self.peek_is_set_op() {
2421                let left = self.query_expr_as_set_branch(q)?;
2422                let body = self.set_expr_rest(left, 0)?;
2423                let (order_by, limit, offset, locking) = self.parse_query_tail_and_locking()?;
2424                let mut q = self.finish_query_expr(body, order_by, limit, offset, locking)?;
2425                q.with = with;
2426                return Ok(q);
2427            }
2428            if !self.query_tail_or_locking_starts() {
2429                let mut q = q;
2430                q.with = with;
2431                return Ok(q);
2432            }
2433            let body = Self::query_expr_as_outer_primary(q);
2434            let (order_by, limit, offset, locking) = self.parse_query_tail_and_locking()?;
2435            let mut q = self.finish_query_expr(body, order_by, limit, offset, locking)?;
2436            q.with = with;
2437            return Ok(q);
2438        }
2439        let body = self.set_expr(0)?;
2440        let (order_by, limit, offset, locking) = self.parse_query_tail_and_locking()?;
2441        let mut q = self.finish_query_expr(body, order_by, limit, offset, locking)?;
2442        q.with = with;
2443        Ok(q)
2444    }
2445
2446    fn parse_query_tail_and_locking(&mut self) -> Result<QueryTailAndLocking, ParseError> {
2447        let (order_by, limit, offset) = self.parse_set_tail()?;
2448        let locking = self.parse_locking()?;
2449        Ok((order_by, limit, offset, locking))
2450    }
2451
2452    fn query_expr_as_set_branch(
2453        &mut self,
2454        q: crate::ast::QueryExpr,
2455    ) -> Result<crate::ast::SetExpr, ParseError> {
2456        use crate::ast::{QueryBody, SetExpr};
2457        let has_tail = q.with.is_some()
2458            || !q.order_by.is_empty()
2459            || q.limit.is_some()
2460            || q.offset.is_some()
2461            || q.locking.is_some();
2462        if !has_tail {
2463            return Ok(q.body);
2464        }
2465        if q.locking.is_some() {
2466            return Err(ParseError::new(
2467                "FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT",
2468                self.peek_pos(),
2469            ));
2470        }
2471        match q.body {
2472            SetExpr::Query(QueryBody::Select(mut select)) => {
2473                select.order_by = q.order_by;
2474                select.limit = q.limit;
2475                select.offset = q.offset;
2476                Ok(SetExpr::Query(QueryBody::Select(select)))
2477            }
2478            body => Ok(SetExpr::Query(QueryBody::Nested(Box::new(
2479                crate::ast::QueryExpr {
2480                    with: q.with,
2481                    body,
2482                    order_by: q.order_by,
2483                    limit: q.limit,
2484                    offset: q.offset,
2485                    locking: q.locking,
2486                },
2487            )))),
2488        }
2489    }
2490
2491    fn query_expr_as_outer_primary(q: crate::ast::QueryExpr) -> crate::ast::SetExpr {
2492        use crate::ast::{QueryBody, SetExpr};
2493        let has_tail = q.with.is_some()
2494            || !q.order_by.is_empty()
2495            || q.limit.is_some()
2496            || q.offset.is_some()
2497            || q.locking.is_some();
2498        if has_tail {
2499            SetExpr::Query(QueryBody::Nested(Box::new(q)))
2500        } else {
2501            q.body
2502        }
2503    }
2504
2505    fn finish_query_expr(
2506        &mut self,
2507        body: crate::ast::SetExpr,
2508        order_by: Vec<crate::ast::OrderItem>,
2509        limit: Option<i64>,
2510        offset: Option<i64>,
2511        locking: Option<crate::ast::RowLockStrength>,
2512    ) -> Result<crate::ast::QueryExpr, ParseError> {
2513        use crate::ast::{QueryBody, QueryExpr, SetExpr};
2514        if locking.is_some() {
2515            match &body {
2516                SetExpr::Query(QueryBody::Select(_)) => {}
2517                SetExpr::Query(QueryBody::Values(_)) => {
2518                    return Err(ParseError::new(
2519                        "FOR UPDATE/SHARE is not allowed with VALUES",
2520                        self.peek_pos(),
2521                    ));
2522                }
2523                SetExpr::Query(QueryBody::Nested(_)) => {
2524                    return Err(ParseError::new(
2525                        "FOR UPDATE/SHARE is not allowed with a nested query expression",
2526                        self.peek_pos(),
2527                    ));
2528                }
2529                SetExpr::SetOp { .. } => {
2530                    return Err(ParseError::new(
2531                        "FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT",
2532                        self.peek_pos(),
2533                    ));
2534                }
2535            }
2536        }
2537        Ok(QueryExpr {
2538            with: None,
2539            body,
2540            order_by,
2541            limit,
2542            offset,
2543            locking,
2544        })
2545    }
2546
2547    fn peek_is_set_op(&self) -> bool {
2548        matches!(
2549            self.peek(),
2550            Token::Keyword(Keyword::Union | Keyword::Except | Keyword::Intersect)
2551        )
2552    }
2553
2554    fn query_tail_or_locking_starts(&self) -> bool {
2555        matches!(
2556            self.peek(),
2557            Token::Keyword(Keyword::Order | Keyword::Limit | Keyword::Offset | Keyword::For)
2558        )
2559    }
2560
2561    fn parse_with_clause(&mut self) -> Result<Option<crate::ast::WithClause>, ParseError> {
2562        use crate::ast::{Cte, WithClause};
2563        if !self.eat_keyword(Keyword::With) {
2564            return Ok(None);
2565        }
2566        let recursive = self.eat_keyword(Keyword::Recursive);
2567        let mut ctes = Vec::new();
2568        loop {
2569            let name = self.expect_ident()?;
2570            if ctes.iter().any(|c: &Cte| c.name == name) {
2571                return Err(ParseError::new_sqlstate(
2572                    "42712",
2573                    format!("table name \"{name}\" specified more than once"),
2574                    self.peek_pos(),
2575                ));
2576            }
2577            let columns = if *self.peek() == Token::LParen {
2578                self.bump();
2579                let mut cols = Vec::new();
2580                loop {
2581                    cols.push(self.expect_ident()?);
2582                    if self.eat_comma() {
2583                        continue;
2584                    }
2585                    break;
2586                }
2587                self.expect(&Token::RParen)?;
2588                Some(cols)
2589            } else {
2590                None
2591            };
2592            self.expect(&Token::Keyword(Keyword::As))?;
2593            self.expect(&Token::LParen)?;
2594            let query = self.query_expr()?;
2595            self.expect(&Token::RParen)?;
2596            ctes.push(Cte {
2597                name,
2598                columns,
2599                query,
2600            });
2601            if !self.eat_comma() {
2602                break;
2603            }
2604        }
2605        Ok(Some(WithClause { recursive, ctes }))
2606    }
2607
2608    /// Precedence-climbing set-op tree. INTERSECT = 2, UNION/EXCEPT = 1; all
2609    /// left-associative (recurse for the RHS at `prec + 1`).
2610    fn set_expr(&mut self, min_prec: u8) -> Result<crate::ast::SetExpr, ParseError> {
2611        // Mode-1 guard: a parenthesized set-op subtree recurses
2612        // `set_primary → set_expr → set_primary` for `(((… query …)))`, a path that
2613        // does NOT funnel through `expr`/`select_core`, so it needs its own guard.
2614        let _guard = DepthGuard::enter(&self.depth, self.peek_pos())?;
2615        let left = self.set_primary()?;
2616        self.set_expr_rest(left, min_prec)
2617    }
2618
2619    fn set_expr_rest(
2620        &mut self,
2621        mut left: crate::ast::SetExpr,
2622        min_prec: u8,
2623    ) -> Result<crate::ast::SetExpr, ParseError> {
2624        use crate::ast::{SetExpr, SetOp};
2625        // Mode-2 cap: a flat left-assoc chain `A UNION B UNION C …` is parsed by this
2626        // LOOP (not recursion), building an N-deep left-nested `SetExpr` that would
2627        // overflow the executor's `fold`/`resolve_set_columns` AND recursive `Drop`.
2628        // Capping the iterations prevents the over-deep tree (mirrors the Pratt loop).
2629        let mut iterations: usize = 0;
2630        loop {
2631            let (op, prec) = match self.peek() {
2632                Token::Keyword(Keyword::Union) => (SetOp::Union, 1u8),
2633                Token::Keyword(Keyword::Except) => (SetOp::Except, 1u8),
2634                Token::Keyword(Keyword::Intersect) => (SetOp::Intersect, 2u8),
2635                _ => break,
2636            };
2637            if prec < min_prec {
2638                break;
2639            }
2640            iterations += 1;
2641            if iterations > MAX_DEPTH {
2642                return Err(ParseError::too_deep(self.peek_pos()));
2643            }
2644            self.bump(); // the operator keyword
2645            let all = self.eat_keyword(Keyword::All);
2646            if !all {
2647                self.eat_keyword(Keyword::Distinct); // explicit default modifier
2648            }
2649            let right = self.set_expr(prec + 1)?;
2650            left = SetExpr::SetOp {
2651                op,
2652                all,
2653                left: Box::new(left),
2654                right: Box::new(right),
2655            };
2656        }
2657        Ok(left)
2658    }
2659
2660    /// A set-op primary: a parenthesized sub-query (precedence grouping, or a
2661    /// parenthesized single SELECT that keeps its own ORDER BY / LIMIT), or a bare
2662    /// SELECT branch (`select_core`, no tail — the query owns the tail).
2663    fn parenthesized_query_expr(&mut self) -> Result<crate::ast::QueryExpr, ParseError> {
2664        self.expect(&Token::LParen)?;
2665        self.query_expr_after_open_paren()
2666    }
2667
2668    fn query_expr_after_open_paren(&mut self) -> Result<crate::ast::QueryExpr, ParseError> {
2669        let with = self.parse_with_clause()?;
2670        let body = self.set_expr(0)?;
2671        let (order_by, limit, offset, locking) = self.parse_query_tail_and_locking()?;
2672        self.expect(&Token::RParen)?;
2673        let mut q = self.finish_query_expr(body, order_by, limit, offset, locking)?;
2674        q.with = with;
2675        Ok(q)
2676    }
2677
2678    fn set_primary(&mut self) -> Result<crate::ast::SetExpr, ParseError> {
2679        use crate::ast::{QueryBody, SetExpr};
2680        if *self.peek() == Token::LParen {
2681            self.bump(); // (
2682            if matches!(self.peek(), Token::Keyword(Keyword::With)) {
2683                let query = self.query_expr_after_open_paren()?;
2684                return Ok(Self::query_expr_as_outer_primary(query));
2685            }
2686            let inner = self.set_expr(0)?;
2687            let inner = self.attach_paren_tail(inner)?;
2688            self.expect(&Token::RParen)?;
2689            Ok(inner)
2690        } else if *self.peek() == Token::Keyword(Keyword::Values) {
2691            Ok(SetExpr::Query(QueryBody::Values(self.values_stmt()?)))
2692        } else {
2693            Ok(SetExpr::Query(QueryBody::Select(Box::new(
2694                self.select_core()?,
2695            ))))
2696        }
2697    }
2698
2699    /// If an ORDER BY / LIMIT / OFFSET follows inside parentheses, attach it to a
2700    /// lone-SELECT inner; otherwise preserve the tailed query as a nested primary.
2701    fn attach_paren_tail(
2702        &mut self,
2703        inner: crate::ast::SetExpr,
2704    ) -> Result<crate::ast::SetExpr, ParseError> {
2705        use crate::ast::{QueryBody, QueryExpr, SetExpr};
2706        let has_tail = matches!(
2707            self.peek(),
2708            Token::Keyword(Keyword::Order | Keyword::Limit | Keyword::Offset)
2709        );
2710        if !has_tail {
2711            return Ok(inner);
2712        }
2713        match inner {
2714            SetExpr::Query(QueryBody::Select(mut s)) => {
2715                let (order_by, limit, offset) = self.parse_set_tail()?;
2716                s.order_by = order_by;
2717                s.limit = limit;
2718                s.offset = offset;
2719                Ok(SetExpr::Query(QueryBody::Select(s)))
2720            }
2721            body => {
2722                let (order_by, limit, offset) = self.parse_set_tail()?;
2723                Ok(SetExpr::Query(QueryBody::Nested(Box::new(QueryExpr {
2724                    with: None,
2725                    body,
2726                    order_by,
2727                    limit,
2728                    offset,
2729                    locking: None,
2730                }))))
2731            }
2732        }
2733    }
2734
2735    /// Parse the FROM clause: a comma-separated list of join trees.
2736    fn parse_from(&mut self) -> Result<Vec<crate::ast::TableExpr>, ParseError> {
2737        let mut items = vec![self.join_tree()?];
2738        while self.eat_comma() {
2739            items.push(self.join_tree()?);
2740        }
2741        Ok(items)
2742    }
2743
2744    /// A left-associative chain of joins over table factors. `JOIN` binds tighter
2745    /// than the top-level comma (handled by `parse_from`).
2746    fn join_tree(&mut self) -> Result<crate::ast::TableExpr, ParseError> {
2747        use crate::ast::{JoinConstraint, JoinKind, TableExpr};
2748        let mut left = self.table_factor()?;
2749        loop {
2750            let (kind, natural) = if self.eat_keyword(Keyword::Natural) {
2751                (self.join_kind()?, true)
2752            } else if self.peek_is_join_start() {
2753                (self.join_kind()?, false)
2754            } else {
2755                break;
2756            };
2757            let right = self.table_factor()?;
2758            let constraint = if natural || kind == JoinKind::Cross {
2759                if natural {
2760                    JoinConstraint::Natural
2761                } else {
2762                    JoinConstraint::None
2763                }
2764            } else if self.eat_keyword(Keyword::On) {
2765                JoinConstraint::On(self.expr(0)?)
2766            } else if self.eat_keyword(Keyword::Using) {
2767                self.expect(&Token::LParen)?;
2768                let mut cols = vec![self.expect_ident()?];
2769                while self.eat_comma() {
2770                    cols.push(self.expect_ident()?);
2771                }
2772                self.expect(&Token::RParen)?;
2773                JoinConstraint::Using(cols)
2774            } else {
2775                return Err(ParseError::new(
2776                    "expected ON or USING after JOIN",
2777                    self.peek_pos(),
2778                ));
2779            };
2780            left = TableExpr::Join {
2781                left: Box::new(left),
2782                right: Box::new(right),
2783                kind,
2784                constraint,
2785            };
2786        }
2787        Ok(left)
2788    }
2789
2790    /// True if the next token begins a join clause (after an optional NATURAL).
2791    fn peek_is_join_start(&self) -> bool {
2792        matches!(
2793            self.peek(),
2794            Token::Keyword(
2795                Keyword::Join
2796                    | Keyword::Inner
2797                    | Keyword::Left
2798                    | Keyword::Right
2799                    | Keyword::Full
2800                    | Keyword::Cross,
2801            )
2802        )
2803    }
2804
2805    /// Consume a join-kind prefix and the `JOIN` keyword. `INNER`/`LEFT`/`RIGHT`/
2806    /// `FULL` may be followed by `OUTER`; a bare `JOIN` is INNER.
2807    fn join_kind(&mut self) -> Result<crate::ast::JoinKind, ParseError> {
2808        use crate::ast::JoinKind;
2809        let kind = if self.eat_keyword(Keyword::Inner) {
2810            JoinKind::Inner
2811        } else if self.eat_keyword(Keyword::Left) {
2812            self.eat_keyword(Keyword::Outer);
2813            JoinKind::Left
2814        } else if self.eat_keyword(Keyword::Right) {
2815            self.eat_keyword(Keyword::Outer);
2816            JoinKind::Right
2817        } else if self.eat_keyword(Keyword::Full) {
2818            self.eat_keyword(Keyword::Outer);
2819            JoinKind::Full
2820        } else if self.eat_keyword(Keyword::Cross) {
2821            JoinKind::Cross
2822        } else {
2823            JoinKind::Inner // a bare JOIN
2824        };
2825        self.expect(&Token::Keyword(Keyword::Join))?;
2826        Ok(kind)
2827    }
2828
2829    fn starts_query_expr(&self) -> bool {
2830        matches!(
2831            self.peek(),
2832            Token::Keyword(Keyword::Select | Keyword::Values | Keyword::With) | Token::LParen
2833        )
2834    }
2835
2836    /// A table factor: a base table (`t` / `t alias` / `t AS alias`), a derived
2837    /// table (`( SELECT … ) alias`), or a parenthesized join (`( … )`).
2838    fn table_factor(&mut self) -> Result<crate::ast::TableExpr, ParseError> {
2839        use crate::ast::TableExpr;
2840        if *self.peek() == Token::LParen {
2841            self.bump();
2842            if matches!(
2843                self.peek(),
2844                Token::Keyword(Keyword::Select | Keyword::Values | Keyword::With)
2845            ) {
2846                let subquery = self.query_expr_after_open_paren()?;
2847                let alias = self.opt_alias()?.ok_or_else(|| {
2848                    ParseError::new("subquery in FROM must have an alias", self.peek_pos())
2849                })?;
2850                let columns = self.opt_column_aliases()?;
2851                return Ok(TableExpr::Derived {
2852                    subquery,
2853                    alias,
2854                    columns,
2855                });
2856            }
2857            let inner = self.join_tree()?;
2858            self.expect(&Token::RParen)?;
2859            return Ok(inner);
2860        }
2861        let mut name = self.expect_ident()?;
2862        if *self.peek() == Token::Dot {
2863            self.bump();
2864            let object = self.expect_ident()?;
2865            name = format!("{name}.{object}");
2866        }
2867        let alias = self.opt_alias()?;
2868        Ok(TableExpr::Table { name, alias })
2869    }
2870
2871    /// An optional table alias: `AS ident`, or a bare `ident` that is not a
2872    /// keyword (so `FROM t JOIN …` does not read `JOIN` as an alias).
2873    fn opt_alias(&mut self) -> Result<Option<String>, ParseError> {
2874        if self.eat_keyword(Keyword::As) {
2875            return Ok(Some(self.expect_ident()?));
2876        }
2877        if let Token::Ident(_) = self.peek() {
2878            return Ok(Some(self.expect_ident()?));
2879        }
2880        Ok(None)
2881    }
2882
2883    fn opt_column_aliases(&mut self) -> Result<Option<Vec<String>>, ParseError> {
2884        if *self.peek() != Token::LParen {
2885            return Ok(None);
2886        }
2887        self.bump();
2888        let mut cols = vec![self.expect_ident()?];
2889        while self.eat_comma() {
2890            cols.push(self.expect_ident()?);
2891        }
2892        self.expect(&Token::RParen)?;
2893        Ok(Some(cols))
2894    }
2895
2896    /// Parse the integer count after a `LIMIT`/`OFFSET` keyword (`what` names it
2897    /// in error messages).
2898    fn expect_int_count(&mut self, what: &str) -> Result<i64, ParseError> {
2899        let pos = self.peek_pos();
2900        match self.bump() {
2901            Token::IntLit(s) => s
2902                .parse::<i64>()
2903                .map_err(|_| ParseError::new(format!("{what} value out of range"), pos)),
2904            other => Err(ParseError::new(
2905                format!("expected {what} count, found {other:?}"),
2906                pos,
2907            )),
2908        }
2909    }
2910
2911    fn eat_comma(&mut self) -> bool {
2912        if *self.peek() == Token::Comma {
2913            self.bump();
2914            true
2915        } else {
2916            false
2917        }
2918    }
2919
2920    /// Consume a string literal or return a 42601 parse error. Used for OPTIONS values.
2921    fn expect_string_lit(&mut self) -> Result<String, ParseError> {
2922        match self.bump() {
2923            Token::StringLit(s) => Ok(s),
2924            other => Err(ParseError::new(
2925                format!("expected string literal, found {other:?}"),
2926                self.peek_pos(),
2927            )),
2928        }
2929    }
2930
2931    /// Parse `OPTIONS ( ident 'string' [, …] )`. Returns an empty list if OPTIONS is absent.
2932    fn parse_options(&mut self) -> Result<crate::ast::OptionList, ParseError> {
2933        if !self.eat_keyword(Keyword::Options) {
2934            return Ok(vec![]);
2935        }
2936        self.expect(&Token::LParen)?;
2937        let mut opts = Vec::new();
2938        loop {
2939            let k = self.expect_ident()?;
2940            let v = self.expect_string_lit()?;
2941            opts.push((k, v));
2942            if self.eat_comma() {
2943                continue;
2944            }
2945            break;
2946        }
2947        self.expect(&Token::RParen)?;
2948        Ok(opts)
2949    }
2950
2951    /// Parse the `FOR <user>` clause of `CREATE/ALTER/DROP USER MAPPING`.
2952    /// Returns the user name as a lowercase string. Accepts `PUBLIC`, `CURRENT_USER`,
2953    /// or a plain identifier.
2954    fn parse_user_mapping_user(&mut self) -> Result<String, ParseError> {
2955        self.expect(&Token::Keyword(Keyword::For))?;
2956        match self.peek().clone() {
2957            Token::Keyword(Keyword::Public) => {
2958                self.bump();
2959                Ok("public".into())
2960            }
2961            Token::Keyword(Keyword::CurrentUser) => {
2962                self.bump();
2963                Ok("current_user".into())
2964            }
2965            Token::Ident(_) => self.expect_ident(),
2966            other => Err(ParseError::new(
2967                format!("expected user name after FOR, found {other:?}"),
2968                self.peek_pos(),
2969            )),
2970        }
2971    }
2972
2973    // SP40: FDW DDL parse functions
2974
2975    /// `CREATE FOREIGN DATA WRAPPER <name> OPTIONS (…)`
2976    fn create_fdw(&mut self) -> Result<crate::ast::Statement, ParseError> {
2977        use crate::ast::Statement;
2978        self.expect(&Token::Keyword(Keyword::Create))?;
2979        self.expect(&Token::Keyword(Keyword::Foreign))?;
2980        self.expect(&Token::Keyword(Keyword::Data))?;
2981        self.expect(&Token::Keyword(Keyword::Wrapper))?;
2982        let name = self.expect_ident()?;
2983        let options = self.parse_options()?;
2984        Ok(Statement::CreateFdw { name, options })
2985    }
2986
2987    /// `DROP FOREIGN DATA WRAPPER [IF EXISTS] <name>`
2988    fn drop_fdw(&mut self) -> Result<crate::ast::Statement, ParseError> {
2989        use crate::ast::Statement;
2990        self.expect(&Token::Keyword(Keyword::Drop))?;
2991        self.expect(&Token::Keyword(Keyword::Foreign))?;
2992        self.expect(&Token::Keyword(Keyword::Data))?;
2993        self.expect(&Token::Keyword(Keyword::Wrapper))?;
2994        let if_exists = self.eat_if_exists()?;
2995        let name = self.expect_ident()?;
2996        Ok(Statement::DropFdw { name, if_exists })
2997    }
2998
2999    /// `CREATE SERVER <name> FOREIGN DATA WRAPPER <wrapper> OPTIONS (…)`
3000    fn create_server(&mut self) -> Result<crate::ast::Statement, ParseError> {
3001        use crate::ast::Statement;
3002        self.expect(&Token::Keyword(Keyword::Create))?;
3003        self.expect(&Token::Keyword(Keyword::Server))?;
3004        let name = self.expect_ident()?;
3005        self.expect(&Token::Keyword(Keyword::Foreign))?;
3006        self.expect(&Token::Keyword(Keyword::Data))?;
3007        self.expect(&Token::Keyword(Keyword::Wrapper))?;
3008        let wrapper = self.expect_ident()?;
3009        let options = self.parse_options()?;
3010        Ok(Statement::CreateServer {
3011            name,
3012            wrapper,
3013            options,
3014        })
3015    }
3016
3017    /// `ALTER SERVER <name> OPTIONS (…)`
3018    fn alter_server(&mut self) -> Result<crate::ast::Statement, ParseError> {
3019        use crate::ast::Statement;
3020        // ALTER is not a keyword yet; matched as ident
3021        self.bump(); // ALTER
3022        self.expect(&Token::Keyword(Keyword::Server))?;
3023        let name = self.expect_ident()?;
3024        let options = self.parse_options()?;
3025        Ok(Statement::AlterServer { name, options })
3026    }
3027
3028    /// `DROP SERVER [IF EXISTS] <name>`
3029    fn drop_server(&mut self) -> Result<crate::ast::Statement, ParseError> {
3030        use crate::ast::Statement;
3031        self.expect(&Token::Keyword(Keyword::Drop))?;
3032        self.expect(&Token::Keyword(Keyword::Server))?;
3033        let if_exists = self.eat_if_exists()?;
3034        let name = self.expect_ident()?;
3035        Ok(Statement::DropServer { name, if_exists })
3036    }
3037
3038    /// `CREATE USER MAPPING FOR <user> SERVER <server> OPTIONS (…)`
3039    fn create_user_mapping(&mut self) -> Result<crate::ast::Statement, ParseError> {
3040        use crate::ast::Statement;
3041        self.expect(&Token::Keyword(Keyword::Create))?;
3042        self.expect(&Token::Keyword(Keyword::User))?;
3043        self.expect(&Token::Keyword(Keyword::Mapping))?;
3044        let user = self.parse_user_mapping_user()?;
3045        self.expect(&Token::Keyword(Keyword::Server))?;
3046        let server = self.expect_ident()?;
3047        let options = self.parse_options()?;
3048        Ok(Statement::CreateUserMapping {
3049            user,
3050            server,
3051            options,
3052        })
3053    }
3054
3055    /// `ALTER USER MAPPING FOR <user> SERVER <server> OPTIONS (…)`
3056    fn alter_user_mapping(&mut self) -> Result<crate::ast::Statement, ParseError> {
3057        use crate::ast::Statement;
3058        // ALTER is not a keyword yet; matched as ident
3059        self.bump(); // ALTER
3060        self.expect(&Token::Keyword(Keyword::User))?;
3061        self.expect(&Token::Keyword(Keyword::Mapping))?;
3062        let user = self.parse_user_mapping_user()?;
3063        self.expect(&Token::Keyword(Keyword::Server))?;
3064        let server = self.expect_ident()?;
3065        let options = self.parse_options()?;
3066        Ok(Statement::AlterUserMapping {
3067            user,
3068            server,
3069            options,
3070        })
3071    }
3072
3073    /// `DROP USER MAPPING [IF EXISTS] FOR <user> SERVER <server>`
3074    fn drop_user_mapping(&mut self) -> Result<crate::ast::Statement, ParseError> {
3075        use crate::ast::Statement;
3076        self.expect(&Token::Keyword(Keyword::Drop))?;
3077        self.expect(&Token::Keyword(Keyword::User))?;
3078        self.expect(&Token::Keyword(Keyword::Mapping))?;
3079        let if_exists = self.eat_if_exists()?;
3080        let user = self.parse_user_mapping_user()?;
3081        self.expect(&Token::Keyword(Keyword::Server))?;
3082        let server = self.expect_ident()?;
3083        Ok(Statement::DropUserMapping {
3084            user,
3085            server,
3086            if_exists,
3087        })
3088    }
3089
3090    /// `CREATE FOREIGN TABLE <name> (<col> <type>, …) SERVER <server> OPTIONS (…)`
3091    fn create_foreign_table(&mut self) -> Result<crate::ast::Statement, ParseError> {
3092        use crate::ast::{ColumnDef, Statement};
3093        self.expect(&Token::Keyword(Keyword::Create))?;
3094        self.expect(&Token::Keyword(Keyword::Foreign))?;
3095        self.expect(&Token::Keyword(Keyword::Table))?;
3096        let name = self.expect_ident()?;
3097        self.expect(&Token::LParen)?;
3098        let mut columns = Vec::new();
3099        loop {
3100            let col_name = self.expect_ident()?;
3101            let ty = self.parse_type_name()?;
3102            columns.push(ColumnDef {
3103                name: col_name,
3104                ty,
3105                serial: None,
3106                constraints: Vec::new(),
3107            });
3108            if self.eat_comma() {
3109                continue;
3110            }
3111            break;
3112        }
3113        self.expect(&Token::RParen)?;
3114        self.expect(&Token::Keyword(Keyword::Server))?;
3115        let server = self.expect_ident()?;
3116        let options = self.parse_options()?;
3117        Ok(Statement::CreateForeignTable {
3118            name,
3119            columns,
3120            server,
3121            options,
3122        })
3123    }
3124
3125    /// `DROP FOREIGN TABLE [IF EXISTS] <name>`
3126    fn drop_foreign_table(&mut self) -> Result<crate::ast::Statement, ParseError> {
3127        use crate::ast::Statement;
3128        self.expect(&Token::Keyword(Keyword::Drop))?;
3129        self.expect(&Token::Keyword(Keyword::Foreign))?;
3130        self.expect(&Token::Keyword(Keyword::Table))?;
3131        let if_exists = self.eat_if_exists()?;
3132        let name = self.expect_ident()?;
3133        Ok(Statement::DropForeignTable { name, if_exists })
3134    }
3135
3136    /// `IMPORT FOREIGN SCHEMA <remote_schema> [LIMIT TO | EXCEPT (<tables>)] FROM SERVER <server> [INTO <schema>]`
3137    fn import_foreign_schema(&mut self) -> Result<crate::ast::Statement, ParseError> {
3138        use crate::ast::{ImportSelector, Statement};
3139        self.expect(&Token::Keyword(Keyword::Import))?;
3140        self.expect(&Token::Keyword(Keyword::Foreign))?;
3141        self.expect(&Token::Keyword(Keyword::Schema))?;
3142        let remote_schema = self.expect_ident()?;
3143        let selector = if self.eat_keyword(Keyword::Limit) {
3144            self.expect(&Token::Keyword(Keyword::To))?;
3145            ImportSelector::LimitTo(self.parse_ident_list()?)
3146        } else if self.eat_keyword(Keyword::Except) {
3147            ImportSelector::Except(self.parse_ident_list()?)
3148        } else {
3149            ImportSelector::All
3150        };
3151        self.expect(&Token::Keyword(Keyword::From))?;
3152        self.expect(&Token::Keyword(Keyword::Server))?;
3153        let server = self.expect_ident()?;
3154        let into_schema = if self.eat_keyword(Keyword::Into) {
3155            // INTO public — `public` is a keyword here
3156            match self.peek().clone() {
3157                Token::Keyword(Keyword::Public) => {
3158                    self.bump();
3159                    "public".into()
3160                }
3161                Token::Ident(_) => self.expect_ident()?,
3162                other => {
3163                    return Err(ParseError::new(
3164                        format!("expected schema name after INTO, found {other:?}"),
3165                        self.peek_pos(),
3166                    ));
3167                }
3168            }
3169        } else {
3170            "public".into()
3171        };
3172        Ok(Statement::ImportForeignSchema {
3173            remote_schema,
3174            selector,
3175            server,
3176            into_schema,
3177        })
3178    }
3179
3180    /// Parse `( ident, ident, … )` — used by `IMPORT FOREIGN SCHEMA`.
3181    fn parse_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
3182        self.expect(&Token::LParen)?;
3183        let mut names = vec![self.expect_ident()?];
3184        while self.eat_comma() {
3185            names.push(self.expect_ident()?);
3186        }
3187        self.expect(&Token::RParen)?;
3188        Ok(names)
3189    }
3190
3191    /// Consume `IF EXISTS` if present, returning whether it was seen.
3192    ///
3193    /// Returns `Ok(true)` when `IF EXISTS` is consumed, `Ok(false)` when `IF`
3194    /// is absent, and `Err` (SQLSTATE 42601) when `IF` is present but `EXISTS`
3195    /// does not follow — a malformed clause like `DROP SERVER IF NOTEXIST s`.
3196    fn eat_if_exists(&mut self) -> Result<bool, ParseError> {
3197        if self.eat_keyword(Keyword::If) {
3198            // `EXISTS` is always a keyword (Keyword::Exists) in the lexer.
3199            if *self.peek() == Token::Keyword(Keyword::Exists) {
3200                self.bump();
3201                return Ok(true);
3202            }
3203            // `IF` was consumed but `EXISTS` did not follow — reject with a
3204            // clear syntax error instead of silently mis-parsing the statement.
3205            return Err(ParseError::new(
3206                format!("expected EXISTS after IF, found {:?}", self.peek()),
3207                self.peek_pos(),
3208            ));
3209        }
3210        Ok(false)
3211    }
3212}
3213
3214/// Test-support entry: parse a bare expression. `pub` (not cfg(test)) so the
3215/// executor crate's tests can reuse it; `doc(hidden)` keeps it out of the API.
3216///
3217/// # Errors
3218///
3219/// Returns a parse error when `sql` is not exactly one valid expression.
3220#[doc(hidden)]
3221pub fn parse_expr_for_test(sql: &str) -> Result<Expr, ParseError> {
3222    let mut p = Parser::new(lex(sql)?, sql.to_string());
3223    let e = p.expr(0)?;
3224    if *p.peek() != Token::Eof {
3225        return Err(ParseError::new(
3226            "trailing tokens after expression",
3227            p.peek_pos(),
3228        ));
3229    }
3230    Ok(e)
3231}
3232
3233/// Public statement entry — implemented in Task 12.
3234///
3235/// # Errors
3236///
3237/// Returns a parse error when the SQL text cannot be tokenized or parsed.
3238pub fn parse(sql: &str) -> Result<Vec<crate::ast::Statement>, ParseError> {
3239    if let Some((statement, _identity)) = bounded_non_goal_refusal(sql) {
3240        return Ok(vec![statement]);
3241    }
3242    let mut p = Parser::new(lex(sql)?, sql.to_string());
3243    Ok(p.program_spanned()?
3244        .into_iter()
3245        .map(|(parsed, _)| parsed.statement)
3246        .collect())
3247}
3248
3249/// Parse statements and return the parser-owned accepted command identity for
3250/// each one. Identity classification is the same mandatory gate used by [`parse`].
3251///
3252/// # Errors
3253///
3254/// Returns a parse error when the SQL text cannot be tokenized, parsed, or
3255/// classified.
3256pub fn parse_with_command_identities(
3257    sql: &str,
3258) -> Result<Vec<(crate::ast::Statement, crate::command::CommandIdentity)>, ParseError> {
3259    if let Some((statement, identity)) = bounded_non_goal_refusal(sql) {
3260        return Ok(vec![(statement, identity)]);
3261    }
3262    let mut parser = Parser::new(lex(sql)?, sql.to_string());
3263    parser
3264        .program_spanned()?
3265        .into_iter()
3266        .map(|(parsed, _range)| Ok((parsed.statement, parsed.command_identity)))
3267        .collect()
3268}
3269
3270/// Parse `sql` into statements, each paired with its EXACT source text — the byte
3271/// slice of `sql` spanning that statement, trimmed of surrounding whitespace. The
3272/// multi-range gateway uses this to forward an INDIVIDUAL statement (not the whole
3273/// `;`-separated simple-query frame) to a remote range's leader, so a frame mixing a
3274/// local and a remote range never re-runs the local statement on the remote node.
3275///
3276/// # Errors
3277///
3278/// Returns a parse error when the SQL text cannot be tokenized or parsed.
3279pub fn parse_with_source(sql: &str) -> Result<Vec<(crate::ast::Statement, String)>, ParseError> {
3280    if let Some((statement, _identity)) = bounded_non_goal_refusal(sql) {
3281        return Ok(vec![(
3282            statement,
3283            sql.trim().trim_end_matches(';').trim().to_string(),
3284        )]);
3285    }
3286    let mut p = Parser::new(lex(sql)?, sql.to_string());
3287    p.program_spanned()?
3288        .into_iter()
3289        .map(|(parsed, range)| Ok((parsed.statement, sql[range].trim().to_string())))
3290        .collect()
3291}
3292
3293fn bounded_non_goal_refusal(
3294    sql: &str,
3295) -> Option<(crate::ast::Statement, crate::command::CommandIdentity)> {
3296    let trimmed = sql.trim();
3297    let statement = trimmed.strip_suffix(';').unwrap_or(trimmed).trim();
3298    let candidate = lex(statement).ok()?;
3299    crate::ast::NON_GOAL_REFUSALS
3300        .iter()
3301        .find(|spec| refusal_tokens_match(&candidate, spec.representative_sql))
3302        .map(|spec| {
3303            (
3304                crate::ast::Statement::CompatibilityRefusal(spec.command),
3305                spec.identity,
3306            )
3307        })
3308}
3309
3310fn refusal_tokens_match(candidate: &[(Token, usize)], representative: &str) -> bool {
3311    const IDENTIFIER_SLOTS: &[&str] = &[
3312        "conv",
3313        "conv2",
3314        "lang",
3315        "lang2",
3316        "postgres",
3317        "opc",
3318        "opc2",
3319        "opf",
3320        "opf2",
3321        "pub",
3322        "r",
3323        "r2",
3324        "sub",
3325        "ts",
3326        "ts2",
3327        "p",
3328        "p2",
3329        "t",
3330        "t2",
3331        "am",
3332        "handler_fn",
3333        "func",
3334        "int4eq",
3335        "f",
3336    ];
3337    let Ok(pattern) = lex(representative) else {
3338        return false;
3339    };
3340    candidate.len() == pattern.len()
3341        && candidate
3342            .iter()
3343            .zip(pattern)
3344            .all(|((actual, _), (expected, _))| match (&expected, actual) {
3345                (Token::Ident(slot), Token::Ident(_))
3346                    if IDENTIFIER_SLOTS.contains(&slot.as_str()) =>
3347                {
3348                    true
3349                }
3350                (Token::StringLit(_), Token::StringLit(_))
3351                | (Token::IntLit(_), Token::IntLit(_)) => true,
3352                _ => actual == &expected,
3353            })
3354}
3355
3356fn encode_sequence_options(options: &crate::ast::SequenceOptions) -> Vec<String> {
3357    let mut encoded = Vec::new();
3358    if let Some(value) = options.start {
3359        encoded.push(format!("start={value}"));
3360    }
3361    if let Some(value) = options.increment {
3362        encoded.push(format!("increment={value}"));
3363    }
3364    if let Some(value) = options.min {
3365        encoded.push(format!("min={value}"));
3366    }
3367    if let Some(value) = options.max {
3368        encoded.push(format!("max={value}"));
3369    }
3370    if let Some(value) = options.cache {
3371        encoded.push(format!("cache={value}"));
3372    }
3373    if let Some(value) = options.cycle {
3374        encoded.push(format!("cycle={value}"));
3375    }
3376    encoded
3377}
3378
3379#[cfg(test)]
3380mod tests {
3381    use crabka_pgtypes::ColumnType;
3382
3383    use super::*;
3384    use crate::ast::{
3385        BinaryOp, ColumnConstraint, ColumnDef, Expr, HashShardingSpec, IndexPlacement,
3386        IsolationLevel, SelectItem, ShardingSpec, Statement, TableConstraint, UnaryOp,
3387    };
3388
3389    fn one(sql: &str) -> Statement {
3390        let mut v = parse(sql).expect("parse");
3391        assert_eq!(v.len(), 1);
3392        v.pop().expect("one statement")
3393    }
3394
3395    fn only_query(sql: &str) -> crate::ast::QueryExpr {
3396        let statements = crate::parse(sql).expect("parse ok");
3397        assert_eq!(statements.len(), 1);
3398        match statements.into_iter().next().expect("one statement") {
3399            Statement::Query(q) => q,
3400            other => panic!("expected Statement::Query, got {other:?}"),
3401        }
3402    }
3403
3404    fn only_select(sql: &str) -> crate::ast::SelectStmt {
3405        use crate::ast::{QueryBody, SetExpr};
3406        let q = only_query(sql);
3407        let SetExpr::Query(QueryBody::Select(select)) = q.body else {
3408            panic!("expected SELECT query body");
3409        };
3410        let mut select = *select;
3411        select.order_by = q.order_by;
3412        select.limit = q.limit;
3413        select.offset = q.offset;
3414        select.locking = q.locking;
3415        select
3416    }
3417
3418    #[test]
3419    fn row_producing_statements_share_query_expr_shape() {
3420        use crate::ast::{QueryBody, SetExpr};
3421
3422        let q = only_query("SELECT 1 ORDER BY 1 LIMIT 1");
3423        assert_eq!(q.order_by.len(), 1);
3424        assert_eq!(q.limit, Some(1));
3425        assert!(matches!(q.body, SetExpr::Query(QueryBody::Select(_))));
3426
3427        let q = only_query("VALUES (1), (2) ORDER BY 1 OFFSET 1");
3428        assert_eq!(q.order_by.len(), 1);
3429        assert_eq!(q.offset, Some(1));
3430        assert!(matches!(q.body, SetExpr::Query(QueryBody::Values(_))));
3431
3432        let q = only_query("SELECT 1 UNION ALL VALUES (2) ORDER BY 1");
3433        assert_eq!(q.order_by.len(), 1);
3434        assert!(matches!(q.body, SetExpr::SetOp { .. }));
3435    }
3436
3437    #[test]
3438    fn parses_view_ddl_and_retains_definition() {
3439        let Statement::CreateView {
3440            name,
3441            definition,
3442            query,
3443        } = one("CREATE VIEW \"Sales View\" AS SELECT id FROM orders WHERE id > 1")
3444        else {
3445            panic!("expected CREATE VIEW");
3446        };
3447        assert_eq!(name, "Sales View");
3448        assert_eq!(definition, "SELECT id FROM orders WHERE id > 1");
3449        assert!(matches!(
3450            query.body,
3451            crate::ast::SetExpr::Query(crate::ast::QueryBody::Select(_))
3452        ));
3453        assert_eq!(
3454            one("DROP VIEW IF EXISTS \"Sales View\""),
3455            Statement::DropView {
3456                name: "Sales View".into(),
3457                if_exists: true,
3458            }
3459        );
3460    }
3461
3462    #[test]
3463    fn legacy_query_forms_still_parse_after_query_unification() {
3464        for sql in [
3465            "SELECT a, b FROM t WHERE a > 1 ORDER BY b LIMIT 5",
3466            "VALUES (1), (2) ORDER BY 1",
3467            "(SELECT 1 ORDER BY 1 LIMIT 1) UNION SELECT 2 ORDER BY 1",
3468            "SELECT * FROM (SELECT 1 AS x) AS d",
3469            "SELECT * FROM (VALUES (1, 'a')) AS v(id, name)",
3470        ] {
3471            let q = only_query(sql);
3472            assert!(q.locking.is_none());
3473        }
3474    }
3475
3476    #[test]
3477    fn derived_and_expression_subqueries_accept_query_exprs() {
3478        use crate::ast::{Expr, QueryBody, SelectItem, SetExpr, TableExpr};
3479
3480        let outer = only_query("SELECT t.x FROM (SELECT 1 AS x UNION SELECT 2) AS t ORDER BY t.x");
3481        let SetExpr::Query(QueryBody::Select(select)) = outer.body else {
3482            panic!("expected outer SELECT query body");
3483        };
3484        let [
3485            TableExpr::Derived {
3486                subquery, alias, ..
3487            },
3488        ] = select.from.as_slice()
3489        else {
3490            panic!("expected one derived table");
3491        };
3492        assert_eq!(alias, "t");
3493        assert!(matches!(subquery.body, SetExpr::SetOp { .. }));
3494
3495        let scalar = only_query("SELECT (VALUES (1) UNION SELECT 2 ORDER BY 1 LIMIT 1)");
3496        let SetExpr::Query(QueryBody::Select(select)) = scalar.body else {
3497            panic!("expected SELECT");
3498        };
3499        let SelectItem::Expr { expr, .. } = &select.projection[0] else {
3500            panic!("expected expression projection");
3501        };
3502        let Expr::ScalarSubquery(q) = expr else {
3503            panic!("expected scalar query expression");
3504        };
3505        assert!(matches!(q.body, SetExpr::SetOp { .. }));
3506        assert_eq!(q.limit, Some(1));
3507    }
3508
3509    #[test]
3510    fn parenthesized_query_expr_tail_is_preserved_for_values_and_setops() {
3511        use crate::ast::{QueryBody, SetExpr, TableExpr};
3512
3513        let q = only_query("SELECT v.x FROM (VALUES (2), (1) ORDER BY 1 LIMIT 1) AS v(x)");
3514        let SetExpr::Query(QueryBody::Select(select)) = q.body else {
3515            panic!("expected SELECT");
3516        };
3517        let [TableExpr::Derived { subquery, .. }] = select.from.as_slice() else {
3518            panic!("expected one derived table");
3519        };
3520        assert!(matches!(
3521            subquery.body,
3522            SetExpr::Query(QueryBody::Values(_))
3523        ));
3524        assert_eq!(subquery.order_by.len(), 1);
3525        assert_eq!(subquery.limit, Some(1));
3526
3527        let q =
3528            only_query("SELECT s.x FROM (SELECT 2 AS x UNION SELECT 1 ORDER BY 1 LIMIT 1) AS s");
3529        let SetExpr::Query(QueryBody::Select(select)) = q.body else {
3530            panic!("expected SELECT");
3531        };
3532        let [TableExpr::Derived { subquery, .. }] = select.from.as_slice() else {
3533            panic!("expected one derived table");
3534        };
3535        assert!(matches!(subquery.body, SetExpr::SetOp { .. }));
3536        assert_eq!(subquery.order_by.len(), 1);
3537        assert_eq!(subquery.limit, Some(1));
3538    }
3539
3540    #[test]
3541    fn quantified_query_expr_preserves_tail() {
3542        let Expr::Quantified { subquery, .. } =
3543            expr("1 = ANY (SELECT 1 ORDER BY 1 LIMIT 1 OFFSET 0)")
3544        else {
3545            panic!("expected quantified query expression");
3546        };
3547        assert_eq!(subquery.order_by.len(), 1);
3548        assert_eq!(subquery.limit, Some(1));
3549        assert_eq!(subquery.offset, Some(0));
3550    }
3551
3552    #[test]
3553    fn nested_query_expr_locking_is_preserved_and_validated() {
3554        use crate::ast::{QueryBody, RowLockStrength, SelectItem, SetExpr};
3555
3556        let q = only_query("SELECT (SELECT 1 FOR UPDATE)");
3557        let SetExpr::Query(QueryBody::Select(select)) = q.body else {
3558            panic!("expected outer SELECT");
3559        };
3560        let SelectItem::Expr { expr, .. } = &select.projection[0] else {
3561            panic!("expected expression projection");
3562        };
3563        let Expr::ScalarSubquery(subquery) = expr else {
3564            panic!("expected scalar subquery");
3565        };
3566        assert_eq!(subquery.locking, Some(RowLockStrength::ForUpdate));
3567
3568        assert!(crate::parse("SELECT (VALUES (1) FOR UPDATE)").is_err());
3569        assert!(crate::parse("SELECT (SELECT 1 UNION SELECT 2 FOR UPDATE)").is_err());
3570    }
3571
3572    #[test]
3573    fn top_level_parenthesized_query_expr_tail_is_preserved() {
3574        use crate::ast::{QueryBody, SetExpr};
3575
3576        let q = only_query("(VALUES (2), (1) ORDER BY 1 LIMIT 1)");
3577        assert!(matches!(q.body, SetExpr::Query(QueryBody::Values(_))));
3578        assert_eq!(q.order_by.len(), 1);
3579        assert_eq!(q.limit, Some(1));
3580
3581        let q = only_query("(SELECT 2 UNION SELECT 1 ORDER BY 1 LIMIT 1)");
3582        assert!(matches!(q.body, SetExpr::SetOp { .. }));
3583        assert_eq!(q.order_by.len(), 1);
3584        assert_eq!(q.limit, Some(1));
3585    }
3586
3587    #[test]
3588    fn parenthesized_query_expr_outer_tail_preserves_inner_values_and_setop_tails() {
3589        use crate::ast::{QueryBody, SetExpr};
3590
3591        let q = only_query("(VALUES (2), (1) ORDER BY 1) LIMIT 1");
3592        assert_eq!(q.limit, Some(1));
3593        assert!(q.order_by.is_empty());
3594        let SetExpr::Query(QueryBody::Nested(inner)) = q.body else {
3595            panic!("expected nested VALUES query body");
3596        };
3597        assert_eq!(inner.order_by.len(), 1);
3598        assert_eq!(inner.limit, None);
3599        assert!(matches!(inner.body, SetExpr::Query(QueryBody::Values(_))));
3600
3601        let q = only_query("(SELECT 2 UNION SELECT 1 ORDER BY 1) LIMIT 1");
3602        assert_eq!(q.limit, Some(1));
3603        assert!(q.order_by.is_empty());
3604        let SetExpr::Query(QueryBody::Nested(inner)) = q.body else {
3605            panic!("expected nested set-op query body");
3606        };
3607        assert_eq!(inner.order_by.len(), 1);
3608        assert_eq!(inner.limit, None);
3609        assert!(matches!(inner.body, SetExpr::SetOp { .. }));
3610    }
3611
3612    #[test]
3613    fn redundant_parenthesized_query_expr_preserves_inner_values_and_setop_tails() {
3614        use crate::ast::{QueryBody, SetExpr};
3615
3616        let q = only_query("((VALUES (2), (1) ORDER BY 1))");
3617        assert!(q.order_by.is_empty());
3618        let SetExpr::Query(QueryBody::Nested(inner)) = q.body else {
3619            panic!("expected nested VALUES query body");
3620        };
3621        assert_eq!(inner.order_by.len(), 1);
3622        assert_eq!(inner.limit, None);
3623        assert!(matches!(inner.body, SetExpr::Query(QueryBody::Values(_))));
3624
3625        let q = only_query("((VALUES (2), (1) ORDER BY 1) LIMIT 1)");
3626        assert!(q.order_by.is_empty());
3627        assert_eq!(q.limit, Some(1));
3628        let SetExpr::Query(QueryBody::Nested(inner)) = q.body else {
3629            panic!("expected nested VALUES query body");
3630        };
3631        assert_eq!(inner.order_by.len(), 1);
3632        assert_eq!(inner.limit, None);
3633        assert!(matches!(inner.body, SetExpr::Query(QueryBody::Values(_))));
3634
3635        let q = only_query("((SELECT 2 UNION SELECT 1 ORDER BY 1))");
3636        assert!(q.order_by.is_empty());
3637        let SetExpr::Query(QueryBody::Nested(inner)) = q.body else {
3638            panic!("expected nested set-op query body");
3639        };
3640        assert_eq!(inner.order_by.len(), 1);
3641        assert_eq!(inner.limit, None);
3642        assert!(matches!(inner.body, SetExpr::SetOp { .. }));
3643
3644        let q = only_query("((SELECT 2 UNION SELECT 1 ORDER BY 1) LIMIT 1)");
3645        assert!(q.order_by.is_empty());
3646        assert_eq!(q.limit, Some(1));
3647        let SetExpr::Query(QueryBody::Nested(inner)) = q.body else {
3648            panic!("expected nested set-op query body");
3649        };
3650        assert_eq!(inner.order_by.len(), 1);
3651        assert_eq!(inner.limit, None);
3652        assert!(matches!(inner.body, SetExpr::SetOp { .. }));
3653    }
3654
3655    #[test]
3656    fn raw_query_expr_tail_placement_is_visible() {
3657        use crate::ast::{QueryBody, SetExpr};
3658
3659        let q = only_query("SELECT 1 ORDER BY 1 LIMIT 1");
3660        assert_eq!(q.order_by.len(), 1);
3661        assert_eq!(q.limit, Some(1));
3662        let SetExpr::Query(QueryBody::Select(select)) = q.body else {
3663            panic!("expected SELECT body");
3664        };
3665        assert!(select.order_by.is_empty());
3666        assert_eq!(select.limit, None);
3667
3668        let q = only_query("((SELECT 1 ORDER BY 1))");
3669        assert!(q.order_by.is_empty());
3670        let SetExpr::Query(QueryBody::Select(select)) = q.body else {
3671            panic!("expected SELECT body");
3672        };
3673        assert_eq!(select.order_by.len(), 1);
3674    }
3675
3676    #[test]
3677    fn left_and_right_keywords_parse_as_functions_in_expression_position() {
3678        use crate::ast::{FuncArgs, FuncCall};
3679        // `LEFT`/`RIGHT` are join keywords, but in expression position they are
3680        // the scalar functions `left(s, n)` / `right(s, n)` (PostgreSQL allows it).
3681        for (sql, name) in [("left('abc', 2)", "left"), ("right('abc', 2)", "right")] {
3682            match parse_expr_for_test(sql).expect("parse fn") {
3683                Expr::Func(FuncCall {
3684                    name: n,
3685                    args: FuncArgs::Exprs(a),
3686                    ..
3687                }) => {
3688                    assert_eq!(n, name);
3689                    assert_eq!(a.len(), 2);
3690                }
3691                other => panic!("expected a function call, got {other:?}"),
3692            }
3693        }
3694        // A bare `left`/`right` not followed by `(` is rejected (still reserved).
3695        assert!(parse_expr_for_test("left + 1").is_err());
3696        // And `LEFT JOIN` still parses as a join (keyword role preserved).
3697        assert!(parse("SELECT * FROM a LEFT JOIN b ON a.id = b.id").is_ok());
3698    }
3699
3700    #[test]
3701    fn parse_with_source_pairs_each_statement_with_its_exact_text() {
3702        let v =
3703            parse_with_source("INSERT INTO a VALUES (1); INSERT INTO b VALUES (2)").expect("parse");
3704        assert_eq!(v.len(), 2);
3705        assert_eq!(v[0].1, "INSERT INTO a VALUES (1)");
3706        assert_eq!(v[1].1, "INSERT INTO b VALUES (2)");
3707        // Surrounding whitespace (and the trailing `;`) is trimmed; a single
3708        // statement yields its own exact text.
3709        let solo = parse_with_source("  SELECT 1 ;  ").expect("parse one");
3710        assert_eq!(solo.len(), 1);
3711        assert_eq!(solo[0].1, "SELECT 1");
3712    }
3713
3714    #[test]
3715    fn parses_create_table() {
3716        assert_eq!(
3717            one("CREATE TABLE t (id int4, name text)"),
3718            Statement::CreateTable {
3719                name: "t".into(),
3720                sharded: false,
3721                sharding: None,
3722                constraints: Vec::new(),
3723                columns: vec![
3724                    ColumnDef {
3725                        name: "id".into(),
3726                        ty: ColumnType::Int4,
3727                        serial: None,
3728                        constraints: Vec::new(),
3729                    },
3730                    ColumnDef {
3731                        name: "name".into(),
3732                        ty: ColumnType::Text,
3733                        serial: None,
3734                        constraints: Vec::new(),
3735                    },
3736                ],
3737            }
3738        );
3739    }
3740
3741    #[test]
3742    fn parses_column_constraints_and_default_insert_marker() {
3743        let Statement::CreateTable {
3744            columns,
3745            constraints,
3746            ..
3747        } = one(
3748            "CREATE TABLE t (id int4 PRIMARY KEY, name text NOT NULL DEFAULT 'anon', CHECK (id > 0), UNIQUE (name))",
3749        )
3750        else {
3751            panic!("expected create table");
3752        };
3753        assert!(matches!(
3754            columns[0].constraints[0],
3755            ColumnConstraint::PrimaryKey
3756        ));
3757        assert!(matches!(
3758            columns[1].constraints[0],
3759            ColumnConstraint::NotNull
3760        ));
3761        assert!(matches!(
3762            columns[1].constraints[1],
3763            ColumnConstraint::Default(_)
3764        ));
3765        assert!(matches!(constraints[0], TableConstraint::Check(_)));
3766        assert!(matches!(constraints[1], TableConstraint::Unique(_)));
3767
3768        let Statement::Insert { rows, .. } = one("INSERT INTO t (id, name) VALUES (1, DEFAULT)")
3769        else {
3770            panic!("expected insert");
3771        };
3772        assert!(matches!(rows[0][1], Expr::Default));
3773    }
3774
3775    #[test]
3776    fn parses_create_table_sharded_suffix() {
3777        assert_eq!(
3778            one("CREATE TABLE t (id int4) SHARDED"),
3779            Statement::CreateTable {
3780                name: "t".into(),
3781                columns: vec![ColumnDef {
3782                    name: "id".into(),
3783                    ty: ColumnType::Int4,
3784                    serial: None,
3785                    constraints: Vec::new(),
3786                }],
3787                constraints: Vec::new(),
3788                sharded: true,
3789                sharding: None,
3790            }
3791        );
3792    }
3793
3794    #[test]
3795    fn parses_create_table_hash_sharded_suffix() {
3796        assert_eq!(
3797            one("CREATE TABLE t (id int4) SHARDED BY HASH (id) BUCKETS 16"),
3798            Statement::CreateTable {
3799                name: "t".into(),
3800                columns: vec![ColumnDef {
3801                    name: "id".into(),
3802                    ty: ColumnType::Int4,
3803                    serial: None,
3804                    constraints: Vec::new(),
3805                }],
3806                constraints: Vec::new(),
3807                sharded: true,
3808                sharding: Some(ShardingSpec::Hash(HashShardingSpec {
3809                    columns: vec!["id".into()],
3810                    buckets: 16,
3811                    co_location_group: None,
3812                })),
3813            }
3814        );
3815    }
3816
3817    #[test]
3818    fn parses_create_index_metadata() {
3819        assert_eq!(
3820            one("CREATE UNIQUE GLOBAL INDEX users_email_idx ON users (email, id)"),
3821            Statement::CreateIndex {
3822                name: "users_email_idx".into(),
3823                table: "users".into(),
3824                columns: vec!["email".into(), "id".into()],
3825                unique: true,
3826                placement: IndexPlacement::Global,
3827            }
3828        );
3829        assert_eq!(
3830            one("CREATE INDEX users_id_idx ON users (id)"),
3831            Statement::CreateIndex {
3832                name: "users_id_idx".into(),
3833                table: "users".into(),
3834                columns: vec!["id".into()],
3835                unique: false,
3836                placement: IndexPlacement::Local,
3837            }
3838        );
3839    }
3840
3841    #[test]
3842    fn parses_drop_index_if_exists() {
3843        assert_eq!(
3844            one("DROP INDEX IF EXISTS \"Users Name Idx\""),
3845            Statement::DropIndex {
3846                name: "Users Name Idx".into(),
3847                if_exists: true,
3848            }
3849        );
3850    }
3851
3852    #[test]
3853    fn rejects_non_power_of_two_hash_bucket_count() {
3854        let err = parse("CREATE TABLE t (id int4) SHARDED BY HASH (id) BUCKETS 3")
3855            .expect_err("non-power-of-two buckets");
3856        assert_eq!(err.sqlstate(), "42601");
3857        assert!(err.message.contains("power of two"));
3858    }
3859
3860    #[test]
3861    fn rejects_multiple_hash_sharding_columns() {
3862        let err = parse("CREATE TABLE t (a int4, b int4) SHARDED BY HASH (a, b) BUCKETS 16")
3863            .expect_err("the G9 hash grammar has exactly one column");
3864        assert_eq!(err.sqlstate(), "42601");
3865        assert!(err.message.contains("exactly one column"));
3866    }
3867
3868    #[test]
3869    fn rejects_sharded_in_invalid_create_table_positions() {
3870        let leading = parse("CREATE SHARDED TABLE t (id int4)").expect_err("leading SHARDED");
3871        assert_eq!(leading.sqlstate(), "42601");
3872        assert!(
3873            leading.message.contains("expected Keyword(Table)")
3874                || leading.message.contains("unexpected token")
3875        );
3876
3877        let embedded = parse("CREATE TABLE t SHARDED (id int4)").expect_err("embedded SHARDED");
3878        assert_eq!(embedded.sqlstate(), "42601");
3879        assert!(embedded.message.contains("expected LParen"));
3880    }
3881
3882    #[test]
3883    fn unknown_column_type_is_error() {
3884        let e = parse("CREATE TABLE t (x widget)").expect_err("bad type");
3885        assert_eq!(e.sqlstate(), "42601");
3886    }
3887
3888    #[test]
3889    fn parses_float8_column_types() {
3890        // SP30: `float8`, `float`, and the two-word `double precision` all map to Float8.
3891        for sql in [
3892            "CREATE TABLE t (x float8)",
3893            "CREATE TABLE t (x float)",
3894            "CREATE TABLE t (x double precision)",
3895        ] {
3896            match one(sql) {
3897                Statement::CreateTable { columns, .. } => {
3898                    assert_eq!(columns[0].ty, ColumnType::Float8, "for `{sql}`");
3899                }
3900                other => panic!("expected CreateTable, got {other:?}"),
3901            }
3902        }
3903        // Bare `double` (without `precision`) is not a type — PG rejects it too.
3904        assert!(parse("CREATE TABLE t (x double)").is_err());
3905    }
3906
3907    #[test]
3908    fn parses_numeric_column_types_with_optional_typmod() {
3909        use crabka_pgtypes::numeric::Typmod;
3910        let ty = |sql: &str| match one(sql) {
3911            Statement::CreateTable { columns, .. } => columns[0].ty,
3912            other => panic!("expected CreateTable, got {other:?}"),
3913        };
3914        // Unconstrained `numeric`/`decimal`.
3915        assert_eq!(ty("CREATE TABLE t (x numeric)"), ColumnType::Numeric(None));
3916        assert_eq!(ty("CREATE TABLE t (x decimal)"), ColumnType::Numeric(None));
3917        // `numeric(p)` ≡ scale 0; `numeric(p, s)`.
3918        assert_eq!(
3919            ty("CREATE TABLE t (x numeric(10))"),
3920            ColumnType::Numeric(Some(Typmod {
3921                precision: 10,
3922                scale: 0
3923            }))
3924        );
3925        assert_eq!(
3926            ty("CREATE TABLE t (x numeric(10, 2))"),
3927            ColumnType::Numeric(Some(Typmod {
3928                precision: 10,
3929                scale: 2
3930            }))
3931        );
3932        // The cast target accepts the same modifier.
3933        assert!(matches!(
3934            expr("x::numeric(5,1)"),
3935            Expr::Cast {
3936                ty: ColumnType::Numeric(Some(Typmod {
3937                    precision: 5,
3938                    scale: 1
3939                })),
3940                ..
3941            }
3942        ));
3943    }
3944
3945    #[test]
3946    fn parses_varchar_and_char_type_modifiers() {
3947        let ty = |sql: &str| match one(sql) {
3948            Statement::CreateTable { columns, .. } => columns[0].ty,
3949            other => panic!("expected CreateTable, got {other:?}"),
3950        };
3951
3952        assert_eq!(ty("CREATE TABLE t (x varchar)"), ColumnType::Varchar(None));
3953        assert_eq!(
3954            ty("CREATE TABLE t (x varchar(12))"),
3955            ColumnType::Varchar(Some(12))
3956        );
3957        assert_eq!(
3958            ty("CREATE TABLE t (x character varying(7))"),
3959            ColumnType::Varchar(Some(7))
3960        );
3961        assert_eq!(ty("CREATE TABLE t (x char)"), ColumnType::Char(Some(1)));
3962        assert_eq!(
3963            ty("CREATE TABLE t (x character(3))"),
3964            ColumnType::Char(Some(3))
3965        );
3966        assert!(matches!(
3967            expr("'abc'::varchar(2)"),
3968            Expr::Cast {
3969                ty: ColumnType::Varchar(Some(2)),
3970                ..
3971            }
3972        ));
3973    }
3974
3975    #[test]
3976    fn parses_niladic_keyword_functions_without_parens() {
3977        use crate::ast::{FuncArgs, FuncCall};
3978        // `current_date` etc. parse as zero-arg func calls (no parens).
3979        for name in [
3980            "current_date",
3981            "current_time",
3982            "localtimestamp",
3983            "localtime",
3984            "current_timestamp",
3985        ] {
3986            assert_eq!(
3987                expr(name),
3988                Expr::Func(FuncCall {
3989                    name: name.into(),
3990                    distinct: false,
3991                    args: FuncArgs::Exprs(vec![]),
3992                }),
3993                "niladic `{name}`"
3994            );
3995        }
3996        // The paren forms still parse via the normal func-call path.
3997        assert_eq!(
3998            expr("now()"),
3999            Expr::Func(FuncCall {
4000                name: "now".into(),
4001                distinct: false,
4002                args: FuncArgs::Exprs(vec![]),
4003            })
4004        );
4005        match expr("current_timestamp(0)") {
4006            Expr::Func(FuncCall { name, args, .. }) => {
4007                assert_eq!(name, "current_timestamp");
4008                assert!(matches!(args, FuncArgs::Exprs(ref v) if v.len() == 1));
4009            }
4010            other => panic!("expected a Func call, got {other:?}"),
4011        }
4012    }
4013
4014    #[test]
4015    fn parses_numeric_literals() {
4016        // SP32: bare decimal/exponent literals are `numeric` (was `float8` in SP30).
4017        assert_eq!(expr("1.5"), Expr::NumericLiteral("1.5".into()));
4018        assert_eq!(expr(".25"), Expr::NumericLiteral(".25".into()));
4019        assert_eq!(expr("1e3"), Expr::NumericLiteral("1e3".into()));
4020        assert_eq!(expr("42"), Expr::IntLiteral("42".into()));
4021        // float participates in arithmetic with the usual precedence.
4022        match expr("1 + 2.5 * 2") {
4023            Expr::Binary {
4024                op: BinaryOp::Add,
4025                right,
4026                ..
4027            } => assert!(matches!(
4028                *right,
4029                Expr::Binary {
4030                    op: BinaryOp::Mul,
4031                    ..
4032                }
4033            )),
4034            other => panic!("expected Add(_, Mul), got {other:?}"),
4035        }
4036    }
4037
4038    #[test]
4039    fn parses_drop_table() {
4040        assert_eq!(
4041            one("DROP TABLE t"),
4042            Statement::DropTable { name: "t".into() }
4043        );
4044    }
4045
4046    #[test]
4047    fn parses_multi_row_insert_with_columns() {
4048        match one("INSERT INTO t (a, b) VALUES (1, 'x'), (2, 'y')") {
4049            Statement::Insert {
4050                table,
4051                columns,
4052                rows,
4053                returning,
4054            } => {
4055                assert_eq!(table, "t");
4056                assert_eq!(columns, Some(vec!["a".into(), "b".into()]));
4057                assert_eq!(rows.len(), 2);
4058                assert_eq!(rows[0].len(), 2);
4059                assert_eq!(returning, None);
4060            }
4061            other => panic!("expected Insert, got {other:?}"),
4062        }
4063    }
4064
4065    #[test]
4066    fn parses_select_with_all_clauses() {
4067        let s = only_select("SELECT a, b AS bee FROM t WHERE a > 1 ORDER BY a DESC, b LIMIT 10");
4068        assert_eq!(s.projection.len(), 2);
4069        assert!(
4070            matches!(s.projection[1], SelectItem::Expr { alias: Some(ref n), .. } if n == "bee")
4071        );
4072        assert!(matches!(
4073            s.from.as_slice(),
4074            [crate::ast::TableExpr::Table { name, alias: None }] if name == "t"
4075        ));
4076        assert!(s.filter.is_some());
4077        assert_eq!(s.order_by.len(), 2);
4078        assert!(!s.order_by[0].asc); // DESC
4079        assert!(s.order_by[1].asc); // default ASC
4080        assert_eq!(s.limit, Some(10));
4081    }
4082
4083    #[test]
4084    fn parses_aggregates_group_by_having() {
4085        use crate::ast::{FuncArgs, FuncCall};
4086        let s = only_select(
4087            "SELECT k, count(*), sum(v) FROM t WHERE v > 0 \
4088             GROUP BY k HAVING count(*) > 1 ORDER BY k LIMIT 5",
4089        );
4090        assert_eq!(s.projection.len(), 3);
4091        // count(*)
4092        assert!(matches!(
4093            s.projection[1],
4094            SelectItem::Expr {
4095                expr: Expr::Func(FuncCall { ref name, distinct: false, args: FuncArgs::Star }),
4096                ..
4097            } if name == "count"
4098        ));
4099        assert_eq!(
4100            s.group_by,
4101            vec![Expr::Column {
4102                table: None,
4103                name: "k".into()
4104            }]
4105        );
4106        assert!(s.having.is_some());
4107        assert_eq!(s.order_by.len(), 1);
4108        assert_eq!(s.limit, Some(5));
4109    }
4110
4111    #[test]
4112    fn parses_count_distinct_and_func_args() {
4113        use crate::ast::{FuncArgs, FuncCall};
4114        let s = only_select("SELECT count(DISTINCT a + 1) FROM t");
4115        match &s.projection[0] {
4116            SelectItem::Expr {
4117                expr:
4118                    Expr::Func(FuncCall {
4119                        name,
4120                        distinct,
4121                        args,
4122                    }),
4123                ..
4124            } => {
4125                assert_eq!(name, "count");
4126                assert!(*distinct);
4127                match args {
4128                    FuncArgs::Exprs(v) => assert_eq!(v.len(), 1),
4129                    other @ FuncArgs::Star => panic!("expected Exprs, got {other:?}"),
4130                }
4131            }
4132            other => panic!("expected a Func projection, got {other:?}"),
4133        }
4134    }
4135
4136    #[test]
4137    fn count_distinct_star_is_rejected() {
4138        // PostgreSQL rejects `count(DISTINCT *)` as a syntax error; so do we.
4139        assert!(parse("SELECT count(DISTINCT *) FROM t").is_err());
4140    }
4141
4142    #[test]
4143    fn parses_multi_key_group_by() {
4144        let s = only_select("SELECT a, b, max(c) FROM t GROUP BY a, b");
4145        assert_eq!(
4146            s.group_by,
4147            vec![
4148                Expr::Column {
4149                    table: None,
4150                    name: "a".into()
4151                },
4152                Expr::Column {
4153                    table: None,
4154                    name: "b".into()
4155                }
4156            ]
4157        );
4158        assert!(s.having.is_none());
4159    }
4160
4161    #[test]
4162    fn parses_select_star_no_from() {
4163        let s = only_select("SELECT *");
4164        assert_eq!(s.projection, vec![SelectItem::Wildcard]);
4165        assert!(s.from.is_empty());
4166    }
4167
4168    #[test]
4169    fn parses_multiple_statements() {
4170        let v = parse("SELECT 1; SELECT 2;").expect("parse");
4171        assert_eq!(v.len(), 2);
4172    }
4173
4174    #[test]
4175    fn trailing_garbage_is_error() {
4176        assert!(parse("SELECT 1 foo bar").is_err());
4177    }
4178
4179    #[test]
4180    fn parses_begin_variants() {
4181        assert_eq!(one("BEGIN"), Statement::Begin { isolation: None });
4182        assert_eq!(
4183            one("START TRANSACTION"),
4184            Statement::Begin { isolation: None }
4185        );
4186        assert_eq!(
4187            one("BEGIN ISOLATION LEVEL REPEATABLE READ"),
4188            Statement::Begin {
4189                isolation: Some(IsolationLevel::RepeatableRead)
4190            }
4191        );
4192        assert_eq!(
4193            one("BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED"),
4194            Statement::Begin {
4195                isolation: Some(IsolationLevel::ReadCommitted)
4196            }
4197        );
4198    }
4199
4200    #[test]
4201    fn start_requires_transaction_keyword() {
4202        // START TRANSACTION is valid; bare START is not a statement.
4203        assert_eq!(
4204            one("START TRANSACTION"),
4205            Statement::Begin { isolation: None }
4206        );
4207        assert!(parse("START").is_err());
4208    }
4209
4210    #[test]
4211    fn parses_commit_rollback_aliases() {
4212        assert_eq!(one("COMMIT"), Statement::Commit);
4213        assert_eq!(one("END"), Statement::Commit);
4214        assert_eq!(one("ROLLBACK"), Statement::Rollback);
4215        assert_eq!(one("ABORT"), Statement::Rollback);
4216    }
4217
4218    #[test]
4219    fn parses_update() {
4220        match one("UPDATE t SET a = 1, b = a + 2 WHERE id = 5") {
4221            Statement::Update {
4222                table,
4223                assignments,
4224                filter,
4225                returning,
4226            } => {
4227                assert_eq!(table, "t");
4228                assert_eq!(assignments.len(), 2);
4229                assert_eq!(assignments[0].0, "a");
4230                assert_eq!(assignments[1].0, "b");
4231                assert!(filter.is_some());
4232                assert_eq!(returning, None);
4233            }
4234            other => panic!("expected Update, got {other:?}"),
4235        }
4236    }
4237
4238    #[test]
4239    fn parses_select_for_update_and_share() {
4240        use crate::ast::RowLockStrength;
4241        assert_eq!(
4242            only_query("SELECT id FROM t FOR UPDATE").locking,
4243            Some(RowLockStrength::ForUpdate)
4244        );
4245        assert_eq!(
4246            only_query("SELECT id FROM t WHERE id > 1 FOR SHARE").locking,
4247            Some(RowLockStrength::ForShare)
4248        );
4249        assert_eq!(only_query("SELECT id FROM t").locking, None);
4250    }
4251
4252    #[test]
4253    fn parses_delete() {
4254        match one("DELETE FROM t WHERE id > 3") {
4255            Statement::Delete {
4256                table,
4257                filter,
4258                returning,
4259            } => {
4260                assert_eq!(table, "t");
4261                assert!(filter.is_some());
4262                assert_eq!(returning, None);
4263            }
4264            other => panic!("expected Delete, got {other:?}"),
4265        }
4266        assert_eq!(
4267            one("DELETE FROM t"),
4268            Statement::Delete {
4269                table: "t".into(),
4270                filter: None,
4271                returning: None,
4272            }
4273        );
4274    }
4275
4276    #[test]
4277    fn parses_dml_returning() {
4278        let Statement::Insert { returning, .. } = one("INSERT INTO t VALUES (1) RETURNING *")
4279        else {
4280            panic!("expected Insert");
4281        };
4282        assert_eq!(returning, Some(vec![SelectItem::Wildcard]));
4283
4284        let Statement::Update { returning, .. } = one("UPDATE t SET a = 1 RETURNING a AS x, a + 1")
4285        else {
4286            panic!("expected Update");
4287        };
4288        assert!(matches!(
4289            returning.as_deref(),
4290            Some([
4291                SelectItem::Expr { alias: Some(alias), .. },
4292                SelectItem::Expr { alias: None, .. }
4293            ]) if alias == "x"
4294        ));
4295
4296        let Statement::Delete { returning, .. } = one("DELETE FROM t RETURNING t.*") else {
4297            panic!("expected Delete");
4298        };
4299        assert_eq!(
4300            returning,
4301            Some(vec![SelectItem::QualifiedWildcard("t".into())])
4302        );
4303    }
4304
4305    fn expr(sql: &str) -> Expr {
4306        // Wrap in a SELECT so the public parse() entry can reach it once
4307        // statements exist; until then, use the crate-internal expr parser.
4308        parse_expr_for_test(sql).expect("parse expr")
4309    }
4310
4311    #[test]
4312    fn every_binary_operator_parses_to_its_op() {
4313        // Each operator token must map to its own BinaryOp arm in `expr` — pin all
4314        // ten so dropping any single arm (e.g. `<>`, `<=`, `-`, `/`) is caught.
4315        use crate::ast::BinaryOp::*;
4316        for (src, want) in [
4317            ("a = b", Eq),
4318            ("a <> b", Ne),
4319            ("a < b", Lt),
4320            ("a <= b", Le),
4321            ("a > b", Gt),
4322            ("a >= b", Ge),
4323            ("a + b", Add),
4324            ("a - b", Sub),
4325            ("a * b", Mul),
4326            ("a / b", Div),
4327            ("a || b", Concat),
4328        ] {
4329            match expr(src) {
4330                Expr::Binary { op, .. } => assert_eq!(op, want, "operator in `{src}`"),
4331                other => panic!("`{src}` should parse to a Binary expr, got {other:?}"),
4332            }
4333        }
4334    }
4335
4336    #[test]
4337    fn bump_does_not_advance_past_eof() {
4338        // `bump` clamps at the trailing Eof token: a statement that runs out of
4339        // input (no table name) makes the parser bump AT Eof and then read the
4340        // next position for its error message. If `bump` advanced past Eof that
4341        // read would be out of bounds — instead we must get a clean parse error.
4342        assert!(parse("DROP TABLE").is_err());
4343        assert!(parse("CREATE TABLE").is_err());
4344    }
4345
4346    #[test]
4347    fn precedence_mul_over_add() {
4348        // 1 + 2 * 3  ==  1 + (2 * 3)
4349        let e = expr("1 + 2 * 3");
4350        assert_eq!(
4351            e,
4352            Expr::Binary {
4353                op: BinaryOp::Add,
4354                left: Box::new(Expr::IntLiteral("1".into())),
4355                right: Box::new(Expr::Binary {
4356                    op: BinaryOp::Mul,
4357                    left: Box::new(Expr::IntLiteral("2".into())),
4358                    right: Box::new(Expr::IntLiteral("3".into())),
4359                }),
4360            }
4361        );
4362    }
4363
4364    #[test]
4365    fn concat_precedence_and_associativity() {
4366        // `||` binds looser than `+` (PG): `a || b + c` == `a || (b + c)`.
4367        match expr("a || b + c") {
4368            Expr::Binary {
4369                op: BinaryOp::Concat,
4370                right,
4371                ..
4372            } => assert!(matches!(
4373                *right,
4374                Expr::Binary {
4375                    op: BinaryOp::Add,
4376                    ..
4377                }
4378            )),
4379            other => panic!("expected Concat(.., Add) , got {other:?}"),
4380        }
4381        // `||` binds tighter than `=` (PG): `a || b = c` == `(a || b) = c`.
4382        match expr("a || b = c") {
4383            Expr::Binary {
4384                op: BinaryOp::Eq,
4385                left,
4386                ..
4387            } => assert!(matches!(
4388                *left,
4389                Expr::Binary {
4390                    op: BinaryOp::Concat,
4391                    ..
4392                }
4393            )),
4394            other => panic!("expected Eq(Concat, ..), got {other:?}"),
4395        }
4396        // Left-associative: `a || b || c` == `(a || b) || c`.
4397        match expr("a || b || c") {
4398            Expr::Binary {
4399                op: BinaryOp::Concat,
4400                left,
4401                ..
4402            } => assert!(matches!(
4403                *left,
4404                Expr::Binary {
4405                    op: BinaryOp::Concat,
4406                    ..
4407                }
4408            )),
4409            other => panic!("expected left-nested Concat, got {other:?}"),
4410        }
4411        // `||` binds tighter than LIKE: `a || b LIKE p` == `(a || b) LIKE p`.
4412        match expr("a || b LIKE 'p'") {
4413            Expr::Like { expr, .. } => {
4414                assert!(matches!(
4415                    *expr,
4416                    Expr::Binary {
4417                        op: BinaryOp::Concat,
4418                        ..
4419                    }
4420                ));
4421            }
4422            other => panic!("expected Like over Concat, got {other:?}"),
4423        }
4424    }
4425
4426    #[test]
4427    fn unary_minus_still_binds_tighter_than_star() {
4428        // After the SP29 renumber, `-a * b` must still be `(-a) * b`.
4429        match expr("-a * b") {
4430            Expr::Binary {
4431                op: BinaryOp::Mul,
4432                left,
4433                ..
4434            } => assert!(matches!(
4435                *left,
4436                Expr::Unary {
4437                    op: UnaryOp::Neg,
4438                    ..
4439                }
4440            )),
4441            other => panic!("expected Mul((-a), b), got {other:?}"),
4442        }
4443    }
4444
4445    #[test]
4446    fn comparison_and_boolean_precedence() {
4447        // a = 1 AND b < 2  ==  (a = 1) AND (b < 2)
4448        let e = expr("a = 1 AND b < 2");
4449        assert!(matches!(
4450            e,
4451            Expr::Binary {
4452                op: BinaryOp::And,
4453                ..
4454            }
4455        ));
4456    }
4457
4458    #[test]
4459    fn not_and_or_precedence() {
4460        // NOT x OR y  ==  (NOT x) OR y
4461        let e = expr("NOT x OR y");
4462        match e {
4463            Expr::Binary {
4464                op: BinaryOp::Or,
4465                left,
4466                ..
4467            } => {
4468                assert!(matches!(
4469                    *left,
4470                    Expr::Unary {
4471                        op: UnaryOp::Not,
4472                        ..
4473                    }
4474                ));
4475            }
4476            _ => panic!("expected OR at top, got {e:?}"),
4477        }
4478    }
4479
4480    #[test]
4481    fn unary_minus_and_parens() {
4482        let e = expr("-(1 + 2)");
4483        assert!(matches!(
4484            e,
4485            Expr::Unary {
4486                op: UnaryOp::Neg,
4487                ..
4488            }
4489        ));
4490    }
4491
4492    #[test]
4493    fn literals_columns_params() {
4494        assert_eq!(expr("'hi'"), Expr::StringLiteral("hi".into()));
4495        assert_eq!(expr("true"), Expr::BoolLiteral(true));
4496        assert_eq!(expr("null"), Expr::NullLiteral);
4497        assert_eq!(
4498            expr("col"),
4499            Expr::Column {
4500                table: None,
4501                name: "col".into()
4502            }
4503        );
4504        assert_eq!(expr("$2"), Expr::Param(2));
4505    }
4506
4507    #[test]
4508    fn not_binds_tighter_than_and() {
4509        // NOT x AND y == (NOT x) AND y
4510        let e = expr("NOT x AND y");
4511        match e {
4512            Expr::Binary {
4513                op: BinaryOp::And,
4514                left,
4515                ..
4516            } => {
4517                assert!(
4518                    matches!(
4519                        *left,
4520                        Expr::Unary {
4521                            op: UnaryOp::Not,
4522                            ..
4523                        }
4524                    ),
4525                    "left of AND must be (NOT x), got {left:?}"
4526                );
4527            }
4528            _ => panic!("expected AND at root, got {e:?}"),
4529        }
4530    }
4531
4532    #[test]
4533    fn comparison_binds_tighter_than_not() {
4534        // NOT a = 1 == NOT (a = 1)
4535        let e = expr("NOT a = 1");
4536        match e {
4537            Expr::Unary {
4538                op: UnaryOp::Not,
4539                expr,
4540            } => {
4541                assert!(
4542                    matches!(
4543                        *expr,
4544                        Expr::Binary {
4545                            op: BinaryOp::Eq,
4546                            ..
4547                        }
4548                    ),
4549                    "NOT operand must be (a = 1), got {expr:?}"
4550                );
4551            }
4552            _ => panic!("expected NOT at root, got {e:?}"),
4553        }
4554    }
4555
4556    // ---- SP28: predicate + conditional expression breadth ----
4557
4558    #[test]
4559    fn parses_is_null_and_is_not_null() {
4560        assert!(matches!(
4561            expr("a IS NULL"),
4562            Expr::IsNull { negated: false, .. }
4563        ));
4564        assert!(matches!(
4565            expr("a IS NOT NULL"),
4566            Expr::IsNull { negated: true, .. }
4567        ));
4568    }
4569
4570    #[test]
4571    fn parses_in_and_not_in() {
4572        match expr("a IN (1, 2, 3)") {
4573            Expr::InList { list, negated, .. } => {
4574                assert_eq!(list.len(), 3);
4575                assert!(!negated);
4576            }
4577            other => panic!("expected InList, got {other:?}"),
4578        }
4579        assert!(matches!(
4580            expr("a NOT IN (1, 2)"),
4581            Expr::InList { negated: true, .. }
4582        ));
4583    }
4584
4585    #[test]
4586    fn empty_in_list_is_rejected() {
4587        assert!(parse("SELECT a FROM t WHERE a IN ()").is_err());
4588    }
4589
4590    #[test]
4591    fn not_in_is_infix_but_prefix_not_wraps_in() {
4592        // `x NOT IN (..)` is the infix negated predicate.
4593        assert!(matches!(
4594            expr("x NOT IN (1)"),
4595            Expr::InList { negated: true, .. }
4596        ));
4597        // `NOT x IN (..)` is prefix NOT over (x IN ..).
4598        match expr("NOT x IN (1)") {
4599            Expr::Unary {
4600                op: UnaryOp::Not,
4601                expr,
4602            } => assert!(matches!(*expr, Expr::InList { negated: false, .. })),
4603            other => panic!("expected NOT over InList, got {other:?}"),
4604        }
4605    }
4606
4607    #[test]
4608    fn between_and_does_not_eat_boolean_and() {
4609        // `a BETWEEN 1 AND 2 AND b` == `(a BETWEEN 1 AND 2) AND b`.
4610        match expr("a BETWEEN 1 AND 2 AND b") {
4611            Expr::Binary {
4612                op: BinaryOp::And,
4613                left,
4614                right,
4615            } => {
4616                assert!(matches!(*left, Expr::Between { negated: false, .. }));
4617                assert_eq!(
4618                    *right,
4619                    Expr::Column {
4620                        table: None,
4621                        name: "b".into()
4622                    }
4623                );
4624            }
4625            other => panic!("expected AND(Between, b), got {other:?}"),
4626        }
4627        assert!(matches!(
4628            expr("a NOT BETWEEN 1 AND 10"),
4629            Expr::Between { negated: true, .. }
4630        ));
4631    }
4632
4633    #[test]
4634    fn parses_like_ilike_all_combinations() {
4635        assert!(matches!(
4636            expr("a LIKE 'x%'"),
4637            Expr::Like {
4638                negated: false,
4639                case_insensitive: false,
4640                ..
4641            }
4642        ));
4643        assert!(matches!(
4644            expr("a NOT LIKE 'x%'"),
4645            Expr::Like {
4646                negated: true,
4647                case_insensitive: false,
4648                ..
4649            }
4650        ));
4651        assert!(matches!(
4652            expr("a ILIKE 'x%'"),
4653            Expr::Like {
4654                negated: false,
4655                case_insensitive: true,
4656                ..
4657            }
4658        ));
4659        assert!(matches!(
4660            expr("a NOT ILIKE 'x%'"),
4661            Expr::Like {
4662                negated: true,
4663                case_insensitive: true,
4664                ..
4665            }
4666        ));
4667    }
4668
4669    #[test]
4670    fn parses_searched_and_simple_case() {
4671        match expr("CASE WHEN a > 0 THEN 'pos' ELSE 'neg' END") {
4672            Expr::Case {
4673                operand,
4674                whens,
4675                else_result,
4676            } => {
4677                assert!(operand.is_none());
4678                assert_eq!(whens.len(), 1);
4679                assert!(else_result.is_some());
4680            }
4681            other => panic!("expected searched CASE, got {other:?}"),
4682        }
4683        match expr("CASE a WHEN 1 THEN 'one' WHEN 2 THEN 'two' END") {
4684            Expr::Case {
4685                operand,
4686                whens,
4687                else_result,
4688            } => {
4689                assert!(operand.is_some());
4690                assert_eq!(whens.len(), 2);
4691                assert!(else_result.is_none());
4692            }
4693            other => panic!("expected simple CASE, got {other:?}"),
4694        }
4695    }
4696
4697    #[test]
4698    fn case_without_when_is_rejected() {
4699        assert!(parse("SELECT CASE END FROM t").is_err());
4700    }
4701
4702    #[test]
4703    fn parses_select_distinct() {
4704        assert!(only_select("SELECT DISTINCT a FROM t").distinct);
4705        assert!(!only_select("SELECT a FROM t").distinct);
4706    }
4707
4708    // ---- SP31: explicit casts ----
4709
4710    #[test]
4711    fn parses_cast_both_forms_to_the_same_node() {
4712        use crabka_pgtypes::ColumnType;
4713        // `expr::type` and `CAST(expr AS type)` produce the identical Cast node.
4714        let want = Expr::Cast {
4715            expr: Box::new(Expr::IntLiteral("1".into())),
4716            ty: ColumnType::Int8,
4717        };
4718        assert_eq!(expr("1::int8"), want);
4719        assert_eq!(expr("CAST(1 AS int8)"), want);
4720        // `double precision` (two-word) and the other spellings resolve.
4721        assert!(matches!(
4722            expr("x::double precision"),
4723            Expr::Cast {
4724                ty: ColumnType::Float8,
4725                ..
4726            }
4727        ));
4728        assert!(matches!(
4729            expr("CAST(x AS integer)"),
4730            Expr::Cast {
4731                ty: ColumnType::Int4,
4732                ..
4733            }
4734        ));
4735        assert!(matches!(
4736            expr("x::text"),
4737            Expr::Cast {
4738                ty: ColumnType::Text,
4739                ..
4740            }
4741        ));
4742    }
4743
4744    #[test]
4745    fn cast_binds_tighter_than_unary_minus_and_arithmetic() {
4746        // `-2::int8` == `-(2::int8)` — the cast binds to `2`, not to `-2`.
4747        match expr("-2::int8") {
4748            Expr::Unary {
4749                op: UnaryOp::Neg,
4750                expr,
4751            } => {
4752                assert!(matches!(*expr, Expr::Cast { .. }), "got {expr:?}");
4753            }
4754            other => panic!("expected Neg(Cast), got {other:?}"),
4755        }
4756        // `1 + 2::int8` == `1 + (2::int8)`.
4757        match expr("1 + 2::int8") {
4758            Expr::Binary {
4759                op: BinaryOp::Add,
4760                right,
4761                ..
4762            } => {
4763                assert!(matches!(*right, Expr::Cast { .. }), "got {right:?}");
4764            }
4765            other => panic!("expected Add(1, Cast), got {other:?}"),
4766        }
4767        // `a::int4 + b` == `(a::int4) + b`.
4768        match expr("a::int4 + b") {
4769            Expr::Binary {
4770                op: BinaryOp::Add,
4771                left,
4772                ..
4773            } => {
4774                assert!(matches!(*left, Expr::Cast { .. }), "got {left:?}");
4775            }
4776            other => panic!("expected Add(Cast, b), got {other:?}"),
4777        }
4778    }
4779
4780    #[test]
4781    fn cast_is_left_associative_when_chained() {
4782        // `a::int4::text` == `(a::int4)::text`.
4783        match expr("a::int4::text") {
4784            Expr::Cast { expr: inner, ty } => {
4785                assert_eq!(ty, crabka_pgtypes::ColumnType::Text);
4786                assert!(
4787                    matches!(
4788                        *inner,
4789                        Expr::Cast {
4790                            ty: crabka_pgtypes::ColumnType::Int4,
4791                            ..
4792                        }
4793                    ),
4794                    "got {inner:?}"
4795                );
4796            }
4797            other => panic!("expected outer text Cast over int4 Cast, got {other:?}"),
4798        }
4799    }
4800
4801    #[test]
4802    fn cast_to_unknown_type_is_a_parse_error() {
4803        assert!(parse("SELECT 1::widget").is_err());
4804        assert!(parse("SELECT CAST(1 AS widget)").is_err());
4805        // `cast` is a reserved keyword now, so `CAST(... )` requires `AS`.
4806        assert!(parse("SELECT CAST(1 int4)").is_err());
4807    }
4808
4809    #[test]
4810    fn parses_uuid_type_in_create_table_and_cast() {
4811        use crate::ast::{Expr, Statement};
4812
4813        let stmts = parse("CREATE TABLE t (id uuid)").expect("parse create");
4814        let Statement::CreateTable { columns, .. } = &stmts[0] else {
4815            panic!("expected create table");
4816        };
4817        assert_eq!(columns[0].ty, crabka_pgtypes::ColumnType::Uuid);
4818        assert!(matches!(
4819            parse_expr_for_test("'550e8400-e29b-41d4-a716-446655440000'::uuid")
4820                .expect("parse cast"),
4821            Expr::Cast {
4822                ty: crabka_pgtypes::ColumnType::Uuid,
4823                ..
4824            }
4825        ));
4826    }
4827
4828    // ---- SP37: date/time type names, typed literals, EXTRACT, AT TIME ZONE ----
4829
4830    #[test]
4831    fn parses_typed_datetime_literals() {
4832        use crate::ast::Expr;
4833        assert!(matches!(
4834            parse_expr_for_test("DATE '2024-01-01'").expect("d"),
4835            Expr::Cast { .. }
4836        ));
4837        assert!(matches!(
4838            parse_expr_for_test("INTERVAL '1 day'").expect("iv"),
4839            Expr::Cast { .. }
4840        ));
4841        assert!(matches!(
4842            parse_expr_for_test("TIMESTAMP '2024-01-01 00:00:00'").expect("ts"),
4843            Expr::Cast { .. }
4844        ));
4845        assert!(matches!(
4846            parse_expr_for_test("TIMESTAMPTZ '2024-01-01 00:00:00+00'").expect("tstz"),
4847            Expr::Cast { .. }
4848        ));
4849    }
4850
4851    #[test]
4852    fn parses_extract_and_at_time_zone() {
4853        use crate::ast::Expr;
4854        assert!(matches!(
4855            parse_expr_for_test("extract(year from x)").expect("ex"),
4856            Expr::Func(_)
4857        ));
4858        let e = parse_expr_for_test("ts AT TIME ZONE 'UTC' = ts2").expect("attz");
4859        assert!(matches!(
4860            e,
4861            Expr::Binary {
4862                op: crate::ast::BinaryOp::Eq,
4863                ..
4864            }
4865        ));
4866    }
4867
4868    #[test]
4869    fn parses_multiword_type_in_create_and_cast() {
4870        use crate::ast::{Expr, Statement};
4871        let stmts = crate::parser::parse(
4872            "CREATE TABLE t (a timestamp with time zone, b time without time zone)",
4873        )
4874        .expect("ct");
4875        assert!(matches!(&stmts[0], Statement::CreateTable { .. }));
4876        assert!(matches!(
4877            parse_expr_for_test("x::timestamp with time zone").expect("c"),
4878            Expr::Cast {
4879                ty: crabka_pgtypes::ColumnType::Timestamptz,
4880                ..
4881            }
4882        ));
4883    }
4884
4885    // ---- SP37: SET / SHOW / RESET timezone GUC ----
4886
4887    #[test]
4888    fn parses_set_timezone_all_spellings() {
4889        use crate::ast::SetValue;
4890        // SET timezone = '...' / SET timezone TO '...'
4891        assert_eq!(
4892            one("SET timezone = 'America/New_York'"),
4893            Statement::Set {
4894                local: false,
4895                name: "timezone".into(),
4896                value: SetValue::Value("America/New_York".into()),
4897            }
4898        );
4899        assert_eq!(
4900            one("SET timezone TO 'UTC'"),
4901            Statement::Set {
4902                local: false,
4903                name: "timezone".into(),
4904                value: SetValue::Value("UTC".into()),
4905            }
4906        );
4907        // SET TIME ZONE '...' (the special two-word spelling normalizes to `timezone`).
4908        assert_eq!(
4909            one("SET TIME ZONE 'America/New_York'"),
4910            Statement::Set {
4911                local: false,
4912                name: "timezone".into(),
4913                value: SetValue::Value("America/New_York".into()),
4914            }
4915        );
4916        // An identifier value (no quotes) is accepted too.
4917        assert_eq!(
4918            one("SET timezone TO utc"),
4919            Statement::Set {
4920                local: false,
4921                name: "timezone".into(),
4922                value: SetValue::Value("utc".into()),
4923            }
4924        );
4925        // The GUC name is normalized to lowercase.
4926        assert_eq!(
4927            one("SET TimeZone = 'UTC'"),
4928            Statement::Set {
4929                local: false,
4930                name: "timezone".into(),
4931                value: SetValue::Value("UTC".into()),
4932            }
4933        );
4934    }
4935
4936    #[test]
4937    fn parses_set_local_flag_vs_local_value() {
4938        use crate::ast::SetValue;
4939        // `SET LOCAL timezone ...` — LOCAL is the flag (followed by a param name).
4940        assert_eq!(
4941            one("SET LOCAL timezone = 'UTC'"),
4942            Statement::Set {
4943                local: true,
4944                name: "timezone".into(),
4945                value: SetValue::Value("UTC".into()),
4946            }
4947        );
4948        assert_eq!(
4949            one("SET LOCAL TIME ZONE 'America/New_York'"),
4950            Statement::Set {
4951                local: true,
4952                name: "timezone".into(),
4953                value: SetValue::Value("America/New_York".into()),
4954            }
4955        );
4956        // `SET TIME ZONE LOCAL` — here LOCAL is the VALUE (→ Default), not the flag.
4957        assert_eq!(
4958            one("SET TIME ZONE LOCAL"),
4959            Statement::Set {
4960                local: false,
4961                name: "timezone".into(),
4962                value: SetValue::Default,
4963            }
4964        );
4965        // `SET TIME ZONE DEFAULT` is likewise the Default value.
4966        assert_eq!(
4967            one("SET TIME ZONE DEFAULT"),
4968            Statement::Set {
4969                local: false,
4970                name: "timezone".into(),
4971                value: SetValue::Default,
4972            }
4973        );
4974        // `SET timezone = DEFAULT` — DEFAULT as the value.
4975        assert_eq!(
4976            one("SET timezone = DEFAULT"),
4977            Statement::Set {
4978                local: false,
4979                name: "timezone".into(),
4980                value: SetValue::Default,
4981            }
4982        );
4983    }
4984
4985    #[test]
4986    fn parses_show_and_reset() {
4987        use crate::ast::ResetTarget;
4988        assert_eq!(
4989            one("SHOW timezone"),
4990            Statement::Show {
4991                name: "timezone".into()
4992            }
4993        );
4994        assert_eq!(
4995            one("SHOW TIME ZONE"),
4996            Statement::Show {
4997                name: "timezone".into()
4998            }
4999        );
5000        assert_eq!(
5001            one("SHOW TimeZone"),
5002            Statement::Show {
5003                name: "timezone".into()
5004            }
5005        );
5006        assert_eq!(
5007            one("RESET timezone"),
5008            Statement::Reset {
5009                target: ResetTarget::Name("timezone".into())
5010            }
5011        );
5012        assert_eq!(
5013            one("RESET ALL"),
5014            Statement::Reset {
5015                target: ResetTarget::All
5016            }
5017        );
5018        assert_eq!(
5019            one("DISCARD ALL"),
5020            Statement::Set {
5021                local: false,
5022                name: "__discard_all".into(),
5023                value: crate::ast::SetValue::Default
5024            }
5025        );
5026        assert_eq!(
5027            one("SET TRANSACTION ISOLATION LEVEL READ COMMITTED"),
5028            Statement::Set {
5029                local: false,
5030                name: "__set_transaction".into(),
5031                value: crate::ast::SetValue::Value("read committed".into())
5032            }
5033        );
5034    }
5035
5036    #[test]
5037    fn parses_f1_guc_command_surface() {
5038        use crate::ast::{ResetTarget, SetValue};
5039
5040        for sql in [
5041            "SET SESSION application_name TO 'session-app'",
5042            "SET application_name = 'session-app'",
5043        ] {
5044            assert_eq!(
5045                one(sql),
5046                Statement::Set {
5047                    local: false,
5048                    name: "application_name".into(),
5049                    value: SetValue::Value("session-app".into()),
5050                }
5051            );
5052        }
5053        assert_eq!(
5054            one("SET extra_float_digits = -15"),
5055            Statement::Set {
5056                local: false,
5057                name: "extra_float_digits".into(),
5058                value: SetValue::Value("-15".into()),
5059            }
5060        );
5061        assert_eq!(
5062            one("SET DateStyle TO ISO, MDY"),
5063            Statement::Set {
5064                local: false,
5065                name: "datestyle".into(),
5066                value: SetValue::Value("iso, mdy".into()),
5067            }
5068        );
5069        let statements = crate::parser::parse("SET DateStyle TO SQL DMY; SHOW DateStyle").unwrap();
5070        assert_eq!(
5071            statements[0],
5072            Statement::Set {
5073                local: false,
5074                name: "datestyle".into(),
5075                value: SetValue::Value("sql dmy".into()),
5076            }
5077        );
5078        assert_eq!(
5079            statements[1],
5080            Statement::Show {
5081                name: "datestyle".into(),
5082            }
5083        );
5084        assert_eq!(one("SHOW ALL"), Statement::Show { name: "all".into() });
5085        assert_eq!(
5086            one("RESET ALL"),
5087            Statement::Reset {
5088                target: ResetTarget::All,
5089            }
5090        );
5091        for unsupported in ["DISCARD PLANS", "DISCARD SEQUENCES", "DISCARD TEMP"] {
5092            let error = crate::parser::parse(unsupported).expect_err("unsupported DISCARD");
5093            assert!(error.to_string().contains("All"));
5094        }
5095    }
5096
5097    #[test]
5098    fn set_show_reset_accept_unknown_names_at_parse_time() {
5099        // Name validation is the executor's job (42704); the parser accepts any name.
5100        use crate::ast::SetValue;
5101        assert_eq!(
5102            one("SET datestyle = 'ISO, MDY'"),
5103            Statement::Set {
5104                local: false,
5105                name: "datestyle".into(),
5106                value: SetValue::Value("ISO, MDY".into()),
5107            }
5108        );
5109        assert_eq!(
5110            one("SHOW search_path"),
5111            Statement::Show {
5112                name: "search_path".into()
5113            }
5114        );
5115    }
5116
5117    #[test]
5118    fn parses_qualified_column() {
5119        use crate::ast::Expr;
5120        assert_eq!(
5121            expr("a.col"),
5122            Expr::Column {
5123                table: Some("a".into()),
5124                name: "col".into()
5125            }
5126        );
5127        assert_eq!(
5128            expr("col"),
5129            Expr::Column {
5130                table: None,
5131                name: "col".into()
5132            }
5133        );
5134    }
5135
5136    #[test]
5137    fn parses_limit_and_offset_either_order() {
5138        for sql in [
5139            "SELECT a FROM t ORDER BY a LIMIT 5 OFFSET 10",
5140            "SELECT a FROM t ORDER BY a OFFSET 10 LIMIT 5",
5141        ] {
5142            let q = only_query(sql);
5143            assert_eq!(q.limit, Some(5));
5144            assert_eq!(q.offset, Some(10));
5145        }
5146    }
5147
5148    #[test]
5149    fn parses_inner_join_on() {
5150        use crate::ast::{JoinConstraint, JoinKind, TableExpr};
5151        let s = only_select("SELECT a.x FROM a JOIN b ON a.id = b.id");
5152        assert_eq!(s.from.len(), 1);
5153        match &s.from[0] {
5154            TableExpr::Join {
5155                kind, constraint, ..
5156            } => {
5157                assert_eq!(*kind, JoinKind::Inner);
5158                assert!(matches!(constraint, JoinConstraint::On(_)));
5159            }
5160            other => panic!("expected Join, got {other:?}"),
5161        }
5162    }
5163
5164    #[test]
5165    fn parses_left_join_using_and_aliases_and_comma() {
5166        use crate::ast::{JoinConstraint, JoinKind, TableExpr};
5167        let s = only_select("SELECT * FROM a x LEFT OUTER JOIN b AS y USING (id), c");
5168        assert_eq!(s.from.len(), 2); // comma -> two top-level items
5169        match &s.from[0] {
5170            TableExpr::Join {
5171                kind,
5172                constraint,
5173                left,
5174                right,
5175            } => {
5176                assert_eq!(*kind, JoinKind::Left);
5177                assert_eq!(*constraint, JoinConstraint::Using(vec!["id".into()]));
5178                assert!(
5179                    matches!(**left, TableExpr::Table { ref alias, .. } if alias.as_deref() == Some("x"))
5180                );
5181                assert!(
5182                    matches!(**right, TableExpr::Table { ref alias, .. } if alias.as_deref() == Some("y"))
5183                );
5184            }
5185            other => panic!("expected Join, got {other:?}"),
5186        }
5187        assert!(matches!(&s.from[1], TableExpr::Table { name, alias: None } if name == "c"));
5188    }
5189
5190    #[test]
5191    fn parses_natural_and_cross_and_derived_and_multiway() {
5192        use crate::ast::TableExpr;
5193        assert!(matches!(
5194            one("SELECT * FROM a NATURAL JOIN b"),
5195            Statement::Query(_)
5196        ));
5197        assert!(matches!(
5198            one("SELECT * FROM a CROSS JOIN b"),
5199            Statement::Query(_)
5200        ));
5201        assert!(matches!(
5202            one("SELECT * FROM a JOIN b ON a.id=b.id JOIN c ON b.id=c.id"),
5203            Statement::Query(_)
5204        ));
5205        let s = only_select("SELECT d.n FROM (SELECT n FROM t) AS d");
5206        assert!(matches!(&s.from[0], TableExpr::Derived { alias, .. } if alias == "d"));
5207    }
5208
5209    #[test]
5210    fn parses_information_schema_qualified_tables() {
5211        let s = only_select(
5212            "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'",
5213        );
5214
5215        assert!(matches!(
5216            &s.from[..],
5217            [crate::ast::TableExpr::Table { name, alias: None }] if name == "information_schema.tables"
5218        ));
5219    }
5220
5221    #[test]
5222    fn parses_qualified_wildcard() {
5223        use crate::ast::SelectItem;
5224        let s = only_select("SELECT a.* FROM a JOIN b ON a.id=b.id");
5225        assert_eq!(s.projection[0], SelectItem::QualifiedWildcard("a".into()));
5226    }
5227
5228    #[test]
5229    fn derived_table_requires_alias() {
5230        assert!(parse("SELECT * FROM (SELECT 1)").is_err());
5231    }
5232
5233    // ---- SP34: subquery expressions ----
5234
5235    #[test]
5236    fn parses_scalar_subquery_in_expression_position() {
5237        match expr("(SELECT 1)") {
5238            Expr::ScalarSubquery(s) => {
5239                let crate::ast::SetExpr::Query(crate::ast::QueryBody::Select(select)) = &s.body
5240                else {
5241                    panic!("expected SELECT scalar subquery");
5242                };
5243                assert_eq!(select.projection.len(), 1);
5244                assert!(select.from.is_empty());
5245            }
5246            other => panic!("expected ScalarSubquery, got {other:?}"),
5247        }
5248        // Nested in arithmetic; and a plain parenthesised expr is still grouping.
5249        assert!(matches!(
5250            expr("1 + (SELECT a FROM t)"),
5251            Expr::Binary { right, .. } if matches!(*right, Expr::ScalarSubquery(_))
5252        ));
5253        assert!(matches!(expr("(1 + 2) * 3"), Expr::Binary { .. }));
5254    }
5255
5256    #[test]
5257    fn parses_exists_and_not_exists() {
5258        assert!(matches!(expr("EXISTS (SELECT 1 FROM t)"), Expr::Exists(_)));
5259        match expr("NOT EXISTS (SELECT 1 FROM t)") {
5260            Expr::Unary {
5261                op: UnaryOp::Not,
5262                expr,
5263            } => {
5264                assert!(matches!(*expr, Expr::Exists(_)));
5265            }
5266            other => panic!("expected NOT(EXISTS …), got {other:?}"),
5267        }
5268    }
5269
5270    #[test]
5271    fn parses_in_subquery_and_keeps_in_list_working() {
5272        assert!(matches!(expr("a IN (1, 2, 3)"), Expr::InList { .. }));
5273        match expr("a IN (SELECT id FROM t)") {
5274            Expr::InSubquery { negated, .. } => assert!(!negated),
5275            other => panic!("expected InSubquery, got {other:?}"),
5276        }
5277        match expr("a NOT IN (SELECT id FROM t)") {
5278            Expr::InSubquery { negated, .. } => assert!(negated),
5279            other => panic!("expected negated InSubquery, got {other:?}"),
5280        }
5281    }
5282
5283    #[test]
5284    fn parses_quantified_any_all_some() {
5285        match expr("a = ANY (SELECT id FROM t)") {
5286            Expr::Quantified {
5287                op: BinaryOp::Eq,
5288                all,
5289                ..
5290            } => assert!(!all),
5291            other => panic!("expected ANY, got {other:?}"),
5292        }
5293        match expr("a > ALL (SELECT v FROM t)") {
5294            Expr::Quantified {
5295                op: BinaryOp::Gt,
5296                all,
5297                ..
5298            } => assert!(all),
5299            other => panic!("expected ALL, got {other:?}"),
5300        }
5301        match expr("a <> SOME (SELECT v FROM t)") {
5302            Expr::Quantified {
5303                op: BinaryOp::Ne,
5304                all,
5305                ..
5306            } => assert!(!all),
5307            other => panic!("expected SOME(=ANY), got {other:?}"),
5308        }
5309    }
5310
5311    #[test]
5312    fn parses_with_in_nested_select_subquery_positions() {
5313        use crate::ast::{QueryBody, SetExpr, TableExpr};
5314
5315        let sel = only_select("SELECT * FROM (WITH c AS (SELECT 1 AS x) SELECT * FROM c) AS d");
5316        let TableExpr::Derived { subquery, .. } = &sel.from[0] else {
5317            panic!("expected derived table");
5318        };
5319        assert!(subquery.with.is_some());
5320        let SetExpr::Query(QueryBody::Select(_)) = &subquery.body else {
5321            panic!("expected SELECT derived table");
5322        };
5323
5324        match expr("(WITH c AS (SELECT 1 AS x) SELECT x FROM c)") {
5325            Expr::ScalarSubquery(s) => assert!(s.with.is_some()),
5326            other => panic!("expected scalar subquery, got {other:?}"),
5327        }
5328        match expr("EXISTS (WITH c AS (SELECT 1 AS x) SELECT x FROM c)") {
5329            Expr::Exists(s) => assert!(s.with.is_some()),
5330            other => panic!("expected EXISTS, got {other:?}"),
5331        }
5332        match expr("1 IN (WITH c AS (SELECT 1 AS x) SELECT x FROM c)") {
5333            Expr::InSubquery { subquery, .. } => assert!(subquery.with.is_some()),
5334            other => panic!("expected IN subquery, got {other:?}"),
5335        }
5336        match expr("1 = ANY (WITH c AS (SELECT 1 AS x) SELECT x FROM c)") {
5337            Expr::Quantified { subquery, .. } => assert!(subquery.with.is_some()),
5338            other => panic!("expected quantified subquery, got {other:?}"),
5339        }
5340    }
5341
5342    // ------------------------------------------------------------------
5343    // Recursion-depth guard (54001 / statement_too_complex).
5344    //
5345    // Two distinct DoS crash modes, both must return a clean 54001 — never a
5346    // stack overflow that aborts the whole server process:
5347    //   mode 1  deep PARSE recursion: nested parens/CASE/NOT/unary minus all
5348    //           funnel through `expr`, so a guard there bounds them.
5349    //   mode 2  deep AST TREE from a flat left-assoc chain (`1+1+1+…`): the
5350    //           Pratt loop parses iteratively but builds an N-deep left-nested
5351    //           tree that then overflows in eval AND on recursive Box `Drop`.
5352    //           Capping the loop iterations prevents the over-deep tree.
5353    // ------------------------------------------------------------------
5354
5355    /// Mode 1: `(((…1…)))` nested far beyond `MAX_DEPTH` → clean 54001, no crash.
5356    #[test]
5357    fn deeply_nested_parens_return_54001_not_a_crash() {
5358        let n = MAX_DEPTH * 4;
5359        let sql = format!("SELECT {}1{}", "(".repeat(n), ")".repeat(n));
5360        let err = parse(&sql).expect_err("too-deep parens must error, not crash");
5361        assert_eq!(err.sqlstate(), "54001", "got {err:?}");
5362        assert_eq!(err.message, "stack depth limit exceeded");
5363    }
5364
5365    /// Mode 1: deep prefix `NOT` chain funnels through `expr` → 54001.
5366    #[test]
5367    fn deeply_nested_not_returns_54001() {
5368        let n = MAX_DEPTH * 4;
5369        let sql = format!("SELECT {}true", "NOT ".repeat(n));
5370        let err = parse(&sql).expect_err("too-deep NOT must error");
5371        assert_eq!(err.sqlstate(), "54001", "got {err:?}");
5372    }
5373
5374    /// Mode 1: deeply nested scalar subqueries `(SELECT (SELECT …))` → 54001.
5375    #[test]
5376    fn deeply_nested_subqueries_return_54001() {
5377        let n = MAX_DEPTH * 2;
5378        let sql = format!("SELECT {}1{}", "(SELECT ".repeat(n), ")".repeat(n));
5379        let err = parse(&sql).expect_err("too-deep subqueries must error");
5380        assert_eq!(err.sqlstate(), "54001", "got {err:?}");
5381    }
5382
5383    /// Mode 2: a long flat `1+1+1+…` chain is parsed iteratively but builds an
5384    /// N-deep left-nested tree; capping the Pratt loop returns 54001 so the
5385    /// tree (and its later eval/Drop) never over-deepens.
5386    #[test]
5387    fn long_left_assoc_chain_returns_54001() {
5388        let n = MAX_DEPTH * 4;
5389        let sql = format!("SELECT {}1", "1+".repeat(n));
5390        let err = parse(&sql).expect_err("too-long additive chain must error");
5391        assert_eq!(err.sqlstate(), "54001", "got {err:?}");
5392    }
5393
5394    /// Crash-safety floor: a query nested right up to the limit must PARSE OK
5395    /// (no stack overflow). If this test ABORTS the process (stack overflow rather
5396    /// than a clean pass), `MAX_DEPTH` is too high for the runner's ~2 MiB stack
5397    /// and must be lowered. Each `(` adds one `expr` frame (one `DepthGuard`
5398    /// level); `select_core` + the outermost projection `expr` add 2 guard
5399    /// levels on top, so `MAX_DEPTH - 2` is the deepest paren query the parser
5400    /// admits — this test uses `MAX_DEPTH - 3` for one extra level of headroom.
5401    #[test]
5402    fn at_limit_parens_parse_ok() {
5403        let n = MAX_DEPTH - 3;
5404        let sql = format!("SELECT {}1{}", "(".repeat(n), ")".repeat(n));
5405        parse(&sql).expect("a query nested at the limit must parse, not crash");
5406    }
5407
5408    /// The guard actually fires near the limit (not merely far away): a paren
5409    /// nest a few levels OVER `MAX_DEPTH` returns 54001, while the `at_limit`
5410    /// test above proves a nest just UNDER it still parses — so the boundary is
5411    /// where it is intended to be.
5412    #[test]
5413    fn parens_just_over_limit_returns_54001() {
5414        let n = MAX_DEPTH + 2;
5415        let sql = format!("SELECT {}1{}", "(".repeat(n), ")".repeat(n));
5416        assert_eq!(
5417            parse(&sql)
5418                .expect_err("just over the limit must error")
5419                .sqlstate(),
5420            "54001",
5421        );
5422    }
5423
5424    /// A modest real-world nesting depth (well under the limit) parses fine —
5425    /// the guard does not reject ordinary queries.
5426    #[test]
5427    fn modest_nesting_parses_fine() {
5428        let sql = format!("SELECT {}1{}", "(".repeat(20), ")".repeat(20));
5429        parse(&sql).expect("modest nesting must parse");
5430        // A flat chain of 20 additions is fine too.
5431        parse(&format!("SELECT {}1", "1+".repeat(20))).expect("modest chain must parse");
5432    }
5433
5434    /// Mode 2 (set ops): a long flat `… UNION ALL …` chain is parsed by the
5435    /// `set_expr` LOOP, building an N-deep left-nested `SetExpr` that would overflow
5436    /// the executor's `fold`/`resolve_set_columns` AND recursive `Drop`. The loop
5437    /// iteration cap returns a clean 54001.
5438    #[test]
5439    fn long_union_chain_returns_54001() {
5440        let n = MAX_DEPTH * 4;
5441        let sql = format!("SELECT 1{}", " UNION ALL SELECT 1".repeat(n));
5442        let err = parse(&sql).expect_err("too-long UNION chain must error, not crash");
5443        assert_eq!(err.sqlstate(), "54001", "got {err:?}");
5444    }
5445
5446    /// Mode 1 (set ops): deeply nested parens around a query recurse
5447    /// `set_primary → set_expr → set_primary` (NOT through `expr`), so the
5448    /// `set_expr` guard must catch them → 54001.
5449    #[test]
5450    fn deeply_nested_query_parens_return_54001() {
5451        let n = MAX_DEPTH * 4;
5452        let sql = format!("{}SELECT 1{}", "(".repeat(n), ")".repeat(n));
5453        let err = parse(&sql).expect_err("too-deep query parens must error, not crash");
5454        assert_eq!(err.sqlstate(), "54001", "got {err:?}");
5455    }
5456
5457    /// A modest `UNION` chain (well under the limit) parses fine — the cap does not
5458    /// reject ordinary set-op queries.
5459    #[test]
5460    fn modest_union_chain_parses_fine() {
5461        let sql = format!("SELECT 1{}", " UNION ALL SELECT 1".repeat(20));
5462        parse(&sql).expect("modest UNION chain must parse");
5463    }
5464
5465    #[test]
5466    fn parses_standalone_values_query() {
5467        use crate::ast::{Expr, QueryBody, SetExpr};
5468        let q = only_query("VALUES (1, 'a'), (2, 'b') ORDER BY 1 LIMIT 1 OFFSET 1");
5469        let SetExpr::Query(QueryBody::Values(body)) = &q.body else {
5470            panic!("expected VALUES, got {q:?}")
5471        };
5472        assert_eq!(body.rows.len(), 2);
5473        assert_eq!(body.rows[0].len(), 2);
5474        assert!(matches!(body.rows[0][0], Expr::IntLiteral(_)));
5475        assert_eq!(q.order_by.len(), 1);
5476        assert_eq!(q.limit, Some(1));
5477        assert_eq!(q.offset, Some(1));
5478    }
5479
5480    #[test]
5481    fn parses_values_as_set_operation_branch() {
5482        use crate::ast::{QueryBody, SetExpr, SetOp};
5483        let q = only_query("VALUES (1) UNION ALL SELECT 2");
5484        let SetExpr::SetOp {
5485            op,
5486            all,
5487            left,
5488            right,
5489        } = &q.body
5490        else {
5491            panic!("expected set op body")
5492        };
5493        assert_eq!(*op, SetOp::Union);
5494        assert!(*all);
5495        assert!(matches!(
5496            left.as_ref(),
5497            SetExpr::Query(QueryBody::Values(_))
5498        ));
5499        assert!(matches!(
5500            right.as_ref(),
5501            SetExpr::Query(QueryBody::Select(_))
5502        ));
5503    }
5504
5505    #[test]
5506    fn parses_values_derived_table_with_column_aliases() {
5507        use crate::ast::{QueryBody, SetExpr, TableExpr};
5508        let sel = only_select("SELECT id, name FROM (VALUES (1, 'a')) AS v(id, name)");
5509        let TableExpr::Derived {
5510            subquery,
5511            alias,
5512            columns,
5513        } = &sel.from[0]
5514        else {
5515            panic!("expected derived table")
5516        };
5517        assert!(matches!(
5518            subquery.body,
5519            SetExpr::Query(QueryBody::Values(_))
5520        ));
5521        assert_eq!(alias, "v");
5522        assert_eq!(
5523            columns.as_ref().expect("column aliases"),
5524            &vec!["id".to_string(), "name".to_string()]
5525        );
5526    }
5527
5528    #[test]
5529    fn parses_select_derived_table_with_column_aliases() {
5530        use crate::ast::{QueryBody, SetExpr, TableExpr};
5531        let sel = only_select("SELECT n FROM (SELECT a FROM t) AS d(n)");
5532        let TableExpr::Derived {
5533            subquery,
5534            alias,
5535            columns,
5536        } = &sel.from[0]
5537        else {
5538            panic!("expected derived table")
5539        };
5540        assert!(matches!(
5541            subquery.body,
5542            SetExpr::Query(QueryBody::Select(_))
5543        ));
5544        assert_eq!(alias, "d");
5545        assert_eq!(
5546            columns.as_ref().expect("column aliases"),
5547            &vec!["n".to_string()]
5548        );
5549    }
5550
5551    #[test]
5552    fn values_rows_must_have_at_least_one_expr() {
5553        assert!(crate::parse("VALUES ()").is_err());
5554    }
5555
5556    #[test]
5557    fn parses_union_all_and_precedence() {
5558        use crate::ast::{SetExpr, SetOp};
5559        // INTERSECT binds tighter than UNION: A UNION B INTERSECT C => A UNION (B INTERSECT C)
5560        let q = only_query("SELECT 1 UNION SELECT 2 INTERSECT SELECT 3");
5561        let SetExpr::SetOp { op, all, right, .. } = &q.body else {
5562            panic!("expected top SetOp")
5563        };
5564        assert_eq!(*op, SetOp::Union);
5565        assert!(!*all);
5566        assert!(matches!(
5567            right.as_ref(),
5568            SetExpr::SetOp {
5569                op: SetOp::Intersect,
5570                ..
5571            }
5572        ));
5573
5574        // UNION ALL sets `all`; left-associativity: A UNION B UNION C => (A UNION B) UNION C
5575        let q = only_query("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3");
5576        let SetExpr::SetOp { all, left, .. } = &q.body else {
5577            panic!()
5578        };
5579        assert!(*all);
5580        assert!(matches!(
5581            left.as_ref(),
5582            SetExpr::SetOp {
5583                op: SetOp::Union,
5584                ..
5585            }
5586        ));
5587    }
5588
5589    #[test]
5590    fn union_order_by_limit_bind_to_whole_query() {
5591        let q = only_query("SELECT 1 UNION SELECT 2 ORDER BY 1 LIMIT 5 OFFSET 1");
5592        assert_eq!(q.order_by.len(), 1);
5593        assert_eq!(q.limit, Some(5));
5594        assert_eq!(q.offset, Some(1));
5595    }
5596
5597    #[test]
5598    fn parenthesized_branch_keeps_its_own_order_limit() {
5599        use crate::ast::{QueryBody, SetExpr};
5600        let q = only_query("(SELECT 1 ORDER BY 1 LIMIT 1) UNION SELECT 2");
5601        let SetExpr::SetOp { left, .. } = &q.body else {
5602            panic!("expected top SetOp")
5603        };
5604        let SetExpr::Query(QueryBody::Select(b)) = left.as_ref() else {
5605            panic!("left branch is a SELECT leaf")
5606        };
5607        assert_eq!(b.limit, Some(1));
5608        assert_eq!(b.order_by.len(), 1);
5609    }
5610
5611    #[test]
5612    fn plain_select_is_unchanged() {
5613        let q = only_query("SELECT a FROM t ORDER BY a LIMIT 3");
5614        assert!(matches!(
5615            q.body,
5616            crate::ast::SetExpr::Query(crate::ast::QueryBody::Select(_))
5617        ));
5618        assert_eq!(q.limit, Some(3));
5619        assert_eq!(q.order_by.len(), 1);
5620    }
5621
5622    #[test]
5623    fn for_update_with_set_op_is_rejected() {
5624        assert!(crate::parse("SELECT 1 UNION SELECT 2 FOR UPDATE").is_err());
5625    }
5626
5627    #[test]
5628    fn parenthesized_tailed_query_exprs_can_be_set_op_branches() {
5629        use crate::ast::{QueryBody, SetExpr};
5630
5631        let q = only_query("(SELECT 1 UNION SELECT 2 ORDER BY 1) UNION SELECT 3");
5632        let SetExpr::SetOp { left, .. } = &q.body else {
5633            panic!("expected top SetOp")
5634        };
5635        let SetExpr::Query(QueryBody::Nested(inner)) = left.as_ref() else {
5636            panic!("left branch preserves the tailed set-op as a nested QueryExpr")
5637        };
5638        assert_eq!(inner.order_by.len(), 1);
5639        assert!(matches!(inner.body, SetExpr::SetOp { .. }));
5640
5641        let q = only_query("(VALUES (2), (1) ORDER BY 1 LIMIT 1) UNION SELECT 3");
5642        let SetExpr::SetOp { left, .. } = &q.body else {
5643            panic!("expected top SetOp")
5644        };
5645        let SetExpr::Query(QueryBody::Nested(inner)) = left.as_ref() else {
5646            panic!("left branch preserves the tailed VALUES as a nested QueryExpr")
5647        };
5648        assert_eq!(inner.order_by.len(), 1);
5649        assert_eq!(inner.limit, Some(1));
5650        assert!(matches!(inner.body, SetExpr::Query(QueryBody::Values(_))));
5651    }
5652
5653    #[test]
5654    fn union_distinct_is_the_default_form() {
5655        use crate::ast::SetExpr;
5656        // `UNION DISTINCT` is the explicit spelling of the default (dedup) form:
5657        // it parses to the same tree as a bare `UNION` (all == false).
5658        let q = only_query("SELECT 1 UNION DISTINCT SELECT 2");
5659        let SetExpr::SetOp { all, .. } = &q.body else {
5660            panic!("expected SetOp")
5661        };
5662        assert!(!*all, "UNION DISTINCT is the dedup (all == false) form");
5663    }
5664
5665    #[test]
5666    fn parses_with_select_values_and_setop_bodies() {
5667        use crate::ast::{QueryBody, SetExpr};
5668
5669        let q = only_query("WITH a AS (SELECT 1 AS x), b(y) AS (VALUES (2)) SELECT x FROM a");
5670        let with = q.with.as_ref().expect("with clause");
5671        assert!(!with.recursive);
5672        assert_eq!(with.ctes.len(), 2);
5673        assert_eq!(with.ctes[0].name, "a");
5674        assert!(with.ctes[0].columns.is_none());
5675        assert_eq!(with.ctes[1].name, "b");
5676        assert_eq!(
5677            with.ctes[1].columns.as_deref(),
5678            Some(&["y".to_string()][..])
5679        );
5680        assert!(matches!(
5681            with.ctes[0].query.body,
5682            SetExpr::Query(QueryBody::Select(_))
5683        ));
5684        assert!(matches!(
5685            with.ctes[1].query.body,
5686            SetExpr::Query(QueryBody::Values(_))
5687        ));
5688
5689        let q = only_query("WITH u AS (SELECT 1 UNION SELECT 2) SELECT * FROM u");
5690        assert!(matches!(
5691            q.with.as_ref().expect("with").ctes[0].query.body,
5692            SetExpr::SetOp { .. }
5693        ));
5694    }
5695
5696    #[test]
5697    fn parses_with_recursive_and_rejects_duplicate_cte_names() {
5698        let q = only_query("WITH RECURSIVE r AS (SELECT 1) SELECT * FROM r");
5699        assert!(q.with.as_ref().expect("with").recursive);
5700
5701        let err = parse("WITH a AS (SELECT 1), a AS (SELECT 2) SELECT * FROM a")
5702            .expect_err("duplicate CTE names rejected during parse");
5703        assert_eq!(err.sqlstate(), "42712");
5704    }
5705
5706    #[test]
5707    fn duplicate_cte_names_follow_identifier_normalization() {
5708        let err = parse("WITH a AS (SELECT 1), A AS (SELECT 2) SELECT * FROM a")
5709            .expect_err("unquoted identifiers normalize before duplicate CTE check");
5710        assert_eq!(err.sqlstate(), "42712");
5711
5712        parse("WITH \"A\" AS (SELECT 1), a AS (SELECT 2) SELECT * FROM a")
5713            .expect("quoted case-distinct CTE names are parser-distinct");
5714    }
5715
5716    // SP40: FDW DDL tests
5717
5718    #[test]
5719    fn parses_create_server() {
5720        assert_eq!(
5721            one(
5722                "CREATE SERVER s FOREIGN DATA WRAPPER kafka_fdw OPTIONS (bootstrap 'h:9092', registry_url 'http://r')"
5723            ),
5724            Statement::CreateServer {
5725                name: "s".into(),
5726                wrapper: "kafka_fdw".into(),
5727                options: vec![
5728                    ("bootstrap".into(), "h:9092".into()),
5729                    ("registry_url".into(), "http://r".into()),
5730                ],
5731            }
5732        );
5733    }
5734
5735    #[test]
5736    fn parses_create_foreign_table() {
5737        match one(
5738            "CREATE FOREIGN TABLE orders (id int4, total numeric) SERVER s OPTIONS (topic 'orders', value_format 'avro')",
5739        ) {
5740            Statement::CreateForeignTable {
5741                name,
5742                columns,
5743                server,
5744                options,
5745            } => {
5746                assert_eq!(name, "orders");
5747                assert_eq!(server, "s");
5748                assert_eq!(columns.len(), 2);
5749                assert_eq!(options[0], ("topic".into(), "orders".into()));
5750            }
5751            other => panic!("got {other:?}"),
5752        }
5753    }
5754
5755    #[test]
5756    fn parses_import_foreign_schema_limit_to() {
5757        match one(
5758            "IMPORT FOREIGN SCHEMA kafka LIMIT TO (orders, payments) FROM SERVER s INTO public",
5759        ) {
5760            Statement::ImportForeignSchema {
5761                server,
5762                into_schema,
5763                selector,
5764                ..
5765            } => {
5766                assert_eq!(server, "s");
5767                assert_eq!(into_schema, "public");
5768                assert!(
5769                    matches!(selector, crate::ast::ImportSelector::LimitTo(ref v) if v == &["orders", "payments"])
5770                );
5771            }
5772            other => panic!("got {other:?}"),
5773        }
5774    }
5775
5776    #[test]
5777    fn create_user_mapping_for_public() {
5778        match one(
5779            "CREATE USER MAPPING FOR PUBLIC SERVER s OPTIONS (sasl_mechanism 'SCRAM-SHA-256', username 'u', password 'p')",
5780        ) {
5781            Statement::CreateUserMapping { user, server, .. } => {
5782                assert_eq!(user, "public");
5783                assert_eq!(server, "s");
5784            }
5785            other => panic!("got {other:?}"),
5786        }
5787    }
5788
5789    #[test]
5790    fn parses_create_fdw() {
5791        assert_eq!(
5792            one("CREATE FOREIGN DATA WRAPPER kafka_fdw OPTIONS (protocol 'kafka')"),
5793            Statement::CreateFdw {
5794                name: "kafka_fdw".into(),
5795                options: vec![("protocol".into(), "kafka".into())],
5796            }
5797        );
5798    }
5799
5800    #[test]
5801    fn parses_drop_fdw() {
5802        assert_eq!(
5803            one("DROP FOREIGN DATA WRAPPER kafka_fdw"),
5804            Statement::DropFdw {
5805                name: "kafka_fdw".into(),
5806                if_exists: false,
5807            }
5808        );
5809        assert_eq!(
5810            one("DROP FOREIGN DATA WRAPPER IF EXISTS kafka_fdw"),
5811            Statement::DropFdw {
5812                name: "kafka_fdw".into(),
5813                if_exists: true,
5814            }
5815        );
5816    }
5817
5818    #[test]
5819    fn parses_drop_server() {
5820        assert_eq!(
5821            one("DROP SERVER s"),
5822            Statement::DropServer {
5823                name: "s".into(),
5824                if_exists: false,
5825            }
5826        );
5827        assert_eq!(
5828            one("DROP SERVER IF EXISTS s"),
5829            Statement::DropServer {
5830                name: "s".into(),
5831                if_exists: true,
5832            }
5833        );
5834    }
5835
5836    #[test]
5837    fn parses_alter_server() {
5838        assert_eq!(
5839            one("ALTER SERVER s OPTIONS (bootstrap 'b:9092')"),
5840            Statement::AlterServer {
5841                name: "s".into(),
5842                options: vec![("bootstrap".into(), "b:9092".into())],
5843            }
5844        );
5845    }
5846
5847    #[test]
5848    fn parses_create_user_mapping_for_current_user() {
5849        match one("CREATE USER MAPPING FOR CURRENT_USER SERVER s OPTIONS (username 'u')") {
5850            Statement::CreateUserMapping {
5851                user,
5852                server,
5853                options,
5854            } => {
5855                assert_eq!(user, "current_user");
5856                assert_eq!(server, "s");
5857                assert_eq!(options[0], ("username".into(), "u".into()));
5858            }
5859            other => panic!("got {other:?}"),
5860        }
5861    }
5862
5863    #[test]
5864    fn parses_alter_user_mapping() {
5865        match one("ALTER USER MAPPING FOR PUBLIC SERVER s OPTIONS (username 'newu')") {
5866            Statement::AlterUserMapping {
5867                user,
5868                server,
5869                options,
5870            } => {
5871                assert_eq!(user, "public");
5872                assert_eq!(server, "s");
5873                assert_eq!(options[0], ("username".into(), "newu".into()));
5874            }
5875            other => panic!("got {other:?}"),
5876        }
5877    }
5878
5879    #[test]
5880    fn parses_drop_user_mapping() {
5881        assert_eq!(
5882            one("DROP USER MAPPING FOR PUBLIC SERVER s"),
5883            Statement::DropUserMapping {
5884                user: "public".into(),
5885                server: "s".into(),
5886                if_exists: false,
5887            }
5888        );
5889        assert_eq!(
5890            one("DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER s"),
5891            Statement::DropUserMapping {
5892                user: "public".into(),
5893                server: "s".into(),
5894                if_exists: true,
5895            }
5896        );
5897    }
5898
5899    #[test]
5900    fn parses_drop_foreign_table() {
5901        assert_eq!(
5902            one("DROP FOREIGN TABLE orders"),
5903            Statement::DropForeignTable {
5904                name: "orders".into(),
5905                if_exists: false,
5906            }
5907        );
5908        assert_eq!(
5909            one("DROP FOREIGN TABLE IF EXISTS orders"),
5910            Statement::DropForeignTable {
5911                name: "orders".into(),
5912                if_exists: true,
5913            }
5914        );
5915    }
5916
5917    #[test]
5918    fn parses_import_foreign_schema_except() {
5919        match one("IMPORT FOREIGN SCHEMA remote EXCEPT (foo, bar) FROM SERVER s INTO myschema") {
5920            Statement::ImportForeignSchema {
5921                remote_schema,
5922                selector,
5923                server,
5924                into_schema,
5925            } => {
5926                assert_eq!(remote_schema, "remote");
5927                assert_eq!(server, "s");
5928                assert_eq!(into_schema, "myschema");
5929                assert!(
5930                    matches!(selector, crate::ast::ImportSelector::Except(ref v) if v == &["foo", "bar"])
5931                );
5932            }
5933            other => panic!("got {other:?}"),
5934        }
5935    }
5936
5937    #[test]
5938    fn parses_import_foreign_schema_all() {
5939        match one("IMPORT FOREIGN SCHEMA kafka FROM SERVER s INTO public") {
5940            Statement::ImportForeignSchema {
5941                remote_schema,
5942                selector,
5943                server,
5944                into_schema,
5945            } => {
5946                assert_eq!(remote_schema, "kafka");
5947                assert_eq!(server, "s");
5948                assert_eq!(into_schema, "public");
5949                assert!(matches!(selector, crate::ast::ImportSelector::All));
5950            }
5951            other => panic!("got {other:?}"),
5952        }
5953    }
5954
5955    #[test]
5956    fn parses_create_server_no_options() {
5957        assert_eq!(
5958            one("CREATE SERVER s FOREIGN DATA WRAPPER w"),
5959            Statement::CreateServer {
5960                name: "s".into(),
5961                wrapper: "w".into(),
5962                options: vec![],
5963            }
5964        );
5965    }
5966
5967    #[test]
5968    fn parses_create_foreign_table_no_options() {
5969        match one("CREATE FOREIGN TABLE t (id int4) SERVER s") {
5970            Statement::CreateForeignTable {
5971                name,
5972                columns,
5973                server,
5974                options,
5975            } => {
5976                assert_eq!(name, "t");
5977                assert_eq!(server, "s");
5978                assert_eq!(columns.len(), 1);
5979                assert!(options.is_empty());
5980            }
5981            other => panic!("got {other:?}"),
5982        }
5983    }
5984
5985    // Mutant-killing tests for DROP dispatch arms and ALTER guard
5986
5987    #[test]
5988    fn drop_server_with_and_without_if_exists() {
5989        // Kills: Token::Keyword(Keyword::Server) arm deletion in DROP dispatch —
5990        // if this arm were deleted, DROP SERVER would fall through to drop_table and fail.
5991        assert_eq!(
5992            one("DROP SERVER myserver"),
5993            Statement::DropServer {
5994                name: "myserver".into(),
5995                if_exists: false,
5996            }
5997        );
5998        assert_eq!(
5999            one("DROP SERVER IF EXISTS myserver"),
6000            Statement::DropServer {
6001                name: "myserver".into(),
6002                if_exists: true,
6003            }
6004        );
6005    }
6006
6007    #[test]
6008    fn drop_user_mapping_routes_correctly() {
6009        // Kills: Token::Keyword(Keyword::User) arm deletion in DROP dispatch
6010        assert_eq!(
6011            one("DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER s"),
6012            Statement::DropUserMapping {
6013                user: "public".into(),
6014                server: "s".into(),
6015                if_exists: true,
6016            }
6017        );
6018    }
6019
6020    #[test]
6021    fn drop_foreign_table_routes_to_correct_fn() {
6022        // Kills: Token::Keyword(Keyword::Foreign) arm + inner Table arm in DROP dispatch.
6023        // If the Foreign arm were deleted, this would fall to drop_table and fail on TABLE.
6024        assert_eq!(
6025            one("DROP FOREIGN TABLE IF EXISTS mytable"),
6026            Statement::DropForeignTable {
6027                name: "mytable".into(),
6028                if_exists: true,
6029            }
6030        );
6031        // Also verify plain DROP FOREIGN TABLE (no IF EXISTS)
6032        assert_eq!(
6033            one("DROP FOREIGN TABLE mytable"),
6034            Statement::DropForeignTable {
6035                name: "mytable".into(),
6036                if_exists: false,
6037            }
6038        );
6039    }
6040
6041    #[test]
6042    fn drop_fdw_routes_to_correct_fn() {
6043        // Kills: Token::Keyword(Keyword::Data) arm inside DROP FOREIGN dispatch.
6044        // If this arm were deleted, DROP FOREIGN DATA WRAPPER would fail on DATA.
6045        assert_eq!(
6046            one("DROP FOREIGN DATA WRAPPER IF EXISTS myfdw"),
6047            Statement::DropFdw {
6048                name: "myfdw".into(),
6049                if_exists: true,
6050            }
6051        );
6052        assert_eq!(
6053            one("DROP FOREIGN DATA WRAPPER myfdw"),
6054            Statement::DropFdw {
6055                name: "myfdw".into(),
6056                if_exists: false,
6057            }
6058        );
6059    }
6060
6061    #[test]
6062    fn alter_non_server_is_error() {
6063        // Kills: `s == "alter"` match guard replaced with true — verifies the guard
6064        // is necessary (non-"alter" idents must not be routed here)
6065        assert!(crate::parse("ALTER TABLE t ADD COLUMN x int4").is_err());
6066    }
6067
6068    #[test]
6069    fn alter_ident_guard_is_case_sensitive_to_alter() {
6070        // Also kills: guard `s == "alter"` — "alters" is not "alter" and must error
6071        assert!(crate::parse("alters SERVER s OPTIONS (a 'b')").is_err());
6072    }
6073
6074    #[test]
6075    fn eat_if_exists_requires_exists_after_if() {
6076        // Kills: `s.eq_ignore_ascii_case("exists")` match guard replaced with true/false —
6077        // "IF notexists" must NOT consume IF EXISTS
6078        assert!(crate::parse("DROP SERVER IF notexists myserver").is_err());
6079    }
6080
6081    #[test]
6082    fn drop_if_without_exists_is_error() {
6083        // IF followed by a non-EXISTS token must produce a 42601 parse error, not
6084        // silently mis-parse the statement (e.g. treating the next ident as the
6085        // object name while `if_exists` comes back false).
6086        let e = crate::parse("DROP SERVER IF NOTEXIST s")
6087            .expect_err("IF not followed by EXISTS must fail");
6088        assert_eq!(e.sqlstate(), "42601");
6089    }
6090
6091    #[test]
6092    fn drop_foreign_table_if_without_exists_is_error() {
6093        // Same invariant verified for a second DROP variant (DROP FOREIGN TABLE).
6094        let e = crate::parse("DROP FOREIGN TABLE IF foo t")
6095            .expect_err("IF not followed by EXISTS must fail");
6096        assert_eq!(e.sqlstate(), "42601");
6097    }
6098
6099    /// Valid `IF EXISTS` and no-`IF` forms still parse correctly after the fix.
6100    #[test]
6101    fn drop_if_exists_valid_forms_still_parse() {
6102        crate::parse("DROP SERVER IF EXISTS s").expect("DROP SERVER IF EXISTS must parse");
6103        crate::parse("DROP SERVER s").expect("DROP SERVER without IF EXISTS must parse");
6104        crate::parse("DROP FOREIGN TABLE IF EXISTS t")
6105            .expect("DROP FOREIGN TABLE IF EXISTS must parse");
6106        crate::parse("DROP FOREIGN TABLE t")
6107            .expect("DROP FOREIGN TABLE without IF EXISTS must parse");
6108    }
6109
6110    #[test]
6111    fn copy_from_stdin_parses_supported_text_subset() {
6112        let stmts = crate::parse("COPY accounts (id, name) FROM STDIN WITH (FORMAT text)")
6113            .expect("COPY FROM STDIN parses");
6114        let [crate::ast::Statement::Set { name, value, .. }] = stmts.as_slice() else {
6115            panic!("expected COPY sentinel statement, got {stmts:?}");
6116        };
6117        assert_eq!(name, crate::ast::COPY_FROM_STDIN_SENTINEL);
6118        assert_eq!(
6119            value,
6120            &crate::ast::SetValue::Value("text\taccounts\tid,name".into())
6121        );
6122    }
6123
6124    #[test]
6125    fn copy_unsupported_paths_are_feature_not_supported() {
6126        for sql in [
6127            "COPY accounts TO STDOUT",
6128            "COPY accounts FROM '/tmp/accounts.tsv'",
6129            "COPY accounts FROM STDIN WITH (FORMAT binary)",
6130            "COPY accounts FROM STDIN WITH (DELIMITER ',')",
6131        ] {
6132            let err = crate::parse(sql).expect_err(sql);
6133            assert_eq!(err.sqlstate(), "0A000", "{sql}");
6134        }
6135    }
6136}
6137#[test]
6138fn explicit_compatibility_refusals_parse_to_typed_statements() {
6139    use crate::ast::{RefusalCommand, Statement};
6140
6141    let cases = [
6142        (
6143            "ALTER DATABASE postgres RENAME TO other",
6144            RefusalCommand::AlterDatabase,
6145        ),
6146        ("CREATE DATABASE other", RefusalCommand::CreateDatabase),
6147        ("DROP DATABASE other", RefusalCommand::DropDatabase),
6148        (
6149            "ALTER EXTENSION plpgsql UPDATE",
6150            RefusalCommand::AlterExtension,
6151        ),
6152        ("DROP EXTENSION plpgsql", RefusalCommand::DropExtension),
6153        (
6154            "PREPARE TRANSACTION 'xid-1'",
6155            RefusalCommand::PrepareTransaction,
6156        ),
6157        ("COMMIT PREPARED 'xid-1'", RefusalCommand::CommitPrepared),
6158        (
6159            "ROLLBACK PREPARED 'xid-1'",
6160            RefusalCommand::RollbackPrepared,
6161        ),
6162    ];
6163
6164    for (sql, command) in cases {
6165        let statements = parse(sql).unwrap_or_else(|error| panic!("{sql}: {error}"));
6166        assert_eq!(statements, vec![Statement::CompatibilityRefusal(command)]);
6167    }
6168}
6169
6170#[test]
6171fn fdw_alter_refusals_share_typed_metadata() {
6172    use crate::ast::RefusalCommand;
6173
6174    for (sql, expected) in [
6175        (
6176            "ALTER SERVER s OPTIONS (host 'localhost')",
6177            RefusalCommand::AlterServer,
6178        ),
6179        (
6180            "ALTER USER MAPPING FOR PUBLIC SERVER s OPTIONS (username 'u')",
6181            RefusalCommand::AlterUserMapping,
6182        ),
6183    ] {
6184        let statement = parse(sql).expect(sql).pop().expect("one statement");
6185        assert_eq!(statement.compatibility_refusal(), Some(expected));
6186    }
6187}
6188
6189#[test]
6190fn dispatch_emits_exact_query_and_table_family_identities() {
6191    use crate::{command::CommandIdentity, parse_with_command_identities};
6192
6193    for (sql, expected) in [
6194        ("SELECT 1", CommandIdentity::Select),
6195        (
6196            "WITH q AS (VALUES (1)) SELECT * FROM q",
6197            CommandIdentity::Select,
6198        ),
6199        ("VALUES (1)", CommandIdentity::Values),
6200        ("(VALUES (1))", CommandIdentity::Values),
6201        ("CREATE TABLE t (id int4)", CommandIdentity::CreateTable),
6202        ("ALTER TABLE t RENAME TO t2", CommandIdentity::AlterTable),
6203    ] {
6204        let parsed = parse_with_command_identities(sql).expect(sql);
6205        assert_eq!(parsed[0].1, expected, "{sql}");
6206    }
6207
6208    for (family, sql) in [
6209        ("CREATE TABLE AS", "CREATE TABLE t AS SELECT 1"),
6210        (
6211            "CREATE TABLE INHERITS",
6212            "CREATE TABLE t (id int4) INHERITS (parent)",
6213        ),
6214        (
6215            "CREATE TABLE PARTITION BY",
6216            "CREATE TABLE t (id int4) PARTITION BY HASH (id)",
6217        ),
6218        (
6219            "CREATE TABLE PARTITION OF",
6220            "CREATE TABLE t PARTITION OF parent DEFAULT",
6221        ),
6222        (
6223            "ALTER TABLE ATTACH PARTITION",
6224            "ALTER TABLE t ATTACH PARTITION p DEFAULT",
6225        ),
6226        (
6227            "ALTER TABLE DETACH PARTITION",
6228            "ALTER TABLE t DETACH PARTITION p",
6229        ),
6230        (
6231            "ALTER TABLE ENABLE ROW LEVEL SECURITY",
6232            "ALTER TABLE t ENABLE ROW LEVEL SECURITY",
6233        ),
6234        ("TABLE", "TABLE t"),
6235    ] {
6236        assert!(
6237            parse_with_command_identities(sql).is_err(),
6238            "unsupported {family} must reject instead of emitting its generic parent identity: {sql}"
6239        );
6240    }
6241}
6242
6243#[test]
6244fn multi_statement_dispatch_preserves_each_emitted_identity() {
6245    use crate::{command::CommandIdentity, parse_with_command_identities};
6246
6247    let parsed = parse_with_command_identities(
6248        "BEGIN; VALUES (1); CREATE USER alice; END; ALTER TABLE t RENAME TO t2",
6249    )
6250    .expect("mixed statements parse");
6251    assert_eq!(
6252        parsed
6253            .into_iter()
6254            .map(|(_, identity)| identity)
6255            .collect::<Vec<_>>(),
6256        vec![
6257            CommandIdentity::Begin,
6258            CommandIdentity::Values,
6259            CommandIdentity::CreateUser,
6260            CommandIdentity::End,
6261            CommandIdentity::AlterTable,
6262        ]
6263    );
6264}
6265
6266#[test]
6267fn shared_ast_fake_dispatch_cannot_exist_without_an_identity_argument() {
6268    use crate::{ast::Statement, command::CommandIdentity};
6269
6270    fn fake_branch(identity: CommandIdentity) -> ParsedStatement {
6271        emitted(identity, Ok(Statement::Commit)).expect("fake branch emits")
6272    }
6273
6274    assert_eq!(
6275        fake_branch(CommandIdentity::Commit).command_identity,
6276        CommandIdentity::Commit
6277    );
6278    assert_eq!(
6279        fake_branch(CommandIdentity::End).command_identity,
6280        CommandIdentity::End
6281    );
6282}
6283
6284#[test]
6285fn explicit_compatibility_refusals_reject_malformed_neighbors() {
6286    for sql in [
6287        "CREATE DATABASE",
6288        "DROP DATABASE db unexpected",
6289        "ALTER DATABASE db",
6290        "ALTER EXTENSION ext UPDATE unexpected",
6291        "DROP EXTENSION",
6292        "PREPARE TRANSACTION xid",
6293        "COMMIT PREPARED xid",
6294        "ROLLBACK PREPARED 'xid' unexpected",
6295    ] {
6296        assert!(parse(sql).is_err(), "malformed refusal form parsed: {sql}");
6297    }
6298}
6299
6300#[test]
6301fn every_non_goal_has_a_bounded_typed_refusal_probe() {
6302    use crate::ast::{NON_GOAL_REFUSALS, Statement};
6303
6304    assert_eq!(NON_GOAL_REFUSALS.len(), 40);
6305    for spec in NON_GOAL_REFUSALS {
6306        assert_eq!(
6307            parse(spec.representative_sql),
6308            Ok(vec![Statement::CompatibilityRefusal(spec.command)]),
6309            "{}",
6310            spec.command.command_name(),
6311        );
6312        assert!(
6313            parse(&format!("{} unexpected", spec.representative_sql)).is_err(),
6314            "{} accepted an arbitrary trailing token",
6315            spec.command.command_name(),
6316        );
6317        let variant = refusal_variant_sql(spec.representative_sql);
6318        assert_ne!(variant, spec.representative_sql);
6319        assert_eq!(
6320            parse(&variant),
6321            Ok(vec![Statement::CompatibilityRefusal(spec.command)]),
6322            "{} variant: {variant}",
6323            spec.command.command_name(),
6324        );
6325    }
6326}
6327
6328#[cfg(test)]
6329fn refusal_variant_sql(sql: &str) -> String {
6330    const PLACEHOLDERS: &[&str] = &[
6331        "conv",
6332        "conv2",
6333        "lang",
6334        "lang2",
6335        "postgres",
6336        "opc",
6337        "opc2",
6338        "opf",
6339        "opf2",
6340        "pub",
6341        "r",
6342        "r2",
6343        "sub",
6344        "ts",
6345        "ts2",
6346        "p",
6347        "p2",
6348        "t",
6349        "t2",
6350        "am",
6351        "handler_fn",
6352        "func",
6353        "int4eq",
6354        "f",
6355    ];
6356    let tokens = lex(sql).expect("representative lexes");
6357    let mut out = String::new();
6358    for (token, _) in tokens {
6359        if token == Token::Eof {
6360            break;
6361        }
6362        if !out.is_empty() {
6363            out.push(' ');
6364        }
6365        match token {
6366            Token::Ident(value) if PLACEHOLDERS.contains(&value.as_str()) => {
6367                out.push_str(&value);
6368                out.push_str("_variant");
6369            }
6370            Token::StringLit(_) => out.push_str("'variant'"),
6371            Token::IntLit(_) => out.push_str("42"),
6372            other => out.push_str(&token_sql(&other)),
6373        }
6374    }
6375    out
6376}
6377
6378#[cfg(test)]
6379fn token_sql(token: &Token) -> String {
6380    match token {
6381        Token::Ident(value) => value.clone(),
6382        Token::Keyword(keyword) => format!("{keyword:?}").to_ascii_lowercase(),
6383        Token::LParen => "(".into(),
6384        Token::RParen => ")".into(),
6385        Token::Comma => ",".into(),
6386        Token::Eq => "=".into(),
6387        Token::Lt => "<".into(),
6388        Token::Plus => "+".into(),
6389        other => panic!("unhandled representative token {other:?}"),
6390    }
6391}