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