Skip to content

Commit 8bf11a1

Browse files
mvzinkayman-sigma
authored andcommitted
Postgres: Support INTERVAL data type options (apache#1984)
1 parent d5bc356 commit 8bf11a1

9 files changed

Lines changed: 239 additions & 13 deletions

File tree

src/ast/data_type.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,16 @@ pub enum DataType {
346346
/// [1]: https://docs.databricks.com/aws/en/sql/language-manual/data-types/timestamp-ntz-type
347347
TimestampNtz,
348348
/// Interval type.
349-
Interval,
349+
Interval {
350+
/// [PostgreSQL] fields specification like `INTERVAL YEAR TO MONTH`.
351+
///
352+
/// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-datetime.html
353+
fields: Option<IntervalFields>,
354+
/// [PostgreSQL] subsecond precision like `INTERVAL HOUR TO SECOND(3)`
355+
///
356+
/// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-datetime.html
357+
precision: Option<u64>,
358+
},
350359
/// JSON type.
351360
JSON,
352361
/// Binary JSON type.
@@ -635,7 +644,16 @@ impl fmt::Display for DataType {
635644
timezone,
636645
)
637646
}
638-
DataType::Interval => write!(f, "INTERVAL"),
647+
DataType::Interval { fields, precision } => {
648+
write!(f, "INTERVAL")?;
649+
if let Some(fields) = fields {
650+
write!(f, " {fields}")?;
651+
}
652+
if let Some(precision) = precision {
653+
write!(f, "({precision})")?;
654+
}
655+
Ok(())
656+
}
639657
DataType::JSON => write!(f, "JSON"),
640658
DataType::JSONB => write!(f, "JSONB"),
641659
DataType::Regclass => write!(f, "REGCLASS"),
@@ -889,6 +907,48 @@ impl fmt::Display for TimezoneInfo {
889907
}
890908
}
891909

910+
/// Fields for [Postgres] `INTERVAL` type.
911+
///
912+
/// [Postgres]: https://www.postgresql.org/docs/17/datatype-datetime.html
913+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
914+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
915+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
916+
pub enum IntervalFields {
917+
Year,
918+
Month,
919+
Day,
920+
Hour,
921+
Minute,
922+
Second,
923+
YearToMonth,
924+
DayToHour,
925+
DayToMinute,
926+
DayToSecond,
927+
HourToMinute,
928+
HourToSecond,
929+
MinuteToSecond,
930+
}
931+
932+
impl fmt::Display for IntervalFields {
933+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
934+
match self {
935+
IntervalFields::Year => write!(f, "YEAR"),
936+
IntervalFields::Month => write!(f, "MONTH"),
937+
IntervalFields::Day => write!(f, "DAY"),
938+
IntervalFields::Hour => write!(f, "HOUR"),
939+
IntervalFields::Minute => write!(f, "MINUTE"),
940+
IntervalFields::Second => write!(f, "SECOND"),
941+
IntervalFields::YearToMonth => write!(f, "YEAR TO MONTH"),
942+
IntervalFields::DayToHour => write!(f, "DAY TO HOUR"),
943+
IntervalFields::DayToMinute => write!(f, "DAY TO MINUTE"),
944+
IntervalFields::DayToSecond => write!(f, "DAY TO SECOND"),
945+
IntervalFields::HourToMinute => write!(f, "HOUR TO MINUTE"),
946+
IntervalFields::HourToSecond => write!(f, "HOUR TO SECOND"),
947+
IntervalFields::MinuteToSecond => write!(f, "MINUTE TO SECOND"),
948+
}
949+
}
950+
}
951+
892952
/// Additional information for `NUMERIC`, `DECIMAL`, and `DEC` data types
893953
/// following the 2016 [SQL Standard].
894954
///

src/ast/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ use crate::{
5252

5353
pub use self::data_type::{
5454
ArrayElemTypeDef, BinaryLength, CharLengthUnits, CharacterLength, DataType, EnumMember,
55-
ExactNumberInfo, StructBracketKind, TimezoneInfo,
55+
ExactNumberInfo, IntervalFields, StructBracketKind, TimezoneInfo,
5656
};
5757
pub use self::dcl::{
5858
AlterRoleOperation, ResetConfig, RoleOption, SecondaryRoles, SetConfigValue, Use,

src/dialect/generic.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,8 @@ impl Dialect for GenericDialect {
187187
fn supports_data_type_signed_suffix(&self) -> bool {
188188
true
189189
}
190+
191+
fn supports_interval_options(&self) -> bool {
192+
true
193+
}
190194
}

src/dialect/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,21 @@ pub trait Dialect: Debug + Any {
11531153
fn supports_data_type_signed_suffix(&self) -> bool {
11541154
false
11551155
}
1156+
1157+
/// Returns true if the dialect supports the `INTERVAL` data type with [Postgres]-style options.
1158+
///
1159+
/// Examples:
1160+
/// ```sql
1161+
/// CREATE TABLE t (i INTERVAL YEAR TO MONTH);
1162+
/// SELECT '1 second'::INTERVAL HOUR TO SECOND(3);
1163+
/// ```
1164+
///
1165+
/// See [`crate::ast::DataType::Interval`] and [`crate::ast::IntervalFields`].
1166+
///
1167+
/// [Postgres]: https://www.postgresql.org/docs/17/datatype-datetime.html
1168+
fn supports_interval_options(&self) -> bool {
1169+
false
1170+
}
11561171
}
11571172

11581173
/// This represents the operators for which precedence must be defined

src/dialect/postgresql.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,11 @@ impl Dialect for PostgreSqlDialect {
269269
fn supports_notnull_operator(&self) -> bool {
270270
true
271271
}
272+
273+
/// [Postgres] supports optional field and precision options for `INTERVAL` data type.
274+
///
275+
/// [Postgres]: https://www.postgresql.org/docs/17/datatype-datetime.html
276+
fn supports_interval_options(&self) -> bool {
277+
true
278+
}
272279
}

src/parser/mod.rs

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,7 +1534,7 @@ impl<'a> Parser<'a> {
15341534
let loc = self.peek_token_ref().span.start;
15351535
let opt_expr = self.maybe_parse(|parser| {
15361536
match parser.parse_data_type()? {
1537-
DataType::Interval => parser.parse_interval(),
1537+
DataType::Interval { .. } => parser.parse_interval(),
15381538
// PostgreSQL allows almost any identifier to be used as custom data type name,
15391539
// and we support that in `parse_data_type()`. But unlike Postgres we don't
15401540
// have a list of globally reserved keywords (since they vary across dialects),
@@ -10097,10 +10097,18 @@ impl<'a> Parser<'a> {
1009710097
self.parse_optional_precision()?,
1009810098
TimezoneInfo::Tz,
1009910099
)),
10100-
// Interval types can be followed by a complicated interval
10101-
// qualifier that we don't currently support. See
10102-
// parse_interval for a taste.
10103-
Keyword::INTERVAL => Ok(DataType::Interval),
10100+
Keyword::INTERVAL => {
10101+
if self.dialect.supports_interval_options() {
10102+
let fields = self.maybe_parse_optional_interval_fields()?;
10103+
let precision = self.parse_optional_precision()?;
10104+
Ok(DataType::Interval { fields, precision })
10105+
} else {
10106+
Ok(DataType::Interval {
10107+
fields: None,
10108+
precision: None,
10109+
})
10110+
}
10111+
}
1010410112
Keyword::JSON => Ok(DataType::JSON),
1010510113
Keyword::JSONB => Ok(DataType::JSONB),
1010610114
Keyword::REGCLASS => Ok(DataType::Regclass),
@@ -11069,6 +11077,85 @@ impl<'a> Parser<'a> {
1106911077
}
1107011078
}
1107111079

11080+
fn maybe_parse_optional_interval_fields(
11081+
&mut self,
11082+
) -> Result<Option<IntervalFields>, ParserError> {
11083+
match self.parse_one_of_keywords(&[
11084+
// Can be followed by `TO` option
11085+
Keyword::YEAR,
11086+
Keyword::DAY,
11087+
Keyword::HOUR,
11088+
Keyword::MINUTE,
11089+
// No `TO` option
11090+
Keyword::MONTH,
11091+
Keyword::SECOND,
11092+
]) {
11093+
Some(Keyword::YEAR) => {
11094+
if self.peek_keyword(Keyword::TO) {
11095+
self.expect_keyword(Keyword::TO)?;
11096+
self.expect_keyword(Keyword::MONTH)?;
11097+
Ok(Some(IntervalFields::YearToMonth))
11098+
} else {
11099+
Ok(Some(IntervalFields::Year))
11100+
}
11101+
}
11102+
Some(Keyword::DAY) => {
11103+
if self.peek_keyword(Keyword::TO) {
11104+
self.expect_keyword(Keyword::TO)?;
11105+
match self.expect_one_of_keywords(&[
11106+
Keyword::HOUR,
11107+
Keyword::MINUTE,
11108+
Keyword::SECOND,
11109+
])? {
11110+
Keyword::HOUR => Ok(Some(IntervalFields::DayToHour)),
11111+
Keyword::MINUTE => Ok(Some(IntervalFields::DayToMinute)),
11112+
Keyword::SECOND => Ok(Some(IntervalFields::DayToSecond)),
11113+
_ => {
11114+
self.prev_token();
11115+
self.expected("HOUR, MINUTE, or SECOND", self.peek_token())
11116+
}
11117+
}
11118+
} else {
11119+
Ok(Some(IntervalFields::Day))
11120+
}
11121+
}
11122+
Some(Keyword::HOUR) => {
11123+
if self.peek_keyword(Keyword::TO) {
11124+
self.expect_keyword(Keyword::TO)?;
11125+
match self.expect_one_of_keywords(&[Keyword::MINUTE, Keyword::SECOND])? {
11126+
Keyword::MINUTE => Ok(Some(IntervalFields::HourToMinute)),
11127+
Keyword::SECOND => Ok(Some(IntervalFields::HourToSecond)),
11128+
_ => {
11129+
self.prev_token();
11130+
self.expected("MINUTE or SECOND", self.peek_token())
11131+
}
11132+
}
11133+
} else {
11134+
Ok(Some(IntervalFields::Hour))
11135+
}
11136+
}
11137+
Some(Keyword::MINUTE) => {
11138+
if self.peek_keyword(Keyword::TO) {
11139+
self.expect_keyword(Keyword::TO)?;
11140+
self.expect_keyword(Keyword::SECOND)?;
11141+
Ok(Some(IntervalFields::MinuteToSecond))
11142+
} else {
11143+
Ok(Some(IntervalFields::Minute))
11144+
}
11145+
}
11146+
Some(Keyword::MONTH) => Ok(Some(IntervalFields::Month)),
11147+
Some(Keyword::SECOND) => Ok(Some(IntervalFields::Second)),
11148+
Some(_) => {
11149+
self.prev_token();
11150+
self.expected(
11151+
"YEAR, MONTH, DAY, HOUR, MINUTE, or SECOND",
11152+
self.peek_token(),
11153+
)
11154+
}
11155+
None => Ok(None),
11156+
}
11157+
}
11158+
1107211159
/// Parse datetime64 [1]
1107311160
/// Syntax
1107411161
/// ```sql

tests/sqlparser_bigquery.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,10 @@ fn parse_typed_struct_syntax_bigquery() {
961961
})],
962962
fields: vec![StructField {
963963
field_name: None,
964-
field_type: DataType::Interval,
964+
field_type: DataType::Interval {
965+
fields: None,
966+
precision: None
967+
},
965968
options: None,
966969
}]
967970
},
@@ -1300,7 +1303,10 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
13001303
})],
13011304
fields: vec![StructField {
13021305
field_name: None,
1303-
field_type: DataType::Interval,
1306+
field_type: DataType::Interval {
1307+
fields: None,
1308+
precision: None
1309+
},
13041310
options: None,
13051311
}]
13061312
},

tests/sqlparser_common.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12958,7 +12958,10 @@ fn test_extract_seconds_ok() {
1295812958
expr: Box::new(Expr::Value(
1295912959
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span()
1296012960
)),
12961-
data_type: DataType::Interval,
12961+
data_type: DataType::Interval {
12962+
fields: None,
12963+
precision: None
12964+
},
1296212965
format: None,
1296312966
}),
1296412967
}
@@ -12983,7 +12986,10 @@ fn test_extract_seconds_ok() {
1298312986
expr: Box::new(Expr::Value(
1298412987
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span(),
1298512988
)),
12986-
data_type: DataType::Interval,
12989+
data_type: DataType::Interval {
12990+
fields: None,
12991+
precision: None,
12992+
},
1298712993
format: None,
1298812994
}),
1298912995
})],
@@ -13037,7 +13043,10 @@ fn test_extract_seconds_single_quote_ok() {
1303713043
expr: Box::new(Expr::Value(
1303813044
(Value::SingleQuotedString("2 seconds".to_string())).with_empty_span()
1303913045
)),
13040-
data_type: DataType::Interval,
13046+
data_type: DataType::Interval {
13047+
fields: None,
13048+
precision: None
13049+
},
1304113050
format: None,
1304213051
}),
1304313052
}

tests/sqlparser_postgres.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5332,6 +5332,44 @@ fn parse_at_time_zone() {
53325332
);
53335333
}
53345334

5335+
#[test]
5336+
fn parse_interval_data_type() {
5337+
pg_and_generic().verified_stmt("CREATE TABLE t (i INTERVAL)");
5338+
for p in 0..=6 {
5339+
pg_and_generic().verified_stmt(&format!("CREATE TABLE t (i INTERVAL({p}))"));
5340+
pg_and_generic().verified_stmt(&format!("SELECT '1 second'::INTERVAL({p})"));
5341+
pg_and_generic().verified_stmt(&format!("SELECT CAST('1 second' AS INTERVAL({p}))"));
5342+
}
5343+
let fields = [
5344+
"YEAR",
5345+
"MONTH",
5346+
"DAY",
5347+
"HOUR",
5348+
"MINUTE",
5349+
"SECOND",
5350+
"YEAR TO MONTH",
5351+
"DAY TO HOUR",
5352+
"DAY TO MINUTE",
5353+
"DAY TO SECOND",
5354+
"HOUR TO MINUTE",
5355+
"HOUR TO SECOND",
5356+
"MINUTE TO SECOND",
5357+
];
5358+
for field in fields {
5359+
pg_and_generic().verified_stmt(&format!("CREATE TABLE t (i INTERVAL {field})"));
5360+
pg_and_generic().verified_stmt(&format!("SELECT '1 second'::INTERVAL {field}"));
5361+
pg_and_generic().verified_stmt(&format!("SELECT CAST('1 second' AS INTERVAL {field})"));
5362+
}
5363+
for p in 0..=6 {
5364+
for field in fields {
5365+
pg_and_generic().verified_stmt(&format!("CREATE TABLE t (i INTERVAL {field}({p}))"));
5366+
pg_and_generic().verified_stmt(&format!("SELECT '1 second'::INTERVAL {field}({p})"));
5367+
pg_and_generic()
5368+
.verified_stmt(&format!("SELECT CAST('1 second' AS INTERVAL {field}({p}))"));
5369+
}
5370+
}
5371+
}
5372+
53355373
#[test]
53365374
fn parse_create_table_with_options() {
53375375
let sql = "CREATE TABLE t (c INT) WITH (foo = 'bar', a = 123)";

0 commit comments

Comments
 (0)