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
|
||||
TimestampNtz,
|
||||
/// 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,
|
||||
/// Binary JSON type.
|
||||
|
@ -635,7 +644,16 @@ impl fmt::Display for DataType {
|
|||
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::JSONB => write!(f, "JSONB"),
|
||||
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
|
||||
/// following the 2016 [SQL Standard].
|
||||
///
|
||||
|
|
|
@ -52,7 +52,7 @@ use crate::{
|
|||
|
||||
pub use self::data_type::{
|
||||
ArrayElemTypeDef, BinaryLength, CharLengthUnits, CharacterLength, DataType, EnumMember,
|
||||
ExactNumberInfo, StructBracketKind, TimezoneInfo,
|
||||
ExactNumberInfo, IntervalFields, StructBracketKind, TimezoneInfo,
|
||||
};
|
||||
pub use self::dcl::{
|
||||
AlterRoleOperation, ResetConfig, RoleOption, SecondaryRoles, SetConfigValue, Use,
|
||||
|
|
|
@ -187,4 +187,8 @@ impl Dialect for GenericDialect {
|
|||
fn supports_data_type_signed_suffix(&self) -> bool {
|
||||
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 {
|
||||
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
|
||||
|
|
|
@ -269,4 +269,11 @@ impl Dialect for PostgreSqlDialect {
|
|||
fn supports_notnull_operator(&self) -> bool {
|
||||
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 opt_expr = self.maybe_parse(|parser| {
|
||||
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,
|
||||
// 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),
|
||||
|
@ -10066,10 +10066,18 @@ impl<'a> Parser<'a> {
|
|||
self.parse_optional_precision()?,
|
||||
TimezoneInfo::Tz,
|
||||
)),
|
||||
// Interval types can be followed by a complicated interval
|
||||
// qualifier that we don't currently support. See
|
||||
// parse_interval for a taste.
|
||||
Keyword::INTERVAL => Ok(DataType::Interval),
|
||||
Keyword::INTERVAL => {
|
||||
if self.dialect.supports_interval_options() {
|
||||
let fields = self.maybe_parse_optional_interval_fields()?;
|
||||
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::JSONB => Ok(DataType::JSONB),
|
||||
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]
|
||||
/// Syntax
|
||||
/// ```sql
|
||||
|
|
|
@ -961,7 +961,10 @@ fn parse_typed_struct_syntax_bigquery() {
|
|||
})],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
field_type: DataType::Interval,
|
||||
field_type: DataType::Interval {
|
||||
fields: None,
|
||||
precision: None
|
||||
},
|
||||
options: None,
|
||||
}]
|
||||
},
|
||||
|
@ -1300,7 +1303,10 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
|
|||
})],
|
||||
fields: vec![StructField {
|
||||
field_name: None,
|
||||
field_type: DataType::Interval,
|
||||
field_type: DataType::Interval {
|
||||
fields: None,
|
||||
precision: None
|
||||
},
|
||||
options: None,
|
||||
}]
|
||||
},
|
||||
|
|
|
@ -12955,7 +12955,10 @@ fn test_extract_seconds_ok() {
|
|||
expr: Box::new(Expr::Value(
|
||||
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span()
|
||||
)),
|
||||
data_type: DataType::Interval,
|
||||
data_type: DataType::Interval {
|
||||
fields: None,
|
||||
precision: None
|
||||
},
|
||||
format: None,
|
||||
}),
|
||||
}
|
||||
|
@ -12980,7 +12983,10 @@ fn test_extract_seconds_ok() {
|
|||
expr: Box::new(Expr::Value(
|
||||
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span(),
|
||||
)),
|
||||
data_type: DataType::Interval,
|
||||
data_type: DataType::Interval {
|
||||
fields: None,
|
||||
precision: None,
|
||||
},
|
||||
format: None,
|
||||
}),
|
||||
})],
|
||||
|
@ -13034,7 +13040,10 @@ fn test_extract_seconds_single_quote_ok() {
|
|||
expr: Box::new(Expr::Value(
|
||||
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span()
|
||||
)),
|
||||
data_type: DataType::Interval,
|
||||
data_type: DataType::Interval {
|
||||
fields: None,
|
||||
precision: 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]
|
||||
fn parse_create_table_with_options() {
|
||||
let sql = "CREATE TABLE t (c INT) WITH (foo = 'bar', a = 123)";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue