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