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