Skip to main content

datafusion_functions_nested/
array_first.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! [`datafusion_expr::HigherOrderUDF`] definitions for array_first function.
19
20use arrow::{
21    array::{Array, BooleanArray, UInt64Array, UInt64Builder},
22    compute::take,
23    datatypes::{DataType, FieldRef},
24};
25use datafusion_common::{Result, exec_err, plan_err};
26use datafusion_expr::{
27    ColumnarValue, Documentation, HigherOrderFunctionArgs, HigherOrderReturnFieldArgs,
28    HigherOrderSignature, HigherOrderUDFImpl, LambdaParametersProgress, ValueOrLambda,
29    Volatility,
30};
31use datafusion_macros::user_doc;
32use std::sync::Arc;
33
34use crate::lambda_utils::{
35    EvaluatedListLambda, SingleListLambdaResult, coerce_single_list_arg,
36    evaluate_single_list_predicate, single_list_lambda_parameters, value_lambda_pair,
37};
38
39make_higher_order_function_expr_and_func!(
40    ArrayFirst,
41    array_first,
42    array lambda,
43    "returns the first element of an array that satisfies the predicate",
44    array_first_higher_order_function
45);
46
47#[user_doc(
48    doc_section(label = "Array Functions"),
49    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.",
50    syntax_example = "array_first(array, predicate)",
51    sql_example = r#"```sql
52> select array_first([1, 2, 3, 4], x -> x > 2);
53+----------------------------------------+
54| array_first([1,2,3,4],x -> x > 2)      |
55+----------------------------------------+
56| 3                                      |
57+----------------------------------------+
58```"#,
59    argument(
60        name = "array",
61        description = "Array expression. Can be a constant, column, or function, and any combination of array operators."
62    ),
63    argument(
64        name = "predicate",
65        description = "Lambda predicate that returns a boolean. The first element for which it returns true is returned."
66    )
67)]
68#[derive(Debug, PartialEq, Eq, Hash)]
69pub struct ArrayFirst {
70    signature: HigherOrderSignature,
71    aliases: Vec<String>,
72}
73
74impl Default for ArrayFirst {
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80impl ArrayFirst {
81    pub fn new() -> Self {
82        Self {
83            signature: HigherOrderSignature::exact(
84                vec![ValueOrLambda::Value(()), ValueOrLambda::Lambda(())],
85                Volatility::Immutable,
86            ),
87            aliases: vec![String::from("list_first")],
88        }
89    }
90}
91
92impl HigherOrderUDFImpl for ArrayFirst {
93    fn name(&self) -> &str {
94        "array_first"
95    }
96
97    fn aliases(&self) -> &[String] {
98        &self.aliases
99    }
100
101    fn signature(&self) -> &HigherOrderSignature {
102        &self.signature
103    }
104
105    fn coerce_value_types(&self, arg_types: &[DataType]) -> Result<Vec<DataType>> {
106        coerce_single_list_arg(self.name(), arg_types)
107    }
108
109    fn lambda_parameters(
110        &self,
111        _step: usize,
112        fields: &[ValueOrLambda<FieldRef, Option<FieldRef>>],
113    ) -> Result<LambdaParametersProgress> {
114        single_list_lambda_parameters(self.name(), fields)
115    }
116
117    fn return_field_from_args(
118        &self,
119        args: HigherOrderReturnFieldArgs,
120    ) -> Result<FieldRef> {
121        let (list, _lambda) = value_lambda_pair(self.name(), args.arg_fields)?;
122
123        let element_field = match list.data_type() {
124            DataType::List(field) | DataType::LargeList(field) => field,
125            other => {
126                return plan_err!(
127                    "{} expected a list as first argument, got {other}",
128                    self.name()
129                );
130            }
131        };
132
133        // The result is a single element of the array. It is always nullable
134        // because an empty array (or no matching element) yields null.
135        Ok(Arc::new(
136            element_field
137                .as_ref()
138                .clone()
139                .with_name("")
140                .with_nullable(true),
141        ))
142    }
143
144    fn invoke_with_args(&self, args: HigherOrderFunctionArgs) -> Result<ColumnarValue> {
145        let evaluated = match evaluate_single_list_predicate(self.name(), &args)? {
146            SingleListLambdaResult::EarlyReturn(v) => return Ok(v),
147            SingleListLambdaResult::Ready(v) => v,
148        };
149
150        let predicate = evaluated.boolean_predicate(self.name())?;
151        let indices = match evaluated.original_list.data_type() {
152            DataType::List(_) | DataType::LargeList(_) => {
153                first_match_indices(&evaluated, &predicate)
154            }
155            other => return exec_err!("expected list, got {other}"),
156        };
157
158        let result = take(evaluated.flattened_values.as_ref(), &indices, None)?;
159        Ok(ColumnarValue::Array(result))
160    }
161
162    fn documentation(&self) -> Option<&Documentation> {
163        self.doc()
164    }
165}
166
167/// Builds a `UInt64` index array (one entry per sublist) pointing at the first
168/// element whose predicate is true, or null when no element matches. Indices are
169/// absolute into the (sliced) flat values array, so `take` gathers the matches.
170///
171/// A null predicate value is treated as not matching. The matched element itself
172/// may be null and is still returned.
173fn first_match_indices(
174    evaluated: &EvaluatedListLambda,
175    predicate: &BooleanArray,
176) -> UInt64Array {
177    let mut builder = UInt64Builder::with_capacity(evaluated.len());
178
179    for i in 0..evaluated.len() {
180        let (start, end) = evaluated.row_range(i);
181
182        match (start..end).find(|&j| predicate.is_valid(j) && predicate.value(j)) {
183            Some(j) => builder.append_value(j as u64),
184            None => builder.append_null(),
185        }
186    }
187
188    builder.finish()
189}
190
191#[cfg(test)]
192mod tests {
193    use arrow::{
194        array::{Array, AsArray, Int32Array, StringArray},
195        buffer::{NullBuffer, OffsetBuffer},
196        datatypes::Int32Type,
197    };
198
199    use crate::array_first::array_first_higher_order_function;
200    use crate::lambda_utils::test_utils::{
201        create_i32_large_list, create_i32_list, eval_hof_on_i32_list,
202        eval_hof_on_i32_list_with_outer, v,
203    };
204    use datafusion_common::Result;
205    use datafusion_expr::{col, lit};
206
207    fn first_greater_than_two(
208        list: impl Array + Clone + 'static,
209    ) -> Result<arrow::array::ArrayRef> {
210        eval_hof_on_i32_list(array_first_higher_order_function(), list, v().gt(lit(2i32)))
211    }
212
213    // predicate: (100 / v) > 5; panics on divide by zero if v == 0 is evaluated
214    fn first_where_hundred_div_gt_five(
215        list: impl Array + Clone + 'static,
216    ) -> Result<arrow::array::ArrayRef> {
217        eval_hof_on_i32_list(
218            array_first_higher_order_function(),
219            list,
220            (lit(100i32) / v()).gt(lit(5i32)),
221        )
222    }
223
224    #[test]
225    fn test_first_basic() -> Result<()> {
226        let list = create_i32_list(
227            vec![1, 2, 3, 4, 5],
228            OffsetBuffer::<i32>::from_lengths(vec![5]),
229            None,
230        );
231        let res = first_greater_than_two(list)?;
232        assert_eq!(
233            res.as_primitive::<Int32Type>(),
234            &Int32Array::from(vec![Some(3)])
235        );
236        Ok(())
237    }
238
239    #[test]
240    fn test_first_no_match_is_null() -> Result<()> {
241        let list =
242            create_i32_list(vec![1, 2], OffsetBuffer::<i32>::from_lengths(vec![2]), None);
243        let res = first_greater_than_two(list)?;
244        assert_eq!(
245            res.as_primitive::<Int32Type>(),
246            &Int32Array::from(vec![None])
247        );
248        Ok(())
249    }
250
251    #[test]
252    fn test_first_empty_array_is_null() -> Result<()> {
253        let list = create_i32_list(
254            Vec::<i32>::new(),
255            OffsetBuffer::<i32>::from_lengths(vec![0]),
256            None,
257        );
258        let res = first_greater_than_two(list)?;
259        assert_eq!(
260            res.as_primitive::<Int32Type>(),
261            &Int32Array::from(vec![None])
262        );
263        Ok(())
264    }
265
266    #[test]
267    fn test_first_multiple_sublists() -> Result<()> {
268        // [1,5] -> 5, [2,4,3] -> 4, [1,2] -> null
269        let list = create_i32_list(
270            vec![1, 5, 2, 4, 3, 1, 2],
271            OffsetBuffer::<i32>::from_lengths(vec![2, 3, 2]),
272            None,
273        );
274        let res = first_greater_than_two(list)?;
275        assert_eq!(
276            res.as_primitive::<Int32Type>(),
277            &Int32Array::from(vec![Some(5), Some(4), None])
278        );
279        Ok(())
280    }
281
282    #[test]
283    fn test_first_null_predicate_element_is_skipped() -> Result<()> {
284        // [1, NULL, 4] with v > 2: the NULL element's predicate is null and is
285        // skipped, so the first match is 4.
286        let list = create_i32_list(
287            Int32Array::from(vec![Some(1), None, Some(4)]),
288            OffsetBuffer::<i32>::from_lengths(vec![3]),
289            None,
290        );
291        let res = first_greater_than_two(list)?;
292        assert_eq!(
293            res.as_primitive::<Int32Type>(),
294            &Int32Array::from(vec![Some(4)])
295        );
296        Ok(())
297    }
298
299    #[test]
300    fn test_first_matched_null_element_is_returned() -> Result<()> {
301        // [1, NULL, 3] with `v IS NULL`: the first match is the null element,
302        // which is returned as null.
303        let list = create_i32_list(
304            Int32Array::from(vec![Some(1), None, Some(3)]),
305            OffsetBuffer::<i32>::from_lengths(vec![3]),
306            None,
307        );
308        let res = eval_hof_on_i32_list(
309            array_first_higher_order_function(),
310            list,
311            v().is_null(),
312        )?;
313        assert_eq!(
314            res.as_primitive::<Int32Type>(),
315            &Int32Array::from(vec![None])
316        );
317        Ok(())
318    }
319
320    // The 0 in the null row would divide by zero if the predicate were evaluated
321    // on it. The result for the null row must be null.
322    #[test]
323    fn test_first_does_not_evaluate_predicate_on_null_row_values() -> Result<()> {
324        let list = create_i32_list(
325            vec![1, 2, 0, 4, 5],
326            OffsetBuffer::<i32>::from_lengths(vec![3, 2]),
327            Some(NullBuffer::from(vec![false, true])),
328        );
329        let res = first_where_hundred_div_gt_five(list)?;
330        assert_eq!(
331            res.as_primitive::<Int32Type>(),
332            &Int32Array::from(vec![None, Some(4)])
333        );
334        Ok(())
335    }
336
337    // The 0 before the slice offset would divide by zero if evaluated.
338    #[test]
339    fn test_first_does_not_evaluate_predicate_on_unreachable_values() -> Result<()> {
340        // sublists: [0], [4,5], [50,100]; slice away the first
341        let list = create_i32_list(
342            vec![0, 4, 5, 50, 100],
343            OffsetBuffer::<i32>::from_lengths(vec![1, 2, 2]),
344            None,
345        )
346        .slice(1, 2);
347        let res = first_where_hundred_div_gt_five(list)?;
348        // [4,5]: 100/4=25>5 -> 4. [50,100]: 2>5 false, 1>5 false -> null
349        assert_eq!(
350            res.as_primitive::<Int32Type>(),
351            &Int32Array::from(vec![Some(4), None])
352        );
353        Ok(())
354    }
355
356    #[test]
357    fn test_first_eagerly_evaluates_predicate_after_match() {
358        // Although 4 is the first match, the predicate is evaluated for the
359        // later 0 in the same sublist and produces a division-by-zero error.
360        let list =
361            create_i32_list(vec![4, 0], OffsetBuffer::<i32>::from_lengths(vec![2]), None);
362
363        let err = first_where_hundred_div_gt_five(list).unwrap_err();
364        assert!(
365            err.to_string().contains("Divide by zero"),
366            "unexpected error: {err}"
367        );
368    }
369
370    #[test]
371    fn test_first_large_list_parity() -> Result<()> {
372        let list = create_i32_large_list(
373            vec![1, 2, 3, 4, 5],
374            OffsetBuffer::<i64>::from_lengths(vec![5]),
375            None,
376        );
377        let res = first_greater_than_two(list)?;
378        assert_eq!(
379            res.as_primitive::<Int32Type>(),
380            &Int32Array::from(vec![Some(3)])
381        );
382        Ok(())
383    }
384
385    #[test]
386    fn test_first_captured_outer_column() -> Result<()> {
387        let list = create_i32_list(
388            vec![1, 50, 4, 50, 7, 50],
389            OffsetBuffer::<i32>::from_lengths(vec![2, 2, 2]),
390            None,
391        );
392        let number = Int32Array::from(vec![10, 40, 60]);
393        let res = eval_hof_on_i32_list_with_outer(
394            array_first_higher_order_function(),
395            list,
396            number,
397            v().gt(col("number")),
398        )?;
399        assert_eq!(
400            res.as_primitive::<Int32Type>(),
401            &Int32Array::from(vec![Some(50), Some(50), None])
402        );
403        Ok(())
404    }
405
406    #[test]
407    fn test_first_string_elements() -> Result<()> {
408        use arrow::array::ListArray;
409        use arrow::datatypes::{DataType, Field};
410        use datafusion_expr::Expr;
411        use datafusion_expr::expr::LambdaVariable;
412        use std::sync::Arc;
413
414        // ['a', 'bb', 'ccc'] with v > 'a' -> 'bb' (exercises take on a non-primitive type)
415        let values = StringArray::from(vec!["a", "bb", "ccc"]);
416        let list = ListArray::new(
417            Arc::new(Field::new_list_field(DataType::Utf8, true)),
418            OffsetBuffer::<i32>::from_lengths(vec![3]),
419            Arc::new(values),
420            None,
421        );
422
423        let x = Expr::LambdaVariable(LambdaVariable::new(
424            "v".to_string(),
425            Some(Arc::new(Field::new("v", DataType::Utf8, true))),
426        ));
427        let body = x.gt(lit("a"));
428
429        let res = eval_hof_on_i32_list(array_first_higher_order_function(), list, body)?;
430        assert_eq!(res.as_string::<i32>(), &StringArray::from(vec![Some("bb")]));
431        Ok(())
432    }
433}