use std::{any::Any, sync::Arc};
use arrow::datatypes::{DataType, TimeUnit};
use datafusion::{
common::ScalarValue,
functions::{
core::nullif::NullIfFunc,
datetime::{date_part::DatePartFunc, date_trunc::DateTruncFunc, to_timestamp::ToTimestampSecondsFunc},
math::{self, abs::AbsFunc, ceil::CeilFunc, floor::FloorFunc, round::RoundFunc},
regex::regexpreplace::RegexpReplaceFunc,
string::{
btrim::BTrimFunc, concat::ConcatFunc, contains::ContainsFunc, ends_with::EndsWithFunc, lower::LowerFunc,
ltrim::LtrimFunc, octet_length::OctetLengthFunc, rtrim::RtrimFunc, starts_with::StartsWithFunc,
upper::UpperFunc,
},
unicode::{character_length::CharacterLengthFunc, substr::SubstrFunc},
},
functions_aggregate::{
average::Avg,
count::Count,
median::Median,
min_max::{Max, Min},
stddev::{Stddev, StddevPop},
sum::Sum,
variance::{VariancePopulation, VarianceSample},
},
logical_expr::{AggregateUDF, Operator, ScalarUDF},
physical_expr::{PhysicalExpr, ScalarFunctionExpr, expressions::Literal},
};
use query_engine::rapids_backend::rapids_backend;
use query_planning::{
datatypes::{
is_narrow_decimal_carry_type, is_supported_arrow_cast, is_supported_list_carry_type, is_timestamp_with_timezone,
},
expr::{AggregateFunction as NativeAggregateFunction, UnaryMathOp},
validation::{is_integral_type, is_string_type},
};
use super::{ExpressionCapability, ExpressionRejection, ExpressionRejectionSubject};
use crate::native::literal::parse_yyyy_mm_dd_date32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::native) enum SupportedBinaryOperator {
Add,
Subtract,
Multiply,
Divide,
Modulo,
BitwiseAnd,
BitwiseOr,
BitwiseXor,
ShiftLeft,
ShiftRight,
Eq,
NotEq,
Lt,
LtEq,
Gt,
GtEq,
And,
Or,
StringConcat,
IsDistinctFrom,
IsNotDistinctFrom,
}
impl SupportedBinaryOperator {
pub(in crate::native) fn from_datafusion(op: &Operator) -> Result<Self, ExpressionRejection> {
match op {
Operator::Plus => Ok(Self::Add),
Operator::Minus => Ok(Self::Subtract),
Operator::Multiply => Ok(Self::Multiply),
Operator::Divide => Ok(Self::Divide),
Operator::Modulo => Ok(Self::Modulo),
Operator::BitwiseAnd => Ok(Self::BitwiseAnd),
Operator::BitwiseOr => Ok(Self::BitwiseOr),
Operator::BitwiseXor => Ok(Self::BitwiseXor),
Operator::BitwiseShiftLeft => Ok(Self::ShiftLeft),
Operator::BitwiseShiftRight => Ok(Self::ShiftRight),
Operator::Eq => Ok(Self::Eq),
Operator::NotEq => Ok(Self::NotEq),
Operator::Lt => Ok(Self::Lt),
Operator::LtEq => Ok(Self::LtEq),
Operator::Gt => Ok(Self::Gt),
Operator::GtEq => Ok(Self::GtEq),
Operator::And => Ok(Self::And),
Operator::Or => Ok(Self::Or),
Operator::StringConcat => Ok(Self::StringConcat),
Operator::IsDistinctFrom => Ok(Self::IsDistinctFrom),
Operator::IsNotDistinctFrom => Ok(Self::IsNotDistinctFrom),
_ => Err(ExpressionRejection::unsupported_expression(ExpressionCapability::BinaryOperator)),
}
}
pub(super) const fn rejects_timestamp_timezone(self) -> bool {
matches!(
self,
Self::Add
| Self::Subtract
| Self::Multiply
| Self::Divide
| Self::Modulo
| Self::Eq
| Self::NotEq
| Self::Lt
| Self::LtEq
| Self::Gt
| Self::GtEq
| Self::IsDistinctFrom
| Self::IsNotDistinctFrom
)
}
pub(super) const fn rejects_list_carry(self) -> bool {
self.rejects_timestamp_timezone()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::native) enum ScalarFunctionKind {
Substring,
Extract,
DateTrunc,
ToTimestampSeconds,
Lower,
Upper,
Trim,
LeftTrim,
RightTrim,
Contains,
StartsWith,
EndsWith,
StringLength,
OctetLength,
RegexpReplace,
Concat,
Abs,
UnaryMath(UnaryMathOp),
Round,
NullIf,
}
impl ScalarFunctionKind {
pub(in crate::native) fn from_scalar_function(function: &ScalarFunctionExpr) -> Result<Self, ExpressionRejection> {
let implementation = function.fun().inner().as_ref() as &dyn Any;
if implementation.is::<SubstrFunc>() {
Ok(Self::Substring)
} else if implementation.is::<DatePartFunc>() {
Ok(Self::Extract)
} else if implementation.is::<DateTruncFunc>() {
Ok(Self::DateTrunc)
} else if implementation.is::<ToTimestampSecondsFunc>() {
Ok(Self::ToTimestampSeconds)
} else if implementation.is::<LowerFunc>() {
Ok(Self::Lower)
} else if implementation.is::<UpperFunc>() {
Ok(Self::Upper)
} else if implementation.is::<BTrimFunc>() {
Ok(Self::Trim)
} else if implementation.is::<LtrimFunc>() {
Ok(Self::LeftTrim)
} else if implementation.is::<RtrimFunc>() {
Ok(Self::RightTrim)
} else if implementation.is::<ContainsFunc>() {
Ok(Self::Contains)
} else if implementation.is::<StartsWithFunc>() {
Ok(Self::StartsWith)
} else if implementation.is::<EndsWithFunc>() {
Ok(Self::EndsWith)
} else if implementation.is::<CharacterLengthFunc>() {
Ok(Self::StringLength)
} else if implementation.is::<OctetLengthFunc>() {
Ok(Self::OctetLength)
} else if implementation.is::<RegexpReplaceFunc>() {
Ok(Self::RegexpReplace)
} else if implementation.is::<ConcatFunc>() {
Ok(Self::Concat)
} else if implementation.is::<AbsFunc>() {
Ok(Self::Abs)
} else if function.name().eq_ignore_ascii_case("sqrt") && is_builtin_scalar_impl(function.fun(), &math::sqrt())
{
Ok(Self::UnaryMath(UnaryMathOp::Sqrt))
} else if function.name().eq_ignore_ascii_case("ln") && is_builtin_scalar_impl(function.fun(), &math::ln()) {
Ok(Self::UnaryMath(UnaryMathOp::Ln))
} else if function.name().eq_ignore_ascii_case("exp") && is_builtin_scalar_impl(function.fun(), &math::exp()) {
Ok(Self::UnaryMath(UnaryMathOp::Exp))
} else if implementation.is::<FloorFunc>() {
Ok(Self::UnaryMath(UnaryMathOp::Floor))
} else if implementation.is::<CeilFunc>() {
Ok(Self::UnaryMath(UnaryMathOp::Ceil))
} else if implementation.is::<RoundFunc>() {
Ok(Self::Round)
} else if implementation.is::<NullIfFunc>() {
Ok(Self::NullIf)
} else {
Err(ExpressionRejection::unsupported_expression(ExpressionCapability::ScalarFunction)
.with_subject(ExpressionRejectionSubject::ScalarFunction(function.name().to_string())))
}
}
}
fn is_builtin_scalar_impl(fun: &ScalarUDF, builtin: &ScalarUDF) -> bool {
(fun.inner().as_ref() as &dyn Any).type_id() == (builtin.inner().as_ref() as &dyn Any).type_id()
}
pub(in crate::native) fn native_aggregate_function_kind(fun: &AggregateUDF) -> Option<NativeAggregateFunction> {
let implementation = fun.inner().as_ref() as &dyn Any;
if implementation.is::<Sum>() {
Some(NativeAggregateFunction::Sum)
} else if implementation.is::<Min>() {
Some(NativeAggregateFunction::Min)
} else if implementation.is::<Max>() {
Some(NativeAggregateFunction::Max)
} else if implementation.is::<Count>() {
Some(NativeAggregateFunction::Count)
} else if implementation.is::<Avg>() {
Some(NativeAggregateFunction::Avg)
} else if implementation.is::<Median>() {
Some(NativeAggregateFunction::Median)
} else if implementation.is::<Stddev>() {
Some(NativeAggregateFunction::StddevSamp)
} else if implementation.is::<StddevPop>() {
Some(NativeAggregateFunction::StddevPop)
} else if implementation.is::<VarianceSample>() {
Some(NativeAggregateFunction::VarSamp)
} else if implementation.is::<VariancePopulation>() {
Some(NativeAggregateFunction::VarPop)
} else {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::native) enum CastCapability {
Native,
LiteralStringDate32,
Decimal128ToFloat32ViaFloat64,
}
pub(in crate::native) fn is_list_carry_type(data_type: &DataType) -> bool {
is_supported_list_carry_type(data_type)
}
pub(in crate::native) fn is_narrow_decimal_carry(data_type: &DataType) -> bool {
is_narrow_decimal_carry_type(data_type)
}
pub(in crate::native) fn is_supported_native_cast(source_type: &DataType, target_type: &DataType) -> bool {
is_supported_arrow_cast(source_type, target_type, rapids_backend())
}
pub(in crate::native) fn cast_capability(
expr: &Arc<dyn PhysicalExpr>,
source_type: &DataType,
target_type: &DataType,
) -> Result<CastCapability, ExpressionRejection> {
if is_supported_native_cast(source_type, target_type) {
return Ok(CastCapability::Native);
}
if literal_string_date32_cast_is_supported(expr, target_type) {
return Ok(CastCapability::LiteralStringDate32);
}
if is_decimal128_to_float32_cast_via_float64(source_type, target_type) {
return Ok(CastCapability::Decimal128ToFloat32ViaFloat64);
}
Err(ExpressionRejection::unsupported_type(ExpressionCapability::CastPair)
.with_subject(ExpressionRejectionSubject::Cast { source: source_type.clone(), target: target_type.clone() }))
}
pub(in crate::native) fn is_decimal128_to_float32_cast_via_float64(
source_type: &DataType,
target_type: &DataType,
) -> bool {
matches!((source_type, target_type), (DataType::Decimal128(_, _), DataType::Float32))
&& is_supported_native_cast(source_type, &DataType::Float64)
&& is_supported_native_cast(&DataType::Float64, target_type)
}
pub(in crate::native) fn is_supported_try_cast_pair(source_type: &DataType, target_type: &DataType) -> bool {
is_string_type(source_type) && is_integral_type(target_type)
}
pub(in crate::native) fn is_supported_to_timestamp_seconds_cast(
source_type: &DataType,
target_type: &DataType,
) -> bool {
matches!(target_type, DataType::Timestamp(TimeUnit::Second, None))
&& matches!(
source_type,
DataType::Int8
| DataType::Int16
| DataType::Int32
| DataType::Int64
| DataType::UInt8
| DataType::UInt16
| DataType::UInt32
)
&& is_supported_arrow_cast(source_type, target_type, rapids_backend())
}
pub(in crate::native) fn is_numeric_or_decimal_type(data_type: &DataType) -> bool {
matches!(
data_type,
DataType::Int8
| DataType::Int16
| DataType::Int32
| DataType::Int64
| DataType::UInt8
| DataType::UInt16
| DataType::UInt32
| DataType::UInt64
| DataType::Float32
| DataType::Float64
| DataType::Decimal128(_, _)
)
}
pub(in crate::native) fn compatible_types(left: &DataType, right: &DataType) -> bool {
!is_timestamp_with_timezone(left)
&& !is_timestamp_with_timezone(right)
&& !is_list_carry_type(left)
&& !is_list_carry_type(right)
&& !is_narrow_decimal_carry(left)
&& !is_narrow_decimal_carry(right)
&& (left == right || is_supported_native_cast(left, right) || is_supported_native_cast(right, left))
}
fn literal_string_date32_cast_is_supported(expr: &Arc<dyn PhysicalExpr>, target_type: &DataType) -> bool {
matches!(target_type, DataType::Date32)
&& expr
.downcast_ref::<Literal>()
.and_then(|literal| match literal.value() {
ScalarValue::Utf8(Some(value))
| ScalarValue::LargeUtf8(Some(value))
| ScalarValue::Utf8View(Some(value)) => parse_yyyy_mm_dd_date32(value),
_ => None,
})
.is_some()
}