diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 7b460c62..8bb662d8 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -194,6 +194,70 @@ impl fmt::Display for Array { } } +/// Represents an INTERVAL expression, roughly in the following format: +/// `INTERVAL '' [ [ () ] ] +/// [ TO [ () ] ]`, +/// e.g. `INTERVAL '123:45.67' MINUTE(3) TO SECOND(2)`. +/// +/// The parser does not validate the ``, nor does it ensure +/// that the `` units >= the units in ``, +/// 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, + pub leading_field: Option, + pub leading_precision: Option, + pub last_field: Option, + /// 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, +} + +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 #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -491,25 +555,8 @@ pub enum Expr { ArrayIndex { obj: Box, indexes: Vec }, /// An array expression e.g. `ARRAY[1, 2]` Array(Array), - /// INTERVAL literals, roughly in the following format: - /// `INTERVAL '' [ [ () ] ] - /// [ TO [ () ] ]`, - /// e.g. `INTERVAL '123:45.67' MINUTE(3) TO SECOND(2)`. - /// - /// The parser does not validate the ``, nor does it ensure - /// that the `` units >= the units in ``, - /// so the user will have to reject intervals like `HOUR TO YEAR`. - Interval { - value: Box, - leading_field: Option, - leading_precision: Option, - last_field: Option, - /// 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, - }, + /// An interval expression e.g. `INTERVAL '1' YEAR` + Interval(Interval), /// `MySQL` specific text search function [(1)]. /// /// Syntax: @@ -861,42 +908,8 @@ impl fmt::Display for Expr { } => { write!(f, "{timestamp} AT TIME ZONE '{time_zone}'") } - Expr::Interval { - value, - 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::Interval(interval) => { + write!(f, "{interval}") } Expr::MatchAgainst { columns, @@ -4410,4 +4423,30 @@ mod tests { ]); 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}")); + } } diff --git a/src/parser.rs b/src/parser.rs index 82cbe9d1..7299a5c5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1615,13 +1615,13 @@ impl<'a> Parser<'a> { } }; - Ok(Expr::Interval { + Ok(Expr::Interval(Interval { value: Box::new(value), leading_field, leading_precision, last_field, fractional_seconds_precision: fsec_precision, - }) + })) } /// Parse an operator following an expression diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 16fd623d..5d28118f 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -3395,20 +3395,20 @@ fn parse_interval() { let sql = "SELECT INTERVAL '1-1' YEAR TO MONTH"; let select = verified_only_select(sql); assert_eq!( - &Expr::Interval { + &Expr::Interval(Interval { value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("1-1")))), leading_field: Some(DateTimeField::Year), leading_precision: None, last_field: Some(DateTimeField::Month), fractional_seconds_precision: None, - }, + }), expr_from_projection(only(&select.projection)), ); let sql = "SELECT INTERVAL '01:01.01' MINUTE (5) TO SECOND (5)"; let select = verified_only_select(sql); assert_eq!( - &Expr::Interval { + &Expr::Interval(Interval { value: Box::new(Expr::Value(Value::SingleQuotedString(String::from( "01:01.01" )))), @@ -3416,53 +3416,53 @@ fn parse_interval() { leading_precision: Some(5), last_field: Some(DateTimeField::Second), fractional_seconds_precision: Some(5), - }, + }), expr_from_projection(only(&select.projection)), ); let sql = "SELECT INTERVAL '1' SECOND (5, 4)"; let select = verified_only_select(sql); assert_eq!( - &Expr::Interval { + &Expr::Interval(Interval { value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("1")))), leading_field: Some(DateTimeField::Second), leading_precision: Some(5), last_field: None, fractional_seconds_precision: Some(4), - }, + }), expr_from_projection(only(&select.projection)), ); let sql = "SELECT INTERVAL '10' HOUR"; let select = verified_only_select(sql); assert_eq!( - &Expr::Interval { + &Expr::Interval(Interval { value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("10")))), leading_field: Some(DateTimeField::Hour), leading_precision: None, last_field: None, fractional_seconds_precision: None, - }, + }), expr_from_projection(only(&select.projection)), ); let sql = "SELECT INTERVAL 5 DAY"; let select = verified_only_select(sql); assert_eq!( - &Expr::Interval { + &Expr::Interval(Interval { value: Box::new(Expr::Value(number("5"))), leading_field: Some(DateTimeField::Day), leading_precision: None, last_field: None, fractional_seconds_precision: None, - }, + }), expr_from_projection(only(&select.projection)), ); let sql = "SELECT INTERVAL 1 + 1 DAY"; let select = verified_only_select(sql); assert_eq!( - &Expr::Interval { + &Expr::Interval(Interval { value: Box::new(Expr::BinaryOp { left: Box::new(Expr::Value(number("1"))), op: BinaryOperator::Plus, @@ -3472,27 +3472,27 @@ fn parse_interval() { leading_precision: None, last_field: None, fractional_seconds_precision: None, - }, + }), expr_from_projection(only(&select.projection)), ); let sql = "SELECT INTERVAL '10' HOUR (1)"; let select = verified_only_select(sql); assert_eq!( - &Expr::Interval { + &Expr::Interval(Interval { value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("10")))), leading_field: Some(DateTimeField::Hour), leading_precision: Some(1), last_field: None, fractional_seconds_precision: None, - }, + }), expr_from_projection(only(&select.projection)), ); let sql = "SELECT INTERVAL '1 DAY'"; let select = verified_only_select(sql); assert_eq!( - &Expr::Interval { + &Expr::Interval(Interval { value: Box::new(Expr::Value(Value::SingleQuotedString(String::from( "1 DAY" )))), @@ -3500,7 +3500,7 @@ fn parse_interval() { leading_precision: None, last_field: None, fractional_seconds_precision: None, - }, + }), expr_from_projection(only(&select.projection)), ); @@ -3581,7 +3581,7 @@ fn parse_interval_and_or_xor() { quote_style: None, })), op: BinaryOperator::Plus, - right: Box::new(Expr::Interval { + right: Box::new(Expr::Interval(Interval { value: Box::new(Expr::Value(Value::SingleQuotedString( "5 days".to_string(), ))), @@ -3589,7 +3589,7 @@ fn parse_interval_and_or_xor() { leading_precision: None, last_field: None, fractional_seconds_precision: None, - }), + })), }), }), op: BinaryOperator::And, @@ -3605,7 +3605,7 @@ fn parse_interval_and_or_xor() { quote_style: None, })), op: BinaryOperator::Plus, - right: Box::new(Expr::Interval { + right: Box::new(Expr::Interval(Interval { value: Box::new(Expr::Value(Value::SingleQuotedString( "3 days".to_string(), ))), @@ -3613,7 +3613,7 @@ fn parse_interval_and_or_xor() { leading_precision: None, last_field: None, fractional_seconds_precision: None, - }), + })), }), }), }),