mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-23 07:24:10 +00:00
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:
parent
cdf4447065
commit
65c5a37bce
5 changed files with 108 additions and 40 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue