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