1use arrow::{
21 array::{Array, AsArray, BooleanArray, BooleanBuilder, new_null_array},
22 buffer::NullBuffer,
23 compute::take_arrays,
24 datatypes::{ArrowNativeType, DataType, Field, FieldRef},
25};
26use datafusion_common::{
27 Result, exec_datafusion_err, exec_err, plan_err,
28 utils::{
29 adjust_offsets_for_slice, list_values, list_values_row_number, take_function_args,
30 },
31};
32use datafusion_expr::{
33 ColumnarValue, Documentation, HigherOrderFunctionArgs, HigherOrderReturnFieldArgs,
34 HigherOrderSignature, HigherOrderUDFImpl, LambdaParametersProgress, ValueOrLambda,
35 Volatility,
36};
37use datafusion_macros::user_doc;
38use std::{fmt::Debug, sync::Arc};
39
40use crate::lambda_utils::coerce_single_list_arg;
41
42make_higher_order_function_expr_and_func!(
43 ArrayAnyMatch,
44 array_any_match,
45 array lambda,
46 "returns true if any element in the array satisfies the predicate",
47 array_any_match_higher_order_function
48);
49
50#[user_doc(
51 doc_section(label = "Array Functions"),
52 description = "Returns whether any elements of an array match the given predicate. Returns true if one or more elements match, false if none match (including empty arrays), and null if the predicate returns null for some elements and false for all others.",
53 syntax_example = "any_match(array, predicate)",
54 sql_example = r#"```sql
55> select any_match([1, 2, 3], x -> x > 2);
56+----------------------------------+
57| any_match([1, 2, 3], x -> x > 2) |
58+----------------------------------+
59| true |
60+----------------------------------+
61```"#,
62 argument(
63 name = "array",
64 description = "Array expression. Can be a constant, column, or function, and any combination of array operators."
65 ),
66 argument(
67 name = "predicate",
68 description = "Lambda predicate that returns a boolean"
69 )
70)]
71#[derive(Debug, PartialEq, Eq, Hash)]
72pub struct ArrayAnyMatch {
73 signature: HigherOrderSignature,
74 aliases: Vec<String>,
75}
76
77impl Default for ArrayAnyMatch {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl ArrayAnyMatch {
84 pub fn new() -> Self {
85 Self {
86 signature: HigherOrderSignature::exact(
87 vec![ValueOrLambda::Value(()), ValueOrLambda::Lambda(())],
88 Volatility::Immutable,
89 ),
90 aliases: vec![String::from("any_match"), String::from("list_any_match")],
91 }
92 }
93}
94
95fn any_match_for_range(
99 predicate: &BooleanArray,
100 start: usize,
101 end: usize,
102) -> Option<bool> {
103 let any_true = (start..end).any(|j| predicate.is_valid(j) && predicate.value(j));
104 if any_true {
105 return Some(true);
106 }
107 let any_null = (start..end).any(|j| predicate.is_null(j));
108 if any_null { None } else { Some(false) }
109}
110
111impl HigherOrderUDFImpl for ArrayAnyMatch {
112 fn name(&self) -> &str {
113 "array_any_match"
114 }
115
116 fn aliases(&self) -> &[String] {
117 &self.aliases
118 }
119
120 fn signature(&self) -> &HigherOrderSignature {
121 &self.signature
122 }
123
124 fn coerce_value_types(&self, arg_types: &[DataType]) -> Result<Vec<DataType>> {
125 coerce_single_list_arg(self.name(), arg_types)
126 }
127
128 fn lambda_parameters(
129 &self,
130 _step: usize,
131 fields: &[ValueOrLambda<FieldRef, Option<FieldRef>>],
132 ) -> Result<LambdaParametersProgress> {
133 let [list, _] = take_function_args(self.name(), fields)?;
134 let ValueOrLambda::Value(list) = list else {
135 return plan_err!("{} expects a value as first argument", self.name());
136 };
137
138 let field = match list.data_type() {
139 DataType::List(field) => field,
140 DataType::LargeList(field) => field,
141 other => return plan_err!("expected list, got {other}"),
142 };
143
144 Ok(LambdaParametersProgress::Complete(vec![vec![Arc::clone(
145 field,
146 )]]))
147 }
148
149 fn return_field_from_args(
150 &self,
151 args: HigherOrderReturnFieldArgs,
152 ) -> Result<Arc<Field>> {
153 let [ValueOrLambda::Value(list), _] =
154 take_function_args(self.name(), args.arg_fields)?
155 else {
156 return plan_err!("{} expects a value as first argument", self.name());
157 };
158 let nullable = list.is_nullable();
159 Ok(Arc::new(Field::new("", DataType::Boolean, nullable)))
160 }
161
162 fn invoke_with_args(&self, args: HigherOrderFunctionArgs) -> Result<ColumnarValue> {
163 let [ValueOrLambda::Value(list), ValueOrLambda::Lambda(lambda)] =
164 take_function_args(self.name(), &args.args)?
165 else {
166 return exec_err!("{} expects a value followed by a lambda", self.name());
167 };
168
169 let list_array = list.to_array(args.number_rows)?;
170
171 if list_array.null_count() == list_array.len() {
174 return Ok(ColumnarValue::Array(new_null_array(
175 args.return_type(),
176 list_array.len(),
177 )));
178 }
179
180 let list_values = list_values(&list_array)?;
181
182 let values_param = || Ok(Arc::clone(&list_values));
183
184 let predicate_results = lambda
185 .evaluate(&[&values_param], |arrays| {
186 let indices = list_values_row_number(&list_array)?;
187 Ok(take_arrays(arrays, &indices, None)?)
188 })?
189 .into_array(list_values.len())?;
190
191 let predicate_bool = predicate_results
192 .as_any()
193 .downcast_ref::<BooleanArray>()
194 .ok_or_else(|| {
195 exec_datafusion_err!(
196 "{} predicate must return boolean array",
197 self.name()
198 )
199 })?;
200
201 let mut values = BooleanBuilder::with_capacity(list_array.len());
202
203 macro_rules! process_list {
206 ($list_typed:expr) => {{
207 let offsets = adjust_offsets_for_slice($list_typed);
208 for i in 0..$list_typed.len() {
209 let start = offsets[i].as_usize();
210 let end = offsets[i + 1].as_usize();
211 values.append_option(any_match_for_range(predicate_bool, start, end));
215 }
216 }};
217 }
218
219 match list_array.data_type() {
220 DataType::List(_) => {
221 process_list!(list_array.as_list::<i32>());
222 }
223 DataType::LargeList(_) => {
224 process_list!(list_array.as_list::<i64>());
225 }
226 other => return exec_err!("expected list, got {other}"),
227 }
228
229 let (boolean_buffer, predicate_nulls) = values.finish().into_parts();
230 let nulls = NullBuffer::union(list_array.nulls(), predicate_nulls.as_ref());
232 Ok(ColumnarValue::Array(Arc::new(BooleanArray::new(
233 boolean_buffer,
234 nulls,
235 ))))
236 }
237
238 fn documentation(&self) -> Option<&Documentation> {
239 self.doc()
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use std::{collections::HashMap, sync::Arc};
246
247 use arrow::{
248 array::{ArrayRef, BooleanArray, Int32Array, ListArray, RecordBatch},
249 buffer::{NullBuffer, OffsetBuffer},
250 datatypes::{DataType, Field},
251 };
252 use datafusion_common::{DFSchema, Result};
253 use datafusion_expr::{
254 Expr, col,
255 execution_props::ExecutionProps,
256 expr::{HigherOrderFunction, LambdaVariable},
257 lambda, lit,
258 };
259 use datafusion_physical_expr::create_physical_expr;
260
261 use crate::array_any_match::array_any_match_higher_order_function;
262
263 fn run_any_match(
264 list: impl arrow::array::Array + Clone + 'static,
265 ) -> Result<ArrayRef> {
266 let schema = DFSchema::from_unqualified_fields(
267 vec![Field::new(
268 "list",
269 list.data_type().clone(),
270 list.is_nullable(),
271 )]
272 .into(),
273 HashMap::new(),
274 )?;
275
276 create_physical_expr(
277 &Expr::HigherOrderFunction(HigherOrderFunction::new(
278 array_any_match_higher_order_function(),
279 vec![
280 col("list"),
281 lambda(
282 ["x"],
283 Expr::LambdaVariable(LambdaVariable::new(
284 "x".to_string(),
285 Some(Arc::new(Field::new("x", DataType::Int32, true))),
286 ))
287 .gt(lit(2i32)),
288 ),
289 ],
290 )),
291 &schema,
292 &ExecutionProps::new(),
293 )?
294 .evaluate(&RecordBatch::try_new(
295 Arc::clone(schema.inner()),
296 vec![Arc::new(list.clone())],
297 )?)?
298 .into_array(list.len())
299 }
300
301 fn run_any_match_div(
302 list: impl arrow::array::Array + Clone + 'static,
303 ) -> Result<ArrayRef> {
304 let schema = DFSchema::from_unqualified_fields(
305 vec![Field::new(
306 "list",
307 list.data_type().clone(),
308 list.is_nullable(),
309 )]
310 .into(),
311 HashMap::new(),
312 )?;
313
314 let x = Expr::LambdaVariable(LambdaVariable::new(
315 "x".to_string(),
316 Some(Arc::new(Field::new("x", DataType::Int32, true))),
317 ));
318 create_physical_expr(
320 &Expr::HigherOrderFunction(HigherOrderFunction::new(
321 array_any_match_higher_order_function(),
322 vec![col("list"), lambda(["x"], (lit(100i32) / x).gt(lit(5i32)))],
323 )),
324 &schema,
325 &ExecutionProps::new(),
326 )?
327 .evaluate(&RecordBatch::try_new(
328 Arc::clone(schema.inner()),
329 vec![Arc::new(list.clone())],
330 )?)?
331 .into_array(list.len())
332 }
333
334 fn make_list(values: Vec<i32>, offsets: OffsetBuffer<i32>) -> ListArray {
335 make_list_with_nulls(values, offsets, None)
336 }
337
338 fn make_list_with_nulls(
339 values: Vec<i32>,
340 offsets: OffsetBuffer<i32>,
341 nulls: Option<NullBuffer>,
342 ) -> ListArray {
343 ListArray::new(
344 Arc::new(Field::new_list_field(DataType::Int32, true)),
345 offsets,
346 Arc::new(Int32Array::from(values)),
347 nulls,
348 )
349 }
350
351 #[test]
352 fn test_any_match_some_true() -> Result<()> {
353 let list = make_list(vec![1, 2, 3], OffsetBuffer::from_lengths(vec![3]));
354 let result = run_any_match(list)?;
355 assert_eq!(
356 result.as_any().downcast_ref::<BooleanArray>().unwrap(),
357 &BooleanArray::from(vec![Some(true)])
358 );
359 Ok(())
360 }
361
362 #[test]
363 fn test_any_match_none_true() -> Result<()> {
364 let list = make_list(vec![1, 2], OffsetBuffer::from_lengths(vec![2]));
365 let result = run_any_match(list)?;
366 assert_eq!(
367 result.as_any().downcast_ref::<BooleanArray>().unwrap(),
368 &BooleanArray::from(vec![Some(false)])
369 );
370 Ok(())
371 }
372
373 #[test]
374 fn test_any_match_empty_array() -> Result<()> {
375 let list = make_list(vec![], OffsetBuffer::from_lengths(vec![0]));
376 let result = run_any_match(list)?;
377 assert_eq!(
378 result.as_any().downcast_ref::<BooleanArray>().unwrap(),
379 &BooleanArray::from(vec![Some(false)])
380 );
381 Ok(())
382 }
383
384 #[test]
385 fn test_any_match_multiple_rows() -> Result<()> {
386 let list = make_list(vec![1, 2, 3, 1, 2], OffsetBuffer::from_lengths(vec![3, 2]));
387 let result = run_any_match(list)?;
388 assert_eq!(
389 result.as_any().downcast_ref::<BooleanArray>().unwrap(),
390 &BooleanArray::from(vec![Some(true), Some(false)])
391 );
392 Ok(())
393 }
394
395 #[test]
398 fn test_any_match_should_not_evaluate_predicate_on_values_underlying_null()
399 -> Result<()> {
400 let list = make_list_with_nulls(
401 vec![1, 2, 10, 1, 2],
402 OffsetBuffer::from_lengths(vec![3, 2]),
403 Some(NullBuffer::from(vec![false, true])),
404 );
405 let result = run_any_match(list)?;
406 assert_eq!(
407 result.as_any().downcast_ref::<BooleanArray>().unwrap(),
408 &BooleanArray::from(vec![None, Some(false)])
409 );
410 Ok(())
411 }
412
413 #[test]
416 fn test_any_match_on_sliced_list_should_not_evaluate_on_unreachable_values()
417 -> Result<()> {
418 let list = make_list(
419 vec![10, 1, 2, 1, 2],
420 OffsetBuffer::from_lengths(vec![1, 2, 2]),
421 )
422 .slice(1, 2);
423 let result = run_any_match(list)?;
424 assert_eq!(
425 result.as_any().downcast_ref::<BooleanArray>().unwrap(),
426 &BooleanArray::from(vec![Some(false), Some(false)])
427 );
428 Ok(())
429 }
430
431 #[test]
433 fn test_any_match_does_not_evaluate_predicate_on_null_row_values() -> Result<()> {
434 let list = make_list_with_nulls(
435 vec![1, 2, 0, 4, 5],
436 OffsetBuffer::from_lengths(vec![3, 2]),
437 Some(NullBuffer::from(vec![false, true])),
438 );
439 let result = run_any_match_div(list)?;
440 assert_eq!(
441 result.as_any().downcast_ref::<BooleanArray>().unwrap(),
442 &BooleanArray::from(vec![None, Some(true)])
443 );
444 Ok(())
445 }
446
447 #[test]
449 fn test_any_match_does_not_evaluate_predicate_on_unreachable_values() -> Result<()> {
450 let list = make_list(
451 vec![0, 4, 5, 50, 100],
452 OffsetBuffer::from_lengths(vec![1, 2, 2]),
453 )
454 .slice(1, 2);
455 let result = run_any_match_div(list)?;
456 assert_eq!(
457 result.as_any().downcast_ref::<BooleanArray>().unwrap(),
458 &BooleanArray::from(vec![Some(true), Some(false)])
459 );
460 Ok(())
461 }
462}