Skip to content

Commit c02efae

Browse files
AugustoFKLalamb
authored andcommitted
Change TIMESTAMP and TIME parsing so that time zone information is preserved (apache#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>
1 parent a71d038 commit c02efae

File tree

6 files changed

+76
-28
lines changed

6 files changed

+76
-28
lines changed

src/ast/data_type.rs

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ pub enum DataType {
5252
/// Date
5353
Date,
5454
/// Time
55-
Time,
56-
/// Timestamp [Without Time Zone]
57-
Timestamp,
58-
/// Timestamp With Time Zone
59-
TimestampTz,
55+
Time(TimezoneInfo),
56+
/// Datetime
57+
Datetime,
58+
/// Timestamp
59+
Timestamp(TimezoneInfo),
6060
/// Interval
6161
Interval,
6262
/// Regclass used in postgresql serial
@@ -100,9 +100,9 @@ impl fmt::Display for DataType {
100100
DataType::Double => write!(f, "DOUBLE"),
101101
DataType::Boolean => write!(f, "BOOLEAN"),
102102
DataType::Date => write!(f, "DATE"),
103-
DataType::Time => write!(f, "TIME"),
104-
DataType::Timestamp => write!(f, "TIMESTAMP"),
105-
DataType::TimestampTz => write!(f, "TIMESTAMPTZ"),
103+
DataType::Datetime => write!(f, "DATETIME"),
104+
DataType::Time(timezone_info) => write!(f, "TIME{}", timezone_info),
105+
DataType::Timestamp(timezone_info) => write!(f, "TIMESTAMP{}", timezone_info),
106106
DataType::Interval => write!(f, "INTERVAL"),
107107
DataType::Regclass => write!(f, "REGCLASS"),
108108
DataType::Text => write!(f, "TEXT"),
@@ -125,3 +125,50 @@ fn format_type_with_optional_length(
125125
}
126126
Ok(())
127127
}
128+
129+
/// Timestamp and Time data types information about TimeZone formatting.
130+
///
131+
/// This is more related to a display information than real differences between each variant. To
132+
/// guarantee compatibility with the input query we must maintain its exact information.
133+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
134+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
135+
pub enum TimezoneInfo {
136+
/// No information about time zone. E.g., TIMESTAMP
137+
None,
138+
/// Temporal type 'WITH TIME ZONE'. E.g., TIMESTAMP WITH TIME ZONE, [standard], [Oracle]
139+
///
140+
/// [standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type
141+
/// [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
142+
WithTimeZone,
143+
/// Temporal type 'WITHOUT TIME ZONE'. E.g., TIME WITHOUT TIME ZONE, [standard], [Postgresql]
144+
///
145+
/// [standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type
146+
/// [Postgresql]: https://www.postgresql.org/docs/current/datatype-datetime.html
147+
WithoutTimeZone,
148+
/// Postgresql specific `WITH TIME ZONE` formatting, for both TIME and TIMESTAMP. E.g., TIMETZ, [Postgresql]
149+
///
150+
/// [Postgresql]: https://www.postgresql.org/docs/current/datatype-datetime.html
151+
Tz,
152+
}
153+
154+
impl fmt::Display for TimezoneInfo {
155+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156+
match self {
157+
TimezoneInfo::None => {
158+
write!(f, "")
159+
}
160+
TimezoneInfo::WithTimeZone => {
161+
write!(f, " WITH TIME ZONE")
162+
}
163+
TimezoneInfo::WithoutTimeZone => {
164+
write!(f, " WITHOUT TIME ZONE")
165+
}
166+
TimezoneInfo::Tz => {
167+
// TZ is the only one that is displayed BEFORE the precision, so the datatype display
168+
// must be aware of that. Check <https://www.postgresql.org/docs/14/datatype-datetime.html>
169+
// for more information
170+
write!(f, "TZ")
171+
}
172+
}
173+
}
174+
}

src/ast/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use serde::{Deserialize, Serialize};
2323
use std::fmt;
2424

2525
pub use self::data_type::DataType;
26+
pub use self::data_type::TimezoneInfo;
2627
pub use self::ddl::{
2728
AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, ReferentialAction,
2829
TableConstraint,

src/dialect/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ define_keywords!(
446446
TIME,
447447
TIMESTAMP,
448448
TIMESTAMPTZ,
449+
TIMETZ,
449450
TIMEZONE,
450451
TIMEZONE_HOUR,
451452
TIMEZONE_MINUTE,

src/parser.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,22 +2026,27 @@ impl<'a> Parser<'a> {
20262026
Keyword::TIMESTAMP => {
20272027
if self.parse_keyword(Keyword::WITH) {
20282028
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
2029-
Ok(DataType::TimestampTz)
2029+
Ok(DataType::Timestamp(TimezoneInfo::WithTimeZone))
20302030
} else if self.parse_keyword(Keyword::WITHOUT) {
20312031
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
2032-
Ok(DataType::Timestamp)
2032+
Ok(DataType::Timestamp(TimezoneInfo::WithoutTimeZone))
20332033
} else {
2034-
Ok(DataType::Timestamp)
2034+
Ok(DataType::Timestamp(TimezoneInfo::None))
20352035
}
20362036
}
2037-
Keyword::TIMESTAMPTZ => Ok(DataType::TimestampTz),
2037+
Keyword::TIMESTAMPTZ => Ok(DataType::Timestamp(TimezoneInfo::Tz)),
20382038
Keyword::TIME => {
2039-
// TBD: we throw away "with/without timezone" information
2040-
if self.parse_keyword(Keyword::WITH) || self.parse_keyword(Keyword::WITHOUT) {
2039+
if self.parse_keyword(Keyword::WITH) {
2040+
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
2041+
Ok(DataType::Time(TimezoneInfo::WithTimeZone))
2042+
} else if self.parse_keyword(Keyword::WITHOUT) {
20412043
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
2044+
Ok(DataType::Time(TimezoneInfo::WithoutTimeZone))
2045+
} else {
2046+
Ok(DataType::Time(TimezoneInfo::None))
20422047
}
2043-
Ok(DataType::Time)
20442048
}
2049+
Keyword::TIMETZ => Ok(DataType::Time(TimezoneInfo::Tz)),
20452050
// Interval types can be followed by a complicated interval
20462051
// qualifier that we don't currently support. See
20472052
// parse_interval_literal for a taste.

tests/sqlparser_common.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,7 +1885,7 @@ fn parse_literal_time() {
18851885
let select = verified_only_select(sql);
18861886
assert_eq!(
18871887
&Expr::TypedString {
1888-
data_type: DataType::Time,
1888+
data_type: DataType::Time(TimezoneInfo::None),
18891889
value: "01:23:34".into()
18901890
},
18911891
expr_from_projection(only(&select.projection)),
@@ -1898,16 +1898,13 @@ fn parse_literal_timestamp_without_time_zone() {
18981898
let select = verified_only_select(sql);
18991899
assert_eq!(
19001900
&Expr::TypedString {
1901-
data_type: DataType::Timestamp,
1901+
data_type: DataType::Timestamp(TimezoneInfo::None),
19021902
value: "1999-01-01 01:23:34".into()
19031903
},
19041904
expr_from_projection(only(&select.projection)),
19051905
);
19061906

1907-
one_statement_parses_to(
1908-
"SELECT TIMESTAMP WITHOUT TIME ZONE '1999-01-01 01:23:34'",
1909-
sql,
1910-
);
1907+
one_statement_parses_to("SELECT TIMESTAMP '1999-01-01 01:23:34'", sql);
19111908
}
19121909

19131910
#[test]
@@ -1916,16 +1913,13 @@ fn parse_literal_timestamp_with_time_zone() {
19161913
let select = verified_only_select(sql);
19171914
assert_eq!(
19181915
&Expr::TypedString {
1919-
data_type: DataType::TimestampTz,
1916+
data_type: DataType::Timestamp(TimezoneInfo::Tz),
19201917
value: "1999-01-01 01:23:34Z".into()
19211918
},
19221919
expr_from_projection(only(&select.projection)),
19231920
);
19241921

1925-
one_statement_parses_to(
1926-
"SELECT TIMESTAMP WITH TIME ZONE '1999-01-01 01:23:34Z'",
1927-
sql,
1928-
);
1922+
one_statement_parses_to("SELECT TIMESTAMPTZ '1999-01-01 01:23:34Z'", sql);
19291923
}
19301924

19311925
#[test]

tests/sqlparser_postgres.rs

Lines changed: 2 additions & 2 deletions
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)