Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions src/ast/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ pub enum DataType {
/// Date
Date,
/// Time
Time,
Time(TimezoneInfo),
/// Datetime
Datetime,
/// Timestamp
Timestamp,
Timestamp(TimezoneInfo),
/// Interval
Interval,
/// Regclass used in postgresql serial
Expand Down Expand Up @@ -98,8 +100,9 @@ impl fmt::Display for DataType {
DataType::Double => write!(f, "DOUBLE"),
DataType::Boolean => write!(f, "BOOLEAN"),
DataType::Date => write!(f, "DATE"),
DataType::Time => write!(f, "TIME"),
DataType::Timestamp => write!(f, "TIMESTAMP"),
DataType::Datetime => write!(f, "DATETIME"),
DataType::Time(timezone_info) => write!(f, "TIME{}", timezone_info),
DataType::Timestamp(timezone_info) => write!(f, "TIMESTAMP{}", timezone_info),
DataType::Interval => write!(f, "INTERVAL"),
DataType::Regclass => write!(f, "REGCLASS"),
DataType::Text => write!(f, "TEXT"),
Expand All @@ -122,3 +125,50 @@ fn format_type_with_optional_length(
}
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")
}
}
}
}
1 change: 1 addition & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use serde::{Deserialize, Serialize};
use std::fmt;

pub use self::data_type::DataType;
pub use self::data_type::TimezoneInfo;
pub use self::ddl::{
AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, ReferentialAction,
TableConstraint,
Expand Down
3 changes: 3 additions & 0 deletions src/dialect/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,9 @@ define_keywords!(
TIES,
TIME,
TIMESTAMP,
TIMESTAMPTZ,
TIMETZ,
TIMEZONE,
TIMEZONE_HOUR,
TIMEZONE_MINUTE,
TO,
Expand Down
22 changes: 16 additions & 6 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2024,19 +2024,29 @@ impl<'a> Parser<'a> {
Keyword::UUID => Ok(DataType::Uuid),
Keyword::DATE => Ok(DataType::Date),
Keyword::TIMESTAMP => {
// TBD: we throw away "with/without timezone" information
if self.parse_keyword(Keyword::WITH) || self.parse_keyword(Keyword::WITHOUT) {
if self.parse_keyword(Keyword::WITH) {
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
Ok(DataType::Timestamp(TimezoneInfo::WithTimeZone))
} else if self.parse_keyword(Keyword::WITHOUT) {
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
Ok(DataType::Timestamp(TimezoneInfo::WithoutTimeZone))
} else {
Ok(DataType::Timestamp(TimezoneInfo::None))
}
Ok(DataType::Timestamp)
}
Keyword::TIMESTAMPTZ => Ok(DataType::Timestamp(TimezoneInfo::Tz)),
Keyword::TIME => {
// TBD: we throw away "with/without timezone" information
if self.parse_keyword(Keyword::WITH) || self.parse_keyword(Keyword::WITHOUT) {
if self.parse_keyword(Keyword::WITH) {
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
// qualifier that we don't currently support. See
// parse_interval_literal for a taste.
Expand Down
2 changes: 1 addition & 1 deletion src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ impl<'a> Tokenizer<'a> {
'r' => s.push('\r'),
't' => s.push('\t'),
'Z' => s.push('\x1a'),
x => s.push(x)
x => s.push(x),
}
}
}
Expand Down
31 changes: 24 additions & 7 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1885,24 +1885,41 @@ fn parse_literal_time() {
let select = verified_only_select(sql);
assert_eq!(
&Expr::TypedString {
data_type: DataType::Time,
data_type: DataType::Time(TimezoneInfo::None),
value: "01:23:34".into()
},
expr_from_projection(only(&select.projection)),
);
}

#[test]
fn parse_literal_timestamp() {
fn parse_literal_timestamp_without_time_zone() {
let sql = "SELECT TIMESTAMP '1999-01-01 01:23:34'";
let select = verified_only_select(sql);
assert_eq!(
&Expr::TypedString {
data_type: DataType::Timestamp,
data_type: DataType::Timestamp(TimezoneInfo::None),
value: "1999-01-01 01:23:34".into()
},
expr_from_projection(only(&select.projection)),
);

one_statement_parses_to("SELECT TIMESTAMP '1999-01-01 01:23:34'", sql);
}

#[test]
fn parse_literal_timestamp_with_time_zone() {
let sql = "SELECT TIMESTAMPTZ '1999-01-01 01:23:34Z'";
let select = verified_only_select(sql);
assert_eq!(
&Expr::TypedString {
data_type: DataType::Timestamp(TimezoneInfo::Tz),
value: "1999-01-01 01:23:34Z".into()
},
expr_from_projection(only(&select.projection)),
);

one_statement_parses_to("SELECT TIMESTAMPTZ '1999-01-01 01:23:34Z'", sql);
}

#[test]
Expand Down Expand Up @@ -2715,10 +2732,10 @@ fn parse_scalar_subqueries() {
assert_matches!(
verified_expr(sql),
Expr::BinaryOp {
op: BinaryOperator::Plus, ..
//left: box Subquery { .. },
//right: box Subquery { .. },
}
op: BinaryOperator::Plus,
.. //left: box Subquery { .. },
//right: box Subquery { .. },
}
);
}

Expand Down
4 changes: 2 additions & 2 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ fn parse_create_table_with_defaults() {
},
ColumnDef {
name: "last_update".into(),
data_type: DataType::Timestamp,
data_type: DataType::Timestamp(TimezoneInfo::WithoutTimeZone),
collation: None,
options: vec![
ColumnOptionDef {
Expand Down Expand Up @@ -212,7 +212,7 @@ fn parse_create_table_from_pg_dump() {
activebool BOOLEAN DEFAULT true 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, \
last_update TIMESTAMP DEFAULT now(), \
last_update TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), \
release_year public.year, \
active INT\
)");
Expand Down