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
58            // Check if this is a subquery (starts with SELECT)
59            if matches!(parser.current_token(), Token::Select) {
60                debug!("Detected NOT IN subquery");
61                let subquery = parser.parse_subquery()?;
62                parser.consume(Token::RightParen)?;
63
64                let result = Ok(SqlExpression::NotInSubquery {
65                    expr: Box::new(left),
66                    subquery: Box::new(subquery),
67                });
68                trace_parse_exit("parse_comparison", &result);
69                return result;
70            } else {
71                // Regular NOT IN with value list
72                let values = parser.parse_expression_list()?;
73                parser.consume(Token::RightParen)?;
74
75                let result = Ok(SqlExpression::NotInList {
76                    expr: Box::new(left),
77                    values,
78                });
79                trace_parse_exit("parse_comparison", &result);
80                return result;
81            }
82        } else {
83            return Err("Expected IN after NOT".to_string());
84        }
85    }
86
87    // Handle IS NULL / IS NOT NULL
88    if matches!(parser.current_token(), Token::Is) {
89        parser.advance(); // consume IS
90
91        if matches!(parser.current_token(), Token::Not) {
92            parser.advance(); // consume NOT
93            if matches!(parser.current_token(), Token::Null) {
94                debug!("IS NOT NULL operator detected");
95                log_parse_decision(
96                    "parse_comparison",
97                    parser.current_token(),
98                    "IS NOT NULL operator",
99                );
100
101                parser.advance(); // consume NULL
102                left = SqlExpression::BinaryOp {
103                    left: Box::new(left),
104                    op: "IS NOT NULL".to_string(),
105                    right: Box::new(SqlExpression::Null),
106                };
107            } else {
108                return Err("Expected NULL after IS NOT".to_string());
109            }
110        } else if matches!(parser.current_token(), Token::Null) {
111            debug!("IS NULL operator detected");
112            log_parse_decision(
113                "parse_comparison",
114                parser.current_token(),
115                "IS NULL operator",
116            );
117
118            parser.advance(); // consume NULL
119            left = SqlExpression::BinaryOp {
120                left: Box::new(left),
121                op: "IS NULL".to_string(),
122                right: Box::new(SqlExpression::Null),
123            };
124        } else {
125            return Err("Expected NULL or NOT after IS".to_string());
126        }
127    }
128    // Handle comparison operators
129    else if let Some(op) = get_comparison_op(parser.current_token()) {
130        log_parse_decision(
131            "parse_comparison",
132            parser.current_token(),
133            &format!("Comparison operator '{}' found", op),
134        );
135
136        debug!(operator = %op, "Processing comparison operator");
137
138        parser.advance();
139        let right = parser.parse_additive()?;
140        left = SqlExpression::BinaryOp {
141            left: Box::new(left),
142            op,
143            right: Box::new(right),
144        };
145    }
146
147    let result = Ok(left);
148    trace_parse_exit("parse_comparison", &result);
149    result
150}
151
152/// Parse an expression that may contain IN operator
153/// This is called from parse_expression to handle IN after other comparisons
154pub fn parse_in_operator<P>(parser: &mut P, expr: SqlExpression) -> Result<SqlExpression, String>
155where
156    P: ParseComparison + ?Sized,
157{
158    trace_parse_entry("parse_in_operator", parser.current_token());
159
160    if matches!(parser.current_token(), Token::In) {
161        debug!("IN operator detected");
162        log_parse_decision(
163            "parse_in_operator",
164            parser.current_token(),
165            "IN operator - parsing value list",
166        );
167
168        parser.advance(); // consume IN
169        parser.consume(Token::LeftParen)?;
170
171        // Check if this is a subquery (starts with SELECT)
172        if matches!(parser.current_token(), Token::Select) {
173            debug!("Detected IN subquery");
174            let subquery = parser.parse_subquery()?;
175            parser.consume(Token::RightParen)?;
176
177            let result = Ok(SqlExpression::InSubquery {
178                expr: Box::new(expr),
179                subquery: Box::new(subquery),
180            });
181            trace_parse_exit("parse_in_operator", &result);
182            return result;
183        } else {
184            // Regular IN with value list
185            let values = parser.parse_expression_list()?;
186            parser.consume(Token::RightParen)?;
187
188            let result = Ok(SqlExpression::InList {
189                expr: Box::new(expr),
190                values,
191            });
192            trace_parse_exit("parse_in_operator", &result);
193            return result;
194        }
195    } else {
196        Ok(expr)
197    }
198}
199
200/// Get comparison operator from token
201fn get_comparison_op(token: &Token) -> Option<String> {
202    match token {
203        Token::Equal => Some("=".to_string()),
204        Token::NotEqual => Some("!=".to_string()),
205        Token::LessThan => Some("<".to_string()),
206        Token::GreaterThan => Some(">".to_string()),
207        Token::LessThanOrEqual => Some("<=".to_string()),
208        Token::GreaterThanOrEqual => Some(">=".to_string()),
209        Token::Like => Some("LIKE".to_string()),
210        _ => None,
211    }
212}
213
214/// Trait that parsers must implement to use comparison expression parsing
215pub trait ParseComparison {
216    fn current_token(&self) -> &Token;
217    fn advance(&mut self);
218    fn consume(&mut self, expected: Token) -> Result<(), String>;
219
220    // These methods are called from comparison parsing
221    fn parse_primary(&mut self) -> Result<SqlExpression, String>;
222    fn parse_additive(&mut self) -> Result<SqlExpression, String>;
223    fn parse_expression_list(&mut self) -> Result<Vec<SqlExpression>, String>;
224
225    // For subquery parsing (without parenthesis balance validation)
226    fn parse_subquery(&mut self) -> Result<crate::sql::parser::ast::SelectStatement, String>;
227}