Skip to content

Commit f70d90a

Browse files
committed
refactor "INTERVAL" parsing
1 parent 19e694a commit f70d90a

File tree

9 files changed

+207
-130
lines changed

9 files changed

+207
-130
lines changed

src/dialect/duckdb.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,8 @@ impl Dialect for DuckDbDialect {
5555
fn support_map_literal_syntax(&self) -> bool {
5656
true
5757
}
58+
59+
fn require_interval_units(&self) -> bool {
60+
false
61+
}
5862
}

src/dialect/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,23 @@ pub trait Dialect: Debug + Any {
499499
fn describe_requires_table_keyword(&self) -> bool {
500500
false
501501
}
502+
503+
/// Whether or not units are required with interval expressions.
504+
///
505+
/// When `true`:
506+
/// * `INTERVAL '1' DAY` is VALID
507+
/// * `INTERVAL 1 + 1 DAY` is VALID
508+
/// * `INTERVAL '1' + '1' DAY` is VALID
509+
/// * `INTERVAL '1'` is INVALID
510+
///
511+
/// When `false`:
512+
/// * `INTERVAL '1' DAY` is VALID
513+
/// * `INTERVAL '1'` is VALID
514+
/// * `INTERVAL '1 second'` is VALID
515+
/// * `INTERVAL 1 + 1 DAY` is INVALID
516+
fn require_interval_units(&self) -> bool {
517+
true
518+
}
502519
}
503520

504521
/// This represents the operators for which precedence must be defined

src/dialect/postgresql.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ impl Dialect for PostgreSqlDialect {
154154
Precedence::Or => OR_PREC,
155155
}
156156
}
157+
158+
fn require_interval_units(&self) -> bool {
159+
false
160+
}
157161
}
158162

159163
pub fn parse_comment(parser: &mut Parser) -> Result<Statement, ParserError> {

src/dialect/redshift.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,8 @@ impl Dialect for RedshiftSqlDialect {
6363
fn supports_connect_by(&self) -> bool {
6464
true
6565
}
66+
67+
fn require_interval_units(&self) -> bool {
68+
false
69+
}
6670
}

src/dialect/snowflake.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ impl Dialect for SnowflakeDialect {
158158
fn describe_requires_table_keyword(&self) -> bool {
159159
true
160160
}
161+
162+
fn require_interval_units(&self) -> bool {
163+
false
164+
}
161165
}
162166

163167
/// Parse snowflake create table statement.

src/parser/mod.rs

Lines changed: 64 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,6 @@ macro_rules! parser_err {
5656
};
5757
}
5858

59-
// Returns a successful result if the optional expression is some
60-
macro_rules! return_ok_if_some {
61-
($e:expr) => {{
62-
if let Some(v) = $e {
63-
return Ok(v);
64-
}
65-
}};
66-
}
67-
6859
#[cfg(feature = "std")]
6960
/// Implementation [`RecursionCounter`] if std is available
7061
mod recursion {
@@ -896,33 +887,61 @@ impl<'a> Parser<'a> {
896887
Ok(expr)
897888
}
898889

899-
pub fn parse_interval_expr(&mut self) -> Result<Expr, ParserError> {
900-
let precedence = self.dialect.prec_unknown();
890+
fn parse_interval_expr(&mut self) -> Result<(Expr, bool), ParserError> {
901891
let mut expr = self.parse_prefix()?;
902892

903-
loop {
904-
let next_precedence = self.get_next_interval_precedence()?;
905-
906-
if precedence >= next_precedence {
907-
break;
908-
}
909-
910-
expr = self.parse_infix(expr, next_precedence)?;
911-
}
912-
913-
Ok(expr)
914-
}
915-
916-
/// Get the precedence of the next token, with AND, OR, and XOR.
917-
pub fn get_next_interval_precedence(&self) -> Result<u8, ParserError> {
918-
let token = self.peek_token();
919-
920-
match token.token {
921-
Token::Word(w) if w.keyword == Keyword::AND => Ok(self.dialect.prec_unknown()),
922-
Token::Word(w) if w.keyword == Keyword::OR => Ok(self.dialect.prec_unknown()),
923-
Token::Word(w) if w.keyword == Keyword::XOR => Ok(self.dialect.prec_unknown()),
924-
_ => self.get_next_precedence(),
925-
}
893+
if self.dialect.require_interval_units() {
894+
// if require_interval_units is true, continue parsing expressions until a unit is foudn
895+
loop {
896+
if self.next_token_is_unit() {
897+
return Ok((expr, true));
898+
} else {
899+
expr = self.parse_infix(expr, self.dialect.prec_unknown())?;
900+
}
901+
}
902+
} else {
903+
// otherwise, check if the next token is a unit, but don't iterate
904+
Ok((expr, self.next_token_is_unit()))
905+
}
906+
}
907+
908+
pub fn next_token_is_unit(&mut self) -> bool {
909+
let token_loc = self.peek_token();
910+
if let Token::Word(word) = token_loc.token {
911+
if matches!(
912+
word.keyword,
913+
Keyword::YEAR
914+
| Keyword::MONTH
915+
| Keyword::WEEK
916+
| Keyword::DAY
917+
| Keyword::HOUR
918+
| Keyword::MINUTE
919+
| Keyword::SECOND
920+
| Keyword::CENTURY
921+
| Keyword::DECADE
922+
| Keyword::DOW
923+
| Keyword::DOY
924+
| Keyword::EPOCH
925+
| Keyword::ISODOW
926+
| Keyword::ISOYEAR
927+
| Keyword::JULIAN
928+
| Keyword::MICROSECOND
929+
| Keyword::MICROSECONDS
930+
| Keyword::MILLENIUM
931+
| Keyword::MILLENNIUM
932+
| Keyword::MILLISECOND
933+
| Keyword::MILLISECONDS
934+
| Keyword::NANOSECOND
935+
| Keyword::NANOSECONDS
936+
| Keyword::QUARTER
937+
| Keyword::TIMEZONE
938+
| Keyword::TIMEZONE_HOUR
939+
| Keyword::TIMEZONE_MINUTE
940+
) {
941+
return true;
942+
}
943+
}
944+
return false;
926945
}
927946

928947
pub fn parse_assert(&mut self) -> Result<Statement, ParserError> {
@@ -972,7 +991,7 @@ impl<'a> Parser<'a> {
972991
// name is not followed by a string literal, but in fact in PostgreSQL it is a valid
973992
// expression that should parse as the column name "date".
974993
let loc = self.peek_token().location;
975-
return_ok_if_some!(self.maybe_parse(|parser| {
994+
let opt_expr = self.maybe_parse(|parser| {
976995
match parser.parse_data_type()? {
977996
DataType::Interval => parser.parse_interval(),
978997
// PostgreSQL allows almost any identifier to be used as custom data type name,
@@ -988,7 +1007,11 @@ impl<'a> Parser<'a> {
9881007
value: parser.parse_literal_string()?,
9891008
}),
9901009
}
991-
}));
1010+
});
1011+
1012+
if let Some(expr) = opt_expr {
1013+
return Ok(expr);
1014+
}
9921015

9931016
let next_token = self.next_token();
9941017
let expr = match next_token.token {
@@ -2079,52 +2102,17 @@ impl<'a> Parser<'a> {
20792102
// don't currently try to parse it. (The sign can instead be included
20802103
// inside the value string.)
20812104

2082-
// The first token in an interval is a string literal which specifies
2083-
// the duration of the interval.
2084-
let value = self.parse_interval_expr()?;
2105+
let (value, has_units) = self.parse_interval_expr()?;
20852106

20862107
// Following the string literal is a qualifier which indicates the units
20872108
// of the duration specified in the string literal.
20882109
//
20892110
// Note that PostgreSQL allows omitting the qualifier, so we provide
20902111
// this more general implementation.
2091-
let leading_field = match self.peek_token().token {
2092-
Token::Word(kw)
2093-
if [
2094-
Keyword::YEAR,
2095-
Keyword::MONTH,
2096-
Keyword::WEEK,
2097-
Keyword::DAY,
2098-
Keyword::HOUR,
2099-
Keyword::MINUTE,
2100-
Keyword::SECOND,
2101-
Keyword::CENTURY,
2102-
Keyword::DECADE,
2103-
Keyword::DOW,
2104-
Keyword::DOY,
2105-
Keyword::EPOCH,
2106-
Keyword::ISODOW,
2107-
Keyword::ISOYEAR,
2108-
Keyword::JULIAN,
2109-
Keyword::MICROSECOND,
2110-
Keyword::MICROSECONDS,
2111-
Keyword::MILLENIUM,
2112-
Keyword::MILLENNIUM,
2113-
Keyword::MILLISECOND,
2114-
Keyword::MILLISECONDS,
2115-
Keyword::NANOSECOND,
2116-
Keyword::NANOSECONDS,
2117-
Keyword::QUARTER,
2118-
Keyword::TIMEZONE,
2119-
Keyword::TIMEZONE_HOUR,
2120-
Keyword::TIMEZONE_MINUTE,
2121-
]
2122-
.iter()
2123-
.any(|d| kw.keyword == *d) =>
2124-
{
2125-
Some(self.parse_date_time_field()?)
2126-
}
2127-
_ => None,
2112+
let leading_field = if has_units {
2113+
Some(self.parse_date_time_field()?)
2114+
} else {
2115+
None
21282116
};
21292117

21302118
let (leading_precision, last_field, fsec_precision) =

tests/sqlparser_bigquery.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -830,16 +830,14 @@ fn parse_typed_struct_syntax_bigquery() {
830830
expr_from_projection(&select.projection[3])
831831
);
832832

833-
let sql = r#"SELECT STRUCT<INTERVAL>(INTERVAL '1-2 3 4:5:6.789999'), STRUCT<JSON>(JSON '{"class" : {"students" : [{"name" : "Jane"}]}}')"#;
833+
let sql = r#"SELECT STRUCT<INTERVAL>(INTERVAL '1' DAY), STRUCT<JSON>(JSON '{"class" : {"students" : [{"name" : "Jane"}]}}')"#;
834834
let select = bigquery().verified_only_select(sql);
835835
assert_eq!(2, select.projection.len());
836836
assert_eq!(
837837
&Expr::Struct {
838838
values: vec![Expr::Interval(ast::Interval {
839-
value: Box::new(Expr::Value(Value::SingleQuotedString(
840-
"1-2 3 4:5:6.789999".to_string()
841-
))),
842-
leading_field: None,
839+
value: Box::new(Expr::Value(Value::SingleQuotedString("1".to_string()))),
840+
leading_field: Some(DateTimeField::Day),
843841
leading_precision: None,
844842
last_field: None,
845843
fractional_seconds_precision: None
@@ -1141,16 +1139,14 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
11411139
expr_from_projection(&select.projection[3])
11421140
);
11431141

1144-
let sql = r#"SELECT STRUCT<INTERVAL>(INTERVAL '1-2 3 4:5:6.789999'), STRUCT<JSON>(JSON '{"class" : {"students" : [{"name" : "Jane"}]}}')"#;
1142+
let sql = r#"SELECT STRUCT<INTERVAL>(INTERVAL '2' MONTH), STRUCT<JSON>(JSON '{"class" : {"students" : [{"name" : "Jane"}]}}')"#;
11451143
let select = bigquery_and_generic().verified_only_select(sql);
11461144
assert_eq!(2, select.projection.len());
11471145
assert_eq!(
11481146
&Expr::Struct {
1149-
values: vec![Expr::Interval(ast::Interval {
1150-
value: Box::new(Expr::Value(Value::SingleQuotedString(
1151-
"1-2 3 4:5:6.789999".to_string()
1152-
))),
1153-
leading_field: None,
1147+
values: vec![Expr::Interval(Interval {
1148+
value: Box::new(Expr::Value(Value::SingleQuotedString("2".to_string()))),
1149+
leading_field: Some(DateTimeField::Month),
11541150
leading_precision: None,
11551151
last_field: None,
11561152
fractional_seconds_precision: None

0 commit comments

Comments
 (0)