From 35c9b09a55945387195de3c371c6ffb37a0f8ff4 Mon Sep 17 00:00:00 2001 From: dust1 <834902408@qq.com> Date: Thu, 23 Sep 2021 20:09:49 +0800 Subject: [PATCH] add toStartOfWeek --- common/functions/src/scalars/dates/date.rs | 3 + .../functions/src/scalars/dates/date_test.rs | 4 +- common/functions/src/scalars/dates/mod.rs | 2 + .../functions/src/scalars/dates/week_date.rs | 190 ++++++++++++++++++ .../02_0012_function_datetimes.result | 22 ++ .../02_0012_function_datetimes.sql | 23 +++ 6 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 common/functions/src/scalars/dates/week_date.rs diff --git a/common/functions/src/scalars/dates/date.rs b/common/functions/src/scalars/dates/date.rs index ffaea535b322..b78cbc038971 100644 --- a/common/functions/src/scalars/dates/date.rs +++ b/common/functions/src/scalars/dates/date.rs @@ -19,6 +19,7 @@ use super::RoundFunction; use super::ToStartOfISOYearFunction; use super::ToStartOfMonthFunction; use super::ToStartOfQuarterFunction; +use super::ToStartOfWeekFunction; use super::ToStartOfYearFunction; use super::ToYYYYMMDDFunction; use super::ToYYYYMMDDhhmmssFunction; @@ -53,7 +54,9 @@ impl DateFunction { "toStartOfQuarter".into(), ToStartOfQuarterFunction::try_create, ); + map.insert("toStartOfWeek".into(), ToStartOfWeekFunction::try_create); map.insert("toStartOfMonth".into(), ToStartOfMonthFunction::try_create); + // rounders { map.insert("toStartOfSecond".into(), |display_name| { diff --git a/common/functions/src/scalars/dates/date_test.rs b/common/functions/src/scalars/dates/date_test.rs index 1ed487a9ad4c..df606b162a7f 100644 --- a/common/functions/src/scalars/dates/date_test.rs +++ b/common/functions/src/scalars/dates/date_test.rs @@ -55,10 +55,10 @@ fn test_round_function() -> Result<()> { fn test_toStartOf_function() -> Result<()> { let test = Test { name: "test-timeSlot-now", - display: "toStartOfQuarter()", + display: "toStartOfWeek()", nullable: false, columns: vec![Series::new(vec![1631705259u32]).into()], - func: ToStartOfQuarterFunction::try_create("toStartOfQuarter"), + func: ToStartOfQuarterFunction::try_create("toStartOfWeek"), expect: Series::new(vec![18809u32]), error: "", }; diff --git a/common/functions/src/scalars/dates/mod.rs b/common/functions/src/scalars/dates/mod.rs index 85c820b22a71..0ba481b3e7b3 100644 --- a/common/functions/src/scalars/dates/mod.rs +++ b/common/functions/src/scalars/dates/mod.rs @@ -22,6 +22,7 @@ mod now; mod number_function; mod round_function; mod simple_date; +mod week_date; pub use date::DateFunction; pub use number_function::ToStartOfISOYearFunction; @@ -35,3 +36,4 @@ pub use round_function::RoundFunction; pub use simple_date::TodayFunction; pub use simple_date::TomorrowFunction; pub use simple_date::YesterdayFunction; +pub use week_date::ToStartOfWeekFunction; diff --git a/common/functions/src/scalars/dates/week_date.rs b/common/functions/src/scalars/dates/week_date.rs new file mode 100644 index 000000000000..1f806daa4e1a --- /dev/null +++ b/common/functions/src/scalars/dates/week_date.rs @@ -0,0 +1,190 @@ +// Copyright 2020 Datafuse Labs. +// +// 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 std::marker::PhantomData; +use std::ops::Sub; + +use common_datavalues::chrono::DateTime; +use common_datavalues::chrono::Datelike; +use common_datavalues::chrono::Duration; +use common_datavalues::chrono::TimeZone; +use common_datavalues::chrono::Utc; +use common_datavalues::prelude::*; +use common_exception::ErrorCode; +use common_exception::Result; + +use crate::scalars::Function; + +#[derive(Clone, Debug)] +pub struct WeekFunction { + display_name: String, + t: PhantomData, + r: PhantomData, +} + +pub trait WeekResultFunction { + fn return_type() -> Result; + fn to_number(_value: DateTime, mode: Option) -> R; + fn to_constant_value(_value: DateTime, mode: Option) -> DataValue; +} + +#[derive(Clone)] +pub struct ToStartOfWeek; + +impl WeekResultFunction for ToStartOfWeek { + fn return_type() -> Result { + Ok(DataType::Date16) + } + fn to_number(value: DateTime, mode: Option) -> u32 { + let week_mode = mode.unwrap_or(0); + let mut weekday = value.weekday().number_from_monday(); + if (week_mode & 1) == 0 { + weekday = value.weekday().number_from_sunday(); + } + weekday -= 1; + let duration = Duration::days(weekday as i64); + let result = value.sub(duration); + get_day(result) + } + + fn to_constant_value(value: DateTime, mode: Option) -> DataValue { + DataValue::UInt16(Some(Self::to_number(value, mode) as u16)) + } +} + +impl WeekFunction +where + T: WeekResultFunction + Clone + Sync + Send + 'static, + R: DFPrimitiveType + Clone, + DFPrimitiveArray: IntoSeries, +{ + pub fn try_create(display_name: &str) -> Result> { + Ok(Box::new(WeekFunction:: { + display_name: display_name.to_string(), + t: PhantomData, + r: PhantomData, + })) + } +} + +impl Function for WeekFunction +where + T: WeekResultFunction + Clone + Sync + Send, + R: DFPrimitiveType + Clone, + DFPrimitiveArray: IntoSeries, +{ + fn name(&self) -> &str { + self.display_name.as_str() + } + + fn return_type(&self, _args: &[DataType]) -> Result { + T::return_type() + } + + fn variadic_arguments(&self) -> Option<(usize, usize)> { + Some((1, 2)) + } + + fn nullable(&self, _input_schema: &DataSchema) -> Result { + Ok(false) + } + + fn eval(&self, columns: &DataColumnsWithField, input_rows: usize) -> Result { + let data_type = columns[0].data_type(); + let mut mode: Option = None; + if columns.len() == 2 && !columns[1].column().is_empty() { + let week_mode = columns[1].column().to_values()?[0].clone().as_u64()?; + if !(0..=9).contains(&week_mode) { + return Err(ErrorCode::BadArguments(format!( + "The parameter:{} range is abnormal, it should be between 0-9", + week_mode + ))); + } + mode = Some(week_mode); + } + let number_array: DataColumn = match data_type { + DataType::Date16 => { + if let DataColumn::Constant(v, _) = columns[0].column() { + let date_time = Utc.timestamp(v.as_u64()? as i64 * 24 * 3600, 0_u32); + let constant_result = T::to_constant_value(date_time, mode); + Ok(DataColumn::Constant(constant_result, input_rows)) + } else { + let result: DFPrimitiveArray = columns[0].column() + .to_array()? + .u16()? + .apply_cast_numeric(|v| { + let date_time = Utc.timestamp(v as i64 * 24 * 3600, 0_u32); + T::to_number(date_time, mode) + } + ); + Ok(result.into()) + } + }, + DataType::Date32 => { + if let DataColumn::Constant(v, _) = columns[0].column() { + let date_time = Utc.timestamp(v.as_u64()? as i64 * 24 * 3600, 0_u32); + let constant_result = T::to_constant_value(date_time, mode); + Ok(DataColumn::Constant(constant_result, input_rows)) + } else { + let result = columns[0].column() + .to_array()? + .u32()? + .apply_cast_numeric(|v| { + let date_time = Utc.timestamp(v as i64 * 24 * 3600, 0_u32); + T::to_number(date_time, mode) + } + ); + Ok(result.into()) + } + }, + DataType::DateTime32(_) => { + if let DataColumn::Constant(v, _) = columns[0].column() { + let date_time = Utc.timestamp(v.as_u64()? as i64, 0_u32); + let constant_result = T::to_constant_value(date_time, mode); + Ok(DataColumn::Constant(constant_result, input_rows)) + } else { + let result = columns[0].column() + .to_array()? + .u32()? + .apply_cast_numeric(|v| { + let date_time = Utc.timestamp(v as i64, 0_u32); + T::to_number(date_time, mode) + } + ); + Ok(result.into()) + } + }, + other => Result::Err(ErrorCode::IllegalDataType(format!( + "Illegal type {:?} of argument of function {}.Should be a date16/data32 or a dateTime32", + other, + self.name()))), + }?; + Ok(number_array) + } +} + +impl fmt::Display for WeekFunction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}()", self.display_name) + } +} + +fn get_day(date: DateTime) -> u32 { + let start: DateTime = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); + let duration = date.signed_duration_since(start); + duration.num_days() as u32 +} + +pub type ToStartOfWeekFunction = WeekFunction; diff --git a/tests/suites/0_stateless/02_0012_function_datetimes.result b/tests/suites/0_stateless/02_0012_function_datetimes.result index 024c9aca55ef..c09fd9dffb70 100644 --- a/tests/suites/0_stateless/02_0012_function_datetimes.result +++ b/tests/suites/0_stateless/02_0012_function_datetimes.result @@ -49,4 +49,26 @@ 2021-07-01 2021-04-01 2021-09-01 +2021-09-19 +2021-09-19 +2021-09-20 +2021-09-19 +2021-09-20 +2021-09-19 +2021-09-20 +2021-09-19 +2021-09-20 +2021-09-19 +2021-09-20 +2021-05-16 +2021-05-16 +2021-05-17 +2021-05-16 +2021-05-17 +2021-05-16 +2021-05-17 +2021-05-16 +2021-05-17 +2021-05-16 +2021-05-17 ===toStartOf=== diff --git a/tests/suites/0_stateless/02_0012_function_datetimes.sql b/tests/suites/0_stateless/02_0012_function_datetimes.sql index d285761a7314..d9dd43cb9673 100644 --- a/tests/suites/0_stateless/02_0012_function_datetimes.sql +++ b/tests/suites/0_stateless/02_0012_function_datetimes.sql @@ -58,4 +58,27 @@ select toStartOfMonth(toDateTime(1631705259)); select toStartOfQuarter(toDate(18885)); select toStartOfQuarter(toDate(18762)); select toStartOfMonth(toDate(18885)); + +select toStartOfWeek(toDateTime(1632397739)); +select toStartOfWeek(toDateTime(1632397739), 0); +select toStartOfWeek(toDateTime(1632397739), 1); +select toStartOfWeek(toDateTime(1632397739), 2); +select toStartOfWeek(toDateTime(1632397739), 3); +select toStartOfWeek(toDateTime(1632397739), 4); +select toStartOfWeek(toDateTime(1632397739), 5); +select toStartOfWeek(toDateTime(1632397739), 6); +select toStartOfWeek(toDateTime(1632397739), 7); +select toStartOfWeek(toDateTime(1632397739), 8); +select toStartOfWeek(toDateTime(1632397739), 9); +select toStartOfWeek(toDate(18769)); +select toStartOfWeek(toDate(18769), 0); +select toStartOfWeek(toDate(18769), 1); +select toStartOfWeek(toDate(18769), 2); +select toStartOfWeek(toDate(18769), 3); +select toStartOfWeek(toDate(18769), 4); +select toStartOfWeek(toDate(18769), 5); +select toStartOfWeek(toDate(18769), 6); +select toStartOfWeek(toDate(18769), 7); +select toStartOfWeek(toDate(18769), 8); +select toStartOfWeek(toDate(18769), 9); select '===toStartOf===';