qail_core/parser/grammar/
special_funcs.rs

1//! Special SQL functions with keyword syntax.
2//!
3//! Handles parsing of:
4//! - SUBSTRING(expr FROM pos [FOR len])
5//! - EXTRACT(field FROM date_expr)
6//! - TRIM(LEADING/TRAILING/BOTH 'x' FROM str) (future)
7
8use super::base::parse_identifier;
9use super::expressions::parse_expression;
10use crate::ast::*;
11use nom::{
12    IResult, Parser,
13    bytes::complete::tag_no_case,
14    character::complete::{char, multispace0, multispace1},
15    combinator::opt,
16    sequence::preceded,
17};
18
19/// Parse special SQL functions with keyword syntax
20/// e.g., SUBSTRING(expr FROM pos [FOR len]), EXTRACT(YEAR FROM date), TRIM(LEADING 'x' FROM str)
21pub fn parse_special_function(input: &str) -> IResult<&str, Expr> {
22    // Try SUBSTRING first
23    if let Ok(result) = parse_substring(input) {
24        return Ok(result);
25    }
26    // Try EXTRACT
27    if let Ok(result) = parse_extract(input) {
28        return Ok(result);
29    }
30    // Not a special function
31    Err(nom::Err::Error(nom::error::Error::new(
32        input,
33        nom::error::ErrorKind::Tag,
34    )))
35}
36
37/// Parse SUBSTRING(expr FROM pos [FOR len])
38pub fn parse_substring(input: &str) -> IResult<&str, Expr> {
39    let (input, _) = tag_no_case("substring").parse(input)?;
40    let (input, _) = multispace0(input)?;
41    let (input, _) = char('(').parse(input)?;
42    let (input, _) = multispace0(input)?;
43
44    // First argument: the string expression
45    let (input, string_expr) = parse_expression(input)?;
46    let (input, _) = multispace1(input)?;
47
48    // FROM keyword
49    let (input, _) = tag_no_case("from").parse(input)?;
50    let (input, _) = multispace1(input)?;
51
52    // Position expression
53    let (input, from_expr) = parse_expression(input)?;
54    let (input, _) = multispace0(input)?;
55
56    // Optional FOR length
57    let (input, for_expr) = opt(preceded(
58        (tag_no_case("for"), multispace1),
59        parse_expression,
60    ))
61    .parse(input)?;
62
63    let (input, _) = multispace0(input)?;
64    let (input, _) = char(')').parse(input)?;
65
66    let mut args = vec![
67        (None, Box::new(string_expr)),
68        (Some("FROM".to_string()), Box::new(from_expr)),
69    ];
70    if let Some(len_expr) = for_expr {
71        args.push((Some("FOR".to_string()), Box::new(len_expr)));
72    }
73
74    Ok((
75        input,
76        Expr::SpecialFunction {
77            name: "SUBSTRING".to_string(),
78            args,
79            alias: None,
80        },
81    ))
82}
83
84/// Parse EXTRACT(field FROM date_expr)
85pub fn parse_extract(input: &str) -> IResult<&str, Expr> {
86    let (input, _) = tag_no_case("extract").parse(input)?;
87    let (input, _) = multispace0(input)?;
88    let (input, _) = char('(').parse(input)?;
89    let (input, _) = multispace0(input)?;
90
91    // Field name (YEAR, MONTH, DAY, etc.)
92    let (input, field) = parse_identifier(input)?;
93    let (input, _) = multispace1(input)?;
94
95    // FROM keyword
96    let (input, _) = tag_no_case("from").parse(input)?;
97    let (input, _) = multispace1(input)?;
98
99    // Date expression
100    let (input, date_expr) = parse_expression(input)?;
101    let (input, _) = multispace0(input)?;
102    let (input, _) = char(')').parse(input)?;
103
104    Ok((
105        input,
106        Expr::SpecialFunction {
107            name: "EXTRACT".to_string(),
108            args: vec![
109                (None, Box::new(Expr::Named(field.to_string()))),
110                (Some("FROM".to_string()), Box::new(date_expr)),
111            ],
112            alias: None,
113        },
114    ))
115}