mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-05 06:48:03 +00:00
Postgres: Support INTERVAL
data type options (#1984)
Some checks failed
Rust / compile (push) Has been cancelled
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled
Some checks failed
Rust / compile (push) Has been cancelled
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled
This commit is contained in:
parent
dd650b88f3
commit
c1648e79fe
9 changed files with 239 additions and 13 deletions
|
@ -346,7 +346,16 @@ pub enum DataType {
|
||||||
/// [1]: https://docs.databricks.com/aws/en/sql/language-manual/data-types/timestamp-ntz-type
|
/// [1]: https://docs.databricks.com/aws/en/sql/language-manual/data-types/timestamp-ntz-type
|
||||||
TimestampNtz,
|
TimestampNtz,
|
||||||
/// Interval type.
|
/// Interval type.
|
||||||
Interval,
|
Interval {
|
||||||
|
/// [PostgreSQL] fields specification like `INTERVAL YEAR TO MONTH`.
|
||||||
|
///
|
||||||
|
/// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-datetime.html
|
||||||
|
fields: Option<IntervalFields>,
|
||||||
|
/// [PostgreSQL] subsecond precision like `INTERVAL HOUR TO SECOND(3)`
|
||||||
|
///
|
||||||
|
/// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-datetime.html
|
||||||
|
precision: Option<u64>,
|
||||||
|
},
|
||||||
/// JSON type.
|
/// JSON type.
|
||||||
JSON,
|
JSON,
|
||||||
/// Binary JSON type.
|
/// Binary JSON type.
|
||||||
|
@ -635,7 +644,16 @@ impl fmt::Display for DataType {
|
||||||
timezone,
|
timezone,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DataType::Interval => write!(f, "INTERVAL"),
|
DataType::Interval { fields, precision } => {
|
||||||
|
write!(f, "INTERVAL")?;
|
||||||
|
if let Some(fields) = fields {
|
||||||
|
write!(f, " {fields}")?;
|
||||||
|
}
|
||||||
|
if let Some(precision) = precision {
|
||||||
|
write!(f, "({precision})")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
DataType::JSON => write!(f, "JSON"),
|
DataType::JSON => write!(f, "JSON"),
|
||||||
DataType::JSONB => write!(f, "JSONB"),
|
DataType::JSONB => write!(f, "JSONB"),
|
||||||
DataType::Regclass => write!(f, "REGCLASS"),
|
DataType::Regclass => write!(f, "REGCLASS"),
|
||||||
|
@ -889,6 +907,48 @@ impl fmt::Display for TimezoneInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fields for [Postgres] `INTERVAL` type.
|
||||||
|
///
|
||||||
|
/// [Postgres]: https://www.postgresql.org/docs/17/datatype-datetime.html
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub enum IntervalFields {
|
||||||
|
Year,
|
||||||
|
Month,
|
||||||
|
Day,
|
||||||
|
Hour,
|
||||||
|
Minute,
|
||||||
|
Second,
|
||||||
|
YearToMonth,
|
||||||
|
DayToHour,
|
||||||
|
DayToMinute,
|
||||||
|
DayToSecond,
|
||||||
|
HourToMinute,
|
||||||
|
HourToSecond,
|
||||||
|
MinuteToSecond,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for IntervalFields {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
IntervalFields::Year => write!(f, "YEAR"),
|
||||||
|
IntervalFields::Month => write!(f, "MONTH"),
|
||||||
|
IntervalFields::Day => write!(f, "DAY"),
|
||||||
|
IntervalFields::Hour => write!(f, "HOUR"),
|
||||||
|
IntervalFields::Minute => write!(f, "MINUTE"),
|
||||||
|
IntervalFields::Second => write!(f, "SECOND"),
|
||||||
|
IntervalFields::YearToMonth => write!(f, "YEAR TO MONTH"),
|
||||||
|
IntervalFields::DayToHour => write!(f, "DAY TO HOUR"),
|
||||||
|
IntervalFields::DayToMinute => write!(f, "DAY TO MINUTE"),
|
||||||
|
IntervalFields::DayToSecond => write!(f, "DAY TO SECOND"),
|
||||||
|
IntervalFields::HourToMinute => write!(f, "HOUR TO MINUTE"),
|
||||||
|
IntervalFields::HourToSecond => write!(f, "HOUR TO SECOND"),
|
||||||
|
IntervalFields::MinuteToSecond => write!(f, "MINUTE TO SECOND"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Additional information for `NUMERIC`, `DECIMAL`, and `DEC` data types
|
/// Additional information for `NUMERIC`, `DECIMAL`, and `DEC` data types
|
||||||
/// following the 2016 [SQL Standard].
|
/// following the 2016 [SQL Standard].
|
||||||
///
|
///
|
||||||
|
|
|
@ -52,7 +52,7 @@ use crate::{
|
||||||
|
|
||||||
pub use self::data_type::{
|
pub use self::data_type::{
|
||||||
ArrayElemTypeDef, BinaryLength, CharLengthUnits, CharacterLength, DataType, EnumMember,
|
ArrayElemTypeDef, BinaryLength, CharLengthUnits, CharacterLength, DataType, EnumMember,
|
||||||
ExactNumberInfo, StructBracketKind, TimezoneInfo,
|
ExactNumberInfo, IntervalFields, StructBracketKind, TimezoneInfo,
|
||||||
};
|
};
|
||||||
pub use self::dcl::{
|
pub use self::dcl::{
|
||||||
AlterRoleOperation, ResetConfig, RoleOption, SecondaryRoles, SetConfigValue, Use,
|
AlterRoleOperation, ResetConfig, RoleOption, SecondaryRoles, SetConfigValue, Use,
|
||||||
|
|
|
@ -187,4 +187,8 @@ impl Dialect for GenericDialect {
|
||||||
fn supports_data_type_signed_suffix(&self) -> bool {
|
fn supports_data_type_signed_suffix(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_interval_options(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1148,6 +1148,21 @@ pub trait Dialect: Debug + Any {
|
||||||
fn supports_data_type_signed_suffix(&self) -> bool {
|
fn supports_data_type_signed_suffix(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the dialect supports the `INTERVAL` data type with [Postgres]-style options.
|
||||||
|
///
|
||||||
|
/// Examples:
|
||||||
|
/// ```sql
|
||||||
|
/// CREATE TABLE t (i INTERVAL YEAR TO MONTH);
|
||||||
|
/// SELECT '1 second'::INTERVAL HOUR TO SECOND(3);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// See [`crate::ast::DataType::Interval`] and [`crate::ast::IntervalFields`].
|
||||||
|
///
|
||||||
|
/// [Postgres]: https://www.postgresql.org/docs/17/datatype-datetime.html
|
||||||
|
fn supports_interval_options(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This represents the operators for which precedence must be defined
|
/// This represents the operators for which precedence must be defined
|
||||||
|
|
|
@ -269,4 +269,11 @@ impl Dialect for PostgreSqlDialect {
|
||||||
fn supports_notnull_operator(&self) -> bool {
|
fn supports_notnull_operator(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Postgres] supports optional field and precision options for `INTERVAL` data type.
|
||||||
|
///
|
||||||
|
/// [Postgres]: https://www.postgresql.org/docs/17/datatype-datetime.html
|
||||||
|
fn supports_interval_options(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1534,7 +1534,7 @@ impl<'a> Parser<'a> {
|
||||||
let loc = self.peek_token_ref().span.start;
|
let loc = self.peek_token_ref().span.start;
|
||||||
let opt_expr = self.maybe_parse(|parser| {
|
let opt_expr = self.maybe_parse(|parser| {
|
||||||
match parser.parse_data_type()? {
|
match parser.parse_data_type()? {
|
||||||
DataType::Interval => parser.parse_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),
|
||||||
|
@ -10066,10 +10066,18 @@ impl<'a> Parser<'a> {
|
||||||
self.parse_optional_precision()?,
|
self.parse_optional_precision()?,
|
||||||
TimezoneInfo::Tz,
|
TimezoneInfo::Tz,
|
||||||
)),
|
)),
|
||||||
// Interval types can be followed by a complicated interval
|
Keyword::INTERVAL => {
|
||||||
// qualifier that we don't currently support. See
|
if self.dialect.supports_interval_options() {
|
||||||
// parse_interval for a taste.
|
let fields = self.maybe_parse_optional_interval_fields()?;
|
||||||
Keyword::INTERVAL => Ok(DataType::Interval),
|
let precision = self.parse_optional_precision()?;
|
||||||
|
Ok(DataType::Interval { fields, precision })
|
||||||
|
} else {
|
||||||
|
Ok(DataType::Interval {
|
||||||
|
fields: None,
|
||||||
|
precision: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Keyword::JSON => Ok(DataType::JSON),
|
Keyword::JSON => Ok(DataType::JSON),
|
||||||
Keyword::JSONB => Ok(DataType::JSONB),
|
Keyword::JSONB => Ok(DataType::JSONB),
|
||||||
Keyword::REGCLASS => Ok(DataType::Regclass),
|
Keyword::REGCLASS => Ok(DataType::Regclass),
|
||||||
|
@ -11038,6 +11046,85 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn maybe_parse_optional_interval_fields(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Option<IntervalFields>, ParserError> {
|
||||||
|
match self.parse_one_of_keywords(&[
|
||||||
|
// Can be followed by `TO` option
|
||||||
|
Keyword::YEAR,
|
||||||
|
Keyword::DAY,
|
||||||
|
Keyword::HOUR,
|
||||||
|
Keyword::MINUTE,
|
||||||
|
// No `TO` option
|
||||||
|
Keyword::MONTH,
|
||||||
|
Keyword::SECOND,
|
||||||
|
]) {
|
||||||
|
Some(Keyword::YEAR) => {
|
||||||
|
if self.peek_keyword(Keyword::TO) {
|
||||||
|
self.expect_keyword(Keyword::TO)?;
|
||||||
|
self.expect_keyword(Keyword::MONTH)?;
|
||||||
|
Ok(Some(IntervalFields::YearToMonth))
|
||||||
|
} else {
|
||||||
|
Ok(Some(IntervalFields::Year))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Keyword::DAY) => {
|
||||||
|
if self.peek_keyword(Keyword::TO) {
|
||||||
|
self.expect_keyword(Keyword::TO)?;
|
||||||
|
match self.expect_one_of_keywords(&[
|
||||||
|
Keyword::HOUR,
|
||||||
|
Keyword::MINUTE,
|
||||||
|
Keyword::SECOND,
|
||||||
|
])? {
|
||||||
|
Keyword::HOUR => Ok(Some(IntervalFields::DayToHour)),
|
||||||
|
Keyword::MINUTE => Ok(Some(IntervalFields::DayToMinute)),
|
||||||
|
Keyword::SECOND => Ok(Some(IntervalFields::DayToSecond)),
|
||||||
|
_ => {
|
||||||
|
self.prev_token();
|
||||||
|
self.expected("HOUR, MINUTE, or SECOND", self.peek_token())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Some(IntervalFields::Day))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Keyword::HOUR) => {
|
||||||
|
if self.peek_keyword(Keyword::TO) {
|
||||||
|
self.expect_keyword(Keyword::TO)?;
|
||||||
|
match self.expect_one_of_keywords(&[Keyword::MINUTE, Keyword::SECOND])? {
|
||||||
|
Keyword::MINUTE => Ok(Some(IntervalFields::HourToMinute)),
|
||||||
|
Keyword::SECOND => Ok(Some(IntervalFields::HourToSecond)),
|
||||||
|
_ => {
|
||||||
|
self.prev_token();
|
||||||
|
self.expected("MINUTE or SECOND", self.peek_token())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Some(IntervalFields::Hour))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Keyword::MINUTE) => {
|
||||||
|
if self.peek_keyword(Keyword::TO) {
|
||||||
|
self.expect_keyword(Keyword::TO)?;
|
||||||
|
self.expect_keyword(Keyword::SECOND)?;
|
||||||
|
Ok(Some(IntervalFields::MinuteToSecond))
|
||||||
|
} else {
|
||||||
|
Ok(Some(IntervalFields::Minute))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Keyword::MONTH) => Ok(Some(IntervalFields::Month)),
|
||||||
|
Some(Keyword::SECOND) => Ok(Some(IntervalFields::Second)),
|
||||||
|
Some(_) => {
|
||||||
|
self.prev_token();
|
||||||
|
self.expected(
|
||||||
|
"YEAR, MONTH, DAY, HOUR, MINUTE, or SECOND",
|
||||||
|
self.peek_token(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse datetime64 [1]
|
/// Parse datetime64 [1]
|
||||||
/// Syntax
|
/// Syntax
|
||||||
/// ```sql
|
/// ```sql
|
||||||
|
|
|
@ -961,7 +961,10 @@ fn parse_typed_struct_syntax_bigquery() {
|
||||||
})],
|
})],
|
||||||
fields: vec![StructField {
|
fields: vec![StructField {
|
||||||
field_name: None,
|
field_name: None,
|
||||||
field_type: DataType::Interval,
|
field_type: DataType::Interval {
|
||||||
|
fields: None,
|
||||||
|
precision: None
|
||||||
|
},
|
||||||
options: None,
|
options: None,
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
@ -1300,7 +1303,10 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
|
||||||
})],
|
})],
|
||||||
fields: vec![StructField {
|
fields: vec![StructField {
|
||||||
field_name: None,
|
field_name: None,
|
||||||
field_type: DataType::Interval,
|
field_type: DataType::Interval {
|
||||||
|
fields: None,
|
||||||
|
precision: None
|
||||||
|
},
|
||||||
options: None,
|
options: None,
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
|
|
@ -12955,7 +12955,10 @@ fn test_extract_seconds_ok() {
|
||||||
expr: Box::new(Expr::Value(
|
expr: Box::new(Expr::Value(
|
||||||
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span()
|
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span()
|
||||||
)),
|
)),
|
||||||
data_type: DataType::Interval,
|
data_type: DataType::Interval {
|
||||||
|
fields: None,
|
||||||
|
precision: None
|
||||||
|
},
|
||||||
format: None,
|
format: None,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -12980,7 +12983,10 @@ fn test_extract_seconds_ok() {
|
||||||
expr: Box::new(Expr::Value(
|
expr: Box::new(Expr::Value(
|
||||||
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span(),
|
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span(),
|
||||||
)),
|
)),
|
||||||
data_type: DataType::Interval,
|
data_type: DataType::Interval {
|
||||||
|
fields: None,
|
||||||
|
precision: None,
|
||||||
|
},
|
||||||
format: None,
|
format: None,
|
||||||
}),
|
}),
|
||||||
})],
|
})],
|
||||||
|
@ -13034,7 +13040,10 @@ fn test_extract_seconds_single_quote_ok() {
|
||||||
expr: Box::new(Expr::Value(
|
expr: Box::new(Expr::Value(
|
||||||
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span()
|
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span()
|
||||||
)),
|
)),
|
||||||
data_type: DataType::Interval,
|
data_type: DataType::Interval {
|
||||||
|
fields: None,
|
||||||
|
precision: None
|
||||||
|
},
|
||||||
format: None,
|
format: None,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -5332,6 +5332,44 @@ fn parse_at_time_zone() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_interval_data_type() {
|
||||||
|
pg_and_generic().verified_stmt("CREATE TABLE t (i INTERVAL)");
|
||||||
|
for p in 0..=6 {
|
||||||
|
pg_and_generic().verified_stmt(&format!("CREATE TABLE t (i INTERVAL({p}))"));
|
||||||
|
pg_and_generic().verified_stmt(&format!("SELECT '1 second'::INTERVAL({p})"));
|
||||||
|
pg_and_generic().verified_stmt(&format!("SELECT CAST('1 second' AS INTERVAL({p}))"));
|
||||||
|
}
|
||||||
|
let fields = [
|
||||||
|
"YEAR",
|
||||||
|
"MONTH",
|
||||||
|
"DAY",
|
||||||
|
"HOUR",
|
||||||
|
"MINUTE",
|
||||||
|
"SECOND",
|
||||||
|
"YEAR TO MONTH",
|
||||||
|
"DAY TO HOUR",
|
||||||
|
"DAY TO MINUTE",
|
||||||
|
"DAY TO SECOND",
|
||||||
|
"HOUR TO MINUTE",
|
||||||
|
"HOUR TO SECOND",
|
||||||
|
"MINUTE TO SECOND",
|
||||||
|
];
|
||||||
|
for field in fields {
|
||||||
|
pg_and_generic().verified_stmt(&format!("CREATE TABLE t (i INTERVAL {field})"));
|
||||||
|
pg_and_generic().verified_stmt(&format!("SELECT '1 second'::INTERVAL {field}"));
|
||||||
|
pg_and_generic().verified_stmt(&format!("SELECT CAST('1 second' AS INTERVAL {field})"));
|
||||||
|
}
|
||||||
|
for p in 0..=6 {
|
||||||
|
for field in fields {
|
||||||
|
pg_and_generic().verified_stmt(&format!("CREATE TABLE t (i INTERVAL {field}({p}))"));
|
||||||
|
pg_and_generic().verified_stmt(&format!("SELECT '1 second'::INTERVAL {field}({p})"));
|
||||||
|
pg_and_generic()
|
||||||
|
.verified_stmt(&format!("SELECT CAST('1 second' AS INTERVAL {field}({p}))"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_create_table_with_options() {
|
fn parse_create_table_with_options() {
|
||||||
let sql = "CREATE TABLE t (c INT) WITH (foo = 'bar', a = 123)";
|
let sql = "CREATE TABLE t (c INT) WITH (foo = 'bar', a = 123)";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue