From 38e65c0db59c5a2cf0d0c28956e276466ae32c4a Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Wed, 6 Dec 2023 15:36:12 +0800 Subject: [PATCH 01/11] feat: adds date_add and date_sub function --- src/common/function/src/helper.rs | 15 + src/common/function/src/lib.rs | 2 + src/common/function/src/scalars.rs | 1 + src/common/function/src/scalars/date.rs | 30 ++ .../function/src/scalars/date/date_add.rs | 328 +++++++++++++++++ .../function/src/scalars/date/date_sub.rs | 338 ++++++++++++++++++ .../function/src/scalars/function_registry.rs | 2 + src/common/query/src/error.rs | 4 + src/common/time/src/interval.rs | 31 +- src/common/time/src/timestamp.rs | 63 ++++ src/datatypes/src/value.rs | 8 + 11 files changed, 821 insertions(+), 1 deletion(-) create mode 100644 src/common/function/src/helper.rs create mode 100644 src/common/function/src/scalars/date.rs create mode 100644 src/common/function/src/scalars/date/date_add.rs create mode 100644 src/common/function/src/scalars/date/date_sub.rs diff --git a/src/common/function/src/helper.rs b/src/common/function/src/helper.rs new file mode 100644 index 000000000000..9085d8d25744 --- /dev/null +++ b/src/common/function/src/helper.rs @@ -0,0 +1,15 @@ +use common_query::prelude::{Signature, TypeSignature, Volatility}; +use datatypes::prelude::ConcreteDataType; + +/// Create a function signature with oneof signatures of interleaving two arguments. +pub fn one_of_sigs2(args1: Vec, args2: Vec) -> Signature { + let mut sigs = Vec::with_capacity(args1.len() + args2.len()); + + for arg1 in &args1 { + for arg2 in &args2 { + sigs.push(TypeSignature::Exact(vec![arg1.clone(), arg2.clone()])); + } + } + + Signature::one_of(sigs, Volatility::Immutable) +} diff --git a/src/common/function/src/lib.rs b/src/common/function/src/lib.rs index a971d5cceb0d..5d3ab6d42069 100644 --- a/src/common/function/src/lib.rs +++ b/src/common/function/src/lib.rs @@ -13,3 +13,5 @@ // limitations under the License. pub mod scalars; + +pub mod helper; diff --git a/src/common/function/src/scalars.rs b/src/common/function/src/scalars.rs index e6f7ed766c95..3aa19632e7df 100644 --- a/src/common/function/src/scalars.rs +++ b/src/common/function/src/scalars.rs @@ -13,6 +13,7 @@ // limitations under the License. pub mod aggregate; +mod date; pub mod expression; pub mod function; pub mod function_registry; diff --git a/src/common/function/src/scalars/date.rs b/src/common/function/src/scalars/date.rs new file mode 100644 index 000000000000..2f901162c5b6 --- /dev/null +++ b/src/common/function/src/scalars/date.rs @@ -0,0 +1,30 @@ +// 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::sync::Arc; +mod date_add; +mod date_sub; + +use date_add::DateAddFunction; +use date_sub::DateSubFunction; + +use crate::scalars::function_registry::FunctionRegistry; + +pub(crate) struct DateFunction; + +impl DateFunction { + pub fn register(registry: &FunctionRegistry) { + registry.register(Arc::new(DateAddFunction)); + registry.register(Arc::new(DateSubFunction)); + } +} diff --git a/src/common/function/src/scalars/date/date_add.rs b/src/common/function/src/scalars/date/date_add.rs new file mode 100644 index 000000000000..da3b83d2762e --- /dev/null +++ b/src/common/function/src/scalars/date/date_add.rs @@ -0,0 +1,328 @@ +// 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::fmt; + +use common_query::error::{ + ExecuteSnafu, InvalidFuncArgsSnafu, Result, UnsupportedInputDataTypeSnafu, +}; +use common_query::prelude::Signature; +use common_time::{Date, DateTime}; +use datatypes::data_type::DataType; +use datatypes::prelude::ConcreteDataType; +use datatypes::value::ValueRef; +use datatypes::vectors::VectorRef; +use snafu::ensure; + +use crate::helper; +use crate::scalars::function::{Function, FunctionContext}; + +/// A function adds a interval value to Timestamp, Date or DateTime, and return the result. +#[derive(Clone, Debug, Default)] +pub struct DateAddFunction; + +const NAME: &str = "date_add"; + +impl Function for DateAddFunction { + fn name(&self) -> &str { + NAME + } + + fn return_type(&self, input_types: &[ConcreteDataType]) -> Result { + Ok(input_types[0].clone()) + } + + fn signature(&self) -> Signature { + helper::one_of_sigs2( + vec![ + ConcreteDataType::timestamp_second_datatype(), + ConcreteDataType::timestamp_millisecond_datatype(), + ConcreteDataType::timestamp_microsecond_datatype(), + ConcreteDataType::timestamp_nanosecond_datatype(), + ConcreteDataType::date_datatype(), + ConcreteDataType::datetime_datatype(), + ], + vec![ + ConcreteDataType::interval_month_day_nano_datatype(), + ConcreteDataType::interval_year_month_datatype(), + ConcreteDataType::interval_day_time_datatype(), + ], + ) + } + + fn eval(&self, _func_ctx: FunctionContext, columns: &[VectorRef]) -> Result { + ensure!( + columns.len() == 2, + InvalidFuncArgsSnafu { + err_msg: format!( + "The length of the args is not correct, expect 2, have: {}", + columns.len() + ), + } + ); + + let left = &columns[0]; + let right = &columns[1]; + + let size = left.len(); + let left_datatype = columns[0].data_type(); + match left_datatype { + ConcreteDataType::Timestamp(_) => { + let mut result = left_datatype.create_mutable_vector(size); + for i in 0..size { + let ts = left.get(i).as_timestamp(); + let interval = right.get(i).as_interval(); + let ts = ts + .map(|ts| { + if let Some(interval) = interval { + let duration = interval + .to_duration() + .map_err(|e| { + ExecuteSnafu { + msg: format!("{e}"), + } + .build() + })? + .into(); + ts.add_duration(duration).map_err(|e| { + ExecuteSnafu { + msg: format!("{e}"), + } + .build() + }) + } else { + Ok(ts) + } + }) + .transpose()?; + result.push_value_ref(ValueRef::from(ts)); + } + + Ok(result.to_vector()) + } + ConcreteDataType::Date(_) => { + let mut result = left_datatype.create_mutable_vector(size); + for i in 0..size { + let date = left.get(i).as_date(); + let interval = right.get(i).as_interval(); + let date = date + .map(|date| { + if let Some(interval) = interval { + let secs = interval.to_nanosecond() as i64 + / common_time::interval::NANOS_PER_SEC; + let secs: i32 = secs.try_into().map_err(|e| { + ExecuteSnafu { + msg: format!("{e}"), + } + .build() + })?; + + Ok(Date::new(date.val() + secs)) + } else { + Ok(date) + } + }) + .transpose()?; + result.push_value_ref(ValueRef::from(date)); + } + + Ok(result.to_vector()) + } + ConcreteDataType::DateTime(_) => { + let mut result = left_datatype.create_mutable_vector(size); + for i in 0..size { + let datetime = left.get(i).as_datetime(); + let interval = right.get(i).as_interval(); + let datetime = datetime.map(|datetime| { + if let Some(interval) = interval { + let millis = interval.to_nanosecond() as i64 + / common_time::interval::NANOS_PER_MILLI; + DateTime::new(datetime.val() + millis) + } else { + datetime + } + }); + result.push_value_ref(ValueRef::from(datetime)); + } + + Ok(result.to_vector()) + } + _ => UnsupportedInputDataTypeSnafu { + function: NAME, + datatypes: columns.iter().map(|c| c.data_type()).collect::>(), + } + .fail(), + } + } +} + +impl fmt::Display for DateAddFunction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DATE_ADD") + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use common_query::prelude::{TypeSignature, Volatility}; + use datatypes::prelude::ConcreteDataType; + use datatypes::value::Value; + use datatypes::vectors::{ + DateTimeVector, DateVector, IntervalDayTimeVector, IntervalYearMonthVector, + TimestampSecondVector, + }; + + use super::{DateAddFunction, *}; + use crate::scalars::Function; + + #[test] + fn test_date_add_misc() { + let f = DateAddFunction; + assert_eq!("date_add", f.name()); + assert_eq!( + ConcreteDataType::timestamp_microsecond_datatype(), + f.return_type(&[ConcreteDataType::timestamp_microsecond_datatype()]) + .unwrap() + ); + assert_eq!( + ConcreteDataType::timestamp_second_datatype(), + f.return_type(&[ConcreteDataType::timestamp_second_datatype()]) + .unwrap() + ); + assert_eq!( + ConcreteDataType::date_datatype(), + f.return_type(&[ConcreteDataType::date_datatype()]).unwrap() + ); + assert_eq!( + ConcreteDataType::datetime_datatype(), + f.return_type(&[ConcreteDataType::datetime_datatype()]) + .unwrap() + ); + assert!(matches!(f.signature(), + Signature { + type_signature: TypeSignature::OneOf(sigs), + volatility: Volatility::Immutable + } if sigs.len() == 18)); + } + + #[test] + fn test_timestamp_date_add() { + let f = DateAddFunction; + + let times = vec![Some(123), None, Some(42), None]; + // Intervals in milliseconds + let intervals = vec![1000, 2000, 3000, 1000]; + let results = [Some(124), None, Some(45), None]; + + let time_vector = TimestampSecondVector::from(times.clone()); + let interval_vector = IntervalDayTimeVector::from_vec(intervals); + let args: Vec = vec![Arc::new(time_vector), Arc::new(interval_vector)]; + let vector = f.eval(FunctionContext::default(), &args).unwrap(); + + assert_eq!(4, vector.len()); + for (i, _t) in times.iter().enumerate() { + let v = vector.get(i); + let result = results.get(i).unwrap(); + + if result.is_none() { + assert_eq!(Value::Null, v); + continue; + } + match v { + Value::Timestamp(ts) => { + assert_eq!(ts.value(), result.unwrap()); + } + _ => unreachable!(), + } + } + } + + #[test] + fn test_date_date_add() { + let f = DateAddFunction; + let seconds_in_month = 3600 * 24 * 30; + + let dates = vec![Some(123), None, Some(42), None]; + // Intervals in months + let intervals = vec![1, 2, 3, 1]; + let results = [ + Some(seconds_in_month + 123), + None, + Some(3 * seconds_in_month + 42), + None, + ]; + + let date_vector = DateVector::from(dates.clone()); + let interval_vector = IntervalYearMonthVector::from_vec(intervals); + let args: Vec = vec![Arc::new(date_vector), Arc::new(interval_vector)]; + let vector = f.eval(FunctionContext::default(), &args).unwrap(); + + assert_eq!(4, vector.len()); + for (i, _t) in dates.iter().enumerate() { + let v = vector.get(i); + let result = results.get(i).unwrap(); + + if result.is_none() { + assert_eq!(Value::Null, v); + continue; + } + match v { + Value::Date(date) => { + assert_eq!(date.val(), result.unwrap()); + } + _ => unreachable!(), + } + } + } + + #[test] + fn test_datetime_date_add() { + let f = DateAddFunction; + let millis_in_month = 3600 * 24 * 30 * 1000; + + let dates = vec![Some(123), None, Some(42), None]; + // Intervals in months + let intervals = vec![1, 2, 3, 1]; + let results = [ + Some(millis_in_month + 123), + None, + Some(3 * millis_in_month + 42), + None, + ]; + + let date_vector = DateTimeVector::from(dates.clone()); + let interval_vector = IntervalYearMonthVector::from_vec(intervals); + let args: Vec = vec![Arc::new(date_vector), Arc::new(interval_vector)]; + let vector = f.eval(FunctionContext::default(), &args).unwrap(); + + assert_eq!(4, vector.len()); + for (i, _t) in dates.iter().enumerate() { + let v = vector.get(i); + let result = results.get(i).unwrap(); + + if result.is_none() { + assert_eq!(Value::Null, v); + continue; + } + match v { + Value::DateTime(date) => { + assert_eq!(date.val(), result.unwrap()); + } + _ => unreachable!(), + } + } + } +} diff --git a/src/common/function/src/scalars/date/date_sub.rs b/src/common/function/src/scalars/date/date_sub.rs new file mode 100644 index 000000000000..e4564914114e --- /dev/null +++ b/src/common/function/src/scalars/date/date_sub.rs @@ -0,0 +1,338 @@ +// 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::fmt; + +use common_query::error::{ + ExecuteSnafu, InvalidFuncArgsSnafu, Result, UnsupportedInputDataTypeSnafu, +}; +use common_query::prelude::Signature; +use common_time::{Date, DateTime}; +use datatypes::data_type::DataType; +use datatypes::prelude::ConcreteDataType; +use datatypes::value::ValueRef; +use datatypes::vectors::VectorRef; +use snafu::ensure; + +use crate::helper; +use crate::scalars::function::{Function, FunctionContext}; + +/// A function subtracts a interval value to Timestamp, Date or DateTime, and return the result. +#[derive(Clone, Debug, Default)] +pub struct DateSubFunction; + +const NAME: &str = "date_sub"; + +impl Function for DateSubFunction { + fn name(&self) -> &str { + NAME + } + + fn return_type(&self, input_types: &[ConcreteDataType]) -> Result { + Ok(input_types[0].clone()) + } + + fn signature(&self) -> Signature { + helper::one_of_sigs2( + vec![ + ConcreteDataType::timestamp_second_datatype(), + ConcreteDataType::timestamp_millisecond_datatype(), + ConcreteDataType::timestamp_microsecond_datatype(), + ConcreteDataType::timestamp_nanosecond_datatype(), + ConcreteDataType::date_datatype(), + ConcreteDataType::datetime_datatype(), + ], + vec![ + ConcreteDataType::interval_month_day_nano_datatype(), + ConcreteDataType::interval_year_month_datatype(), + ConcreteDataType::interval_day_time_datatype(), + ], + ) + } + + fn eval(&self, _func_ctx: FunctionContext, columns: &[VectorRef]) -> Result { + ensure!( + columns.len() == 2, + InvalidFuncArgsSnafu { + err_msg: format!( + "The length of the args is not correct, expect 2, have: {}", + columns.len() + ), + } + ); + + let left = &columns[0]; + let right = &columns[1]; + + let size = left.len(); + let left_datatype = columns[0].data_type(); + match left_datatype { + ConcreteDataType::Timestamp(_) => { + let mut result = left_datatype.create_mutable_vector(size); + for i in 0..size { + let ts = left.get(i).as_timestamp(); + let interval = right.get(i).as_interval(); + let ts = ts + .map(|ts| { + if let Some(interval) = interval { + let duration = interval + .to_duration() + .map_err(|e| { + ExecuteSnafu { + msg: format!("{e}"), + } + .build() + })? + .into(); + ts.sub_duration(duration).map_err(|e| { + ExecuteSnafu { + msg: format!("{e}"), + } + .build() + }) + } else { + Ok(ts) + } + }) + .transpose()?; + result.push_value_ref(ValueRef::from(ts)); + } + + Ok(result.to_vector()) + } + ConcreteDataType::Date(_) => { + let mut result = left_datatype.create_mutable_vector(size); + for i in 0..size { + let date = left.get(i).as_date(); + let interval = right.get(i).as_interval(); + let date = date + .map(|date| { + if let Some(interval) = interval { + let secs = interval.to_nanosecond() as i64 + / common_time::interval::NANOS_PER_SEC; + let secs: i32 = secs.try_into().map_err(|e| { + ExecuteSnafu { + msg: format!("{e}"), + } + .build() + })?; + + Ok(Date::new(date.val() - secs)) + } else { + Ok(date) + } + }) + .transpose()?; + result.push_value_ref(ValueRef::from(date)); + } + + Ok(result.to_vector()) + } + ConcreteDataType::DateTime(_) => { + let mut result = left_datatype.create_mutable_vector(size); + for i in 0..size { + let datetime = left.get(i).as_datetime(); + let interval = right.get(i).as_interval(); + let datetime = datetime.map(|datetime| { + if let Some(interval) = interval { + let millis = interval.to_nanosecond() as i64 + / common_time::interval::NANOS_PER_MILLI; + DateTime::new(datetime.val() - millis) + } else { + datetime + } + }); + result.push_value_ref(ValueRef::from(datetime)); + } + + Ok(result.to_vector()) + } + _ => UnsupportedInputDataTypeSnafu { + function: NAME, + datatypes: columns.iter().map(|c| c.data_type()).collect::>(), + } + .fail(), + } + } +} + +impl fmt::Display for DateSubFunction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DATE_SUB") + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use common_query::prelude::{TypeSignature, Volatility}; + use datatypes::prelude::ConcreteDataType; + use datatypes::value::Value; + use datatypes::vectors::{ + DateTimeVector, DateVector, IntervalDayTimeVector, IntervalYearMonthVector, + TimestampSecondVector, + }; + + use super::{DateSubFunction, *}; + use crate::scalars::Function; + + #[test] + fn test_date_sub_misc() { + let f = DateSubFunction; + assert_eq!("date_sub", f.name()); + assert_eq!( + ConcreteDataType::timestamp_microsecond_datatype(), + f.return_type(&[ConcreteDataType::timestamp_microsecond_datatype()]) + .unwrap() + ); + assert_eq!( + ConcreteDataType::timestamp_second_datatype(), + f.return_type(&[ConcreteDataType::timestamp_second_datatype()]) + .unwrap() + ); + assert_eq!( + ConcreteDataType::date_datatype(), + f.return_type(&[ConcreteDataType::date_datatype()]).unwrap() + ); + assert_eq!( + ConcreteDataType::datetime_datatype(), + f.return_type(&[ConcreteDataType::datetime_datatype()]) + .unwrap() + ); + assert!(matches!(f.signature(), + Signature { + type_signature: TypeSignature::OneOf(sigs), + volatility: Volatility::Immutable + } if sigs.len() == 18)); + } + + #[test] + fn test_timestamp_date_sub() { + let f = DateSubFunction; + + let times = vec![Some(123), None, Some(42), None]; + // Intervals in milliseconds + let intervals = vec![1000, 2000, 3000, 1000]; + let results = [Some(122), None, Some(39), None]; + + let time_vector = TimestampSecondVector::from(times.clone()); + let interval_vector = IntervalDayTimeVector::from_vec(intervals); + let args: Vec = vec![Arc::new(time_vector), Arc::new(interval_vector)]; + let vector = f.eval(FunctionContext::default(), &args).unwrap(); + + assert_eq!(4, vector.len()); + for (i, _t) in times.iter().enumerate() { + let v = vector.get(i); + let result = results.get(i).unwrap(); + + if result.is_none() { + assert_eq!(Value::Null, v); + continue; + } + match v { + Value::Timestamp(ts) => { + assert_eq!(ts.value(), result.unwrap()); + } + _ => unreachable!(), + } + } + } + + #[test] + fn test_date_date_sub() { + let f = DateSubFunction; + let seconds_in_month = 3600 * 24 * 30; + + let dates = vec![ + Some(123 * seconds_in_month), + None, + Some(42 * seconds_in_month), + None, + ]; + // Intervals in months + let intervals = vec![1, 2, 3, 1]; + let results = [ + Some(122 * seconds_in_month), + None, + Some(39 * seconds_in_month), + None, + ]; + + let date_vector = DateVector::from(dates.clone()); + let interval_vector = IntervalYearMonthVector::from_vec(intervals); + let args: Vec = vec![Arc::new(date_vector), Arc::new(interval_vector)]; + let vector = f.eval(FunctionContext::default(), &args).unwrap(); + + assert_eq!(4, vector.len()); + for (i, _t) in dates.iter().enumerate() { + let v = vector.get(i); + let result = results.get(i).unwrap(); + + if result.is_none() { + assert_eq!(Value::Null, v); + continue; + } + match v { + Value::Date(date) => { + assert_eq!(date.val(), result.unwrap()); + } + _ => unreachable!(), + } + } + } + + #[test] + fn test_datetime_date_sub() { + let f = DateSubFunction; + let millis_in_month = 3600 * 24 * 30 * 1000; + + let dates = vec![ + Some(123 * millis_in_month), + None, + Some(42 * millis_in_month), + None, + ]; + // Intervals in months + let intervals = vec![1, 2, 3, 1]; + let results = [ + Some(122 * millis_in_month), + None, + Some(39 * millis_in_month), + None, + ]; + + let date_vector = DateTimeVector::from(dates.clone()); + let interval_vector = IntervalYearMonthVector::from_vec(intervals); + let args: Vec = vec![Arc::new(date_vector), Arc::new(interval_vector)]; + let vector = f.eval(FunctionContext::default(), &args).unwrap(); + + assert_eq!(4, vector.len()); + for (i, _t) in dates.iter().enumerate() { + let v = vector.get(i); + let result = results.get(i).unwrap(); + + if result.is_none() { + assert_eq!(Value::Null, v); + continue; + } + match v { + Value::DateTime(date) => { + assert_eq!(date.val(), result.unwrap()); + } + _ => unreachable!(), + } + } + } +} diff --git a/src/common/function/src/scalars/function_registry.rs b/src/common/function/src/scalars/function_registry.rs index 1fd536982e35..5d6751df7503 100644 --- a/src/common/function/src/scalars/function_registry.rs +++ b/src/common/function/src/scalars/function_registry.rs @@ -19,6 +19,7 @@ use std::sync::{Arc, RwLock}; use once_cell::sync::Lazy; use crate::scalars::aggregate::{AggregateFunctionMetaRef, AggregateFunctions}; +use crate::scalars::date::DateFunction; use crate::scalars::function::FunctionRef; use crate::scalars::math::MathFunction; use crate::scalars::numpy::NumpyFunction; @@ -75,6 +76,7 @@ pub static FUNCTION_REGISTRY: Lazy> = Lazy::new(|| { MathFunction::register(&function_registry); NumpyFunction::register(&function_registry); TimestampFunction::register(&function_registry); + DateFunction::register(&function_registry); AggregateFunctions::register(&function_registry); diff --git a/src/common/query/src/error.rs b/src/common/query/src/error.rs index 25f9d3c71523..b649e4724e0a 100644 --- a/src/common/query/src/error.rs +++ b/src/common/query/src/error.rs @@ -165,6 +165,9 @@ pub enum Error { #[snafu(display("Invalid function args: {}", err_msg))] InvalidFuncArgs { err_msg: String, location: Location }, + + #[snafu(display("Execute function error: {msg}"))] + Execute { msg: String, location: Location }, } pub type Result = std::result::Result; @@ -183,6 +186,7 @@ impl ErrorExt for Error { | Error::BadAccumulatorImpl { .. } | Error::ToScalarValue { .. } | Error::GetScalarVector { .. } + | Error::Execute { .. } | Error::ArrowCompute { .. } => StatusCode::EngineExecuteQuery, Error::InvalidInputType { source, .. } diff --git a/src/common/time/src/interval.rs b/src/common/time/src/interval.rs index a7c184c0ec47..04bf71d9918a 100644 --- a/src/common/time/src/interval.rs +++ b/src/common/time/src/interval.rs @@ -20,6 +20,10 @@ use std::hash::{Hash, Hasher}; use arrow::datatypes::IntervalUnit as ArrowIntervalUnit; use serde::{Deserialize, Serialize}; use serde_json::Value; +use snafu::ResultExt; + +use crate::duration::Duration; +use crate::error::{Result, TimestampOverflowSnafu}; #[derive( Debug, Default, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, @@ -63,7 +67,7 @@ impl From for IntervalUnit { /// month-day-nano, which will be stored in the following format. /// Interval data format: /// | months | days | nsecs | -/// | 4bytes | 4bytes | 8bytes | +/// | 4bytes | 4bytes | 8bytes | #[derive(Debug, Clone, Default, Copy, Serialize, Deserialize)] pub struct Interval { months: i32, @@ -114,6 +118,15 @@ impl Interval { } } + /// Return a `[Duration]` from the interval + pub fn to_duration(&self) -> Result { + Ok(Duration::new_nanosecond( + self.to_nanosecond() + .try_into() + .context(TimestampOverflowSnafu)?, + )) + } + /// Return a tuple(months, days, nanoseconds) from the interval. pub fn to_month_day_nano(&self) -> (i32, i32, i64) { (self.months, self.days, self.nsecs) @@ -558,6 +571,7 @@ mod tests { use std::collections::HashMap; use super::*; + use crate::timestamp::TimeUnit; #[test] fn test_from_year_month() { @@ -572,6 +586,21 @@ mod tests { assert_eq!(interval.nsecs, 2_000_000); } + #[test] + fn test_to_duration() { + let interval = Interval::from_day_time(1, 2); + + let duration = interval.to_duration().unwrap(); + assert_eq!(86400002000000, duration.value()); + assert_eq!(TimeUnit::Nanosecond, duration.unit()); + + let interval = Interval::from_year_month(12); + + let duration = interval.to_duration().unwrap(); + assert_eq!(31104000000000000, duration.value()); + assert_eq!(TimeUnit::Nanosecond, duration.unit()); + } + #[test] fn test_interval_is_positive() { let interval = Interval::from_year_month(1); diff --git a/src/common/time/src/timestamp.rs b/src/common/time/src/timestamp.rs index 898e1c7b39c4..44c675644243 100644 --- a/src/common/time/src/timestamp.rs +++ b/src/common/time/src/timestamp.rs @@ -104,6 +104,42 @@ impl Timestamp { }) } + /// Adds a duration to timestamp. + /// # Note + /// The result time unit remains unchanged even if `duration` has a different unit with `self`. + /// For example, a timestamp with value 1 and time unit second, subtracted by 1 millisecond + /// and the result is still 1 second. + pub fn add_duration(&self, duration: Duration) -> error::Result { + let duration: i64 = match self.unit { + TimeUnit::Second => { + i64::try_from(duration.as_secs()).context(TimestampOverflowSnafu)? + } + TimeUnit::Millisecond => { + i64::try_from(duration.as_millis()).context(TimestampOverflowSnafu)? + } + TimeUnit::Microsecond => { + i64::try_from(duration.as_micros()).context(TimestampOverflowSnafu)? + } + TimeUnit::Nanosecond => { + i64::try_from(duration.as_nanos()).context(TimestampOverflowSnafu)? + } + }; + + let value = self + .value + .checked_add(duration) + .with_context(|| ArithmeticOverflowSnafu { + msg: format!( + "Try to add timestamp: {:?} with duration: {:?}", + self, duration + ), + })?; + Ok(Timestamp { + value, + unit: self.unit, + }) + } + /// Subtracts current timestamp with another timestamp, yielding a duration. pub fn sub(&self, rhs: &Self) -> Option { let lhs = self.to_chrono_datetime()?; @@ -1006,6 +1042,33 @@ mod tests { assert_eq!(TimeUnit::Second, res.unit); } + #[test] + fn test_timestamp_add() { + let res = Timestamp::new(1, TimeUnit::Second) + .add_duration(Duration::from_secs(1)) + .unwrap(); + assert_eq!(2, res.value); + assert_eq!(TimeUnit::Second, res.unit); + + let res = Timestamp::new(0, TimeUnit::Second) + .add_duration(Duration::from_secs(1)) + .unwrap(); + assert_eq!(1, res.value); + assert_eq!(TimeUnit::Second, res.unit); + + let res = Timestamp::new(1, TimeUnit::Second) + .add_duration(Duration::from_millis(1)) + .unwrap(); + assert_eq!(1, res.value); + assert_eq!(TimeUnit::Second, res.unit); + + let res = Timestamp::new(100, TimeUnit::Second) + .add_duration(Duration::from_millis(1000)) + .unwrap(); + assert_eq!(101, res.value); + assert_eq!(TimeUnit::Second, res.unit); + } + #[test] fn test_parse_in_time_zone() { std::env::set_var("TZ", "Asia/Shanghai"); diff --git a/src/datatypes/src/value.rs b/src/datatypes/src/value.rs index 23135a6e44a8..c198fde9a7b1 100644 --- a/src/datatypes/src/value.rs +++ b/src/datatypes/src/value.rs @@ -210,6 +210,14 @@ impl Value { } } + /// Cast Value to Interval. Return None if value is not a valid interval data type. + pub fn as_interval(&self) -> Option { + match self { + Value::Interval(i) => Some(*i), + _ => None, + } + } + /// Cast Value to Date. Return None if value is not a valid date data type. pub fn as_date(&self) -> Option { match self { From d32e7d245334d3fc4eb204c41fb0f3113e7a9ee0 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Wed, 6 Dec 2023 16:01:35 +0800 Subject: [PATCH 02/11] test: add date function --- tests/cases/standalone/common/function/date.sql | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/cases/standalone/common/function/date.sql diff --git a/tests/cases/standalone/common/function/date.sql b/tests/cases/standalone/common/function/date.sql new file mode 100644 index 000000000000..c84aa9963d43 --- /dev/null +++ b/tests/cases/standalone/common/function/date.sql @@ -0,0 +1,15 @@ +SELECT date_add('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, INTERVAL '5 day'); + +SELECT date_add('2023-12-06'::DATE, INTERVAL '3 month 5 day'); + +CREATE TABLE dates(d DATE, ts timestamp time index); + +INSERT INTO dates VALUES ('1992-01-01'::DATE, 1); + +INSERT INTO dates VALUES ('1993-12-30'::DATE, 2); + +INSERT INTO dates VALUES ('2023-12-06'::DATE, 3); + +SELECT date_add(d, INTERVAL '1 year 2 month 3 day') from dates; + +DROP TABLE dates; From e2473d67306b9d27acf162c5143cb8d5106137c0 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Wed, 6 Dec 2023 16:19:12 +0800 Subject: [PATCH 03/11] fix: adds interval to date returns wrong result --- src/common/function/src/helper.rs | 14 ++ .../function/src/scalars/date/date_add.rs | 20 +-- .../function/src/scalars/date/date_sub.rs | 28 +-- .../standalone/common/function/date.result | 164 ++++++++++++++++++ .../cases/standalone/common/function/date.sql | 27 +++ 5 files changed, 229 insertions(+), 24 deletions(-) create mode 100644 tests/cases/standalone/common/function/date.result diff --git a/src/common/function/src/helper.rs b/src/common/function/src/helper.rs index 9085d8d25744..c707c725ba48 100644 --- a/src/common/function/src/helper.rs +++ b/src/common/function/src/helper.rs @@ -1,3 +1,17 @@ +// 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_query::prelude::{Signature, TypeSignature, Volatility}; use datatypes::prelude::ConcreteDataType; diff --git a/src/common/function/src/scalars/date/date_add.rs b/src/common/function/src/scalars/date/date_add.rs index da3b83d2762e..71ab3f6ad95c 100644 --- a/src/common/function/src/scalars/date/date_add.rs +++ b/src/common/function/src/scalars/date/date_add.rs @@ -119,16 +119,16 @@ impl Function for DateAddFunction { let date = date .map(|date| { if let Some(interval) = interval { - let secs = interval.to_nanosecond() as i64 - / common_time::interval::NANOS_PER_SEC; - let secs: i32 = secs.try_into().map_err(|e| { + let days = interval.to_nanosecond() as i64 + / common_time::interval::NANOS_PER_DAY; + let days: i32 = days.try_into().map_err(|e| { ExecuteSnafu { msg: format!("{e}"), } .build() })?; - Ok(Date::new(date.val() + secs)) + Ok(Date::new(date.val() + days)) } else { Ok(date) } @@ -253,15 +253,15 @@ mod tests { #[test] fn test_date_date_add() { let f = DateAddFunction; - let seconds_in_month = 3600 * 24 * 30; + let days_per_month = 30; let dates = vec![Some(123), None, Some(42), None]; // Intervals in months let intervals = vec![1, 2, 3, 1]; let results = [ - Some(seconds_in_month + 123), + Some(days_per_month + 123), None, - Some(3 * seconds_in_month + 42), + Some(3 * days_per_month + 42), None, ]; @@ -291,15 +291,15 @@ mod tests { #[test] fn test_datetime_date_add() { let f = DateAddFunction; - let millis_in_month = 3600 * 24 * 30 * 1000; + let millis_per_month = 3600 * 24 * 30 * 1000; let dates = vec![Some(123), None, Some(42), None]; // Intervals in months let intervals = vec![1, 2, 3, 1]; let results = [ - Some(millis_in_month + 123), + Some(millis_per_month + 123), None, - Some(3 * millis_in_month + 42), + Some(3 * millis_per_month + 42), None, ]; diff --git a/src/common/function/src/scalars/date/date_sub.rs b/src/common/function/src/scalars/date/date_sub.rs index e4564914114e..5cacdecdc4b1 100644 --- a/src/common/function/src/scalars/date/date_sub.rs +++ b/src/common/function/src/scalars/date/date_sub.rs @@ -119,16 +119,16 @@ impl Function for DateSubFunction { let date = date .map(|date| { if let Some(interval) = interval { - let secs = interval.to_nanosecond() as i64 - / common_time::interval::NANOS_PER_SEC; - let secs: i32 = secs.try_into().map_err(|e| { + let days = interval.to_nanosecond() as i64 + / common_time::interval::NANOS_PER_DAY; + let days: i32 = days.try_into().map_err(|e| { ExecuteSnafu { msg: format!("{e}"), } .build() })?; - Ok(Date::new(date.val() - secs)) + Ok(Date::new(date.val() - days)) } else { Ok(date) } @@ -253,20 +253,20 @@ mod tests { #[test] fn test_date_date_sub() { let f = DateSubFunction; - let seconds_in_month = 3600 * 24 * 30; + let days_per_month = 30; let dates = vec![ - Some(123 * seconds_in_month), + Some(123 * days_per_month), None, - Some(42 * seconds_in_month), + Some(42 * days_per_month), None, ]; // Intervals in months let intervals = vec![1, 2, 3, 1]; let results = [ - Some(122 * seconds_in_month), + Some(122 * days_per_month), None, - Some(39 * seconds_in_month), + Some(39 * days_per_month), None, ]; @@ -296,20 +296,20 @@ mod tests { #[test] fn test_datetime_date_sub() { let f = DateSubFunction; - let millis_in_month = 3600 * 24 * 30 * 1000; + let millis_per_month = 3600 * 24 * 30 * 1000; let dates = vec![ - Some(123 * millis_in_month), + Some(123 * millis_per_month), None, - Some(42 * millis_in_month), + Some(42 * millis_per_month), None, ]; // Intervals in months let intervals = vec![1, 2, 3, 1]; let results = [ - Some(122 * millis_in_month), + Some(122 * millis_per_month), None, - Some(39 * millis_in_month), + Some(39 * millis_per_month), None, ]; diff --git a/tests/cases/standalone/common/function/date.result b/tests/cases/standalone/common/function/date.result new file mode 100644 index 000000000000..c133e8d36a1c --- /dev/null +++ b/tests/cases/standalone/common/function/date.result @@ -0,0 +1,164 @@ +SELECT date_add('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, INTERVAL '5 day'); + ++-------------------------------------------------------------------------------------------+ +| date_add(Utf8("2023-12-06 07:39:46.222726"),IntervalMonthDayNano("92233720368547758080")) | ++-------------------------------------------------------------------------------------------+ +| 2023-12-11T07:39:46.222 | ++-------------------------------------------------------------------------------------------+ + +SELECT date_add('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, '5 day'); + ++------------------------------------------------------------+ +| date_add(Utf8("2023-12-06 07:39:46.222726"),Utf8("5 day")) | ++------------------------------------------------------------+ +| 2023-12-11T07:39:46.222 | ++------------------------------------------------------------+ + +SELECT date_add('2023-12-06'::DATE, INTERVAL '3 month 5 day'); + ++-------------------------------------------------------------------------------------+ +| date_add(Utf8("2023-12-06"),IntervalMonthDayNano("237684487635026733149179609088")) | ++-------------------------------------------------------------------------------------+ +| 2024-03-10 | ++-------------------------------------------------------------------------------------+ + +SELECT date_add('2023-12-06'::DATE, '3 month 5 day'); + ++----------------------------------------------------+ +| date_add(Utf8("2023-12-06"),Utf8("3 month 5 day")) | ++----------------------------------------------------+ +| 2024-03-10T00:00:00 | ++----------------------------------------------------+ + +SELECT date_sub('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, INTERVAL '5 day'); + ++-------------------------------------------------------------------------------------------+ +| date_sub(Utf8("2023-12-06 07:39:46.222726"),IntervalMonthDayNano("92233720368547758080")) | ++-------------------------------------------------------------------------------------------+ +| 2023-12-01T07:39:46.222 | ++-------------------------------------------------------------------------------------------+ + +SELECT date_sub('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, '5 day'); + ++------------------------------------------------------------+ +| date_sub(Utf8("2023-12-06 07:39:46.222726"),Utf8("5 day")) | ++------------------------------------------------------------+ +| 2023-12-01T07:39:46.222 | ++------------------------------------------------------------+ + +SELECT date_sub('2023-12-06'::DATE, INTERVAL '3 month 5 day'); + ++-------------------------------------------------------------------------------------+ +| date_sub(Utf8("2023-12-06"),IntervalMonthDayNano("237684487635026733149179609088")) | ++-------------------------------------------------------------------------------------+ +| 2023-09-02 | ++-------------------------------------------------------------------------------------+ + +SELECT date_sub('2023-12-06'::DATE, '3 month 5 day'); + ++----------------------------------------------------+ +| date_sub(Utf8("2023-12-06"),Utf8("3 month 5 day")) | ++----------------------------------------------------+ +| 2023-09-02T00:00:00 | ++----------------------------------------------------+ + +CREATE TABLE dates(d DATE, ts timestamp time index); + +Affected Rows: 0 + +INSERT INTO dates VALUES ('1992-01-01'::DATE, 1); + +Affected Rows: 1 + +INSERT INTO dates VALUES ('1993-12-30'::DATE, 2); + +Affected Rows: 1 + +INSERT INTO dates VALUES ('2023-12-06'::DATE, 3); + +Affected Rows: 1 + +SELECT date_add(d, INTERVAL '1 year 2 month 3 day') from dates; + ++---------------------------------------------------------------------------+ +| date_add(dates.d,IntervalMonthDayNano("1109194275255040958530743959552")) | ++---------------------------------------------------------------------------+ +| 1993-02-27 | +| 1995-02-26 | +| 2025-02-01 | ++---------------------------------------------------------------------------+ + +SELECT date_add(d, '1 year 2 month 3 day') from dates; + ++------------------------------------------------+ +| date_add(dates.d,Utf8("1 year 2 month 3 day")) | ++------------------------------------------------+ +| 1993-02-27T00:00:00 | +| 1995-02-26T00:00:00 | +| 2025-02-01T00:00:00 | ++------------------------------------------------+ + +SELECT date_add(ts, INTERVAL '1 year 2 month 3 day') from dates; + ++----------------------------------------------------------------------------+ +| date_add(dates.ts,IntervalMonthDayNano("1109194275255040958530743959552")) | ++----------------------------------------------------------------------------+ +| 1971-02-28T00:00:00.001 | +| 1971-02-28T00:00:00.002 | +| 1971-02-28T00:00:00.003 | ++----------------------------------------------------------------------------+ + +SELECT date_add(ts, '1 year 2 month 3 day') from dates; + ++-------------------------------------------------+ +| date_add(dates.ts,Utf8("1 year 2 month 3 day")) | ++-------------------------------------------------+ +| 1971-02-28T00:00:00.001 | +| 1971-02-28T00:00:00.002 | +| 1971-02-28T00:00:00.003 | ++-------------------------------------------------+ + +SELECT date_sub(d, INTERVAL '1 year 2 month 3 day') from dates; + ++---------------------------------------------------------------------------+ +| date_sub(dates.d,IntervalMonthDayNano("1109194275255040958530743959552")) | ++---------------------------------------------------------------------------+ +| 1990-11-04 | +| 1992-11-02 | +| 2022-10-09 | ++---------------------------------------------------------------------------+ + +SELECT date_sub(d, '1 year 2 month 3 day') from dates; + ++------------------------------------------------+ +| date_sub(dates.d,Utf8("1 year 2 month 3 day")) | ++------------------------------------------------+ +| 1990-11-04T00:00:00 | +| 1992-11-02T00:00:00 | +| 2022-10-09T00:00:00 | ++------------------------------------------------+ + +SELECT date_sub(ts, INTERVAL '1 year 2 month 3 day') from dates; + ++----------------------------------------------------------------------------+ +| date_sub(dates.ts,IntervalMonthDayNano("1109194275255040958530743959552")) | ++----------------------------------------------------------------------------+ +| 1968-11-04T00:00:00.001 | +| 1968-11-04T00:00:00.002 | +| 1968-11-04T00:00:00.003 | ++----------------------------------------------------------------------------+ + +SELECT date_sub(ts, '1 year 2 month 3 day') from dates; + ++-------------------------------------------------+ +| date_sub(dates.ts,Utf8("1 year 2 month 3 day")) | ++-------------------------------------------------+ +| 1968-11-04T00:00:00.001 | +| 1968-11-04T00:00:00.002 | +| 1968-11-04T00:00:00.003 | ++-------------------------------------------------+ + +DROP TABLE dates; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/function/date.sql b/tests/cases/standalone/common/function/date.sql index c84aa9963d43..497b880c2cbd 100644 --- a/tests/cases/standalone/common/function/date.sql +++ b/tests/cases/standalone/common/function/date.sql @@ -1,7 +1,20 @@ SELECT date_add('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, INTERVAL '5 day'); +SELECT date_add('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, '5 day'); + SELECT date_add('2023-12-06'::DATE, INTERVAL '3 month 5 day'); +SELECT date_add('2023-12-06'::DATE, '3 month 5 day'); + +SELECT date_sub('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, INTERVAL '5 day'); + +SELECT date_sub('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, '5 day'); + +SELECT date_sub('2023-12-06'::DATE, INTERVAL '3 month 5 day'); + +SELECT date_sub('2023-12-06'::DATE, '3 month 5 day'); + + CREATE TABLE dates(d DATE, ts timestamp time index); INSERT INTO dates VALUES ('1992-01-01'::DATE, 1); @@ -12,4 +25,18 @@ INSERT INTO dates VALUES ('2023-12-06'::DATE, 3); SELECT date_add(d, INTERVAL '1 year 2 month 3 day') from dates; +SELECT date_add(d, '1 year 2 month 3 day') from dates; + +SELECT date_add(ts, INTERVAL '1 year 2 month 3 day') from dates; + +SELECT date_add(ts, '1 year 2 month 3 day') from dates; + +SELECT date_sub(d, INTERVAL '1 year 2 month 3 day') from dates; + +SELECT date_sub(d, '1 year 2 month 3 day') from dates; + +SELECT date_sub(ts, INTERVAL '1 year 2 month 3 day') from dates; + +SELECT date_sub(ts, '1 year 2 month 3 day') from dates; + DROP TABLE dates; From f3fcfb811ad975ce1794ef3282ab94bf6f7a9166 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Wed, 6 Dec 2023 16:22:10 +0800 Subject: [PATCH 04/11] fix: header --- src/common/function/src/scalars/date.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/function/src/scalars/date.rs b/src/common/function/src/scalars/date.rs index 2f901162c5b6..0e16019d527b 100644 --- a/src/common/function/src/scalars/date.rs +++ b/src/common/function/src/scalars/date.rs @@ -11,6 +11,7 @@ // 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::sync::Arc; mod date_add; mod date_sub; From 57826ad73c04c5e70355e58529d7e7c1fffa0d5d Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Wed, 6 Dec 2023 16:23:51 +0800 Subject: [PATCH 05/11] fix: typo --- src/common/function/src/scalars/date/date_add.rs | 2 +- src/common/function/src/scalars/date/date_sub.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/function/src/scalars/date/date_add.rs b/src/common/function/src/scalars/date/date_add.rs index 71ab3f6ad95c..aff9006c70d0 100644 --- a/src/common/function/src/scalars/date/date_add.rs +++ b/src/common/function/src/scalars/date/date_add.rs @@ -28,7 +28,7 @@ use snafu::ensure; use crate::helper; use crate::scalars::function::{Function, FunctionContext}; -/// A function adds a interval value to Timestamp, Date or DateTime, and return the result. +/// A function adds an interval value to Timestamp, Date or DateTime, and return the result. #[derive(Clone, Debug, Default)] pub struct DateAddFunction; diff --git a/src/common/function/src/scalars/date/date_sub.rs b/src/common/function/src/scalars/date/date_sub.rs index 5cacdecdc4b1..0426b1a2536c 100644 --- a/src/common/function/src/scalars/date/date_sub.rs +++ b/src/common/function/src/scalars/date/date_sub.rs @@ -28,7 +28,7 @@ use snafu::ensure; use crate::helper; use crate::scalars::function::{Function, FunctionContext}; -/// A function subtracts a interval value to Timestamp, Date or DateTime, and return the result. +/// A function subtracts an interval value to Timestamp, Date or DateTime, and return the result. #[derive(Clone, Debug, Default)] pub struct DateSubFunction; From 0eab2e949918c9fea0dd98eb95d2f2efc7ce1ef4 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Wed, 6 Dec 2023 17:23:53 +0800 Subject: [PATCH 06/11] fix: timestamp resolution --- .../standalone/common/function/date.result | 48 +++++++++---------- .../cases/standalone/common/function/date.sql | 8 ++-- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/cases/standalone/common/function/date.result b/tests/cases/standalone/common/function/date.result index c133e8d36a1c..75d36f31a2f0 100644 --- a/tests/cases/standalone/common/function/date.result +++ b/tests/cases/standalone/common/function/date.result @@ -1,18 +1,18 @@ -SELECT date_add('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, INTERVAL '5 day'); +SELECT date_add('2023-12-06 07:39:46.222'::TIMESTAMP_MS, INTERVAL '5 day'); -+-------------------------------------------------------------------------------------------+ -| date_add(Utf8("2023-12-06 07:39:46.222726"),IntervalMonthDayNano("92233720368547758080")) | -+-------------------------------------------------------------------------------------------+ -| 2023-12-11T07:39:46.222 | -+-------------------------------------------------------------------------------------------+ ++----------------------------------------------------------------------------------------+ +| date_add(Utf8("2023-12-06 07:39:46.222"),IntervalMonthDayNano("92233720368547758080")) | ++----------------------------------------------------------------------------------------+ +| 2023-12-11T07:39:46.222 | ++----------------------------------------------------------------------------------------+ -SELECT date_add('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, '5 day'); +SELECT date_add('2023-12-06 07:39:46.222'::TIMESTAMP_MS, '5 day'); -+------------------------------------------------------------+ -| date_add(Utf8("2023-12-06 07:39:46.222726"),Utf8("5 day")) | -+------------------------------------------------------------+ -| 2023-12-11T07:39:46.222 | -+------------------------------------------------------------+ ++---------------------------------------------------------+ +| date_add(Utf8("2023-12-06 07:39:46.222"),Utf8("5 day")) | ++---------------------------------------------------------+ +| 2023-12-11T07:39:46.222 | ++---------------------------------------------------------+ SELECT date_add('2023-12-06'::DATE, INTERVAL '3 month 5 day'); @@ -30,21 +30,21 @@ SELECT date_add('2023-12-06'::DATE, '3 month 5 day'); | 2024-03-10T00:00:00 | +----------------------------------------------------+ -SELECT date_sub('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, INTERVAL '5 day'); +SELECT date_sub('2023-12-06 07:39:46.222'::TIMESTAMP_MS, INTERVAL '5 day'); -+-------------------------------------------------------------------------------------------+ -| date_sub(Utf8("2023-12-06 07:39:46.222726"),IntervalMonthDayNano("92233720368547758080")) | -+-------------------------------------------------------------------------------------------+ -| 2023-12-01T07:39:46.222 | -+-------------------------------------------------------------------------------------------+ ++----------------------------------------------------------------------------------------+ +| date_sub(Utf8("2023-12-06 07:39:46.222"),IntervalMonthDayNano("92233720368547758080")) | ++----------------------------------------------------------------------------------------+ +| 2023-12-01T07:39:46.222 | ++----------------------------------------------------------------------------------------+ -SELECT date_sub('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, '5 day'); +SELECT date_sub('2023-12-06 07:39:46.222'::TIMESTAMP_MS, '5 day'); -+------------------------------------------------------------+ -| date_sub(Utf8("2023-12-06 07:39:46.222726"),Utf8("5 day")) | -+------------------------------------------------------------+ -| 2023-12-01T07:39:46.222 | -+------------------------------------------------------------+ ++---------------------------------------------------------+ +| date_sub(Utf8("2023-12-06 07:39:46.222"),Utf8("5 day")) | ++---------------------------------------------------------+ +| 2023-12-01T07:39:46.222 | ++---------------------------------------------------------+ SELECT date_sub('2023-12-06'::DATE, INTERVAL '3 month 5 day'); diff --git a/tests/cases/standalone/common/function/date.sql b/tests/cases/standalone/common/function/date.sql index 497b880c2cbd..a5f962749975 100644 --- a/tests/cases/standalone/common/function/date.sql +++ b/tests/cases/standalone/common/function/date.sql @@ -1,14 +1,14 @@ -SELECT date_add('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, INTERVAL '5 day'); +SELECT date_add('2023-12-06 07:39:46.222'::TIMESTAMP_MS, INTERVAL '5 day'); -SELECT date_add('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, '5 day'); +SELECT date_add('2023-12-06 07:39:46.222'::TIMESTAMP_MS, '5 day'); SELECT date_add('2023-12-06'::DATE, INTERVAL '3 month 5 day'); SELECT date_add('2023-12-06'::DATE, '3 month 5 day'); -SELECT date_sub('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, INTERVAL '5 day'); +SELECT date_sub('2023-12-06 07:39:46.222'::TIMESTAMP_MS, INTERVAL '5 day'); -SELECT date_sub('2023-12-06 07:39:46.222726'::TIMESTAMP_MS, '5 day'); +SELECT date_sub('2023-12-06 07:39:46.222'::TIMESTAMP_MS, '5 day'); SELECT date_sub('2023-12-06'::DATE, INTERVAL '3 month 5 day'); From b38a7cad93282c07bd350393db428477726d6be7 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Wed, 6 Dec 2023 17:38:15 +0800 Subject: [PATCH 07/11] fix: capacity --- src/common/function/src/helper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/function/src/helper.rs b/src/common/function/src/helper.rs index c707c725ba48..6f549d6619e3 100644 --- a/src/common/function/src/helper.rs +++ b/src/common/function/src/helper.rs @@ -17,7 +17,7 @@ use datatypes::prelude::ConcreteDataType; /// Create a function signature with oneof signatures of interleaving two arguments. pub fn one_of_sigs2(args1: Vec, args2: Vec) -> Signature { - let mut sigs = Vec::with_capacity(args1.len() + args2.len()); + let mut sigs = Vec::with_capacity(args1.len() * args2.len()); for arg1 in &args1 { for arg2 in &args2 { From f82ad29d566a169d09b9ea1e44dcc3f75c21643a Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Wed, 6 Dec 2023 18:01:46 +0800 Subject: [PATCH 08/11] chore: apply suggestion --- src/common/function/src/scalars/date/date_add.rs | 4 ++-- src/common/function/src/scalars/date/date_sub.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/function/src/scalars/date/date_add.rs b/src/common/function/src/scalars/date/date_add.rs index aff9006c70d0..75fb2902765d 100644 --- a/src/common/function/src/scalars/date/date_add.rs +++ b/src/common/function/src/scalars/date/date_add.rs @@ -119,8 +119,8 @@ impl Function for DateAddFunction { let date = date .map(|date| { if let Some(interval) = interval { - let days = interval.to_nanosecond() as i64 - / common_time::interval::NANOS_PER_DAY; + let days = interval.to_nanosecond() + / common_time::interval::NANOS_PER_DAY as i128; let days: i32 = days.try_into().map_err(|e| { ExecuteSnafu { msg: format!("{e}"), diff --git a/src/common/function/src/scalars/date/date_sub.rs b/src/common/function/src/scalars/date/date_sub.rs index 0426b1a2536c..b278f9522328 100644 --- a/src/common/function/src/scalars/date/date_sub.rs +++ b/src/common/function/src/scalars/date/date_sub.rs @@ -119,8 +119,8 @@ impl Function for DateSubFunction { let date = date .map(|date| { if let Some(interval) = interval { - let days = interval.to_nanosecond() as i64 - / common_time::interval::NANOS_PER_DAY; + let days = interval.to_nanosecond() + / common_time::interval::NANOS_PER_DAY as i128; let days: i32 = days.try_into().map_err(|e| { ExecuteSnafu { msg: format!("{e}"), From 6c2b6754e7f76eaaafec0982597ce2f9f251bc98 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Wed, 6 Dec 2023 22:53:06 +0800 Subject: [PATCH 09/11] fix: wrong behavior when adding intervals to timestamp, date and datetime --- .../function/src/scalars/date/date_add.rs | 97 +++++-------------- .../function/src/scalars/date/date_sub.rs | 96 +++++------------- src/common/time/src/date.rs | 31 +++++- src/common/time/src/datetime.rs | 30 +++++- src/common/time/src/interval.rs | 1 - src/common/time/src/timestamp.rs | 38 +++++++- .../standalone/common/function/date.result | 56 +++++------ 7 files changed, 171 insertions(+), 178 deletions(-) diff --git a/src/common/function/src/scalars/date/date_add.rs b/src/common/function/src/scalars/date/date_add.rs index 75fb2902765d..e299f7947297 100644 --- a/src/common/function/src/scalars/date/date_add.rs +++ b/src/common/function/src/scalars/date/date_add.rs @@ -14,11 +14,8 @@ use std::fmt; -use common_query::error::{ - ExecuteSnafu, InvalidFuncArgsSnafu, Result, UnsupportedInputDataTypeSnafu, -}; +use common_query::error::{InvalidFuncArgsSnafu, Result, UnsupportedInputDataTypeSnafu}; use common_query::prelude::Signature; -use common_time::{Date, DateTime}; use datatypes::data_type::DataType; use datatypes::prelude::ConcreteDataType; use datatypes::value::ValueRef; @@ -46,12 +43,12 @@ impl Function for DateAddFunction { fn signature(&self) -> Signature { helper::one_of_sigs2( vec![ + ConcreteDataType::date_datatype(), + ConcreteDataType::datetime_datatype(), ConcreteDataType::timestamp_second_datatype(), ConcreteDataType::timestamp_millisecond_datatype(), ConcreteDataType::timestamp_microsecond_datatype(), ConcreteDataType::timestamp_nanosecond_datatype(), - ConcreteDataType::date_datatype(), - ConcreteDataType::datetime_datatype(), ], vec![ ConcreteDataType::interval_month_day_nano_datatype(), @@ -83,30 +80,13 @@ impl Function for DateAddFunction { for i in 0..size { let ts = left.get(i).as_timestamp(); let interval = right.get(i).as_interval(); - let ts = ts - .map(|ts| { - if let Some(interval) = interval { - let duration = interval - .to_duration() - .map_err(|e| { - ExecuteSnafu { - msg: format!("{e}"), - } - .build() - })? - .into(); - ts.add_duration(duration).map_err(|e| { - ExecuteSnafu { - msg: format!("{e}"), - } - .build() - }) - } else { - Ok(ts) - } - }) - .transpose()?; - result.push_value_ref(ValueRef::from(ts)); + + let new_ts = match (ts, interval) { + (Some(ts), Some(interval)) => ts.add_interval(interval), + _ => ts, + }; + + result.push_value_ref(ValueRef::from(new_ts)); } Ok(result.to_vector()) @@ -116,25 +96,12 @@ impl Function for DateAddFunction { for i in 0..size { let date = left.get(i).as_date(); let interval = right.get(i).as_interval(); - let date = date - .map(|date| { - if let Some(interval) = interval { - let days = interval.to_nanosecond() - / common_time::interval::NANOS_PER_DAY as i128; - let days: i32 = days.try_into().map_err(|e| { - ExecuteSnafu { - msg: format!("{e}"), - } - .build() - })?; - - Ok(Date::new(date.val() + days)) - } else { - Ok(date) - } - }) - .transpose()?; - result.push_value_ref(ValueRef::from(date)); + let new_date = match (date, interval) { + (Some(date), Some(interval)) => date.add_interval(interval), + _ => date, + }; + + result.push_value_ref(ValueRef::from(new_date)); } Ok(result.to_vector()) @@ -144,16 +111,12 @@ impl Function for DateAddFunction { for i in 0..size { let datetime = left.get(i).as_datetime(); let interval = right.get(i).as_interval(); - let datetime = datetime.map(|datetime| { - if let Some(interval) = interval { - let millis = interval.to_nanosecond() as i64 - / common_time::interval::NANOS_PER_MILLI; - DateTime::new(datetime.val() + millis) - } else { - datetime - } - }); - result.push_value_ref(ValueRef::from(datetime)); + let new_datetime = match (datetime, interval) { + (Some(datetime), Some(interval)) => datetime.add_interval(interval), + _ => datetime, + }; + + result.push_value_ref(ValueRef::from(new_datetime)); } Ok(result.to_vector()) @@ -253,17 +216,11 @@ mod tests { #[test] fn test_date_date_add() { let f = DateAddFunction; - let days_per_month = 30; let dates = vec![Some(123), None, Some(42), None]; // Intervals in months let intervals = vec![1, 2, 3, 1]; - let results = [ - Some(days_per_month + 123), - None, - Some(3 * days_per_month + 42), - None, - ]; + let results = [Some(154), None, Some(131), None]; let date_vector = DateVector::from(dates.clone()); let interval_vector = IntervalYearMonthVector::from_vec(intervals); @@ -291,17 +248,11 @@ mod tests { #[test] fn test_datetime_date_add() { let f = DateAddFunction; - let millis_per_month = 3600 * 24 * 30 * 1000; let dates = vec![Some(123), None, Some(42), None]; // Intervals in months let intervals = vec![1, 2, 3, 1]; - let results = [ - Some(millis_per_month + 123), - None, - Some(3 * millis_per_month + 42), - None, - ]; + let results = [Some(2678400123), None, Some(7776000042), None]; let date_vector = DateTimeVector::from(dates.clone()); let interval_vector = IntervalYearMonthVector::from_vec(intervals); diff --git a/src/common/function/src/scalars/date/date_sub.rs b/src/common/function/src/scalars/date/date_sub.rs index b278f9522328..15660850f558 100644 --- a/src/common/function/src/scalars/date/date_sub.rs +++ b/src/common/function/src/scalars/date/date_sub.rs @@ -14,11 +14,8 @@ use std::fmt; -use common_query::error::{ - ExecuteSnafu, InvalidFuncArgsSnafu, Result, UnsupportedInputDataTypeSnafu, -}; +use common_query::error::{InvalidFuncArgsSnafu, Result, UnsupportedInputDataTypeSnafu}; use common_query::prelude::Signature; -use common_time::{Date, DateTime}; use datatypes::data_type::DataType; use datatypes::prelude::ConcreteDataType; use datatypes::value::ValueRef; @@ -46,12 +43,12 @@ impl Function for DateSubFunction { fn signature(&self) -> Signature { helper::one_of_sigs2( vec![ + ConcreteDataType::date_datatype(), + ConcreteDataType::datetime_datatype(), ConcreteDataType::timestamp_second_datatype(), ConcreteDataType::timestamp_millisecond_datatype(), ConcreteDataType::timestamp_microsecond_datatype(), ConcreteDataType::timestamp_nanosecond_datatype(), - ConcreteDataType::date_datatype(), - ConcreteDataType::datetime_datatype(), ], vec![ ConcreteDataType::interval_month_day_nano_datatype(), @@ -77,36 +74,20 @@ impl Function for DateSubFunction { let size = left.len(); let left_datatype = columns[0].data_type(); + match left_datatype { ConcreteDataType::Timestamp(_) => { let mut result = left_datatype.create_mutable_vector(size); for i in 0..size { let ts = left.get(i).as_timestamp(); let interval = right.get(i).as_interval(); - let ts = ts - .map(|ts| { - if let Some(interval) = interval { - let duration = interval - .to_duration() - .map_err(|e| { - ExecuteSnafu { - msg: format!("{e}"), - } - .build() - })? - .into(); - ts.sub_duration(duration).map_err(|e| { - ExecuteSnafu { - msg: format!("{e}"), - } - .build() - }) - } else { - Ok(ts) - } - }) - .transpose()?; - result.push_value_ref(ValueRef::from(ts)); + + let new_ts = match (ts, interval) { + (Some(ts), Some(interval)) => ts.sub_interval(interval), + _ => ts, + }; + + result.push_value_ref(ValueRef::from(new_ts)); } Ok(result.to_vector()) @@ -116,25 +97,12 @@ impl Function for DateSubFunction { for i in 0..size { let date = left.get(i).as_date(); let interval = right.get(i).as_interval(); - let date = date - .map(|date| { - if let Some(interval) = interval { - let days = interval.to_nanosecond() - / common_time::interval::NANOS_PER_DAY as i128; - let days: i32 = days.try_into().map_err(|e| { - ExecuteSnafu { - msg: format!("{e}"), - } - .build() - })?; - - Ok(Date::new(date.val() - days)) - } else { - Ok(date) - } - }) - .transpose()?; - result.push_value_ref(ValueRef::from(date)); + let new_date = match (date, interval) { + (Some(date), Some(interval)) => date.sub_interval(interval), + _ => date, + }; + + result.push_value_ref(ValueRef::from(new_date)); } Ok(result.to_vector()) @@ -144,16 +112,12 @@ impl Function for DateSubFunction { for i in 0..size { let datetime = left.get(i).as_datetime(); let interval = right.get(i).as_interval(); - let datetime = datetime.map(|datetime| { - if let Some(interval) = interval { - let millis = interval.to_nanosecond() as i64 - / common_time::interval::NANOS_PER_MILLI; - DateTime::new(datetime.val() - millis) - } else { - datetime - } - }); - result.push_value_ref(ValueRef::from(datetime)); + let new_datetime = match (datetime, interval) { + (Some(datetime), Some(interval)) => datetime.sub_interval(interval), + _ => datetime, + }; + + result.push_value_ref(ValueRef::from(new_datetime)); } Ok(result.to_vector()) @@ -263,12 +227,7 @@ mod tests { ]; // Intervals in months let intervals = vec![1, 2, 3, 1]; - let results = [ - Some(122 * days_per_month), - None, - Some(39 * days_per_month), - None, - ]; + let results = [Some(3659), None, Some(1168), None]; let date_vector = DateVector::from(dates.clone()); let interval_vector = IntervalYearMonthVector::from_vec(intervals); @@ -306,12 +265,7 @@ mod tests { ]; // Intervals in months let intervals = vec![1, 2, 3, 1]; - let results = [ - Some(122 * millis_per_month), - None, - Some(39 * millis_per_month), - None, - ]; + let results = [Some(316137600000), None, Some(100915200000), None]; let date_vector = DateTimeVector::from(dates.clone()); let interval_vector = IntervalYearMonthVector::from_vec(intervals); diff --git a/src/common/time/src/date.rs b/src/common/time/src/date.rs index b94c483e3081..5c34b64f414c 100644 --- a/src/common/time/src/date.rs +++ b/src/common/time/src/date.rs @@ -15,12 +15,13 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; -use chrono::{Datelike, NaiveDate}; +use chrono::{Datelike, Days, Months, NaiveDate}; use serde::{Deserialize, Serialize}; use serde_json::Value; use snafu::ResultExt; use crate::error::{Error, ParseDateStrSnafu, Result}; +use crate::interval::Interval; const UNIX_EPOCH_FROM_CE: i32 = 719_163; @@ -86,6 +87,34 @@ impl Date { pub fn to_secs(&self) -> i64 { (self.0 as i64) * 24 * 3600 } + + /// Adds given Interval to the current date. + /// Returns None if the resulting date would be out of range. + pub fn add_interval(&self, interval: Interval) -> Option { + let naive_date = self.to_chrono_date()?; + + let (months, days, _) = interval.to_month_day_nano(); + + let naive_date = naive_date.checked_add_months(Months::new(months as u32))?; + + naive_date + .checked_add_days(Days::new(days as u64)) + .map(Into::into) + } + + /// Subtracts given Interval to the current date. + /// Returns None if the resulting date would be out of range. + pub fn sub_interval(&self, interval: Interval) -> Option { + let naive_date = self.to_chrono_date()?; + + let (months, days, _) = interval.to_month_day_nano(); + + let naive_date = naive_date.checked_sub_months(Months::new(months as u32))?; + + naive_date + .checked_sub_days(Days::new(days as u64)) + .map(Into::into) + } } #[cfg(test)] diff --git a/src/common/time/src/datetime.rs b/src/common/time/src/datetime.rs index 7f1517523072..0f525a1dd4f1 100644 --- a/src/common/time/src/datetime.rs +++ b/src/common/time/src/datetime.rs @@ -14,14 +14,15 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; +use std::time::Duration; -use chrono::{LocalResult, NaiveDateTime, TimeZone as ChronoTimeZone, Utc}; +use chrono::{Days, LocalResult, Months, NaiveDateTime, TimeZone as ChronoTimeZone, Utc}; use serde::{Deserialize, Serialize}; use crate::error::{Error, InvalidDateStrSnafu, Result}; use crate::timezone::TimeZone; use crate::util::{format_utc_datetime, local_datetime_to_utc}; -use crate::Date; +use crate::{Date, Interval}; const DATETIME_FORMAT: &str = "%F %T"; const DATETIME_FORMAT_WITH_TZ: &str = "%F %T%z"; @@ -117,6 +118,31 @@ impl DateTime { None => Utc.from_utc_datetime(&v).naive_local(), }) } + /// Adds given Interval to the current datetime. + /// Returns None if the resulting datetime would be out of range. + pub fn add_interval(&self, interval: Interval) -> Option { + let naive_datetime = self.to_chrono_datetime()?; + let (months, days, nsecs) = interval.to_month_day_nano(); + + let naive_datetime = naive_datetime.checked_add_months(Months::new(months as u32))?; + let naive_datetime = naive_datetime + Duration::from_nanos(nsecs as u64); + naive_datetime + .checked_add_days(Days::new(days as u64)) + .map(Into::into) + } + + /// Subtracts given Interval to the current datetime. + /// Returns None if the resulting datetime would be out of range. + pub fn sub_interval(&self, interval: Interval) -> Option { + let naive_datetime = self.to_chrono_datetime()?; + let (months, days, nsecs) = interval.to_month_day_nano(); + + let naive_datetime = naive_datetime.checked_sub_months(Months::new(months as u32))?; + let naive_datetime = naive_datetime - Duration::from_nanos(nsecs as u64); + naive_datetime + .checked_sub_days(Days::new(days as u64)) + .map(Into::into) + } /// Convert to [common_time::date]. pub fn to_date(&self) -> Option { diff --git a/src/common/time/src/interval.rs b/src/common/time/src/interval.rs index 04bf71d9918a..716602b8e2a8 100644 --- a/src/common/time/src/interval.rs +++ b/src/common/time/src/interval.rs @@ -118,7 +118,6 @@ impl Interval { } } - /// Return a `[Duration]` from the interval pub fn to_duration(&self) -> Result { Ok(Duration::new_nanosecond( self.to_nanosecond() diff --git a/src/common/time/src/timestamp.rs b/src/common/time/src/timestamp.rs index 44c675644243..291a5b956184 100644 --- a/src/common/time/src/timestamp.rs +++ b/src/common/time/src/timestamp.rs @@ -21,15 +21,16 @@ use std::time::Duration; use arrow::datatypes::TimeUnit as ArrowTimeUnit; use chrono::{ - DateTime, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, TimeZone as ChronoTimeZone, Utc, + DateTime, Days, LocalResult, Months, NaiveDate, NaiveDateTime, NaiveTime, + TimeZone as ChronoTimeZone, Utc, }; use serde::{Deserialize, Serialize}; use snafu::{OptionExt, ResultExt}; -use crate::error; use crate::error::{ArithmeticOverflowSnafu, Error, ParseTimestampSnafu, TimestampOverflowSnafu}; use crate::timezone::TimeZone; use crate::util::{div_ceil, format_utc_datetime, local_datetime_to_utc}; +use crate::{error, Interval}; /// Timestamp represents the value of units(seconds/milliseconds/microseconds/nanoseconds) elapsed /// since UNIX epoch. The valid value range of [Timestamp] depends on it's unit (all in UTC time zone): @@ -140,6 +141,39 @@ impl Timestamp { }) } + /// Adds given Interval to the current timestamp. + /// Returns None if the resulting timestamp would be out of range. + pub fn add_interval(&self, interval: Interval) -> Option { + let naive_datetime = self.to_chrono_datetime()?; + let (months, days, nsecs) = interval.to_month_day_nano(); + + let naive_datetime = naive_datetime.checked_add_months(Months::new(months as u32))?; + let naive_datetime = naive_datetime.checked_add_days(Days::new(days as u64))?; + let naive_datetime = naive_datetime + Duration::from_nanos(nsecs as u64); + match Timestamp::from_chrono_datetime(naive_datetime) { + // Have to convert the new timestamp by the current unit. + Some(ts) => ts.convert_to(self.unit), + None => None, + } + } + + /// Subtracts given Interval to the current timestamp. + /// Returns None if the resulting timestamp would be out of range. + pub fn sub_interval(&self, interval: Interval) -> Option { + let naive_datetime = self.to_chrono_datetime()?; + let (months, days, nsecs) = interval.to_month_day_nano(); + + let naive_datetime = naive_datetime.checked_sub_months(Months::new(months as u32))?; + + let naive_datetime = naive_datetime.checked_sub_days(Days::new(days as u64))?; + let naive_datetime = naive_datetime - Duration::from_nanos(nsecs as u64); + match Timestamp::from_chrono_datetime(naive_datetime) { + // Have to convert the new timestamp by the current unit. + Some(ts) => ts.convert_to(self.unit), + None => None, + } + } + /// Subtracts current timestamp with another timestamp, yielding a duration. pub fn sub(&self, rhs: &Self) -> Option { let lhs = self.to_chrono_datetime()?; diff --git a/tests/cases/standalone/common/function/date.result b/tests/cases/standalone/common/function/date.result index 75d36f31a2f0..122866efac4d 100644 --- a/tests/cases/standalone/common/function/date.result +++ b/tests/cases/standalone/common/function/date.result @@ -19,7 +19,7 @@ SELECT date_add('2023-12-06'::DATE, INTERVAL '3 month 5 day'); +-------------------------------------------------------------------------------------+ | date_add(Utf8("2023-12-06"),IntervalMonthDayNano("237684487635026733149179609088")) | +-------------------------------------------------------------------------------------+ -| 2024-03-10 | +| 2024-03-11 | +-------------------------------------------------------------------------------------+ SELECT date_add('2023-12-06'::DATE, '3 month 5 day'); @@ -27,7 +27,7 @@ SELECT date_add('2023-12-06'::DATE, '3 month 5 day'); +----------------------------------------------------+ | date_add(Utf8("2023-12-06"),Utf8("3 month 5 day")) | +----------------------------------------------------+ -| 2024-03-10T00:00:00 | +| 2024-03-11 | +----------------------------------------------------+ SELECT date_sub('2023-12-06 07:39:46.222'::TIMESTAMP_MS, INTERVAL '5 day'); @@ -51,7 +51,7 @@ SELECT date_sub('2023-12-06'::DATE, INTERVAL '3 month 5 day'); +-------------------------------------------------------------------------------------+ | date_sub(Utf8("2023-12-06"),IntervalMonthDayNano("237684487635026733149179609088")) | +-------------------------------------------------------------------------------------+ -| 2023-09-02 | +| 2023-09-01 | +-------------------------------------------------------------------------------------+ SELECT date_sub('2023-12-06'::DATE, '3 month 5 day'); @@ -59,7 +59,7 @@ SELECT date_sub('2023-12-06'::DATE, '3 month 5 day'); +----------------------------------------------------+ | date_sub(Utf8("2023-12-06"),Utf8("3 month 5 day")) | +----------------------------------------------------+ -| 2023-09-02T00:00:00 | +| 2023-09-01 | +----------------------------------------------------+ CREATE TABLE dates(d DATE, ts timestamp time index); @@ -83,9 +83,9 @@ SELECT date_add(d, INTERVAL '1 year 2 month 3 day') from dates; +---------------------------------------------------------------------------+ | date_add(dates.d,IntervalMonthDayNano("1109194275255040958530743959552")) | +---------------------------------------------------------------------------+ -| 1993-02-27 | -| 1995-02-26 | -| 2025-02-01 | +| 1993-03-04 | +| 1995-03-03 | +| 2025-02-09 | +---------------------------------------------------------------------------+ SELECT date_add(d, '1 year 2 month 3 day') from dates; @@ -93,9 +93,9 @@ SELECT date_add(d, '1 year 2 month 3 day') from dates; +------------------------------------------------+ | date_add(dates.d,Utf8("1 year 2 month 3 day")) | +------------------------------------------------+ -| 1993-02-27T00:00:00 | -| 1995-02-26T00:00:00 | -| 2025-02-01T00:00:00 | +| 1993-03-04 | +| 1995-03-03 | +| 2025-02-09 | +------------------------------------------------+ SELECT date_add(ts, INTERVAL '1 year 2 month 3 day') from dates; @@ -103,9 +103,9 @@ SELECT date_add(ts, INTERVAL '1 year 2 month 3 day') from dates; +----------------------------------------------------------------------------+ | date_add(dates.ts,IntervalMonthDayNano("1109194275255040958530743959552")) | +----------------------------------------------------------------------------+ -| 1971-02-28T00:00:00.001 | -| 1971-02-28T00:00:00.002 | -| 1971-02-28T00:00:00.003 | +| 1971-03-04T00:00:00.001 | +| 1971-03-04T00:00:00.002 | +| 1971-03-04T00:00:00.003 | +----------------------------------------------------------------------------+ SELECT date_add(ts, '1 year 2 month 3 day') from dates; @@ -113,9 +113,9 @@ SELECT date_add(ts, '1 year 2 month 3 day') from dates; +-------------------------------------------------+ | date_add(dates.ts,Utf8("1 year 2 month 3 day")) | +-------------------------------------------------+ -| 1971-02-28T00:00:00.001 | -| 1971-02-28T00:00:00.002 | -| 1971-02-28T00:00:00.003 | +| 1971-03-04T00:00:00.001 | +| 1971-03-04T00:00:00.002 | +| 1971-03-04T00:00:00.003 | +-------------------------------------------------+ SELECT date_sub(d, INTERVAL '1 year 2 month 3 day') from dates; @@ -123,9 +123,9 @@ SELECT date_sub(d, INTERVAL '1 year 2 month 3 day') from dates; +---------------------------------------------------------------------------+ | date_sub(dates.d,IntervalMonthDayNano("1109194275255040958530743959552")) | +---------------------------------------------------------------------------+ -| 1990-11-04 | -| 1992-11-02 | -| 2022-10-09 | +| 1990-10-29 | +| 1992-10-27 | +| 2022-10-03 | +---------------------------------------------------------------------------+ SELECT date_sub(d, '1 year 2 month 3 day') from dates; @@ -133,9 +133,9 @@ SELECT date_sub(d, '1 year 2 month 3 day') from dates; +------------------------------------------------+ | date_sub(dates.d,Utf8("1 year 2 month 3 day")) | +------------------------------------------------+ -| 1990-11-04T00:00:00 | -| 1992-11-02T00:00:00 | -| 2022-10-09T00:00:00 | +| 1990-10-29 | +| 1992-10-27 | +| 2022-10-03 | +------------------------------------------------+ SELECT date_sub(ts, INTERVAL '1 year 2 month 3 day') from dates; @@ -143,9 +143,9 @@ SELECT date_sub(ts, INTERVAL '1 year 2 month 3 day') from dates; +----------------------------------------------------------------------------+ | date_sub(dates.ts,IntervalMonthDayNano("1109194275255040958530743959552")) | +----------------------------------------------------------------------------+ -| 1968-11-04T00:00:00.001 | -| 1968-11-04T00:00:00.002 | -| 1968-11-04T00:00:00.003 | +| 1968-10-29T00:00:00.001 | +| 1968-10-29T00:00:00.002 | +| 1968-10-29T00:00:00.003 | +----------------------------------------------------------------------------+ SELECT date_sub(ts, '1 year 2 month 3 day') from dates; @@ -153,9 +153,9 @@ SELECT date_sub(ts, '1 year 2 month 3 day') from dates; +-------------------------------------------------+ | date_sub(dates.ts,Utf8("1 year 2 month 3 day")) | +-------------------------------------------------+ -| 1968-11-04T00:00:00.001 | -| 1968-11-04T00:00:00.002 | -| 1968-11-04T00:00:00.003 | +| 1968-10-29T00:00:00.001 | +| 1968-10-29T00:00:00.002 | +| 1968-10-29T00:00:00.003 | +-------------------------------------------------+ DROP TABLE dates; From ce8995acd700193206c161f08f0fd4d081616747 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Wed, 6 Dec 2023 22:59:28 +0800 Subject: [PATCH 10/11] chore: remove unused error --- src/common/query/src/error.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/common/query/src/error.rs b/src/common/query/src/error.rs index b649e4724e0a..25f9d3c71523 100644 --- a/src/common/query/src/error.rs +++ b/src/common/query/src/error.rs @@ -165,9 +165,6 @@ pub enum Error { #[snafu(display("Invalid function args: {}", err_msg))] InvalidFuncArgs { err_msg: String, location: Location }, - - #[snafu(display("Execute function error: {msg}"))] - Execute { msg: String, location: Location }, } pub type Result = std::result::Result; @@ -186,7 +183,6 @@ impl ErrorExt for Error { | Error::BadAccumulatorImpl { .. } | Error::ToScalarValue { .. } | Error::GetScalarVector { .. } - | Error::Execute { .. } | Error::ArrowCompute { .. } => StatusCode::EngineExecuteQuery, Error::InvalidInputType { source, .. } From 9ab9ffcb1ec388a8a9dbe47e13872d5be635c5a3 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Thu, 7 Dec 2023 10:43:25 +0800 Subject: [PATCH 11/11] test: refactor and add some tests --- src/common/time/src/date.rs | 18 +++++++++++++---- src/common/time/src/datetime.rs | 34 ++++++++++++++++++++++---------- src/common/time/src/timestamp.rs | 28 ++++++++++++++++++++------ 3 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/common/time/src/date.rs b/src/common/time/src/date.rs index 5c34b64f414c..7ff19145430a 100644 --- a/src/common/time/src/date.rs +++ b/src/common/time/src/date.rs @@ -95,9 +95,8 @@ impl Date { let (months, days, _) = interval.to_month_day_nano(); - let naive_date = naive_date.checked_add_months(Months::new(months as u32))?; - naive_date + .checked_add_months(Months::new(months as u32))? .checked_add_days(Days::new(days as u64)) .map(Into::into) } @@ -109,9 +108,8 @@ impl Date { let (months, days, _) = interval.to_month_day_nano(); - let naive_date = naive_date.checked_sub_months(Months::new(months as u32))?; - naive_date + .checked_sub_months(Months::new(months as u32))? .checked_sub_days(Days::new(days as u64)) .map(Into::into) } @@ -153,6 +151,18 @@ mod tests { assert_eq!(now, Date::from_str(&now).unwrap().to_string()); } + #[test] + fn test_add_sub_interval() { + let date = Date::new(1000); + + let interval = Interval::from_year_month(3); + + let new_date = date.add_interval(interval).unwrap(); + assert_eq!(new_date.val(), 1091); + + assert_eq!(date, new_date.sub_interval(interval).unwrap()); + } + #[test] pub fn test_min_max() { let mut date = Date::from_str("9999-12-31").unwrap(); diff --git a/src/common/time/src/datetime.rs b/src/common/time/src/datetime.rs index 0f525a1dd4f1..3a2274cae7fb 100644 --- a/src/common/time/src/datetime.rs +++ b/src/common/time/src/datetime.rs @@ -124,11 +124,12 @@ impl DateTime { let naive_datetime = self.to_chrono_datetime()?; let (months, days, nsecs) = interval.to_month_day_nano(); - let naive_datetime = naive_datetime.checked_add_months(Months::new(months as u32))?; - let naive_datetime = naive_datetime + Duration::from_nanos(nsecs as u64); - naive_datetime - .checked_add_days(Days::new(days as u64)) - .map(Into::into) + let naive_datetime = naive_datetime + .checked_add_months(Months::new(months as u32))? + .checked_add_days(Days::new(days as u64))? + + Duration::from_nanos(nsecs as u64); + + Some(naive_datetime.into()) } /// Subtracts given Interval to the current datetime. @@ -137,11 +138,12 @@ impl DateTime { let naive_datetime = self.to_chrono_datetime()?; let (months, days, nsecs) = interval.to_month_day_nano(); - let naive_datetime = naive_datetime.checked_sub_months(Months::new(months as u32))?; - let naive_datetime = naive_datetime - Duration::from_nanos(nsecs as u64); - naive_datetime - .checked_sub_days(Days::new(days as u64)) - .map(Into::into) + let naive_datetime = naive_datetime + .checked_sub_months(Months::new(months as u32))? + .checked_sub_days(Days::new(days as u64))? + - Duration::from_nanos(nsecs as u64); + + Some(naive_datetime.into()) } /// Convert to [common_time::date]. @@ -178,6 +180,18 @@ mod tests { assert_eq!(42, d.val()); } + #[test] + fn test_add_sub_interval() { + let datetime = DateTime::new(1000); + + let interval = Interval::from_day_time(1, 200); + + let new_datetime = datetime.add_interval(interval).unwrap(); + assert_eq!(new_datetime.val(), 1000 + 3600 * 24 * 1000 + 200); + + assert_eq!(datetime, new_datetime.sub_interval(interval).unwrap()); + } + #[test] fn test_parse_local_date_time() { std::env::set_var("TZ", "Asia/Shanghai"); diff --git a/src/common/time/src/timestamp.rs b/src/common/time/src/timestamp.rs index 291a5b956184..e8cf29a68737 100644 --- a/src/common/time/src/timestamp.rs +++ b/src/common/time/src/timestamp.rs @@ -147,9 +147,11 @@ impl Timestamp { let naive_datetime = self.to_chrono_datetime()?; let (months, days, nsecs) = interval.to_month_day_nano(); - let naive_datetime = naive_datetime.checked_add_months(Months::new(months as u32))?; - let naive_datetime = naive_datetime.checked_add_days(Days::new(days as u64))?; - let naive_datetime = naive_datetime + Duration::from_nanos(nsecs as u64); + let naive_datetime = naive_datetime + .checked_add_months(Months::new(months as u32))? + .checked_add_days(Days::new(days as u64))? + + Duration::from_nanos(nsecs as u64); + match Timestamp::from_chrono_datetime(naive_datetime) { // Have to convert the new timestamp by the current unit. Some(ts) => ts.convert_to(self.unit), @@ -163,10 +165,11 @@ impl Timestamp { let naive_datetime = self.to_chrono_datetime()?; let (months, days, nsecs) = interval.to_month_day_nano(); - let naive_datetime = naive_datetime.checked_sub_months(Months::new(months as u32))?; + let naive_datetime = naive_datetime + .checked_sub_months(Months::new(months as u32))? + .checked_sub_days(Days::new(days as u64))? + - Duration::from_nanos(nsecs as u64); - let naive_datetime = naive_datetime.checked_sub_days(Days::new(days as u64))?; - let naive_datetime = naive_datetime - Duration::from_nanos(nsecs as u64); match Timestamp::from_chrono_datetime(naive_datetime) { // Have to convert the new timestamp by the current unit. Some(ts) => ts.convert_to(self.unit), @@ -613,6 +616,19 @@ mod tests { Timestamp::new(value, unit) } + #[test] + fn test_add_sub_interval() { + let ts = Timestamp::new(1000, TimeUnit::Millisecond); + + let interval = Interval::from_day_time(1, 200); + + let new_ts = ts.add_interval(interval).unwrap(); + assert_eq!(new_ts.unit(), TimeUnit::Millisecond); + assert_eq!(new_ts.value(), 1000 + 3600 * 24 * 1000 + 200); + + assert_eq!(ts, new_ts.sub_interval(interval).unwrap()); + } + #[test] fn test_timestamp_reflexivity() { for _ in 0..1000 {