use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::sync::Arc;
use arrow::array::{Array, ArrayRef, AsArray, Float64Array};
use arrow::datatypes::{DataType, Field, FieldRef, Float64Type};
use datafusion::common::{DataFusionError, Result as DFResult};
use datafusion::logical_expr::function::{PartitionEvaluatorArgs, WindowUDFFieldArgs};
use datafusion::logical_expr::{
PartitionEvaluator, Signature, TypeSignature, Volatility, WindowUDF, WindowUDFImpl,
};
pub(super) trait PartitionKernel: std::fmt::Debug + Send + Sync + 'static {
const NAME: &'static str;
fn arg_types() -> Vec<DataType>;
fn return_type() -> DataType {
DataType::Float64
}
fn evaluate(args: &[ArrayRef], num_rows: usize) -> DFResult<ArrayRef>;
}
pub(super) struct PartitionWindowUdf<K: PartitionKernel> {
signature: Signature,
kernel: PhantomData<fn() -> K>,
}
impl<K: PartitionKernel> std::fmt::Debug for PartitionWindowUdf<K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PartitionWindowUdf")
.field("name", &K::NAME)
.finish()
}
}
impl<K: PartitionKernel> PartialEq for PartitionWindowUdf<K> {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl<K: PartitionKernel> Eq for PartitionWindowUdf<K> {}
impl<K: PartitionKernel> Hash for PartitionWindowUdf<K> {
fn hash<H: Hasher>(&self, state: &mut H) {
K::NAME.hash(state);
}
}
impl<K: PartitionKernel> PartitionWindowUdf<K> {
fn new() -> Self {
Self {
signature: Signature::new(
TypeSignature::Exact(K::arg_types()),
Volatility::Volatile,
),
kernel: PhantomData,
}
}
pub(super) fn udf() -> WindowUDF {
WindowUDF::new_from_impl(Self::new())
}
}
impl<K: PartitionKernel> WindowUDFImpl for PartitionWindowUdf<K> {
fn name(&self) -> &str {
K::NAME
}
fn signature(&self) -> &Signature {
&self.signature
}
fn partition_evaluator(
&self,
_args: PartitionEvaluatorArgs,
) -> DFResult<Box<dyn PartitionEvaluator>> {
Ok(Box::new(PartitionEval::<K>(PhantomData)))
}
fn field(&self, field_args: WindowUDFFieldArgs) -> DFResult<FieldRef> {
Ok(Arc::new(Field::new(
field_args.name(),
K::return_type(),
true,
)))
}
}
struct PartitionEval<K: PartitionKernel>(PhantomData<fn() -> K>);
impl<K: PartitionKernel> std::fmt::Debug for PartitionEval<K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("PartitionEval").field(&K::NAME).finish()
}
}
impl<K: PartitionKernel> PartitionEvaluator for PartitionEval<K> {
fn evaluate_all(&mut self, values: &[ArrayRef], num_rows: usize) -> DFResult<ArrayRef> {
let out = K::evaluate(values, num_rows)?;
if out.len() != num_rows {
return Err(DataFusionError::Internal(format!(
"{} produced {} rows for a partition of {num_rows}",
K::NAME,
out.len()
)));
}
Ok(out)
}
}
pub(super) fn column_f64(args: &[ArrayRef], idx: usize) -> DFResult<Vec<f64>> {
let arr = args.get(idx).ok_or_else(|| {
DataFusionError::Internal(format!("missing window function argument {idx}"))
})?;
if !matches!(arr.data_type(), DataType::Float64) {
return Err(DataFusionError::Plan(format!(
"argument {idx} must be Float64, got {}",
arr.data_type()
)));
}
let f = arr.as_primitive::<Float64Type>();
Ok((0..f.len())
.map(|i| if f.is_null(i) { f64::NAN } else { f.value(i) })
.collect())
}
pub(super) fn constant_usize(args: &[ArrayRef], idx: usize, name: &str) -> DFResult<usize> {
let arr = args.get(idx).ok_or_else(|| {
DataFusionError::Internal(format!("missing window function argument {idx}"))
})?;
let i = arr
.as_any()
.downcast_ref::<arrow::array::Int64Array>()
.ok_or_else(|| DataFusionError::Plan(format!("{name} must be an integer")))?;
if i.is_empty() || i.is_null(0) {
return Err(DataFusionError::Plan(format!("{name} must not be NULL")));
}
let raw = i.value(0);
usize::try_from(raw)
.ok()
.filter(|v| *v > 0)
.ok_or_else(|| DataFusionError::Plan(format!("{name} must be positive, got {raw}")))
}
pub(super) fn constant_f64(args: &[ArrayRef], idx: usize, name: &str) -> DFResult<f64> {
let arr = args.get(idx).ok_or_else(|| {
DataFusionError::Internal(format!("missing window function argument {idx}"))
})?;
let f = arr.as_primitive::<Float64Type>();
if f.is_empty() || f.is_null(0) {
return Err(DataFusionError::Plan(format!("{name} must not be NULL")));
}
Ok(f.value(0))
}
pub(super) fn f64_array(values: Vec<f64>) -> ArrayRef {
Arc::new(
values
.into_iter()
.map(|v| v.is_finite().then_some(v))
.collect::<Float64Array>(),
)
}
pub(super) fn on_dense<F>(values: &[f64], f: F) -> DFResult<Vec<f64>>
where
F: FnOnce(&[f64]) -> DFResult<Vec<f64>>,
{
let dense: Vec<f64> = values.iter().copied().filter(|v| v.is_finite()).collect();
let computed = f(&dense)?;
let mut out = vec![f64::NAN; values.len()];
let mut k = 0;
for (i, v) in values.iter().enumerate() {
if v.is_finite() {
if let Some(c) = computed.get(k) {
out[i] = *c;
}
k += 1;
}
}
Ok(out)
}
pub(super) fn exec_err<E: std::fmt::Display>(e: E) -> DataFusionError {
DataFusionError::Execution(e.to_string())
}