Allow omitting units after INTERVAL (#184)

Alter INTERVAL to support postgres syntax

This patch updates our INTERVAL implementation such that the Postgres
and Redshfit variation of the syntax is supported: namely that 'leading
field' is optional.

Fixes #177.
This commit is contained in:
Max Countryman 2020-06-09 23:32:13 -07:00 committed by GitHub
parent d842f495db
commit 846c52f450
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 44 additions and 18 deletions

View file

@ -37,7 +37,7 @@ pub enum Value {
/// `TIMESTAMP '...'` literals /// `TIMESTAMP '...'` literals
Timestamp(String), Timestamp(String),
/// INTERVAL literals, roughly in the following format: /// INTERVAL literals, roughly in the following format:
/// `INTERVAL '<value>' <leading_field> [ (<leading_precision>) ] /// `INTERVAL '<value>' [ <leading_field> [ (<leading_precision>) ] ]
/// [ TO <last_field> [ (<fractional_seconds_precision>) ] ]`, /// [ TO <last_field> [ (<fractional_seconds_precision>) ] ]`,
/// e.g. `INTERVAL '123:45.67' MINUTE(3) TO SECOND(2)`. /// e.g. `INTERVAL '123:45.67' MINUTE(3) TO SECOND(2)`.
/// ///
@ -46,7 +46,7 @@ pub enum Value {
/// so the user will have to reject intervals like `HOUR TO YEAR`. /// so the user will have to reject intervals like `HOUR TO YEAR`.
Interval { Interval {
value: String, value: String,
leading_field: DateTimeField, leading_field: Option<DateTimeField>,
leading_precision: Option<u64>, leading_precision: Option<u64>,
last_field: Option<DateTimeField>, last_field: Option<DateTimeField>,
/// The seconds precision can be specified in SQL source as /// The seconds precision can be specified in SQL source as
@ -72,7 +72,7 @@ impl fmt::Display for Value {
Value::Timestamp(v) => write!(f, "TIMESTAMP '{}'", escape_single_quote_string(v)), Value::Timestamp(v) => write!(f, "TIMESTAMP '{}'", escape_single_quote_string(v)),
Value::Interval { Value::Interval {
value, value,
leading_field: DateTimeField::Second, leading_field: Some(DateTimeField::Second),
leading_precision: Some(leading_precision), leading_precision: Some(leading_precision),
last_field, last_field,
fractional_seconds_precision: Some(fractional_seconds_precision), fractional_seconds_precision: Some(fractional_seconds_precision),
@ -95,12 +95,10 @@ impl fmt::Display for Value {
last_field, last_field,
fractional_seconds_precision, fractional_seconds_precision,
} => { } => {
write!( write!(f, "INTERVAL '{}'", escape_single_quote_string(value))?;
f, if let Some(leading_field) = leading_field {
"INTERVAL '{}' {}", write!(f, " {}", leading_field)?;
escape_single_quote_string(value), }
leading_field
)?;
if let Some(leading_precision) = leading_precision { if let Some(leading_precision) = leading_precision {
write!(f, " ({})", leading_precision)?; write!(f, " ({})", leading_precision)?;
} }

View file

@ -526,12 +526,21 @@ impl Parser {
// Following the string literal is a qualifier which indicates the units // Following the string literal is a qualifier which indicates the units
// of the duration specified in the string literal. // of the duration specified in the string literal.
// //
// Note that PostgreSQL allows omitting the qualifier, but we currently // Note that PostgreSQL allows omitting the qualifier, so we provide
// require at least the leading field, in accordance with the ANSI spec. // this more general implemenation.
let leading_field = self.parse_date_time_field()?; let leading_field = match self.peek_token() {
Some(Token::Word(kw))
if ["YEAR", "MONTH", "DAY", "HOUR", "MINUTE", "SECOND"]
.iter()
.any(|d| kw.keyword == *d) =>
{
Some(self.parse_date_time_field()?)
}
_ => None,
};
let (leading_precision, last_field, fsec_precision) = let (leading_precision, last_field, fsec_precision) =
if leading_field == DateTimeField::Second { if leading_field == Some(DateTimeField::Second) {
// SQL mandates special syntax for `SECOND TO SECOND` literals. // SQL mandates special syntax for `SECOND TO SECOND` literals.
// Instead of // Instead of
// `SECOND [(<leading precision>)] TO SECOND[(<fractional seconds precision>)]` // `SECOND [(<leading precision>)] TO SECOND[(<fractional seconds precision>)]`

View file

@ -1459,7 +1459,7 @@ fn parse_literal_interval() {
assert_eq!( assert_eq!(
&Expr::Value(Value::Interval { &Expr::Value(Value::Interval {
value: "1-1".into(), value: "1-1".into(),
leading_field: 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,
@ -1472,7 +1472,7 @@ fn parse_literal_interval() {
assert_eq!( assert_eq!(
&Expr::Value(Value::Interval { &Expr::Value(Value::Interval {
value: "01:01.01".into(), value: "01:01.01".into(),
leading_field: DateTimeField::Minute, leading_field: Some(DateTimeField::Minute),
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),
@ -1485,7 +1485,7 @@ fn parse_literal_interval() {
assert_eq!( assert_eq!(
&Expr::Value(Value::Interval { &Expr::Value(Value::Interval {
value: "1".into(), value: "1".into(),
leading_field: 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),
@ -1498,7 +1498,7 @@ fn parse_literal_interval() {
assert_eq!( assert_eq!(
&Expr::Value(Value::Interval { &Expr::Value(Value::Interval {
value: "10".into(), value: "10".into(),
leading_field: 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,
@ -1511,7 +1511,7 @@ fn parse_literal_interval() {
assert_eq!( assert_eq!(
&Expr::Value(Value::Interval { &Expr::Value(Value::Interval {
value: "10".into(), value: "10".into(),
leading_field: 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,
@ -1519,6 +1519,19 @@ fn parse_literal_interval() {
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
); );
let sql = "SELECT INTERVAL '1 DAY'";
let select = verified_only_select(sql);
assert_eq!(
&Expr::Value(Value::Interval {
value: "1 DAY".into(),
leading_field: None,
leading_precision: None,
last_field: None,
fractional_seconds_precision: None,
}),
expr_from_projection(only(&select.projection)),
);
let result = parse_sql_statements("SELECT INTERVAL '1' SECOND TO SECOND"); let result = parse_sql_statements("SELECT INTERVAL '1' SECOND TO SECOND");
assert_eq!( assert_eq!(
ParserError::ParserError("Expected end of statement, found: SECOND".to_string()), ParserError::ParserError("Expected end of statement, found: SECOND".to_string()),
@ -1544,6 +1557,12 @@ fn parse_literal_interval() {
verified_only_select("SELECT INTERVAL '1' HOUR TO MINUTE"); verified_only_select("SELECT INTERVAL '1' HOUR TO MINUTE");
verified_only_select("SELECT INTERVAL '1' HOUR TO SECOND"); verified_only_select("SELECT INTERVAL '1' HOUR TO SECOND");
verified_only_select("SELECT INTERVAL '1' MINUTE TO SECOND"); verified_only_select("SELECT INTERVAL '1' MINUTE TO SECOND");
verified_only_select("SELECT INTERVAL '1 YEAR'");
verified_only_select("SELECT INTERVAL '1 YEAR' AS one_year");
one_statement_parses_to(
"SELECT INTERVAL '1 YEAR' one_year",
"SELECT INTERVAL '1 YEAR' AS one_year",
);
} }
#[test] #[test]