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