mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-10 01:08:02 +00:00
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:
parent
b29b551fa1
commit
f15da8772e
3 changed files with 116 additions and 77 deletions
149
src/ast/mod.rs
149
src/ast/mod.rs
|
@ -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
|
||||
#[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<Expr>, indexes: Vec<Expr> },
|
||||
/// An array expression e.g. `ARRAY[1, 2]`
|
||||
Array(Array),
|
||||
/// INTERVAL literals, 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`.
|
||||
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>,
|
||||
},
|
||||
/// 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}"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
})),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue