Skip to content

Commit f15da87

Browse files
authored
Make Expr::Interval its own struct (apache#872)
* Make Expr::Interval its own struct * Add test interval display * Fix cargo fmt
1 parent b29b551 commit f15da87

File tree

3 files changed

+116
-77
lines changed

3 files changed

+116
-77
lines changed

src/ast/mod.rs

Lines changed: 94 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,70 @@ impl fmt::Display for Array {
194194
}
195195
}
196196

197+
/// Represents an INTERVAL expression, roughly in the following format:
198+
/// `INTERVAL '<value>' [ <leading_field> [ (<leading_precision>) ] ]
199+
/// [ TO <last_field> [ (<fractional_seconds_precision>) ] ]`,
200+
/// e.g. `INTERVAL '123:45.67' MINUTE(3) TO SECOND(2)`.
201+
///
202+
/// The parser does not validate the `<value>`, nor does it ensure
203+
/// that the `<leading_field>` units >= the units in `<last_field>`,
204+
/// so the user will have to reject intervals like `HOUR TO YEAR`.
205+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
206+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
207+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
208+
pub struct Interval {
209+
pub value: Box<Expr>,
210+
pub leading_field: Option<DateTimeField>,
211+
pub leading_precision: Option<u64>,
212+
pub last_field: Option<DateTimeField>,
213+
/// The seconds precision can be specified in SQL source as
214+
/// `INTERVAL '__' SECOND(_, x)` (in which case the `leading_field`
215+
/// will be `Second` and the `last_field` will be `None`),
216+
/// or as `__ TO SECOND(x)`.
217+
pub fractional_seconds_precision: Option<u64>,
218+
}
219+
220+
impl fmt::Display for Interval {
221+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
222+
let value = self.value.as_ref();
223+
match (
224+
self.leading_field,
225+
self.leading_precision,
226+
self.fractional_seconds_precision,
227+
) {
228+
(
229+
Some(DateTimeField::Second),
230+
Some(leading_precision),
231+
Some(fractional_seconds_precision),
232+
) => {
233+
// When the leading field is SECOND, the parser guarantees that
234+
// the last field is None.
235+
assert!(self.last_field.is_none());
236+
write!(
237+
f,
238+
"INTERVAL {value} SECOND ({leading_precision}, {fractional_seconds_precision})"
239+
)
240+
}
241+
_ => {
242+
write!(f, "INTERVAL {value}")?;
243+
if let Some(leading_field) = self.leading_field {
244+
write!(f, " {leading_field}")?;
245+
}
246+
if let Some(leading_precision) = self.leading_precision {
247+
write!(f, " ({leading_precision})")?;
248+
}
249+
if let Some(last_field) = self.last_field {
250+
write!(f, " TO {last_field}")?;
251+
}
252+
if let Some(fractional_seconds_precision) = self.fractional_seconds_precision {
253+
write!(f, " ({fractional_seconds_precision})")?;
254+
}
255+
Ok(())
256+
}
257+
}
258+
}
259+
}
260+
197261
/// JsonOperator
198262
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
199263
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -491,25 +555,8 @@ pub enum Expr {
491555
ArrayIndex { obj: Box<Expr>, indexes: Vec<Expr> },
492556
/// An array expression e.g. `ARRAY[1, 2]`
493557
Array(Array),
494-
/// INTERVAL literals, roughly in the following format:
495-
/// `INTERVAL '<value>' [ <leading_field> [ (<leading_precision>) ] ]
496-
/// [ TO <last_field> [ (<fractional_seconds_precision>) ] ]`,
497-
/// e.g. `INTERVAL '123:45.67' MINUTE(3) TO SECOND(2)`.
498-
///
499-
/// The parser does not validate the `<value>`, nor does it ensure
500-
/// that the `<leading_field>` units >= the units in `<last_field>`,
501-
/// so the user will have to reject intervals like `HOUR TO YEAR`.
502-
Interval {
503-
value: Box<Expr>,
504-
leading_field: Option<DateTimeField>,
505-
leading_precision: Option<u64>,
506-
last_field: Option<DateTimeField>,
507-
/// The seconds precision can be specified in SQL source as
508-
/// `INTERVAL '__' SECOND(_, x)` (in which case the `leading_field`
509-
/// will be `Second` and the `last_field` will be `None`),
510-
/// or as `__ TO SECOND(x)`.
511-
fractional_seconds_precision: Option<u64>,
512-
},
558+
/// An interval expression e.g. `INTERVAL '1' YEAR`
559+
Interval(Interval),
513560
/// `MySQL` specific text search function [(1)].
514561
///
515562
/// Syntax:
@@ -861,42 +908,8 @@ impl fmt::Display for Expr {
861908
} => {
862909
write!(f, "{timestamp} AT TIME ZONE '{time_zone}'")
863910
}
864-
Expr::Interval {
865-
value,
866-
leading_field: Some(DateTimeField::Second),
867-
leading_precision: Some(leading_precision),
868-
last_field,
869-
fractional_seconds_precision: Some(fractional_seconds_precision),
870-
} => {
871-
// When the leading field is SECOND, the parser guarantees that
872-
// the last field is None.
873-
assert!(last_field.is_none());
874-
write!(
875-
f,
876-
"INTERVAL {value} SECOND ({leading_precision}, {fractional_seconds_precision})"
877-
)
878-
}
879-
Expr::Interval {
880-
value,
881-
leading_field,
882-
leading_precision,
883-
last_field,
884-
fractional_seconds_precision,
885-
} => {
886-
write!(f, "INTERVAL {value}")?;
887-
if let Some(leading_field) = leading_field {
888-
write!(f, " {leading_field}")?;
889-
}
890-
if let Some(leading_precision) = leading_precision {
891-
write!(f, " ({leading_precision})")?;
892-
}
893-
if let Some(last_field) = last_field {
894-
write!(f, " TO {last_field}")?;
895-
}
896-
if let Some(fractional_seconds_precision) = fractional_seconds_precision {
897-
write!(f, " ({fractional_seconds_precision})")?;
898-
}
899-
Ok(())
911+
Expr::Interval(interval) => {
912+
write!(f, "{interval}")
900913
}
901914
Expr::MatchAgainst {
902915
columns,
@@ -4410,4 +4423,30 @@ mod tests {
44104423
]);
44114424
assert_eq!("CUBE (a, (b, c), d)", format!("{cube}"));
44124425
}
4426+
4427+
#[test]
4428+
fn test_interval_display() {
4429+
let interval = Expr::Interval(Interval {
4430+
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from(
4431+
"123:45.67",
4432+
)))),
4433+
leading_field: Some(DateTimeField::Minute),
4434+
leading_precision: Some(10),
4435+
last_field: Some(DateTimeField::Second),
4436+
fractional_seconds_precision: Some(9),
4437+
});
4438+
assert_eq!(
4439+
"INTERVAL '123:45.67' MINUTE (10) TO SECOND (9)",
4440+
format!("{interval}"),
4441+
);
4442+
4443+
let interval = Expr::Interval(Interval {
4444+
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("5")))),
4445+
leading_field: Some(DateTimeField::Second),
4446+
leading_precision: Some(1),
4447+
last_field: None,
4448+
fractional_seconds_precision: Some(3),
4449+
});
4450+
assert_eq!("INTERVAL '5' SECOND (1, 3)", format!("{interval}"));
4451+
}
44134452
}

src/parser.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,13 +1615,13 @@ impl<'a> Parser<'a> {
16151615
}
16161616
};
16171617

1618-
Ok(Expr::Interval {
1618+
Ok(Expr::Interval(Interval {
16191619
value: Box::new(value),
16201620
leading_field,
16211621
leading_precision,
16221622
last_field,
16231623
fractional_seconds_precision: fsec_precision,
1624-
})
1624+
}))
16251625
}
16261626

16271627
/// Parse an operator following an expression

tests/sqlparser_common.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3395,74 +3395,74 @@ fn parse_interval() {
33953395
let sql = "SELECT INTERVAL '1-1' YEAR TO MONTH";
33963396
let select = verified_only_select(sql);
33973397
assert_eq!(
3398-
&Expr::Interval {
3398+
&Expr::Interval(Interval {
33993399
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("1-1")))),
34003400
leading_field: Some(DateTimeField::Year),
34013401
leading_precision: None,
34023402
last_field: Some(DateTimeField::Month),
34033403
fractional_seconds_precision: None,
3404-
},
3404+
}),
34053405
expr_from_projection(only(&select.projection)),
34063406
);
34073407

34083408
let sql = "SELECT INTERVAL '01:01.01' MINUTE (5) TO SECOND (5)";
34093409
let select = verified_only_select(sql);
34103410
assert_eq!(
3411-
&Expr::Interval {
3411+
&Expr::Interval(Interval {
34123412
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from(
34133413
"01:01.01"
34143414
)))),
34153415
leading_field: Some(DateTimeField::Minute),
34163416
leading_precision: Some(5),
34173417
last_field: Some(DateTimeField::Second),
34183418
fractional_seconds_precision: Some(5),
3419-
},
3419+
}),
34203420
expr_from_projection(only(&select.projection)),
34213421
);
34223422

34233423
let sql = "SELECT INTERVAL '1' SECOND (5, 4)";
34243424
let select = verified_only_select(sql);
34253425
assert_eq!(
3426-
&Expr::Interval {
3426+
&Expr::Interval(Interval {
34273427
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("1")))),
34283428
leading_field: Some(DateTimeField::Second),
34293429
leading_precision: Some(5),
34303430
last_field: None,
34313431
fractional_seconds_precision: Some(4),
3432-
},
3432+
}),
34333433
expr_from_projection(only(&select.projection)),
34343434
);
34353435

34363436
let sql = "SELECT INTERVAL '10' HOUR";
34373437
let select = verified_only_select(sql);
34383438
assert_eq!(
3439-
&Expr::Interval {
3439+
&Expr::Interval(Interval {
34403440
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("10")))),
34413441
leading_field: Some(DateTimeField::Hour),
34423442
leading_precision: None,
34433443
last_field: None,
34443444
fractional_seconds_precision: None,
3445-
},
3445+
}),
34463446
expr_from_projection(only(&select.projection)),
34473447
);
34483448

34493449
let sql = "SELECT INTERVAL 5 DAY";
34503450
let select = verified_only_select(sql);
34513451
assert_eq!(
3452-
&Expr::Interval {
3452+
&Expr::Interval(Interval {
34533453
value: Box::new(Expr::Value(number("5"))),
34543454
leading_field: Some(DateTimeField::Day),
34553455
leading_precision: None,
34563456
last_field: None,
34573457
fractional_seconds_precision: None,
3458-
},
3458+
}),
34593459
expr_from_projection(only(&select.projection)),
34603460
);
34613461

34623462
let sql = "SELECT INTERVAL 1 + 1 DAY";
34633463
let select = verified_only_select(sql);
34643464
assert_eq!(
3465-
&Expr::Interval {
3465+
&Expr::Interval(Interval {
34663466
value: Box::new(Expr::BinaryOp {
34673467
left: Box::new(Expr::Value(number("1"))),
34683468
op: BinaryOperator::Plus,
@@ -3472,35 +3472,35 @@ fn parse_interval() {
34723472
leading_precision: None,
34733473
last_field: None,
34743474
fractional_seconds_precision: None,
3475-
},
3475+
}),
34763476
expr_from_projection(only(&select.projection)),
34773477
);
34783478

34793479
let sql = "SELECT INTERVAL '10' HOUR (1)";
34803480
let select = verified_only_select(sql);
34813481
assert_eq!(
3482-
&Expr::Interval {
3482+
&Expr::Interval(Interval {
34833483
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("10")))),
34843484
leading_field: Some(DateTimeField::Hour),
34853485
leading_precision: Some(1),
34863486
last_field: None,
34873487
fractional_seconds_precision: None,
3488-
},
3488+
}),
34893489
expr_from_projection(only(&select.projection)),
34903490
);
34913491

34923492
let sql = "SELECT INTERVAL '1 DAY'";
34933493
let select = verified_only_select(sql);
34943494
assert_eq!(
3495-
&Expr::Interval {
3495+
&Expr::Interval(Interval {
34963496
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from(
34973497
"1 DAY"
34983498
)))),
34993499
leading_field: None,
35003500
leading_precision: None,
35013501
last_field: None,
35023502
fractional_seconds_precision: None,
3503-
},
3503+
}),
35043504
expr_from_projection(only(&select.projection)),
35053505
);
35063506

@@ -3581,15 +3581,15 @@ fn parse_interval_and_or_xor() {
35813581
quote_style: None,
35823582
})),
35833583
op: BinaryOperator::Plus,
3584-
right: Box::new(Expr::Interval {
3584+
right: Box::new(Expr::Interval(Interval {
35853585
value: Box::new(Expr::Value(Value::SingleQuotedString(
35863586
"5 days".to_string(),
35873587
))),
35883588
leading_field: None,
35893589
leading_precision: None,
35903590
last_field: None,
35913591
fractional_seconds_precision: None,
3592-
}),
3592+
})),
35933593
}),
35943594
}),
35953595
op: BinaryOperator::And,
@@ -3605,15 +3605,15 @@ fn parse_interval_and_or_xor() {
36053605
quote_style: None,
36063606
})),
36073607
op: BinaryOperator::Plus,
3608-
right: Box::new(Expr::Interval {
3608+
right: Box::new(Expr::Interval(Interval {
36093609
value: Box::new(Expr::Value(Value::SingleQuotedString(
36103610
"3 days".to_string(),
36113611
))),
36123612
leading_field: None,
36133613
leading_precision: None,
36143614
last_field: None,
36153615
fractional_seconds_precision: None,
3616-
}),
3616+
})),
36173617
}),
36183618
}),
36193619
}),

0 commit comments

Comments
 (0)