Skip to main content

fsqlite_parser/
expr.rs

1// bd-16ov: §12.15 Expression Syntax
2//
3// Pratt expression parser with SQLite-correct operator precedence.
4// Normative reference: §10.2 of the FrankenSQLite specification.
5//
6// Precedence table (from canonical upstream SQLite grammar, lowest to highest):
7//   OR
8//   AND
9//   NOT (prefix)
10//   = == != <> IS [NOT] MATCH LIKE GLOB BETWEEN IN ISNULL NOTNULL
11//   < <= > >=
12//   & | << >> (bitwise)
13//   + - (binary)
14//   * / %
15//   || -> ->> (left-associative; same precedence level)
16//   COLLATE (postfix)
17//   ~ - + (unary prefix)
18
19use fsqlite_ast::{
20    BinaryOp, ColumnRef, Expr, FunctionArgs, InSet, JsonArrow, LikeOp, Literal, PlaceholderType,
21    RaiseAction, SelectStatement, Span, TypeName, UnaryOp, WindowSpec,
22};
23use std::sync::Arc;
24
25use crate::parser::{ParseError, Parser, is_nonreserved_kw, kw_to_str};
26use crate::token::{Token, TokenKind};
27
28// Binding powers: higher = tighter binding.
29// Left BP is checked against min_bp; right BP is passed to recursive call.
30mod bp {
31    // Infix: (left, right)
32    pub const OR: (u8, u8) = (1, 2);
33    pub const AND: (u8, u8) = (3, 4);
34    // Prefix NOT right BP:
35    pub const NOT_PREFIX: u8 = 5;
36    // Equality / pattern / membership:
37    pub const EQUALITY: (u8, u8) = (7, 8);
38    // Relational comparison:
39    pub const COMPARISON: (u8, u8) = (9, 10);
40    // Bitwise operators (all share one level in SQLite):
41    pub const BITWISE: (u8, u8) = (13, 14);
42    // Addition / subtraction:
43    pub const ADD: (u8, u8) = (15, 16);
44    // Multiplication / division / modulo:
45    pub const MUL: (u8, u8) = (17, 18);
46    // String concatenation:
47    pub const CONCAT: (u8, u8) = (19, 20);
48    // COLLATE (postfix left BP):
49    pub const COLLATE: u8 = 21;
50    // Unary prefix (- + ~) right BP:
51    pub const UNARY: u8 = 23;
52    // JSON access (-> ->>): Same as CONCAT
53    pub const JSON: (u8, u8) = (19, 20);
54}
55
56impl Parser {
57    /// Parse a single SQL expression.
58    pub fn parse_expr(&mut self) -> Result<Expr, ParseError> {
59        self.parse_expr_bp(0)
60    }
61
62    // ── Pratt core ──────────────────────────────────────────────────────
63
64    fn parse_expr_bp(&mut self, min_bp: u8) -> Result<Expr, ParseError> {
65        self.with_recursion_guard(|p| p.parse_expr_bp_inner(min_bp))
66    }
67
68    fn parse_expr_bp_inner(&mut self, min_bp: u8) -> Result<Expr, ParseError> {
69        let mut lhs = self.parse_prefix()?;
70
71        loop {
72            // Postfix: COLLATE, ISNULL, NOTNULL
73            if let Some(l_bp) = self.postfix_bp() {
74                if l_bp < min_bp {
75                    break;
76                }
77                lhs = self.parse_postfix(lhs)?;
78                continue;
79            }
80
81            // Infix: binary operators, IS, LIKE, BETWEEN, IN, etc.
82            if let Some((l_bp, r_bp)) = self.infix_bp() {
83                if l_bp < min_bp {
84                    break;
85                }
86                lhs = self.parse_infix(lhs, r_bp)?;
87                continue;
88            }
89
90            break;
91        }
92
93        Ok(lhs)
94    }
95
96    // ── Token helpers ───────────────────────────────────────────────────
97
98    fn peek_kind(&self) -> &TokenKind {
99        self.tokens
100            .get(self.pos)
101            .map_or(&TokenKind::Eof, |t| &t.kind)
102    }
103
104    #[allow(dead_code)]
105    fn peek_span(&self) -> Span {
106        self.tokens.get(self.pos).map_or(Span::ZERO, |t| t.span)
107    }
108
109    fn peek_token(&self) -> Option<&Token> {
110        self.tokens.get(self.pos)
111    }
112
113    fn peek_nth_token(&self, offset: usize) -> Option<&Token> {
114        self.tokens.get(self.pos + offset)
115    }
116
117    fn advance_token(&mut self) -> Token {
118        let tok = self.tokens[self.pos].clone();
119        if tok.kind != TokenKind::Eof {
120            self.pos += 1;
121        }
122        tok
123    }
124
125    fn at_kind(&self, kind: &TokenKind) -> bool {
126        std::mem::discriminant(self.peek_kind()) == std::mem::discriminant(kind)
127    }
128
129    fn eat_kind(&mut self, kind: &TokenKind) -> bool {
130        if self.at_kind(kind) {
131            self.advance_token();
132            true
133        } else {
134            false
135        }
136    }
137
138    fn expect_kind(&mut self, expected: &TokenKind) -> Result<Span, ParseError> {
139        if self.at_kind(expected) {
140            Ok(self.advance_token().span)
141        } else {
142            Err(self.err_here(format!("expected {expected:?}, got {:?}", self.peek_kind())))
143        }
144    }
145
146    fn err_here(&self, message: impl Into<String>) -> ParseError {
147        ParseError::at(message, self.peek_token())
148    }
149
150    // ── Prefix (nud) ────────────────────────────────────────────────────
151
152    #[allow(clippy::too_many_lines)]
153    fn parse_prefix(&mut self) -> Result<Expr, ParseError> {
154        let tok = self.advance_token();
155        match &tok.kind {
156            // ── Literals ────────────────────────────────────────────────
157            TokenKind::Integer(i) => Ok(Expr::Literal(Literal::Integer(*i), tok.span)),
158            TokenKind::OversizedInt(s) => match s.parse::<f64>() {
159                Ok(v) if v.is_finite() => Ok(Expr::Literal(Literal::Float(v), tok.span)),
160                Ok(_) => Ok(Expr::Literal(Literal::Float(f64::MAX), tok.span)),
161                Err(_) => Err(ParseError::at("integer out of range", Some(&tok))),
162            },
163            TokenKind::Float(f) => Ok(Expr::Literal(Literal::Float(*f), tok.span)),
164            TokenKind::String(s) => Ok(Expr::Literal(Literal::String(s.clone()), tok.span)),
165            TokenKind::Blob(b) => Ok(Expr::Literal(Literal::Blob(b.clone()), tok.span)),
166            TokenKind::KwNull => Ok(Expr::Literal(Literal::Null, tok.span)),
167            TokenKind::KwTrue => Ok(Expr::Literal(Literal::True, tok.span)),
168            TokenKind::KwFalse => Ok(Expr::Literal(Literal::False, tok.span)),
169            TokenKind::KwCurrentTime => Ok(Expr::Literal(Literal::CurrentTime, tok.span)),
170            TokenKind::KwCurrentDate => Ok(Expr::Literal(Literal::CurrentDate, tok.span)),
171            TokenKind::KwCurrentTimestamp => Ok(Expr::Literal(Literal::CurrentTimestamp, tok.span)),
172
173            // ── Bind parameters ─────────────────────────────────────────
174            TokenKind::Question => Ok(Expr::Placeholder(PlaceholderType::Anonymous, tok.span)),
175            TokenKind::QuestionNum(n) => {
176                Ok(Expr::Placeholder(PlaceholderType::Numbered(*n), tok.span))
177            }
178            TokenKind::ColonParam(s) => Ok(Expr::Placeholder(
179                PlaceholderType::ColonNamed(s.clone()),
180                tok.span,
181            )),
182            TokenKind::AtParam(s) => Ok(Expr::Placeholder(
183                PlaceholderType::AtNamed(s.clone()),
184                tok.span,
185            )),
186            TokenKind::DollarParam(s) => Ok(Expr::Placeholder(
187                PlaceholderType::DollarNamed(s.clone()),
188                tok.span,
189            )),
190
191            // ── Unary prefix: - + ~ ─────────────────────────────────────
192            TokenKind::Minus => {
193                // Peek ahead to handle exactly `-9223372036854775808`
194                if let TokenKind::OversizedInt(s) = self.peek_kind() {
195                    if s == "9223372036854775808" {
196                        let num_span = self.advance_token().span;
197                        let span = tok.span.merge(num_span);
198                        return Ok(Expr::Literal(Literal::Integer(i64::MIN), span));
199                    }
200                }
201                let inner = self.parse_expr_bp(bp::UNARY)?;
202                let span = tok.span.merge(inner.span());
203                Ok(Expr::UnaryOp {
204                    op: UnaryOp::Negate,
205                    expr: Box::new(inner),
206                    span,
207                })
208            }
209            TokenKind::Plus => {
210                let inner = self.parse_expr_bp(bp::UNARY)?;
211                let span = tok.span.merge(inner.span());
212                Ok(Expr::UnaryOp {
213                    op: UnaryOp::Plus,
214                    expr: Box::new(inner),
215                    span,
216                })
217            }
218            TokenKind::Tilde => {
219                let inner = self.parse_expr_bp(bp::UNARY)?;
220                let span = tok.span.merge(inner.span());
221                Ok(Expr::UnaryOp {
222                    op: UnaryOp::BitNot,
223                    expr: Box::new(inner),
224                    span,
225                })
226            }
227
228            // ── Prefix NOT ──────────────────────────────────────────────
229            TokenKind::KwNot => {
230                // NOT EXISTS (subquery)
231                if matches!(self.peek_kind(), TokenKind::KwExists) {
232                    self.advance_token();
233                    self.expect_kind(&TokenKind::LeftParen)?;
234                    let subquery = self.parse_subquery_minimal()?;
235                    let end = self.expect_kind(&TokenKind::RightParen)?;
236                    let span = tok.span.merge(end);
237                    return Ok(Expr::Exists {
238                        subquery: Box::new(subquery),
239                        not: true,
240                        span,
241                    });
242                }
243                let inner = self.parse_expr_bp(bp::NOT_PREFIX)?;
244                let span = tok.span.merge(inner.span());
245                Ok(Expr::UnaryOp {
246                    op: UnaryOp::Not,
247                    expr: Box::new(inner),
248                    span,
249                })
250            }
251
252            // ── EXISTS (subquery) ───────────────────────────────────────
253            TokenKind::KwExists => {
254                self.expect_kind(&TokenKind::LeftParen)?;
255                let subquery = self.parse_subquery_minimal()?;
256                let end = self.expect_kind(&TokenKind::RightParen)?;
257                let span = tok.span.merge(end);
258                Ok(Expr::Exists {
259                    subquery: Box::new(subquery),
260                    not: false,
261                    span,
262                })
263            }
264
265            // ── CAST(expr AS type_name) ─────────────────────────────────
266            TokenKind::KwCast => {
267                self.expect_kind(&TokenKind::LeftParen)?;
268                let inner = self.parse_expr()?;
269                self.expect_kind(&TokenKind::KwAs)?;
270                let type_name = self.parse_type_name()?;
271                let end = self.expect_kind(&TokenKind::RightParen)?;
272                let span = tok.span.merge(end);
273                Ok(Expr::Cast {
274                    expr: Box::new(inner),
275                    type_name,
276                    span,
277                })
278            }
279
280            // ── CASE [operand] WHEN ... THEN ... [ELSE ...] END ────────
281            TokenKind::KwCase => self.parse_case_expr(tok.span),
282
283            // ── RAISE(action, message) ──────────────────────────────────
284            TokenKind::KwRaise => {
285                self.expect_kind(&TokenKind::LeftParen)?;
286                let (action, message) = self.parse_raise_args()?;
287                let end = self.expect_kind(&TokenKind::RightParen)?;
288                let span = tok.span.merge(end);
289                Ok(Expr::Raise {
290                    action,
291                    message,
292                    span,
293                })
294            }
295
296            // ── Parenthesized expr / subquery / row-value ───────────────
297            TokenKind::LeftParen => {
298                if matches!(
299                    self.peek_kind(),
300                    TokenKind::KwSelect | TokenKind::KwWith | TokenKind::KwValues
301                ) {
302                    let subquery = self.parse_subquery_minimal()?;
303                    let end = self.expect_kind(&TokenKind::RightParen)?;
304                    let span = tok.span.merge(end);
305                    return Ok(Expr::Subquery(Box::new(subquery), span));
306                }
307                let first = self.parse_expr()?;
308                if self.eat_kind(&TokenKind::Comma) {
309                    let mut exprs = vec![first];
310                    loop {
311                        exprs.push(self.parse_expr()?);
312                        if !self.eat_kind(&TokenKind::Comma) {
313                            break;
314                        }
315                    }
316                    let end = self.expect_kind(&TokenKind::RightParen)?;
317                    let span = tok.span.merge(end);
318                    Ok(Expr::RowValue(exprs, span))
319                } else {
320                    self.expect_kind(&TokenKind::RightParen)?;
321                    Ok(first)
322                }
323            }
324
325            // ── Identifier: column ref or function call ─────────────────
326            TokenKind::Id(name) | TokenKind::QuotedId(name, _) => {
327                self.parse_ident_expr(Arc::clone(name), tok.span)
328            }
329
330            // ── Keywords usable as function names ───────────────────────
331            TokenKind::KwReplace if matches!(self.peek_kind(), TokenKind::LeftParen) => {
332                self.parse_function_call("replace".to_owned(), tok.span)
333            }
334            // C SQLite exposes the pattern-matching operators as scalar
335            // functions too: `like(P, X [, E])`, `glob(P, X)`,
336            // `regexp(P, X)`, `match(P, X)`. The token doubles as an infix
337            // operator, so only treat it as a function name when directly
338            // followed by `(`.
339            TokenKind::KwLike if matches!(self.peek_kind(), TokenKind::LeftParen) => {
340                self.parse_function_call("like".to_owned(), tok.span)
341            }
342            TokenKind::KwGlob if matches!(self.peek_kind(), TokenKind::LeftParen) => {
343                self.parse_function_call("glob".to_owned(), tok.span)
344            }
345            TokenKind::KwRegexp if matches!(self.peek_kind(), TokenKind::LeftParen) => {
346                self.parse_function_call("regexp".to_owned(), tok.span)
347            }
348            TokenKind::KwMatch if matches!(self.peek_kind(), TokenKind::LeftParen) => {
349                self.parse_function_call("match".to_owned(), tok.span)
350            }
351
352            // ── Non-reserved keywords usable as identifiers ─────────────
353            // In SQL, non-reserved keywords (like KEY, MATCH, FIRST, etc.)
354            // can be used as column names without quoting.
355            k if is_nonreserved_kw(k) => {
356                let name = kw_to_str(k);
357                self.parse_ident_expr(name, tok.span)
358            }
359
360            _ => Err(ParseError::at(
361                format!("unexpected token in expression: {:?}", tok.kind),
362                Some(&tok),
363            )),
364        }
365    }
366
367    /// Parse `name`, `name.column`, or `name(args)`.
368    fn parse_ident_expr<S>(&mut self, name: S, start: Span) -> Result<Expr, ParseError>
369    where
370        S: AsRef<str> + Into<Arc<str>>,
371    {
372        // Function call: name(...)
373        if matches!(self.peek_kind(), TokenKind::LeftParen) {
374            return self.parse_function_call(name.as_ref().to_owned(), start);
375        }
376        let name = name.into();
377        // Table-qualified column: name.column
378        if matches!(self.peek_kind(), TokenKind::Dot) {
379            let Some(col_tok) = self.peek_nth_token(1) else {
380                return Err(self.err_here("expected column name after '.'"));
381            };
382            let col_name = match &col_tok.kind {
383                TokenKind::Id(c) | TokenKind::QuotedId(c, _) => Arc::clone(c),
384                TokenKind::Star => Arc::<str>::from("*"),
385                // After a dot, ANY keyword is a valid column name (SQLite
386                // allows reserved keywords in table-qualified positions).
387                k if k.keyword_str().is_some() => Arc::<str>::from(kw_to_str(k)),
388                _ => {
389                    return Err(ParseError::at(
390                        format!("expected column name after '.', got {:?}", col_tok.kind),
391                        Some(col_tok),
392                    ));
393                }
394            };
395            let span = start.merge(col_tok.span);
396            self.pos = self.pos.saturating_add(2);
397            return Ok(Expr::Column(ColumnRef::qualified(name, col_name), span));
398        }
399        Ok(Expr::Column(ColumnRef::bare(name), start))
400    }
401
402    // ── Postfix ─────────────────────────────────────────────────────────
403
404    fn postfix_bp(&self) -> Option<u8> {
405        match self.peek_kind() {
406            TokenKind::KwCollate => Some(bp::COLLATE),
407            TokenKind::KwIsnull | TokenKind::KwNotnull => Some(bp::EQUALITY.0),
408            TokenKind::KwNot => {
409                if let Some(next) = self.tokens.get(self.pos + 1) {
410                    if matches!(next.kind, TokenKind::KwNull) {
411                        return Some(bp::EQUALITY.0);
412                    }
413                }
414                None
415            }
416            _ => None,
417        }
418    }
419
420    fn parse_postfix(&mut self, lhs: Expr) -> Result<Expr, ParseError> {
421        let tok = self.advance_token();
422        match &tok.kind {
423            TokenKind::KwCollate => {
424                let collation = match self.parse_identifier() {
425                    Ok(s) => s,
426                    Err(_) => {
427                        return Err(self.err_here("expected collation name after COLLATE"));
428                    }
429                };
430                let name_span = self.tokens[self.pos.saturating_sub(1)].span;
431                let span = lhs.span().merge(name_span);
432                Ok(Expr::Collate {
433                    expr: Box::new(lhs),
434                    collation,
435                    span,
436                })
437            }
438            TokenKind::KwIsnull => {
439                let span = lhs.span().merge(tok.span);
440                Ok(Expr::IsNull {
441                    expr: Box::new(lhs),
442                    not: false,
443                    span,
444                })
445            }
446            TokenKind::KwNotnull => {
447                let span = lhs.span().merge(tok.span);
448                Ok(Expr::IsNull {
449                    expr: Box::new(lhs),
450                    not: true,
451                    span,
452                })
453            }
454            TokenKind::KwNot => {
455                let null_tok = self.advance_token(); // we know from postfix_bp that this is KwNull
456                let span = lhs.span().merge(null_tok.span);
457                Ok(Expr::IsNull {
458                    expr: Box::new(lhs),
459                    not: true,
460                    span,
461                })
462            }
463            other => Err(ParseError::at(
464                format!("unexpected postfix token: {other:?}"),
465                Some(&tok),
466            )),
467        }
468    }
469
470    // ── Infix ───────────────────────────────────────────────────────────
471
472    fn infix_bp(&self) -> Option<(u8, u8)> {
473        match self.peek_kind() {
474            TokenKind::KwOr => Some(bp::OR),
475            TokenKind::KwAnd => Some(bp::AND),
476
477            TokenKind::Eq
478            | TokenKind::EqEq
479            | TokenKind::Ne
480            | TokenKind::LtGt
481            | TokenKind::KwIs
482            | TokenKind::KwLike
483            | TokenKind::KwGlob
484            | TokenKind::KwMatch
485            | TokenKind::KwRegexp
486            | TokenKind::KwBetween
487            | TokenKind::KwIn => Some(bp::EQUALITY),
488
489            // NOT LIKE / NOT IN / NOT BETWEEN / NOT GLOB / NOT MATCH / NOT REGEXP
490            TokenKind::KwNot => {
491                let next = self.tokens.get(self.pos + 1).map(|t| &t.kind);
492                match next {
493                    Some(
494                        TokenKind::KwLike
495                        | TokenKind::KwGlob
496                        | TokenKind::KwMatch
497                        | TokenKind::KwRegexp
498                        | TokenKind::KwBetween
499                        | TokenKind::KwIn,
500                    ) => Some(bp::EQUALITY),
501                    _ => None,
502                }
503            }
504
505            TokenKind::Lt | TokenKind::Le | TokenKind::Gt | TokenKind::Ge => Some(bp::COMPARISON),
506
507            TokenKind::Ampersand
508            | TokenKind::Pipe
509            | TokenKind::ShiftLeft
510            | TokenKind::ShiftRight => Some(bp::BITWISE),
511
512            TokenKind::Plus | TokenKind::Minus => Some(bp::ADD),
513            TokenKind::Star | TokenKind::Slash | TokenKind::Percent => Some(bp::MUL),
514            TokenKind::Concat => Some(bp::CONCAT),
515            TokenKind::Arrow | TokenKind::DoubleArrow => Some(bp::JSON),
516
517            _ => None,
518        }
519    }
520
521    #[allow(clippy::too_many_lines)]
522    fn parse_infix(&mut self, lhs: Expr, r_bp: u8) -> Result<Expr, ParseError> {
523        let tok = self.advance_token();
524        match &tok.kind {
525            // ── Simple binary operators ──────────────────────────────────
526            TokenKind::Plus => self.make_binop(lhs, BinaryOp::Add, r_bp),
527            TokenKind::Minus => self.make_binop(lhs, BinaryOp::Subtract, r_bp),
528            TokenKind::Star => self.make_binop(lhs, BinaryOp::Multiply, r_bp),
529            TokenKind::Slash => self.make_binop(lhs, BinaryOp::Divide, r_bp),
530            TokenKind::Percent => self.make_binop(lhs, BinaryOp::Modulo, r_bp),
531            TokenKind::Concat => self.make_binop(lhs, BinaryOp::Concat, r_bp),
532            TokenKind::Eq | TokenKind::EqEq => self.make_binop(lhs, BinaryOp::Eq, r_bp),
533            TokenKind::Ne | TokenKind::LtGt => self.make_binop(lhs, BinaryOp::Ne, r_bp),
534            TokenKind::Lt => self.make_binop(lhs, BinaryOp::Lt, r_bp),
535            TokenKind::Le => self.make_binop(lhs, BinaryOp::Le, r_bp),
536            TokenKind::Gt => self.make_binop(lhs, BinaryOp::Gt, r_bp),
537            TokenKind::Ge => self.make_binop(lhs, BinaryOp::Ge, r_bp),
538            TokenKind::Ampersand => self.make_binop(lhs, BinaryOp::BitAnd, r_bp),
539            TokenKind::Pipe => self.make_binop(lhs, BinaryOp::BitOr, r_bp),
540            TokenKind::ShiftLeft => self.make_binop(lhs, BinaryOp::ShiftLeft, r_bp),
541            TokenKind::ShiftRight => self.make_binop(lhs, BinaryOp::ShiftRight, r_bp),
542            TokenKind::KwOr => self.make_binop(lhs, BinaryOp::Or, r_bp),
543            TokenKind::KwAnd => self.make_binop(lhs, BinaryOp::And, r_bp),
544
545            // ── IS [NOT] [DISTINCT FROM | NULL | expr] ──────────────────────────────────
546            TokenKind::KwIs => {
547                let not = self.eat_kind(&TokenKind::KwNot);
548                if self.eat_kind(&TokenKind::KwDistinct) {
549                    self.expect_kind(&TokenKind::KwFrom)?;
550                    let rhs = self.parse_expr_bp(r_bp)?;
551                    let span = lhs.span().merge(rhs.span());
552                    // IS DISTINCT FROM is equivalent to IS NOT
553                    // IS NOT DISTINCT FROM is equivalent to IS
554                    let op = if not { BinaryOp::Is } else { BinaryOp::IsNot };
555                    return Ok(Expr::BinaryOp {
556                        left: Box::new(lhs),
557                        op,
558                        right: Box::new(rhs),
559                        span,
560                    });
561                }
562                if matches!(self.peek_kind(), TokenKind::KwNull) {
563                    let end = self.advance_token().span;
564                    let span = lhs.span().merge(end);
565                    return Ok(Expr::IsNull {
566                        expr: Box::new(lhs),
567                        not,
568                        span,
569                    });
570                }
571                let rhs = self.parse_expr_bp(r_bp)?;
572                let span = lhs.span().merge(rhs.span());
573                let op = if not { BinaryOp::IsNot } else { BinaryOp::Is };
574                Ok(Expr::BinaryOp {
575                    left: Box::new(lhs),
576                    op,
577                    right: Box::new(rhs),
578                    span,
579                })
580            }
581
582            // ── LIKE / GLOB / MATCH / REGEXP ────────────────────────────
583            TokenKind::KwLike => self.parse_like(lhs, LikeOp::Like, false),
584            TokenKind::KwGlob => self.parse_like(lhs, LikeOp::Glob, false),
585            TokenKind::KwMatch => self.parse_like(lhs, LikeOp::Match, false),
586            TokenKind::KwRegexp => self.parse_like(lhs, LikeOp::Regexp, false),
587
588            // ── BETWEEN ─────────────────────────────────────────────────
589            TokenKind::KwBetween => self.parse_between(lhs, false),
590
591            // ── IN ──────────────────────────────────────────────────────
592            TokenKind::KwIn => self.parse_in(lhs, false),
593
594            // ── JSON -> / ->> ───────────────────────────────────────────
595            TokenKind::Arrow => {
596                let rhs = self.parse_expr_bp(r_bp)?;
597                let span = lhs.span().merge(rhs.span());
598                Ok(Expr::JsonAccess {
599                    expr: Box::new(lhs),
600                    path: Box::new(rhs),
601                    arrow: JsonArrow::Arrow,
602                    span,
603                })
604            }
605            TokenKind::DoubleArrow => {
606                let rhs = self.parse_expr_bp(r_bp)?;
607                let span = lhs.span().merge(rhs.span());
608                Ok(Expr::JsonAccess {
609                    expr: Box::new(lhs),
610                    path: Box::new(rhs),
611                    arrow: JsonArrow::DoubleArrow,
612                    span,
613                })
614            }
615
616            // ── NOT LIKE / GLOB / BETWEEN / IN ──────────────────────────
617            TokenKind::KwNot => {
618                let next = self.advance_token();
619                match &next.kind {
620                    TokenKind::KwLike => self.parse_like(lhs, LikeOp::Like, true),
621                    TokenKind::KwGlob => self.parse_like(lhs, LikeOp::Glob, true),
622                    TokenKind::KwMatch => self.parse_like(lhs, LikeOp::Match, true),
623                    TokenKind::KwRegexp => self.parse_like(lhs, LikeOp::Regexp, true),
624                    TokenKind::KwBetween => self.parse_between(lhs, true),
625                    TokenKind::KwIn => self.parse_in(lhs, true),
626                    _ => Err(ParseError::at(
627                        format!(
628                            "expected LIKE/GLOB/MATCH/REGEXP/BETWEEN/IN \
629                             after NOT, got {:?}",
630                            next.kind
631                        ),
632                        Some(&next),
633                    )),
634                }
635            }
636
637            other => Err(ParseError::at(
638                format!("unexpected infix token: {other:?}"),
639                Some(&tok),
640            )),
641        }
642    }
643
644    fn make_binop(&mut self, lhs: Expr, op: BinaryOp, r_bp: u8) -> Result<Expr, ParseError> {
645        let rhs = self.parse_expr_bp(r_bp)?;
646        let span = lhs.span().merge(rhs.span());
647        Ok(Expr::BinaryOp {
648            left: Box::new(lhs),
649            op,
650            right: Box::new(rhs),
651            span,
652        })
653    }
654
655    // ── Special expression forms ────────────────────────────────────────
656
657    fn parse_like(&mut self, lhs: Expr, op: LikeOp, not: bool) -> Result<Expr, ParseError> {
658        let pattern = self.parse_expr_bp(bp::EQUALITY.1)?;
659        let escape = if self.eat_kind(&TokenKind::KwEscape) {
660            // SQLite's grammar accepts ESCAPE for all pattern-matching operators
661            // (LIKE, GLOB, MATCH, REGEXP), not just LIKE.
662            Some(Box::new(self.parse_expr_bp(bp::EQUALITY.1)?))
663        } else {
664            None
665        };
666        let end = escape.as_ref().map_or_else(|| pattern.span(), |e| e.span());
667        let span = lhs.span().merge(end);
668        Ok(Expr::Like {
669            expr: Box::new(lhs),
670            pattern: Box::new(pattern),
671            escape,
672            op,
673            not,
674            span,
675        })
676    }
677
678    fn parse_between(&mut self, lhs: Expr, not: bool) -> Result<Expr, ParseError> {
679        // Parse low bound above AND level so AND keyword is not consumed.
680        let low = self.parse_expr_bp(bp::NOT_PREFIX)?;
681        if !self.eat_kind(&TokenKind::KwAnd) {
682            return Err(self.err_here("expected AND in BETWEEN expression"));
683        }
684        let high = self.parse_expr_bp(bp::EQUALITY.1)?;
685        let span = lhs.span().merge(high.span());
686        Ok(Expr::Between {
687            expr: Box::new(lhs),
688            low: Box::new(low),
689            high: Box::new(high),
690            not,
691            span,
692        })
693    }
694
695    fn parse_in(&mut self, lhs: Expr, not: bool) -> Result<Expr, ParseError> {
696        let start = lhs.span();
697
698        // SQLite supports both "x IN ( ... )" and "x IN table_name".
699        if !self.at_kind(&TokenKind::LeftParen) {
700            let table = self.parse_qualified_name()?;
701            let end = self.tokens[self.pos.saturating_sub(1)].span;
702            let span = start.merge(end);
703            return Ok(Expr::In {
704                expr: Box::new(lhs),
705                set: InSet::Table(table),
706                not,
707                span,
708            });
709        }
710
711        self.expect_kind(&TokenKind::LeftParen)?;
712
713        if matches!(
714            self.peek_kind(),
715            TokenKind::KwSelect | TokenKind::KwWith | TokenKind::KwValues
716        ) {
717            let subquery = self.parse_subquery_minimal()?;
718            let end = self.expect_kind(&TokenKind::RightParen)?;
719            let span = start.merge(end);
720            return Ok(Expr::In {
721                expr: Box::new(lhs),
722                set: InSet::Subquery(Box::new(subquery)),
723                not,
724                span,
725            });
726        }
727
728        let mut exprs = Vec::new();
729        if !self.at_kind(&TokenKind::RightParen) {
730            exprs.push(self.parse_expr()?);
731            while self.eat_kind(&TokenKind::Comma) {
732                exprs.push(self.parse_expr()?);
733            }
734        }
735        let end = self.expect_kind(&TokenKind::RightParen)?;
736        let span = start.merge(end);
737        Ok(Expr::In {
738            expr: Box::new(lhs),
739            set: InSet::List(exprs),
740            not,
741            span,
742        })
743    }
744
745    fn parse_case_expr(&mut self, start: Span) -> Result<Expr, ParseError> {
746        let operand = if matches!(self.peek_kind(), TokenKind::KwWhen) {
747            None
748        } else {
749            Some(Box::new(self.parse_expr()?))
750        };
751
752        let mut whens = Vec::new();
753        while self.eat_kind(&TokenKind::KwWhen) {
754            let condition = self.parse_expr()?;
755            if !self.eat_kind(&TokenKind::KwThen) {
756                return Err(self.err_here("expected THEN in CASE expression"));
757            }
758            let result = self.parse_expr()?;
759            whens.push((condition, result));
760        }
761        if whens.is_empty() {
762            return Err(self.err_here("CASE requires at least one WHEN clause"));
763        }
764
765        let else_expr = if self.eat_kind(&TokenKind::KwElse) {
766            Some(Box::new(self.parse_expr()?))
767        } else {
768            None
769        };
770
771        if !self.eat_kind(&TokenKind::KwEnd) {
772            return Err(self.err_here("expected END for CASE expression"));
773        }
774        let end = self.tokens[self.pos.saturating_sub(1)].span;
775        let span = start.merge(end);
776        Ok(Expr::Case {
777            operand,
778            whens,
779            else_expr,
780            span,
781        })
782    }
783
784    fn parse_function_call(&mut self, name: String, start: Span) -> Result<Expr, ParseError> {
785        self.expect_kind(&TokenKind::LeftParen)?;
786
787        let (args, distinct) = if matches!(self.peek_kind(), TokenKind::Star) {
788            if !name.eq_ignore_ascii_case("count") {
789                return Err(self.err_here("'*' can only be used with count() function"));
790            }
791            self.advance_token();
792            (FunctionArgs::Star, false)
793        } else {
794            let distinct = self.eat_kind(&TokenKind::KwDistinct);
795            let args = if matches!(self.peek_kind(), TokenKind::RightParen) {
796                if distinct {
797                    return Err(self.err_here("DISTINCT requires at least one argument"));
798                }
799                FunctionArgs::List(Vec::new())
800            } else {
801                let mut list = vec![self.parse_expr()?];
802                while self.eat_kind(&TokenKind::Comma) {
803                    list.push(self.parse_expr()?);
804                }
805                FunctionArgs::List(list)
806            };
807            (args, distinct)
808        };
809
810        // In-aggregate ORDER BY (SQLite 3.44+): group_concat(x, ',' ORDER BY y DESC)
811        let order_by = if self.eat_kind(&TokenKind::KwOrder) {
812            self.expect_kind(&TokenKind::KwBy)?;
813            self.parse_comma_sep(Self::parse_ordering_term)?
814        } else {
815            vec![]
816        };
817
818        let mut end = self.expect_kind(&TokenKind::RightParen)?;
819        // Peek ahead: only consume FILTER if followed by '(' to avoid
820        // swallowing FILTER when used as a column alias (it's non-reserved).
821        let filter = if matches!(self.peek_kind(), TokenKind::KwFilter)
822            && self
823                .tokens
824                .get(self.pos + 1)
825                .is_some_and(|t| t.kind == TokenKind::LeftParen)
826        {
827            self.advance_token(); // consume FILTER
828            self.expect_kind(&TokenKind::LeftParen)?;
829            self.expect_kind(&TokenKind::KwWhere)?;
830            let predicate = self.parse_expr()?;
831            let filter_end = self.expect_kind(&TokenKind::RightParen)?;
832            end = end.merge(filter_end);
833            Some(Box::new(predicate))
834        } else {
835            None
836        };
837        // Peek: only consume OVER if followed by '(' or an identifier
838        // (window name), to avoid swallowing OVER as a column alias.
839        let over = if matches!(self.peek_kind(), TokenKind::KwOver)
840            && self.tokens.get(self.pos + 1).is_some_and(|t| {
841                matches!(
842                    t.kind,
843                    TokenKind::LeftParen | TokenKind::Id(_) | TokenKind::QuotedId(_, _)
844                )
845            }) {
846            self.advance_token(); // consume OVER
847            if self.eat_kind(&TokenKind::LeftParen) {
848                let spec = self.parse_window_spec()?;
849                let over_end = self.expect_kind(&TokenKind::RightParen)?;
850                end = end.merge(over_end);
851                Some(spec)
852            } else {
853                let base_window = self.parse_identifier()?;
854                let base_span = self.tokens[self.pos.saturating_sub(1)].span;
855                end = end.merge(base_span);
856                Some(WindowSpec {
857                    base_window: Some(base_window),
858                    partition_by: Vec::new(),
859                    order_by: Vec::new(),
860                    frame: None,
861                })
862            }
863        } else {
864            None
865        };
866
867        let span = start.merge(end);
868        Ok(Expr::FunctionCall {
869            name,
870            args,
871            distinct,
872            order_by,
873            filter,
874            over,
875            span,
876        })
877    }
878
879    fn parse_raise_args(&mut self) -> Result<(RaiseAction, Option<String>), ParseError> {
880        let action_tok = self.advance_token();
881        let action = match &action_tok.kind {
882            TokenKind::KwIgnore => RaiseAction::Ignore,
883            TokenKind::KwRollback => RaiseAction::Rollback,
884            TokenKind::KwAbort => RaiseAction::Abort,
885            TokenKind::KwFail => RaiseAction::Fail,
886            _ => {
887                return Err(ParseError::at(
888                    "expected IGNORE, ROLLBACK, ABORT, or FAIL in RAISE",
889                    Some(&action_tok),
890                ));
891            }
892        };
893        if matches!(action, RaiseAction::Ignore) {
894            return Ok((action, None));
895        }
896        self.expect_kind(&TokenKind::Comma)?;
897        let msg_tok = self.advance_token();
898        let message = match &msg_tok.kind {
899            TokenKind::String(s) => s.clone(),
900            _ => {
901                return Err(ParseError::at(
902                    "expected string message in RAISE",
903                    Some(&msg_tok),
904                ));
905            }
906        };
907        Ok((action, Some(message)))
908    }
909
910    fn parse_type_name(&mut self) -> Result<TypeName, ParseError> {
911        let mut parts = Vec::new();
912        loop {
913            match self.peek_kind() {
914                TokenKind::Id(_) | TokenKind::QuotedId(_, _) => {
915                    let tok = self.advance_token();
916                    if let TokenKind::Id(s) | TokenKind::QuotedId(s, _) = &tok.kind {
917                        parts.push(s.to_string());
918                    } else {
919                        unreachable!();
920                    }
921                }
922                k if is_nonreserved_kw(k) => {
923                    let tok = self.advance_token();
924                    parts.push(kw_to_str(&tok.kind));
925                }
926                _ => break,
927            }
928        }
929        if parts.is_empty() {
930            return Err(self.err_here("expected type name"));
931        }
932        let name = parts.join(" ");
933
934        let (arg1, arg2) = if self.eat_kind(&TokenKind::LeftParen) {
935            let a1 = self.parse_type_arg()?;
936            let a2 = if self.eat_kind(&TokenKind::Comma) {
937                Some(self.parse_type_arg()?)
938            } else {
939                None
940            };
941            self.expect_kind(&TokenKind::RightParen)?;
942            (Some(a1), a2)
943        } else {
944            (None, None)
945        };
946
947        Ok(TypeName { name, arg1, arg2 })
948    }
949
950    fn parse_type_arg(&mut self) -> Result<String, ParseError> {
951        let tok = self.advance_token();
952        match &tok.kind {
953            TokenKind::Integer(i) => Ok(i.to_string()),
954            TokenKind::Float(f) => Ok(f.to_string()),
955            TokenKind::Minus => {
956                let next = self.advance_token();
957                match &next.kind {
958                    TokenKind::Integer(i) => Ok(format!("-{i}")),
959                    TokenKind::OversizedInt(s) => Ok(format!("-{s}")),
960                    TokenKind::Float(f) => Ok(format!("-{f}")),
961                    _ => Err(ParseError::at(
962                        "expected number in type argument",
963                        Some(&next),
964                    )),
965                }
966            }
967            TokenKind::Plus => {
968                let next = self.advance_token();
969                match &next.kind {
970                    TokenKind::Integer(i) => Ok(format!("+{i}")),
971                    TokenKind::OversizedInt(s) => Ok(format!("+{s}")),
972                    TokenKind::Float(f) => Ok(format!("+{f}")),
973                    _ => Err(ParseError::at(
974                        "expected number in type argument",
975                        Some(&next),
976                    )),
977                }
978            }
979            TokenKind::OversizedInt(s) => Ok(s.clone()),
980            TokenKind::Id(s) | TokenKind::QuotedId(s, _) => Ok(s.to_string()),
981            _ => Err(ParseError::at("expected type argument", Some(&tok))),
982        }
983    }
984
985    /// Subquery parser for EXISTS/IN expression support.
986    fn parse_subquery_minimal(&mut self) -> Result<SelectStatement, ParseError> {
987        let with = if self.at_kind(&TokenKind::KwWith) {
988            Some(self.parse_with_clause()?)
989        } else {
990            None
991        };
992        self.parse_select_stmt(with)
993    }
994}
995
996/// Parse a single expression from raw SQL text.
997pub fn parse_expr(sql: &str) -> Result<Expr, ParseError> {
998    let mut parser = Parser::from_sql(sql);
999    let expr = parser.parse_expr()?;
1000    if !matches!(parser.peek_kind(), TokenKind::Eof | TokenKind::Semicolon) {
1001        return Err(parser.err_here(format!(
1002            "unexpected token after expression: {:?}",
1003            parser.peek_kind()
1004        )));
1005    }
1006    Ok(expr)
1007}
1008
1009#[cfg(test)]
1010mod tests {
1011    use super::*;
1012    use fsqlite_ast::{SelectCore, TableOrSubquery};
1013
1014    fn parse(sql: &str) -> Expr {
1015        match parse_expr(sql) {
1016            Ok(expr) => expr,
1017            Err(err) => unreachable!("parse error for `{sql}`: {err}"),
1018        }
1019    }
1020
1021    // ── Precedence tests (normative invariants) ─────────────────────────
1022
1023    #[test]
1024    fn test_not_lower_precedence_than_comparison() {
1025        // NOT x = y → NOT (x = y)
1026        let expr = parse("NOT x = y");
1027        match &expr {
1028            Expr::UnaryOp {
1029                op: UnaryOp::Not,
1030                expr: inner,
1031                ..
1032            } => match inner.as_ref() {
1033                Expr::BinaryOp {
1034                    op: BinaryOp::Eq, ..
1035                } => {}
1036                other => unreachable!("expected Eq inside NOT, got {other:?}"),
1037            },
1038            other => unreachable!("expected NOT(Eq), got {other:?}"),
1039        }
1040    }
1041
1042    #[test]
1043    fn test_unary_binds_tighter_than_collate() {
1044        // -x COLLATE NOCASE → (-x) COLLATE NOCASE
1045        let expr = parse("-x COLLATE NOCASE");
1046        match &expr {
1047            Expr::Collate {
1048                expr: inner,
1049                collation,
1050                ..
1051            } => {
1052                assert_eq!(collation, "NOCASE");
1053                assert!(matches!(
1054                    inner.as_ref(),
1055                    Expr::UnaryOp {
1056                        op: UnaryOp::Negate,
1057                        ..
1058                    }
1059                ));
1060            }
1061            other => unreachable!("expected COLLATE(Negate), got {other:?}"),
1062        }
1063    }
1064
1065    #[test]
1066    fn test_arithmetic_precedence() {
1067        // 1 + 2 * 3 → 1 + (2 * 3)
1068        let expr = parse("1 + 2 * 3");
1069        match &expr {
1070            Expr::BinaryOp {
1071                op: BinaryOp::Add,
1072                left,
1073                right,
1074                ..
1075            } => {
1076                assert!(matches!(
1077                    left.as_ref(),
1078                    Expr::Literal(Literal::Integer(1), _)
1079                ));
1080                assert!(matches!(
1081                    right.as_ref(),
1082                    Expr::BinaryOp {
1083                        op: BinaryOp::Multiply,
1084                        ..
1085                    }
1086                ));
1087            }
1088            other => unreachable!("expected Add(1, Mul(2,3)), got {other:?}"),
1089        }
1090    }
1091
1092    #[test]
1093    fn test_and_higher_than_or() {
1094        // a OR b AND c → a OR (b AND c)
1095        let expr = parse("a OR b AND c");
1096        match &expr {
1097            Expr::BinaryOp {
1098                op: BinaryOp::Or,
1099                right,
1100                ..
1101            } => {
1102                assert!(matches!(
1103                    right.as_ref(),
1104                    Expr::BinaryOp {
1105                        op: BinaryOp::And,
1106                        ..
1107                    }
1108                ));
1109            }
1110            other => unreachable!("expected Or(a, And(b,c)), got {other:?}"),
1111        }
1112    }
1113
1114    // ── CAST ────────────────────────────────────────────────────────────
1115
1116    #[test]
1117    fn test_cast_expression() {
1118        let expr = parse("CAST(42 AS INTEGER)");
1119        match &expr {
1120            Expr::Cast {
1121                expr: inner,
1122                type_name,
1123                ..
1124            } => {
1125                assert!(matches!(
1126                    inner.as_ref(),
1127                    Expr::Literal(Literal::Integer(42), _)
1128                ));
1129                assert_eq!(type_name.name, "INTEGER");
1130            }
1131            other => unreachable!("expected Cast, got {other:?}"),
1132        }
1133    }
1134
1135    #[test]
1136    fn test_cast_float_argument() {
1137        // CAST(x AS DECIMAL(10.5, -2.5))
1138        let expr = parse("CAST(x AS DECIMAL(10.5, -2.5))");
1139        match &expr {
1140            Expr::Cast { type_name, .. } => {
1141                assert_eq!(type_name.name, "DECIMAL");
1142                assert_eq!(type_name.arg1.as_deref(), Some("10.5"));
1143                assert_eq!(type_name.arg2.as_deref(), Some("-2.5"));
1144            }
1145            other => unreachable!("expected Cast with float args, got {other:?}"),
1146        }
1147    }
1148
1149    #[test]
1150    fn test_cast_signed_args() {
1151        // CAST(x AS NUMERIC(+5, -5))
1152        let expr = parse("CAST(x AS NUMERIC(+5, -5))");
1153        match &expr {
1154            Expr::Cast { type_name, .. } => {
1155                assert_eq!(type_name.name, "NUMERIC");
1156                assert_eq!(type_name.arg1.as_deref(), Some("+5"));
1157                assert_eq!(type_name.arg2.as_deref(), Some("-5"));
1158            }
1159            other => unreachable!("expected Cast with signed args, got {other:?}"),
1160        }
1161    }
1162
1163    // ── CASE ────────────────────────────────────────────────────────────
1164
1165    #[test]
1166    fn test_case_when_simple() {
1167        let expr = parse(
1168            "CASE x WHEN 1 THEN 'one' WHEN 2 THEN 'two' \
1169             ELSE 'other' END",
1170        );
1171        match &expr {
1172            Expr::Case {
1173                operand: Some(op),
1174                whens,
1175                else_expr: Some(_),
1176                ..
1177            } => {
1178                assert!(matches!(op.as_ref(), Expr::Column(..)));
1179                assert_eq!(whens.len(), 2);
1180            }
1181            other => unreachable!("expected simple CASE, got {other:?}"),
1182        }
1183    }
1184
1185    #[test]
1186    fn test_case_when_searched() {
1187        let expr = parse(
1188            "CASE WHEN x > 0 THEN 'pos' WHEN x < 0 THEN 'neg' \
1189             ELSE 'zero' END",
1190        );
1191        match &expr {
1192            Expr::Case {
1193                operand: None,
1194                whens,
1195                else_expr: Some(_),
1196                ..
1197            } => {
1198                assert_eq!(whens.len(), 2);
1199                assert!(matches!(
1200                    &whens[0].0,
1201                    Expr::BinaryOp {
1202                        op: BinaryOp::Gt,
1203                        ..
1204                    }
1205                ));
1206            }
1207            other => unreachable!("expected searched CASE, got {other:?}"),
1208        }
1209    }
1210
1211    // ── EXISTS ──────────────────────────────────────────────────────────
1212
1213    #[test]
1214    fn test_exists_subquery() {
1215        let expr = parse("EXISTS (SELECT 1)");
1216        assert!(matches!(expr, Expr::Exists { not: false, .. }));
1217    }
1218
1219    #[test]
1220    fn test_not_exists_subquery() {
1221        let expr = parse("NOT EXISTS (SELECT 1)");
1222        assert!(matches!(expr, Expr::Exists { not: true, .. }));
1223    }
1224
1225    #[test]
1226    fn test_exists_subquery_supports_qualified_table_with_alias() {
1227        let expr = parse("EXISTS (SELECT 1 FROM main.users AS u WHERE u.id = 1)");
1228        match expr {
1229            Expr::Exists { subquery, .. } => match subquery.body.select {
1230                SelectCore::Select {
1231                    from: Some(from), ..
1232                } => match from.source {
1233                    TableOrSubquery::Table { name, alias, .. } => {
1234                        assert_eq!(name.schema.as_deref(), Some("main"));
1235                        assert_eq!(name.name, "users");
1236                        assert_eq!(alias.as_deref(), Some("u"));
1237                    }
1238                    other => unreachable!("expected table source, got {other:?}"),
1239                },
1240                other => unreachable!("expected SELECT core with FROM, got {other:?}"),
1241            },
1242            other => unreachable!("expected EXISTS subquery, got {other:?}"),
1243        }
1244    }
1245
1246    // ── IN ──────────────────────────────────────────────────────────────
1247
1248    #[test]
1249    fn test_in_expr_list() {
1250        let expr = parse("x IN (1, 2, 3)");
1251        match &expr {
1252            Expr::In {
1253                not: false,
1254                set: InSet::List(items),
1255                ..
1256            } => assert_eq!(items.len(), 3),
1257            other => unreachable!("expected IN list, got {other:?}"),
1258        }
1259    }
1260
1261    #[test]
1262    fn test_in_subquery() {
1263        let expr = parse("x IN (SELECT y FROM t)");
1264        assert!(matches!(
1265            expr,
1266            Expr::In {
1267                not: false,
1268                set: InSet::Subquery(_),
1269                ..
1270            }
1271        ));
1272    }
1273
1274    #[test]
1275    fn test_in_subquery_with_order_by_and_limit() {
1276        // This is the pattern used in mcp-agent-mail-db prune queries
1277        let expr =
1278            parse("id NOT IN (SELECT id FROM search_recipes ORDER BY updated_ts DESC LIMIT 5)");
1279        match &expr {
1280            Expr::In {
1281                not: true,
1282                set: InSet::Subquery(stmt),
1283                ..
1284            } => {
1285                assert_eq!(stmt.order_by.len(), 1, "ORDER BY should be parsed");
1286                assert!(stmt.limit.is_some(), "LIMIT should be parsed");
1287            }
1288            other => unreachable!("expected NOT IN subquery, got {other:?}"),
1289        }
1290    }
1291
1292    #[test]
1293    fn test_in_subquery_supports_group_by_and_having() {
1294        let expr = parse("x IN (SELECT y FROM t GROUP BY y HAVING COUNT(*) > 1)");
1295        match expr {
1296            Expr::In {
1297                set: InSet::Subquery(stmt),
1298                ..
1299            } => match stmt.body.select {
1300                SelectCore::Select {
1301                    group_by, having, ..
1302                } => {
1303                    assert_eq!(group_by.len(), 1, "GROUP BY should be parsed");
1304                    assert!(having.is_some(), "HAVING should be parsed");
1305                }
1306                SelectCore::Values(_) => unreachable!("expected SELECT core"),
1307            },
1308            other => unreachable!("expected IN subquery, got {other:?}"),
1309        }
1310    }
1311
1312    #[test]
1313    fn test_not_in() {
1314        let expr = parse("x NOT IN (1, 2)");
1315        assert!(matches!(expr, Expr::In { not: true, .. }));
1316    }
1317
1318    #[test]
1319    fn test_in_table_name() {
1320        let expr = parse("x IN t");
1321        assert!(matches!(
1322            expr,
1323            Expr::In {
1324                not: false,
1325                set: InSet::Table(_),
1326                ..
1327            }
1328        ));
1329    }
1330
1331    #[test]
1332    fn test_not_in_table_name() {
1333        let expr = parse("x NOT IN t");
1334        assert!(matches!(
1335            expr,
1336            Expr::In {
1337                not: true,
1338                set: InSet::Table(_),
1339                ..
1340            }
1341        ));
1342    }
1343
1344    #[test]
1345    fn test_in_schema_table_name() {
1346        let expr = parse("x IN main.t");
1347        match expr {
1348            Expr::In {
1349                set: InSet::Table(name),
1350                ..
1351            } => {
1352                assert_eq!(name.schema.as_deref(), Some("main"));
1353                assert_eq!(name.name, "t");
1354            }
1355            other => unreachable!("expected IN table form, got {other:?}"),
1356        }
1357    }
1358
1359    // ── BETWEEN ─────────────────────────────────────────────────────────
1360
1361    #[test]
1362    fn test_between_and() {
1363        let expr = parse("x BETWEEN 1 AND 10");
1364        assert!(matches!(expr, Expr::Between { not: false, .. }));
1365    }
1366
1367    #[test]
1368    fn test_not_between() {
1369        let expr = parse("x NOT BETWEEN 1 AND 10");
1370        assert!(matches!(expr, Expr::Between { not: true, .. }));
1371    }
1372
1373    #[test]
1374    fn test_between_does_not_consume_outer_and() {
1375        // x BETWEEN 1 AND 10 AND y = 1 → (BETWEEN) AND (y = 1)
1376        let expr = parse("x BETWEEN 1 AND 10 AND y = 1");
1377        match &expr {
1378            Expr::BinaryOp {
1379                op: BinaryOp::And,
1380                left,
1381                ..
1382            } => assert!(matches!(left.as_ref(), Expr::Between { .. })),
1383            other => unreachable!("expected AND(BETWEEN, Eq), got {other:?}"),
1384        }
1385    }
1386
1387    // ── LIKE / GLOB ─────────────────────────────────────────────────────
1388
1389    #[test]
1390    fn test_like_pattern() {
1391        let expr = parse("name LIKE '%foo%'");
1392        assert!(matches!(
1393            expr,
1394            Expr::Like {
1395                op: LikeOp::Like,
1396                not: false,
1397                escape: None,
1398                ..
1399            }
1400        ));
1401    }
1402
1403    #[test]
1404    fn test_like_escape() {
1405        let expr = parse("name LIKE '%\\%%' ESCAPE '\\'");
1406        assert!(matches!(
1407            expr,
1408            Expr::Like {
1409                op: LikeOp::Like,
1410                escape: Some(_),
1411                ..
1412            }
1413        ));
1414    }
1415
1416    #[test]
1417    fn test_glob_pattern() {
1418        let expr = parse("path GLOB '*.rs'");
1419        assert!(matches!(
1420            expr,
1421            Expr::Like {
1422                op: LikeOp::Glob,
1423                not: false,
1424                ..
1425            }
1426        ));
1427    }
1428
1429    #[test]
1430    fn test_glob_character_class() {
1431        let expr = parse("name GLOB '[a-z]*'");
1432        match &expr {
1433            Expr::Like {
1434                op: LikeOp::Glob,
1435                pattern,
1436                ..
1437            } => assert!(matches!(
1438                pattern.as_ref(),
1439                Expr::Literal(Literal::String(s), _) if s == "[a-z]*"
1440            )),
1441            other => unreachable!("expected GLOB, got {other:?}"),
1442        }
1443    }
1444
1445    // ── COLLATE ─────────────────────────────────────────────────────────
1446
1447    #[test]
1448    fn test_collate_override() {
1449        let expr = parse("name COLLATE NOCASE");
1450        match &expr {
1451            Expr::Collate { collation, .. } => {
1452                assert_eq!(collation, "NOCASE");
1453            }
1454            other => unreachable!("expected COLLATE, got {other:?}"),
1455        }
1456    }
1457
1458    // ── JSON operators ──────────────────────────────────────────────────
1459
1460    #[test]
1461    fn test_json_arrow_operator() {
1462        let expr = parse("data -> 'key'");
1463        assert!(matches!(
1464            expr,
1465            Expr::JsonAccess {
1466                arrow: JsonArrow::Arrow,
1467                ..
1468            }
1469        ));
1470    }
1471
1472    #[test]
1473    fn test_json_double_arrow_operator() {
1474        let expr = parse("data ->> 'key'");
1475        assert!(matches!(
1476            expr,
1477            Expr::JsonAccess {
1478                arrow: JsonArrow::DoubleArrow,
1479                ..
1480            }
1481        ));
1482    }
1483
1484    // ── IS NULL / IS NOT ─────────────────────────────────────────────────────
1485
1486    #[test]
1487    fn test_is_null() {
1488        assert!(matches!(
1489            parse("42"),
1490            Expr::Literal(Literal::Integer(42), _)
1491        ));
1492        assert!(matches!(parse("3.14"), Expr::Literal(Literal::Float(_), _)));
1493        assert!(matches!(
1494            parse("'hello'"),
1495            Expr::Literal(Literal::String(_), _)
1496        ));
1497        assert!(matches!(parse("NULL"), Expr::Literal(Literal::Null, _)));
1498        assert!(matches!(parse("TRUE"), Expr::Literal(Literal::True, _)));
1499        assert!(matches!(parse("FALSE"), Expr::Literal(Literal::False, _)));
1500    }
1501
1502    #[test]
1503    fn test_placeholders() {
1504        assert!(matches!(
1505            parse("?"),
1506            Expr::Placeholder(PlaceholderType::Anonymous, _)
1507        ));
1508        assert!(matches!(
1509            parse("?1"),
1510            Expr::Placeholder(PlaceholderType::Numbered(1), _)
1511        ));
1512        assert!(matches!(
1513            parse(":name"),
1514            Expr::Placeholder(PlaceholderType::ColonNamed(_), _)
1515        ));
1516    }
1517
1518    // ── Column references ───────────────────────────────────────────────
1519
1520    #[test]
1521    fn test_column_bare() {
1522        match &parse("x") {
1523            Expr::Column(
1524                ColumnRef {
1525                    table: None,
1526                    column,
1527                },
1528                _,
1529            ) => assert_eq!(column.as_ref(), "x"),
1530            other => unreachable!("expected bare column, got {other:?}"),
1531        }
1532    }
1533
1534    #[test]
1535    fn test_column_qualified() {
1536        match &parse("t.x") {
1537            Expr::Column(
1538                ColumnRef {
1539                    table: Some(t),
1540                    column,
1541                },
1542                _,
1543            ) => {
1544                assert_eq!(t.as_ref(), "t");
1545                assert_eq!(column.as_ref(), "x");
1546            }
1547            other => unreachable!("expected qualified column, got {other:?}"),
1548        }
1549    }
1550
1551    // ── Concat / precedence ─────────────────────────────────────────────
1552
1553    #[test]
1554    fn test_concat_higher_than_add() {
1555        // a + b || c → a + (b || c) since || binds tighter
1556        let expr = parse("a + b || c");
1557        match &expr {
1558            Expr::BinaryOp {
1559                op: BinaryOp::Add,
1560                right,
1561                ..
1562            } => assert!(matches!(
1563                right.as_ref(),
1564                Expr::BinaryOp {
1565                    op: BinaryOp::Concat,
1566                    ..
1567                }
1568            )),
1569            other => unreachable!("expected Add(a, Concat(b,c)), got {other:?}"),
1570        }
1571    }
1572
1573    // ── Parenthesized ───────────────────────────────────────────────────
1574
1575    #[test]
1576    fn test_parenthesized() {
1577        // (1 + 2) * 3 → Mul(Add(1,2), 3)
1578        let expr = parse("(1 + 2) * 3");
1579        match &expr {
1580            Expr::BinaryOp {
1581                op: BinaryOp::Multiply,
1582                left,
1583                ..
1584            } => assert!(matches!(
1585                left.as_ref(),
1586                Expr::BinaryOp {
1587                    op: BinaryOp::Add,
1588                    ..
1589                }
1590            )),
1591            other => unreachable!("expected Mul(Add, 3), got {other:?}"),
1592        }
1593    }
1594
1595    // ── IS / IS NOT ─────────────────────────────────────────────────────
1596
1597    #[test]
1598    fn test_is_operator() {
1599        assert!(matches!(
1600            parse("a IS b"),
1601            Expr::BinaryOp {
1602                op: BinaryOp::Is,
1603                ..
1604            }
1605        ));
1606    }
1607
1608    #[test]
1609    fn test_is_not_operator() {
1610        assert!(matches!(
1611            parse("a IS NOT b"),
1612            Expr::BinaryOp {
1613                op: BinaryOp::IsNot,
1614                ..
1615            }
1616        ));
1617    }
1618
1619    // ── Bitwise ─────────────────────────────────────────────────────────
1620
1621    #[test]
1622    fn test_bitwise_ops() {
1623        // & and | share the same precedence (left-associative)
1624        let expr = parse("a & b | c");
1625        match &expr {
1626            Expr::BinaryOp {
1627                op: BinaryOp::BitOr,
1628                left,
1629                ..
1630            } => assert!(
1631                matches!(
1632                    left.as_ref(),
1633                    Expr::BinaryOp {
1634                        op: BinaryOp::BitAnd,
1635                        ..
1636                    }
1637                ),
1638                "bitwise operators should be left-associative"
1639            ),
1640            other => unreachable!("expected BitOr(BitAnd, c), got {other:?}"),
1641        }
1642    }
1643
1644    #[test]
1645    fn test_bitnot() {
1646        assert!(matches!(
1647            parse("~x"),
1648            Expr::UnaryOp {
1649                op: UnaryOp::BitNot,
1650                ..
1651            }
1652        ));
1653    }
1654
1655    // ── Complex expressions ─────────────────────────────────────────────
1656
1657    #[test]
1658    fn test_complex_where_clause() {
1659        let expr = parse("a > 1 AND b LIKE '%test%' OR NOT c IS NULL");
1660        assert!(matches!(
1661            expr,
1662            Expr::BinaryOp {
1663                op: BinaryOp::Or,
1664                ..
1665            }
1666        ));
1667    }
1668
1669    #[test]
1670    fn test_not_like_pattern() {
1671        assert!(matches!(
1672            parse("name NOT LIKE '%foo'"),
1673            Expr::Like {
1674                op: LikeOp::Like,
1675                not: true,
1676                ..
1677            }
1678        ));
1679    }
1680
1681    #[test]
1682    fn test_subquery_expr() {
1683        assert!(matches!(parse("(SELECT 1)"), Expr::Subquery(..)));
1684    }
1685
1686    // ── bd-kzat: §10.2 Pratt Precedence Validation ─────────────────────
1687    //
1688    // Systematic tests for ALL 11 operator precedence levels.
1689    // Each level gets a dedicated associativity test and a boundary test
1690    // against the adjacent level.
1691
1692    // Level 1: OR — left-associative
1693    #[test]
1694    fn test_pratt_level1_or_left_assoc() {
1695        // a OR b OR c → (a OR b) OR c
1696        let expr = parse("a OR b OR c");
1697        match &expr {
1698            Expr::BinaryOp {
1699                op: BinaryOp::Or,
1700                left,
1701                ..
1702            } => assert!(
1703                matches!(
1704                    left.as_ref(),
1705                    Expr::BinaryOp {
1706                        op: BinaryOp::Or,
1707                        ..
1708                    }
1709                ),
1710                "OR should be left-associative"
1711            ),
1712            other => unreachable!("expected Or(Or(a,b), c), got {other:?}"),
1713        }
1714    }
1715
1716    // Level 2: AND — left-associative, tighter than OR
1717    #[test]
1718    fn test_pratt_level2_and_left_assoc() {
1719        // a AND b AND c → (a AND b) AND c
1720        let expr = parse("a AND b AND c");
1721        match &expr {
1722            Expr::BinaryOp {
1723                op: BinaryOp::And,
1724                left,
1725                ..
1726            } => assert!(
1727                matches!(
1728                    left.as_ref(),
1729                    Expr::BinaryOp {
1730                        op: BinaryOp::And,
1731                        ..
1732                    }
1733                ),
1734                "AND should be left-associative"
1735            ),
1736            other => unreachable!("expected And(And(a,b), c), got {other:?}"),
1737        }
1738    }
1739
1740    // Level 3: NOT — prefix, higher than AND, lower than equality
1741    #[test]
1742    fn test_pratt_level3_not_higher_than_and() {
1743        // NOT a AND b → (NOT a) AND b
1744        let expr = parse("NOT a AND b");
1745        match &expr {
1746            Expr::BinaryOp {
1747                op: BinaryOp::And,
1748                left,
1749                ..
1750            } => assert!(
1751                matches!(
1752                    left.as_ref(),
1753                    Expr::UnaryOp {
1754                        op: UnaryOp::Not,
1755                        ..
1756                    }
1757                ),
1758                "NOT should bind tighter than AND"
1759            ),
1760            other => unreachable!("expected And(Not(a), b), got {other:?}"),
1761        }
1762    }
1763
1764    // Level 4: Equality/membership — left-associative
1765    #[test]
1766    fn test_pratt_level4_equality_left_assoc() {
1767        // a = b != c → (a = b) != c
1768        let expr = parse("a = b != c");
1769        match &expr {
1770            Expr::BinaryOp {
1771                op: BinaryOp::Ne,
1772                left,
1773                ..
1774            } => assert!(
1775                matches!(
1776                    left.as_ref(),
1777                    Expr::BinaryOp {
1778                        op: BinaryOp::Eq,
1779                        ..
1780                    }
1781                ),
1782                "equality operators should be left-associative at same level"
1783            ),
1784            other => unreachable!("expected Ne(Eq(a,b), c), got {other:?}"),
1785        }
1786    }
1787
1788    // Level 4 vs Level 5: THE CRITICAL BOUNDARY
1789    // Equality (level 4) and relational (level 5) are SEPARATE levels
1790    // per canonical upstream SQLite grammar.
1791    #[test]
1792    fn test_pratt_level4_vs_level5_eq_lt_boundary() {
1793        // a = b < c MUST parse as a = (b < c), NOT (a = b) < c
1794        // This is the normative invariant from §10.2.
1795        let expr = parse("a = b < c");
1796        match &expr {
1797            Expr::BinaryOp {
1798                op: BinaryOp::Eq,
1799                right,
1800                ..
1801            } => assert!(
1802                matches!(
1803                    right.as_ref(),
1804                    Expr::BinaryOp {
1805                        op: BinaryOp::Lt,
1806                        ..
1807                    }
1808                ),
1809                "a = b < c MUST parse as a = (b < c): relational binds tighter"
1810            ),
1811            other => unreachable!("expected Eq(a, Lt(b,c)), got {other:?}"),
1812        }
1813    }
1814
1815    // Reverse direction of the same boundary
1816    #[test]
1817    fn test_pratt_level4_vs_level5_ne_ge_boundary() {
1818        // a != b >= c → a != (b >= c)
1819        let expr = parse("a != b >= c");
1820        match &expr {
1821            Expr::BinaryOp {
1822                op: BinaryOp::Ne,
1823                right,
1824                ..
1825            } => assert!(
1826                matches!(
1827                    right.as_ref(),
1828                    Expr::BinaryOp {
1829                        op: BinaryOp::Ge,
1830                        ..
1831                    }
1832                ),
1833                "a != b >= c must parse as a != (b >= c)"
1834            ),
1835            other => unreachable!("expected Ne(Ge(b,c)), got {other:?}"),
1836        }
1837    }
1838
1839    // Level 5: Relational — left-associative
1840    #[test]
1841    fn test_pratt_level5_relational_left_assoc() {
1842        // a < b >= c → (a < b) >= c
1843        let expr = parse("a < b >= c");
1844        match &expr {
1845            Expr::BinaryOp {
1846                op: BinaryOp::Ge,
1847                left,
1848                ..
1849            } => assert!(
1850                matches!(
1851                    left.as_ref(),
1852                    Expr::BinaryOp {
1853                        op: BinaryOp::Lt,
1854                        ..
1855                    }
1856                ),
1857                "relational operators should be left-associative"
1858            ),
1859            other => unreachable!("expected Ge(Lt(a,b), c), got {other:?}"),
1860        }
1861    }
1862
1863    // Level 6: Bitwise — tighter than relational
1864    #[test]
1865    fn test_pratt_level6_bitwise_tighter_than_comparison() {
1866        // a < b & c → a < (b & c)
1867        let expr = parse("a < b & c");
1868        match &expr {
1869            Expr::BinaryOp {
1870                op: BinaryOp::Lt,
1871                right,
1872                ..
1873            } => assert!(
1874                matches!(
1875                    right.as_ref(),
1876                    Expr::BinaryOp {
1877                        op: BinaryOp::BitAnd,
1878                        ..
1879                    }
1880                ),
1881                "bitwise should bind tighter than relational"
1882            ),
1883            other => unreachable!("expected Lt(a, BitAnd(b,c)), got {other:?}"),
1884        }
1885    }
1886
1887    // Level 6: Shift operators left-associative
1888    #[test]
1889    fn test_pratt_level6_shifts_left_assoc() {
1890        // a << b >> c → (a << b) >> c
1891        let expr = parse("a << b >> c");
1892        match &expr {
1893            Expr::BinaryOp {
1894                op: BinaryOp::ShiftRight,
1895                left,
1896                ..
1897            } => assert!(
1898                matches!(
1899                    left.as_ref(),
1900                    Expr::BinaryOp {
1901                        op: BinaryOp::ShiftLeft,
1902                        ..
1903                    }
1904                ),
1905                "shift operators should be left-associative"
1906            ),
1907            other => unreachable!("expected ShiftRight(ShiftLeft(a,b), c), got {other:?}"),
1908        }
1909    }
1910
1911    // Level 7: Addition/subtraction — left-associative, tighter than bitwise
1912    #[test]
1913    fn test_pratt_level7_add_sub_left_assoc() {
1914        // a + b - c → (a + b) - c
1915        let expr = parse("a + b - c");
1916        match &expr {
1917            Expr::BinaryOp {
1918                op: BinaryOp::Subtract,
1919                left,
1920                ..
1921            } => assert!(
1922                matches!(
1923                    left.as_ref(),
1924                    Expr::BinaryOp {
1925                        op: BinaryOp::Add,
1926                        ..
1927                    }
1928                ),
1929                "add/sub should be left-associative"
1930            ),
1931            other => unreachable!("expected Sub(Add(a,b), c), got {other:?}"),
1932        }
1933    }
1934
1935    #[test]
1936    fn test_pratt_level7_add_sub_left_assoc_reverse() {
1937        // a - b + c → (a - b) + c
1938        let expr = parse("a - b + c");
1939        match &expr {
1940            Expr::BinaryOp {
1941                op: BinaryOp::Add,
1942                left,
1943                ..
1944            } => assert!(
1945                matches!(
1946                    left.as_ref(),
1947                    Expr::BinaryOp {
1948                        op: BinaryOp::Subtract,
1949                        ..
1950                    }
1951                ),
1952                "add/sub should be left-associative"
1953            ),
1954            other => unreachable!("expected Add(Sub(a,b), c), got {other:?}"),
1955        }
1956    }
1957
1958    #[test]
1959    fn test_pratt_level9_concat_tighter_than_mul() {
1960        // a * b || c → a * (b || c)
1961        let expr = parse("a * b || c");
1962        match &expr {
1963            Expr::BinaryOp {
1964                op: BinaryOp::Multiply,
1965                right,
1966                ..
1967            } => assert!(
1968                matches!(
1969                    right.as_ref(),
1970                    Expr::BinaryOp {
1971                        op: BinaryOp::Concat,
1972                        ..
1973                    }
1974                ),
1975                "concat should bind tighter than multiply"
1976            ),
1977            other => unreachable!("expected Mul(a, Concat(b,c)), got {other:?}"),
1978        }
1979    }
1980
1981    // Level 8: Multiplication/division/modulo — left-associative
1982    #[test]
1983    fn test_pratt_level8_mul_div_left_assoc() {
1984        // a * b / c → (a * b) / c
1985        let expr = parse("a * b / c");
1986        match &expr {
1987            Expr::BinaryOp {
1988                op: BinaryOp::Divide,
1989                left,
1990                ..
1991            } => assert!(
1992                matches!(
1993                    left.as_ref(),
1994                    Expr::BinaryOp {
1995                        op: BinaryOp::Multiply,
1996                        ..
1997                    }
1998                ),
1999                "mul/div should be left-associative"
2000            ),
2001            other => unreachable!("expected Div(Mul(a,b), c), got {other:?}"),
2002        }
2003    }
2004
2005    #[test]
2006    fn test_pratt_level8_modulo() {
2007        // a * b % c → (a * b) % c
2008        let expr = parse("a * b % c");
2009        match &expr {
2010            Expr::BinaryOp {
2011                op: BinaryOp::Modulo,
2012                left,
2013                ..
2014            } => assert!(
2015                matches!(
2016                    left.as_ref(),
2017                    Expr::BinaryOp {
2018                        op: BinaryOp::Multiply,
2019                        ..
2020                    }
2021                ),
2022                "modulo and multiply at same level, left-associative"
2023            ),
2024            other => unreachable!("expected Mod(Mul(a,b), c), got {other:?}"),
2025        }
2026    }
2027
2028    // Level 9: Concatenation (||) — left-associative, tighter than mul
2029    #[test]
2030    fn test_pratt_level9_concat_left_assoc() {
2031        // a || b || c → (a || b) || c
2032        let expr = parse("a || b || c");
2033        match &expr {
2034            Expr::BinaryOp {
2035                op: BinaryOp::Concat,
2036                left,
2037                ..
2038            } => assert!(
2039                matches!(
2040                    left.as_ref(),
2041                    Expr::BinaryOp {
2042                        op: BinaryOp::Concat,
2043                        ..
2044                    }
2045                ),
2046                "concatenation should be left-associative"
2047            ),
2048            other => unreachable!("expected Concat(Concat(a,b), c), got {other:?}"),
2049        }
2050    }
2051
2052    #[test]
2053    fn test_pratt_level9_concat_left_assoc_reverse() {
2054        // a || b || c → (a || b) || c
2055        let expr = parse("a || b || c");
2056        match &expr {
2057            Expr::BinaryOp {
2058                op: BinaryOp::Concat,
2059                left,
2060                ..
2061            } => assert!(
2062                matches!(
2063                    left.as_ref(),
2064                    Expr::BinaryOp {
2065                        op: BinaryOp::Concat,
2066                        ..
2067                    }
2068                ),
2069                "concatenation should be left-associative"
2070            ),
2071            other => unreachable!("expected Concat(Concat(a,b), c), got {other:?}"),
2072        }
2073    }
2074
2075    // Level 10: COLLATE — postfix, tighter than concat
2076    #[test]
2077    fn test_pratt_level10_collate_tighter_than_concat() {
2078        // a || b COLLATE NOCASE → a || (b COLLATE NOCASE)
2079        let expr = parse("a || b COLLATE NOCASE");
2080        match &expr {
2081            Expr::BinaryOp {
2082                op: BinaryOp::Concat,
2083                right,
2084                ..
2085            } => assert!(
2086                matches!(right.as_ref(), Expr::Collate { .. }),
2087                "COLLATE should bind tighter than concat"
2088            ),
2089            other => unreachable!("expected Concat(a, Collate(b)), got {other:?}"),
2090        }
2091    }
2092
2093    // Level 11: Unary prefix (- + ~) — tightest of all
2094    #[test]
2095    fn test_pratt_level11_unary_negate_tightest() {
2096        // -a * b → (-a) * b
2097        let expr = parse("-a * b");
2098        match &expr {
2099            Expr::BinaryOp {
2100                op: BinaryOp::Multiply,
2101                left,
2102                ..
2103            } => assert!(
2104                matches!(
2105                    left.as_ref(),
2106                    Expr::UnaryOp {
2107                        op: UnaryOp::Negate,
2108                        ..
2109                    }
2110                ),
2111                "unary minus should bind tighter than multiply"
2112            ),
2113            other => unreachable!("expected Mul(Negate(a), b), got {other:?}"),
2114        }
2115    }
2116
2117    #[test]
2118    fn test_pratt_level11_bitnot_tightest() {
2119        // ~a + b → (~a) + b
2120        let expr = parse("~a + b");
2121        match &expr {
2122            Expr::BinaryOp {
2123                op: BinaryOp::Add,
2124                left,
2125                ..
2126            } => assert!(
2127                matches!(
2128                    left.as_ref(),
2129                    Expr::UnaryOp {
2130                        op: UnaryOp::BitNot,
2131                        ..
2132                    }
2133                ),
2134                "bitwise NOT should bind tighter than addition"
2135            ),
2136            other => unreachable!("expected Add(BitNot(a), b), got {other:?}"),
2137        }
2138    }
2139
2140    // ESCAPE is NOT a standalone infix operator — it's suffix of LIKE/GLOB
2141    #[test]
2142    fn test_pratt_escape_not_infix_operator() {
2143        // a LIKE b ESCAPE c → Like(a, b, escape=c)
2144        let expr = parse("a LIKE b ESCAPE c");
2145        match &expr {
2146            Expr::Like {
2147                escape: Some(esc), ..
2148            } => assert!(
2149                matches!(esc.as_ref(), Expr::Column(_, _)),
2150                "ESCAPE should be parsed as suffix of LIKE, not standalone infix"
2151            ),
2152            other => unreachable!("expected Like with escape, got {other:?}"),
2153        }
2154    }
2155
2156    #[test]
2157    fn test_pratt_escape_glob_not_infix() {
2158        // a GLOB b ESCAPE c → Like(a, b, op=Glob, escape=c)
2159        let expr = parse("a GLOB b ESCAPE c");
2160        match &expr {
2161            Expr::Like {
2162                op: LikeOp::Glob,
2163                escape: Some(_),
2164                ..
2165            } => {}
2166            other => unreachable!("expected Glob with escape, got {other:?}"),
2167        }
2168    }
2169
2170    // Error recovery: multiple errors collected in one pass
2171    #[test]
2172    fn test_pratt_error_recovery_multiple_errors() {
2173        use crate::parser::Parser;
2174        let mut p = Parser::from_sql("SELECT +; SELECT *; SELECT 1");
2175        let (stmts, errs) = p.parse_all();
2176        // SELECT + fails (missing operand), SELECT * fails (no FROM for bare *),
2177        // SELECT 1 should succeed.
2178        assert!(
2179            !stmts.is_empty(),
2180            "should recover and parse at least one valid statement"
2181        );
2182        assert!(
2183            !errs.is_empty(),
2184            "should collect at least one error from malformed statements"
2185        );
2186    }
2187
2188    // Complex mixed expression: full 11-level test
2189    #[test]
2190    fn test_pratt_complex_mixed_all_levels() {
2191        // NOT a = b + c * -d OR e < f AND g LIKE h
2192        // → (NOT (a = (b + (c * (-d))))) OR ((e < f) AND (g LIKE h))
2193        let expr = parse("NOT a = b + c * -d OR e < f AND g LIKE h");
2194        // Top level: OR
2195        match &expr {
2196            Expr::BinaryOp {
2197                op: BinaryOp::Or,
2198                left,
2199                right,
2200                ..
2201            } => {
2202                // left = NOT (a = (b + (c * (-d))))
2203                assert!(
2204                    matches!(
2205                        left.as_ref(),
2206                        Expr::UnaryOp {
2207                            op: UnaryOp::Not,
2208                            ..
2209                        }
2210                    ),
2211                    "left of OR should be NOT(...)"
2212                );
2213                // right = (e < f) AND (g LIKE h)
2214                match right.as_ref() {
2215                    Expr::BinaryOp {
2216                        op: BinaryOp::And,
2217                        left: and_left,
2218                        right: and_right,
2219                        ..
2220                    } => {
2221                        assert!(
2222                            matches!(
2223                                and_left.as_ref(),
2224                                Expr::BinaryOp {
2225                                    op: BinaryOp::Lt,
2226                                    ..
2227                                }
2228                            ),
2229                            "left of AND should be Lt(e,f)"
2230                        );
2231                        assert!(
2232                            matches!(and_right.as_ref(), Expr::Like { .. }),
2233                            "right of AND should be Like(g,h)"
2234                        );
2235                    }
2236                    other => unreachable!("expected And(Lt, Like), got {other:?}"),
2237                }
2238
2239                // Drill into the NOT to verify deeper structure:
2240                // NOT → Eq → right = Add → right = Mul → right = Negate
2241                if let Expr::UnaryOp {
2242                    expr: not_inner, ..
2243                } = left.as_ref()
2244                {
2245                    if let Expr::BinaryOp {
2246                        op: BinaryOp::Eq,
2247                        right: eq_right,
2248                        ..
2249                    } = not_inner.as_ref()
2250                    {
2251                        if let Expr::BinaryOp {
2252                            op: BinaryOp::Add,
2253                            right: add_right,
2254                            ..
2255                        } = eq_right.as_ref()
2256                        {
2257                            if let Expr::BinaryOp {
2258                                op: BinaryOp::Multiply,
2259                                right: mul_right,
2260                                ..
2261                            } = add_right.as_ref()
2262                            {
2263                                assert!(
2264                                    matches!(
2265                                        mul_right.as_ref(),
2266                                        Expr::UnaryOp {
2267                                            op: UnaryOp::Negate,
2268                                            ..
2269                                        }
2270                                    ),
2271                                    "deepest: negate"
2272                                );
2273                            } else {
2274                                unreachable!("expected Mul in add_right");
2275                            }
2276                        } else {
2277                            unreachable!("expected Add in eq_right");
2278                        }
2279                    } else {
2280                        unreachable!("expected Eq inside NOT");
2281                    }
2282                }
2283            }
2284            other => unreachable!("expected Or(Not(...), And(...)), got {other:?}"),
2285        }
2286    }
2287
2288    // JSON operators share precedence with concat and associate left-to-right.
2289    #[test]
2290    fn test_pratt_json_same_precedence_as_concat() {
2291        // a || b -> c parses as (a || b) -> c.
2292        let expr = parse("a || b -> c");
2293        match &expr {
2294            Expr::JsonAccess {
2295                expr: left,
2296                path: right,
2297                arrow: JsonArrow::Arrow,
2298                ..
2299            } => {
2300                assert!(
2301                    matches!(
2302                        left.as_ref(),
2303                        Expr::BinaryOp {
2304                            op: BinaryOp::Concat,
2305                            ..
2306                        }
2307                    ),
2308                    "left side should be concat expression"
2309                );
2310                assert!(
2311                    matches!(right.as_ref(), Expr::Column(_, _)),
2312                    "path should remain the right-hand expression"
2313                );
2314            }
2315            other => unreachable!("expected JsonAccess(Concat(a,b), c), got {other:?}"),
2316        }
2317    }
2318
2319    #[test]
2320    fn test_pratt_double_arrow_same_precedence_as_concat() {
2321        let expr = parse("a || b ->> c");
2322        assert!(
2323            matches!(
2324                expr,
2325                Expr::JsonAccess {
2326                    arrow: JsonArrow::DoubleArrow,
2327                    ..
2328                }
2329            ),
2330            "double-arrow should parse as JsonAccess at the same precedence level as concat"
2331        );
2332    }
2333}