blob: 22752a00e92593feb07ddc511b2fae1a25048579 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
//! Utility functions for the interval arithmetic library
use std::sync::Arc;
use crate::{
expressions::{BinaryExpr, CastExpr, Column, Literal, NegativeExpr},
PhysicalExpr,
};
use arrow::array::types::{IntervalDayTime, IntervalMonthDayNano};
use arrow::datatypes::{DataType, SchemaRef};
use datafusion_common::{internal_err, Result, ScalarValue};
use datafusion_expr::interval_arithmetic::Interval;
use datafusion_expr::Operator;
/// Indicates whether interval arithmetic is supported for the given expression.
/// Currently, we do not support all [`PhysicalExpr`]s for interval calculations.
/// We do not support every type of [`Operator`]s either. Over time, this check
/// will relax as more types of `PhysicalExpr`s and `Operator`s are supported.
/// Currently, [`CastExpr`], [`NegativeExpr`], [`BinaryExpr`], [`Column`] and [`Literal`] are supported.
pub fn check_support(expr: &Arc<dyn PhysicalExpr>, schema: &SchemaRef) -> bool {
let expr_any = expr.as_any();
if let Some(binary_expr) = expr_any.downcast_ref::<BinaryExpr>() {
is_operator_supported(binary_expr.op())
&& check_support(binary_expr.left(), schema)
&& check_support(binary_expr.right(), schema)
} else if let Some(column) = expr_any.downcast_ref::<Column>() {
if let Ok(field) = schema.field_with_name(column.name()) {
is_datatype_supported(field.data_type())
} else {
false
}
} else if let Some(literal) = expr_any.downcast_ref::<Literal>() {
if let Ok(dt) = literal.data_type(schema) {
is_datatype_supported(&dt)
} else {
false
}
} else if let Some(cast) = expr_any.downcast_ref::<CastExpr>() {
check_support(cast.expr(), schema)
} else if let Some(negative) = expr_any.downcast_ref::<NegativeExpr>() {
check_support(negative.arg(), schema)
} else {
false
}
}
// This function returns the inverse operator of the given operator.
pub fn get_inverse_op(op: Operator) -> Result<Operator> {
match op {
Operator::Plus => Ok(Operator::Minus),
Operator::Minus => Ok(Operator::Plus),
Operator::Multiply => Ok(Operator::Divide),
Operator::Divide => Ok(Operator::Multiply),
_ => internal_err!("Interval arithmetic does not support the operator {}", op),
}
}
/// Indicates whether interval arithmetic is supported for the given operator.
pub fn is_operator_supported(op: &Operator) -> bool {
matches!(
op,
&Operator::Plus
| &Operator::Minus
| &Operator::And
| &Operator::Gt
| &Operator::GtEq
| &Operator::Lt
| &Operator::LtEq
| &Operator::Eq
| &Operator::Multiply
| &Operator::Divide
)
}
/// Indicates whether interval arithmetic is supported for the given data type.
pub fn is_datatype_supported(data_type: &DataType) -> bool {
matches!(
data_type,
&DataType::Int64
| &DataType::Int32
| &DataType::Int16
| &DataType::Int8
| &DataType::UInt64
| &DataType::UInt32
| &DataType::UInt16
| &DataType::UInt8
| &DataType::Float64
| &DataType::Float32
)
}
/// Converts an [`Interval`] of time intervals to one of `Duration`s, if applicable. Otherwise, returns [`None`].
pub fn convert_interval_type_to_duration(interval: &Interval) -> Option<Interval> {
if let (Some(lower), Some(upper)) = (
convert_interval_bound_to_duration(interval.lower()),
convert_interval_bound_to_duration(interval.upper()),
) {
Interval::try_new(lower, upper).ok()
} else {
None
}
}
/// Converts an [`ScalarValue`] containing a time interval to one containing a `Duration`, if applicable. Otherwise, returns [`None`].
fn convert_interval_bound_to_duration(
interval_bound: &ScalarValue,
) -> Option<ScalarValue> {
match interval_bound {
ScalarValue::IntervalMonthDayNano(Some(mdn)) => interval_mdn_to_duration_ns(mdn)
.ok()
.map(|duration| ScalarValue::DurationNanosecond(Some(duration))),
ScalarValue::IntervalDayTime(Some(dt)) => interval_dt_to_duration_ms(dt)
.ok()
.map(|duration| ScalarValue::DurationMillisecond(Some(duration))),
_ => None,
}
}
/// Converts an [`Interval`] of `Duration`s to one of time intervals, if applicable. Otherwise, returns [`None`].
pub fn convert_duration_type_to_interval(interval: &Interval) -> Option<Interval> {
if let (Some(lower), Some(upper)) = (
convert_duration_bound_to_interval(interval.lower()),
convert_duration_bound_to_interval(interval.upper()),
) {
Interval::try_new(lower, upper).ok()
} else {
None
}
}
/// Converts a [`ScalarValue`] containing a `Duration` to one containing a time interval, if applicable. Otherwise, returns [`None`].
fn convert_duration_bound_to_interval(
interval_bound: &ScalarValue,
) -> Option<ScalarValue> {
match interval_bound {
ScalarValue::DurationNanosecond(Some(duration)) => {
Some(ScalarValue::new_interval_mdn(0, 0, *duration))
}
ScalarValue::DurationMicrosecond(Some(duration)) => {
Some(ScalarValue::new_interval_mdn(0, 0, *duration * 1000))
}
ScalarValue::DurationMillisecond(Some(duration)) => {
Some(ScalarValue::new_interval_dt(0, *duration as i32))
}
ScalarValue::DurationSecond(Some(duration)) => {
Some(ScalarValue::new_interval_dt(0, *duration as i32 * 1000))
}
_ => None,
}
}
/// If both the month and day fields of [`ScalarValue::IntervalMonthDayNano`] are zero, this function returns the nanoseconds part.
/// Otherwise, it returns an error.
fn interval_mdn_to_duration_ns(mdn: &IntervalMonthDayNano) -> Result<i64> {
if mdn.months == 0 && mdn.days == 0 {
Ok(mdn.nanoseconds)
} else {
internal_err!(
"The interval cannot have a non-zero month or day value for duration convertibility"
)
}
}
/// If the day field of the [`ScalarValue::IntervalDayTime`] is zero, this function returns the milliseconds part.
/// Otherwise, it returns an error.
fn interval_dt_to_duration_ms(dt: &IntervalDayTime) -> Result<i64> {
if dt.days == 0 {
// Safe to cast i32 to i64
Ok(dt.milliseconds as i64)
} else {
internal_err!(
"The interval cannot have a non-zero day value for duration convertibility"
)
}
}