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(String),
/// 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>) ] ]`,
/// 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`.
Interval {
value: String,
leading_field: DateTimeField,
leading_field: Option<DateTimeField>,
leading_precision: Option<u64>,
last_field: Option<DateTimeField>,
/// 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::Interval {
value,
leading_field: DateTimeField::Second,
leading_field: Some(DateTimeField::Second),
leading_precision: Some(leading_precision),
last_field,
fractional_seconds_precision: Some(fractional_seconds_precision),
@ -95,12 +95,10 @@ impl fmt::Display for Value {
last_field,
fractional_seconds_precision,
} => {
write!(
f,
"INTERVAL '{}' {}",
escape_single_quote_string(value),
leading_field
)?;
write!(f, "INTERVAL '{}'", escape_single_quote_string(value))?;
if let Some(leading_field) = leading_field {
write!(f, " {}", leading_field)?;
}
if let Some(leading_precision) = 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
// of the duration specified in the string literal.
//
// Note that PostgreSQL allows omitting the qualifier, but we currently
// require at least the leading field, in accordance with the ANSI spec.
let leading_field = self.parse_date_time_field()?;
// Note that PostgreSQL allows omitting the qualifier, so we provide
// this more general implemenation.
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) =
if leading_field == DateTimeField::Second {
if leading_field == Some(DateTimeField::Second) {
// SQL mandates special syntax for `SECOND TO SECOND` literals.
// Instead of
// `SECOND [(<leading precision>)] TO SECOND[(<fractional seconds precision>)]`

View file

@ -1459,7 +1459,7 @@ fn parse_literal_interval() {
assert_eq!(
&Expr::Value(Value::Interval {
value: "1-1".into(),
leading_field: DateTimeField::Year,
leading_field: Some(DateTimeField::Year),
leading_precision: None,
last_field: Some(DateTimeField::Month),
fractional_seconds_precision: None,
@ -1472,7 +1472,7 @@ fn parse_literal_interval() {
assert_eq!(
&Expr::Value(Value::Interval {
value: "01:01.01".into(),
leading_field: DateTimeField::Minute,
leading_field: Some(DateTimeField::Minute),
leading_precision: Some(5),
last_field: Some(DateTimeField::Second),
fractional_seconds_precision: Some(5),
@ -1485,7 +1485,7 @@ fn parse_literal_interval() {
assert_eq!(
&Expr::Value(Value::Interval {
value: "1".into(),
leading_field: DateTimeField::Second,
leading_field: Some(DateTimeField::Second),
leading_precision: Some(5),
last_field: None,
fractional_seconds_precision: Some(4),
@ -1498,7 +1498,7 @@ fn parse_literal_interval() {
assert_eq!(
&Expr::Value(Value::Interval {
value: "10".into(),
leading_field: DateTimeField::Hour,
leading_field: Some(DateTimeField::Hour),
leading_precision: None,
last_field: None,
fractional_seconds_precision: None,
@ -1511,7 +1511,7 @@ fn parse_literal_interval() {
assert_eq!(
&Expr::Value(Value::Interval {
value: "10".into(),
leading_field: DateTimeField::Hour,
leading_field: Some(DateTimeField::Hour),
leading_precision: Some(1),
last_field: None,
fractional_seconds_precision: None,
@ -1519,6 +1519,19 @@ fn parse_literal_interval() {
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");
assert_eq!(
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 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]