Move Value::Interval to Expr::Interval (#609)

* refactor(value): convert Value::Interval to Expr::Interval

* test(sqlparser_common): modify test case

* refactor(parser): rename func parse_interval

* refactor(tests) rename parse_interval test func
This commit is contained in:
ding-young 2022-09-27 22:30:48 +09:00 committed by GitHub
parent d971a029dd
commit 3ac1bb5b80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 83 deletions

View file

@ -410,6 +410,25 @@ pub enum Expr {
ArrayIndex { obj: Box<Expr>, indexes: Vec<Expr> },
/// An array expression e.g. `ARRAY[1, 2]`
Array(Array),
/// INTERVAL literals, roughly in the following format:
/// `INTERVAL '<value>' [ <leading_field> [ (<leading_precision>) ] ]
/// [ TO <last_field> [ (<fractional_seconds_precision>) ] ]`,
/// e.g. `INTERVAL '123:45.67' MINUTE(3) TO SECOND(2)`.
///
/// The parser does not validate the `<value>`, nor does it ensure
/// that the `<leading_field>` units >= the units in `<last_field>`,
/// so the user will have to reject intervals like `HOUR TO YEAR`.
Interval {
value: Box<Expr>,
leading_field: Option<DateTimeField>,
leading_precision: Option<u64>,
last_field: Option<DateTimeField>,
/// The seconds precision can be specified in SQL source as
/// `INTERVAL '__' SECOND(_, x)` (in which case the `leading_field`
/// will be `Second` and the `last_field` will be `None`),
/// or as `__ TO SECOND(x)`.
fractional_seconds_precision: Option<u64>,
},
}
impl fmt::Display for Expr {
@ -722,6 +741,44 @@ impl fmt::Display for Expr {
} => {
write!(f, "{} AT TIME ZONE '{}'", timestamp, time_zone)
}
Expr::Interval {
value,
leading_field: Some(DateTimeField::Second),
leading_precision: Some(leading_precision),
last_field,
fractional_seconds_precision: Some(fractional_seconds_precision),
} => {
// When the leading field is SECOND, the parser guarantees that
// the last field is None.
assert!(last_field.is_none());
write!(
f,
"INTERVAL {} SECOND ({}, {})",
value, leading_precision, fractional_seconds_precision
)
}
Expr::Interval {
value,
leading_field,
leading_precision,
last_field,
fractional_seconds_precision,
} => {
write!(f, "INTERVAL {}", value)?;
if let Some(leading_field) = leading_field {
write!(f, " {}", leading_field)?;
}
if let Some(leading_precision) = leading_precision {
write!(f, " ({})", leading_precision)?;
}
if let Some(last_field) = last_field {
write!(f, " TO {}", last_field)?;
}
if let Some(fractional_seconds_precision) = fractional_seconds_precision {
write!(f, " ({})", fractional_seconds_precision)?;
}
Ok(())
}
}
}
}

View file

@ -21,8 +21,6 @@ use bigdecimal::BigDecimal;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::Expr;
/// Primitive SQL values such as number and string
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -45,25 +43,6 @@ pub enum Value {
DoubleQuotedString(String),
/// Boolean value true or false
Boolean(bool),
/// INTERVAL literals, roughly in the following format:
/// `INTERVAL '<value>' [ <leading_field> [ (<leading_precision>) ] ]
/// [ TO <last_field> [ (<fractional_seconds_precision>) ] ]`,
/// e.g. `INTERVAL '123:45.67' MINUTE(3) TO SECOND(2)`.
///
/// The parser does not validate the `<value>`, nor does it ensure
/// that the `<leading_field>` units >= the units in `<last_field>`,
/// so the user will have to reject intervals like `HOUR TO YEAR`.
Interval {
value: Box<Expr>,
leading_field: Option<DateTimeField>,
leading_precision: Option<u64>,
last_field: Option<DateTimeField>,
/// The seconds precision can be specified in SQL source as
/// `INTERVAL '__' SECOND(_, x)` (in which case the `leading_field`
/// will be `Second` and the `last_field` will be `None`),
/// or as `__ TO SECOND(x)`.
fractional_seconds_precision: Option<u64>,
},
/// `NULL` value
Null,
/// `?` or `$` Prepared statement arg placeholder
@ -80,44 +59,6 @@ impl fmt::Display for Value {
Value::NationalStringLiteral(v) => write!(f, "N'{}'", v),
Value::HexStringLiteral(v) => write!(f, "X'{}'", v),
Value::Boolean(v) => write!(f, "{}", v),
Value::Interval {
value,
leading_field: Some(DateTimeField::Second),
leading_precision: Some(leading_precision),
last_field,
fractional_seconds_precision: Some(fractional_seconds_precision),
} => {
// When the leading field is SECOND, the parser guarantees that
// the last field is None.
assert!(last_field.is_none());
write!(
f,
"INTERVAL {} SECOND ({}, {})",
value, leading_precision, fractional_seconds_precision
)
}
Value::Interval {
value,
leading_field,
leading_precision,
last_field,
fractional_seconds_precision,
} => {
write!(f, "INTERVAL {}", value)?;
if let Some(leading_field) = leading_field {
write!(f, " {}", leading_field)?;
}
if let Some(leading_precision) = leading_precision {
write!(f, " ({})", leading_precision)?;
}
if let Some(last_field) = last_field {
write!(f, " TO {}", last_field)?;
}
if let Some(fractional_seconds_precision) = fractional_seconds_precision {
write!(f, " ({})", fractional_seconds_precision)?;
}
Ok(())
}
Value::Null => write!(f, "NULL"),
Value::Placeholder(v) => write!(f, "{}", v),
}

View file

@ -402,7 +402,7 @@ impl<'a> Parser<'a> {
// expression that should parse as the column name "date".
return_ok_if_some!(self.maybe_parse(|parser| {
match parser.parse_data_type()? {
DataType::Interval => parser.parse_literal_interval(),
DataType::Interval => parser.parse_interval(),
// PostgreSQL allows almost any identifier to be used as custom data type name,
// and we support that in `parse_data_type()`. But unlike Postgres we don't
// have a list of globally reserved keywords (since they vary across dialects),
@ -455,7 +455,7 @@ impl<'a> Parser<'a> {
Keyword::SUBSTRING => self.parse_substring_expr(),
Keyword::OVERLAY => self.parse_overlay_expr(),
Keyword::TRIM => self.parse_trim_expr(),
Keyword::INTERVAL => self.parse_literal_interval(),
Keyword::INTERVAL => self.parse_interval(),
Keyword::LISTAGG => self.parse_listagg_expr(),
// Treat ARRAY[1,2,3] as an array [1,2,3], otherwise try as subquery or a function call
Keyword::ARRAY if self.peek_token() == Token::LBracket => {
@ -1096,7 +1096,7 @@ impl<'a> Parser<'a> {
}
}
/// Parse an INTERVAL literal.
/// Parse an INTERVAL expression.
///
/// Some syntactically valid intervals:
///
@ -1109,7 +1109,7 @@ impl<'a> Parser<'a> {
/// 7. (MySql and BigQuey only):`INTERVAL 1 DAY`
///
/// Note that we do not currently attempt to parse the quoted value.
pub fn parse_literal_interval(&mut self) -> Result<Expr, ParserError> {
pub fn parse_interval(&mut self) -> Result<Expr, ParserError> {
// The SQL standard allows an optional sign before the value string, but
// it is not clear if any implementations support that syntax, so we
// don't currently try to parse it. (The sign can instead be included
@ -1183,13 +1183,13 @@ impl<'a> Parser<'a> {
}
};
Ok(Expr::Value(Value::Interval {
Ok(Expr::Interval {
value: Box::new(value),
leading_field,
leading_precision,
last_field,
fractional_seconds_precision: fsec_precision,
}))
})
}
/// Parse an operator following an expression
@ -3178,7 +3178,7 @@ impl<'a> Parser<'a> {
}
// Interval types can be followed by a complicated interval
// qualifier that we don't currently support. See
// parse_interval_literal for a taste.
// parse_interval for a taste.
Keyword::INTERVAL => Ok(DataType::Interval),
Keyword::REGCLASS => Ok(DataType::Regclass),
Keyword::STRING => Ok(DataType::String),

View file

@ -2930,24 +2930,24 @@ fn parse_literal_timestamp_with_time_zone() {
}
#[test]
fn parse_literal_interval() {
fn parse_interval() {
let sql = "SELECT INTERVAL '1-1' YEAR TO MONTH";
let select = verified_only_select(sql);
assert_eq!(
&Expr::Value(Value::Interval {
&Expr::Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("1-1")))),
leading_field: Some(DateTimeField::Year),
leading_precision: None,
last_field: Some(DateTimeField::Month),
fractional_seconds_precision: None,
}),
},
expr_from_projection(only(&select.projection)),
);
let sql = "SELECT INTERVAL '01:01.01' MINUTE (5) TO SECOND (5)";
let select = verified_only_select(sql);
assert_eq!(
&Expr::Value(Value::Interval {
&Expr::Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from(
"01:01.01"
)))),
@ -2955,53 +2955,53 @@ fn parse_literal_interval() {
leading_precision: Some(5),
last_field: Some(DateTimeField::Second),
fractional_seconds_precision: Some(5),
}),
},
expr_from_projection(only(&select.projection)),
);
let sql = "SELECT INTERVAL '1' SECOND (5, 4)";
let select = verified_only_select(sql);
assert_eq!(
&Expr::Value(Value::Interval {
&Expr::Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("1")))),
leading_field: Some(DateTimeField::Second),
leading_precision: Some(5),
last_field: None,
fractional_seconds_precision: Some(4),
}),
},
expr_from_projection(only(&select.projection)),
);
let sql = "SELECT INTERVAL '10' HOUR";
let select = verified_only_select(sql);
assert_eq!(
&Expr::Value(Value::Interval {
&Expr::Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("10")))),
leading_field: Some(DateTimeField::Hour),
leading_precision: None,
last_field: None,
fractional_seconds_precision: None,
}),
},
expr_from_projection(only(&select.projection)),
);
let sql = "SELECT INTERVAL 5 DAY";
let select = verified_only_select(sql);
assert_eq!(
&Expr::Value(Value::Interval {
&Expr::Interval {
value: Box::new(Expr::Value(number("5"))),
leading_field: Some(DateTimeField::Day),
leading_precision: None,
last_field: None,
fractional_seconds_precision: None,
}),
},
expr_from_projection(only(&select.projection)),
);
let sql = "SELECT INTERVAL 1 + 1 DAY";
let select = verified_only_select(sql);
assert_eq!(
&Expr::Value(Value::Interval {
&Expr::Interval {
value: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Value(number("1"))),
op: BinaryOperator::Plus,
@ -3011,27 +3011,27 @@ fn parse_literal_interval() {
leading_precision: None,
last_field: None,
fractional_seconds_precision: None,
}),
},
expr_from_projection(only(&select.projection)),
);
let sql = "SELECT INTERVAL '10' HOUR (1)";
let select = verified_only_select(sql);
assert_eq!(
&Expr::Value(Value::Interval {
&Expr::Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from("10")))),
leading_field: Some(DateTimeField::Hour),
leading_precision: Some(1),
last_field: None,
fractional_seconds_precision: None,
}),
},
expr_from_projection(only(&select.projection)),
);
let sql = "SELECT INTERVAL '1 DAY'";
let select = verified_only_select(sql);
assert_eq!(
&Expr::Value(Value::Interval {
&Expr::Interval {
value: Box::new(Expr::Value(Value::SingleQuotedString(String::from(
"1 DAY"
)))),
@ -3039,7 +3039,7 @@ fn parse_literal_interval() {
leading_precision: None,
last_field: None,
fractional_seconds_precision: None,
}),
},
expr_from_projection(only(&select.projection)),
);