sql_cli/sql/parser/expressions/
comparison.rs

1// Comparison expression parsing
2// Handles comparison operators, BETWEEN, IN/NOT IN, LIKE, IS NULL/IS NOT NULL
3
4use crate::sql::parser::ast::SqlExpression;
5use crate::sql::parser::lexer::Token;
6use tracing::debug;
7
8use super::{log_parse_decision, trace_parse_entry, trace_parse_exit};
9
10/// Parse a comparison expression
11/// This handles comparison operators and special SQL operators
12pub fn parse_comparison<P>(parser: &mut P) -> Result<SqlExpression, String>
13where
14    P: ParseComparison + ?Sized,
15{
16    trace_parse_entry("parse_comparison", parser.current_token());
17
18    let mut left = parser.parse_additive()?;
19
20    // Handle BETWEEN operator
21    if matches!(parser.current_token(), Token::Between) {
22        debug!("BETWEEN operator detected");
23        log_parse_decision(
24            "parse_comparison",
25            parser.current_token(),
26            "BETWEEN operator - parsing range bounds",
27        );
28
29        parser.advance(); // consume BETWEEN
30        let lower = parser.parse_additive()?;
31        parser.consume(Token::And)?; // BETWEEN requires AND
32        let upper = parser.parse_additive()?;
33
34        let result = Ok(SqlExpression::Between {
35            expr: Box::new(left),
36            lower: Box::new(lower),
37            upper: Box::new(upper),
38        });
39        trace_parse_exit("parse_comparison", &result);
40        return result;
41    }
42
43    // Handle NOT IN operator
44    if matches!(parser.current_token(), Token::Not) {
45        // Peek ahead to see if this is NOT IN
46        parser.advance(); // consume NOT
47        if matches!(parser.current_token(), Token::In) {
48            debug!("NOT IN operator detected");
49            log_parse_decision(
50                "parse_comparison",
51                parser.current_token(),
52                "NOT IN operator - parsing value list",
53            );
54
55            parser.advance(); // consume IN
56            parser.consume(Token::LeftParen)?;
57            let values = parser.parse_expression_list()?;
58            parser.consume(Token::RightParen)?;
59
60            let result = Ok(SqlExpression::NotInList {
61                expr: Box::new(left),
62                values,
63            });
64            trace_parse_exit("parse_comparison", &result);
65            return result;
66        }
67        return Err("Expected IN after NOT".to_string());
68    }
69
70    // Handle IS NULL / IS NOT NULL
71    if matches!(parser.current_token(), Token::Is) {
72        parser.advance(); // consume IS
73
74        if matches!(parser.current_token(), Token::Not) {
75            parser.advance(); // consume NOT
76            if matches!(parser.current_token(), Token::Null) {
77                debug!("IS NOT NULL operator detected");
78                log_parse_decision(
79                    "parse_comparison",
80                    parser.current_token(),
81                    "IS NOT NULL operator",
82                );
83
84                parser.advance(); // consume NULL
85                left = SqlExpression::BinaryOp {
86                    left: Box::new(left),
87                    op: "IS NOT NULL".to_string(),
88                    right: Box::new(SqlExpression::Null),
89                };
90            } else {
91                return Err("Expected NULL after IS NOT".to_string());
92            }
93        } else if matches!(parser.current_token(), Token::Null) {
94            debug!("IS NULL operator detected");
95            log_parse_decision(
96                "parse_comparison",
97                parser.current_token(),
98                "IS NULL operator",
99            );
100
101            parser.advance(); // consume NULL
102            left = SqlExpression::BinaryOp {
103                left: Box::new(left),
104                op: "IS NULL".to_string(),
105                right: Box::new(SqlExpression::Null),
106            };
107        } else {
108            return Err("Expected NULL or NOT after IS".to_string());
109        }
110    }
111    // Handle comparison operators
112    else if let Some(op) = get_comparison_op(parser.current_token()) {
113        log_parse_decision(
114            "parse_comparison",
115            parser.current_token(),
116            &format!("Comparison operator '{}' found", op),
117        );
118
119        debug!(operator = %op, "Processing comparison operator");
120
121        parser.advance();
122        let right = parser.parse_additive()?;
123        left = SqlExpression::BinaryOp {
124            left: Box::new(left),
125            op,
126            right: Box::new(right),
127        };
128    }
129
130    let result = Ok(left);
131    trace_parse_exit("parse_comparison", &result);
132    result
133}
134
135/// Parse an expression that may contain IN operator
136/// This is called from parse_expression to handle IN after other comparisons
137pub fn parse_in_operator<P>(parser: &mut P, expr: SqlExpression) -> Result<SqlExpression, String>
138where
139    P: ParseComparison + ?Sized,
140{
141    trace_parse_entry("parse_in_operator", parser.current_token());
142
143    if matches!(parser.current_token(), Token::In) {
144        debug!("IN operator detected");
145        log_parse_decision(
146            "parse_in_operator",
147            parser.current_token(),
148            "IN operator - parsing value list",
149        );
150
151        parser.advance();
152        parser.consume(Token::LeftParen)?;
153        let values = parser.parse_expression_list()?;
154        parser.consume(Token::RightParen)?;
155
156        let result = Ok(SqlExpression::InList {
157            expr: Box::new(expr),
158            values,
159        });
160        trace_parse_exit("parse_in_operator", &result);
161        result
162    } else {
163        Ok(expr)
164    }
165}
166
167/// Get comparison operator from token
168fn get_comparison_op(token: &Token) -> Option<String> {
169    match token {
170        Token::Equal => Some("=".to_string()),
171        Token::NotEqual => Some("!=".to_string()),
172        Token::LessThan => Some("<".to_string()),
173        Token::GreaterThan => Some(">".to_string()),
174        Token::LessThanOrEqual => Some("<=".to_string()),
175        Token::GreaterThanOrEqual => Some(">=".to_string()),
176        Token::Like => Some("LIKE".to_string()),
177        _ => None,
178    }
179}
180
181/// Trait that parsers must implement to use comparison expression parsing
182pub trait ParseComparison {
183    fn current_token(&self) -> &Token;
184    fn advance(&mut self);
185    fn consume(&mut self, expected: Token) -> Result<(), String>;
186
187    // These methods are called from comparison parsing
188    fn parse_primary(&mut self) -> Result<SqlExpression, String>;
189    fn parse_additive(&mut self) -> Result<SqlExpression, String>;
190    fn parse_expression_list(&mut self) -> Result<Vec<SqlExpression>, String>;
191}