mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 15:39:12 +00:00
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:
parent
d842f495db
commit
846c52f450
3 changed files with 44 additions and 18 deletions
|
@ -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)?;
|
||||
}
|
||||
|
|
|
@ -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>)]`
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue