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