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