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 optional_arg_types() -> Vec<Vec<DataType>> {
Vec::new()
}
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(
{
let mut forms = vec![TypeSignature::Exact(K::arg_types())];
forms.extend(
K::optional_arg_types()
.into_iter()
.map(TypeSignature::Exact),
);
if forms.len() == 1 {
forms.pop().expect("just built one")
} else {
TypeSignature::OneOf(forms)
}
},
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 optional_bool(args: &[ArrayRef], idx: usize, what: &str) -> DFResult<bool> {
let Some(arr) = args.get(idx) else {
return Ok(false);
};
let col = arr
.as_any()
.downcast_ref::<arrow::array::BooleanArray>()
.ok_or_else(|| DataFusionError::Plan(format!("{what} must be a boolean")))?;
if col.is_empty() || col.is_null(0) {
return Err(DataFusionError::Plan(format!("{what} must not be NULL")));
}
Ok(col.value(0))
}
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 on_positional<F>(values: &[f64], f: F) -> DFResult<Vec<f64>>
where
F: FnOnce(&[f64]) -> DFResult<Vec<f64>>,
{
let mut out = on_positional_multi(values, |dense| Ok(vec![f(dense)?]))?;
Ok(out.pop().unwrap_or_else(|| vec![f64::NAN; values.len()]))
}
pub(super) fn on_positional_multi<F>(values: &[f64], f: F) -> DFResult<Vec<Vec<f64>>>
where
F: FnOnce(&[f64]) -> DFResult<Vec<Vec<f64>>>,
{
let n = values.len();
let Some(first) = values.iter().position(|v| v.is_finite()) else {
return f(&[]).map(|outs| outs.iter().map(|_| vec![f64::NAN; n]).collect());
};
let last = values.iter().rposition(|v| v.is_finite()).unwrap_or(first);
let mut filled: Vec<f64> = values[first..=last].to_vec();
let mut anchor = 0usize;
for i in 1..filled.len() {
if !filled[i].is_finite() {
continue;
}
if i > anchor + 1 {
let (lo, hi) = (filled[anchor], filled[i]);
let span = (i - anchor) as f64;
for (k, slot) in filled[anchor + 1..i].iter_mut().enumerate() {
*slot = (hi - lo).mul_add((k + 1) as f64 / span, lo);
}
}
anchor = i;
}
let computed = f(&filled)?;
Ok(computed
.into_iter()
.map(|series| {
let mut out = vec![f64::NAN; n];
for i in first..=last {
if values[i].is_finite()
&& let Some(c) = series.get(i - first)
{
out[i] = *c;
}
}
out
})
.collect())
}
pub(super) fn exec_err<E: std::fmt::Display>(e: E) -> DataFusionError {
DataFusionError::Execution(e.to_string())
}