diff --git a/src/rowbinary/validation.rs b/src/rowbinary/validation.rs index 43e6ad30..abecd5e7 100644 --- a/src/rowbinary/validation.rs +++ b/src/rowbinary/validation.rs @@ -467,7 +467,8 @@ fn validate_impl<'de, 'cursor, R: Row>( || matches!( data_type, DataTypeNode::Decimal(_, _, DecimalType::Decimal64) - ) => + ) + || matches!(data_type, DataTypeNode::Interval(_)) => { None } diff --git a/tests/it/rbwnat.rs b/tests/it/rbwnat.rs index bdfd4608..4200b62b 100644 --- a/tests/it/rbwnat.rs +++ b/tests/it/rbwnat.rs @@ -996,6 +996,131 @@ async fn date_and_time() { ); } +#[tokio::test] +async fn interval() { + #[derive(Debug, Row, Serialize, Deserialize, PartialEq)] + struct Data { + id: u32, + interval_nanosecond: i64, + interval_microsecond: i64, + interval_millisecond: i64, + interval_second: i64, + interval_minute: i64, + interval_hour: i64, + interval_day: i64, + interval_week: i64, + interval_month: i64, + interval_quarter: i64, + interval_year: i64, + } + + let client = get_client(); + let mut cursor = client + .query( + " + SELECT * FROM ( + SELECT + 0 :: UInt32 AS id, + toIntervalNanosecond (0) AS interval_nanosecond, + toIntervalMicrosecond (0) AS interval_microsecond, + toIntervalMillisecond (0) AS interval_millisecond, + toIntervalSecond (0) AS interval_second, + toIntervalMinute (0) AS interval_minute, + toIntervalHour (0) AS interval_hour, + toIntervalDay (0) AS interval_day, + toIntervalWeek (0) AS interval_week, + toIntervalMonth (0) AS interval_month, + toIntervalQuarter (0) AS interval_quarter, + toIntervalYear (0) AS interval_year + UNION ALL + SELECT + 1 :: UInt32 AS id, + toIntervalNanosecond (-9223372036854775808) AS interval_nanosecond, + toIntervalMicrosecond (-9223372036854775808) AS interval_microsecond, + toIntervalMillisecond (-9223372036854775808) AS interval_millisecond, + toIntervalSecond (-9223372036854775808) AS interval_second, + toIntervalMinute (-9223372036854775808) AS interval_minute, + toIntervalHour (-9223372036854775808) AS interval_hour, + toIntervalDay (-9223372036854775808) AS interval_day, + toIntervalWeek (-9223372036854775808) AS interval_week, + toIntervalMonth (-9223372036854775808) AS interval_month, + toIntervalQuarter (-9223372036854775808) AS interval_quarter, + toIntervalYear (-9223372036854775808) AS interval_year + UNION ALL + SELECT + 2 :: UInt32 AS id, + toIntervalNanosecond (9223372036854775807) AS interval_nanosecond, + toIntervalMicrosecond (9223372036854775807) AS interval_microsecond, + toIntervalMillisecond (9223372036854775807) AS interval_millisecond, + toIntervalSecond (9223372036854775807) AS interval_second, + toIntervalMinute (9223372036854775807) AS interval_minute, + toIntervalHour (9223372036854775807) AS interval_hour, + toIntervalDay (9223372036854775807) AS interval_day, + toIntervalWeek (9223372036854775807) AS interval_week, + toIntervalMonth (9223372036854775807) AS interval_month, + toIntervalQuarter (9223372036854775807) AS interval_quarter, + toIntervalYear (9223372036854775807) AS interval_year + ) ORDER BY id ASC + ", + ) + .fetch::() + .unwrap(); + + assert_eq!( + cursor.next().await.unwrap().unwrap(), + Data { + id: 0, + interval_nanosecond: 0, + interval_microsecond: 0, + interval_millisecond: 0, + interval_second: 0, + interval_minute: 0, + interval_hour: 0, + interval_day: 0, + interval_week: 0, + interval_month: 0, + interval_quarter: 0, + interval_year: 0, + } + ); + + assert_eq!( + cursor.next().await.unwrap().unwrap(), + Data { + id: 1, + interval_nanosecond: i64::MIN, + interval_microsecond: i64::MIN, + interval_millisecond: i64::MIN, + interval_second: i64::MIN, + interval_minute: i64::MIN, + interval_hour: i64::MIN, + interval_day: i64::MIN, + interval_week: i64::MIN, + interval_month: i64::MIN, + interval_quarter: i64::MIN, + interval_year: i64::MIN, + } + ); + + assert_eq!( + cursor.next().await.unwrap().unwrap(), + Data { + id: 2, + interval_nanosecond: i64::MAX, + interval_microsecond: i64::MAX, + interval_millisecond: i64::MAX, + interval_second: i64::MAX, + interval_minute: i64::MAX, + interval_hour: i64::MAX, + interval_day: i64::MAX, + interval_week: i64::MAX, + interval_month: i64::MAX, + interval_quarter: i64::MAX, + interval_year: i64::MAX, + } + ); +} + #[tokio::test] #[cfg(feature = "uuid")] async fn uuid() { diff --git a/types/src/data_types.rs b/types/src/data_types.rs index 4bc056b3..92194aef 100644 --- a/types/src/data_types.rs +++ b/types/src/data_types.rs @@ -71,6 +71,8 @@ pub enum DataTypeNode { /// Precision and optional timezone (timezone is ignored in value operations) Time64(DateTimePrecision), + Interval(IntervalType), + IPv4, IPv6, @@ -143,6 +145,7 @@ impl DataTypeNode { str if str.starts_with("DateTime") => parse_datetime(str), str if str.starts_with("Time64") => parse_time64(str), str if str.starts_with("Time") => Ok(Self::Time), + str if str.starts_with("Interval") => Ok(Self::Interval(str[8..].parse()?)), str if str.starts_with("Nullable") => parse_nullable(str), str if str.starts_with("LowCardinality") => parse_low_cardinality(str), @@ -208,6 +211,7 @@ impl Display for DataTypeNode { DateTime64(precision, Some(tz)) => write!(f, "DateTime64({precision}, '{tz}')"), Time => write!(f, "Time"), Time64(precision) => write!(f, "Time64({precision})"), + Interval(interval) => write!(f, "Interval{interval}"), IPv4 => write!(f, "IPv4"), IPv6 => write!(f, "IPv6"), Bool => write!(f, "Bool"), @@ -392,6 +396,65 @@ impl Display for DateTimePrecision { } } +/// Represents the type of an interval. +/// See also: +#[derive(Debug, Clone, PartialEq)] +#[allow(missing_docs)] +pub enum IntervalType { + Nanosecond, + Microsecond, + Millisecond, + Second, + Minute, + Hour, + Day, + Week, + Month, + Quarter, + Year, +} + +impl std::str::FromStr for IntervalType { + type Err = TypesError; + + fn from_str(s: &str) -> Result { + match s { + "Nanosecond" => Ok(IntervalType::Nanosecond), + "Microsecond" => Ok(IntervalType::Microsecond), + "Millisecond" => Ok(IntervalType::Millisecond), + "Second" => Ok(IntervalType::Second), + "Minute" => Ok(IntervalType::Minute), + "Hour" => Ok(IntervalType::Hour), + "Day" => Ok(IntervalType::Day), + "Week" => Ok(IntervalType::Week), + "Month" => Ok(IntervalType::Month), + "Quarter" => Ok(IntervalType::Quarter), + "Year" => Ok(IntervalType::Year), + _ => Err(TypesError::TypeParsingError(format!( + "Unknown interval type: {s}" + ))), + } + } +} + +impl Display for IntervalType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Nanosecond => write!(f, "Nanosecond"), + Self::Microsecond => write!(f, "Microsecond"), + Self::Millisecond => write!(f, "Millisecond"), + Self::Second => write!(f, "Second"), + Self::Minute => write!(f, "Minute"), + Self::Hour => write!(f, "Hour"), + Self::Day => write!(f, "Day"), + Self::Week => write!(f, "Week"), + Self::Month => write!(f, "Month"), + Self::Quarter => write!(f, "Quarter"), + Self::Year => write!(f, "Year"), + } + } +} + fn parse_fixed_string(input: &str) -> Result { if input.len() >= 14 { let size_str = &input[12..input.len() - 1]; @@ -1136,6 +1199,54 @@ mod tests { assert!(DataTypeNode::new("Time64(x)").is_err()); } + #[test] + fn test_data_type_new_interval() { + assert_eq!( + DataTypeNode::new("IntervalNanosecond").unwrap(), + DataTypeNode::Interval(IntervalType::Nanosecond) + ); + assert_eq!( + DataTypeNode::new("IntervalMicrosecond").unwrap(), + DataTypeNode::Interval(IntervalType::Microsecond) + ); + assert_eq!( + DataTypeNode::new("IntervalMillisecond").unwrap(), + DataTypeNode::Interval(IntervalType::Millisecond) + ); + assert_eq!( + DataTypeNode::new("IntervalSecond").unwrap(), + DataTypeNode::Interval(IntervalType::Second) + ); + assert_eq!( + DataTypeNode::new("IntervalMinute").unwrap(), + DataTypeNode::Interval(IntervalType::Minute) + ); + assert_eq!( + DataTypeNode::new("IntervalHour").unwrap(), + DataTypeNode::Interval(IntervalType::Hour) + ); + assert_eq!( + DataTypeNode::new("IntervalDay").unwrap(), + DataTypeNode::Interval(IntervalType::Day) + ); + assert_eq!( + DataTypeNode::new("IntervalWeek").unwrap(), + DataTypeNode::Interval(IntervalType::Week) + ); + assert_eq!( + DataTypeNode::new("IntervalMonth").unwrap(), + DataTypeNode::Interval(IntervalType::Month) + ); + assert_eq!( + DataTypeNode::new("IntervalQuarter").unwrap(), + DataTypeNode::Interval(IntervalType::Quarter) + ); + assert_eq!( + DataTypeNode::new("IntervalYear").unwrap(), + DataTypeNode::Interval(IntervalType::Year) + ); + } + #[test] fn test_data_type_new_low_cardinality() { assert_eq!( @@ -1542,6 +1653,54 @@ mod tests { } } + #[test] + fn test_interval_to_string() { + assert_eq!( + DataTypeNode::Interval(IntervalType::Nanosecond).to_string(), + "IntervalNanosecond" + ); + assert_eq!( + DataTypeNode::Interval(IntervalType::Microsecond).to_string(), + "IntervalMicrosecond" + ); + assert_eq!( + DataTypeNode::Interval(IntervalType::Millisecond).to_string(), + "IntervalMillisecond" + ); + assert_eq!( + DataTypeNode::Interval(IntervalType::Second).to_string(), + "IntervalSecond" + ); + assert_eq!( + DataTypeNode::Interval(IntervalType::Minute).to_string(), + "IntervalMinute" + ); + assert_eq!( + DataTypeNode::Interval(IntervalType::Hour).to_string(), + "IntervalHour" + ); + assert_eq!( + DataTypeNode::Interval(IntervalType::Day).to_string(), + "IntervalDay" + ); + assert_eq!( + DataTypeNode::Interval(IntervalType::Week).to_string(), + "IntervalWeek" + ); + assert_eq!( + DataTypeNode::Interval(IntervalType::Month).to_string(), + "IntervalMonth" + ); + assert_eq!( + DataTypeNode::Interval(IntervalType::Quarter).to_string(), + "IntervalQuarter" + ); + assert_eq!( + DataTypeNode::Interval(IntervalType::Year).to_string(), + "IntervalYear" + ); + } + #[test] fn test_data_type_node_into_string() { let data_type = DataTypeNode::new("Array(Int32)").unwrap();