Skip to main content

sql_cli/sql/parser/expressions/
primary.rs

1// Primary expression parsing
2// Handles literals, identifiers, function calls, and parenthesized expressions
3
4use crate::sql::parser::ast::{ColumnRef, SqlExpression, WindowSpec};
5use crate::sql::parser::lexer::Token;
6use tracing::{debug, trace};
7
8use super::{log_parse_decision, trace_parse_entry, trace_parse_exit, ExpressionParser};
9
10/// Parser context for primary expressions
11pub struct PrimaryExpressionContext<'a> {
12    pub columns: &'a [String],
13    pub in_method_args: bool,
14}
15
16impl<'a> Default for PrimaryExpressionContext<'a> {
17    fn default() -> Self {
18        Self {
19            columns: &[],
20            in_method_args: false,
21        }
22    }
23}
24
25/// Parse a primary expression (literals, identifiers, functions, parentheses)
26/// This is the bottom of the expression hierarchy
27pub fn parse_primary<P>(
28    parser: &mut P,
29    ctx: &PrimaryExpressionContext,
30) -> Result<SqlExpression, String>
31where
32    P: ParsePrimary + ExpressionParser + ?Sized,
33{
34    trace_parse_entry("parse_primary", ExpressionParser::current_token(parser));
35
36    // Special case: check if a number literal could actually be a column name
37    // This handles cases where columns are named with pure numbers like "202204"
38    if let Token::NumberLiteral(num_str) = ExpressionParser::current_token(parser) {
39        if ctx.columns.iter().any(|col| col == num_str) {
40            log_parse_decision(
41                "parse_primary",
42                ExpressionParser::current_token(parser),
43                "Number literal matches column name, treating as column",
44            );
45            let expr = SqlExpression::Column(ColumnRef::unquoted(num_str.clone()));
46            ExpressionParser::advance(parser);
47            let result = Ok(expr);
48            trace_parse_exit("parse_primary", &result);
49            return result;
50        }
51    }
52
53    let result = match ExpressionParser::current_token(parser) {
54        Token::Case => {
55            debug!("Parsing CASE expression");
56            parser.parse_case_expression()
57        }
58
59        Token::DateTime => {
60            debug!("Parsing DateTime constructor");
61            parse_datetime_constructor(parser)
62        }
63
64        Token::Unnest => {
65            debug!("Parsing UNNEST expression");
66            parse_unnest(parser)
67        }
68
69        Token::Identifier(id) => {
70            let id_upper = id.to_uppercase();
71            let id_clone = id.clone();
72
73            // Check for boolean literals first
74            if id_upper == "TRUE" {
75                log_parse_decision(
76                    "parse_primary",
77                    ExpressionParser::current_token(parser),
78                    "Boolean literal TRUE",
79                );
80                ExpressionParser::advance(parser);
81                Ok(SqlExpression::BooleanLiteral(true))
82            } else if id_upper == "FALSE" {
83                log_parse_decision(
84                    "parse_primary",
85                    ExpressionParser::current_token(parser),
86                    "Boolean literal FALSE",
87                );
88                ExpressionParser::advance(parser);
89                Ok(SqlExpression::BooleanLiteral(false))
90            } else {
91                ExpressionParser::advance(parser);
92
93                // Check for table.column notation or method calls
94                if matches!(ExpressionParser::current_token(parser), Token::Dot) {
95                    ExpressionParser::advance(parser); // consume dot
96
97                    if let Token::Identifier(next_id) = ExpressionParser::current_token(parser) {
98                        let next_id = next_id.clone();
99                        ExpressionParser::advance(parser);
100
101                        // Check if this is a method call (followed by parentheses)
102                        if matches!(ExpressionParser::current_token(parser), Token::LeftParen) {
103                            debug!(object = %id_clone, method = %next_id, "Parsing method call");
104                            ExpressionParser::advance(parser); // consume (
105
106                            // Handle empty argument list
107                            let args = if matches!(
108                                ExpressionParser::current_token(parser),
109                                Token::RightParen
110                            ) {
111                                Vec::new()
112                            } else {
113                                parser.parse_expression_list()?
114                            };
115                            ExpressionParser::consume(parser, Token::RightParen)?;
116
117                            log_parse_decision(
118                                "parse_primary",
119                                &Token::Identifier(next_id.clone()),
120                                "Method call",
121                            );
122                            Ok(SqlExpression::MethodCall {
123                                object: id_clone,
124                                method: next_id,
125                                args,
126                            })
127                        } else {
128                            // It's a qualified column reference
129                            let col_ref = ColumnRef::qualified(id_clone, next_id.clone());
130                            log_parse_decision(
131                                "parse_primary",
132                                &Token::Identifier(next_id),
133                                "Qualified column reference",
134                            );
135                            Ok(SqlExpression::Column(col_ref))
136                        }
137                    } else {
138                        Err("Expected identifier after '.'".to_string())
139                    }
140                // Check if this is a function call
141                } else if matches!(ExpressionParser::current_token(parser), Token::LeftParen) {
142                    debug!(function = %id_upper, "Parsing function call");
143                    ExpressionParser::advance(parser); // consume (
144                    let (args, has_distinct) = parser.parse_function_args()?;
145                    ExpressionParser::consume(parser, Token::RightParen)?;
146
147                    // Check for OVER clause for window functions
148                    if matches!(ExpressionParser::current_token(parser), Token::Over) {
149                        debug!(function = %id_upper, "Window function detected");
150                        ExpressionParser::advance(parser); // consume OVER
151                        ExpressionParser::consume(parser, Token::LeftParen)?;
152                        let window_spec = parser.parse_window_spec()?;
153                        ExpressionParser::consume(parser, Token::RightParen)?;
154                        Ok(SqlExpression::WindowFunction {
155                            name: id_upper,
156                            args,
157                            window_spec,
158                        })
159                    } else {
160                        Ok(SqlExpression::FunctionCall {
161                            name: id_upper,
162                            args,
163                            distinct: has_distinct,
164                        })
165                    }
166                } else {
167                    // Otherwise treat as simple column
168                    log_parse_decision(
169                        "parse_primary",
170                        &Token::Identifier(id_clone.clone()),
171                        "Column reference",
172                    );
173                    Ok(SqlExpression::Column(ColumnRef::unquoted(id_clone)))
174                }
175            }
176        }
177
178        Token::QuotedIdentifier(id) => {
179            let expr = if ctx.in_method_args {
180                // In method arguments, treat quoted identifiers as string literals
181                log_parse_decision(
182                    "parse_primary",
183                    ExpressionParser::current_token(parser),
184                    "Quoted identifier in method args - treating as string",
185                );
186                SqlExpression::StringLiteral(id.clone())
187            } else {
188                // Otherwise it's a column name like "Customer Id"
189                log_parse_decision(
190                    "parse_primary",
191                    ExpressionParser::current_token(parser),
192                    "Quoted identifier as column name",
193                );
194                SqlExpression::Column(ColumnRef::quoted(id.clone()))
195            };
196            ExpressionParser::advance(parser);
197            Ok(expr)
198        }
199
200        Token::StringLiteral(s) => {
201            trace!("String literal: {}", s);
202            let expr = SqlExpression::StringLiteral(s.clone());
203            ExpressionParser::advance(parser);
204            Ok(expr)
205        }
206
207        Token::NumberLiteral(n) => {
208            trace!("Number literal: {}", n);
209            let expr = SqlExpression::NumberLiteral(n.clone());
210            ExpressionParser::advance(parser);
211            Ok(expr)
212        }
213
214        Token::Null => {
215            trace!("NULL literal");
216            ExpressionParser::advance(parser);
217            Ok(SqlExpression::Null)
218        }
219
220        // Handle LEFT and RIGHT as function names when followed by parentheses
221        Token::Left | Token::Right => {
222            let func_name = match ExpressionParser::current_token(parser) {
223                Token::Left => "LEFT".to_string(),
224                Token::Right => "RIGHT".to_string(),
225                _ => unreachable!(),
226            };
227
228            ExpressionParser::advance(parser);
229
230            // Check if this is a function call
231            if matches!(ExpressionParser::current_token(parser), Token::LeftParen) {
232                debug!(function = %func_name, "Parsing LEFT/RIGHT function call");
233                ExpressionParser::advance(parser); // consume (
234                let (args, _has_distinct) = parser.parse_function_args()?;
235                ExpressionParser::consume(parser, Token::RightParen)?;
236
237                Ok(SqlExpression::FunctionCall {
238                    name: func_name,
239                    args,
240                    distinct: false,
241                })
242            } else {
243                // If not followed by parenthesis, this is likely a JOIN keyword - error
244                Err(format!(
245                    "{} keyword unexpected in expression context",
246                    func_name
247                ))
248            }
249        }
250
251        Token::LeftParen => {
252            debug!("Parsing parenthesized expression or subquery");
253            ExpressionParser::advance(parser); // consume (
254
255            // Check if this is a subquery (starts with SELECT)
256            if matches!(ExpressionParser::current_token(parser), Token::Select) {
257                debug!("Detected subquery - parsing SELECT statement");
258                let subquery = parser.parse_subquery()?;
259                ExpressionParser::consume(parser, Token::RightParen)?;
260                Ok(SqlExpression::ScalarSubquery {
261                    query: Box::new(subquery),
262                })
263            } else {
264                // Parenthesized expression, possibly a tuple for tuple IN:
265                // (a, b) IN (SELECT x, y FROM ...)
266                let first = parser.parse_logical_or()?;
267
268                if matches!(ExpressionParser::current_token(parser), Token::Comma) {
269                    // Collect the remaining tuple elements
270                    let mut exprs = vec![first];
271                    while matches!(ExpressionParser::current_token(parser), Token::Comma) {
272                        ExpressionParser::advance(parser); // consume ,
273                        exprs.push(parser.parse_logical_or()?);
274                    }
275                    ExpressionParser::consume(parser, Token::RightParen)?;
276
277                    // Expect IN or NOT IN immediately after
278                    match ExpressionParser::current_token(parser) {
279                        Token::In => {
280                            ExpressionParser::advance(parser); // consume IN
281                            ExpressionParser::consume(parser, Token::LeftParen)?;
282                            if !matches!(ExpressionParser::current_token(parser), Token::Select) {
283                                return Err("Tuple IN requires a subquery on the right".to_string());
284                            }
285                            let subquery = parser.parse_subquery()?;
286                            ExpressionParser::consume(parser, Token::RightParen)?;
287                            Ok(SqlExpression::InSubqueryTuple {
288                                exprs,
289                                subquery: Box::new(subquery),
290                            })
291                        }
292                        Token::Not => {
293                            ExpressionParser::advance(parser); // consume NOT
294                            if !matches!(ExpressionParser::current_token(parser), Token::In) {
295                                return Err("Expected IN after NOT for tuple".to_string());
296                            }
297                            ExpressionParser::advance(parser); // consume IN
298                            ExpressionParser::consume(parser, Token::LeftParen)?;
299                            if !matches!(ExpressionParser::current_token(parser), Token::Select) {
300                                return Err(
301                                    "Tuple NOT IN requires a subquery on the right".to_string()
302                                );
303                            }
304                            let subquery = parser.parse_subquery()?;
305                            ExpressionParser::consume(parser, Token::RightParen)?;
306                            Ok(SqlExpression::NotInSubqueryTuple {
307                                exprs,
308                                subquery: Box::new(subquery),
309                            })
310                        }
311                        _ => Err(
312                            "A tuple (expr, expr, ...) may only appear as the left side of IN / NOT IN"
313                                .to_string(),
314                        ),
315                    }
316                } else {
317                    // Regular parenthesized expression
318                    debug!("Regular parenthesized expression");
319                    ExpressionParser::consume(parser, Token::RightParen)?;
320                    Ok(first)
321                }
322            }
323        }
324
325        Token::Not => {
326            debug!("Parsing NOT expression");
327            parse_not_expression(parser)
328        }
329
330        Token::Star => {
331            // Handle * as a literal (like in COUNT(*))
332            trace!("Star token as literal");
333            ExpressionParser::advance(parser);
334            Ok(SqlExpression::StringLiteral("*".to_string()))
335        }
336
337        // Handle window-related keywords that can also be column names
338        Token::Row => {
339            trace!("ROW token treated as identifier in expression context");
340            ExpressionParser::advance(parser);
341            Ok(SqlExpression::Column(ColumnRef::unquoted(
342                "row".to_string(),
343            )))
344        }
345
346        Token::Rows => {
347            trace!("ROWS token treated as identifier in expression context");
348            ExpressionParser::advance(parser);
349            Ok(SqlExpression::Column(ColumnRef::unquoted(
350                "rows".to_string(),
351            )))
352        }
353
354        Token::Range => {
355            trace!("RANGE token treated as identifier in expression context");
356            ExpressionParser::advance(parser);
357            Ok(SqlExpression::Column(ColumnRef::unquoted(
358                "range".to_string(),
359            )))
360        }
361
362        Token::Minus => {
363            // Unary minus: -expr is parsed as 0 - expr
364            debug!("Parsing unary minus expression");
365            ExpressionParser::advance(parser);
366            let operand = parse_primary(parser, ctx)?;
367            Ok(SqlExpression::BinaryOp {
368                left: Box::new(SqlExpression::NumberLiteral("0".to_string())),
369                op: "-".to_string(),
370                right: Box::new(operand),
371            })
372        }
373
374        _ => {
375            let err = format!(
376                "Unexpected token in primary expression: {:?}",
377                ExpressionParser::current_token(parser)
378            );
379            debug!(error = %err);
380            Err(err)
381        }
382    };
383
384    trace_parse_exit("parse_primary", &result);
385    result
386}
387
388/// Parse DateTime constructor
389fn parse_datetime_constructor<P>(parser: &mut P) -> Result<SqlExpression, String>
390where
391    P: ParsePrimary + ExpressionParser + ?Sized,
392{
393    ExpressionParser::advance(parser); // consume DateTime
394    ExpressionParser::consume(parser, Token::LeftParen)?;
395
396    // Check if empty parentheses for DateTime() - today's date
397    if matches!(ExpressionParser::current_token(parser), Token::RightParen) {
398        ExpressionParser::advance(parser); // consume )
399        debug!("DateTime() - today's date");
400        return Ok(SqlExpression::DateTimeToday {
401            hour: None,
402            minute: None,
403            second: None,
404        });
405    }
406
407    // Parse year
408    let year = if let Token::NumberLiteral(n) = ExpressionParser::current_token(parser) {
409        n.parse::<i32>().map_err(|_| "Invalid year")?
410    } else {
411        return Err("Expected year in DateTime constructor".to_string());
412    };
413    ExpressionParser::advance(parser);
414    ExpressionParser::consume(parser, Token::Comma)?;
415
416    // Parse month
417    let month = if let Token::NumberLiteral(n) = ExpressionParser::current_token(parser) {
418        n.parse::<u32>().map_err(|_| "Invalid month")?
419    } else {
420        return Err("Expected month in DateTime constructor".to_string());
421    };
422    ExpressionParser::advance(parser);
423    ExpressionParser::consume(parser, Token::Comma)?;
424
425    // Parse day
426    let day = if let Token::NumberLiteral(n) = ExpressionParser::current_token(parser) {
427        n.parse::<u32>().map_err(|_| "Invalid day")?
428    } else {
429        return Err("Expected day in DateTime constructor".to_string());
430    };
431    ExpressionParser::advance(parser);
432
433    // Check for optional time components
434    let mut hour = None;
435    let mut minute = None;
436    let mut second = None;
437
438    if matches!(ExpressionParser::current_token(parser), Token::Comma) {
439        ExpressionParser::advance(parser); // consume comma
440
441        // Parse hour
442        if let Token::NumberLiteral(n) = ExpressionParser::current_token(parser) {
443            hour = Some(n.parse::<u32>().map_err(|_| "Invalid hour")?);
444            ExpressionParser::advance(parser);
445
446            // Check for minute
447            if matches!(ExpressionParser::current_token(parser), Token::Comma) {
448                ExpressionParser::advance(parser); // consume comma
449
450                if let Token::NumberLiteral(n) = ExpressionParser::current_token(parser) {
451                    minute = Some(n.parse::<u32>().map_err(|_| "Invalid minute")?);
452                    ExpressionParser::advance(parser);
453
454                    // Check for second
455                    if matches!(ExpressionParser::current_token(parser), Token::Comma) {
456                        ExpressionParser::advance(parser); // consume comma
457
458                        if let Token::NumberLiteral(n) = ExpressionParser::current_token(parser) {
459                            second = Some(n.parse::<u32>().map_err(|_| "Invalid second")?);
460                            ExpressionParser::advance(parser);
461                        }
462                    }
463                }
464            }
465        }
466    }
467
468    ExpressionParser::consume(parser, Token::RightParen)?;
469
470    debug!(year = year, month = month, day = day, hour = ?hour, minute = ?minute, second = ?second, "DateTime constructor parsed");
471
472    Ok(SqlExpression::DateTimeConstructor {
473        year,
474        month,
475        day,
476        hour,
477        minute,
478        second,
479    })
480}
481
482/// Parse NOT expression
483fn parse_not_expression<P>(parser: &mut P) -> Result<SqlExpression, String>
484where
485    P: ParsePrimary + ExpressionParser + ?Sized,
486{
487    ExpressionParser::advance(parser); // consume NOT
488
489    // Check if this is a NOT IN expression
490    if let Ok(inner_expr) = parser.parse_comparison() {
491        // After parsing the inner expression, check if we're followed by IN
492        if matches!(ExpressionParser::current_token(parser), Token::In) {
493            debug!("NOT IN expression detected");
494            ExpressionParser::advance(parser); // consume IN
495            ExpressionParser::consume(parser, Token::LeftParen)?;
496            let values = parser.parse_expression_list()?;
497            ExpressionParser::consume(parser, Token::RightParen)?;
498
499            Ok(SqlExpression::NotInList {
500                expr: Box::new(inner_expr),
501                values,
502            })
503        } else {
504            // Regular NOT expression
505            debug!("Regular NOT expression");
506            Ok(SqlExpression::Not {
507                expr: Box::new(inner_expr),
508            })
509        }
510    } else {
511        Err("Expected expression after NOT".to_string())
512    }
513}
514
515/// Parse UNNEST expression
516/// Syntax: UNNEST(column_expr, 'delimiter')
517fn parse_unnest<P>(parser: &mut P) -> Result<SqlExpression, String>
518where
519    P: ParsePrimary + ExpressionParser + ?Sized,
520{
521    debug!("parse_unnest: starting");
522    ExpressionParser::advance(parser); // consume UNNEST
523    ExpressionParser::consume(parser, Token::LeftParen)?;
524
525    // Parse the column expression (first argument)
526    let column = parser.parse_logical_or()?;
527    debug!("parse_unnest: parsed column expression");
528
529    // Expect comma
530    ExpressionParser::consume(parser, Token::Comma)?;
531
532    // Parse the delimiter (second argument - must be a string literal)
533    let delimiter = match ExpressionParser::current_token(parser) {
534        Token::StringLiteral(s) => {
535            let delim = s.clone();
536            ExpressionParser::advance(parser);
537            delim
538        }
539        _ => {
540            return Err("UNNEST delimiter must be a string literal".to_string());
541        }
542    };
543
544    debug!(delimiter = %delimiter, "parse_unnest: parsed delimiter");
545
546    ExpressionParser::consume(parser, Token::RightParen)?;
547
548    debug!("parse_unnest: complete");
549    Ok(SqlExpression::Unnest {
550        column: Box::new(column),
551        delimiter,
552    })
553}
554
555/// Trait that parsers must implement to use primary expression parsing
556pub trait ParsePrimary {
557    fn current_token(&self) -> &Token;
558    fn advance(&mut self);
559    fn consume(&mut self, expected: Token) -> Result<(), String>;
560
561    // These methods are called from parse_primary
562    fn parse_case_expression(&mut self) -> Result<SqlExpression, String>;
563    fn parse_function_args(&mut self) -> Result<(Vec<SqlExpression>, bool), String>;
564    fn parse_window_spec(&mut self) -> Result<WindowSpec, String>;
565    fn parse_logical_or(&mut self) -> Result<SqlExpression, String>;
566    fn parse_comparison(&mut self) -> Result<SqlExpression, String>;
567    fn parse_expression_list(&mut self) -> Result<Vec<SqlExpression>, String>;
568
569    // For subquery parsing (without parenthesis balance validation)
570    fn parse_subquery(&mut self) -> Result<crate::sql::parser::ast::SelectStatement, String>;
571}