From 7727508485bd9b6224105145017951822a1a12df Mon Sep 17 00:00:00 2001 From: Zou Wei <47681251+QuenKar@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:54:39 +0800 Subject: [PATCH] feat: impl interval type (#1952) * feat: impl interval type in common time * feat: impl datatype, vectors, value for interval pick 0c1d9f297 feat: impl interval type in common time pick d528c647f feat: impl datatype, vectors, value for interval pick 1e12dd5c7 comments update pick 74103e36c add license header * comments update * add license header * cargo clippy * refactor interval type * add unit test and case to dummy.sql * cargo clippy * chore: add doc comments * chore: cargo fmt * feat: add formats, refactor comparison * add docs comments * Apply suggestions from code review Co-authored-by: Yingwen * chore: cr comment --------- Co-authored-by: Yingwen --- src/api/src/helper.rs | 5 +- src/common/grpc-expr/src/insert.rs | 10 +- src/common/grpc/src/select.rs | 2 +- src/common/time/src/error.rs | 5 + src/common/time/src/interval.rs | 743 ++++++++++++++++++ src/common/time/src/lib.rs | 2 + src/datatypes/src/data_type.rs | 62 +- src/datatypes/src/interval.rs | 138 ++++ src/datatypes/src/lib.rs | 1 + src/datatypes/src/type_id.rs | 11 + src/datatypes/src/types.rs | 4 + src/datatypes/src/types/interval_type.rs | 176 +++++ src/datatypes/src/types/primitive_type.rs | 1 + src/datatypes/src/value.rs | 72 +- src/datatypes/src/vectors.rs | 5 + src/datatypes/src/vectors/eq.rs | 45 +- src/datatypes/src/vectors/helper.rs | 48 +- src/datatypes/src/vectors/interval.rs | 25 + src/datatypes/src/vectors/primitive.rs | 66 +- src/servers/src/mysql/writer.rs | 2 + src/servers/src/postgres/types.rs | 15 +- src/sql/src/statements.rs | 8 +- .../standalone/common/select/dummy.result | 24 + .../cases/standalone/common/select/dummy.sql | 6 + 24 files changed, 1444 insertions(+), 32 deletions(-) create mode 100644 src/common/time/src/interval.rs create mode 100644 src/datatypes/src/interval.rs create mode 100644 src/datatypes/src/types/interval_type.rs create mode 100644 src/datatypes/src/vectors/interval.rs diff --git a/src/api/src/helper.rs b/src/api/src/helper.rs index 143c44ceb0..b708b05449 100644 --- a/src/api/src/helper.rs +++ b/src/api/src/helper.rs @@ -111,7 +111,8 @@ impl TryFrom for ColumnDataTypeWrapper { TimeType::Microsecond(_) => ColumnDataType::TimeMicrosecond, TimeType::Nanosecond(_) => ColumnDataType::TimeNanosecond, }, - ConcreteDataType::Null(_) + ConcreteDataType::Interval(_) + | ConcreteDataType::Null(_) | ConcreteDataType::List(_) | ConcreteDataType::Dictionary(_) => { return error::IntoColumnDataTypeSnafu { from: datatype }.fail() @@ -255,7 +256,7 @@ pub fn push_vals(column: &mut Column, origin_count: usize, vector: VectorRef) { TimeUnit::Microsecond => values.time_microsecond_values.push(val.value()), TimeUnit::Nanosecond => values.time_nanosecond_values.push(val.value()), }, - Value::List(_) => unreachable!(), + Value::Interval(_) | Value::List(_) => unreachable!(), }); column.null_mask = null_mask.into_vec(); } diff --git a/src/common/grpc-expr/src/insert.rs b/src/common/grpc-expr/src/insert.rs index b220e2e6f4..13a51ddc89 100644 --- a/src/common/grpc-expr/src/insert.rs +++ b/src/common/grpc-expr/src/insert.rs @@ -424,7 +424,10 @@ fn values_to_vector(data_type: &ConcreteDataType, values: Values) -> VectorRef { )), }, - ConcreteDataType::Null(_) | ConcreteDataType::List(_) | ConcreteDataType::Dictionary(_) => { + ConcreteDataType::Interval(_) + | ConcreteDataType::Null(_) + | ConcreteDataType::List(_) + | ConcreteDataType::Dictionary(_) => { unreachable!() } } @@ -553,7 +556,10 @@ fn convert_values(data_type: &ConcreteDataType, values: Values) -> Vec { .map(|v| Value::Time(Time::new_nanosecond(v))) .collect(), - ConcreteDataType::Null(_) | ConcreteDataType::List(_) | ConcreteDataType::Dictionary(_) => { + ConcreteDataType::Interval(_) + | ConcreteDataType::Null(_) + | ConcreteDataType::List(_) + | ConcreteDataType::Dictionary(_) => { unreachable!() } } diff --git a/src/common/grpc/src/select.rs b/src/common/grpc/src/select.rs index 21eed06c0c..aed198d7fa 100644 --- a/src/common/grpc/src/select.rs +++ b/src/common/grpc/src/select.rs @@ -68,7 +68,7 @@ macro_rules! convert_arrow_array_to_grpc_vals { return Ok(vals); }, )+ - ConcreteDataType::Null(_) | ConcreteDataType::List(_) | ConcreteDataType::Dictionary(_) => unreachable!("Should not send {:?} in gRPC", $data_type), + ConcreteDataType::Null(_) | ConcreteDataType::List(_) | ConcreteDataType::Dictionary(_)| ConcreteDataType::Interval(_) => unreachable!("Should not send {:?} in gRPC", $data_type), } }}; } diff --git a/src/common/time/src/error.rs b/src/common/time/src/error.rs index d9511a3f74..6e4f4d78ae 100644 --- a/src/common/time/src/error.rs +++ b/src/common/time/src/error.rs @@ -32,6 +32,9 @@ pub enum Error { #[snafu(display("Failed to parse a string into Timestamp, raw string: {}", raw))] ParseTimestamp { raw: String, location: Location }, + #[snafu(display("Failed to parse a string into Interval, raw string: {}", raw))] + ParseInterval { raw: String, location: Location }, + #[snafu(display("Current timestamp overflow, source: {}", source))] TimestampOverflow { source: TryFromIntError, @@ -71,6 +74,7 @@ impl ErrorExt for Error { Error::InvalidDateStr { .. } | Error::ArithmeticOverflow { .. } => { StatusCode::InvalidArguments } + Error::ParseInterval { .. } => StatusCode::InvalidArguments, } } @@ -88,6 +92,7 @@ impl ErrorExt for Error { | Error::ParseOffsetStr { .. } | Error::ParseTimeZoneName { .. } => None, Error::InvalidDateStr { location, .. } => Some(*location), + Error::ParseInterval { location, .. } => Some(*location), } } } diff --git a/src/common/time/src/interval.rs b/src/common/time/src/interval.rs new file mode 100644 index 0000000000..7d4f6bc5f7 --- /dev/null +++ b/src/common/time/src/interval.rs @@ -0,0 +1,743 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::cmp::Ordering; +use std::default::Default; +use std::fmt::{self, Display, Formatter, Write}; +use std::hash::{Hash, Hasher}; + +use arrow::datatypes::IntervalUnit as ArrowIntervalUnit; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive( + Debug, Default, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, +)] +pub enum IntervalUnit { + /// Indicates the number of elapsed whole months, stored as 4-byte integers. + YearMonth, + /// Indicates the number of elapsed days and milliseconds, + /// stored as 2 contiguous 32-bit integers (days, milliseconds) (8-bytes in total). + DayTime, + /// A triple of the number of elapsed months, days, and nanoseconds. + /// The values are stored contiguously in 16 byte blocks. Months and + /// days are encoded as 32 bit integers and nanoseconds is encoded as a + /// 64 bit integer. All integers are signed. Each field is independent + /// (e.g. there is no constraint that nanoseconds have the same sign + /// as days or that the quantity of nanoseconds represents less + /// than a day's worth of time). + #[default] + MonthDayNano, +} + +impl From<&ArrowIntervalUnit> for IntervalUnit { + fn from(unit: &ArrowIntervalUnit) -> Self { + match unit { + ArrowIntervalUnit::YearMonth => IntervalUnit::YearMonth, + ArrowIntervalUnit::DayTime => IntervalUnit::DayTime, + ArrowIntervalUnit::MonthDayNano => IntervalUnit::MonthDayNano, + } + } +} + +impl From for IntervalUnit { + fn from(unit: ArrowIntervalUnit) -> Self { + (&unit).into() + } +} + +/// Interval Type represents a period of time. +/// It is composed of months, days and nanoseconds. +/// 3 kinds of interval are supported: year-month, day-time and +/// month-day-nano, which will be stored in the following format. +/// Interval data format: +/// | months | days | nsecs | +/// | 4bytes | 4bytes | 8bytes | +#[derive(Debug, Clone, Default, Copy, Serialize, Deserialize)] +pub struct Interval { + months: i32, + days: i32, + nsecs: i64, + unit: IntervalUnit, +} + +// Nanosecond convert to other time unit +pub const NANOS_PER_SEC: i64 = 1_000_000_000; +pub const NANOS_PER_MILLI: i64 = 1_000_000; +pub const NANOS_PER_MICRO: i64 = 1_000; +pub const NANOS_PER_HOUR: i64 = 60 * 60 * NANOS_PER_SEC; +pub const NANOS_PER_DAY: i64 = 24 * NANOS_PER_HOUR; +pub const NANOS_PER_MONTH: i64 = 30 * NANOS_PER_DAY; + +pub const DAYS_PER_MONTH: i64 = 30; + +impl Interval { + /// Creates a new interval from months, days and nanoseconds. + /// Precision is nanosecond. + pub fn from_month_day_nano(months: i32, days: i32, nsecs: i64) -> Self { + Interval { + months, + days, + nsecs, + unit: IntervalUnit::MonthDayNano, + } + } + + /// Creates a new interval from months. + pub fn from_year_month(months: i32) -> Self { + Interval { + months, + days: 0, + nsecs: 0, + unit: IntervalUnit::YearMonth, + } + } + + /// Creates a new interval from days and milliseconds. + pub fn from_day_time(days: i32, millis: i32) -> Self { + Interval { + months: 0, + days, + nsecs: (millis as i64) * NANOS_PER_MILLI, + unit: IntervalUnit::DayTime, + } + } + + /// Converts the interval to nanoseconds. + pub fn to_nanosecond(&self) -> i128 { + let days = (self.days as i64) + DAYS_PER_MONTH * (self.months as i64); + (self.nsecs as i128) + (NANOS_PER_DAY as i128) * (days as i128) + } + + /// Smallest interval value. + pub const MIN: Self = Self { + months: i32::MIN, + days: i32::MIN, + nsecs: i64::MIN, + unit: IntervalUnit::MonthDayNano, + }; + + /// Largest interval value. + pub const MAX: Self = Self { + months: i32::MAX, + days: i32::MAX, + nsecs: i64::MAX, + unit: IntervalUnit::MonthDayNano, + }; + + /// Returns the justified interval. + /// allows you to adjust the interval of 30-day as one month and the interval of 24-hour as one day + pub fn justified_interval(&self) -> Self { + let mut result = *self; + let extra_months_d = self.days as i64 / DAYS_PER_MONTH; + let extra_months_nsecs = self.nsecs / NANOS_PER_MONTH; + result.days -= (extra_months_d * DAYS_PER_MONTH) as i32; + result.nsecs -= extra_months_nsecs * NANOS_PER_MONTH; + + let extra_days = self.nsecs / NANOS_PER_DAY; + result.nsecs -= extra_days * NANOS_PER_DAY; + + result.months += extra_months_d as i32 + extra_months_nsecs as i32; + result.days += extra_days as i32; + + result + } + + /// Convert Interval to nanoseconds, + /// to check whether Interval is positive + pub fn is_positive(&self) -> bool { + self.to_nanosecond() > 0 + } + + /// is_zero + pub fn is_zero(&self) -> bool { + self.months == 0 && self.days == 0 && self.nsecs == 0 + } + + /// get unit + pub fn unit(&self) -> IntervalUnit { + self.unit + } + + /// Multiple Interval by an integer with overflow check. + /// Returns justified Interval, or `None` if overflow occurred. + pub fn checked_mul_int(&self, rhs: I) -> Option + where + I: TryInto, + { + let rhs = rhs.try_into().ok()?; + let months = self.months.checked_mul(rhs)?; + let days = self.days.checked_mul(rhs)?; + let nsecs = self.nsecs.checked_mul(rhs as i64)?; + + Some( + Self { + months, + days, + nsecs, + unit: self.unit, + } + .justified_interval(), + ) + } + + /// Convert Interval to ISO 8601 string + pub fn to_iso8601_string(self) -> String { + IntervalFormat::from(self).to_iso8601_string() + } + + /// Convert Interval to postgres verbose string + pub fn to_postgres_string(self) -> String { + IntervalFormat::from(self).to_postgres_string() + } + + /// Convert Interval to sql_standard string + pub fn to_sql_standard_string(self) -> String { + IntervalFormat::from(self).to_sql_standard_string() + } + + /// Interval Type and i128[MonthDayNano] Convert + /// v consists of months(i32) | days(i32) | nsecs(i64) + pub fn from_i128(v: i128) -> Self { + Interval { + nsecs: v as i64, + days: (v >> 64) as i32, + months: (v >> 96) as i32, + unit: IntervalUnit::MonthDayNano, + } + } + + /// `Interval` Type and i64[DayTime] Convert + /// v consists of days(i32) | milliseconds(i32) + pub fn from_i64(v: i64) -> Self { + Interval { + nsecs: ((v as i32) as i64) * NANOS_PER_MILLI, + days: (v >> 32) as i32, + months: 0, + unit: IntervalUnit::DayTime, + } + } + + /// `Interval` Type and i32[YearMonth] Convert + /// v consists of months(i32) + pub fn from_i32(v: i32) -> Self { + Interval { + nsecs: 0, + days: 0, + months: v, + unit: IntervalUnit::YearMonth, + } + } + + pub fn to_i128(&self) -> i128 { + let mut result = 0; + result |= self.months as i128; + result <<= 32; + result |= self.days as i128; + result <<= 64; + result |= self.nsecs as i128; + result + } + + pub fn to_i64(&self) -> i64 { + let mut result = 0; + result |= self.days as i64; + result <<= 32; + result |= self.nsecs / NANOS_PER_MILLI; + result + } + + pub fn to_i32(&self) -> i32 { + self.months + } +} + +impl From for Interval { + fn from(v: i128) -> Self { + Self::from_i128(v) + } +} + +impl From for i128 { + fn from(v: Interval) -> Self { + v.to_i128() + } +} + +impl From for serde_json::Value { + fn from(v: Interval) -> Self { + Value::String(v.to_string()) + } +} + +impl Display for Interval { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut s = String::new(); + if self.months != 0 { + write!(s, "{} months ", self.months)?; + } + if self.days != 0 { + write!(s, "{} days ", self.days)?; + } + if self.nsecs != 0 { + write!(s, "{} nsecs", self.nsecs)?; + } + write!(f, "{}", s.trim()) + } +} + +/// https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-INTERVAL-OUTPUT +/// support postgres format, iso8601 format and sql standard format +#[derive(Debug, Clone, Default, Copy, Serialize, Deserialize)] +pub struct IntervalFormat { + pub years: i32, + pub months: i32, + pub days: i32, + pub hours: i64, + pub minutes: i64, + pub seconds: i64, + pub microseconds: i64, +} + +impl From for IntervalFormat { + fn from(val: Interval) -> IntervalFormat { + let months = val.months; + let days = val.days; + let microseconds = val.nsecs / NANOS_PER_MICRO; + let years = (months - (months % 12)) / 12; + let months = months - years * 12; + let hours = (microseconds - (microseconds % 3_600_000_000)) / 3_600_000_000; + let microseconds = microseconds - hours * 3_600_000_000; + let minutes = (microseconds - (microseconds % 60_000_000)) / 60_000_000; + let microseconds = microseconds - minutes * 60_000_000; + let seconds = (microseconds - (microseconds % 1_000_000)) / 1_000_000; + let microseconds = microseconds - seconds * 1_000_000; + IntervalFormat { + years, + months, + days, + hours, + minutes, + seconds, + microseconds, + } + } +} + +impl IntervalFormat { + /// All the field in the interval is 0 + pub fn is_zero(&self) -> bool { + self.years == 0 + && self.months == 0 + && self.days == 0 + && self.hours == 0 + && self.minutes == 0 + && self.seconds == 0 + && self.microseconds == 0 + } + + /// Determine if year or month exist + pub fn has_year_month(&self) -> bool { + self.years != 0 || self.months != 0 + } + + /// Determine if day exists + pub fn has_day(&self) -> bool { + self.days != 0 + } + + /// Determine time part(includes hours, minutes, seconds, microseconds) is positive + pub fn has_time_part_positive(&self) -> bool { + self.hours > 0 || self.minutes > 0 || self.seconds > 0 || self.microseconds > 0 + } + + // time part means hours, minutes, seconds, microseconds + pub fn has_time_part(&self) -> bool { + self.hours != 0 || self.minutes != 0 || self.seconds != 0 || self.microseconds != 0 + } + + /// Convert IntervalFormat to iso8601 format string + /// ISO pattern - PnYnMnDTnHnMnS + /// for example: P1Y2M3DT4H5M6.789S + pub fn to_iso8601_string(&self) -> String { + if self.is_zero() { + return "PT0S".to_string(); + } + let fract_str = match self.microseconds { + 0 => "".to_string(), + _ => format!(".{:06}", self.microseconds) + .trim_end_matches('0') + .to_string(), + }; + format!( + "P{}Y{}M{}DT{}H{}M{}{}S", + self.years, self.months, self.days, self.hours, self.minutes, self.seconds, fract_str + ) + } + + /// Convert IntervalFormat to sql standard format string + /// SQL standard pattern - [years - months] [days] [hours:minutes:seconds[.fractional seconds]] + /// for example: 1-2 3:4:5.678 + pub fn to_sql_standard_string(self) -> String { + if self.is_zero() { + "0".to_string() + } else if !self.has_time_part() && !self.has_day() { + get_year_month(self.months, self.years, true) + } else if !self.has_time_part() && !self.has_year_month() { + format!("{} 0:00:00", self.days) + } else if !self.has_year_month() && !self.has_day() { + get_time_part( + self.hours, + self.minutes, + self.seconds, + self.microseconds, + self.has_time_part_positive(), + true, + ) + } else { + let year_month = get_year_month(self.months, self.years, false); + let time_interval = get_time_part( + self.hours, + self.minutes, + self.seconds, + self.microseconds, + self.has_time_part_positive(), + false, + ); + format!("{} {:+} {}", year_month, self.days, time_interval) + } + } + + /// Convert IntervalFormat to postgres format string + /// postgres pattern - [years - months] [days] [hours[:minutes[:seconds[.fractional seconds]]]] + /// for example: -1 year -2 mons +3 days -04:05:06 + pub fn to_postgres_string(&self) -> String { + if self.is_zero() { + return "00:00:00".to_string(); + } + let mut result = "".to_string(); + if self.has_year_month() { + if self.years != 0 { + result.push_str(&format!("{} year ", self.years)); + } + if self.months != 0 { + result.push_str(&format!("{} mons ", self.months)); + } + } + if self.has_day() { + result.push_str(&format!("{} days ", self.days)); + } + result.push_str(&self.get_postgres_time_part()); + result.trim().to_string() + } + + /// get postgres time part(include hours, minutes, seconds, microseconds) + fn get_postgres_time_part(&self) -> String { + let mut time_part = "".to_string(); + if self.has_time_part() { + let sign = if !self.has_time_part_positive() { + "-" + } else { + "" + }; + let hours = Self::padding_i64(self.hours); + time_part.push_str(&format!( + "{}{}:{}:{}", + sign, + hours, + Self::padding_i64(self.minutes), + Self::padding_i64(self.seconds), + )); + if self.microseconds != 0 { + time_part.push_str(&format!(".{:06}", self.microseconds.unsigned_abs())) + } + } + time_part + } + + /// padding i64 to string with 2 digits + fn padding_i64(val: i64) -> String { + let num = if val < 0 { + val.unsigned_abs() + } else { + val as u64 + }; + format!("{:02}", num) + } +} + +/// get year month string +fn get_year_month(mons: i32, years: i32, is_only_year_month: bool) -> String { + let months = mons.unsigned_abs(); + if years == 0 || is_only_year_month { + format!("{}-{}", years, months) + } else { + format!("{:+}-{}", years, months) + } +} + +/// get time part string +fn get_time_part( + hours: i64, + mins: i64, + secs: i64, + micros: i64, + is_time_part_positive: bool, + is_only_time: bool, +) -> String { + let mut interval = "".to_string(); + if is_time_part_positive && is_only_time { + interval.push_str(&format!("{}:{:02}:{:02}", hours, mins, secs)); + } else { + let minutes = mins.unsigned_abs(); + let seconds = secs.unsigned_abs(); + interval.push_str(&format!("{:+}:{:02}:{:02}", hours, minutes, seconds)); + } + if micros != 0 { + let microseconds = format!(".{:06}", micros.unsigned_abs()); + interval.push_str(µseconds); + } + interval +} + +/// IntervalCompare is used to compare two intervals +/// It makes interval into nanoseconds style. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] +struct IntervalCompare(i128); + +impl From for IntervalCompare { + fn from(interval: Interval) -> Self { + Self(interval.to_nanosecond()) + } +} + +impl Ord for Interval { + fn cmp(&self, other: &Self) -> Ordering { + IntervalCompare::from(*self).cmp(&IntervalCompare::from(*other)) + } +} + +impl PartialOrd for Interval { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Eq for Interval {} + +impl PartialEq for Interval { + fn eq(&self, other: &Self) -> bool { + self.cmp(other).is_eq() + } +} + +impl Hash for Interval { + fn hash(&self, state: &mut H) { + IntervalCompare::from(*self).hash(state) + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use super::*; + + #[test] + fn test_from_year_month() { + let interval = Interval::from_year_month(1); + assert_eq!(interval.months, 1); + } + + #[test] + fn test_from_date_time() { + let interval = Interval::from_day_time(1, 2); + assert_eq!(interval.days, 1); + assert_eq!(interval.nsecs, 2_000_000); + } + + #[test] + fn test_interval_is_positive() { + let interval = Interval::from_year_month(1); + assert!(interval.is_positive()); + let interval = Interval::from_year_month(-1); + assert!(!interval.is_positive()); + + let interval = Interval::from_day_time(1, i32::MIN); + assert!(!interval.is_positive()); + } + + #[test] + fn test_to_nanosecond() { + let interval = Interval::from_year_month(1); + assert_eq!(interval.to_nanosecond(), 2592000000000000); + let interval = Interval::from_day_time(1, 2); + assert_eq!(interval.to_nanosecond(), 86400002000000); + + let max_interval = Interval::from_month_day_nano(i32::MAX, i32::MAX, i64::MAX); + assert_eq!(max_interval.to_nanosecond(), 5751829423496836854775807); + + let min_interval = Interval::from_month_day_nano(i32::MIN, i32::MIN, i64::MIN); + assert_eq!(min_interval.to_nanosecond(), -5751829426175236854775808); + } + + #[test] + fn test_interval_is_zero() { + let interval = Interval::from_month_day_nano(1, 1, 1); + assert!(!interval.is_zero()); + let interval = Interval::from_month_day_nano(0, 0, 0); + assert!(interval.is_zero()); + } + + #[test] + fn test_interval_i128_convert() { + let interval = Interval::from_month_day_nano(1, 1, 1); + let interval_i128 = interval.to_i128(); + assert_eq!(interval_i128, 79228162532711081667253501953); + } + + #[test] + fn test_convert_interval_format() { + let interval = Interval::from_month_day_nano(14, 160, 1000000); + let interval_format = IntervalFormat::from(interval); + assert_eq!(interval_format.years, 1); + assert_eq!(interval_format.months, 2); + assert_eq!(interval_format.days, 160); + assert_eq!(interval_format.hours, 0); + assert_eq!(interval_format.minutes, 0); + assert_eq!(interval_format.seconds, 0); + assert_eq!(interval_format.microseconds, 1000); + } + + #[test] + fn test_interval_hash() { + let interval = Interval::from_month_day_nano(1, 31, 1); + let interval2 = Interval::from_month_day_nano(2, 1, 1); + let mut map = HashMap::new(); + map.insert(interval, 1); + assert_eq!(map.get(&interval2), Some(&1)); + } + + #[test] + fn test_interval_mul_int() { + let interval = Interval::from_month_day_nano(1, 1, 1); + let interval2 = interval.checked_mul_int(2).unwrap(); + assert_eq!(interval2.months, 2); + assert_eq!(interval2.days, 2); + assert_eq!(interval2.nsecs, 2); + + // test justified interval + let interval = Interval::from_month_day_nano(1, 31, 1); + let interval2 = interval.checked_mul_int(2).unwrap(); + assert_eq!(interval2.months, 4); + assert_eq!(interval2.days, 2); + assert_eq!(interval2.nsecs, 2); + + // test overflow situation + let interval = Interval::from_month_day_nano(i32::MAX, 1, 1); + let interval2 = interval.checked_mul_int(2); + assert!(interval2.is_none()); + } + + #[test] + fn test_display() { + let interval = Interval::from_month_day_nano(1, 1, 1); + assert_eq!(interval.to_string(), "1 months 1 days 1 nsecs"); + + let interval = Interval::from_month_day_nano(14, 31, 10000000000); + assert_eq!(interval.to_string(), "14 months 31 days 10000000000 nsecs"); + } + + #[test] + fn test_interval_justified() { + let interval = Interval::from_month_day_nano(1, 131, 1).justified_interval(); + let interval2 = Interval::from_month_day_nano(5, 11, 1); + assert_eq!(interval, interval2); + + let interval = Interval::from_month_day_nano(1, 1, NANOS_PER_MONTH + 2 * NANOS_PER_DAY) + .justified_interval(); + let interval2 = Interval::from_month_day_nano(2, 3, 0); + assert_eq!(interval, interval2); + } + + #[test] + fn test_serde_json() { + let interval = Interval::from_month_day_nano(1, 1, 1); + let json = serde_json::to_string(&interval).unwrap(); + assert_eq!( + json, + "{\"months\":1,\"days\":1,\"nsecs\":1,\"unit\":\"MonthDayNano\"}" + ); + let interval2: Interval = serde_json::from_str(&json).unwrap(); + assert_eq!(interval, interval2); + } + + #[test] + fn test_to_iso8601_string() { + // Test interval zero + let interval = Interval::from_month_day_nano(0, 0, 0); + assert_eq!(interval.to_iso8601_string(), "PT0S"); + + let interval = Interval::from_month_day_nano(1, 1, 1); + assert_eq!(interval.to_iso8601_string(), "P0Y1M1DT0H0M0S"); + + let interval = Interval::from_month_day_nano(14, 31, 10000000000); + assert_eq!(interval.to_iso8601_string(), "P1Y2M31DT0H0M10S"); + + let interval = Interval::from_month_day_nano(14, 31, 23210200000000); + assert_eq!(interval.to_iso8601_string(), "P1Y2M31DT6H26M50.2S"); + } + + #[test] + fn test_to_postgres_string() { + // Test interval zero + let interval = Interval::from_month_day_nano(0, 0, 0); + assert_eq!(interval.to_postgres_string(), "00:00:00"); + + let interval = Interval::from_month_day_nano(23, 100, 23210200000000); + assert_eq!( + interval.to_postgres_string(), + "1 year 11 mons 100 days 06:26:50.200000" + ); + } + + #[test] + fn test_to_sql_standard_string() { + // Test zero interval + let interval = Interval::from_month_day_nano(0, 0, 0); + assert_eq!(interval.to_sql_standard_string(), "0"); + + let interval = Interval::from_month_day_nano(23, 100, 23210200000000); + assert_eq!( + interval.to_sql_standard_string(), + "+1-11 +100 +6:26:50.200000" + ); + + // Test interval without year, month, day + let interval = Interval::from_month_day_nano(0, 0, 23210200000000); + assert_eq!(interval.to_sql_standard_string(), "6:26:50.200000"); + } + + #[test] + fn test_from_arrow_interval_unit() { + let unit = ArrowIntervalUnit::YearMonth; + assert_eq!(IntervalUnit::from(unit), IntervalUnit::YearMonth); + + let unit = ArrowIntervalUnit::DayTime; + assert_eq!(IntervalUnit::from(unit), IntervalUnit::DayTime); + + let unit = ArrowIntervalUnit::MonthDayNano; + assert_eq!(IntervalUnit::from(unit), IntervalUnit::MonthDayNano); + } +} diff --git a/src/common/time/src/lib.rs b/src/common/time/src/lib.rs index 1328d08a23..9cd61cf6fb 100644 --- a/src/common/time/src/lib.rs +++ b/src/common/time/src/lib.rs @@ -15,6 +15,7 @@ pub mod date; pub mod datetime; pub mod error; +pub mod interval; pub mod range; pub mod time; pub mod timestamp; @@ -24,6 +25,7 @@ pub mod util; pub use date::Date; pub use datetime::DateTime; +pub use interval::Interval; pub use range::RangeMillis; pub use timestamp::Timestamp; pub use timestamp_millis::TimestampMillis; diff --git a/src/datatypes/src/data_type.rs b/src/datatypes/src/data_type.rs index 74c5450a94..4d87c76412 100644 --- a/src/datatypes/src/data_type.rs +++ b/src/datatypes/src/data_type.rs @@ -15,7 +15,10 @@ use std::fmt; use std::sync::Arc; -use arrow::datatypes::{DataType as ArrowDataType, TimeUnit as ArrowTimeUnit}; +use arrow::datatypes::{ + DataType as ArrowDataType, IntervalUnit as ArrowIntervalUnit, TimeUnit as ArrowTimeUnit, +}; +use common_time::interval::IntervalUnit; use common_time::timestamp::TimeUnit; use paste::paste; use serde::{Deserialize, Serialize}; @@ -24,7 +27,8 @@ use crate::error::{self, Error, Result}; use crate::type_id::LogicalTypeId; use crate::types::{ BinaryType, BooleanType, DateTimeType, DateType, DictionaryType, Float32Type, Float64Type, - Int16Type, Int32Type, Int64Type, Int8Type, ListType, NullType, StringType, TimeMillisecondType, + Int16Type, Int32Type, Int64Type, Int8Type, IntervalDayTimeType, IntervalMonthDayNanoType, + IntervalType, IntervalYearMonthType, ListType, NullType, StringType, TimeMillisecondType, TimeType, TimestampMicrosecondType, TimestampMillisecondType, TimestampNanosecondType, TimestampSecondType, TimestampType, UInt16Type, UInt32Type, UInt64Type, UInt8Type, }; @@ -59,6 +63,9 @@ pub enum ConcreteDataType { Timestamp(TimestampType), Time(TimeType), + // Interval types: + Interval(IntervalType), + // Compound types: List(ListType), Dictionary(DictionaryType), @@ -87,6 +94,7 @@ impl fmt::Display for ConcreteDataType { ConcreteDataType::Time(_) => write!(f, "Time"), ConcreteDataType::List(_) => write!(f, "List"), ConcreteDataType::Dictionary(_) => write!(f, "Dictionary"), + ConcreteDataType::Interval(_) => write!(f, "Interval"), } } } @@ -113,6 +121,7 @@ impl ConcreteDataType { | ConcreteDataType::DateTime(_) | ConcreteDataType::Timestamp(_) | ConcreteDataType::Time(_) + | ConcreteDataType::Interval(_) ) } @@ -127,6 +136,7 @@ impl ConcreteDataType { | ConcreteDataType::DateTime(_) | ConcreteDataType::Timestamp(_) | ConcreteDataType::Time(_) + | ConcreteDataType::Interval(_) ) } @@ -222,6 +232,7 @@ impl TryFrom<&ArrowDataType> for ConcreteDataType { ArrowDataType::Date32 => Self::date_datatype(), ArrowDataType::Date64 => Self::datetime_datatype(), ArrowDataType::Timestamp(u, _) => ConcreteDataType::from_arrow_time_unit(u), + ArrowDataType::Interval(u) => ConcreteDataType::from_arrow_interval_unit(u), ArrowDataType::Binary | ArrowDataType::LargeBinary => Self::binary_datatype(), ArrowDataType::Utf8 | ArrowDataType::LargeUtf8 => Self::string_datatype(), ArrowDataType::List(field) => Self::List(ListType::new( @@ -311,6 +322,20 @@ impl ConcreteDataType { Self::time_datatype(TimeUnit::Nanosecond) } + pub fn interval_month_day_nano_datatype() -> Self { + ConcreteDataType::Interval(IntervalType::MonthDayNano( + IntervalMonthDayNanoType::default(), + )) + } + + pub fn interval_year_month_datatype() -> Self { + ConcreteDataType::Interval(IntervalType::YearMonth(IntervalYearMonthType::default())) + } + + pub fn interval_day_time_datatype() -> Self { + ConcreteDataType::Interval(IntervalType::DayTime(IntervalDayTimeType::default())) + } + pub fn timestamp_datatype(unit: TimeUnit) -> Self { match unit { TimeUnit::Second => Self::timestamp_second_datatype(), @@ -330,6 +355,22 @@ impl ConcreteDataType { } } + pub fn interval_datatype(unit: IntervalUnit) -> Self { + match unit { + IntervalUnit::YearMonth => Self::interval_year_month_datatype(), + IntervalUnit::DayTime => Self::interval_day_time_datatype(), + IntervalUnit::MonthDayNano => Self::interval_month_day_nano_datatype(), + } + } + + pub fn from_arrow_interval_unit(u: &ArrowIntervalUnit) -> Self { + match u { + ArrowIntervalUnit::YearMonth => Self::interval_year_month_datatype(), + ArrowIntervalUnit::DayTime => Self::interval_day_time_datatype(), + ArrowIntervalUnit::MonthDayNano => Self::interval_month_day_nano_datatype(), + } + } + pub fn list_datatype(item_type: ConcreteDataType) -> ConcreteDataType { ConcreteDataType::List(ListType::new(item_type)) } @@ -545,6 +586,10 @@ mod tests { assert!(ConcreteDataType::time_millisecond_datatype().is_stringifiable()); assert!(ConcreteDataType::time_microsecond_datatype().is_stringifiable()); assert!(ConcreteDataType::time_nanosecond_datatype().is_stringifiable()); + + assert!(ConcreteDataType::interval_year_month_datatype().is_stringifiable()); + assert!(ConcreteDataType::interval_day_time_datatype().is_stringifiable()); + assert!(ConcreteDataType::interval_month_day_nano_datatype().is_stringifiable()); } #[test] @@ -563,6 +608,9 @@ mod tests { assert!(ConcreteDataType::time_millisecond_datatype().is_signed()); assert!(ConcreteDataType::time_microsecond_datatype().is_signed()); assert!(ConcreteDataType::time_nanosecond_datatype().is_signed()); + assert!(ConcreteDataType::interval_year_month_datatype().is_signed()); + assert!(ConcreteDataType::interval_day_time_datatype().is_signed()); + assert!(ConcreteDataType::interval_month_day_nano_datatype().is_signed()); assert!(!ConcreteDataType::uint8_datatype().is_signed()); assert!(!ConcreteDataType::uint16_datatype().is_signed()); @@ -589,6 +637,9 @@ mod tests { assert!(!ConcreteDataType::time_millisecond_datatype().is_unsigned()); assert!(!ConcreteDataType::time_microsecond_datatype().is_unsigned()); assert!(!ConcreteDataType::time_nanosecond_datatype().is_unsigned()); + assert!(!ConcreteDataType::interval_year_month_datatype().is_unsigned()); + assert!(!ConcreteDataType::interval_day_time_datatype().is_unsigned()); + assert!(!ConcreteDataType::interval_month_day_nano_datatype().is_unsigned()); assert!(ConcreteDataType::uint8_datatype().is_unsigned()); assert!(ConcreteDataType::uint16_datatype().is_unsigned()); @@ -691,5 +742,12 @@ mod tests { "Date" ); assert_eq!(ConcreteDataType::time_second_datatype().to_string(), "Time"); + assert_eq!( + ConcreteDataType::from_arrow_type(&ArrowDataType::Interval( + arrow_schema::IntervalUnit::MonthDayNano, + )) + .to_string(), + "Interval" + ) } } diff --git a/src/datatypes/src/interval.rs b/src/datatypes/src/interval.rs new file mode 100644 index 0000000000..1a1e2b1dbd --- /dev/null +++ b/src/datatypes/src/interval.rs @@ -0,0 +1,138 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use common_time::interval::Interval; +use paste::paste; +use serde::{Deserialize, Serialize}; + +use crate::prelude::{Scalar, Value, ValueRef}; +use crate::scalars::ScalarRef; +use crate::types::{ + IntervalDayTimeType, IntervalMonthDayNanoType, IntervalYearMonthType, WrapperType, +}; +use crate::vectors::{IntervalDayTimeVector, IntervalMonthDayNanoVector, IntervalYearMonthVector}; + +macro_rules! define_interval_with_unit { + ($unit: ident, $native_ty: ty) => { + paste! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] + pub struct [](pub Interval); + + impl [] { + pub fn new(val: $native_ty) -> Self { + Self(Interval:: [](val)) + } + } + + impl Default for [] { + fn default() -> Self { + Self::new(0) + } + } + + impl From<[]> for Value { + fn from(t: []) -> Value { + Value::Interval(t.0) + } + } + + impl From<[]> for serde_json::Value { + fn from(t: []) -> Self { + t.0.into() + } + } + + impl From<[]> for ValueRef<'static> { + fn from(t: []) -> Self { + ValueRef::Interval(t.0) + } + } + + impl Scalar for [] { + type VectorType = []; + type RefType<'a> = []; + + fn as_scalar_ref(&self) -> Self::RefType<'_> { + *self + } + + fn upcast_gat<'short, 'long: 'short>( + long: Self::RefType<'long>, + ) -> Self::RefType<'short> { + long + } + } + + impl<'a> ScalarRef<'a> for [] { + type ScalarType = []; + + fn to_owned_scalar(&self) -> Self::ScalarType { + *self + } + } + + impl WrapperType for [] { + type LogicalType = []; + type Native = $native_ty; + + fn from_native(value: Self::Native) -> Self { + Self::new(value) + } + + fn into_native(self) -> Self::Native { + self.0.[]() + } + } + + impl From<$native_ty> for [] { + fn from(val: $native_ty) -> Self { + []::from_native(val as $native_ty) + } + } + + impl From<[]> for $native_ty { + fn from(val: []) -> Self { + val.0.[]() + } + } + } + }; +} + +define_interval_with_unit!(YearMonth, i32); +define_interval_with_unit!(DayTime, i64); +define_interval_with_unit!(MonthDayNano, i128); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_interval_scalar() { + let interval = IntervalYearMonth::new(1000); + assert_eq!(interval, interval.as_scalar_ref()); + assert_eq!(interval, interval.to_owned_scalar()); + assert_eq!(1000, interval.into_native()); + + let interval = IntervalDayTime::new(1000); + assert_eq!(interval, interval.as_scalar_ref()); + assert_eq!(interval, interval.to_owned_scalar()); + assert_eq!(1000, interval.into_native()); + + let interval = IntervalMonthDayNano::new(1000); + assert_eq!(interval, interval.as_scalar_ref()); + assert_eq!(interval, interval.to_owned_scalar()); + assert_eq!(1000, interval.into_native()); + } +} diff --git a/src/datatypes/src/lib.rs b/src/datatypes/src/lib.rs index 2ed804deee..3d8a7835c4 100644 --- a/src/datatypes/src/lib.rs +++ b/src/datatypes/src/lib.rs @@ -17,6 +17,7 @@ pub mod arrow_array; pub mod data_type; pub mod error; +pub mod interval; pub mod macros; pub mod prelude; pub mod scalars; diff --git a/src/datatypes/src/type_id.rs b/src/datatypes/src/type_id.rs index 602d4cba8b..8a4a02589d 100644 --- a/src/datatypes/src/type_id.rs +++ b/src/datatypes/src/type_id.rs @@ -51,6 +51,12 @@ pub enum LogicalTypeId { TimeMillisecond, TimeMicrosecond, TimeNanosecond, + /// A 32-bit interval representing the elapsed time in months. + IntervalYearMonth, + /// A 64-bit interval representing the elapsed time in days and milliseconds. + IntervalDayTime, + /// A 128-bit interval representing the elapsed time in months, days and nanoseconds. + IntervalMonthDayNano, List, Dictionary, @@ -102,6 +108,11 @@ impl LogicalTypeId { LogicalTypeId::TimeMillisecond => ConcreteDataType::time_millisecond_datatype(), LogicalTypeId::TimeMicrosecond => ConcreteDataType::time_microsecond_datatype(), LogicalTypeId::TimeNanosecond => ConcreteDataType::time_nanosecond_datatype(), + LogicalTypeId::IntervalYearMonth => ConcreteDataType::interval_year_month_datatype(), + LogicalTypeId::IntervalDayTime => ConcreteDataType::interval_day_time_datatype(), + LogicalTypeId::IntervalMonthDayNano => { + ConcreteDataType::interval_month_day_nano_datatype() + } } } } diff --git a/src/datatypes/src/types.rs b/src/datatypes/src/types.rs index 41870c35f7..d01822a3b9 100644 --- a/src/datatypes/src/types.rs +++ b/src/datatypes/src/types.rs @@ -17,6 +17,7 @@ mod boolean_type; mod date_type; mod datetime_type; mod dictionary_type; +mod interval_type; mod list_type; mod null_type; mod primitive_type; @@ -29,6 +30,9 @@ pub use boolean_type::BooleanType; pub use date_type::DateType; pub use datetime_type::DateTimeType; pub use dictionary_type::DictionaryType; +pub use interval_type::{ + IntervalDayTimeType, IntervalMonthDayNanoType, IntervalType, IntervalYearMonthType, +}; pub use list_type::ListType; pub use null_type::NullType; pub use primitive_type::{ diff --git a/src/datatypes/src/types/interval_type.rs b/src/datatypes/src/types/interval_type.rs new file mode 100644 index 0000000000..d9f105673b --- /dev/null +++ b/src/datatypes/src/types/interval_type.rs @@ -0,0 +1,176 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use arrow::datatypes::{ + DataType as ArrowDataType, IntervalDayTimeType as ArrowIntervalDayTimeType, + IntervalMonthDayNanoType as ArrowIntervalMonthDayNanoType, IntervalUnit as ArrowIntervalUnit, + IntervalYearMonthType as ArrowIntervalYearMonthType, +}; +use common_time::interval::IntervalUnit; +use common_time::Interval; +use enum_dispatch::enum_dispatch; +use paste::paste; +use serde::{Deserialize, Serialize}; +use snafu::OptionExt; + +use crate::data_type::ConcreteDataType; +use crate::error; +use crate::interval::{IntervalDayTime, IntervalMonthDayNano, IntervalYearMonth}; +use crate::prelude::{ + DataType, LogicalTypeId, MutableVector, ScalarVectorBuilder, Value, ValueRef, Vector, +}; +use crate::types::LogicalPrimitiveType; +use crate::vectors::{ + IntervalDayTimeVector, IntervalDayTimeVectorBuilder, IntervalMonthDayNanoVector, + IntervalMonthDayNanoVectorBuilder, IntervalYearMonthVector, IntervalYearMonthVectorBuilder, + PrimitiveVector, +}; + +/// The "calendar" interval is a type of time interval that does not +/// have a precise duration without taking into account a specific +/// base timestamp. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[enum_dispatch(DataType)] +pub enum IntervalType { + YearMonth(IntervalYearMonthType), + DayTime(IntervalDayTimeType), + MonthDayNano(IntervalMonthDayNanoType), +} + +impl IntervalType { + /// Returns the unit of the interval. + pub fn unit(&self) -> IntervalUnit { + match self { + IntervalType::YearMonth(_) => IntervalUnit::YearMonth, + IntervalType::DayTime(_) => IntervalUnit::DayTime, + IntervalType::MonthDayNano(_) => IntervalUnit::MonthDayNano, + } + } +} + +macro_rules! impl_data_type_for_interval { + ($unit: ident, $type: ty) => { + paste! { + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] + pub struct []; + + impl DataType for [] { + fn name(&self) -> &str { + stringify!([]) + } + + fn logical_type_id(&self) -> LogicalTypeId { + LogicalTypeId::[] + } + + fn default_value(&self) -> Value { + Value::Interval(Interval::from_i128(0)) + } + + fn as_arrow_type(&self) -> ArrowDataType { + ArrowDataType::Interval(ArrowIntervalUnit::$unit) + } + + fn create_mutable_vector(&self, capacity: usize) -> Box { + Box::new([]::with_capacity(capacity)) + } + + fn is_timestamp_compatible(&self) -> bool { + false + } + } + + impl LogicalPrimitiveType for [] { + type ArrowPrimitive = []; + type Native = $type; + type Wrapper = []; + type LargestType = Self; + + fn build_data_type() -> ConcreteDataType { + ConcreteDataType::Interval(IntervalType::$unit( + []::default(), + )) + } + + fn type_name() -> &'static str { + stringify!([]) + } + + fn cast_vector(vector: &dyn Vector) -> crate::Result<&PrimitiveVector> { + vector + .as_any() + .downcast_ref::<[]>() + .with_context(|| error::CastTypeSnafu { + msg: format!( + "Failed to cast {} to {}", + vector.vector_type_name(), stringify!([]) + ), + }) + } + + fn cast_value_ref(value: ValueRef) -> crate::Result> { + match value { + ValueRef::Null => Ok(None), + ValueRef::Interval(t) => Ok(Some([](t))), + other => error::CastTypeSnafu { + msg: format!("Failed to cast value {:?} to {}", other, stringify!([])), + } + .fail(), + } + } + } + } + } +} + +impl_data_type_for_interval!(YearMonth, i32); +impl_data_type_for_interval!(DayTime, i64); +impl_data_type_for_interval!(MonthDayNano, i128); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_interval_type_unit() { + assert_eq!( + IntervalUnit::DayTime, + IntervalType::DayTime(IntervalDayTimeType).unit() + ); + assert_eq!( + IntervalUnit::MonthDayNano, + IntervalType::MonthDayNano(IntervalMonthDayNanoType).unit() + ); + assert_eq!( + IntervalUnit::YearMonth, + IntervalType::YearMonth(IntervalYearMonthType).unit() + ); + } + + #[test] + fn test_interval_as_arrow_type() { + assert_eq!( + ArrowDataType::Interval(ArrowIntervalUnit::DayTime), + IntervalType::DayTime(IntervalDayTimeType).as_arrow_type() + ); + assert_eq!( + ArrowDataType::Interval(ArrowIntervalUnit::MonthDayNano), + IntervalType::MonthDayNano(IntervalMonthDayNanoType).as_arrow_type() + ); + assert_eq!( + ArrowDataType::Interval(ArrowIntervalUnit::YearMonth), + IntervalType::YearMonth(IntervalYearMonthType).as_arrow_type() + ); + } +} diff --git a/src/datatypes/src/types/primitive_type.rs b/src/datatypes/src/types/primitive_type.rs index 4a9df85b30..d8526d8a13 100644 --- a/src/datatypes/src/types/primitive_type.rs +++ b/src/datatypes/src/types/primitive_type.rs @@ -46,6 +46,7 @@ impl_native_type!(i8); impl_native_type!(i16); impl_native_type!(i32); impl_native_type!(i64); +impl_native_type!(i128); impl_native_type!(f32); impl_native_type!(f64); diff --git a/src/datatypes/src/value.rs b/src/datatypes/src/value.rs index 483f4ec9de..097cb4d103 100644 --- a/src/datatypes/src/value.rs +++ b/src/datatypes/src/value.rs @@ -22,8 +22,10 @@ use common_base::bytes::{Bytes, StringBytes}; use common_telemetry::logging; use common_time::date::Date; use common_time::datetime::DateTime; +use common_time::interval::IntervalUnit; use common_time::time::Time; use common_time::timestamp::{TimeUnit, Timestamp}; +use common_time::Interval; use datafusion_common::ScalarValue; pub use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize}; @@ -33,7 +35,7 @@ use crate::error; use crate::error::Result; use crate::prelude::*; use crate::type_id::LogicalTypeId; -use crate::types::ListType; +use crate::types::{IntervalType, ListType}; use crate::vectors::ListVector; pub type OrderedF32 = OrderedFloat; @@ -68,6 +70,7 @@ pub enum Value { DateTime(DateTime), Timestamp(Timestamp), Time(Time), + Interval(Interval), List(ListValue), } @@ -100,6 +103,7 @@ impl Display for Value { Value::DateTime(v) => write!(f, "{v}"), Value::Timestamp(v) => write!(f, "{}", v.to_iso8601_string()), Value::Time(t) => write!(f, "{}", t.to_iso8601_string()), + Value::Interval(v) => write!(f, "{}", v.to_iso8601_string()), Value::List(v) => { let default = Box::>::default(); let items = v.items().as_ref().unwrap_or(&default); @@ -139,6 +143,7 @@ impl Value { Value::DateTime(_) => ConcreteDataType::datetime_datatype(), Value::Time(t) => ConcreteDataType::time_datatype(*t.unit()), Value::Timestamp(v) => ConcreteDataType::timestamp_datatype(v.unit()), + Value::Interval(v) => ConcreteDataType::interval_datatype(v.unit()), Value::List(list) => ConcreteDataType::list_datatype(list.datatype().clone()), } } @@ -182,6 +187,7 @@ impl Value { Value::List(v) => ValueRef::List(ListValueRef::Ref { val: v }), Value::Timestamp(v) => ValueRef::Timestamp(*v), Value::Time(v) => ValueRef::Time(*v), + Value::Interval(v) => ValueRef::Interval(*v), } } @@ -235,6 +241,11 @@ impl Value { TimeUnit::Microsecond => LogicalTypeId::TimeMicrosecond, TimeUnit::Nanosecond => LogicalTypeId::TimeNanosecond, }, + Value::Interval(v) => match v.unit() { + IntervalUnit::YearMonth => LogicalTypeId::IntervalYearMonth, + IntervalUnit::DayTime => LogicalTypeId::IntervalDayTime, + IntervalUnit::MonthDayNano => LogicalTypeId::IntervalMonthDayNano, + }, } } @@ -276,6 +287,11 @@ impl Value { } Value::Timestamp(t) => timestamp_to_scalar_value(t.unit(), Some(t.value())), Value::Time(t) => time_to_scalar_value(*t.unit(), Some(t.value()))?, + Value::Interval(v) => match v.unit() { + IntervalUnit::YearMonth => ScalarValue::IntervalYearMonth(Some(v.to_i32())), + IntervalUnit::DayTime => ScalarValue::IntervalDayTime(Some(v.to_i64())), + IntervalUnit::MonthDayNano => ScalarValue::IntervalMonthDayNano(Some(v.to_i128())), + }, }; Ok(scalar_value) @@ -301,6 +317,11 @@ pub fn to_null_scalar_value(output_type: &ConcreteDataType) -> Result ScalarValue::Date32(None), ConcreteDataType::DateTime(_) => ScalarValue::Date64(None), ConcreteDataType::Timestamp(t) => timestamp_to_scalar_value(t.unit(), None), + ConcreteDataType::Interval(v) => match v { + IntervalType::YearMonth(_) => ScalarValue::IntervalYearMonth(None), + IntervalType::DayTime(_) => ScalarValue::IntervalDayTime(None), + IntervalType::MonthDayNano(_) => ScalarValue::IntervalMonthDayNano(None), + }, ConcreteDataType::List(_) => { ScalarValue::List(None, Arc::new(new_item_field(output_type.as_arrow_type()))) } @@ -361,6 +382,16 @@ pub fn scalar_value_to_timestamp(scalar: &ScalarValue) -> Option { } } +/// Convert [ScalarValue] to [Interval]. +pub fn scalar_value_to_interval(scalar: &ScalarValue) -> Option { + match scalar { + ScalarValue::IntervalYearMonth(v) => v.map(Interval::from_i32), + ScalarValue::IntervalDayTime(v) => v.map(Interval::from_i64), + ScalarValue::IntervalMonthDayNano(v) => v.map(Interval::from_i128), + _ => None, + } +} + macro_rules! impl_ord_for_value_like { ($Type: ident, $left: ident, $right: ident) => { if $left.is_null() && !$right.is_null() { @@ -387,6 +418,7 @@ macro_rules! impl_ord_for_value_like { ($Type::DateTime(v1), $Type::DateTime(v2)) => v1.cmp(v2), ($Type::Timestamp(v1), $Type::Timestamp(v2)) => v1.cmp(v2), ($Type::Time(v1), $Type::Time(v2)) => v1.cmp(v2), + ($Type::Interval(v1), $Type::Interval(v2)) => v1.cmp(v2), ($Type::List(v1), $Type::List(v2)) => v1.cmp(v2), _ => panic!( "Cannot compare different values {:?} and {:?}", @@ -444,6 +476,7 @@ impl_value_from!(Binary, Bytes); impl_value_from!(Date, Date); impl_value_from!(DateTime, DateTime); impl_value_from!(Timestamp, Timestamp); +impl_value_from!(Interval, Interval); impl From for Value { fn from(string: String) -> Value { @@ -493,6 +526,7 @@ impl TryFrom for serde_json::Value { Value::List(v) => serde_json::to_value(v)?, Value::Timestamp(v) => serde_json::to_value(v.value())?, Value::Time(v) => serde_json::to_value(v.value())?, + Value::Interval(v) => serde_json::to_value(v.to_i128())?, }; Ok(json_value) @@ -643,10 +677,16 @@ impl TryFrom for Value { .map(|x| Value::Time(Time::new(x, TimeUnit::Nanosecond))) .unwrap_or(Value::Null), + ScalarValue::IntervalYearMonth(t) => t + .map(|x| Value::Interval(Interval::from_i32(x))) + .unwrap_or(Value::Null), + ScalarValue::IntervalDayTime(t) => t + .map(|x| Value::Interval(Interval::from_i64(x))) + .unwrap_or(Value::Null), + ScalarValue::IntervalMonthDayNano(t) => t + .map(|x| Value::Interval(Interval::from_i128(x))) + .unwrap_or(Value::Null), ScalarValue::Decimal128(_, _, _) - | ScalarValue::IntervalYearMonth(_) - | ScalarValue::IntervalDayTime(_) - | ScalarValue::IntervalMonthDayNano(_) | ScalarValue::Struct(_, _) | ScalarValue::Dictionary(_, _) => { return error::UnsupportedArrowTypeSnafu { @@ -686,6 +726,7 @@ pub enum ValueRef<'a> { DateTime(DateTime), Timestamp(Timestamp), Time(Time), + Interval(Interval), // Compound types: List(ListValueRef<'a>), @@ -749,6 +790,11 @@ impl<'a> ValueRef<'a> { impl_as_for_value_ref!(self, Time) } + /// Cast itself to [Interval]. + pub fn as_interval(&self) -> Result> { + impl_as_for_value_ref!(self, Interval) + } + /// Cast itself to [ListValueRef]. pub fn as_list(&self) -> Result> { impl_as_for_value_ref!(self, List) @@ -801,6 +847,7 @@ impl_value_ref_from!(Date, Date); impl_value_ref_from!(DateTime, DateTime); impl_value_ref_from!(Timestamp, Timestamp); impl_value_ref_from!(Time, Time); +impl_value_ref_from!(Interval, Interval); impl<'a> From<&'a str> for ValueRef<'a> { fn from(string: &'a str) -> ValueRef<'a> { @@ -1063,6 +1110,18 @@ mod tests { .try_into() .unwrap() ); + assert_eq!( + Value::Null, + ScalarValue::IntervalMonthDayNano(None).try_into().unwrap() + ); + assert_eq!( + Value::Interval(Interval::from_month_day_nano(1, 1, 1)), + ScalarValue::IntervalMonthDayNano(Some( + Interval::from_month_day_nano(1, 1, 1).to_i128() + )) + .try_into() + .unwrap() + ); assert_eq!( Value::Time(Time::new(1, TimeUnit::Second)), @@ -1248,6 +1307,10 @@ mod tests { &ConcreteDataType::time_nanosecond_datatype(), &Value::Time(Time::new_nanosecond(1)), ); + check_type_and_value( + &ConcreteDataType::interval_month_day_nano_datatype(), + &Value::Interval(Interval::from_month_day_nano(1, 2, 3)), + ); } #[test] @@ -1405,6 +1468,7 @@ mod tests { check_as_value_ref!(Float64, OrderedF64::from(16.0)); check_as_value_ref!(Timestamp, Timestamp::new_millisecond(1)); check_as_value_ref!(Time, Time::new_millisecond(1)); + check_as_value_ref!(Interval, Interval::from_month_day_nano(1, 2, 3)); assert_eq!( ValueRef::String("hello"), diff --git a/src/datatypes/src/vectors.rs b/src/datatypes/src/vectors.rs index 5c873a070a..0e4c40fafd 100644 --- a/src/datatypes/src/vectors.rs +++ b/src/datatypes/src/vectors.rs @@ -32,6 +32,7 @@ mod date; mod datetime; mod eq; mod helper; +mod interval; mod list; mod null; pub(crate) mod operations; @@ -47,6 +48,10 @@ pub use constant::ConstantVector; pub use date::{DateVector, DateVectorBuilder}; pub use datetime::{DateTimeVector, DateTimeVectorBuilder}; pub use helper::Helper; +pub use interval::{ + IntervalDayTimeVector, IntervalDayTimeVectorBuilder, IntervalMonthDayNanoVector, + IntervalMonthDayNanoVectorBuilder, IntervalYearMonthVector, IntervalYearMonthVectorBuilder, +}; pub use list::{ListIter, ListVector, ListVectorBuilder}; pub use null::{NullVector, NullVectorBuilder}; pub use primitive::{ diff --git a/src/datatypes/src/vectors/eq.rs b/src/datatypes/src/vectors/eq.rs index 1f9c2527de..4adb0df34d 100644 --- a/src/datatypes/src/vectors/eq.rs +++ b/src/datatypes/src/vectors/eq.rs @@ -14,14 +14,17 @@ use std::sync::Arc; +use common_time::interval::IntervalUnit; + use crate::data_type::DataType; use crate::types::{TimeType, TimestampType}; use crate::vectors::constant::ConstantVector; use crate::vectors::{ - BinaryVector, BooleanVector, DateTimeVector, DateVector, ListVector, PrimitiveVector, - StringVector, TimeMicrosecondVector, TimeMillisecondVector, TimeNanosecondVector, - TimeSecondVector, TimestampMicrosecondVector, TimestampMillisecondVector, - TimestampNanosecondVector, TimestampSecondVector, Vector, + BinaryVector, BooleanVector, DateTimeVector, DateVector, IntervalDayTimeVector, + IntervalMonthDayNanoVector, IntervalYearMonthVector, ListVector, PrimitiveVector, StringVector, + TimeMicrosecondVector, TimeMillisecondVector, TimeNanosecondVector, TimeSecondVector, + TimestampMicrosecondVector, TimestampMillisecondVector, TimestampNanosecondVector, + TimestampSecondVector, Vector, }; use crate::with_match_primitive_type_id; @@ -94,6 +97,17 @@ fn equal(lhs: &dyn Vector, rhs: &dyn Vector) -> bool { is_vector_eq!(TimestampNanosecondVector, lhs, rhs) } }, + Interval(v) => match v.unit() { + IntervalUnit::YearMonth => { + is_vector_eq!(IntervalYearMonthVector, lhs, rhs) + } + IntervalUnit::DayTime => { + is_vector_eq!(IntervalDayTimeVector, lhs, rhs) + } + IntervalUnit::MonthDayNano => { + is_vector_eq!(IntervalMonthDayNanoVector, lhs, rhs) + } + }, List(_) => is_vector_eq!(ListVector, lhs, rhs), UInt8(_) | UInt16(_) | UInt32(_) | UInt64(_) | Int8(_) | Int16(_) | Int32(_) | Int64(_) | Float32(_) | Float64(_) | Dictionary(_) => { @@ -198,6 +212,16 @@ mod tests { assert_vector_ref_eq(Arc::new(TimeMillisecondVector::from_values([100, 120]))); assert_vector_ref_eq(Arc::new(TimeMicrosecondVector::from_values([100, 120]))); assert_vector_ref_eq(Arc::new(TimeNanosecondVector::from_values([100, 120]))); + + assert_vector_ref_eq(Arc::new(IntervalYearMonthVector::from_values([ + 1000, 2000, 3000, 4000, + ]))); + assert_vector_ref_eq(Arc::new(IntervalDayTimeVector::from_values([ + 1000, 2000, 3000, 4000, + ]))); + assert_vector_ref_eq(Arc::new(IntervalMonthDayNanoVector::from_values([ + 1000, 2000, 3000, 4000, + ]))); } #[test] @@ -250,5 +274,18 @@ mod tests { Arc::new(TimeMicrosecondVector::from_values([100, 120])), Arc::new(TimeMicrosecondVector::from_values([200, 220])), ); + + assert_vector_ref_ne( + Arc::new(IntervalDayTimeVector::from_values([1000, 2000])), + Arc::new(IntervalDayTimeVector::from_values([2100, 1200])), + ); + assert_vector_ref_ne( + Arc::new(IntervalMonthDayNanoVector::from_values([1000, 2000])), + Arc::new(IntervalMonthDayNanoVector::from_values([2100, 1200])), + ); + assert_vector_ref_ne( + Arc::new(IntervalYearMonthVector::from_values([1000, 2000])), + Arc::new(IntervalYearMonthVector::from_values([2100, 1200])), + ); } } diff --git a/src/datatypes/src/vectors/helper.rs b/src/datatypes/src/vectors/helper.rs index 8a7c45cf28..44a9a2cc7d 100644 --- a/src/datatypes/src/vectors/helper.rs +++ b/src/datatypes/src/vectors/helper.rs @@ -21,17 +21,19 @@ use arrow::array::{Array, ArrayRef, StringArray}; use arrow::compute; use arrow::compute::kernels::comparison; use arrow::datatypes::{DataType as ArrowDataType, TimeUnit}; +use arrow_schema::IntervalUnit; use datafusion_common::ScalarValue; use snafu::{OptionExt, ResultExt}; +use super::{IntervalDayTimeVector, IntervalYearMonthVector}; use crate::data_type::ConcreteDataType; use crate::error::{self, Result}; use crate::scalars::{Scalar, ScalarVectorBuilder}; use crate::value::{ListValue, ListValueRef}; use crate::vectors::{ BinaryVector, BooleanVector, ConstantVector, DateTimeVector, DateVector, Float32Vector, - Float64Vector, Int16Vector, Int32Vector, Int64Vector, Int8Vector, ListVector, - ListVectorBuilder, MutableVector, NullVector, StringVector, TimeMicrosecondVector, + Float64Vector, Int16Vector, Int32Vector, Int64Vector, Int8Vector, IntervalMonthDayNanoVector, + ListVector, ListVectorBuilder, MutableVector, NullVector, StringVector, TimeMicrosecondVector, TimeMillisecondVector, TimeNanosecondVector, TimeSecondVector, TimestampMicrosecondVector, TimestampMillisecondVector, TimestampNanosecondVector, TimestampSecondVector, UInt16Vector, UInt32Vector, UInt64Vector, UInt8Vector, Vector, VectorRef, @@ -207,10 +209,16 @@ impl Helper { ScalarValue::Time64Nanosecond(v) => { ConstantVector::new(Arc::new(TimeNanosecondVector::from(vec![v])), length) } + ScalarValue::IntervalYearMonth(v) => { + ConstantVector::new(Arc::new(IntervalYearMonthVector::from(vec![v])), length) + } + ScalarValue::IntervalDayTime(v) => { + ConstantVector::new(Arc::new(IntervalDayTimeVector::from(vec![v])), length) + } + ScalarValue::IntervalMonthDayNano(v) => { + ConstantVector::new(Arc::new(IntervalMonthDayNanoVector::from(vec![v])), length) + } ScalarValue::Decimal128(_, _, _) - | ScalarValue::IntervalYearMonth(_) - | ScalarValue::IntervalDayTime(_) - | ScalarValue::IntervalMonthDayNano(_) | ScalarValue::Struct(_, _) | ScalarValue::Dictionary(_, _) => { return error::ConversionSnafu { @@ -286,9 +294,19 @@ impl Helper { } _ => unimplemented!("Arrow array datatype: {:?}", array.as_ref().data_type()), }, + ArrowDataType::Interval(unit) => match unit { + IntervalUnit::YearMonth => Arc::new( + IntervalYearMonthVector::try_from_arrow_interval_array(array)?, + ), + IntervalUnit::DayTime => { + Arc::new(IntervalDayTimeVector::try_from_arrow_interval_array(array)?) + } + IntervalUnit::MonthDayNano => Arc::new( + IntervalMonthDayNanoVector::try_from_arrow_interval_array(array)?, + ), + }, ArrowDataType::Float16 | ArrowDataType::Duration(_) - | ArrowDataType::Interval(_) | ArrowDataType::LargeList(_) | ArrowDataType::FixedSizeList(_, _) | ArrowDataType::Struct(_) @@ -330,7 +348,7 @@ mod tests { }; use arrow::datatypes::{Field, Int32Type}; use common_time::time::Time; - use common_time::{Date, DateTime}; + use common_time::{Date, DateTime, Interval}; use super::*; use crate::value::Value; @@ -478,4 +496,20 @@ mod tests { assert_eq!(Value::Time(Time::new_second(42)), vector.get(i)); } } + + #[test] + fn test_try_from_scalar_interval_value() { + let vector = + Helper::try_from_scalar_value(ScalarValue::IntervalMonthDayNano(Some(2000)), 3) + .unwrap(); + + assert_eq!( + ConcreteDataType::interval_month_day_nano_datatype(), + vector.data_type() + ); + assert_eq!(3, vector.len()); + for i in 0..vector.len() { + assert_eq!(Value::Interval(Interval::from_i128(2000)), vector.get(i)); + } + } } diff --git a/src/datatypes/src/vectors/interval.rs b/src/datatypes/src/vectors/interval.rs new file mode 100644 index 0000000000..cf619cfc92 --- /dev/null +++ b/src/datatypes/src/vectors/interval.rs @@ -0,0 +1,25 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{PrimitiveVector, PrimitiveVectorBuilder}; +use crate::types::{IntervalDayTimeType, IntervalMonthDayNanoType, IntervalYearMonthType}; + +pub type IntervalYearMonthVector = PrimitiveVector; +pub type IntervalYearMonthVectorBuilder = PrimitiveVectorBuilder; + +pub type IntervalMonthDayNanoVector = PrimitiveVector; +pub type IntervalMonthDayNanoVectorBuilder = PrimitiveVectorBuilder; + +pub type IntervalDayTimeVector = PrimitiveVector; +pub type IntervalDayTimeVectorBuilder = PrimitiveVectorBuilder; diff --git a/src/datatypes/src/vectors/primitive.rs b/src/datatypes/src/vectors/primitive.rs index 3b61a8da09..506a6bf997 100644 --- a/src/datatypes/src/vectors/primitive.rs +++ b/src/datatypes/src/vectors/primitive.rs @@ -23,6 +23,7 @@ use arrow::array::{ TimestampMicrosecondArray, TimestampMillisecondArray, TimestampNanosecondArray, TimestampSecondArray, }; +use arrow_array::{IntervalDayTimeArray, IntervalMonthDayNanoArray, IntervalYearMonthArray}; use arrow_schema::DataType; use serde_json::Value as JsonValue; use snafu::OptionExt; @@ -159,6 +160,40 @@ impl PrimitiveVector { Ok(Self::new(concrete_array)) } + pub fn try_from_arrow_interval_array(array: impl AsRef) -> Result { + let array = array.as_ref(); + let array_data = match array.data_type() { + DataType::Interval(unit) => match unit { + arrow_schema::IntervalUnit::YearMonth => array + .as_any() + .downcast_ref::() + .unwrap() + .to_data(), + arrow_schema::IntervalUnit::DayTime => array + .as_any() + .downcast_ref::() + .unwrap() + .to_data(), + arrow_schema::IntervalUnit::MonthDayNano => array + .as_any() + .downcast_ref::() + .unwrap() + .to_data(), + }, + arrow_type => { + return CastTypeSnafu { + msg: format!( + "Failed to cast arrow array {:?} to interval vector", + arrow_type, + ), + } + .fail()?; + } + }; + let concrete_array = PrimitiveArray::::from(array_data); + Ok(Self::new(concrete_array)) + } + pub fn from_slice>(slice: P) -> Self { let iter = slice.as_ref().iter().copied(); Self { @@ -495,6 +530,7 @@ mod tests { Time64NanosecondArray, }; use arrow::datatypes::DataType as ArrowDataType; + use arrow_array::{IntervalDayTimeArray, IntervalYearMonthArray}; use serde_json; use super::*; @@ -502,9 +538,9 @@ mod tests { use crate::serialize::Serializable; use crate::types::Int64Type; use crate::vectors::{ - TimeMicrosecondVector, TimeMillisecondVector, TimeNanosecondVector, TimeSecondVector, - TimestampMicrosecondVector, TimestampMillisecondVector, TimestampNanosecondVector, - TimestampSecondVector, + IntervalDayTimeVector, IntervalYearMonthVector, TimeMicrosecondVector, + TimeMillisecondVector, TimeNanosecondVector, TimeSecondVector, TimestampMicrosecondVector, + TimestampMillisecondVector, TimestampNanosecondVector, TimestampSecondVector, }; fn check_vec(v: Int32Vector) { @@ -711,4 +747,28 @@ mod tests { let array: ArrayRef = Arc::new(Int32Array::from(vec![1i32, 2, 3])); assert!(TimestampSecondVector::try_from_arrow_timestamp_array(array).is_err()); } + + #[test] + fn test_try_from_arrow_interval_array() { + let array: ArrayRef = Arc::new(IntervalYearMonthArray::from(vec![1000, 2000, 3000])); + let vector = IntervalYearMonthVector::try_from_arrow_interval_array(array).unwrap(); + assert_eq!( + IntervalYearMonthVector::from_values(vec![1000, 2000, 3000]), + vector + ); + + let array: ArrayRef = Arc::new(IntervalDayTimeArray::from(vec![1000, 2000, 3000])); + let vector = IntervalDayTimeVector::try_from_arrow_interval_array(array).unwrap(); + assert_eq!( + IntervalDayTimeVector::from_values(vec![1000, 2000, 3000]), + vector + ); + + let array: ArrayRef = Arc::new(IntervalYearMonthArray::from(vec![1000, 2000, 3000])); + let vector = IntervalYearMonthVector::try_from_arrow_interval_array(array).unwrap(); + assert_eq!( + IntervalYearMonthVector::from_values(vec![1000, 2000, 3000]), + vector + ); + } } diff --git a/src/servers/src/mysql/writer.rs b/src/servers/src/mysql/writer.rs index abacc17fb0..83a802dcd3 100644 --- a/src/servers/src/mysql/writer.rs +++ b/src/servers/src/mysql/writer.rs @@ -182,6 +182,7 @@ impl<'a, W: AsyncWrite + Unpin> MysqlResultWriter<'a, W> { Value::DateTime(v) => row_writer.write_col(v.to_chrono_datetime())?, Value::Timestamp(v) => row_writer .write_col(v.to_timezone_aware_string(query_context.time_zone()))?, + Value::Interval(v) => row_writer.write_col(v.to_iso8601_string())?, Value::List(_) => { return Err(Error::Internal { err_msg: format!( @@ -241,6 +242,7 @@ pub(crate) fn create_mysql_column( ConcreteDataType::Time(_) => Ok(ColumnType::MYSQL_TYPE_TIME), ConcreteDataType::Date(_) => Ok(ColumnType::MYSQL_TYPE_DATE), ConcreteDataType::DateTime(_) => Ok(ColumnType::MYSQL_TYPE_DATETIME), + ConcreteDataType::Interval(_) => Ok(ColumnType::MYSQL_TYPE_VARCHAR), _ => error::InternalSnafu { err_msg: format!("not implemented for column datatype {:?}", data_type), } diff --git a/src/servers/src/postgres/types.rs b/src/servers/src/postgres/types.rs index 28322b74b9..89ecec05b4 100644 --- a/src/servers/src/postgres/types.rs +++ b/src/servers/src/postgres/types.rs @@ -97,12 +97,14 @@ pub(super) fn encode_value(value: &Value, builder: &mut DataRowEncoder) -> PgWir }))) } } - Value::List(_) => Err(PgWireError::ApiError(Box::new(Error::Internal { - err_msg: format!( - "cannot write value {:?} in postgres protocol: unimplemented", - &value - ), - }))), + Value::Interval(_) | Value::List(_) => { + Err(PgWireError::ApiError(Box::new(Error::Internal { + err_msg: format!( + "cannot write value {:?} in postgres protocol: unimplemented", + &value + ), + }))) + } } } @@ -122,6 +124,7 @@ pub(super) fn type_gt_to_pg(origin: &ConcreteDataType) -> Result { &ConcreteDataType::DateTime(_) => Ok(Type::TIMESTAMP), &ConcreteDataType::Timestamp(_) => Ok(Type::TIMESTAMP), &ConcreteDataType::Time(_) => Ok(Type::TIME), + &ConcreteDataType::Interval(_) => Ok(Type::INTERVAL), &ConcreteDataType::List(_) | &ConcreteDataType::Dictionary(_) => error::InternalSnafu { err_msg: format!("not implemented for column datatype {origin:?}"), } diff --git a/src/sql/src/statements.rs b/src/sql/src/statements.rs index e9baa3c8ad..955c6cdedb 100644 --- a/src/sql/src/statements.rs +++ b/src/sql/src/statements.rs @@ -369,6 +369,7 @@ pub fn sql_data_type_to_concrete_data_type(data_type: &SqlDataType) -> Result Ok(ConcreteDataType::interval_month_day_nano_datatype()), _ => error::SqlTypeNotSupportedSnafu { t: data_type.clone(), } @@ -404,6 +405,7 @@ pub fn concrete_data_type_to_sql_data_type(data_type: &ConcreteDataType) -> Resu ConcreteDataType::Null(_) | ConcreteDataType::List(_) | ConcreteDataType::Dictionary(_) => { unreachable!() } + ConcreteDataType::Interval(_) => Ok(SqlDataType::Interval), } } @@ -499,7 +501,11 @@ mod tests { check_type( SqlDataType::Datetime(None), ConcreteDataType::datetime_datatype(), - ) + ); + check_type( + SqlDataType::Interval, + ConcreteDataType::interval_month_day_nano_datatype(), + ); } #[test] diff --git a/tests/cases/standalone/common/select/dummy.result b/tests/cases/standalone/common/select/dummy.result index 0887fdd7f8..fe0ef9853c 100644 --- a/tests/cases/standalone/common/select/dummy.result +++ b/tests/cases/standalone/common/select/dummy.result @@ -103,3 +103,27 @@ DROP TABLE test_unixtime; Affected Rows: 1 +select INTERVAL '1 year 2 months 3 days 4 hours 5 minutes 6 seconds 100 microseconds'; + ++---------------------------------------------------------+ +| IntervalMonthDayNano("1109194275255040973236744059552") | ++---------------------------------------------------------+ +| 0 years 14 mons 3 days 4 hours 5 mins 6.000100000 secs | ++---------------------------------------------------------+ + +select INTERVAL '1 year 2 months 3 days 4 hours' + INTERVAL '1 year'; + ++------------------------------------------------------------------------------------------------------------------+ +| IntervalMonthDayNano("1109194275255040972930743959552") + IntervalMonthDayNano("950737950171172051122527404032") | ++------------------------------------------------------------------------------------------------------------------+ +| 0 years 26 mons 3 days 4 hours 0 mins 0.000000000 secs | ++------------------------------------------------------------------------------------------------------------------+ + +select INTERVAL '1 year 2 months 3 days 4 hours' - INTERVAL '1 year'; + ++------------------------------------------------------------------------------------------------------------------+ +| IntervalMonthDayNano("1109194275255040972930743959552") - IntervalMonthDayNano("950737950171172051122527404032") | ++------------------------------------------------------------------------------------------------------------------+ +| 0 years 2 mons 3 days 4 hours 0 mins 0.000000000 secs | ++------------------------------------------------------------------------------------------------------------------+ + diff --git a/tests/cases/standalone/common/select/dummy.sql b/tests/cases/standalone/common/select/dummy.sql index 5bf16da864..e4c10cdd74 100644 --- a/tests/cases/standalone/common/select/dummy.sql +++ b/tests/cases/standalone/common/select/dummy.sql @@ -29,3 +29,9 @@ select b from test_unixtime; select TO_UNIXTIME(b) from test_unixtime; DROP TABLE test_unixtime; + +select INTERVAL '1 year 2 months 3 days 4 hours 5 minutes 6 seconds 100 microseconds'; + +select INTERVAL '1 year 2 months 3 days 4 hours' + INTERVAL '1 year'; + +select INTERVAL '1 year 2 months 3 days 4 hours' - INTERVAL '1 year';