Skip to content

Commit 95464ec

Browse files
AugustoFKLalamb
andauthored
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 <[email protected]>
1 parent 3beecc0 commit 95464ec

File tree

6 files changed

+142
-42
lines changed

6 files changed

+142
-42
lines changed

src/ast/data_type.rs

+52-8
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,11 @@ pub enum DataType {
9999
/// Date
100100
Date,
101101
/// Time
102-
Time,
102+
Time(TimezoneInfo),
103103
/// Datetime
104104
Datetime,
105-
/// Timestamp [Without Time Zone]
106-
Timestamp,
107-
/// Timestamp With Time Zone
108-
TimestampTz,
105+
/// Timestamp
106+
Timestamp(TimezoneInfo),
109107
/// Interval
110108
Interval,
111109
/// Regclass used in postgresql serial
@@ -190,10 +188,9 @@ impl fmt::Display for DataType {
190188
DataType::DoublePrecision => write!(f, "DOUBLE PRECISION"),
191189
DataType::Boolean => write!(f, "BOOLEAN"),
192190
DataType::Date => write!(f, "DATE"),
193-
DataType::Time => write!(f, "TIME"),
191+
DataType::Time(timezone_info) => write!(f, "TIME{}", timezone_info),
194192
DataType::Datetime => write!(f, "DATETIME"),
195-
DataType::Timestamp => write!(f, "TIMESTAMP"),
196-
DataType::TimestampTz => write!(f, "TIMESTAMPTZ"),
193+
DataType::Timestamp(timezone_info) => write!(f, "TIMESTAMP{}", timezone_info),
197194
DataType::Interval => write!(f, "INTERVAL"),
198195
DataType::Regclass => write!(f, "REGCLASS"),
199196
DataType::Text => write!(f, "TEXT"),
@@ -240,3 +237,50 @@ fn format_type_with_optional_length(
240237
}
241238
Ok(())
242239
}
240+
241+
/// Timestamp and Time data types information about TimeZone formatting.
242+
///
243+
/// This is more related to a display information than real differences between each variant. To
244+
/// guarantee compatibility with the input query we must maintain its exact information.
245+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
246+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
247+
pub enum TimezoneInfo {
248+
/// No information about time zone. E.g., TIMESTAMP
249+
None,
250+
/// Temporal type 'WITH TIME ZONE'. E.g., TIMESTAMP WITH TIME ZONE, [standard], [Oracle]
251+
///
252+
/// [standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type
253+
/// [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
254+
WithTimeZone,
255+
/// Temporal type 'WITHOUT TIME ZONE'. E.g., TIME WITHOUT TIME ZONE, [standard], [Postgresql]
256+
///
257+
/// [standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type
258+
/// [Postgresql]: https://www.postgresql.org/docs/current/datatype-datetime.html
259+
WithoutTimeZone,
260+
/// Postgresql specific `WITH TIME ZONE` formatting, for both TIME and TIMESTAMP. E.g., TIMETZ, [Postgresql]
261+
///
262+
/// [Postgresql]: https://www.postgresql.org/docs/current/datatype-datetime.html
263+
Tz,
264+
}
265+
266+
impl fmt::Display for TimezoneInfo {
267+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268+
match self {
269+
TimezoneInfo::None => {
270+
write!(f, "")
271+
}
272+
TimezoneInfo::WithTimeZone => {
273+
write!(f, " WITH TIME ZONE")
274+
}
275+
TimezoneInfo::WithoutTimeZone => {
276+
write!(f, " WITHOUT TIME ZONE")
277+
}
278+
TimezoneInfo::Tz => {
279+
// TZ is the only one that is displayed BEFORE the precision, so the datatype display
280+
// must be aware of that. Check <https://www.postgresql.org/docs/14/datatype-datetime.html>
281+
// for more information
282+
write!(f, "TZ")
283+
}
284+
}
285+
}
286+
}

src/ast/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use core::fmt;
2929
use serde::{Deserialize, Serialize};
3030

3131
pub use self::data_type::DataType;
32+
pub use self::data_type::TimezoneInfo;
3233
pub use self::ddl::{
3334
AlterColumnOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef,
3435
ReferentialAction, TableConstraint,

src/keywords.rs

+1
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ define_keywords!(
528528
TIME,
529529
TIMESTAMP,
530530
TIMESTAMPTZ,
531+
TIMETZ,
531532
TIMEZONE,
532533
TIMEZONE_HOUR,
533534
TIMEZONE_MINUTE,

src/parser.rs

+81-21
Original file line numberDiff line numberDiff line change
@@ -3412,22 +3412,27 @@ impl<'a> Parser<'a> {
34123412
Keyword::TIMESTAMP => {
34133413
if self.parse_keyword(Keyword::WITH) {
34143414
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
3415-
Ok(DataType::TimestampTz)
3415+
Ok(DataType::Timestamp(TimezoneInfo::WithTimeZone))
34163416
} else if self.parse_keyword(Keyword::WITHOUT) {
34173417
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
3418-
Ok(DataType::Timestamp)
3418+
Ok(DataType::Timestamp(TimezoneInfo::WithoutTimeZone))
34193419
} else {
3420-
Ok(DataType::Timestamp)
3420+
Ok(DataType::Timestamp(TimezoneInfo::None))
34213421
}
34223422
}
3423-
Keyword::TIMESTAMPTZ => Ok(DataType::TimestampTz),
3423+
Keyword::TIMESTAMPTZ => Ok(DataType::Timestamp(TimezoneInfo::Tz)),
34243424
Keyword::TIME => {
3425-
// TBD: we throw away "with/without timezone" information
3426-
if self.parse_keyword(Keyword::WITH) || self.parse_keyword(Keyword::WITHOUT) {
3425+
if self.parse_keyword(Keyword::WITH) {
3426+
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
3427+
Ok(DataType::Time(TimezoneInfo::WithTimeZone))
3428+
} else if self.parse_keyword(Keyword::WITHOUT) {
34273429
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
3430+
Ok(DataType::Time(TimezoneInfo::WithoutTimeZone))
3431+
} else {
3432+
Ok(DataType::Time(TimezoneInfo::None))
34283433
}
3429-
Ok(DataType::Time)
34303434
}
3435+
Keyword::TIMETZ => Ok(DataType::Time(TimezoneInfo::Tz)),
34313436
// Interval types can be followed by a complicated interval
34323437
// qualifier that we don't currently support. See
34333438
// parse_interval for a taste.
@@ -5265,23 +5270,78 @@ mod tests {
52655270
}
52665271

52675272
// TODO add tests for all data types? https://github.com/sqlparser-rs/sqlparser-rs/issues/2
5273+
// TODO when we have dialect validation by data type parsing, split test
52685274
#[test]
52695275
fn test_parse_data_type() {
5270-
test_parse_data_type("BLOB", "BLOB");
5271-
test_parse_data_type("BLOB(50)", "BLOB(50)");
5272-
test_parse_data_type("CLOB", "CLOB");
5273-
test_parse_data_type("CLOB(50)", "CLOB(50)");
5274-
test_parse_data_type("DOUBLE PRECISION", "DOUBLE PRECISION");
5275-
test_parse_data_type("DOUBLE", "DOUBLE");
5276-
test_parse_data_type("VARBINARY", "VARBINARY");
5277-
test_parse_data_type("VARBINARY(20)", "VARBINARY(20)");
5278-
test_parse_data_type("BINARY", "BINARY");
5279-
test_parse_data_type("BINARY(20)", "BINARY(20)");
5280-
5281-
fn test_parse_data_type(input: &str, expected: &str) {
5276+
// BINARY data type
5277+
test_parse_data_type("BINARY", DataType::Binary(None), "BINARY");
5278+
test_parse_data_type("BINARY(20)", DataType::Binary(Some(20)), "BINARY(20)");
5279+
5280+
// BLOB data type
5281+
test_parse_data_type("BLOB", DataType::Blob(None), "BLOB");
5282+
test_parse_data_type("BLOB(50)", DataType::Blob(Some(50)), "BLOB(50)");
5283+
5284+
// CLOB data type
5285+
test_parse_data_type("CLOB", DataType::Clob(None), "CLOB");
5286+
test_parse_data_type("CLOB(50)", DataType::Clob(Some(50)), "CLOB(50)");
5287+
5288+
// Double data type
5289+
test_parse_data_type(
5290+
"DOUBLE PRECISION",
5291+
DataType::DoublePrecision,
5292+
"DOUBLE PRECISION",
5293+
);
5294+
test_parse_data_type("DOUBLE", DataType::Double, "DOUBLE");
5295+
5296+
// Time data type
5297+
test_parse_data_type("TIME", DataType::Time(TimezoneInfo::None), "TIME");
5298+
test_parse_data_type(
5299+
"TIME WITH TIME ZONE",
5300+
DataType::Time(TimezoneInfo::WithTimeZone),
5301+
"TIME WITH TIME ZONE",
5302+
);
5303+
test_parse_data_type(
5304+
"TIME WITHOUT TIME ZONE",
5305+
DataType::Time(TimezoneInfo::WithoutTimeZone),
5306+
"TIME WITHOUT TIME ZONE",
5307+
);
5308+
test_parse_data_type("TIMETZ", DataType::Time(TimezoneInfo::Tz), "TIMETZ");
5309+
5310+
// Timestamp data type
5311+
test_parse_data_type(
5312+
"TIMESTAMP",
5313+
DataType::Timestamp(TimezoneInfo::None),
5314+
"TIMESTAMP",
5315+
);
5316+
test_parse_data_type(
5317+
"TIMESTAMP WITH TIME ZONE",
5318+
DataType::Timestamp(TimezoneInfo::WithTimeZone),
5319+
"TIMESTAMP WITH TIME ZONE",
5320+
);
5321+
test_parse_data_type(
5322+
"TIMESTAMP WITHOUT TIME ZONE",
5323+
DataType::Timestamp(TimezoneInfo::WithoutTimeZone),
5324+
"TIMESTAMP WITHOUT TIME ZONE",
5325+
);
5326+
test_parse_data_type(
5327+
"TIMESTAMPTZ",
5328+
DataType::Timestamp(TimezoneInfo::Tz),
5329+
"TIMESTAMPTZ",
5330+
);
5331+
5332+
// VARBINARY data type
5333+
test_parse_data_type("VARBINARY", DataType::Varbinary(None), "VARBINARY");
5334+
test_parse_data_type(
5335+
"VARBINARY(20)",
5336+
DataType::Varbinary(Some(20)),
5337+
"VARBINARY(20)",
5338+
);
5339+
5340+
fn test_parse_data_type(input: &str, expected_type: DataType, expected_str: &str) {
52825341
all_dialects().run_parser_method(input, |parser| {
5283-
let data_type = parser.parse_data_type().unwrap().to_string();
5284-
assert_eq!(data_type, expected);
5342+
let data_type = parser.parse_data_type().unwrap();
5343+
assert_eq!(data_type, expected_type);
5344+
assert_eq!(expected_type.to_string(), expected_str.to_string());
52855345
});
52865346
}
52875347
}

tests/sqlparser_common.rs

+5-11
Original file line numberDiff line numberDiff line change
@@ -2933,7 +2933,7 @@ fn parse_literal_time() {
29332933
let select = verified_only_select(sql);
29342934
assert_eq!(
29352935
&Expr::TypedString {
2936-
data_type: DataType::Time,
2936+
data_type: DataType::Time(TimezoneInfo::None),
29372937
value: "01:23:34".into()
29382938
},
29392939
expr_from_projection(only(&select.projection)),
@@ -2959,16 +2959,13 @@ fn parse_literal_timestamp_without_time_zone() {
29592959
let select = verified_only_select(sql);
29602960
assert_eq!(
29612961
&Expr::TypedString {
2962-
data_type: DataType::Timestamp,
2962+
data_type: DataType::Timestamp(TimezoneInfo::None),
29632963
value: "1999-01-01 01:23:34".into()
29642964
},
29652965
expr_from_projection(only(&select.projection)),
29662966
);
29672967

2968-
one_statement_parses_to(
2969-
"SELECT TIMESTAMP WITHOUT TIME ZONE '1999-01-01 01:23:34'",
2970-
sql,
2971-
);
2968+
one_statement_parses_to("SELECT TIMESTAMP '1999-01-01 01:23:34'", sql);
29722969
}
29732970

29742971
#[test]
@@ -2977,16 +2974,13 @@ fn parse_literal_timestamp_with_time_zone() {
29772974
let select = verified_only_select(sql);
29782975
assert_eq!(
29792976
&Expr::TypedString {
2980-
data_type: DataType::TimestampTz,
2977+
data_type: DataType::Timestamp(TimezoneInfo::Tz),
29812978
value: "1999-01-01 01:23:34Z".into()
29822979
},
29832980
expr_from_projection(only(&select.projection)),
29842981
);
29852982

2986-
one_statement_parses_to(
2987-
"SELECT TIMESTAMP WITH TIME ZONE '1999-01-01 01:23:34Z'",
2988-
sql,
2989-
);
2983+
one_statement_parses_to("SELECT TIMESTAMPTZ '1999-01-01 01:23:34Z'", sql);
29902984
}
29912985

29922986
#[test]

tests/sqlparser_postgres.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ fn parse_create_table_with_defaults() {
139139
},
140140
ColumnDef {
141141
name: "last_update".into(),
142-
data_type: DataType::Timestamp,
142+
data_type: DataType::Timestamp(TimezoneInfo::WithoutTimeZone),
143143
collation: None,
144144
options: vec![
145145
ColumnOptionDef {
@@ -212,7 +212,7 @@ fn parse_create_table_from_pg_dump() {
212212
activebool BOOLEAN DEFAULT true NOT NULL, \
213213
create_date DATE DEFAULT CAST(now() AS DATE) NOT NULL, \
214214
create_date1 DATE DEFAULT CAST(CAST('now' AS TEXT) AS DATE) NOT NULL, \
215-
last_update TIMESTAMP DEFAULT now(), \
215+
last_update TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), \
216216
release_year public.year, \
217217
active INT\
218218
)");

0 commit comments

Comments
 (0)