use arrow::{
array::{Array, BooleanArray, UInt64Array, UInt64Builder},
compute::take,
datatypes::{DataType, FieldRef},
};
use datafusion_common::{Result, exec_err, plan_err};
use datafusion_expr::{
ColumnarValue, Documentation, HigherOrderFunctionArgs, HigherOrderReturnFieldArgs,
HigherOrderSignature, HigherOrderUDFImpl, LambdaParametersProgress, ValueOrLambda,
Volatility,
};
use datafusion_macros::user_doc;
use std::sync::Arc;
use crate::lambda_utils::{
EvaluatedListLambda, SingleListLambdaResult, coerce_single_list_arg,
evaluate_single_list_predicate, single_list_lambda_parameters, value_lambda_pair,
};
make_higher_order_function_expr_and_func!(
ArrayFirst,
array_first,
array lambda,
"returns the first element of an array that satisfies the predicate",
array_first_higher_order_function
);
#[user_doc(
doc_section(label = "Array Functions"),
description = "Returns the first element of an array that satisfies the given predicate. Returns null if the array is empty or no element matches. A predicate that returns null for an element is treated as not matching.",
syntax_example = "array_first(array, predicate)",
sql_example = r#"```sql
> select array_first([1, 2, 3, 4], x -> x > 2);
+----------------------------------------+
| array_first([1,2,3,4],x -> x > 2) |
+----------------------------------------+
| 3 |
+----------------------------------------+
```"#,
argument(
name = "array",
description = "Array expression. Can be a constant, column, or function, and any combination of array operators."
),
argument(
name = "predicate",
description = "Lambda predicate that returns a boolean. The first element for which it returns true is returned."
)
)]
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ArrayFirst {
signature: HigherOrderSignature,
aliases: Vec<String>,
}
impl Default for ArrayFirst {
fn default() -> Self {
Self::new()
}
}
impl ArrayFirst {
pub fn new() -> Self {
Self {
signature: HigherOrderSignature::exact(
vec![ValueOrLambda::Value(()), ValueOrLambda::Lambda(())],
Volatility::Immutable,
),
aliases: vec![String::from("list_first")],
}
}
}
impl HigherOrderUDFImpl for ArrayFirst {
fn name(&self) -> &str {
"array_first"
}
fn aliases(&self) -> &[String] {
&self.aliases
}
fn signature(&self) -> &HigherOrderSignature {
&self.signature
}
fn coerce_value_types(&self, arg_types: &[DataType]) -> Result<Vec<DataType>> {
coerce_single_list_arg(self.name(), arg_types)
}
fn lambda_parameters(
&self,
_step: usize,
fields: &[ValueOrLambda<FieldRef, Option<FieldRef>>],
) -> Result<LambdaParametersProgress> {
single_list_lambda_parameters(self.name(), fields)
}
fn return_field_from_args(
&self,
args: HigherOrderReturnFieldArgs,
) -> Result<FieldRef> {
let (list, _lambda) = value_lambda_pair(self.name(), args.arg_fields)?;
let element_field = match list.data_type() {
DataType::List(field) | DataType::LargeList(field) => field,
other => {
return plan_err!(
"{} expected a list as first argument, got {other}",
self.name()
);
}
};
Ok(Arc::new(
element_field
.as_ref()
.clone()
.with_name("")
.with_nullable(true),
))
}
fn invoke_with_args(&self, args: HigherOrderFunctionArgs) -> Result<ColumnarValue> {
let evaluated = match evaluate_single_list_predicate(self.name(), &args)? {
SingleListLambdaResult::EarlyReturn(v) => return Ok(v),
SingleListLambdaResult::Ready(v) => v,
};
let predicate = evaluated.boolean_predicate(self.name())?;
let indices = match evaluated.original_list.data_type() {
DataType::List(_) | DataType::LargeList(_) => {
first_match_indices(&evaluated, &predicate)
}
other => return exec_err!("expected list, got {other}"),
};
let result = take(evaluated.flattened_values.as_ref(), &indices, None)?;
Ok(ColumnarValue::Array(result))
}
fn documentation(&self) -> Option<&Documentation> {
self.doc()
}
}
fn first_match_indices(
evaluated: &EvaluatedListLambda,
predicate: &BooleanArray,
) -> UInt64Array {
let mut builder = UInt64Builder::with_capacity(evaluated.len());
for i in 0..evaluated.len() {
let (start, end) = evaluated.row_range(i);
match (start..end).find(|&j| predicate.is_valid(j) && predicate.value(j)) {
Some(j) => builder.append_value(j as u64),
None => builder.append_null(),
}
}
builder.finish()
}
#[cfg(test)]
mod tests {
use arrow::{
array::{Array, AsArray, Int32Array, StringArray},
buffer::{NullBuffer, OffsetBuffer},
datatypes::Int32Type,
};
use crate::array_first::array_first_higher_order_function;
use crate::lambda_utils::test_utils::{
create_i32_large_list, create_i32_list, eval_hof_on_i32_list,
eval_hof_on_i32_list_with_outer, v,
};
use datafusion_common::Result;
use datafusion_expr::{col, lit};
fn first_greater_than_two(
list: impl Array + Clone + 'static,
) -> Result<arrow::array::ArrayRef> {
eval_hof_on_i32_list(array_first_higher_order_function(), list, v().gt(lit(2i32)))
}
fn first_where_hundred_div_gt_five(
list: impl Array + Clone + 'static,
) -> Result<arrow::array::ArrayRef> {
eval_hof_on_i32_list(
array_first_higher_order_function(),
list,
(lit(100i32) / v()).gt(lit(5i32)),
)
}
#[test]
fn test_first_basic() -> Result<()> {
let list = create_i32_list(
vec![1, 2, 3, 4, 5],
OffsetBuffer::<i32>::from_lengths(vec![5]),
None,
);
let res = first_greater_than_two(list)?;
assert_eq!(
res.as_primitive::<Int32Type>(),
&Int32Array::from(vec![Some(3)])
);
Ok(())
}
#[test]
fn test_first_no_match_is_null() -> Result<()> {
let list =
create_i32_list(vec![1, 2], OffsetBuffer::<i32>::from_lengths(vec![2]), None);
let res = first_greater_than_two(list)?;
assert_eq!(
res.as_primitive::<Int32Type>(),
&Int32Array::from(vec![None])
);
Ok(())
}
#[test]
fn test_first_empty_array_is_null() -> Result<()> {
let list = create_i32_list(
Vec::<i32>::new(),
OffsetBuffer::<i32>::from_lengths(vec![0]),
None,
);
let res = first_greater_than_two(list)?;
assert_eq!(
res.as_primitive::<Int32Type>(),
&Int32Array::from(vec![None])
);
Ok(())
}
#[test]
fn test_first_multiple_sublists() -> Result<()> {
let list = create_i32_list(
vec![1, 5, 2, 4, 3, 1, 2],
OffsetBuffer::<i32>::from_lengths(vec![2, 3, 2]),
None,
);
let res = first_greater_than_two(list)?;
assert_eq!(
res.as_primitive::<Int32Type>(),
&Int32Array::from(vec![Some(5), Some(4), None])
);
Ok(())
}
#[test]
fn test_first_null_predicate_element_is_skipped() -> Result<()> {
let list = create_i32_list(
Int32Array::from(vec![Some(1), None, Some(4)]),
OffsetBuffer::<i32>::from_lengths(vec![3]),
None,
);
let res = first_greater_than_two(list)?;
assert_eq!(
res.as_primitive::<Int32Type>(),
&Int32Array::from(vec![Some(4)])
);
Ok(())
}
#[test]
fn test_first_matched_null_element_is_returned() -> Result<()> {
let list = create_i32_list(
Int32Array::from(vec![Some(1), None, Some(3)]),
OffsetBuffer::<i32>::from_lengths(vec![3]),
None,
);
let res = eval_hof_on_i32_list(
array_first_higher_order_function(),
list,
v().is_null(),
)?;
assert_eq!(
res.as_primitive::<Int32Type>(),
&Int32Array::from(vec![None])
);
Ok(())
}
#[test]
fn test_first_does_not_evaluate_predicate_on_null_row_values() -> Result<()> {
let list = create_i32_list(
vec![1, 2, 0, 4, 5],
OffsetBuffer::<i32>::from_lengths(vec![3, 2]),
Some(NullBuffer::from(vec![false, true])),
);
let res = first_where_hundred_div_gt_five(list)?;
assert_eq!(
res.as_primitive::<Int32Type>(),
&Int32Array::from(vec![None, Some(4)])
);
Ok(())
}
#[test]
fn test_first_does_not_evaluate_predicate_on_unreachable_values() -> Result<()> {
let list = create_i32_list(
vec![0, 4, 5, 50, 100],
OffsetBuffer::<i32>::from_lengths(vec![1, 2, 2]),
None,
)
.slice(1, 2);
let res = first_where_hundred_div_gt_five(list)?;
assert_eq!(
res.as_primitive::<Int32Type>(),
&Int32Array::from(vec![Some(4), None])
);
Ok(())
}
#[test]
fn test_first_eagerly_evaluates_predicate_after_match() {
let list =
create_i32_list(vec![4, 0], OffsetBuffer::<i32>::from_lengths(vec![2]), None);
let err = first_where_hundred_div_gt_five(list).unwrap_err();
assert!(
err.to_string().contains("Divide by zero"),
"unexpected error: {err}"
);
}
#[test]
fn test_first_large_list_parity() -> Result<()> {
let list = create_i32_large_list(
vec![1, 2, 3, 4, 5],
OffsetBuffer::<i64>::from_lengths(vec![5]),
None,
);
let res = first_greater_than_two(list)?;
assert_eq!(
res.as_primitive::<Int32Type>(),
&Int32Array::from(vec![Some(3)])
);
Ok(())
}
#[test]
fn test_first_captured_outer_column() -> Result<()> {
let list = create_i32_list(
vec![1, 50, 4, 50, 7, 50],
OffsetBuffer::<i32>::from_lengths(vec![2, 2, 2]),
None,
);
let number = Int32Array::from(vec![10, 40, 60]);
let res = eval_hof_on_i32_list_with_outer(
array_first_higher_order_function(),
list,
number,
v().gt(col("number")),
)?;
assert_eq!(
res.as_primitive::<Int32Type>(),
&Int32Array::from(vec![Some(50), Some(50), None])
);
Ok(())
}
#[test]
fn test_first_string_elements() -> Result<()> {
use arrow::array::ListArray;
use arrow::datatypes::{DataType, Field};
use datafusion_expr::Expr;
use datafusion_expr::expr::LambdaVariable;
use std::sync::Arc;
let values = StringArray::from(vec!["a", "bb", "ccc"]);
let list = ListArray::new(
Arc::new(Field::new_list_field(DataType::Utf8, true)),
OffsetBuffer::<i32>::from_lengths(vec![3]),
Arc::new(values),
None,
);
let x = Expr::LambdaVariable(LambdaVariable::new(
"v".to_string(),
Some(Arc::new(Field::new("v", DataType::Utf8, true))),
));
let body = x.gt(lit("a"));
let res = eval_hof_on_i32_list(array_first_higher_order_function(), list, body)?;
assert_eq!(res.as_string::<i32>(), &StringArray::from(vec![Some("bb")]));
Ok(())
}
}