vibesql_parser/parser/expressions/
operators.rs

1use super::*;
2
3impl Parser {
4    /// Parse OR expression (lowest precedence)
5    pub(super) fn parse_or_expression(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
6        let mut left = self.parse_and_expression()?;
7
8        while self.peek_keyword(Keyword::Or) {
9            self.consume_keyword(Keyword::Or)?;
10            let right = self.parse_and_expression()?;
11            left = vibesql_ast::Expression::BinaryOp {
12                op: vibesql_ast::BinaryOperator::Or,
13                left: Box::new(left),
14                right: Box::new(right),
15            };
16        }
17
18        Ok(left)
19    }
20
21    /// Parse AND expression
22    pub(super) fn parse_and_expression(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
23        let mut left = self.parse_not_expression()?;
24
25        while self.peek_keyword(Keyword::And) {
26            self.consume_keyword(Keyword::And)?;
27            let right = self.parse_not_expression()?;
28            left = vibesql_ast::Expression::BinaryOp {
29                op: vibesql_ast::BinaryOperator::And,
30                left: Box::new(left),
31                right: Box::new(right),
32            };
33        }
34
35        Ok(left)
36    }
37
38    /// Parse NOT expression
39    /// NOT has precedence between AND and comparison operators
40    /// This ensures "NOT col IS NULL" parses as "NOT (col IS NULL)" not "(NOT col) IS NULL"
41    pub(super) fn parse_not_expression(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
42        // Check for NOT keyword (but not NOT IN, NOT BETWEEN, NOT LIKE, NOT EXISTS)
43        // Those are handled in parse_comparison_expression and parse_primary_expression
44        if self.peek_keyword(Keyword::Not) {
45            // Peek ahead to see if it's a special case
46            let saved_pos = self.position;
47            self.advance(); // consume NOT
48
49            // Check for special cases that are NOT unary NOT
50            if self.peek_keyword(Keyword::In)
51                || self.peek_keyword(Keyword::Between)
52                || self.peek_keyword(Keyword::Like)
53                || self.peek_keyword(Keyword::Glob)
54                || self.peek_keyword(Keyword::Exists)
55            {
56                // Restore position and let the other parsers handle it
57                self.position = saved_pos;
58                return self.parse_bitwise_or_expression();
59            }
60
61            // It's a unary NOT - parse the expression it applies to
62            // Recursively call parse_not_expression to handle multiple NOTs
63            let expr = self.parse_not_expression()?;
64
65            Ok(vibesql_ast::Expression::UnaryOp {
66                op: vibesql_ast::UnaryOperator::Not,
67                expr: Box::new(expr),
68            })
69        } else {
70            self.parse_bitwise_or_expression()
71        }
72    }
73
74    /// Parse bitwise OR expression (handles |)
75    /// Precedence: between NOT and bitwise AND
76    pub(super) fn parse_bitwise_or_expression(
77        &mut self,
78    ) -> Result<vibesql_ast::Expression, ParseError> {
79        let mut left = self.parse_bitwise_and_expression()?;
80
81        while self.peek() == &Token::Symbol('|') {
82            self.advance();
83            let right = self.parse_bitwise_and_expression()?;
84            left = vibesql_ast::Expression::BinaryOp {
85                op: vibesql_ast::BinaryOperator::BitwiseOr,
86                left: Box::new(left),
87                right: Box::new(right),
88            };
89        }
90
91        Ok(left)
92    }
93
94    /// Parse bitwise AND expression (handles &)
95    /// Precedence: between bitwise OR and comparison
96    pub(super) fn parse_bitwise_and_expression(
97        &mut self,
98    ) -> Result<vibesql_ast::Expression, ParseError> {
99        let mut left = self.parse_comparison_expression()?;
100
101        while self.peek() == &Token::Symbol('&') {
102            self.advance();
103            let right = self.parse_comparison_expression()?;
104            left = vibesql_ast::Expression::BinaryOp {
105                op: vibesql_ast::BinaryOperator::BitwiseAnd,
106                left: Box::new(left),
107                right: Box::new(right),
108            };
109        }
110
111        Ok(left)
112    }
113
114    /// Parse shift expression (handles <<, >>)
115    /// Precedence: between comparison and additive
116    pub(super) fn parse_shift_expression(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
117        let mut left = self.parse_additive_expression()?;
118
119        loop {
120            let op = match self.peek() {
121                Token::Operator(crate::token::MultiCharOperator::LeftShift) => {
122                    vibesql_ast::BinaryOperator::LeftShift
123                }
124                Token::Operator(crate::token::MultiCharOperator::RightShift) => {
125                    vibesql_ast::BinaryOperator::RightShift
126                }
127                _ => break,
128            };
129            self.advance();
130
131            let right = self.parse_additive_expression()?;
132            left = vibesql_ast::Expression::BinaryOp {
133                op,
134                left: Box::new(left),
135                right: Box::new(right),
136            };
137        }
138
139        Ok(left)
140    }
141
142    /// Parse additive expression (handles +, -)
143    /// Per SQLite, || has higher precedence than + and -, so it's handled in parse_concat_expression
144    pub(super) fn parse_additive_expression(
145        &mut self,
146    ) -> Result<vibesql_ast::Expression, ParseError> {
147        let mut left = self.parse_concat_expression()?;
148
149        loop {
150            let op = match self.peek() {
151                Token::Symbol('+') => vibesql_ast::BinaryOperator::Plus,
152                Token::Symbol('-') => vibesql_ast::BinaryOperator::Minus,
153                _ => break,
154            };
155            self.advance();
156
157            let right = self.parse_concat_expression()?;
158            left = vibesql_ast::Expression::BinaryOp {
159                op,
160                left: Box::new(left),
161                right: Box::new(right),
162            };
163        }
164
165        Ok(left)
166    }
167
168    /// Parse string concatenation expression (handles ||)
169    /// Per SQLite, || has higher precedence than + and -, but lower than * / %
170    pub(super) fn parse_concat_expression(
171        &mut self,
172    ) -> Result<vibesql_ast::Expression, ParseError> {
173        let mut left = self.parse_multiplicative_expression()?;
174
175        while self.peek() == &Token::Operator(crate::token::MultiCharOperator::Concat) {
176            self.advance();
177            let right = self.parse_multiplicative_expression()?;
178            left = vibesql_ast::Expression::BinaryOp {
179                op: vibesql_ast::BinaryOperator::Concat,
180                left: Box::new(left),
181                right: Box::new(right),
182            };
183        }
184
185        Ok(left)
186    }
187
188    /// Parse multiplicative expression (handles *, /, DIV, %)
189    pub(super) fn parse_multiplicative_expression(
190        &mut self,
191    ) -> Result<vibesql_ast::Expression, ParseError> {
192        let mut left = self.parse_unary_expression()?;
193
194        loop {
195            let op = match self.peek() {
196                Token::Symbol('*') => vibesql_ast::BinaryOperator::Multiply,
197                Token::Symbol('/') => vibesql_ast::BinaryOperator::Divide,
198                Token::Symbol('%') => vibesql_ast::BinaryOperator::Modulo,
199                Token::Keyword { keyword: Keyword::Div, .. } => {
200                    vibesql_ast::BinaryOperator::IntegerDivide
201                }
202                _ => break,
203            };
204            self.advance();
205
206            let right = self.parse_unary_expression()?;
207            left = vibesql_ast::Expression::BinaryOp {
208                op,
209                left: Box::new(left),
210                right: Box::new(right),
211            };
212        }
213
214        Ok(left)
215    }
216
217    /// Parse comparison expression (handles =, <, >, <=, >=, !=, <>, IN, BETWEEN, LIKE, IS NULL)
218    /// These operators have lower precedence than arithmetic operators
219    pub(super) fn parse_comparison_expression(
220        &mut self,
221    ) -> Result<vibesql_ast::Expression, ParseError> {
222        let mut left = self.parse_shift_expression()?;
223
224        // Check for IN operator (including NOT IN) and BETWEEN (including NOT BETWEEN)
225        if self.peek_keyword(Keyword::Not) {
226            // Peek ahead to see if it's "NOT IN" or "NOT BETWEEN"
227            let saved_pos = self.position;
228            self.advance(); // consume NOT
229
230            if self.peek_keyword(Keyword::In) {
231                // It's NOT IN
232                self.consume_keyword(Keyword::In)?;
233
234                // Check if it's NOT IN table_name (SQLite syntax) or NOT IN (...)
235                if self.peek() != &Token::LParen {
236                    // SQLite compatibility: NOT IN table_name is equivalent to NOT IN (SELECT *
237                    // FROM table_name) Parse the table name and expand to a
238                    // subquery
239                    let table_ref = self.parse_table_ref()?;
240                    let table_name = table_ref.full_name();
241                    let quoted = table_ref.is_any_quoted();
242
243                    // Create a SELECT * FROM table_name subquery
244                    let subquery = vibesql_ast::SelectStmt {
245                        with_clause: None,
246                        distinct: false,
247                        select_list: vec![vibesql_ast::SelectItem::Wildcard { alias: None }],
248                        into_table: None,
249                        into_variables: None,
250                        from: Some(vibesql_ast::FromClause::Table {
251                            name: table_name,
252                            alias: None,
253                            column_aliases: None,
254                            quoted,
255                        }),
256                        where_clause: None,
257                        group_by: None,
258                        having: None,
259                        order_by: None,
260                        limit: None,
261                        offset: None,
262                        set_operation: None,
263                        values: None,
264                    };
265
266                    left = vibesql_ast::Expression::In {
267                        expr: Box::new(left),
268                        subquery: Box::new(subquery),
269                        negated: true,
270                    };
271                } else {
272                    // Standard syntax: NOT IN (...)
273                    self.expect_token(Token::LParen)?;
274
275                    // Check if it's a subquery (SELECT ...) or a value list
276                    // Also check for parenthesized subqueries like NOT IN ((SELECT ...))
277                    let (is_subquery_through_parens, extra_paren_depth) =
278                        self.peek_select_through_parens();
279
280                    if self.peek_keyword(Keyword::Select)
281                        || self.peek_keyword(Keyword::Values)
282                        || is_subquery_through_parens
283                    {
284                        // It's a subquery: NOT IN (SELECT ...) or NOT IN ((SELECT ...))
285                        // Consume any extra opening parentheses
286                        for _ in 0..extra_paren_depth {
287                            self.expect_token(Token::LParen)?;
288                        }
289
290                        let subquery = self.parse_select_statement()?;
291
292                        // Consume matching closing parentheses
293                        for _ in 0..extra_paren_depth {
294                            self.expect_token(Token::RParen)?;
295                        }
296                        self.expect_token(Token::RParen)?;
297
298                        // Don't return - assign to left and continue to check for IS NULL
299                        left = vibesql_ast::Expression::In {
300                            expr: Box::new(left),
301                            subquery: Box::new(subquery),
302                            negated: true,
303                        };
304                    } else {
305                        // It's a value list: NOT IN (val1, val2, ...)
306                        let values = self.parse_expression_list()?;
307                        self.expect_token(Token::RParen)?;
308
309                        // Empty IN lists are allowed per SQL:1999 (evaluates to TRUE for NOT IN)
310                        // Don't return - assign to left and continue to check for IS NULL
311                        left = vibesql_ast::Expression::InList {
312                            expr: Box::new(left),
313                            values,
314                            negated: true,
315                        };
316                    }
317                }
318            } else if self.peek_keyword(Keyword::Between) {
319                // It's NOT BETWEEN
320                self.consume_keyword(Keyword::Between)?;
321
322                // Check for optional ASYMMETRIC or SYMMETRIC
323                let symmetric = if self.peek_keyword(Keyword::Symmetric) {
324                    self.consume_keyword(Keyword::Symmetric)?;
325                    true
326                } else {
327                    // ASYMMETRIC is default, but can be explicitly specified
328                    if self.peek_keyword(Keyword::Asymmetric) {
329                        self.consume_keyword(Keyword::Asymmetric)?;
330                    }
331                    false
332                };
333
334                // Parse low AND high
335                let low = self.parse_shift_expression()?;
336                self.consume_keyword(Keyword::And)?;
337                let high = self.parse_shift_expression()?;
338
339                // Don't return - assign to left and continue to check for IS NULL
340                left = vibesql_ast::Expression::Between {
341                    expr: Box::new(left),
342                    low: Box::new(low),
343                    high: Box::new(high),
344                    negated: true,
345                    symmetric,
346                };
347            } else if self.peek_keyword(Keyword::Like) {
348                // It's NOT LIKE
349                self.consume_keyword(Keyword::Like)?;
350
351                // Parse pattern expression
352                let pattern = self.parse_shift_expression()?;
353
354                // Check for optional ESCAPE clause
355                let escape = if self.peek_keyword(Keyword::Escape) {
356                    self.consume_keyword(Keyword::Escape)?;
357                    Some(Box::new(self.parse_shift_expression()?))
358                } else {
359                    None
360                };
361
362                // Don't return - assign to left and continue to check for IS NULL
363                left = vibesql_ast::Expression::Like {
364                    expr: Box::new(left),
365                    pattern: Box::new(pattern),
366                    negated: true,
367                    escape,
368                };
369            } else if self.peek_keyword(Keyword::Glob) {
370                // It's NOT GLOB (SQLite)
371                self.consume_keyword(Keyword::Glob)?;
372
373                // Parse pattern expression
374                let pattern = self.parse_shift_expression()?;
375
376                // Check for optional ESCAPE clause
377                let escape = if self.peek_keyword(Keyword::Escape) {
378                    self.consume_keyword(Keyword::Escape)?;
379                    Some(Box::new(self.parse_shift_expression()?))
380                } else {
381                    None
382                };
383
384                // Don't return - assign to left and continue to check for IS NULL
385                left = vibesql_ast::Expression::Glob {
386                    expr: Box::new(left),
387                    pattern: Box::new(pattern),
388                    negated: true,
389                    escape,
390                };
391            } else if self.peek_keyword(Keyword::Null) {
392                // SQLite compatibility: "expr NOT NULL" (without IS) is equivalent to "expr IS NOT
393                // NULL" BUT: In column definition context, "DEFAULT expr NOT NULL"
394                // should parse NOT NULL as a column constraint, not as part of the
395                // expression.
396                //
397                // Heuristic: If the left expression is a simple literal and what follows NULL
398                // could be a column constraint context (`,` `)` or constraint keyword), then
399                // treat NOT NULL as a column constraint, not an operator.
400                // This handles: DEFAULT 'table' NOT NULL
401                // But allows: WHERE col NOT NULL (col is not a literal)
402                self.consume_keyword(Keyword::Null)?;
403
404                // Check if left is a literal (in which case NOT NULL as an operator is semantically
405                // odd)
406                let left_is_literal = matches!(&left, vibesql_ast::Expression::Literal(_));
407
408                // Check what comes after NULL
409                let next_could_be_column_constraint = match self.peek() {
410                    Token::Comma | Token::RParen => true,
411                    Token::Keyword { keyword: kw, .. } => matches!(
412                        kw,
413                        // Column constraint keywords that can follow NOT NULL
414                        Keyword::Check
415                            | Keyword::Unique
416                            | Keyword::Primary
417                            | Keyword::References
418                            | Keyword::Collate
419                            | Keyword::Default
420                            | Keyword::On
421                            | Keyword::Generated
422                            | Keyword::AutoIncrement
423                            | Keyword::Constraint
424                    ),
425                    Token::Semicolon | Token::Eof => false, // At end of query, treat as operator
426                    _ => false,
427                };
428
429                if left_is_literal && next_could_be_column_constraint {
430                    // Likely in column definition: DEFAULT 'value' NOT NULL
431                    // Restore position - NOT NULL should be parsed as column constraint
432                    self.position = saved_pos;
433                } else {
434                    // General case: expr NOT NULL is an operator
435                    return Ok(vibesql_ast::Expression::IsNull {
436                        expr: Box::new(left),
437                        negated: true,
438                    });
439                }
440            } else {
441                // Not "NOT IN", "NOT BETWEEN", "NOT LIKE", "NOT GLOB", or "NOT NULL", restore
442                // position and continue Note: NOT EXISTS is handled in
443                // parse_primary_expression()
444                self.position = saved_pos;
445            }
446        } else if self.peek_keyword(Keyword::In) {
447            // It's IN (not negated)
448            self.consume_keyword(Keyword::In)?;
449
450            // Check if it's IN table_name (SQLite syntax) or IN (...)
451            if self.peek() != &Token::LParen {
452                // SQLite compatibility: IN table_name is equivalent to IN (SELECT * FROM
453                // table_name) Parse the table name and expand to a subquery
454                let table_ref = self.parse_table_ref()?;
455                let table_name = table_ref.full_name();
456                let quoted = table_ref.is_any_quoted();
457
458                // Create a SELECT * FROM table_name subquery
459                let subquery = vibesql_ast::SelectStmt {
460                    with_clause: None,
461                    distinct: false,
462                    select_list: vec![vibesql_ast::SelectItem::Wildcard { alias: None }],
463                    into_table: None,
464                    into_variables: None,
465                    from: Some(vibesql_ast::FromClause::Table {
466                        name: table_name,
467                        alias: None,
468                        column_aliases: None,
469                        quoted,
470                    }),
471                    where_clause: None,
472                    group_by: None,
473                    having: None,
474                    order_by: None,
475                    limit: None,
476                    offset: None,
477                    set_operation: None,
478                    values: None,
479                };
480
481                left = vibesql_ast::Expression::In {
482                    expr: Box::new(left),
483                    subquery: Box::new(subquery),
484                    negated: false,
485                };
486            } else {
487                // Standard syntax: IN (...)
488                self.expect_token(Token::LParen)?;
489
490                // Check if it's a subquery (SELECT ...) or a value list
491                // Also check for parenthesized subqueries like IN ((SELECT ...))
492                let (is_subquery_through_parens, extra_paren_depth) =
493                    self.peek_select_through_parens();
494
495                if self.peek_keyword(Keyword::Select)
496                    || self.peek_keyword(Keyword::Values)
497                    || is_subquery_through_parens
498                {
499                    // It's a subquery: IN (SELECT ...) or IN ((SELECT ...))
500                    // Consume any extra opening parentheses
501                    for _ in 0..extra_paren_depth {
502                        self.expect_token(Token::LParen)?;
503                    }
504
505                    let subquery = self.parse_select_statement()?;
506
507                    // Consume matching closing parentheses
508                    for _ in 0..extra_paren_depth {
509                        self.expect_token(Token::RParen)?;
510                    }
511                    self.expect_token(Token::RParen)?;
512
513                    // Don't return - assign to left and continue to check for IS NULL
514                    left = vibesql_ast::Expression::In {
515                        expr: Box::new(left),
516                        subquery: Box::new(subquery),
517                        negated: false,
518                    };
519                } else {
520                    // It's a value list: IN (val1, val2, ...)
521                    let values = self.parse_expression_list()?;
522                    self.expect_token(Token::RParen)?;
523
524                    // Empty IN lists are allowed per SQL:1999 (evaluates to FALSE)
525                    // Don't return - assign to left and continue to check for IS NULL
526                    left = vibesql_ast::Expression::InList {
527                        expr: Box::new(left),
528                        values,
529                        negated: false,
530                    };
531                }
532            }
533        } else if self.peek_keyword(Keyword::Between) {
534            // It's BETWEEN (not negated)
535            self.consume_keyword(Keyword::Between)?;
536
537            // Check for optional ASYMMETRIC or SYMMETRIC
538            let symmetric = if self.peek_keyword(Keyword::Symmetric) {
539                self.consume_keyword(Keyword::Symmetric)?;
540                true
541            } else {
542                // ASYMMETRIC is default, but can be explicitly specified
543                if self.peek_keyword(Keyword::Asymmetric) {
544                    self.consume_keyword(Keyword::Asymmetric)?;
545                }
546                false
547            };
548
549            // Parse low AND high
550            let low = self.parse_additive_expression()?;
551            self.consume_keyword(Keyword::And)?;
552            let high = self.parse_additive_expression()?;
553
554            // Don't return - assign to left and continue to check for IS NULL
555            left = vibesql_ast::Expression::Between {
556                expr: Box::new(left),
557                low: Box::new(low),
558                high: Box::new(high),
559                negated: false,
560                symmetric,
561            };
562        } else if self.peek_keyword(Keyword::Like) {
563            // It's LIKE (not negated)
564            self.consume_keyword(Keyword::Like)?;
565
566            // Parse pattern expression
567            let pattern = self.parse_additive_expression()?;
568
569            // Check for optional ESCAPE clause
570            let escape = if self.peek_keyword(Keyword::Escape) {
571                self.consume_keyword(Keyword::Escape)?;
572                Some(Box::new(self.parse_additive_expression()?))
573            } else {
574                None
575            };
576
577            // Don't return - assign to left and continue to check for IS NULL
578            left = vibesql_ast::Expression::Like {
579                expr: Box::new(left),
580                pattern: Box::new(pattern),
581                negated: false,
582                escape,
583            };
584        } else if self.peek_keyword(Keyword::Glob) {
585            // It's GLOB (SQLite) (not negated)
586            self.consume_keyword(Keyword::Glob)?;
587
588            // Parse pattern expression
589            let pattern = self.parse_additive_expression()?;
590
591            // Check for optional ESCAPE clause
592            let escape = if self.peek_keyword(Keyword::Escape) {
593                self.consume_keyword(Keyword::Escape)?;
594                Some(Box::new(self.parse_additive_expression()?))
595            } else {
596                None
597            };
598
599            // Don't return - assign to left and continue to check for IS NULL
600            left = vibesql_ast::Expression::Glob {
601                expr: Box::new(left),
602                pattern: Box::new(pattern),
603                negated: false,
604                escape,
605            };
606        }
607
608        // Check for comparison operators (both single-char and multi-char)
609        // Note: Exclude || (concat) operator which should be handled in additive expression
610        let is_comparison = match self.peek() {
611            Token::Symbol('=') | Token::Symbol('<') | Token::Symbol('>') => true,
612            Token::Operator(op) => !matches!(op, crate::token::MultiCharOperator::Concat),
613            _ => false,
614        };
615
616        if is_comparison {
617            let op = match self.peek() {
618                Token::Symbol('=') => vibesql_ast::BinaryOperator::Equal,
619                Token::Symbol('<') => vibesql_ast::BinaryOperator::LessThan,
620                Token::Symbol('>') => vibesql_ast::BinaryOperator::GreaterThan,
621                Token::Operator(op) => {
622                    use crate::token::MultiCharOperator;
623                    match op {
624                        MultiCharOperator::LessEqual => {
625                            vibesql_ast::BinaryOperator::LessThanOrEqual
626                        }
627                        MultiCharOperator::GreaterEqual => {
628                            vibesql_ast::BinaryOperator::GreaterThanOrEqual
629                        }
630                        MultiCharOperator::NotEqual | MultiCharOperator::NotEqualAlt => {
631                            vibesql_ast::BinaryOperator::NotEqual
632                        }
633                        // SQLite compatibility: == is a synonym for =
634                        MultiCharOperator::DoubleEqual => vibesql_ast::BinaryOperator::Equal,
635                        // Vector distance operators (pgvector compatible)
636                        MultiCharOperator::CosineDistance => {
637                            vibesql_ast::BinaryOperator::CosineDistance
638                        }
639                        MultiCharOperator::NegativeInnerProduct => {
640                            vibesql_ast::BinaryOperator::NegativeInnerProduct
641                        }
642                        MultiCharOperator::L2Distance => vibesql_ast::BinaryOperator::L2Distance,
643                        MultiCharOperator::Concat
644                        | MultiCharOperator::LeftShift
645                        | MultiCharOperator::RightShift
646                        | MultiCharOperator::JsonExtract
647                        | MultiCharOperator::JsonExtractText => {
648                            return Err(ParseError {
649                                message: format!("Unexpected operator: {}", op),
650                            })
651                        }
652                    }
653                }
654                _ => unreachable!(),
655            };
656            self.advance();
657
658            // Check for quantified comparison (ALL, ANY, SOME)
659            if self.peek_keyword(Keyword::All)
660                || self.peek_keyword(Keyword::Any)
661                || self.peek_keyword(Keyword::Some)
662            {
663                let quantifier = if self.peek_keyword(Keyword::All) {
664                    self.consume_keyword(Keyword::All)?;
665                    vibesql_ast::Quantifier::All
666                } else if self.peek_keyword(Keyword::Any) {
667                    self.consume_keyword(Keyword::Any)?;
668                    vibesql_ast::Quantifier::Any
669                } else {
670                    self.consume_keyword(Keyword::Some)?;
671                    vibesql_ast::Quantifier::Some
672                };
673
674                // Expect opening paren
675                self.expect_token(Token::LParen)?;
676
677                // Parse subquery
678                let subquery = self.parse_select_statement()?;
679
680                // Expect closing paren
681                self.expect_token(Token::RParen)?;
682
683                return Ok(vibesql_ast::Expression::QuantifiedComparison {
684                    expr: Box::new(left),
685                    op,
686                    quantifier,
687                    subquery: Box::new(subquery),
688                });
689            }
690
691            let right = self.parse_shift_expression()?;
692            left = vibesql_ast::Expression::BinaryOp {
693                op,
694                left: Box::new(left),
695                right: Box::new(right),
696            };
697        }
698
699        // Check for IS NULL / IS NOT NULL / IS [NOT] DISTINCT FROM / IS [NOT] TRUE/FALSE/UNKNOWN
700        if self.peek_keyword(Keyword::Is) {
701            self.consume_keyword(Keyword::Is)?;
702
703            // Check for NOT
704            let negated = if self.peek_keyword(Keyword::Not) {
705                self.consume_keyword(Keyword::Not)?;
706                true
707            } else {
708                false
709            };
710
711            // Check for DISTINCT FROM (SQL:1999), NULL, TRUE, FALSE, or UNKNOWN
712            if self.peek_keyword(Keyword::Distinct) {
713                self.consume_keyword(Keyword::Distinct)?;
714                self.expect_keyword(Keyword::From)?;
715                let right = self.parse_shift_expression()?;
716                left = vibesql_ast::Expression::IsDistinctFrom {
717                    left: Box::new(left),
718                    right: Box::new(right),
719                    negated,
720                };
721            } else if self.peek_keyword(Keyword::True) {
722                self.consume_keyword(Keyword::True)?;
723                left = vibesql_ast::Expression::IsTruthValue {
724                    expr: Box::new(left),
725                    truth_value: vibesql_ast::TruthValue::True,
726                    negated,
727                };
728            } else if self.peek_keyword(Keyword::False) {
729                self.consume_keyword(Keyword::False)?;
730                left = vibesql_ast::Expression::IsTruthValue {
731                    expr: Box::new(left),
732                    truth_value: vibesql_ast::TruthValue::False,
733                    negated,
734                };
735            } else if self.peek_keyword(Keyword::Unknown) {
736                self.consume_keyword(Keyword::Unknown)?;
737                left = vibesql_ast::Expression::IsTruthValue {
738                    expr: Box::new(left),
739                    truth_value: vibesql_ast::TruthValue::Unknown,
740                    negated,
741                };
742            } else if self.peek_keyword(Keyword::Null) {
743                // IS NULL / IS NOT NULL
744                self.consume_keyword(Keyword::Null)?;
745                left = vibesql_ast::Expression::IsNull { expr: Box::new(left), negated };
746            } else {
747                // SQLite compatibility: IS <expr> - compare using IS semantics (NULL-safe equals)
748                // This handles cases like `expr IS 0` or `expr IS 1`
749                let right = self.parse_shift_expression()?;
750                left = vibesql_ast::Expression::IsDistinctFrom {
751                    left: Box::new(left),
752                    right: Box::new(right),
753                    // IS is equivalent to IS NOT DISTINCT FROM (NULL-safe equals)
754                    // IS NOT is equivalent to IS DISTINCT FROM (NULL-safe not equals)
755                    negated: !negated,
756                };
757            }
758        }
759
760        // SQLite compatibility: ISNULL and NOTNULL as postfix operators
761        // These are equivalent to IS NULL and IS NOT NULL respectively
762        // Loop to support chaining: `x NOTNULL NOTNULL` → `(x IS NOT NULL) IS NOT NULL`
763        loop {
764            if self.peek_keyword(Keyword::Isnull) {
765                self.consume_keyword(Keyword::Isnull)?;
766                left = vibesql_ast::Expression::IsNull { expr: Box::new(left), negated: false };
767            } else if self.peek_keyword(Keyword::Notnull) {
768                self.consume_keyword(Keyword::Notnull)?;
769                left = vibesql_ast::Expression::IsNull { expr: Box::new(left), negated: true };
770            } else {
771                break;
772            }
773        }
774
775        Ok(left)
776    }
777
778    /// Parse unary expression (handles unary +, -, ~ operators)
779    pub(super) fn parse_unary_expression(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
780        // Check for unary +, -, or ~
781        match self.peek() {
782            Token::Symbol('+') => {
783                self.advance();
784                let expr = self.parse_unary_expression()?;
785                Ok(vibesql_ast::Expression::UnaryOp {
786                    op: vibesql_ast::UnaryOperator::Plus,
787                    expr: Box::new(expr),
788                })
789            }
790            Token::Symbol('-') => {
791                self.advance();
792                // Special case: handle i64::MIN (-9223372036854775808)
793                // The positive value 9223372036854775808 overflows i64, but when
794                // negated it becomes i64::MIN which is valid. We need to detect
795                // this case and parse the combined negative number as i64.
796                if let Token::Number(n) = self.peek() {
797                    if n == "9223372036854775808" {
798                        // This is the only number that overflows i64 but is valid when negated
799                        self.advance();
800                        return Ok(vibesql_ast::Expression::Literal(
801                            vibesql_types::SqlValue::Integer(i64::MIN),
802                        ));
803                    }
804                }
805                let expr = self.parse_unary_expression()?;
806                Ok(vibesql_ast::Expression::UnaryOp {
807                    op: vibesql_ast::UnaryOperator::Minus,
808                    expr: Box::new(expr),
809                })
810            }
811            Token::Symbol('~') => {
812                self.advance();
813                let expr = self.parse_unary_expression()?;
814                Ok(vibesql_ast::Expression::UnaryOp {
815                    op: vibesql_ast::UnaryOperator::BitwiseNot,
816                    expr: Box::new(expr),
817                })
818            }
819            _ => self.parse_postfix_expression(),
820        }
821    }
822
823    /// Parse postfix expressions (JSON operators ->, ->>, and COLLATE)
824    /// These have high precedence, binding tighter than most operators
825    pub(super) fn parse_postfix_expression(
826        &mut self,
827    ) -> Result<vibesql_ast::Expression, ParseError> {
828        let mut expr = self.parse_primary_expression()?;
829
830        // Handle JSON operators -> and ->> (high precedence, left-associative)
831        loop {
832            let op = match self.peek() {
833                Token::Operator(crate::token::MultiCharOperator::JsonExtract) => {
834                    vibesql_ast::BinaryOperator::JsonExtract
835                }
836                Token::Operator(crate::token::MultiCharOperator::JsonExtractText) => {
837                    vibesql_ast::BinaryOperator::JsonExtractText
838                }
839                _ => break,
840            };
841            self.advance();
842
843            // Parse the key/index expression (usually a string or integer)
844            let right = self.parse_primary_expression()?;
845            expr = vibesql_ast::Expression::BinaryOp {
846                op,
847                left: Box::new(expr),
848                right: Box::new(right),
849            };
850        }
851
852        // Handle COLLATE postfix operator
853        while self.peek_keyword(Keyword::Collate) {
854            self.consume_keyword(Keyword::Collate)?;
855            // Parse collation name (identifier)
856            let collation = match self.peek() {
857                Token::Identifier(name) | Token::DelimitedIdentifier(name) => {
858                    let name = name.clone();
859                    self.advance();
860                    name
861                }
862                Token::Keyword { keyword: kw, .. } => {
863                    // Allow keywords like BINARY, NOCASE, RTRIM as collation names
864                    let name = kw.to_string();
865                    self.advance();
866                    name
867                }
868                _ => {
869                    return Err(ParseError {
870                        message: "Expected collation name after COLLATE".to_string(),
871                    })
872                }
873            };
874            expr = vibesql_ast::Expression::Collate { expr: Box::new(expr), collation };
875        }
876
877        Ok(expr)
878    }
879
880    /// Parse a comma-separated list of expressions
881    /// Used for IN (val1, val2, ...) and function arguments
882    /// Does NOT consume the opening or closing parentheses
883    pub fn parse_expression_list(&mut self) -> Result<Vec<vibesql_ast::Expression>, ParseError> {
884        let mut expressions = Vec::new();
885
886        // Check for empty list (SQLite compatibility)
887        if matches!(self.peek(), Token::RParen) {
888            return Ok(expressions);
889        }
890
891        // Parse first expression
892        expressions.push(self.parse_expression()?);
893
894        // Parse remaining expressions
895        while matches!(self.peek(), Token::Comma) {
896            self.advance(); // consume comma
897            expressions.push(self.parse_expression()?);
898        }
899
900        Ok(expressions)
901    }
902}