flow/adapter/
parse_expr.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! parse expr like "ts <= now() - interval '5 m'"
16
17use nom::branch::alt;
18use nom::bytes::complete::{tag, tag_no_case};
19use nom::character::complete::{alphanumeric1, digit0, multispace0};
20use nom::combinator::peek;
21use nom::sequence::tuple;
22use nom::IResult;
23
24use crate::repr;
25
26#[test]
27fn test_parse_duration() {
28    let input = "1 h 5 m 42 second";
29    let (remain, ttl) = parse_duration(input).unwrap();
30    assert_eq!(remain, "");
31    assert_eq!(ttl, (3600 + 5 * 60 + 42) * 1000);
32}
33
34#[test]
35fn test_parse_fixed() {
36    let input = "timestamp < now() - INTERVAL '5m 42s'";
37    let (remain, ttl) = parse_fixed(input).unwrap();
38    assert_eq!(remain, "");
39    assert_eq!(ttl, (5 * 60 + 42) * 1000);
40}
41
42pub fn parse_fixed(input: &str) -> IResult<&str, i64> {
43    let (r, _) = tuple((
44        multispace0,
45        tag_no_case("timestamp"),
46        multispace0,
47        tag("<"),
48        multispace0,
49        tag_no_case("now()"),
50        multispace0,
51        tag("-"),
52        multispace0,
53        tag_no_case("interval"),
54        multispace0,
55    ))(input)?;
56    tuple((tag("'"), parse_duration, tag("'")))(r).map(|(r, (_, ttl, _))| (r, ttl))
57}
58
59/// parse duration and return ttl, currently only support time part of psql interval type
60pub fn parse_duration(input: &str) -> IResult<&str, i64> {
61    let mut intervals = vec![];
62    let mut remain = input;
63    while peek(parse_quality)(remain).is_ok() {
64        let (r, number) = parse_quality(remain)?;
65        let (r, unit) = parse_time_unit(r)?;
66        intervals.push((number, unit));
67        remain = r;
68    }
69    let mut total = 0;
70    for (number, unit) in intervals {
71        let number = match unit {
72            TimeUnit::Second => number,
73            TimeUnit::Minute => number * 60,
74            TimeUnit::Hour => number * 60 * 60,
75        };
76        total += number;
77    }
78    total *= 1000;
79    Ok((remain, total))
80}
81
82enum Expr {
83    Col(String),
84    Now,
85    Duration(repr::Duration),
86    Binary {
87        left: Box<Expr>,
88        op: String,
89        right: Box<Expr>,
90    },
91}
92
93fn parse_expr(input: &str) -> IResult<&str, Expr> {
94    parse_expr_bp(input, 0)
95}
96
97/// a simple pratt parser
98fn parse_expr_bp(input: &str, min_bp: u8) -> IResult<&str, Expr> {
99    let (mut input, mut lhs): (&str, Expr) = parse_item(input)?;
100    loop {
101        let (r, op) = parse_op(input)?;
102        let (_, (l_bp, r_bp)) = infix_binding_power(op)?;
103        if l_bp < min_bp {
104            return Ok((input, lhs));
105        }
106        let (r, rhs) = parse_expr_bp(r, r_bp)?;
107        input = r;
108        lhs = Expr::Binary {
109            left: Box::new(lhs),
110            op: op.to_string(),
111            right: Box::new(rhs),
112        };
113    }
114}
115
116fn parse_op(input: &str) -> IResult<&str, &str> {
117    alt((parse_add_sub, parse_cmp))(input)
118}
119
120fn parse_item(input: &str) -> IResult<&str, Expr> {
121    if let Ok((r, name)) = parse_col_name(input) {
122        Ok((r, Expr::Col(name.to_string())))
123    } else if let Ok((r, _now)) = parse_now(input) {
124        Ok((r, Expr::Now))
125    } else if let Ok((_r, _num)) = parse_quality(input) {
126        todo!()
127    } else {
128        todo!()
129    }
130}
131
132fn infix_binding_power(op: &str) -> IResult<&str, (u8, u8)> {
133    let ret = match op {
134        "<" | ">" | "<=" | ">=" => (1, 2),
135        "+" | "-" => (3, 4),
136        _ => {
137            return Err(nom::Err::Error(nom::error::Error::new(
138                op,
139                nom::error::ErrorKind::Fail,
140            )))
141        }
142    };
143    Ok((op, ret))
144}
145
146fn parse_col_name(input: &str) -> IResult<&str, &str> {
147    tuple((multispace0, alphanumeric1, multispace0))(input).map(|(r, (_, name, _))| (r, name))
148}
149
150fn parse_now(input: &str) -> IResult<&str, &str> {
151    tag_no_case("now()")(input)
152}
153
154fn parse_add_sub(input: &str) -> IResult<&str, &str> {
155    tuple((multispace0, alt((tag("+"), tag("-"))), multispace0))(input)
156        .map(|(r, (_, op, _))| (r, op))
157}
158
159fn parse_cmp(input: &str) -> IResult<&str, &str> {
160    tuple((
161        multispace0,
162        alt((tag("<="), tag(">="), tag("<"), tag(">"))),
163        multispace0,
164    ))(input)
165    .map(|(r, (_, op, _))| (r, op))
166}
167
168/// parse a number with optional sign
169fn parse_quality(input: &str) -> IResult<&str, repr::Duration> {
170    tuple((
171        multispace0,
172        alt((tag("+"), tag("-"), tag(""))),
173        digit0,
174        multispace0,
175    ))(input)
176    .map(|(r, (_, sign, name, _))| (r, sign, name))
177    .and_then(|(r, sign, name)| {
178        let num = name.parse::<repr::Duration>().map_err(|_| {
179            nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
180        })?;
181        let num = match sign {
182            "+" => num,
183            "-" => -num,
184            _ => num,
185        };
186        Ok((r, num))
187    })
188}
189
190#[derive(Debug, Clone)]
191enum TimeUnit {
192    Second,
193    Minute,
194    Hour,
195}
196
197#[derive(Debug, Clone)]
198enum DateUnit {
199    Day,
200    Month,
201    Year,
202}
203
204fn parse_time_unit(input: &str) -> IResult<&str, TimeUnit> {
205    fn to_second(input: &str) -> IResult<&str, TimeUnit> {
206        alt((
207            tag_no_case("second"),
208            tag_no_case("seconds"),
209            tag_no_case("S"),
210        ))(input)
211        .map(move |(r, _)| (r, TimeUnit::Second))
212    }
213    fn to_minute(input: &str) -> IResult<&str, TimeUnit> {
214        alt((
215            tag_no_case("minute"),
216            tag_no_case("minutes"),
217            tag_no_case("m"),
218        ))(input)
219        .map(move |(r, _)| (r, TimeUnit::Minute))
220    }
221    fn to_hour(input: &str) -> IResult<&str, TimeUnit> {
222        alt((tag_no_case("hour"), tag_no_case("hours"), tag_no_case("h")))(input)
223            .map(move |(r, _)| (r, TimeUnit::Hour))
224    }
225
226    tuple((
227        multispace0,
228        alt((
229            to_second, to_minute,
230            to_hour, /*
231                    tag_no_case("day"),
232                    tag_no_case("days"),
233                    tag_no_case("d"),
234                    tag_no_case("month"),
235                    tag_no_case("months"),
236                    tag_no_case("m"),
237                    tag_no_case("year"),
238                    tag_no_case("years"),
239                    tag_no_case("y"),
240                    */
241        )),
242        multispace0,
243    ))(input)
244    .map(|(r, (_, unit, _))| (r, unit))
245}