Make Expr::Interval its own struct (#872)

* Make Expr::Interval its own struct

* Add test interval display

* Fix cargo fmt
This commit is contained in:
Armin Primadi 2023-05-10 07:42:03 +07:00 committed by GitHub
parent b29b551fa1
commit f15da8772e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 116 additions and 77 deletions

View file

@ -194,6 +194,70 @@ impl fmt::Display for Array {
} }
} }
/// Represents an INTERVAL expression, roughly in the following format:
/// `INTERVAL '<value>' [ <leading_field> [ (<leading_precision>) ] ]
/// [ TO <last_field> [ (<fractional_seconds_precision>) ] ]`,
/// e.g. `INTERVAL '123:45.67' MINUTE(3) TO SECOND(2)`.
///
/// The parser does not validate the `<value>`, nor does it ensure
/// that the `<leading_field>` units >= the units in `<last_field>`,
/// so the user will have to reject intervals like `HOUR TO YEAR`.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct Interval {
pub value: Box<Expr>,
pub leading_field: Option<DateTimeField>,
pub leading_precision: Option<u64>,
pub last_field: Option<DateTimeField>,
/// The seconds precision can be specified in SQL source as
/// `INTERVAL '__' SECOND(_, x)` (in which case the `leading_field`
/// will be `Second` and the `last_field` will be `None`),
/// or as `__ TO SECOND(x)`.
pub fractional_seconds_precision: Option<u64>,
}
impl fmt::Display for Interval {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let value = self.value.as_ref();
match (
self.leading_field,
self.leading_precision,
self.fractional_seconds_precision,
) {
(
Some(DateTimeField::Second),
Some(leading_precision),
Some(fractional_seconds_precision),
) => {
// When the leading field is SECOND, the parser guarantees that
// the last field is None.
assert!(self.last_field.is_none());
write!(
f,
"INTERVAL {value} SECOND ({leading_precision}, {fractional_seconds_precision})"
)
}
_ => {
write!(f, "INTERVAL {value}")?;
if let Some(leading_field) = self.leading_field {
write!(f, " {leading_field}")?;
}
if let Some(leading_precision) = self.leading_precision {
write!(f, " ({leading_precision})")?;
}
if let Some(last_field) = self.last_field {
write!(f, " TO {last_field}")?;
}
if let Some(fractional_seconds_precision) = self.fractional_seconds_precision {
write!(f, " ({fractional_seconds_precision})")?;
}
Ok(())
}
}
}
}
/// JsonOperator /// JsonOperator
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -491,25 +555,8 @@ pub enum Expr {
ArrayIndex { obj: Box<Expr>, indexes: Vec<Expr> }, ArrayIndex { obj: Box<Expr>, indexes: Vec<Expr> },
/// An array expression e.g. `ARRAY[1, 2]` /// An array expression e.g. `ARRAY[1, 2]`
Array(Array), Array(Array),
/// INTERVAL literals, roughly in the following format: /// An interval expression e.g. `INTERVAL '1' YEAR`
/// `INTERVAL '<value>' [ <leading_field> [ (<leading_precision>) ] ] Interval(Interval),
/// [ TO <last_field> [ (<fractional_seconds_precision>) ] ]`,
/// e.g. `INTERVAL '123:45.67' MINUTE(3) TO SECOND(2)`.
///
/// The parser does not validate the `<value>`, nor does it ensure
/// that the `<leading_field>` units >= the units in `<last_field>`,
/// so the user will have to reject intervals like `HOUR TO YEAR`.
Interval {
value: Box<Expr>,
leading_field: Option<DateTimeField>,
leading_precision: Option<u64>,
last_field: Option<DateTimeField>,
/// The seconds precision can be specified in SQL source as
/// `INTERVAL '__' SECOND(_, x)` (in which case the `leading_field`
/// will be `Second` and the `last_field` will be `None`),
/// or as `__ TO SECOND(x)`.
fractional_seconds_precision: Option<u64>,
},
/// `MySQL` specific text search function [(1)]. /// `MySQL` specific text search function [(1)].
/// ///
/// Syntax: /// Syntax:
@ -861,42 +908,8 @@ impl fmt::Display for Expr {
} => { } => {
write!(f, "{timestamp} AT TIME ZONE '{time_zone}'") write!(f, "{timestamp} AT TIME ZONE '{time_zone}'")
} }
Expr::Interval { Expr::Interval(interval) => {
value, write!(f, "{interval}")
leading_field: Some(DateTimeField::Second),
leading_precision: Some(leading_precision),
last_field,
fractional_seconds_precision: Some(fractional_seconds_precision),
} => {
// When the leading field is SECOND, the parser guarantees that
// the last field is None.
assert!(last_field.is_none());
write!(
f,
"INTERVAL {value} SECOND ({leading_precision}, {fractional_seconds_precision})"
)
}
Expr::Interval {
value,
leading_field,
leading_precision,
last_field,
fractional_seconds_precision,
} => {
write!(f, "INTERVAL {value}")?;
if let Some(leading_field) = leading_field {
write!(f, " {leading_field}")?;
}
if let Some(leading_precision) = leading_precision {
write!(f, " ({leading_precision})")?;
}
if let Some(last_field) = last_field {
write!(f, " TO {last_field}")?;
}
if let Some(fractional_seconds_precision) = fractional_seconds_precision {
write!(f, " ({fractional_seconds_precision})")?;
}
Ok(())
} }
Expr::MatchAgainst { Expr::MatchAgainst {
columns, columns,
@ -4410,4 +4423,30 @@ mod tests {
]); ]);
assert_eq!("CUBE (a, (b, c), d)", format!("{cube}")); assert_eq!("CUBE (a, (b, c), d)", format!("{cube}"));
} }
#[test]
fn test_interval_display() {
let interval = Expr::Interval(Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from(
"123:45.67",
)))),
leading_field: Some(DateTimeField::Minute),
leading_precision: Some(10),
last_field: Some(DateTimeField::Second),
fractional_seconds_precision: Some(9),
});
assert_eq!(
"INTERVAL '123:45.67' MINUTE (10) TO SECOND (9)",
format!("{interval}"),
);
let interval = Expr::Interval(Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("5")))),
leading_field: Some(DateTimeField::Second),
leading_precision: Some(1),
last_field: None,
fractional_seconds_precision: Some(3),
});
assert_eq!("INTERVAL '5' SECOND (1, 3)", format!("{interval}"));
}
} }

View file

@ -1615,13 +1615,13 @@ impl<'a> Parser<'a> {
} }
}; };
Ok(Expr::Interval { Ok(Expr::Interval(Interval {
value: Box::new(value), value: Box::new(value),
leading_field, leading_field,
leading_precision, leading_precision,
last_field, last_field,
fractional_seconds_precision: fsec_precision, fractional_seconds_precision: fsec_precision,
}) }))
} }
/// Parse an operator following an expression /// Parse an operator following an expression

View file

@ -3395,20 +3395,20 @@ fn parse_interval() {
let sql = "SELECT INTERVAL '1-1' YEAR TO MONTH"; let sql = "SELECT INTERVAL '1-1' YEAR TO MONTH";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
&Expr::Interval { &Expr::Interval(Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("1-1")))), value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("1-1")))),
leading_field: Some(DateTimeField::Year), leading_field: Some(DateTimeField::Year),
leading_precision: None, leading_precision: None,
last_field: Some(DateTimeField::Month), last_field: Some(DateTimeField::Month),
fractional_seconds_precision: None, fractional_seconds_precision: None,
}, }),
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
); );
let sql = "SELECT INTERVAL '01:01.01' MINUTE (5) TO SECOND (5)"; let sql = "SELECT INTERVAL '01:01.01' MINUTE (5) TO SECOND (5)";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
&Expr::Interval { &Expr::Interval(Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from( value: Box::new(Expr::Value(Value::SingleQuotedString(String::from(
"01:01.01" "01:01.01"
)))), )))),
@ -3416,53 +3416,53 @@ fn parse_interval() {
leading_precision: Some(5), leading_precision: Some(5),
last_field: Some(DateTimeField::Second), last_field: Some(DateTimeField::Second),
fractional_seconds_precision: Some(5), fractional_seconds_precision: Some(5),
}, }),
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
); );
let sql = "SELECT INTERVAL '1' SECOND (5, 4)"; let sql = "SELECT INTERVAL '1' SECOND (5, 4)";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
&Expr::Interval { &Expr::Interval(Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("1")))), value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("1")))),
leading_field: Some(DateTimeField::Second), leading_field: Some(DateTimeField::Second),
leading_precision: Some(5), leading_precision: Some(5),
last_field: None, last_field: None,
fractional_seconds_precision: Some(4), fractional_seconds_precision: Some(4),
}, }),
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
); );
let sql = "SELECT INTERVAL '10' HOUR"; let sql = "SELECT INTERVAL '10' HOUR";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
&Expr::Interval { &Expr::Interval(Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("10")))), value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("10")))),
leading_field: Some(DateTimeField::Hour), leading_field: Some(DateTimeField::Hour),
leading_precision: None, leading_precision: None,
last_field: None, last_field: None,
fractional_seconds_precision: None, fractional_seconds_precision: None,
}, }),
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
); );
let sql = "SELECT INTERVAL 5 DAY"; let sql = "SELECT INTERVAL 5 DAY";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
&Expr::Interval { &Expr::Interval(Interval {
value: Box::new(Expr::Value(number("5"))), value: Box::new(Expr::Value(number("5"))),
leading_field: Some(DateTimeField::Day), leading_field: Some(DateTimeField::Day),
leading_precision: None, leading_precision: None,
last_field: None, last_field: None,
fractional_seconds_precision: None, fractional_seconds_precision: None,
}, }),
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
); );
let sql = "SELECT INTERVAL 1 + 1 DAY"; let sql = "SELECT INTERVAL 1 + 1 DAY";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
&Expr::Interval { &Expr::Interval(Interval {
value: Box::new(Expr::BinaryOp { value: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Value(number("1"))), left: Box::new(Expr::Value(number("1"))),
op: BinaryOperator::Plus, op: BinaryOperator::Plus,
@ -3472,27 +3472,27 @@ fn parse_interval() {
leading_precision: None, leading_precision: None,
last_field: None, last_field: None,
fractional_seconds_precision: None, fractional_seconds_precision: None,
}, }),
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
); );
let sql = "SELECT INTERVAL '10' HOUR (1)"; let sql = "SELECT INTERVAL '10' HOUR (1)";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
&Expr::Interval { &Expr::Interval(Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("10")))), value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("10")))),
leading_field: Some(DateTimeField::Hour), leading_field: Some(DateTimeField::Hour),
leading_precision: Some(1), leading_precision: Some(1),
last_field: None, last_field: None,
fractional_seconds_precision: None, fractional_seconds_precision: None,
}, }),
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
); );
let sql = "SELECT INTERVAL '1 DAY'"; let sql = "SELECT INTERVAL '1 DAY'";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
&Expr::Interval { &Expr::Interval(Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from( value: Box::new(Expr::Value(Value::SingleQuotedString(String::from(
"1 DAY" "1 DAY"
)))), )))),
@ -3500,7 +3500,7 @@ fn parse_interval() {
leading_precision: None, leading_precision: None,
last_field: None, last_field: None,
fractional_seconds_precision: None, fractional_seconds_precision: None,
}, }),
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
); );
@ -3581,7 +3581,7 @@ fn parse_interval_and_or_xor() {
quote_style: None, quote_style: None,
})), })),
op: BinaryOperator::Plus, op: BinaryOperator::Plus,
right: Box::new(Expr::Interval { right: Box::new(Expr::Interval(Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString( value: Box::new(Expr::Value(Value::SingleQuotedString(
"5 days".to_string(), "5 days".to_string(),
))), ))),
@ -3589,7 +3589,7 @@ fn parse_interval_and_or_xor() {
leading_precision: None, leading_precision: None,
last_field: None, last_field: None,
fractional_seconds_precision: None, fractional_seconds_precision: None,
}), })),
}), }),
}), }),
op: BinaryOperator::And, op: BinaryOperator::And,
@ -3605,7 +3605,7 @@ fn parse_interval_and_or_xor() {
quote_style: None, quote_style: None,
})), })),
op: BinaryOperator::Plus, op: BinaryOperator::Plus,
right: Box::new(Expr::Interval { right: Box::new(Expr::Interval(Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString( value: Box::new(Expr::Value(Value::SingleQuotedString(
"3 days".to_string(), "3 days".to_string(),
))), ))),
@ -3613,7 +3613,7 @@ fn parse_interval_and_or_xor() {
leading_precision: None, leading_precision: None,
last_field: None, last_field: None,
fractional_seconds_precision: None, fractional_seconds_precision: None,
}), })),
}), }),
}), }),
}), }),