use arrow::array::{ArrayRef, AsArray, BooleanArray, OffsetSizeTrait, new_null_array};
use arrow::buffer::{NullBuffer, OffsetBuffer};
use arrow::compute::take_arrays;
use arrow::datatypes::{ArrowNativeType, DataType, FieldRef};
use datafusion_common::utils::{adjust_offsets_for_slice, list_values_row_number};
use datafusion_common::{
Result, ScalarValue, plan_err,
utils::{list_values, take_function_args},
};
use datafusion_common::{exec_datafusion_err, exec_err};
use datafusion_expr::{
ColumnarValue, HigherOrderFunctionArgs, LambdaParametersProgress, ValueOrLambda,
};
use std::sync::Arc;
pub(crate) fn value_lambda_pair<'a, V: std::fmt::Debug, L: std::fmt::Debug>(
name: &str,
args: &'a [ValueOrLambda<V, L>],
) -> Result<(&'a V, &'a L)> {
let [value, lambda] = take_function_args(name, args)?;
let (ValueOrLambda::Value(value), ValueOrLambda::Lambda(lambda)) = (value, lambda)
else {
return plan_err!(
"{name} expects a value followed by a lambda, got {value:?} and {lambda:?}"
);
};
Ok((value, lambda))
}
pub(crate) fn coerce_single_list_arg(
name: &str,
arg_types: &[DataType],
) -> Result<Vec<DataType>> {
let list = if arg_types.len() == 1 {
&arg_types[0]
} else {
return plan_err!(
"{name} function requires 1 value arguments, got {}",
arg_types.len()
);
};
let coerced = match list {
DataType::List(_) | DataType::LargeList(_) => list.clone(),
DataType::ListView(field) | DataType::FixedSizeList(field, _) => {
DataType::List(Arc::clone(field))
}
DataType::LargeListView(field) => DataType::LargeList(Arc::clone(field)),
DataType::Null => DataType::new_list(DataType::Null, true),
_ => return plan_err!("{name} expected a list as first argument, got {list}"),
};
Ok(vec![coerced])
}
pub(crate) fn single_list_lambda_parameters(
name: &str,
fields: &[ValueOrLambda<FieldRef, Option<FieldRef>>],
) -> Result<LambdaParametersProgress> {
let (list, _lambda) = value_lambda_pair(name, fields)?;
let field = match list.data_type() {
DataType::List(field) | DataType::LargeList(field) => field,
_ => return plan_err!("expected list, got {list}"),
};
Ok(LambdaParametersProgress::Complete(vec![vec![Arc::clone(
field,
)]]))
}
pub(crate) enum ListValuesResult {
EarlyReturn(ColumnarValue),
Values(ArrayRef),
}
pub(crate) fn extract_list_values(
list_array: &ArrayRef,
return_type: &DataType,
) -> Result<ListValuesResult> {
if list_array.null_count() == list_array.len() {
return Ok(ListValuesResult::EarlyReturn(ColumnarValue::Scalar(
ScalarValue::try_new_null(return_type)?,
)));
}
let values = list_values(list_array)?;
if values.is_empty()
&& list_array.null_count() == 0
&& matches!(return_type, DataType::List(_) | DataType::LargeList(_))
{
return Ok(ListValuesResult::EarlyReturn(ColumnarValue::Scalar(
ScalarValue::new_default(return_type)?,
)));
}
Ok(ListValuesResult::Values(values))
}
pub(crate) enum SingleListLambdaResult {
EarlyReturn(ColumnarValue),
Ready(EvaluatedListLambda),
}
pub(crate) struct EvaluatedListLambda {
pub original_list: ArrayRef,
pub flattened_values: ArrayRef,
pub evaluated_result: ColumnarValue,
row_offsets: Vec<usize>,
}
impl EvaluatedListLambda {
pub(crate) fn len(&self) -> usize {
self.original_list.len()
}
pub(crate) fn nulls(&self) -> Option<&NullBuffer> {
self.original_list.nulls()
}
pub(crate) fn row_range(&self, i: usize) -> (usize, usize) {
(self.row_offsets[i], self.row_offsets[i + 1])
}
pub(crate) fn adjusted_offsets<O: OffsetSizeTrait>(&self) -> OffsetBuffer<O> {
OffsetBuffer::from_lengths(self.row_offsets.windows(2).map(|w| w[1] - w[0]))
}
pub(crate) fn boolean_predicate(&self, name: &str) -> Result<BooleanArray> {
let arr = self
.evaluated_result
.clone()
.into_array(self.flattened_values.len())?;
let predicate = arr.as_any().downcast_ref::<BooleanArray>().ok_or_else(|| {
exec_datafusion_err!("{} predicate must return boolean array", name)
})?;
Ok(predicate.clone())
}
}
fn adjusted_row_offsets(list: &ArrayRef) -> Result<Vec<usize>> {
Ok(match list.data_type() {
DataType::List(_) => adjust_offsets_for_slice(list.as_list::<i32>())
.iter()
.map(|o| o.as_usize())
.collect(),
DataType::LargeList(_) => adjust_offsets_for_slice(list.as_list::<i64>())
.iter()
.map(|o| o.as_usize())
.collect(),
other => return exec_err!("expected list, got {other}"),
})
}
fn evaluate_single_list_lambda(
name: &str,
args: &HigherOrderFunctionArgs,
) -> Result<SingleListLambdaResult> {
let (original_list, lambda) = value_lambda_pair(name, &args.args)?;
let original_list = original_list.to_array(args.number_rows)?;
if original_list.null_count() == original_list.len() {
return Ok(SingleListLambdaResult::EarlyReturn(ColumnarValue::Array(
new_null_array(args.return_type(), original_list.len()),
)));
}
let flattened_values = list_values(&original_list)?;
let values_param = || Ok(Arc::clone(&flattened_values));
let evaluated_result = lambda.evaluate(&[&values_param], |arrays| {
let indices = list_values_row_number(&original_list)?;
Ok(take_arrays(arrays, &indices, None)?)
})?;
let row_offsets = adjusted_row_offsets(&original_list)?;
Ok(SingleListLambdaResult::Ready(EvaluatedListLambda {
original_list,
flattened_values,
evaluated_result,
row_offsets,
}))
}
pub(crate) fn evaluate_single_list_predicate(
name: &str,
args: &HigherOrderFunctionArgs,
) -> Result<SingleListLambdaResult> {
let result = evaluate_single_list_lambda(name, args)?;
let SingleListLambdaResult::Ready(evaluated_list_lambda) = &result else {
return Ok(result);
};
match &evaluated_list_lambda.evaluated_result {
ColumnarValue::Scalar(ScalarValue::Boolean(_)) => Ok(result),
ColumnarValue::Scalar(scalar) => exec_err!(
"{name} lambda must return boolean, got {}",
scalar.data_type()
),
ColumnarValue::Array(array) if array.as_any().is::<BooleanArray>() => Ok(result),
ColumnarValue::Array(array) => exec_err!(
"{name} lambda must return boolean, got {}",
array.data_type()
),
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use arrow::{
array::ArrayRef,
buffer::{NullBuffer, OffsetBuffer},
datatypes::{DataType, Field},
};
use datafusion_common::Result;
use super::{adjusted_row_offsets, coerce_single_list_arg};
use crate::lambda_utils::test_utils::{create_i32_large_list, create_i32_list};
#[test]
fn adjusted_row_offsets_matches_list_lengths() -> Result<()> {
let list = create_i32_list(
vec![1, 2, 3, 4, 5],
OffsetBuffer::<i32>::from_lengths(vec![2, 0, 3]),
None,
);
let list = Arc::new(list) as ArrayRef;
assert_eq!(adjusted_row_offsets(&list)?, vec![0, 2, 2, 5]);
Ok(())
}
#[test]
fn adjusted_row_offsets_on_sliced_list() -> Result<()> {
let list = create_i32_list(
vec![10, 1, 2, 3, 4],
OffsetBuffer::<i32>::from_lengths(vec![1, 2, 2]),
None,
)
.slice(1, 2);
let list = Arc::new(list) as ArrayRef;
assert_eq!(adjusted_row_offsets(&list)?, vec![0, 2, 4]);
Ok(())
}
#[test]
fn adjusted_row_offsets_null_rows_keep_backing_lengths() -> Result<()> {
let list = create_i32_list(
vec![1, 99, 100, 2],
OffsetBuffer::<i32>::from_lengths(vec![1, 2, 1]),
Some(NullBuffer::from(vec![true, false, true])),
);
let list = Arc::new(list) as ArrayRef;
assert_eq!(adjusted_row_offsets(&list)?, vec![0, 1, 3, 4]);
Ok(())
}
#[test]
fn adjusted_row_offsets_large_list_parity() -> Result<()> {
let list = create_i32_large_list(
vec![1, 2, 3, 4],
OffsetBuffer::<i64>::from_lengths(vec![1, 3]),
None,
);
let list = Arc::new(list) as ArrayRef;
assert_eq!(adjusted_row_offsets(&list)?, vec![0, 1, 4]);
Ok(())
}
#[test]
fn coerce_single_list_arg_supports_advertised_list_likes() -> Result<()> {
let field = Arc::new(Field::new_list_field(DataType::Int32, true));
assert_eq!(
coerce_single_list_arg("test", &[DataType::List(Arc::clone(&field))])?,
vec![DataType::List(Arc::clone(&field))]
);
assert_eq!(
coerce_single_list_arg("test", &[DataType::LargeList(Arc::clone(&field))])?,
vec![DataType::LargeList(Arc::clone(&field))]
);
assert_eq!(
coerce_single_list_arg(
"test",
&[DataType::FixedSizeList(Arc::clone(&field), 3)]
)?,
vec![DataType::List(Arc::clone(&field))]
);
assert_eq!(
coerce_single_list_arg("test", &[DataType::ListView(Arc::clone(&field))])?,
vec![DataType::List(Arc::clone(&field))]
);
assert_eq!(
coerce_single_list_arg(
"test",
&[DataType::LargeListView(Arc::clone(&field))]
)?,
vec![DataType::LargeList(field)]
);
Ok(())
}
#[test]
fn coerce_single_list_arg_rejects_non_list() {
let err = coerce_single_list_arg("test", &[DataType::Int32]).unwrap_err();
assert!(err.to_string().contains("expected a list"));
}
}
#[cfg(test)]
pub(crate) mod test_utils {
use std::{collections::HashMap, sync::Arc};
use arrow::{
array::{Array, ArrayRef, Int32Array, LargeListArray, ListArray, RecordBatch},
buffer::{NullBuffer, OffsetBuffer},
datatypes::{DataType, Field},
};
use datafusion_common::{DFSchema, Result};
use datafusion_expr::{
Expr, HigherOrderUDF, col,
execution_props::ExecutionProps,
expr::{HigherOrderFunction, LambdaVariable},
lambda,
physical_planning_context::PhysicalPlanningContext,
};
use datafusion_physical_expr::create_physical_expr;
pub(crate) fn create_i32_list(
values: impl Into<Int32Array>,
offsets: OffsetBuffer<i32>,
nulls: Option<NullBuffer>,
) -> ListArray {
let list_field = Arc::new(Field::new_list_field(DataType::Int32, true));
ListArray::new(list_field, offsets, Arc::new(values.into()), nulls)
}
pub(crate) fn create_i32_large_list(
values: impl Into<Int32Array>,
offsets: OffsetBuffer<i64>,
nulls: Option<NullBuffer>,
) -> LargeListArray {
let list_field = Arc::new(Field::new_list_field(DataType::Int32, true));
LargeListArray::new(list_field, offsets, Arc::new(values.into()), nulls)
}
pub(crate) fn eval_hof_on_i32_list(
func: Arc<HigherOrderUDF>,
list: impl Array + Clone + 'static,
lambda_body: Expr,
) -> Result<ArrayRef> {
let schema = DFSchema::from_unqualified_fields(
vec![Field::new(
"list",
list.data_type().clone(),
list.is_nullable(),
)]
.into(),
HashMap::new(),
)?;
create_physical_expr(
&Expr::HigherOrderFunction(HigherOrderFunction::new(
func,
vec![col("list"), lambda(["v"], lambda_body)],
)),
&schema,
&ExecutionProps::new(),
&PhysicalPlanningContext::default(),
)?
.evaluate(&RecordBatch::try_new(
Arc::clone(schema.inner()),
vec![Arc::new(list.clone())],
)?)?
.into_array(list.len())
}
pub(crate) fn eval_hof_on_i32_list_with_outer(
func: Arc<HigherOrderUDF>,
list: impl Array + Clone + 'static,
number: Int32Array,
lambda_body: Expr,
) -> Result<ArrayRef> {
assert_eq!(list.len(), number.len());
let schema = DFSchema::from_unqualified_fields(
vec![
Field::new("list", list.data_type().clone(), list.is_nullable()),
Field::new("number", DataType::Int32, true),
]
.into(),
HashMap::new(),
)?;
create_physical_expr(
&Expr::HigherOrderFunction(HigherOrderFunction::new(
func,
vec![col("list"), lambda(["v"], lambda_body)],
)),
&schema,
&ExecutionProps::new(),
&PhysicalPlanningContext::default(),
)?
.evaluate(&RecordBatch::try_new(
Arc::clone(schema.inner()),
vec![Arc::new(list.clone()), Arc::new(number)],
)?)?
.into_array(list.len())
}
pub(crate) fn v() -> Expr {
Expr::LambdaVariable(LambdaVariable::new(
"v".to_string(),
Some(Arc::new(Field::new("v", DataType::Int32, true))),
))
}
}