mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-04 06:18:17 +00:00
Change TIMESTAMP
and TIME
parsing so that time zone information is preserved (#641)
* 640 Fixing time zone printing format for TIMESTAMP and TIME * 640 Removing unnecessary changes * Update src/ast/data_type.rs Fix comment typo Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
parent
3beecc0a7a
commit
95464ec72c
6 changed files with 141 additions and 41 deletions
|
@ -99,13 +99,11 @@ pub enum DataType {
|
||||||
/// Date
|
/// Date
|
||||||
Date,
|
Date,
|
||||||
/// Time
|
/// Time
|
||||||
Time,
|
Time(TimezoneInfo),
|
||||||
/// Datetime
|
/// Datetime
|
||||||
Datetime,
|
Datetime,
|
||||||
/// Timestamp [Without Time Zone]
|
/// Timestamp
|
||||||
Timestamp,
|
Timestamp(TimezoneInfo),
|
||||||
/// Timestamp With Time Zone
|
|
||||||
TimestampTz,
|
|
||||||
/// Interval
|
/// Interval
|
||||||
Interval,
|
Interval,
|
||||||
/// Regclass used in postgresql serial
|
/// Regclass used in postgresql serial
|
||||||
|
@ -190,10 +188,9 @@ impl fmt::Display for DataType {
|
||||||
DataType::DoublePrecision => write!(f, "DOUBLE PRECISION"),
|
DataType::DoublePrecision => write!(f, "DOUBLE PRECISION"),
|
||||||
DataType::Boolean => write!(f, "BOOLEAN"),
|
DataType::Boolean => write!(f, "BOOLEAN"),
|
||||||
DataType::Date => write!(f, "DATE"),
|
DataType::Date => write!(f, "DATE"),
|
||||||
DataType::Time => write!(f, "TIME"),
|
DataType::Time(timezone_info) => write!(f, "TIME{}", timezone_info),
|
||||||
DataType::Datetime => write!(f, "DATETIME"),
|
DataType::Datetime => write!(f, "DATETIME"),
|
||||||
DataType::Timestamp => write!(f, "TIMESTAMP"),
|
DataType::Timestamp(timezone_info) => write!(f, "TIMESTAMP{}", timezone_info),
|
||||||
DataType::TimestampTz => write!(f, "TIMESTAMPTZ"),
|
|
||||||
DataType::Interval => write!(f, "INTERVAL"),
|
DataType::Interval => write!(f, "INTERVAL"),
|
||||||
DataType::Regclass => write!(f, "REGCLASS"),
|
DataType::Regclass => write!(f, "REGCLASS"),
|
||||||
DataType::Text => write!(f, "TEXT"),
|
DataType::Text => write!(f, "TEXT"),
|
||||||
|
@ -240,3 +237,50 @@ fn format_type_with_optional_length(
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Timestamp and Time data types information about TimeZone formatting.
|
||||||
|
///
|
||||||
|
/// This is more related to a display information than real differences between each variant. To
|
||||||
|
/// guarantee compatibility with the input query we must maintain its exact information.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum TimezoneInfo {
|
||||||
|
/// No information about time zone. E.g., TIMESTAMP
|
||||||
|
None,
|
||||||
|
/// Temporal type 'WITH TIME ZONE'. E.g., TIMESTAMP WITH TIME ZONE, [standard], [Oracle]
|
||||||
|
///
|
||||||
|
/// [standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type
|
||||||
|
/// [Oracle]: https://docs.oracle.com/en/database/oracle/oracle-database/12.2/nlspg/datetime-data-types-and-time-zone-support.html#GUID-3F1C388E-C651-43D5-ADBC-1A49E5C2CA05
|
||||||
|
WithTimeZone,
|
||||||
|
/// Temporal type 'WITHOUT TIME ZONE'. E.g., TIME WITHOUT TIME ZONE, [standard], [Postgresql]
|
||||||
|
///
|
||||||
|
/// [standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type
|
||||||
|
/// [Postgresql]: https://www.postgresql.org/docs/current/datatype-datetime.html
|
||||||
|
WithoutTimeZone,
|
||||||
|
/// Postgresql specific `WITH TIME ZONE` formatting, for both TIME and TIMESTAMP. E.g., TIMETZ, [Postgresql]
|
||||||
|
///
|
||||||
|
/// [Postgresql]: https://www.postgresql.org/docs/current/datatype-datetime.html
|
||||||
|
Tz,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TimezoneInfo {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
TimezoneInfo::None => {
|
||||||
|
write!(f, "")
|
||||||
|
}
|
||||||
|
TimezoneInfo::WithTimeZone => {
|
||||||
|
write!(f, " WITH TIME ZONE")
|
||||||
|
}
|
||||||
|
TimezoneInfo::WithoutTimeZone => {
|
||||||
|
write!(f, " WITHOUT TIME ZONE")
|
||||||
|
}
|
||||||
|
TimezoneInfo::Tz => {
|
||||||
|
// TZ is the only one that is displayed BEFORE the precision, so the datatype display
|
||||||
|
// must be aware of that. Check <https://www.postgresql.org/docs/14/datatype-datetime.html>
|
||||||
|
// for more information
|
||||||
|
write!(f, "TZ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ use core::fmt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub use self::data_type::DataType;
|
pub use self::data_type::DataType;
|
||||||
|
pub use self::data_type::TimezoneInfo;
|
||||||
pub use self::ddl::{
|
pub use self::ddl::{
|
||||||
AlterColumnOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef,
|
AlterColumnOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef,
|
||||||
ReferentialAction, TableConstraint,
|
ReferentialAction, TableConstraint,
|
||||||
|
|
|
@ -528,6 +528,7 @@ define_keywords!(
|
||||||
TIME,
|
TIME,
|
||||||
TIMESTAMP,
|
TIMESTAMP,
|
||||||
TIMESTAMPTZ,
|
TIMESTAMPTZ,
|
||||||
|
TIMETZ,
|
||||||
TIMEZONE,
|
TIMEZONE,
|
||||||
TIMEZONE_HOUR,
|
TIMEZONE_HOUR,
|
||||||
TIMEZONE_MINUTE,
|
TIMEZONE_MINUTE,
|
||||||
|
|
100
src/parser.rs
100
src/parser.rs
|
@ -3412,22 +3412,27 @@ impl<'a> Parser<'a> {
|
||||||
Keyword::TIMESTAMP => {
|
Keyword::TIMESTAMP => {
|
||||||
if self.parse_keyword(Keyword::WITH) {
|
if self.parse_keyword(Keyword::WITH) {
|
||||||
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
|
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
|
||||||
Ok(DataType::TimestampTz)
|
Ok(DataType::Timestamp(TimezoneInfo::WithTimeZone))
|
||||||
} else if self.parse_keyword(Keyword::WITHOUT) {
|
} else if self.parse_keyword(Keyword::WITHOUT) {
|
||||||
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
|
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
|
||||||
Ok(DataType::Timestamp)
|
Ok(DataType::Timestamp(TimezoneInfo::WithoutTimeZone))
|
||||||
} else {
|
} else {
|
||||||
Ok(DataType::Timestamp)
|
Ok(DataType::Timestamp(TimezoneInfo::None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keyword::TIMESTAMPTZ => Ok(DataType::TimestampTz),
|
Keyword::TIMESTAMPTZ => Ok(DataType::Timestamp(TimezoneInfo::Tz)),
|
||||||
Keyword::TIME => {
|
Keyword::TIME => {
|
||||||
// TBD: we throw away "with/without timezone" information
|
if self.parse_keyword(Keyword::WITH) {
|
||||||
if self.parse_keyword(Keyword::WITH) || self.parse_keyword(Keyword::WITHOUT) {
|
|
||||||
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
|
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
|
||||||
|
Ok(DataType::Time(TimezoneInfo::WithTimeZone))
|
||||||
|
} else if self.parse_keyword(Keyword::WITHOUT) {
|
||||||
|
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
|
||||||
|
Ok(DataType::Time(TimezoneInfo::WithoutTimeZone))
|
||||||
|
} else {
|
||||||
|
Ok(DataType::Time(TimezoneInfo::None))
|
||||||
}
|
}
|
||||||
Ok(DataType::Time)
|
|
||||||
}
|
}
|
||||||
|
Keyword::TIMETZ => Ok(DataType::Time(TimezoneInfo::Tz)),
|
||||||
// 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 for a taste.
|
// parse_interval for a taste.
|
||||||
|
@ -5265,23 +5270,78 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add tests for all data types? https://github.com/sqlparser-rs/sqlparser-rs/issues/2
|
// TODO add tests for all data types? https://github.com/sqlparser-rs/sqlparser-rs/issues/2
|
||||||
|
// TODO when we have dialect validation by data type parsing, split test
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_data_type() {
|
fn test_parse_data_type() {
|
||||||
test_parse_data_type("BLOB", "BLOB");
|
// BINARY data type
|
||||||
test_parse_data_type("BLOB(50)", "BLOB(50)");
|
test_parse_data_type("BINARY", DataType::Binary(None), "BINARY");
|
||||||
test_parse_data_type("CLOB", "CLOB");
|
test_parse_data_type("BINARY(20)", DataType::Binary(Some(20)), "BINARY(20)");
|
||||||
test_parse_data_type("CLOB(50)", "CLOB(50)");
|
|
||||||
test_parse_data_type("DOUBLE PRECISION", "DOUBLE PRECISION");
|
|
||||||
test_parse_data_type("DOUBLE", "DOUBLE");
|
|
||||||
test_parse_data_type("VARBINARY", "VARBINARY");
|
|
||||||
test_parse_data_type("VARBINARY(20)", "VARBINARY(20)");
|
|
||||||
test_parse_data_type("BINARY", "BINARY");
|
|
||||||
test_parse_data_type("BINARY(20)", "BINARY(20)");
|
|
||||||
|
|
||||||
fn test_parse_data_type(input: &str, expected: &str) {
|
// BLOB data type
|
||||||
|
test_parse_data_type("BLOB", DataType::Blob(None), "BLOB");
|
||||||
|
test_parse_data_type("BLOB(50)", DataType::Blob(Some(50)), "BLOB(50)");
|
||||||
|
|
||||||
|
// CLOB data type
|
||||||
|
test_parse_data_type("CLOB", DataType::Clob(None), "CLOB");
|
||||||
|
test_parse_data_type("CLOB(50)", DataType::Clob(Some(50)), "CLOB(50)");
|
||||||
|
|
||||||
|
// Double data type
|
||||||
|
test_parse_data_type(
|
||||||
|
"DOUBLE PRECISION",
|
||||||
|
DataType::DoublePrecision,
|
||||||
|
"DOUBLE PRECISION",
|
||||||
|
);
|
||||||
|
test_parse_data_type("DOUBLE", DataType::Double, "DOUBLE");
|
||||||
|
|
||||||
|
// Time data type
|
||||||
|
test_parse_data_type("TIME", DataType::Time(TimezoneInfo::None), "TIME");
|
||||||
|
test_parse_data_type(
|
||||||
|
"TIME WITH TIME ZONE",
|
||||||
|
DataType::Time(TimezoneInfo::WithTimeZone),
|
||||||
|
"TIME WITH TIME ZONE",
|
||||||
|
);
|
||||||
|
test_parse_data_type(
|
||||||
|
"TIME WITHOUT TIME ZONE",
|
||||||
|
DataType::Time(TimezoneInfo::WithoutTimeZone),
|
||||||
|
"TIME WITHOUT TIME ZONE",
|
||||||
|
);
|
||||||
|
test_parse_data_type("TIMETZ", DataType::Time(TimezoneInfo::Tz), "TIMETZ");
|
||||||
|
|
||||||
|
// Timestamp data type
|
||||||
|
test_parse_data_type(
|
||||||
|
"TIMESTAMP",
|
||||||
|
DataType::Timestamp(TimezoneInfo::None),
|
||||||
|
"TIMESTAMP",
|
||||||
|
);
|
||||||
|
test_parse_data_type(
|
||||||
|
"TIMESTAMP WITH TIME ZONE",
|
||||||
|
DataType::Timestamp(TimezoneInfo::WithTimeZone),
|
||||||
|
"TIMESTAMP WITH TIME ZONE",
|
||||||
|
);
|
||||||
|
test_parse_data_type(
|
||||||
|
"TIMESTAMP WITHOUT TIME ZONE",
|
||||||
|
DataType::Timestamp(TimezoneInfo::WithoutTimeZone),
|
||||||
|
"TIMESTAMP WITHOUT TIME ZONE",
|
||||||
|
);
|
||||||
|
test_parse_data_type(
|
||||||
|
"TIMESTAMPTZ",
|
||||||
|
DataType::Timestamp(TimezoneInfo::Tz),
|
||||||
|
"TIMESTAMPTZ",
|
||||||
|
);
|
||||||
|
|
||||||
|
// VARBINARY data type
|
||||||
|
test_parse_data_type("VARBINARY", DataType::Varbinary(None), "VARBINARY");
|
||||||
|
test_parse_data_type(
|
||||||
|
"VARBINARY(20)",
|
||||||
|
DataType::Varbinary(Some(20)),
|
||||||
|
"VARBINARY(20)",
|
||||||
|
);
|
||||||
|
|
||||||
|
fn test_parse_data_type(input: &str, expected_type: DataType, expected_str: &str) {
|
||||||
all_dialects().run_parser_method(input, |parser| {
|
all_dialects().run_parser_method(input, |parser| {
|
||||||
let data_type = parser.parse_data_type().unwrap().to_string();
|
let data_type = parser.parse_data_type().unwrap();
|
||||||
assert_eq!(data_type, expected);
|
assert_eq!(data_type, expected_type);
|
||||||
|
assert_eq!(expected_type.to_string(), expected_str.to_string());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2933,7 +2933,7 @@ fn parse_literal_time() {
|
||||||
let select = verified_only_select(sql);
|
let select = verified_only_select(sql);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&Expr::TypedString {
|
&Expr::TypedString {
|
||||||
data_type: DataType::Time,
|
data_type: DataType::Time(TimezoneInfo::None),
|
||||||
value: "01:23:34".into()
|
value: "01:23:34".into()
|
||||||
},
|
},
|
||||||
expr_from_projection(only(&select.projection)),
|
expr_from_projection(only(&select.projection)),
|
||||||
|
@ -2959,16 +2959,13 @@ fn parse_literal_timestamp_without_time_zone() {
|
||||||
let select = verified_only_select(sql);
|
let select = verified_only_select(sql);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&Expr::TypedString {
|
&Expr::TypedString {
|
||||||
data_type: DataType::Timestamp,
|
data_type: DataType::Timestamp(TimezoneInfo::None),
|
||||||
value: "1999-01-01 01:23:34".into()
|
value: "1999-01-01 01:23:34".into()
|
||||||
},
|
},
|
||||||
expr_from_projection(only(&select.projection)),
|
expr_from_projection(only(&select.projection)),
|
||||||
);
|
);
|
||||||
|
|
||||||
one_statement_parses_to(
|
one_statement_parses_to("SELECT TIMESTAMP '1999-01-01 01:23:34'", sql);
|
||||||
"SELECT TIMESTAMP WITHOUT TIME ZONE '1999-01-01 01:23:34'",
|
|
||||||
sql,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2977,16 +2974,13 @@ fn parse_literal_timestamp_with_time_zone() {
|
||||||
let select = verified_only_select(sql);
|
let select = verified_only_select(sql);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&Expr::TypedString {
|
&Expr::TypedString {
|
||||||
data_type: DataType::TimestampTz,
|
data_type: DataType::Timestamp(TimezoneInfo::Tz),
|
||||||
value: "1999-01-01 01:23:34Z".into()
|
value: "1999-01-01 01:23:34Z".into()
|
||||||
},
|
},
|
||||||
expr_from_projection(only(&select.projection)),
|
expr_from_projection(only(&select.projection)),
|
||||||
);
|
);
|
||||||
|
|
||||||
one_statement_parses_to(
|
one_statement_parses_to("SELECT TIMESTAMPTZ '1999-01-01 01:23:34Z'", sql);
|
||||||
"SELECT TIMESTAMP WITH TIME ZONE '1999-01-01 01:23:34Z'",
|
|
||||||
sql,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -139,7 +139,7 @@ fn parse_create_table_with_defaults() {
|
||||||
},
|
},
|
||||||
ColumnDef {
|
ColumnDef {
|
||||||
name: "last_update".into(),
|
name: "last_update".into(),
|
||||||
data_type: DataType::Timestamp,
|
data_type: DataType::Timestamp(TimezoneInfo::WithoutTimeZone),
|
||||||
collation: None,
|
collation: None,
|
||||||
options: vec![
|
options: vec![
|
||||||
ColumnOptionDef {
|
ColumnOptionDef {
|
||||||
|
@ -212,7 +212,7 @@ fn parse_create_table_from_pg_dump() {
|
||||||
activebool BOOLEAN DEFAULT true NOT NULL, \
|
activebool BOOLEAN DEFAULT true NOT NULL, \
|
||||||
create_date DATE DEFAULT CAST(now() AS DATE) NOT NULL, \
|
create_date DATE DEFAULT CAST(now() AS DATE) NOT NULL, \
|
||||||
create_date1 DATE DEFAULT CAST(CAST('now' AS TEXT) AS DATE) NOT NULL, \
|
create_date1 DATE DEFAULT CAST(CAST('now' AS TEXT) AS DATE) NOT NULL, \
|
||||||
last_update TIMESTAMP DEFAULT now(), \
|
last_update TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), \
|
||||||
release_year public.year, \
|
release_year public.year, \
|
||||||
active INT\
|
active INT\
|
||||||
)");
|
)");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue