Skip to content

Commit 846c52f

Browse files
Allow omitting units after INTERVAL (apache#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 apache#177.
1 parent d842f49 commit 846c52f

File tree

3 files changed

+44
-18
lines changed

3 files changed

+44
-18
lines changed

src/ast/value.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub enum Value {
3737
/// `TIMESTAMP '...'` literals
3838
Timestamp(String),
3939
/// INTERVAL literals, roughly in the following format:
40-
/// `INTERVAL '<value>' <leading_field> [ (<leading_precision>) ]
40+
/// `INTERVAL '<value>' [ <leading_field> [ (<leading_precision>) ] ]
4141
/// [ TO <last_field> [ (<fractional_seconds_precision>) ] ]`,
4242
/// e.g. `INTERVAL '123:45.67' MINUTE(3) TO SECOND(2)`.
4343
///
@@ -46,7 +46,7 @@ pub enum Value {
4646
/// so the user will have to reject intervals like `HOUR TO YEAR`.
4747
Interval {
4848
value: String,
49-
leading_field: DateTimeField,
49+
leading_field: Option<DateTimeField>,
5050
leading_precision: Option<u64>,
5151
last_field: Option<DateTimeField>,
5252
/// The seconds precision can be specified in SQL source as
@@ -72,7 +72,7 @@ impl fmt::Display for Value {
7272
Value::Timestamp(v) => write!(f, "TIMESTAMP '{}'", escape_single_quote_string(v)),
7373
Value::Interval {
7474
value,
75-
leading_field: DateTimeField::Second,
75+
leading_field: Some(DateTimeField::Second),
7676
leading_precision: Some(leading_precision),
7777
last_field,
7878
fractional_seconds_precision: Some(fractional_seconds_precision),
@@ -95,12 +95,10 @@ impl fmt::Display for Value {
9595
last_field,
9696
fractional_seconds_precision,
9797
} => {
98-
write!(
99-
f,
100-
"INTERVAL '{}' {}",
101-
escape_single_quote_string(value),
102-
leading_field
103-
)?;
98+
write!(f, "INTERVAL '{}'", escape_single_quote_string(value))?;
99+
if let Some(leading_field) = leading_field {
100+
write!(f, " {}", leading_field)?;
101+
}
104102
if let Some(leading_precision) = leading_precision {
105103
write!(f, " ({})", leading_precision)?;
106104
}

src/parser.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -526,12 +526,21 @@ impl Parser {
526526
// Following the string literal is a qualifier which indicates the units
527527
// of the duration specified in the string literal.
528528
//
529-
// Note that PostgreSQL allows omitting the qualifier, but we currently
530-
// require at least the leading field, in accordance with the ANSI spec.
531-
let leading_field = self.parse_date_time_field()?;
529+
// Note that PostgreSQL allows omitting the qualifier, so we provide
530+
// this more general implemenation.
531+
let leading_field = match self.peek_token() {
532+
Some(Token::Word(kw))
533+
if ["YEAR", "MONTH", "DAY", "HOUR", "MINUTE", "SECOND"]
534+
.iter()
535+
.any(|d| kw.keyword == *d) =>
536+
{
537+
Some(self.parse_date_time_field()?)
538+
}
539+
_ => None,
540+
};
532541

533542
let (leading_precision, last_field, fsec_precision) =
534-
if leading_field == DateTimeField::Second {
543+
if leading_field == Some(DateTimeField::Second) {
535544
// SQL mandates special syntax for `SECOND TO SECOND` literals.
536545
// Instead of
537546
// `SECOND [(<leading precision>)] TO SECOND[(<fractional seconds precision>)]`

tests/sqlparser_common.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,7 +1459,7 @@ fn parse_literal_interval() {
14591459
assert_eq!(
14601460
&Expr::Value(Value::Interval {
14611461
value: "1-1".into(),
1462-
leading_field: DateTimeField::Year,
1462+
leading_field: Some(DateTimeField::Year),
14631463
leading_precision: None,
14641464
last_field: Some(DateTimeField::Month),
14651465
fractional_seconds_precision: None,
@@ -1472,7 +1472,7 @@ fn parse_literal_interval() {
14721472
assert_eq!(
14731473
&Expr::Value(Value::Interval {
14741474
value: "01:01.01".into(),
1475-
leading_field: DateTimeField::Minute,
1475+
leading_field: Some(DateTimeField::Minute),
14761476
leading_precision: Some(5),
14771477
last_field: Some(DateTimeField::Second),
14781478
fractional_seconds_precision: Some(5),
@@ -1485,7 +1485,7 @@ fn parse_literal_interval() {
14851485
assert_eq!(
14861486
&Expr::Value(Value::Interval {
14871487
value: "1".into(),
1488-
leading_field: DateTimeField::Second,
1488+
leading_field: Some(DateTimeField::Second),
14891489
leading_precision: Some(5),
14901490
last_field: None,
14911491
fractional_seconds_precision: Some(4),
@@ -1498,7 +1498,7 @@ fn parse_literal_interval() {
14981498
assert_eq!(
14991499
&Expr::Value(Value::Interval {
15001500
value: "10".into(),
1501-
leading_field: DateTimeField::Hour,
1501+
leading_field: Some(DateTimeField::Hour),
15021502
leading_precision: None,
15031503
last_field: None,
15041504
fractional_seconds_precision: None,
@@ -1511,14 +1511,27 @@ fn parse_literal_interval() {
15111511
assert_eq!(
15121512
&Expr::Value(Value::Interval {
15131513
value: "10".into(),
1514-
leading_field: DateTimeField::Hour,
1514+
leading_field: Some(DateTimeField::Hour),
15151515
leading_precision: Some(1),
15161516
last_field: None,
15171517
fractional_seconds_precision: None,
15181518
}),
15191519
expr_from_projection(only(&select.projection)),
15201520
);
15211521

1522+
let sql = "SELECT INTERVAL '1 DAY'";
1523+
let select = verified_only_select(sql);
1524+
assert_eq!(
1525+
&Expr::Value(Value::Interval {
1526+
value: "1 DAY".into(),
1527+
leading_field: None,
1528+
leading_precision: None,
1529+
last_field: None,
1530+
fractional_seconds_precision: None,
1531+
}),
1532+
expr_from_projection(only(&select.projection)),
1533+
);
1534+
15221535
let result = parse_sql_statements("SELECT INTERVAL '1' SECOND TO SECOND");
15231536
assert_eq!(
15241537
ParserError::ParserError("Expected end of statement, found: SECOND".to_string()),
@@ -1544,6 +1557,12 @@ fn parse_literal_interval() {
15441557
verified_only_select("SELECT INTERVAL '1' HOUR TO MINUTE");
15451558
verified_only_select("SELECT INTERVAL '1' HOUR TO SECOND");
15461559
verified_only_select("SELECT INTERVAL '1' MINUTE TO SECOND");
1560+
verified_only_select("SELECT INTERVAL '1 YEAR'");
1561+
verified_only_select("SELECT INTERVAL '1 YEAR' AS one_year");
1562+
one_statement_parses_to(
1563+
"SELECT INTERVAL '1 YEAR' one_year",
1564+
"SELECT INTERVAL '1 YEAR' AS one_year",
1565+
);
15471566
}
15481567

15491568
#[test]

0 commit comments

Comments
 (0)