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