Skip to content
Merged
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
60 changes: 52 additions & 8 deletions src/ast/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,11 @@ pub enum DataType {
/// Date
Date,
/// Time
Time,
Time(TimezoneInfo),
/// Datetime
Datetime,
/// Timestamp [Without Time Zone]
Timestamp,
/// Timestamp With Time Zone
TimestampTz,
/// Timestamp
Timestamp(TimezoneInfo),
/// Interval
Interval,
/// Regclass used in postgresql serial
Expand Down Expand Up @@ -190,10 +188,9 @@ impl fmt::Display for DataType {
DataType::DoublePrecision => write!(f, "DOUBLE PRECISION"),
DataType::Boolean => write!(f, "BOOLEAN"),
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::Timestamp => write!(f, "TIMESTAMP"),
DataType::TimestampTz => write!(f, "TIMESTAMPTZ"),
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 Down Expand Up @@ -240,3 +237,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 @@ -29,6 +29,7 @@ use core::fmt;
use serde::{Deserialize, Serialize};

pub use self::data_type::DataType;
pub use self::data_type::TimezoneInfo;
pub use self::ddl::{
AlterColumnOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef,
ReferentialAction, TableConstraint,
Expand Down
1 change: 1 addition & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ define_keywords!(
TIME,
TIMESTAMP,
TIMESTAMPTZ,
TIMETZ,
TIMEZONE,
TIMEZONE_HOUR,
TIMEZONE_MINUTE,
Expand Down
112 changes: 86 additions & 26 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ use core::fmt;

use log::debug;

use IsLateral::*;
use IsOptional::*;

use crate::ast::*;
use crate::dialect::*;
use crate::keywords::{self, Keyword};
Expand Down Expand Up @@ -57,15 +60,11 @@ pub enum IsOptional {
Mandatory,
}

use IsOptional::*;

pub enum IsLateral {
Lateral,
NotLateral,
}

use IsLateral::*;

pub enum WildcardExpr {
Expr(Expr),
QualifiedWildcard(ObjectName),
Expand Down Expand Up @@ -3413,22 +3412,27 @@ impl<'a> Parser<'a> {
Keyword::TIMESTAMP => {
if self.parse_keyword(Keyword::WITH) {
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
Ok(DataType::TimestampTz)
Ok(DataType::Timestamp(TimezoneInfo::WithTimeZone))
} else if self.parse_keyword(Keyword::WITHOUT) {
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
Ok(DataType::Timestamp)
Ok(DataType::Timestamp(TimezoneInfo::WithoutTimeZone))
} else {
Ok(DataType::Timestamp)
Ok(DataType::Timestamp(TimezoneInfo::None))
}
}
Keyword::TIMESTAMPTZ => Ok(DataType::TimestampTz),
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 for a taste.
Expand Down Expand Up @@ -5197,9 +5201,10 @@ impl Word {

#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{all_dialects, TestedDialects};

use super::*;

#[test]
fn test_prev_index() {
let sql = "SELECT version";
Expand Down Expand Up @@ -5256,23 +5261,78 @@ mod tests {
}

// 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]
fn test_parse_data_type() {
test_parse_data_type("BLOB", "BLOB");
test_parse_data_type("BLOB(50)", "BLOB(50)");
test_parse_data_type("CLOB", "CLOB");
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) {
// BINARY data type
test_parse_data_type("BINARY", DataType::Binary(None), "BINARY");
test_parse_data_type("BINARY(20)", DataType::Binary(Some(20)), "BINARY(20)");

// 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| {
let data_type = parser.parse_data_type().unwrap().to_string();
assert_eq!(data_type, expected);
let data_type = parser.parse_data_type().unwrap();
assert_eq!(data_type, expected_type);
assert_eq!(expected_type.to_string(), expected_str.to_string());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

});
}
}
Expand Down
16 changes: 5 additions & 11 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2941,7 +2941,7 @@ 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)),
Expand All @@ -2967,16 +2967,13 @@ fn parse_literal_timestamp_without_time_zone() {
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 WITHOUT TIME ZONE '1999-01-01 01:23:34'",
sql,
);
one_statement_parses_to("SELECT TIMESTAMP '1999-01-01 01:23:34'", sql);
}

#[test]
Expand All @@ -2985,16 +2982,13 @@ fn parse_literal_timestamp_with_time_zone() {
let select = verified_only_select(sql);
assert_eq!(
&Expr::TypedString {
data_type: DataType::TimestampTz,
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 TIMESTAMP WITH TIME ZONE '1999-01-01 01:23:34Z'",
sql,
);
one_statement_parses_to("SELECT TIMESTAMPTZ '1999-01-01 01:23:34Z'", sql);
}

#[test]
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