Skip to content

Commit 65c5a37

Browse files
authored
feat: add precision for TIME, DATETIME, and TIMESTAMP data types (apache#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.
1 parent cdf4447 commit 65c5a37

File tree

5 files changed

+108
-40
lines changed

5 files changed

+108
-40
lines changed

src/ast/data_type.rs

+43-10
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// limitations under the License.
1212

1313
#[cfg(not(feature = "std"))]
14-
use alloc::{boxed::Box, string::String, vec::Vec};
14+
use alloc::{boxed::Box, format, string::String, vec::Vec};
1515
use core::fmt;
1616

1717
#[cfg(feature = "serde")]
@@ -122,12 +122,18 @@ pub enum DataType {
122122
Boolean,
123123
/// Date
124124
Date,
125-
/// Time
126-
Time(TimezoneInfo),
127-
/// Datetime
128-
Datetime,
129-
/// Timestamp
130-
Timestamp(TimezoneInfo),
125+
/// Time with optional time precision and time zone information e.g. [standard][1].
126+
///
127+
/// [1]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type
128+
Time(Option<u64>, TimezoneInfo),
129+
/// Datetime with optional time precision e.g. [MySQL][1].
130+
///
131+
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/datetime.html
132+
Datetime(Option<u64>),
133+
/// Timestamp with optional time precision and time zone information e.g. [standard][1].
134+
///
135+
/// [1]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type
136+
Timestamp(Option<u64>, TimezoneInfo),
131137
/// Interval
132138
Interval,
133139
/// Regclass used in postgresql serial
@@ -224,9 +230,15 @@ impl fmt::Display for DataType {
224230
DataType::DoublePrecision => write!(f, "DOUBLE PRECISION"),
225231
DataType::Boolean => write!(f, "BOOLEAN"),
226232
DataType::Date => write!(f, "DATE"),
227-
DataType::Time(timezone_info) => write!(f, "TIME{}", timezone_info),
228-
DataType::Datetime => write!(f, "DATETIME"),
229-
DataType::Timestamp(timezone_info) => write!(f, "TIMESTAMP{}", timezone_info),
233+
DataType::Time(precision, timezone_info) => {
234+
format_datetime_precision_and_tz(f, "TIME", precision, timezone_info)
235+
}
236+
DataType::Datetime(precision) => {
237+
format_type_with_optional_length(f, "DATETIME", precision, false)
238+
}
239+
DataType::Timestamp(precision, timezone_info) => {
240+
format_datetime_precision_and_tz(f, "TIMESTAMP", precision, timezone_info)
241+
}
230242
DataType::Interval => write!(f, "INTERVAL"),
231243
DataType::Regclass => write!(f, "REGCLASS"),
232244
DataType::Text => write!(f, "TEXT"),
@@ -298,6 +310,27 @@ fn format_character_string_type(
298310
Ok(())
299311
}
300312

313+
fn format_datetime_precision_and_tz(
314+
f: &mut fmt::Formatter,
315+
sql_type: &'static str,
316+
len: &Option<u64>,
317+
time_zone: &TimezoneInfo,
318+
) -> fmt::Result {
319+
write!(f, "{}", sql_type)?;
320+
let len_fmt = len.as_ref().map(|l| format!("({l})")).unwrap_or_default();
321+
322+
match time_zone {
323+
TimezoneInfo::Tz => {
324+
write!(f, "{time_zone}{len_fmt}")?;
325+
}
326+
_ => {
327+
write!(f, "{len_fmt}{time_zone}")?;
328+
}
329+
}
330+
331+
Ok(())
332+
}
333+
301334
/// Timestamp and Time data types information about TimeZone formatting.
302335
///
303336
/// This is more related to a display information than real differences between each variant. To

src/parser.rs

+59-24
Original file line numberDiff line numberDiff line change
@@ -3708,31 +3708,41 @@ impl<'a> Parser<'a> {
37083708
Keyword::BLOB => Ok(DataType::Blob(self.parse_optional_precision()?)),
37093709
Keyword::UUID => Ok(DataType::Uuid),
37103710
Keyword::DATE => Ok(DataType::Date),
3711-
Keyword::DATETIME => Ok(DataType::Datetime),
3711+
Keyword::DATETIME => Ok(DataType::Datetime(self.parse_optional_precision()?)),
37123712
Keyword::TIMESTAMP => {
3713-
if self.parse_keyword(Keyword::WITH) {
3713+
let precision = self.parse_optional_precision()?;
3714+
let tz = if self.parse_keyword(Keyword::WITH) {
37143715
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
3715-
Ok(DataType::Timestamp(TimezoneInfo::WithTimeZone))
3716+
TimezoneInfo::WithTimeZone
37163717
} else if self.parse_keyword(Keyword::WITHOUT) {
37173718
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
3718-
Ok(DataType::Timestamp(TimezoneInfo::WithoutTimeZone))
3719+
TimezoneInfo::WithoutTimeZone
37193720
} else {
3720-
Ok(DataType::Timestamp(TimezoneInfo::None))
3721-
}
3721+
TimezoneInfo::None
3722+
};
3723+
Ok(DataType::Timestamp(precision, tz))
37223724
}
3723-
Keyword::TIMESTAMPTZ => Ok(DataType::Timestamp(TimezoneInfo::Tz)),
3725+
Keyword::TIMESTAMPTZ => Ok(DataType::Timestamp(
3726+
self.parse_optional_precision()?,
3727+
TimezoneInfo::Tz,
3728+
)),
37243729
Keyword::TIME => {
3725-
if self.parse_keyword(Keyword::WITH) {
3730+
let precision = self.parse_optional_precision()?;
3731+
let tz = if self.parse_keyword(Keyword::WITH) {
37263732
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
3727-
Ok(DataType::Time(TimezoneInfo::WithTimeZone))
3733+
TimezoneInfo::WithTimeZone
37283734
} else if self.parse_keyword(Keyword::WITHOUT) {
37293735
self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?;
3730-
Ok(DataType::Time(TimezoneInfo::WithoutTimeZone))
3736+
TimezoneInfo::WithoutTimeZone
37313737
} else {
3732-
Ok(DataType::Time(TimezoneInfo::None))
3733-
}
3738+
TimezoneInfo::None
3739+
};
3740+
Ok(DataType::Time(precision, tz))
37343741
}
3735-
Keyword::TIMETZ => Ok(DataType::Time(TimezoneInfo::Tz)),
3742+
Keyword::TIMETZ => Ok(DataType::Time(
3743+
self.parse_optional_precision()?,
3744+
TimezoneInfo::Tz,
3745+
)),
37363746
// Interval types can be followed by a complicated interval
37373747
// qualifier that we don't currently support. See
37383748
// parse_interval for a taste.
@@ -5789,6 +5799,7 @@ mod tests {
57895799

57905800
#[cfg(test)]
57915801
mod test_parse_data_type {
5802+
57925803
use crate::ast::{
57935804
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ObjectName, TimezoneInfo,
57945805
};
@@ -5799,8 +5810,8 @@ mod tests {
57995810
($dialect:expr, $input:expr, $expected_type:expr $(,)?) => {{
58005811
$dialect.run_parser_method(&*$input, |parser| {
58015812
let data_type = parser.parse_data_type().unwrap();
5802-
assert_eq!(data_type, $expected_type);
5803-
assert_eq!(data_type.to_string(), $input.to_string());
5813+
assert_eq!($expected_type, data_type);
5814+
assert_eq!($input.to_string(), data_type.to_string());
58045815
});
58055816
}};
58065817
}
@@ -6048,44 +6059,68 @@ mod tests {
60486059
}
60496060

60506061
#[test]
6051-
fn test_ansii_datetime_types() {
6062+
fn test_ansii_date_type() {
60526063
// Datetime types: <https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type>
60536064
let dialect = TestedDialects {
60546065
dialects: vec![Box::new(GenericDialect {}), Box::new(AnsiDialect {})],
60556066
};
60566067

60576068
test_parse_data_type!(dialect, "DATE", DataType::Date);
60586069

6059-
test_parse_data_type!(dialect, "TIME", DataType::Time(TimezoneInfo::None));
6070+
test_parse_data_type!(dialect, "TIME", DataType::Time(None, TimezoneInfo::None));
6071+
6072+
test_parse_data_type!(
6073+
dialect,
6074+
"TIME(6)",
6075+
DataType::Time(Some(6), TimezoneInfo::None)
6076+
);
60606077

60616078
test_parse_data_type!(
60626079
dialect,
60636080
"TIME WITH TIME ZONE",
6064-
DataType::Time(TimezoneInfo::WithTimeZone)
6081+
DataType::Time(None, TimezoneInfo::WithTimeZone)
6082+
);
6083+
6084+
test_parse_data_type!(
6085+
dialect,
6086+
"TIME(6) WITH TIME ZONE",
6087+
DataType::Time(Some(6), TimezoneInfo::WithTimeZone)
60656088
);
60666089

60676090
test_parse_data_type!(
60686091
dialect,
60696092
"TIME WITHOUT TIME ZONE",
6070-
DataType::Time(TimezoneInfo::WithoutTimeZone)
6093+
DataType::Time(None, TimezoneInfo::WithoutTimeZone)
6094+
);
6095+
6096+
test_parse_data_type!(
6097+
dialect,
6098+
"TIME(6) WITHOUT TIME ZONE",
6099+
DataType::Time(Some(6), TimezoneInfo::WithoutTimeZone)
60716100
);
60726101

60736102
test_parse_data_type!(
60746103
dialect,
60756104
"TIMESTAMP",
6076-
DataType::Timestamp(TimezoneInfo::None)
6105+
DataType::Timestamp(None, TimezoneInfo::None)
6106+
);
6107+
6108+
test_parse_data_type!(
6109+
dialect,
6110+
"TIMESTAMP(22)",
6111+
DataType::Timestamp(Some(22), TimezoneInfo::None)
60776112
);
60786113

60796114
test_parse_data_type!(
60806115
dialect,
6081-
"TIMESTAMP WITH TIME ZONE",
6082-
DataType::Timestamp(TimezoneInfo::WithTimeZone)
6116+
"TIMESTAMP(22) WITH TIME ZONE",
6117+
DataType::Timestamp(Some(22), TimezoneInfo::WithTimeZone)
60836118
);
60846119

60856120
test_parse_data_type!(
60866121
dialect,
6087-
"TIMESTAMP WITHOUT TIME ZONE",
6088-
DataType::Timestamp(TimezoneInfo::WithoutTimeZone)
6122+
"TIMESTAMP(33) WITHOUT TIME ZONE",
6123+
DataType::Timestamp(Some(33), TimezoneInfo::WithoutTimeZone)
60896124
);
60906125
}
60916126
}

tests/sqlparser_common.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -2944,7 +2944,7 @@ fn parse_literal_time() {
29442944
let select = verified_only_select(sql);
29452945
assert_eq!(
29462946
&Expr::TypedString {
2947-
data_type: DataType::Time(TimezoneInfo::None),
2947+
data_type: DataType::Time(None, TimezoneInfo::None),
29482948
value: "01:23:34".into(),
29492949
},
29502950
expr_from_projection(only(&select.projection)),
@@ -2957,7 +2957,7 @@ fn parse_literal_datetime() {
29572957
let select = verified_only_select(sql);
29582958
assert_eq!(
29592959
&Expr::TypedString {
2960-
data_type: DataType::Datetime,
2960+
data_type: DataType::Datetime(None),
29612961
value: "1999-01-01 01:23:34.45".into(),
29622962
},
29632963
expr_from_projection(only(&select.projection)),
@@ -2970,7 +2970,7 @@ fn parse_literal_timestamp_without_time_zone() {
29702970
let select = verified_only_select(sql);
29712971
assert_eq!(
29722972
&Expr::TypedString {
2973-
data_type: DataType::Timestamp(TimezoneInfo::None),
2973+
data_type: DataType::Timestamp(None, TimezoneInfo::None),
29742974
value: "1999-01-01 01:23:34".into(),
29752975
},
29762976
expr_from_projection(only(&select.projection)),
@@ -2985,7 +2985,7 @@ fn parse_literal_timestamp_with_time_zone() {
29852985
let select = verified_only_select(sql);
29862986
assert_eq!(
29872987
&Expr::TypedString {
2988-
data_type: DataType::Timestamp(TimezoneInfo::Tz),
2988+
data_type: DataType::Timestamp(None, TimezoneInfo::Tz),
29892989
value: "1999-01-01 01:23:34Z".into(),
29902990
},
29912991
expr_from_projection(only(&select.projection)),

tests/sqlparser_mysql.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,7 @@ fn parse_table_colum_option_on_update() {
10261026
assert_eq!(
10271027
vec![ColumnDef {
10281028
name: Ident::with_quote('`', "modification_time"),
1029-
data_type: DataType::Datetime,
1029+
data_type: DataType::Datetime(None),
10301030
collation: None,
10311031
options: vec![ColumnOptionDef {
10321032
name: None,

tests/sqlparser_postgres.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ fn parse_create_table_with_defaults() {
227227
},
228228
ColumnDef {
229229
name: "last_update".into(),
230-
data_type: DataType::Timestamp(TimezoneInfo::WithoutTimeZone),
230+
data_type: DataType::Timestamp(None, TimezoneInfo::WithoutTimeZone),
231231
collation: None,
232232
options: vec![
233233
ColumnOptionDef {

0 commit comments

Comments
 (0)