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.
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, string::String, vec::Vec};
use alloc::{boxed::Box, format, string::String, vec::Vec};
use core::fmt;
#[cfg(feature = "serde")]
@ -122,12 +122,18 @@ pub enum DataType {
Boolean,
/// Date
Date,
/// Time
Time(TimezoneInfo),
/// Datetime
Datetime,
/// Timestamp
Timestamp(TimezoneInfo),
/// Time 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
Time(Option<u64>, TimezoneInfo),
/// Datetime with optional time precision e.g. [MySQL][1].
///
/// [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,
/// Regclass used in postgresql serial
@ -224,9 +230,15 @@ impl fmt::Display for DataType {
DataType::DoublePrecision => write!(f, "DOUBLE PRECISION"),
DataType::Boolean => write!(f, "BOOLEAN"),
DataType::Date => write!(f, "DATE"),
DataType::Time(timezone_info) => write!(f, "TIME{}", timezone_info),
DataType::Datetime => write!(f, "DATETIME"),
DataType::Timestamp(timezone_info) => write!(f, "TIMESTAMP{}", timezone_info),
DataType::Time(precision, timezone_info) => {
format_datetime_precision_and_tz(f, "TIME", precision, 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::Regclass => write!(f, "REGCLASS"),
DataType::Text => write!(f, "TEXT"),
@ -298,6 +310,27 @@ fn format_character_string_type(
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.
///
/// 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::UUID => Ok(DataType::Uuid),
Keyword::DATE => Ok(DataType::Date),
Keyword::DATETIME => Ok(DataType::Datetime),
Keyword::DATETIME => Ok(DataType::Datetime(self.parse_optional_precision()?)),
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])?;
Ok(DataType::Timestamp(TimezoneInfo::WithTimeZone))
TimezoneInfo::WithTimeZone
} else if self.parse_keyword(Keyword::WITHOUT) {
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
Ok(DataType::Timestamp(TimezoneInfo::WithoutTimeZone))
TimezoneInfo::WithoutTimeZone
} else {
Ok(DataType::Timestamp(TimezoneInfo::None))
TimezoneInfo::None
};
Ok(DataType::Timestamp(precision, tz))
}
}
Keyword::TIMESTAMPTZ => Ok(DataType::Timestamp(TimezoneInfo::Tz)),
Keyword::TIMESTAMPTZ => Ok(DataType::Timestamp(
self.parse_optional_precision()?,
TimezoneInfo::Tz,
)),
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])?;
Ok(DataType::Time(TimezoneInfo::WithTimeZone))
TimezoneInfo::WithTimeZone
} else if self.parse_keyword(Keyword::WITHOUT) {
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
Ok(DataType::Time(TimezoneInfo::WithoutTimeZone))
TimezoneInfo::WithoutTimeZone
} else {
Ok(DataType::Time(TimezoneInfo::None))
TimezoneInfo::None
};
Ok(DataType::Time(precision, tz))
}
}
Keyword::TIMETZ => Ok(DataType::Time(TimezoneInfo::Tz)),
Keyword::TIMETZ => Ok(DataType::Time(
self.parse_optional_precision()?,
TimezoneInfo::Tz,
)),
// Interval types can be followed by a complicated interval
// qualifier that we don't currently support. See
// parse_interval for a taste.
@ -5789,6 +5799,7 @@ mod tests {
#[cfg(test)]
mod test_parse_data_type {
use crate::ast::{
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ObjectName, TimezoneInfo,
};
@ -5799,8 +5810,8 @@ mod tests {
($dialect:expr, $input:expr, $expected_type:expr $(,)?) => {{
$dialect.run_parser_method(&*$input, |parser| {
let data_type = parser.parse_data_type().unwrap();
assert_eq!(data_type, $expected_type);
assert_eq!(data_type.to_string(), $input.to_string());
assert_eq!($expected_type, data_type);
assert_eq!($input.to_string(), data_type.to_string());
});
}};
}
@ -6048,7 +6059,7 @@ mod tests {
}
#[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>
let dialect = TestedDialects {
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, "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!(
dialect,
"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!(
dialect,
"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!(
dialect,
"TIMESTAMP",
DataType::Timestamp(TimezoneInfo::None)
DataType::Timestamp(None, TimezoneInfo::None)
);
test_parse_data_type!(
dialect,
"TIMESTAMP WITH TIME ZONE",
DataType::Timestamp(TimezoneInfo::WithTimeZone)
"TIMESTAMP(22)",
DataType::Timestamp(Some(22), TimezoneInfo::None)
);
test_parse_data_type!(
dialect,
"TIMESTAMP WITHOUT TIME ZONE",
DataType::Timestamp(TimezoneInfo::WithoutTimeZone)
"TIMESTAMP(22) WITH TIME ZONE",
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);
assert_eq!(
&Expr::TypedString {
data_type: DataType::Time(TimezoneInfo::None),
data_type: DataType::Time(None, TimezoneInfo::None),
value: "01:23:34".into(),
},
expr_from_projection(only(&select.projection)),
@ -2957,7 +2957,7 @@ fn parse_literal_datetime() {
let select = verified_only_select(sql);
assert_eq!(
&Expr::TypedString {
data_type: DataType::Datetime,
data_type: DataType::Datetime(None),
value: "1999-01-01 01:23:34.45".into(),
},
expr_from_projection(only(&select.projection)),
@ -2970,7 +2970,7 @@ fn parse_literal_timestamp_without_time_zone() {
let select = verified_only_select(sql);
assert_eq!(
&Expr::TypedString {
data_type: DataType::Timestamp(TimezoneInfo::None),
data_type: DataType::Timestamp(None, TimezoneInfo::None),
value: "1999-01-01 01:23:34".into(),
},
expr_from_projection(only(&select.projection)),
@ -2985,7 +2985,7 @@ fn parse_literal_timestamp_with_time_zone() {
let select = verified_only_select(sql);
assert_eq!(
&Expr::TypedString {
data_type: DataType::Timestamp(TimezoneInfo::Tz),
data_type: DataType::Timestamp(None, TimezoneInfo::Tz),
value: "1999-01-01 01:23:34Z".into(),
},
expr_from_projection(only(&select.projection)),

View file

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

View file

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