Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(query): implement arithmetic functions for Date and Timestamp. #8202

Merged
merged 9 commits into from
Oct 16, 2022
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions src/query/expression/src/converts/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@ use crate::Value;
pub fn can_convert(datatype: &DataTypeImpl) -> bool {
!matches!(
datatype,
DataTypeImpl::Date(_)
| DataTypeImpl::Interval(_)
| DataTypeImpl::VariantArray(_)
| DataTypeImpl::VariantObject(_)
DataTypeImpl::Date(_) | DataTypeImpl::VariantArray(_) | DataTypeImpl::VariantObject(_)
)
}

Expand All @@ -48,7 +45,6 @@ pub fn from_type(datatype: &DataTypeImpl) -> DataType {
DataTypeImpl::Boolean(_) => DataType::Boolean,
DataTypeImpl::Timestamp(_) => DataType::Timestamp,
DataTypeImpl::Date(_) => DataType::Date,
DataTypeImpl::Interval(_) => DataType::Interval,
DataTypeImpl::String(_) => DataType::String,
DataTypeImpl::Struct(ty) => {
let inners = ty.types().iter().map(from_type).collect();
Expand All @@ -58,6 +54,7 @@ pub fn from_type(datatype: &DataTypeImpl) -> DataType {
DataTypeImpl::Variant(_)
| DataTypeImpl::VariantArray(_)
| DataTypeImpl::VariantObject(_) => DataType::Variant,
DataTypeImpl::Interval(_) => unimplemented!(),
})
}

Expand Down Expand Up @@ -102,7 +99,6 @@ pub fn from_scalar(datavalue: &DataValue, datatype: &DataTypeImpl) -> Scalar {
}
DataTypeImpl::Timestamp(_) => Scalar::Timestamp(datavalue.as_i64().unwrap() as i64),
DataTypeImpl::Date(_) => Scalar::Date(datavalue.as_i64().unwrap() as i32),
DataTypeImpl::Interval(_) => Scalar::Interval(datavalue.as_i64().unwrap() as i64),
DataTypeImpl::String(_) => Scalar::String(datavalue.as_string().unwrap()),
DataTypeImpl::Struct(types) => {
let values = match datavalue {
Expand Down
2 changes: 1 addition & 1 deletion src/query/expression/src/converts/to.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ pub fn to_type(datatype: &DataTypeImpl) -> DataType {
DataTypeImpl::Boolean(_) => DataType::Boolean,
DataTypeImpl::Timestamp(_) => DataType::Timestamp,
DataTypeImpl::Date(_) => DataType::Date,
DataTypeImpl::Interval(_) => DataType::Interval,
DataTypeImpl::String(_) => DataType::String,
DataTypeImpl::Struct(ty) => {
let inners = ty.types().iter().map(to_type).collect();
Expand All @@ -48,6 +47,7 @@ pub fn to_type(datatype: &DataTypeImpl) -> DataType {
DataTypeImpl::Variant(_)
| DataTypeImpl::VariantArray(_)
| DataTypeImpl::VariantObject(_) => DataType::Variant,
DataTypeImpl::Interval(_) => unimplemented!(),
})
}

Expand Down
46 changes: 0 additions & 46 deletions src/query/expression/src/date_converter.rs

This file was deleted.

163 changes: 163 additions & 0 deletions src/query/expression/src/date_helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright 2022 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 chrono::Date;
use chrono::DateTime;
use chrono::Datelike;
use chrono::Duration;
use chrono::NaiveDate;
use chrono::NaiveDateTime;
use chrono::TimeZone;
use chrono_tz::Tz;
use num_traits::AsPrimitive;

use crate::types::date::check_date;
use crate::types::timestamp::check_timestamp;

pub trait DateConverter {
fn to_date(&self, tz: &Tz) -> Date<Tz>;
fn to_timestamp(&self, tz: &Tz) -> DateTime<Tz>;
}

impl<T> DateConverter for T
where T: AsPrimitive<i64>
{
fn to_date(&self, tz: &Tz) -> Date<Tz> {
let mut dt = tz.ymd(1970, 1, 1);
dt = dt.checked_add_signed(Duration::days(self.as_())).unwrap();
dt
}

fn to_timestamp(&self, tz: &Tz) -> DateTime<Tz> {
// Can't use `tz.timestamp_nanos(self.as_() * 1000)` directly, is may cause multiply with overflow.
let micros = self.as_();
let (mut secs, mut nanos) = (micros / 1_000_000, (micros % 1_000_000) * 1_000);
if nanos < 0 {
secs -= 1;
nanos += 1_000_000_000;
}
tz.timestamp_opt(secs, nanos as u32).unwrap()
}
}

// Timestamp arithmetic factors.
pub const FACTOR_HOUR: i64 = 3600;
pub const FACTOR_MINUTE: i64 = 60;
pub const FACTOR_SECOND: i64 = 1;

fn add_years_base(year: i32, month: u32, day: u32, delta: i64) -> Result<NaiveDate, String> {
let new_year = year + delta as i32;
let mut new_day = day;
if std::intrinsics::unlikely(month == 2 && day == 29) {
new_day = last_day_of_year_month(new_year, month);
}
NaiveDate::from_ymd_opt(new_year, month, new_day).ok_or(format!(
"Overflow on date YMD {}-{}-{}.",
new_year, month, new_day
))
}

fn add_months_base(year: i32, month: u32, day: u32, delta: i64) -> Result<NaiveDate, String> {
let total_months = month as i64 + delta - 1;
let mut new_year = year + (total_months / 12) as i32;
let mut new_month0 = total_months % 12;
if new_month0 < 0 {
new_year -= 1;
new_month0 += 12;
}

// Handle month last day overflow, "2020-2-29" + "1 year" should be "2021-2-28", or "1990-1-31" + "3 month" should be "1990-4-30".
let new_day = std::cmp::min::<u32>(
day,
last_day_of_year_month(new_year, (new_month0 + 1) as u32),
);

NaiveDate::from_ymd_opt(new_year, (new_month0 + 1) as u32, new_day).ok_or(format!(
"Overflow on date YMD {}-{}-{}.",
new_year,
new_month0 + 1,
new_day
))
}

// Get the last day of the year month, could be 28(non leap Feb), 29(leap year Feb), 30 or 31
fn last_day_of_year_month(year: i32, month: u32) -> u32 {
let is_leap_year = NaiveDate::from_ymd_opt(year, 2, 29).is_some();
if std::intrinsics::unlikely(month == 2 && is_leap_year) {
return 29;
}
let last_day_lookup = [0u32, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
RinChanNOWWW marked this conversation as resolved.
Show resolved Hide resolved
last_day_lookup[month as usize]
}

macro_rules! impl_interval_year_month {
($name: ident, $op: expr) => {
#[derive(Clone)]
pub struct $name;

impl $name {
pub fn eval_date(date: i32, delta: impl AsPrimitive<i64>) -> Result<i32, String> {
let date = date.to_date(&Tz::UTC);
let new_date = $op(date.year(), date.month(), date.day(), delta.as_());
new_date.and_then(|d| {
check_date(
d.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1))
.num_days(),
)
})
}

pub fn eval_timestamp(ts: i64, delta: impl AsPrimitive<i64>) -> Result<i64, String> {
let ts = ts.to_timestamp(&Tz::UTC);
let new_ts = $op(ts.year(), ts.month(), ts.day(), delta.as_());
new_ts.and_then(|t| {
check_timestamp(NaiveDateTime::new(t, ts.time()).timestamp_micros())
})
}
}
};
}

impl_interval_year_month!(AddYearsImpl, add_years_base);
impl_interval_year_month!(AddMonthsImpl, add_months_base);

pub struct AddDaysImpl;

impl AddDaysImpl {
pub fn eval_date(date: i32, delta: impl AsPrimitive<i64>) -> Result<i32, String> {
check_date((date as i64).wrapping_add(delta.as_()))
}

pub fn eval_timestamp(date: i64, delta: impl AsPrimitive<i64>) -> Result<i64, String> {
check_timestamp((date as i64).wrapping_add(delta.as_() * 24 * 3600 * 1_000_000))
}
}

pub struct AddTimesImpl;

impl AddTimesImpl {
pub fn eval_date(date: i32, delta: impl AsPrimitive<i64>, factor: i64) -> Result<i32, String> {
check_date(
(date as i64 * 3600 * 24 * 1_000_000).wrapping_add(delta.as_() * factor * 1_000_000),
)
}

pub fn eval_timestamp(
ts: i64,
delta: impl AsPrimitive<i64>,
factor: i64,
) -> Result<i64, String> {
check_timestamp(ts.wrapping_add(delta.as_() * factor * 1_000_000))
}
}
7 changes: 1 addition & 6 deletions src/query/expression/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use rust_decimal::Decimal;
use rust_decimal::RoundingStrategy;

use crate::chunk::Chunk;
use crate::date_converter::DateConverter;
use crate::date_helper::DateConverter;
use crate::expression::Expr;
use crate::expression::Literal;
use crate::expression::RawExpr;
Expand Down Expand Up @@ -97,7 +97,6 @@ impl<'a> Debug for ScalarRef<'a> {
ScalarRef::String(s) => write!(f, "{:?}", String::from_utf8_lossy(s)),
ScalarRef::Timestamp(t) => write!(f, "{t:?}"),
ScalarRef::Date(d) => write!(f, "{d:?}"),
ScalarRef::Interval(i) => write!(f, "{i:?}"),
ScalarRef::Array(col) => write!(f, "[{}]", col.iter().join(", ")),
ScalarRef::Tuple(fields) => {
write!(
Expand All @@ -121,7 +120,6 @@ impl Debug for Column {
Column::String(col) => write!(f, "{col:?}"),
Column::Timestamp(col) => write!(f, "{col:?}"),
Column::Date(col) => write!(f, "{col:?}"),
Column::Interval(col) => write!(f, "{col:?}"),
Column::Array(col) => write!(f, "{col:?}"),
Column::Nullable(col) => write!(f, "{col:?}"),
Column::Tuple { fields, len } => f
Expand All @@ -144,7 +142,6 @@ impl<'a> Display for ScalarRef<'a> {
ScalarRef::String(s) => write!(f, "{:?}", String::from_utf8_lossy(s)),
ScalarRef::Timestamp(t) => write!(f, "{}", display_timestamp(*t)),
ScalarRef::Date(d) => write!(f, "{}", display_date(*d as i64)),
ScalarRef::Interval(i) => write!(f, "{}", display_timestamp(*i)),
ScalarRef::Array(col) => write!(f, "[{}]", col.iter().join(", ")),
ScalarRef::Tuple(fields) => {
write!(
Expand Down Expand Up @@ -357,7 +354,6 @@ impl Display for DataType {
DataType::Number(num) => write!(f, "{num}"),
DataType::Timestamp => write!(f, "Timestamp"),
DataType::Date => write!(f, "Date"),
DataType::Interval => write!(f, "Interval"),
DataType::Null => write!(f, "NULL"),
DataType::Nullable(inner) => write!(f, "{inner} NULL"),
DataType::EmptyArray => write!(f, "Array(Nothing)"),
Expand Down Expand Up @@ -565,7 +561,6 @@ impl Display for Domain {
Domain::String(domain) => write!(f, "{domain}"),
Domain::Timestamp(domain) => write!(f, "{domain}"),
Domain::Date(domain) => write!(f, "{domain}"),
Domain::Interval(domain) => write!(f, "{domain}"),
Domain::Nullable(domain) => write!(f, "{domain}"),
Domain::Array(None) => write!(f, "[]"),
Domain::Array(Some(domain)) => write!(f, "[{domain}]"),
Expand Down
15 changes: 5 additions & 10 deletions src/query/expression/src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,7 @@ impl<'a> Evaluator<'a> {
| (scalar @ Scalar::Boolean(_), DataType::Boolean)
| (scalar @ Scalar::String(_), DataType::String)
| (scalar @ Scalar::Timestamp(_), DataType::Timestamp)
| (scalar @ Scalar::Date(_), DataType::Date)
| (scalar @ Scalar::Interval(_), DataType::Interval) => Ok(scalar),
| (scalar @ Scalar::Date(_), DataType::Date) => Ok(scalar),

(scalar, dest_ty) => Err((
span,
Expand Down Expand Up @@ -440,8 +439,7 @@ impl<'a> Evaluator<'a> {
| (col @ Column::Boolean(_), DataType::Boolean)
| (col @ Column::String { .. }, DataType::String)
| (col @ Column::Timestamp { .. }, DataType::Timestamp)
| (col @ Column::Date(_), DataType::Date)
| (col @ Column::Interval(_), DataType::Interval) => Ok(col),
| (col @ Column::Date(_), DataType::Date) => Ok(col),

(col, dest_ty) => Err((span, (format!("unable to cast {col:?} to {dest_ty}")))),
}
Expand Down Expand Up @@ -655,8 +653,7 @@ impl<'a> Evaluator<'a> {
| (column @ Column::String { .. }, DataType::String)
| (column @ Column::EmptyArray { .. }, DataType::EmptyArray)
| (column @ Column::Timestamp { .. }, DataType::Timestamp)
| (column @ Column::Date(_), DataType::Date)
| (column @ Column::Interval(_), DataType::Interval) => {
| (column @ Column::Date(_), DataType::Date) => {
Column::Nullable(Box::new(NullableColumn {
validity: constant_bitmap(true, column.len()).into(),
column,
Expand Down Expand Up @@ -933,8 +930,7 @@ impl<'a> ConstantFolder<'a> {
(Domain::Boolean(_), DataType::Boolean)
| (Domain::String(_), DataType::String)
| (Domain::Timestamp(_), DataType::Timestamp)
| (Domain::Date(_), DataType::Date)
| (Domain::Interval(_), DataType::Interval) => Some(domain.clone()),
| (Domain::Date(_), DataType::Date) => Some(domain.clone()),

// failure cases
_ => None,
Expand Down Expand Up @@ -1059,8 +1055,7 @@ impl<'a> ConstantFolder<'a> {
(Domain::Boolean(_), DataType::Boolean)
| (Domain::String(_), DataType::String)
| (Domain::Timestamp(_), DataType::Timestamp)
| (Domain::Date(_), DataType::Date)
| (Domain::Interval(_), DataType::Interval) => Domain::Nullable(NullableDomain {
| (Domain::Date(_), DataType::Date) => Domain::Nullable(NullableDomain {
has_null: false,
value: Some(Box::new(domain.clone())),
}),
Expand Down
5 changes: 0 additions & 5 deletions src/query/expression/src/kernels/concat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ use crate::types::ArrayType;
use crate::types::BooleanType;
use crate::types::DateType;
use crate::types::EmptyArrayType;
use crate::types::IntervalType;
use crate::types::NullType;
use crate::types::NullableType;
use crate::types::NumberType;
Expand Down Expand Up @@ -101,10 +100,6 @@ impl Column {
let builder = Vec::with_capacity(capacity);
Self::concat_value_types::<DateType>(builder, columns)
}
Column::Interval(_) => {
let builder = Vec::with_capacity(capacity);
Self::concat_value_types::<IntervalType>(builder, columns)
}
Column::Array(col) => {
let mut builder = ArrayColumnBuilder::<AnyType>::from_column(col.slice(0..0));
builder.reserve(capacity);
Expand Down
4 changes: 0 additions & 4 deletions src/query/expression/src/kernels/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,6 @@ impl Column {
let d = Self::filter_primitive_types(column, filter);
Column::Date(d)
}
Column::Interval(column) => {
let i = Self::filter_primitive_types(column, filter);
Column::Interval(i)
}
Column::Array(column) => {
let mut builder = ArrayColumnBuilder::<AnyType>::from_column(column.slice(0..0));
builder.reserve(length);
Expand Down
Loading