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