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> }, ArrayIndex { obj: Box<Expr>, indexes: Vec<Expr> },
/// An array expression e.g. `ARRAY[1, 2]` /// An array expression e.g. `ARRAY[1, 2]`
Array(Array), 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 { impl fmt::Display for Expr {
@ -722,6 +741,44 @@ impl fmt::Display for Expr {
} => { } => {
write!(f, "{} AT TIME ZONE '{}'", timestamp, time_zone) 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")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::Expr;
/// Primitive SQL values such as number and string /// Primitive SQL values such as number and string
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -45,25 +43,6 @@ pub enum Value {
DoubleQuotedString(String), DoubleQuotedString(String),
/// Boolean value true or false /// Boolean value true or false
Boolean(bool), 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` value
Null, Null,
/// `?` or `$` Prepared statement arg placeholder /// `?` or `$` Prepared statement arg placeholder
@ -80,44 +59,6 @@ impl fmt::Display for Value {
Value::NationalStringLiteral(v) => write!(f, "N'{}'", v), Value::NationalStringLiteral(v) => write!(f, "N'{}'", v),
Value::HexStringLiteral(v) => write!(f, "X'{}'", v), Value::HexStringLiteral(v) => write!(f, "X'{}'", v),
Value::Boolean(v) => write!(f, "{}", 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::Null => write!(f, "NULL"),
Value::Placeholder(v) => write!(f, "{}", v), 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". // expression that should parse as the column name "date".
return_ok_if_some!(self.maybe_parse(|parser| { return_ok_if_some!(self.maybe_parse(|parser| {
match parser.parse_data_type()? { 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, // 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 // 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), // 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::SUBSTRING => self.parse_substring_expr(),
Keyword::OVERLAY => self.parse_overlay_expr(), Keyword::OVERLAY => self.parse_overlay_expr(),
Keyword::TRIM => self.parse_trim_expr(), Keyword::TRIM => self.parse_trim_expr(),
Keyword::INTERVAL => self.parse_literal_interval(), Keyword::INTERVAL => self.parse_interval(),
Keyword::LISTAGG => self.parse_listagg_expr(), 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 // 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 => { 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: /// Some syntactically valid intervals:
/// ///
@ -1109,7 +1109,7 @@ impl<'a> Parser<'a> {
/// 7. (MySql and BigQuey only):`INTERVAL 1 DAY` /// 7. (MySql and BigQuey only):`INTERVAL 1 DAY`
/// ///
/// Note that we do not currently attempt to parse the quoted value. /// 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 // The SQL standard allows an optional sign before the value string, but
// it is not clear if any implementations support that syntax, so we // 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 // 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), value: Box::new(value),
leading_field, leading_field,
leading_precision, leading_precision,
last_field, last_field,
fractional_seconds_precision: fsec_precision, fractional_seconds_precision: fsec_precision,
})) })
} }
/// Parse an operator following an expression /// Parse an operator following an expression
@ -3178,7 +3178,7 @@ impl<'a> Parser<'a> {
} }
// Interval types can be followed by a complicated interval // Interval types can be followed by a complicated interval
// qualifier that we don't currently support. See // 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::INTERVAL => Ok(DataType::Interval),
Keyword::REGCLASS => Ok(DataType::Regclass), Keyword::REGCLASS => Ok(DataType::Regclass),
Keyword::STRING => Ok(DataType::String), Keyword::STRING => Ok(DataType::String),

View file

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