sql_cli/sql/parser/expressions/
arithmetic.rs

1// Arithmetic expression parsing
2// Handles additive (+, -) and multiplicative (*, /, %) expressions
3// Also handles method calls (e.g., column.upper()) as part of multiplicative precedence
4
5use crate::sql::parser::ast::{ColumnRef, SqlExpression};
6use crate::sql::parser::lexer::Token;
7use tracing::debug;
8
9use super::{log_parse_decision, trace_parse_entry, trace_parse_exit};
10
11/// Parse an additive expression (+ and - operators, and || for string concatenation)
12/// This handles left-associative binary operators at the additive precedence level
13pub fn parse_additive<P>(parser: &mut P) -> Result<SqlExpression, String>
14where
15    P: ParseArithmetic + ?Sized,
16{
17    trace_parse_entry("parse_additive", parser.current_token());
18
19    let mut left = parser.parse_multiplicative()?;
20
21    while matches!(
22        parser.current_token(),
23        Token::Plus | Token::Minus | Token::Concat
24    ) {
25        let op = match parser.current_token() {
26            Token::Plus => "+",
27            Token::Minus => "-",
28            Token::Concat => {
29                // Handle || as syntactic sugar for TEXTJOIN
30                log_parse_decision(
31                    "parse_additive",
32                    parser.current_token(),
33                    "String concatenation '||' found, converting to TEXTJOIN",
34                );
35
36                parser.advance();
37                let right = parser.parse_multiplicative()?;
38
39                // Convert left || right to TEXTJOIN('', 1, left, right)
40                // TEXTJOIN needs: delimiter, ignore_empty flag, then values
41                left = SqlExpression::FunctionCall {
42                    name: "TEXTJOIN".to_string(),
43                    args: vec![
44                        SqlExpression::StringLiteral("".to_string()), // Empty separator
45                        SqlExpression::NumberLiteral("1".to_string()), // ignore_empty = true
46                        left,
47                        right,
48                    ],
49                    distinct: false,
50                };
51                continue; // Process next operator if any
52            }
53            _ => unreachable!(),
54        };
55
56        log_parse_decision(
57            "parse_additive",
58            parser.current_token(),
59            &format!("Binary operator '{}' found, parsing right operand", op),
60        );
61
62        parser.advance();
63        let right = parser.parse_multiplicative()?;
64
65        debug!(operator = op, "Creating additive binary operation");
66
67        left = SqlExpression::BinaryOp {
68            left: Box::new(left),
69            op: op.to_string(),
70            right: Box::new(right),
71        };
72    }
73
74    let result = Ok(left);
75    trace_parse_exit("parse_additive", &result);
76    result
77}
78
79/// Parse a multiplicative expression (*, /, % operators and method calls)
80/// This handles left-associative binary operators at the multiplicative precedence level
81/// Also handles method calls (.) which have the same precedence as multiplication
82pub fn parse_multiplicative<P>(parser: &mut P) -> Result<SqlExpression, String>
83where
84    P: ParseArithmetic + ?Sized,
85{
86    trace_parse_entry("parse_multiplicative", parser.current_token());
87
88    let mut left = parser.parse_primary()?;
89
90    // Handle method calls and qualified column names
91    // Method calls have the same precedence as multiplication
92    while matches!(parser.current_token(), Token::Dot) {
93        debug!("Found dot operator");
94        parser.advance();
95
96        if let Token::Identifier(name) = parser.current_token() {
97            let name_str = name.clone();
98            parser.advance();
99
100            if matches!(parser.current_token(), Token::LeftParen) {
101                // This is a method call
102                log_parse_decision(
103                    "parse_multiplicative",
104                    parser.current_token(),
105                    &format!("Method call '{}' detected", name_str),
106                );
107
108                parser.advance();
109                let args = parser.parse_method_args()?;
110                parser.consume(Token::RightParen)?;
111
112                // Support chained method calls
113                left = match left {
114                    SqlExpression::Column(obj) => {
115                        // First method call on a column
116                        debug!(
117                            column = %obj,
118                            method = %name_str,
119                            "Creating method call on column"
120                        );
121                        SqlExpression::MethodCall {
122                            object: obj.name,
123                            method: name_str,
124                            args,
125                        }
126                    }
127                    SqlExpression::MethodCall { .. } | SqlExpression::ChainedMethodCall { .. } => {
128                        // Chained method call on a previous method call
129                        debug!(
130                            method = %name_str,
131                            "Creating chained method call"
132                        );
133                        SqlExpression::ChainedMethodCall {
134                            base: Box::new(left),
135                            method: name_str,
136                            args,
137                        }
138                    }
139                    _ => {
140                        // Method call on any other expression
141                        debug!(
142                            method = %name_str,
143                            "Creating method call on expression"
144                        );
145                        SqlExpression::ChainedMethodCall {
146                            base: Box::new(left),
147                            method: name_str,
148                            args,
149                        }
150                    }
151                };
152            } else {
153                // This is a qualified column name (table.column or alias.column)
154                // Combine the left expression with the column name
155                left = match left {
156                    SqlExpression::Column(table_or_alias) => {
157                        debug!(
158                            table = %table_or_alias,
159                            column = %name_str,
160                            "Creating qualified column reference"
161                        );
162                        SqlExpression::Column(ColumnRef::unquoted(format!(
163                            "{}.{}",
164                            table_or_alias, name_str
165                        )))
166                    }
167                    _ => {
168                        // If left is not a simple column, this is an error
169                        return Err(format!(
170                            "Invalid qualified column reference with expression"
171                        ));
172                    }
173                };
174            }
175        } else {
176            return Err("Expected identifier after '.'".to_string());
177        }
178    }
179
180    // Handle multiplicative binary operators
181    while matches!(
182        parser.current_token(),
183        Token::Star | Token::Divide | Token::Modulo
184    ) {
185        let op = match parser.current_token() {
186            Token::Star => "*",
187            Token::Divide => "/",
188            Token::Modulo => "%",
189            _ => unreachable!(),
190        };
191
192        log_parse_decision(
193            "parse_multiplicative",
194            parser.current_token(),
195            &format!("Binary operator '{}' found, parsing right operand", op),
196        );
197
198        parser.advance();
199        let right = parser.parse_primary()?;
200
201        debug!(operator = op, "Creating multiplicative binary operation");
202
203        left = SqlExpression::BinaryOp {
204            left: Box::new(left),
205            op: op.to_string(),
206            right: Box::new(right),
207        };
208    }
209
210    let result = Ok(left);
211    trace_parse_exit("parse_multiplicative", &result);
212    result
213}
214
215/// Trait that parsers must implement to use arithmetic expression parsing
216pub trait ParseArithmetic {
217    fn current_token(&self) -> &Token;
218    fn advance(&mut self);
219    fn consume(&mut self, expected: Token) -> Result<(), String>;
220
221    // These methods are called from arithmetic parsing
222    fn parse_primary(&mut self) -> Result<SqlExpression, String>;
223    fn parse_multiplicative(&mut self) -> Result<SqlExpression, String>;
224    fn parse_method_args(&mut self) -> Result<Vec<SqlExpression>, String>;
225}