Skip to main content

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