mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-09 00:38: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
|
/// 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}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
}),
|
})),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue