feat: add precision for TIME, DATETIME, and TIMESTAMP data types (#701)

Now all those statements are both parsed and displayed with precision
and timezone info. Tests were added to the ones presented in the ANSI
standard.
This commit is contained in:
Augusto Fotino 2022-11-11 18:06:57 -03:00 committed by GitHub
parent cdf4447065
commit 65c5a37bce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 108 additions and 40 deletions

View file

@ -11,7 +11,7 @@
// limitations under the License. // limitations under the License.
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{boxed::Box, string::String, vec::Vec}; use alloc::{boxed::Box, format, string::String, vec::Vec};
use core::fmt; use core::fmt;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
@ -122,12 +122,18 @@ pub enum DataType {
Boolean, Boolean,
/// Date /// Date
Date, Date,
/// Time /// Time with optional time precision and time zone information e.g. [standard][1].
Time(TimezoneInfo), ///
/// Datetime /// [1]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type
Datetime, Time(Option<u64>, TimezoneInfo),
/// Timestamp /// Datetime with optional time precision e.g. [MySQL][1].
Timestamp(TimezoneInfo), ///
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/datetime.html
Datetime(Option<u64>),
/// Timestamp with optional time precision and time zone information e.g. [standard][1].
///
/// [1]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type
Timestamp(Option<u64>, TimezoneInfo),
/// Interval /// Interval
Interval, Interval,
/// Regclass used in postgresql serial /// Regclass used in postgresql serial
@ -224,9 +230,15 @@ 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(timezone_info) => write!(f, "TIME{}", timezone_info), DataType::Time(precision, timezone_info) => {
DataType::Datetime => write!(f, "DATETIME"), format_datetime_precision_and_tz(f, "TIME", precision, timezone_info)
DataType::Timestamp(timezone_info) => write!(f, "TIMESTAMP{}", timezone_info), }
DataType::Datetime(precision) => {
format_type_with_optional_length(f, "DATETIME", precision, false)
}
DataType::Timestamp(precision, timezone_info) => {
format_datetime_precision_and_tz(f, "TIMESTAMP", precision, timezone_info)
}
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"),
@ -298,6 +310,27 @@ fn format_character_string_type(
Ok(()) Ok(())
} }
fn format_datetime_precision_and_tz(
f: &mut fmt::Formatter,
sql_type: &'static str,
len: &Option<u64>,
time_zone: &TimezoneInfo,
) -> fmt::Result {
write!(f, "{}", sql_type)?;
let len_fmt = len.as_ref().map(|l| format!("({l})")).unwrap_or_default();
match time_zone {
TimezoneInfo::Tz => {
write!(f, "{time_zone}{len_fmt}")?;
}
_ => {
write!(f, "{len_fmt}{time_zone}")?;
}
}
Ok(())
}
/// Timestamp and Time data types information about TimeZone formatting. /// Timestamp and Time data types information about TimeZone formatting.
/// ///
/// This is more related to a display information than real differences between each variant. To /// This is more related to a display information than real differences between each variant. To

View file

@ -3708,31 +3708,41 @@ impl<'a> Parser<'a> {
Keyword::BLOB => Ok(DataType::Blob(self.parse_optional_precision()?)), Keyword::BLOB => Ok(DataType::Blob(self.parse_optional_precision()?)),
Keyword::UUID => Ok(DataType::Uuid), Keyword::UUID => Ok(DataType::Uuid),
Keyword::DATE => Ok(DataType::Date), Keyword::DATE => Ok(DataType::Date),
Keyword::DATETIME => Ok(DataType::Datetime), Keyword::DATETIME => Ok(DataType::Datetime(self.parse_optional_precision()?)),
Keyword::TIMESTAMP => { Keyword::TIMESTAMP => {
if self.parse_keyword(Keyword::WITH) { let precision = self.parse_optional_precision()?;
let tz = if self.parse_keyword(Keyword::WITH) {
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?; self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
Ok(DataType::Timestamp(TimezoneInfo::WithTimeZone)) 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(TimezoneInfo::WithoutTimeZone)) TimezoneInfo::WithoutTimeZone
} else { } else {
Ok(DataType::Timestamp(TimezoneInfo::None)) TimezoneInfo::None
};
Ok(DataType::Timestamp(precision, tz))
} }
} Keyword::TIMESTAMPTZ => Ok(DataType::Timestamp(
Keyword::TIMESTAMPTZ => Ok(DataType::Timestamp(TimezoneInfo::Tz)), self.parse_optional_precision()?,
TimezoneInfo::Tz,
)),
Keyword::TIME => { Keyword::TIME => {
if self.parse_keyword(Keyword::WITH) { let precision = self.parse_optional_precision()?;
let tz = if self.parse_keyword(Keyword::WITH) {
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?; self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
Ok(DataType::Time(TimezoneInfo::WithTimeZone)) 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::Time(TimezoneInfo::WithoutTimeZone)) TimezoneInfo::WithoutTimeZone
} else { } else {
Ok(DataType::Time(TimezoneInfo::None)) TimezoneInfo::None
};
Ok(DataType::Time(precision, tz))
} }
} Keyword::TIMETZ => Ok(DataType::Time(
Keyword::TIMETZ => Ok(DataType::Time(TimezoneInfo::Tz)), self.parse_optional_precision()?,
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.
@ -5789,6 +5799,7 @@ mod tests {
#[cfg(test)] #[cfg(test)]
mod test_parse_data_type { mod test_parse_data_type {
use crate::ast::{ use crate::ast::{
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ObjectName, TimezoneInfo, CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ObjectName, TimezoneInfo,
}; };
@ -5799,8 +5810,8 @@ mod tests {
($dialect:expr, $input:expr, $expected_type:expr $(,)?) => {{ ($dialect:expr, $input:expr, $expected_type:expr $(,)?) => {{
$dialect.run_parser_method(&*$input, |parser| { $dialect.run_parser_method(&*$input, |parser| {
let data_type = parser.parse_data_type().unwrap(); let data_type = parser.parse_data_type().unwrap();
assert_eq!(data_type, $expected_type); assert_eq!($expected_type, data_type);
assert_eq!(data_type.to_string(), $input.to_string()); assert_eq!($input.to_string(), data_type.to_string());
}); });
}}; }};
} }
@ -6048,7 +6059,7 @@ mod tests {
} }
#[test] #[test]
fn test_ansii_datetime_types() { fn test_ansii_date_type() {
// Datetime types: <https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type> // Datetime types: <https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type>
let dialect = TestedDialects { let dialect = TestedDialects {
dialects: vec![Box::new(GenericDialect {}), Box::new(AnsiDialect {})], dialects: vec![Box::new(GenericDialect {}), Box::new(AnsiDialect {})],
@ -6056,36 +6067,60 @@ mod tests {
test_parse_data_type!(dialect, "DATE", DataType::Date); test_parse_data_type!(dialect, "DATE", DataType::Date);
test_parse_data_type!(dialect, "TIME", DataType::Time(TimezoneInfo::None)); test_parse_data_type!(dialect, "TIME", DataType::Time(None, TimezoneInfo::None));
test_parse_data_type!(
dialect,
"TIME(6)",
DataType::Time(Some(6), TimezoneInfo::None)
);
test_parse_data_type!( test_parse_data_type!(
dialect, dialect,
"TIME WITH TIME ZONE", "TIME WITH TIME ZONE",
DataType::Time(TimezoneInfo::WithTimeZone) DataType::Time(None, TimezoneInfo::WithTimeZone)
);
test_parse_data_type!(
dialect,
"TIME(6) WITH TIME ZONE",
DataType::Time(Some(6), TimezoneInfo::WithTimeZone)
); );
test_parse_data_type!( test_parse_data_type!(
dialect, dialect,
"TIME WITHOUT TIME ZONE", "TIME WITHOUT TIME ZONE",
DataType::Time(TimezoneInfo::WithoutTimeZone) DataType::Time(None, TimezoneInfo::WithoutTimeZone)
);
test_parse_data_type!(
dialect,
"TIME(6) WITHOUT TIME ZONE",
DataType::Time(Some(6), TimezoneInfo::WithoutTimeZone)
); );
test_parse_data_type!( test_parse_data_type!(
dialect, dialect,
"TIMESTAMP", "TIMESTAMP",
DataType::Timestamp(TimezoneInfo::None) DataType::Timestamp(None, TimezoneInfo::None)
); );
test_parse_data_type!( test_parse_data_type!(
dialect, dialect,
"TIMESTAMP WITH TIME ZONE", "TIMESTAMP(22)",
DataType::Timestamp(TimezoneInfo::WithTimeZone) DataType::Timestamp(Some(22), TimezoneInfo::None)
); );
test_parse_data_type!( test_parse_data_type!(
dialect, dialect,
"TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMP(22) WITH TIME ZONE",
DataType::Timestamp(TimezoneInfo::WithoutTimeZone) DataType::Timestamp(Some(22), TimezoneInfo::WithTimeZone)
);
test_parse_data_type!(
dialect,
"TIMESTAMP(33) WITHOUT TIME ZONE",
DataType::Timestamp(Some(33), TimezoneInfo::WithoutTimeZone)
); );
} }
} }

View file

@ -2944,7 +2944,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(TimezoneInfo::None), data_type: DataType::Time(None, TimezoneInfo::None),
value: "01:23:34".into(), value: "01:23:34".into(),
}, },
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
@ -2957,7 +2957,7 @@ fn parse_literal_datetime() {
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
&Expr::TypedString { &Expr::TypedString {
data_type: DataType::Datetime, data_type: DataType::Datetime(None),
value: "1999-01-01 01:23:34.45".into(), value: "1999-01-01 01:23:34.45".into(),
}, },
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
@ -2970,7 +2970,7 @@ 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(TimezoneInfo::None), data_type: DataType::Timestamp(None, 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)),
@ -2985,7 +2985,7 @@ 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::Timestamp(TimezoneInfo::Tz), data_type: DataType::Timestamp(None, 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)),

View file

@ -1026,7 +1026,7 @@ fn parse_table_colum_option_on_update() {
assert_eq!( assert_eq!(
vec![ColumnDef { vec![ColumnDef {
name: Ident::with_quote('`', "modification_time"), name: Ident::with_quote('`', "modification_time"),
data_type: DataType::Datetime, data_type: DataType::Datetime(None),
collation: None, collation: None,
options: vec![ColumnOptionDef { options: vec![ColumnOptionDef {
name: None, name: None,

View file

@ -227,7 +227,7 @@ fn parse_create_table_with_defaults() {
}, },
ColumnDef { ColumnDef {
name: "last_update".into(), name: "last_update".into(),
data_type: DataType::Timestamp(TimezoneInfo::WithoutTimeZone), data_type: DataType::Timestamp(None, TimezoneInfo::WithoutTimeZone),
collation: None, collation: None,
options: vec![ options: vec![
ColumnOptionDef { ColumnOptionDef {