This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new 36d1c98f22 feat: add array_first higher-order array function (#23267)
36d1c98f22 is described below
commit 36d1c98f22d2217dc0849b5095679368794a816a
Author: Edson Petry <[email protected]>
AuthorDate: Mon Jul 13 06:42:28 2026 -0400
feat: add array_first higher-order array function (#23267)
## Which issue does this PR close?
<!--
We generally require a GitHub issue to be filed for all bug fixes and
enhancements and this helps us generate change logs for our releases.
-->
No dedicated tracking issue; this adds a single self-contained
higher-order array function. Happy to file one if preferred.
## Rationale for this change
DataFusion already provides higher-order array functions such as
`array_any_match`, `array_filter`, and `array_transform`, but there is
no direct way to retrieve the *first* element of an array that satisfies
a predicate. Today this requires `array_filter` followed by
`array_element(..., 1)`, which materializes an intermediate filtered
array. `array_first` expresses this directly and rounds out the set of
lambda-based array functions.
## What changes are included in this PR?
- New higher-order function `array_first(array, predicate)` (alias
`list_first`) in `datafusion-functions-nested`, returning the first
element for which the lambda predicate returns `true`:
- returns `null` when the array is empty or no element matches;
- a predicate that evaluates to `null` for an element is treated as not
matching;
- a matched element that is itself `null` is returned as `null`.
- Implemented as a `HigherOrderUDFImpl` following the existing
array-lambda functions, including the standard fast paths (fully-null
input) and correct handling of sliced lists, null sublists, and captured
outer columns.
- Registration in `functions-nested` (`expr_fn` re-export and the
default higher-order function list).
- Unit tests, sqllogictest coverage, and regenerated SQL function
documentation.
## Are these changes tested?
Yes:
- Unit tests in `array_first.rs` covering match/no-match, empty and null
arrays, null-predicate handling, matched-null elements, sliced lists,
captured outer columns, and non-primitive element types.
- sqllogictest cases in `test_files/array/array_first.slt`, including
`LargeList` and the `list_first` alias.
## Are there any user-facing changes?
Yes. A new array function `array_first` (alias `list_first`) is
available in SQL, with generated documentation under the Array Functions
section. There are no breaking changes to existing public APIs.
---
datafusion/functions-nested/src/array_first.rs | 441 +++++++++++++++++++++
datafusion/functions-nested/src/lib.rs | 3 +
.../sqllogictest/test_files/array/array_first.slt | 127 ++++++
docs/source/user-guide/sql/scalar_functions.md | 34 ++
4 files changed, 605 insertions(+)
diff --git a/datafusion/functions-nested/src/array_first.rs
b/datafusion/functions-nested/src/array_first.rs
new file mode 100644
index 0000000000..07154a2db7
--- /dev/null
+++ b/datafusion/functions-nested/src/array_first.rs
@@ -0,0 +1,441 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! [`datafusion_expr::HigherOrderUDF`] definitions for array_first function.
+
+use arrow::{
+ array::{
+ Array, AsArray, BooleanArray, GenericListArray, OffsetSizeTrait,
UInt64Array,
+ UInt64Builder, new_null_array,
+ },
+ compute::{take, take_arrays},
+ datatypes::{DataType, FieldRef},
+};
+use datafusion_common::{
+ Result, exec_datafusion_err, exec_err, plan_err,
+ utils::{adjust_offsets_for_slice, list_values, list_values_row_number},
+};
+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::{
+ coerce_single_list_arg, 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()
+ );
+ }
+ };
+
+ // The result is a single element of the array. It is always nullable
+ // because an empty array (or no matching element) yields null.
+ Ok(Arc::new(
+ element_field
+ .as_ref()
+ .clone()
+ .with_name("")
+ .with_nullable(true),
+ ))
+ }
+
+ fn invoke_with_args(&self, args: HigherOrderFunctionArgs) ->
Result<ColumnarValue> {
+ let (list, lambda) = value_lambda_pair(self.name(), &args.args)?;
+
+ let list_array = list.to_array(args.number_rows)?;
+
+ // Fast path: fully null input. Also required for FixedSizeList which
+ // can't be handled by clear_null_values when fully null.
+ if list_array.null_count() == list_array.len() {
+ return Ok(ColumnarValue::Array(new_null_array(
+ args.return_type(),
+ list_array.len(),
+ )));
+ }
+
+ let list_values = list_values(&list_array)?;
+
+ // Evaluate the predicate over every flat element. Captured columns are
+ // spread to align with the flattened values via
list_values_row_number.
+ let values_param = || Ok(Arc::clone(&list_values));
+
+ let predicate_results = lambda
+ .evaluate(&[&values_param], |arrays| {
+ let indices = list_values_row_number(&list_array)?;
+ Ok(take_arrays(arrays, &indices, None)?)
+ })?
+ .into_array(list_values.len())?;
+
+ let predicate_bool = predicate_results
+ .as_any()
+ .downcast_ref::<BooleanArray>()
+ .ok_or_else(|| {
+ exec_datafusion_err!(
+ "{} predicate must return boolean array, got {}",
+ self.name(),
+ predicate_results.data_type()
+ )
+ })?;
+
+ // For each row, find the flat index of the first element whose
predicate
+ // is true. Rows with no match, including empty rows and null rows that
+ // clear_null_values truncated to empty, map to a null index, producing
+ // a null result via `take`.
+ let indices = match list_array.data_type() {
+ DataType::List(_) => {
+ first_match_indices(list_array.as_list::<i32>(),
predicate_bool)
+ }
+ DataType::LargeList(_) => {
+ first_match_indices(list_array.as_list::<i64>(),
predicate_bool)
+ }
+ other => return exec_err!("expected list, got {other}"),
+ };
+
+ let result = take(list_values.as_ref(), &indices, None)?;
+ Ok(ColumnarValue::Array(result))
+ }
+
+ fn documentation(&self) -> Option<&Documentation> {
+ self.doc()
+ }
+}
+
+/// Builds a `UInt64` index array (one entry per sublist) pointing at the first
+/// element whose predicate is true, or null when no element matches. Indices
are
+/// absolute into the (sliced) flat values array, so `take` gathers the
matches.
+///
+/// A null predicate value is treated as not matching. The matched element
itself
+/// may be null and is still returned.
+fn first_match_indices<O: OffsetSizeTrait>(
+ list: &GenericListArray<O>,
+ predicate: &BooleanArray,
+) -> UInt64Array {
+ // Offsets are adjusted so that sliced lists index correctly into the
+ // predicate / values arrays returned by list_values.
+ let offsets = adjust_offsets_for_slice(list);
+ let mut builder = UInt64Builder::with_capacity(list.len());
+
+ for i in 0..list.len() {
+ let start = offsets[i].as_usize();
+ let end = offsets[i + 1].as_usize();
+
+ 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_list,
eval_hof_on_i32_list, v};
+ use datafusion_common::Result;
+ use datafusion_expr::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)))
+ }
+
+ // predicate: (100 / v) > 5; panics on divide by zero if v == 0 is
evaluated
+ 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<()> {
+ // [1,5] -> 5, [2,4,3] -> 4, [1,2] -> null
+ 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<()> {
+ // [1, NULL, 4] with v > 2: the NULL element's predicate is null and is
+ // skipped, so the first match is 4.
+ 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<()> {
+ // [1, NULL, 3] with `v IS NULL`: the first match is the null element,
+ // which is returned as null.
+ 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(())
+ }
+
+ // The 0 in the null row would divide by zero if the predicate were
evaluated
+ // on it. The result for the null row must be null.
+ #[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(())
+ }
+
+ // The 0 before the slice offset would divide by zero if evaluated.
+ #[test]
+ fn test_first_does_not_evaluate_predicate_on_unreachable_values() ->
Result<()> {
+ // sublists: [0], [4,5], [50,100]; slice away the first
+ 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)?;
+ // [4,5]: 100/4=25>5 -> 4. [50,100]: 2>5 false, 1>5 false -> null
+ assert_eq!(
+ res.as_primitive::<Int32Type>(),
+ &Int32Array::from(vec![Some(4), None])
+ );
+ Ok(())
+ }
+
+ #[test]
+ fn test_first_eagerly_evaluates_predicate_after_match() {
+ // Although 4 is the first match, the predicate is evaluated for the
+ // later 0 in the same sublist and produces a division-by-zero error.
+ 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_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;
+
+ // ['a', 'bb', 'ccc'] with v > 'a' -> 'bb' (exercises take on a
non-primitive type)
+ 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(())
+ }
+}
diff --git a/datafusion/functions-nested/src/lib.rs
b/datafusion/functions-nested/src/lib.rs
index 59117f16f1..2c7bd25d7d 100644
--- a/datafusion/functions-nested/src/lib.rs
+++ b/datafusion/functions-nested/src/lib.rs
@@ -45,6 +45,7 @@ pub mod array_any_match;
pub mod array_avg;
pub mod array_compact;
pub mod array_filter;
+pub mod array_first;
pub mod array_has;
pub mod array_normalize;
pub mod array_product;
@@ -99,6 +100,7 @@ pub mod expr_fn {
pub use super::array_avg::array_avg;
pub use super::array_compact::array_compact;
pub use super::array_filter::array_filter;
+ pub use super::array_first::array_first;
pub use super::array_has::array_has;
pub use super::array_has::array_has_all;
pub use super::array_has::array_has_any;
@@ -222,6 +224,7 @@ pub fn all_default_higher_order_functions() ->
Vec<Arc<HigherOrderUDF>> {
vec![
array_any_match::array_any_match_higher_order_function(),
array_filter::array_filter_higher_order_function(),
+ array_first::array_first_higher_order_function(),
array_transform::array_transform_higher_order_function(),
]
}
diff --git a/datafusion/sqllogictest/test_files/array/array_first.slt
b/datafusion/sqllogictest/test_files/array/array_first.slt
new file mode 100644
index 0000000000..d761c3a4d1
--- /dev/null
+++ b/datafusion/sqllogictest/test_files/array/array_first.slt
@@ -0,0 +1,127 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+#############
+## array_first Tests
+#############
+
+statement ok
+set datafusion.sql_parser.dialect = databricks;
+
+statement ok
+CREATE TABLE t (list array<int>, number int)
+AS VALUES
+([1, 50], 10),
+([4, 50], 40),
+([7, 50], 60);
+
+# basic: returns the first element that matches the predicate
+query I
+SELECT array_first([1, 2, 3, 4], x -> x > 2);
+----
+3
+
+# no element matches returns null
+query I
+SELECT array_first([1, 2, 3], x -> x > 5);
+----
+NULL
+
+# empty array returns null
+query I
+SELECT array_first(arrow_cast(make_array(), 'List(Int32)'), x -> x > 0);
+----
+NULL
+
+# null array returns null
+query I
+SELECT array_first(arrow_cast(NULL, 'List(Int32)'), x -> x > 0);
+----
+NULL
+
+# a predicate that returns null for an element is treated as not matching
+query I
+SELECT array_first([1, 2, NULL, 4], x -> x > 2);
+----
+4
+
+# the predicate may match a null element, which is returned as null
+query I
+SELECT array_first(arrow_cast([NULL, 2], 'List(Int32)'), x -> x IS NULL);
+----
+NULL
+
+# predicate always returns null -> no match -> null
+query I
+SELECT array_first([1, 2, 3], x -> NULL::boolean);
+----
+NULL
+
+# a predicate matching every element returns the first element
+query I
+SELECT array_first([10, 20, 30], x -> true);
+----
+10
+
+# string elements
+query T
+SELECT array_first(['a', 'bb', 'ccc'], x -> length(x) > 1);
+----
+bb
+
+# multiple rows
+query I
+SELECT array_first(list, x -> x > 5) FROM t;
+----
+50
+50
+7
+
+# predicate can reference an outer column (last row has no match -> null)
+query I
+SELECT array_first(list, x -> x > number) FROM t;
+----
+50
+50
+NULL
+
+# large list works
+query I
+SELECT array_first(arrow_cast([1, 2, 3, 4], 'LargeList(Int32)'), x -> x > 2);
+----
+3
+
+# other list representations are coerced during planning
+query III
+SELECT
+ array_first(arrow_cast([1, 2, 3, 4], 'FixedSizeList(4, Int32)'), x -> x > 2),
+ array_first(arrow_cast([1, 2, 3, 4], 'ListView(Int32)'), x -> x > 2),
+ array_first(arrow_cast([1, 2, 3, 4], 'LargeListView(Int32)'), x -> x > 2);
+----
+3 3 3
+
+# alias array_first/list_first work
+query I
+SELECT list_first([1, 2, 3, 4], x -> x > 2);
+----
+3
+
+statement ok
+drop table t;
+
+statement ok
+set datafusion.sql_parser.dialect = generic;
diff --git a/docs/source/user-guide/sql/scalar_functions.md
b/docs/source/user-guide/sql/scalar_functions.md
index 497d899762..cb748f57d9 100644
--- a/docs/source/user-guide/sql/scalar_functions.md
+++ b/docs/source/user-guide/sql/scalar_functions.md
@@ -3261,6 +3261,7 @@ _Alias of [current_date](#current_date)._
- [array_except](#array_except)
- [array_extract](#array_extract)
- [array_filter](#array_filter)
+- [array_first](#array_first)
- [array_has](#array_has)
- [array_has_all](#array_has_all)
- [array_has_any](#array_has_any)
@@ -3323,6 +3324,7 @@ _Alias of [current_date](#current_date)._
- [list_except](#list_except)
- [list_extract](#list_extract)
- [list_filter](#list_filter)
+- [list_first](#list_first)
- [list_has](#list_has)
- [list_has_all](#list_has_all)
- [list_has_any](#list_has_any)
@@ -3757,6 +3759,34 @@ array_filter(array, x -> x > 2)
- list_filter
+### `array_first`
+
+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.
+
+```sql
+array_first(array, predicate)
+```
+
+#### Arguments
+
+- **array**: Array expression. Can be a constant, column, or function, and any
combination of array operators.
+- **predicate**: Lambda predicate that returns a boolean. The first element
for which it returns true is returned.
+
+#### Example
+
+```sql
+> select array_first([1, 2, 3, 4], x -> x > 2);
++----------------------------------------+
+| array_first([1,2,3,4],x -> x > 2) |
++----------------------------------------+
+| 3 |
++----------------------------------------+
+```
+
+#### Aliases
+
+- list_first
+
### `array_has`
Returns true if the array contains the element.
@@ -4990,6 +5020,10 @@ _Alias of [array_element](#array_element)._
_Alias of [array_filter](#array_filter)._
+### `list_first`
+
+_Alias of [array_first](#array_first)._
+
### `list_has`
_Alias of [array_has](#array_has)._
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]