mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 23:49:10 +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 '...'` 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)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>)]`
|
||||||
|
|
|
@ -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]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue