gluesql_core/data/interval/
string.rs

1use {
2    super::{Interval, IntervalError, DAY, HOUR, MINUTE, SECOND},
3    crate::{
4        ast::{Expr, ToSql},
5        parse_sql::parse_interval,
6        result::Result,
7        translate::translate_expr,
8    },
9};
10
11impl Interval {
12    pub fn parse(s: &str) -> Result<Self> {
13        let parsed = parse_interval(s)?;
14
15        match translate_expr(&parsed)? {
16            Expr::Interval {
17                expr,
18                leading_field,
19                last_field,
20            } => {
21                let literal = match expr.as_ref() {
22                    Expr::Literal(literal) => literal.to_sql(),
23                    _ => {
24                        return Err(IntervalError::ParseSupportedOnlyLiteral { expr: *expr }.into())
25                    }
26                };
27
28                Interval::try_from_str(&literal, leading_field, last_field)
29            }
30            _ => Err(IntervalError::Unreachable.into()),
31        }
32    }
33
34    pub fn to_sql_str(&self) -> String {
35        match self {
36            Interval::Month(v) => {
37                let v = *v;
38                let (sign, v) = if v < 0 { ("-", -v) } else { ("", v) };
39
40                let year = v / 12;
41                let month = v % 12;
42
43                match (year, month) {
44                    (_, 0) if year != 0 => format!("'{}{}' YEAR", sign, year),
45                    (0, _) => format!("'{}{}' MONTH", sign, month),
46                    _ => format!("'{}{}-{}' YEAR TO MONTH", sign, year, month),
47                }
48            }
49            Interval::Microsecond(v) => {
50                let v = *v;
51                let (sign, v) = if v < 0 { ("-", -v) } else { ("", v) };
52
53                let day = v / DAY;
54                let hour = (v % DAY) / HOUR;
55                let minute = (v % HOUR) / MINUTE;
56                let second = (v % MINUTE) / SECOND;
57                let microsecond = v % SECOND;
58
59                macro_rules! micro {
60                    () => {
61                        format!("{:06}", microsecond).trim_end_matches('0')
62                    };
63                }
64
65                macro_rules! f {
66                    ($template: literal; $( $value: expr )*; $from_to: literal) => {
67                        format!("'{}{}' {}", sign, format!($template, $( $value ),*), $from_to)
68                    };
69
70                    (DAY $template: literal) => {
71                        f!($template; day; "DAY")
72                    };
73                    (HOUR $template: literal) => {
74                        f!($template; hour; "HOUR")
75                    };
76                    (MINUTE $template: literal) => {
77                        f!($template; minute; "MINUTE")
78                    };
79                    (SECOND $template: literal) => {
80                        f!($template; second; "SECOND")
81                    };
82                    (MICRO $template: literal) => {
83                        f!($template; second micro!(); "SECOND")
84                    };
85
86                    // MINUTE TO ..
87                    (MINUTE TO SECOND $template: literal) => {
88                        f!($template; minute second; "MINUTE TO SECOND")
89                    };
90                    (MINUTE TO MICRO $template: literal) => {
91                        f!($template; minute second micro!(); "MINUTE TO SECOND")
92                    };
93
94                    // HOUR TO ..
95                    (HOUR TO MINUTE $template: literal) => {
96                        f!($template; hour minute; "HOUR TO MINUTE")
97                    };
98                    (HOUR TO SECOND $template: literal) => {
99                        f!($template; hour minute second; "HOUR TO SECOND")
100                    };
101                    (HOUR TO MICRO $template: literal) => {
102                        f!($template; hour minute second micro!(); "HOUR TO SECOND")
103                    };
104
105                    // DAY TO ..
106                    (DAY TO HOUR $template: literal) => {
107                        f!($template; day hour; "DAY TO HOUR")
108                    };
109                    (DAY TO MINUTE $template: literal) => {
110                        f!($template; day hour minute; "DAY TO MINUTE")
111                    };
112                    (DAY TO SECOND $template: literal) => {
113                        f!($template; day hour minute second; "DAY TO SECOND")
114                    };
115                    (DAY TO MICRO $template: literal) => {
116                        f!($template; day hour minute second micro!(); "DAY TO SECOND")
117                    };
118                }
119
120                match (day, hour, minute, second, microsecond) {
121                    (_, 0, 0, 0, 0) if day != 0 => f!(DAY "{}"),
122                    (0, _, 0, 0, 0) if hour != 0 => f!(HOUR "{}"),
123                    (0, 0, _, 0, 0) if minute != 0 => f!(MINUTE "{}"),
124                    (0, 0, 0, _, 0) if second != 0 => f!(SECOND "{}"),
125                    (0, 0, 0, _, _) if microsecond != 0 => f!(MICRO "{}.{}"),
126
127                    // MINUTE TO ..
128                    (0, 0, _, _, 0) => f!(MINUTE TO SECOND "{:02}:{:02}"),
129                    (0, 0, _, _, _) => f!(MINUTE TO MICRO  "{:02}:{:02}.{}"),
130
131                    // HOUR TO ..
132                    (0, _, _, 0, 0) => f!(HOUR TO MINUTE "{:02}:{:02}"),
133                    (0, _, _, _, 0) => f!(HOUR TO SECOND "{:02}:{:02}:{:02}"),
134                    (0, _, _, _, _) => f!(HOUR TO MICRO  "{:02}:{:02}:{:02}.{}"),
135
136                    // DAY TO ..
137                    (_, _, 0, 0, 0) => f!(DAY TO HOUR   "{} {}"),
138                    (_, _, _, 0, 0) => f!(DAY TO MINUTE "{} {:02}:{:02}"),
139                    (_, _, _, _, 0) => f!(DAY TO SECOND "{} {:02}:{:02}:{:02}"),
140                    (_, _, _, _, _) => f!(DAY TO MICRO  "{} {:02}:{:02}:{:02}.{}"),
141                }
142            }
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::Interval;
150
151    #[test]
152    fn parse() {
153        macro_rules! test {
154            ($( $value: literal $duration: ident ),* => $result: literal $from_to: tt) => {
155                let interval = interval!($( $value $duration ),*);
156                let interval_str = format!("'{}' {}", $result, stringify!($from_to));
157
158                let expected = Interval::parse(interval_str.as_str());
159                assert_eq!(Ok(interval), expected);
160                assert_eq!(interval.to_sql_str(), interval_str);
161            };
162            ($( $value: literal $duration: ident ),* => $result: literal $from: tt TO $to: tt) => {
163                let interval = interval!($( $value $duration ),*);
164                let interval_str = format!(
165                    "'{}' {} TO {}",
166                    $result,
167                    stringify!($from),
168                    stringify!($to),
169                );
170
171                let expected = Interval::parse(interval_str.as_str());
172                assert_eq!(Ok(interval), expected);
173                assert_eq!(interval.to_sql_str(), interval_str);
174            };
175        }
176
177        macro_rules! interval {
178            ($value: literal $duration:ident) => {
179                Interval::$duration($value)
180            };
181            ($interval: expr ; $value: literal $duration:ident) => {
182                $interval.add(&Interval::$duration($value)).unwrap()
183            };
184            ($v0: literal $d0: ident, $( $v1: literal $d1: ident ),*) => {
185                interval!(
186                    Interval::$d0($v0) ;
187                    $( $v1 $d1 ),*
188                )
189            };
190            ($interval: expr ; $v0: literal $d0: ident, $( $v1: literal $d1: ident ),*) => {
191                interval!(
192                    $interval.add(&Interval::$d0($v0)).unwrap() ;
193                    $( $v1 $d1 ),*
194                )
195            };
196        }
197
198        // YEAR and MONTH
199        test!(33 years            => "33" YEAR);
200        test!(-33 years           => "-33" YEAR);
201        test!(14 months           => "1-2" YEAR TO MONTH);
202        test!(-33 months          => "-2-9" YEAR TO MONTH);
203        test!(12 months           => "1" YEAR);
204        test!(11 months           => "11" MONTH);
205        test!(-3 months           => "-3" MONTH);
206        test!(33 years, 11 months => "33-11" YEAR TO MONTH);
207        test!(1 years, 100 months => "9-4" YEAR TO MONTH);
208
209        // DAY, HOUR, MINUTE and SECOND
210        test!(102 days          => "102" DAY);
211        test!(-20 days          => "-20" DAY);
212        test!(30 hours          => "1 6" DAY TO HOUR);
213        test!(24 hours          => "1" DAY);
214        test!(3 hours           => "3" HOUR);
215        test!(-5 hours          => "-5" HOUR);
216        test!(350 minutes       => "05:50" HOUR TO MINUTE);
217        test!(600 minutes       => "10" HOUR);
218        test!(59 minutes        => "59" MINUTE);
219        test!(3 minutes         => "3" MINUTE);
220        test!(-9 minutes        => "-9" MINUTE);
221        test!(7298 seconds      => "02:01:38" HOUR TO SECOND);
222        test!(98 seconds        => "01:38" MINUTE TO SECOND);
223        test!(120 seconds       => "2" MINUTE);
224        test!(30 seconds        => "30" SECOND);
225        test!(3 seconds         => "3" SECOND);
226        test!(-37 seconds       => "-37" SECOND);
227        test!(1234 milliseconds => "1.234" SECOND);
228        test!(-234 milliseconds  => "-0.234" SECOND);
229        test!(1234 microseconds => "0.001234" SECOND);
230
231        // MINUTE TO ..
232        test!(11 minutes, 30 seconds                     => "11:30" MINUTE TO SECOND);
233        test!(-11 minutes, -39 seconds                   => "-11:39" MINUTE TO SECOND);
234        test!(11 minutes, 36540 microseconds             => "11:00.03654" MINUTE TO SECOND);
235        test!(-30 minutes, -9876 microseconds            => "-30:00.009876" MINUTE TO SECOND);
236        test!(11 minutes, 30 seconds, 36540 microseconds => "11:30.03654" MINUTE TO SECOND);
237
238        // HOUR TO ..
239        test!(11 hours, 50 minutes                   => "11:50" HOUR TO MINUTE);
240        test!(-20 hours, -39 minutes                 => "-20:39" HOUR TO MINUTE);
241        test!(23 hours, 59 seconds                   => "23:00:59" HOUR TO SECOND);
242        test!(-23 hours, -59 seconds                 => "-23:00:59" HOUR TO SECOND);
243        test!(7 hours, 30040 microseconds            => "07:00:00.03004" HOUR TO SECOND);
244        test!(-7 hours, -30040 microseconds          => "-07:00:00.03004" HOUR TO SECOND);
245        test!(23 hours, 12 minutes, 59 seconds       => "23:12:59" HOUR TO SECOND);
246        test!(23 hours, 12 minutes, 300 microseconds => "23:12:00.0003" HOUR TO SECOND);
247        test!(
248            23 hours, 12 minutes, 9 seconds, 300 microseconds =>
249            "23:12:09.0003" HOUR TO SECOND
250        );
251
252        // DAY TO ..
253        test!(1 days, 1 hours                    => "1 1" DAY TO HOUR);
254        test!(-1 days, -7 hours                  => "-1 7" DAY TO HOUR);
255        test!(30 days, 5 minutes                 => "30 00:05" DAY TO MINUTE);
256        test!(-1 days, -130 minutes              => "-1 02:10" DAY TO MINUTE);
257        test!(30 days, 100 seconds               => "30 00:01:40" DAY TO SECOND);
258        test!(-5 days, -3800 seconds             => "-5 01:03:20" DAY TO SECOND);
259        test!(1 days, 1 microseconds             => "1 00:00:00.000001" DAY TO SECOND);
260        test!(-1 days, -1 microseconds           => "-1 00:00:00.000001" DAY TO SECOND);
261        test!(100 days, 2 hours, 3 minutes       => "100 02:03" DAY TO MINUTE);
262        test!(2 days, 20 hours, 3 seconds        => "2 20:00:03" DAY TO SECOND);
263        test!(2 days, 20 hours, 234 milliseconds => "2 20:00:00.234" DAY TO SECOND);
264        test!(
265            30 days, 30 hours, 100 seconds, 3 microseconds =>
266            "31 06:01:40.000003" DAY TO SECOND
267        );
268    }
269}