Skip to main content

datafusion_functions_nested/
set_ops.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//! [`ScalarUDFImpl`] definitions for array_union, array_intersect and array_distinct functions.
19
20use crate::utils::make_scalar_function;
21use arrow::array::{
22    Array, ArrayRef, GenericListArray, OffsetSizeTrait, UInt32Array, UInt64Array,
23    new_empty_array, new_null_array,
24};
25use arrow::buffer::{NullBuffer, OffsetBuffer};
26use arrow::compute::{concat, take};
27use arrow::datatypes::DataType::{LargeList, List, Null};
28use arrow::datatypes::{DataType, Field, FieldRef};
29use arrow::row::{RowConverter, SortField};
30use datafusion_common::cast::{as_large_list_array, as_list_array};
31use datafusion_common::utils::{ListCoercion, normalize_float_zero};
32use datafusion_common::{
33    Result, assert_eq_or_internal_err, exec_err, internal_err, utils::take_function_args,
34};
35use datafusion_expr::{
36    ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
37    Volatility,
38};
39use datafusion_macros::user_doc;
40use hashbrown::HashSet;
41use std::fmt::{Display, Formatter};
42use std::sync::Arc;
43
44// Create static instances of ScalarUDFs for each function
45make_udf_expr_and_func!(
46    ArrayUnion,
47    array_union,
48    array1 array2,
49    "returns an array of the elements in the union of array1 and array2 without duplicates.",
50    array_union_udf
51);
52
53make_udf_expr_and_func!(
54    ArrayIntersect,
55    array_intersect,
56    first_array second_array,
57    "returns an array of the elements in the intersection of array1 and array2.",
58    array_intersect_udf
59);
60
61make_udf_expr_and_func!(
62    ArrayDistinct,
63    array_distinct,
64    array,
65    "returns distinct values from the array after removing duplicates.",
66    array_distinct_udf
67);
68
69#[user_doc(
70    doc_section(label = "Array Functions"),
71    description = "Returns an array of elements that are present in both arrays (all elements from both arrays) without duplicates.",
72    syntax_example = "array_union(array1, array2)",
73    sql_example = r#"```sql
74> select array_union([1, 2, 3, 4], [5, 6, 3, 4]);
75+----------------------------------------------------+
76| array_union([1, 2, 3, 4], [5, 6, 3, 4]);           |
77+----------------------------------------------------+
78| [1, 2, 3, 4, 5, 6]                                 |
79+----------------------------------------------------+
80> select array_union([1, 2, 3, 4], [5, 6, 7, 8]);
81+----------------------------------------------------+
82| array_union([1, 2, 3, 4], [5, 6, 7, 8]);           |
83+----------------------------------------------------+
84| [1, 2, 3, 4, 5, 6, 7, 8]                           |
85+----------------------------------------------------+
86```"#,
87    argument(
88        name = "array1",
89        description = "Array expression. Can be a constant, column, or function, and any combination of array operators."
90    ),
91    argument(
92        name = "array2",
93        description = "Array expression. Can be a constant, column, or function, and any combination of array operators."
94    )
95)]
96#[derive(Debug, PartialEq, Eq, Hash)]
97pub struct ArrayUnion {
98    signature: Signature,
99    aliases: Vec<String>,
100}
101
102impl Default for ArrayUnion {
103    fn default() -> Self {
104        Self::new()
105    }
106}
107
108impl ArrayUnion {
109    pub fn new() -> Self {
110        Self {
111            signature: Signature::arrays(
112                2,
113                Some(ListCoercion::FixedSizedListToList),
114                Volatility::Immutable,
115            ),
116            aliases: vec![String::from("list_union")],
117        }
118    }
119}
120
121impl ScalarUDFImpl for ArrayUnion {
122    fn name(&self) -> &str {
123        "array_union"
124    }
125
126    fn signature(&self) -> &Signature {
127        &self.signature
128    }
129
130    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
131        let [array1, array2] = take_function_args(self.name(), arg_types)?;
132        match (array1, array2) {
133            (Null, Null) => Ok(DataType::new_list(Null, true)),
134            (Null, dt) | (dt, Null) => Ok(dt.clone()),
135            (dt, _) => Ok(dt.clone()),
136        }
137    }
138
139    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
140        make_scalar_function(array_union_inner)(&args.args)
141    }
142
143    fn aliases(&self) -> &[String] {
144        &self.aliases
145    }
146
147    fn documentation(&self) -> Option<&Documentation> {
148        self.doc()
149    }
150}
151
152#[user_doc(
153    doc_section(label = "Array Functions"),
154    description = "Returns an array of elements in the intersection of array1 and array2.",
155    syntax_example = "array_intersect(array1, array2)",
156    sql_example = r#"```sql
157> select array_intersect([1, 2, 3, 4], [5, 6, 3, 4]);
158+----------------------------------------------------+
159| array_intersect([1, 2, 3, 4], [5, 6, 3, 4]);       |
160+----------------------------------------------------+
161| [3, 4]                                             |
162+----------------------------------------------------+
163> select array_intersect([1, 2, 3, 4], [5, 6, 7, 8]);
164+----------------------------------------------------+
165| array_intersect([1, 2, 3, 4], [5, 6, 7, 8]);       |
166+----------------------------------------------------+
167| []                                                 |
168+----------------------------------------------------+
169```"#,
170    argument(
171        name = "array1",
172        description = "Array expression. Can be a constant, column, or function, and any combination of array operators."
173    ),
174    argument(
175        name = "array2",
176        description = "Array expression. Can be a constant, column, or function, and any combination of array operators."
177    )
178)]
179#[derive(Debug, PartialEq, Eq, Hash)]
180pub struct ArrayIntersect {
181    signature: Signature,
182    aliases: Vec<String>,
183}
184
185impl Default for ArrayIntersect {
186    fn default() -> Self {
187        Self::new()
188    }
189}
190
191impl ArrayIntersect {
192    pub fn new() -> Self {
193        Self {
194            signature: Signature::arrays(
195                2,
196                Some(ListCoercion::FixedSizedListToList),
197                Volatility::Immutable,
198            ),
199            aliases: vec![String::from("list_intersect")],
200        }
201    }
202}
203
204impl ScalarUDFImpl for ArrayIntersect {
205    fn name(&self) -> &str {
206        "array_intersect"
207    }
208
209    fn signature(&self) -> &Signature {
210        &self.signature
211    }
212
213    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
214        let [array1, array2] = take_function_args(self.name(), arg_types)?;
215        match (array1, array2) {
216            (Null, Null) => Ok(DataType::new_list(Null, true)),
217            (Null, dt) | (dt, Null) => Ok(dt.clone()),
218            (dt, _) => Ok(dt.clone()),
219        }
220    }
221
222    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
223        make_scalar_function(array_intersect_inner)(&args.args)
224    }
225
226    fn aliases(&self) -> &[String] {
227        &self.aliases
228    }
229
230    fn documentation(&self) -> Option<&Documentation> {
231        self.doc()
232    }
233}
234
235#[user_doc(
236    doc_section(label = "Array Functions"),
237    description = "Returns distinct values from the array after removing duplicates.",
238    syntax_example = "array_distinct(array)",
239    sql_example = r#"```sql
240> select array_distinct([1, 3, 2, 3, 1, 2, 4]);
241+---------------------------------+
242| array_distinct(List([1,2,3,4])) |
243+---------------------------------+
244| [1, 2, 3, 4]                    |
245+---------------------------------+
246```"#,
247    argument(
248        name = "array",
249        description = "Array expression. Can be a constant, column, or function, and any combination of array operators."
250    )
251)]
252#[derive(Debug, PartialEq, Eq, Hash)]
253pub struct ArrayDistinct {
254    signature: Signature,
255    aliases: Vec<String>,
256}
257
258impl ArrayDistinct {
259    pub fn new() -> Self {
260        Self {
261            signature: Signature::array(Volatility::Immutable),
262            aliases: vec!["list_distinct".to_string()],
263        }
264    }
265}
266
267impl Default for ArrayDistinct {
268    fn default() -> Self {
269        Self::new()
270    }
271}
272
273impl ScalarUDFImpl for ArrayDistinct {
274    fn name(&self) -> &str {
275        "array_distinct"
276    }
277
278    fn signature(&self) -> &Signature {
279        &self.signature
280    }
281
282    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
283        Ok(arg_types[0].clone())
284    }
285
286    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
287        make_scalar_function(array_distinct_inner)(&args.args)
288    }
289
290    fn aliases(&self) -> &[String] {
291        &self.aliases
292    }
293
294    fn documentation(&self) -> Option<&Documentation> {
295        self.doc()
296    }
297}
298
299/// array_distinct SQL function
300/// example: from list [1, 3, 2, 3, 1, 2, 4] to [1, 2, 3, 4]
301fn array_distinct_inner(args: &[ArrayRef]) -> Result<ArrayRef> {
302    let [array] = take_function_args("array_distinct", args)?;
303    match array.data_type() {
304        Null => Ok(Arc::clone(array)),
305        List(field) => {
306            let array = as_list_array(&array)?;
307            general_array_distinct(array, field)
308        }
309        LargeList(field) => {
310            let array = as_large_list_array(&array)?;
311            general_array_distinct(array, field)
312        }
313        arg_type => exec_err!("array_distinct does not support type {arg_type}"),
314    }
315}
316
317#[derive(Debug, PartialEq, Copy, Clone)]
318enum SetOp {
319    Union,
320    Intersect,
321}
322
323impl Display for SetOp {
324    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
325        match self {
326            SetOp::Union => write!(f, "array_union"),
327            SetOp::Intersect => write!(f, "array_intersect"),
328        }
329    }
330}
331
332fn generic_set_lists<OffsetSize: OffsetSizeTrait>(
333    l: &GenericListArray<OffsetSize>,
334    r: &GenericListArray<OffsetSize>,
335    field: Arc<Field>,
336    set_op: SetOp,
337) -> Result<ArrayRef> {
338    if l.is_empty() || l.value_type().is_null() {
339        let field = Arc::new(Field::new_list_field(r.value_type(), true));
340        return general_array_distinct::<OffsetSize>(r, &field);
341    } else if r.is_empty() || r.value_type().is_null() {
342        let field = Arc::new(Field::new_list_field(l.value_type(), true));
343        return general_array_distinct::<OffsetSize>(l, &field);
344    }
345
346    assert_eq_or_internal_err!(
347        l.value_type(),
348        r.value_type(),
349        "{set_op:?} is not implemented for '{l:?}' and '{r:?}'"
350    );
351
352    let converter = RowConverter::new(vec![SortField::new(l.value_type())])?;
353
354    // Normalize -0.0 → +0.0 so RowConverter (which uses IEEE 754 totalOrder
355    // and treats ±0 as distinct) groups them together. Use the normalized
356    // arrays for both row conversion and the final output values.
357    let l_values_norm = normalize_float_zero(l.values());
358    let r_values_norm = normalize_float_zero(r.values());
359
360    // Only convert the visible portion of the values array. For sliced
361    // ListArrays, values() returns the full underlying array but only
362    // elements between the first and last offset are referenced.
363    let l_first = l.offsets()[0].as_usize();
364    let l_len = l.offsets()[l.len()].as_usize() - l_first;
365    let l_values = l_values_norm.slice(l_first, l_len);
366    let rows_l = converter.convert_columns(&[Arc::clone(&l_values)])?;
367
368    let r_first = r.offsets()[0].as_usize();
369    let r_len = r.offsets()[r.len()].as_usize() - r_first;
370    let r_values = r_values_norm.slice(r_first, r_len);
371    let rows_r = converter.convert_columns(&[Arc::clone(&r_values)])?;
372
373    // Indices from the row converter are 0-based in the per-side slice;
374    // concatenating those same slices lets indices map directly into the
375    // combined values array.
376    let combined_values = concat(&[l_values.as_ref(), r_values.as_ref()])?;
377    let r_offset = l_len;
378
379    match set_op {
380        SetOp::Union => generic_set_loop::<OffsetSize, true>(
381            l,
382            r,
383            &rows_l,
384            &rows_r,
385            field,
386            &combined_values,
387            r_offset,
388        ),
389        SetOp::Intersect => generic_set_loop::<OffsetSize, false>(
390            l,
391            r,
392            &rows_l,
393            &rows_r,
394            field,
395            &combined_values,
396            r_offset,
397        ),
398    }
399}
400
401/// Inner loop for set operations, parameterized by const generic to
402/// avoid branching inside the hot loop.
403fn generic_set_loop<OffsetSize: OffsetSizeTrait, const IS_UNION: bool>(
404    l: &GenericListArray<OffsetSize>,
405    r: &GenericListArray<OffsetSize>,
406    rows_l: &arrow::row::Rows,
407    rows_r: &arrow::row::Rows,
408    field: Arc<Field>,
409    combined_values: &ArrayRef,
410    r_offset: usize,
411) -> Result<ArrayRef> {
412    let l_offsets = l.value_offsets();
413    let r_offsets = r.value_offsets();
414    let l_first = l.offsets()[0].as_usize();
415    let r_first = r.offsets()[0].as_usize();
416
417    let mut result_offsets = Vec::with_capacity(l.len() + 1);
418    result_offsets.push(OffsetSize::usize_as(0));
419    let initial_capacity = if IS_UNION {
420        // Union can include all elements from both sides
421        rows_l.num_rows()
422    } else {
423        // Intersect result is bounded by the smaller side
424        rows_l.num_rows().min(rows_r.num_rows())
425    };
426
427    let mut indices: Vec<usize> = Vec::with_capacity(initial_capacity);
428
429    // Reuse hash sets across iterations
430    let mut seen = HashSet::new();
431    let mut lookup_set = HashSet::new();
432    for i in 0..l.len() {
433        let last_offset = *result_offsets.last().unwrap();
434
435        if l.is_null(i) || r.is_null(i) {
436            result_offsets.push(last_offset);
437            continue;
438        }
439
440        let l_start = l_offsets[i].as_usize() - l_first;
441        let l_end = l_offsets[i + 1].as_usize() - l_first;
442        let r_start = r_offsets[i].as_usize() - r_first;
443        let r_end = r_offsets[i + 1].as_usize() - r_first;
444
445        seen.clear();
446
447        if IS_UNION {
448            for idx in l_start..l_end {
449                let row = rows_l.row(idx);
450                if seen.insert(row) {
451                    indices.push(idx);
452                }
453            }
454            for idx in r_start..r_end {
455                let row = rows_r.row(idx);
456                if seen.insert(row) {
457                    indices.push(idx + r_offset);
458                }
459            }
460        } else {
461            let l_len = l_end - l_start;
462            let r_len = r_end - r_start;
463
464            // Select shorter side for lookup, longer side for probing.
465            // Track the probe side's offset into the combined values array.
466            let (lookup_rows, lookup_range, probe_rows, probe_range, probe_offset) =
467                if l_len < r_len {
468                    (rows_l, l_start..l_end, rows_r, r_start..r_end, r_offset)
469                } else {
470                    (rows_r, r_start..r_end, rows_l, l_start..l_end, 0)
471                };
472            lookup_set.clear();
473            lookup_set.reserve(lookup_range.len());
474
475            // Build lookup table
476            for idx in lookup_range {
477                lookup_set.insert(lookup_rows.row(idx));
478            }
479
480            // Probe and emit distinct intersected rows
481            for idx in probe_range {
482                let row = probe_rows.row(idx);
483                if lookup_set.contains(&row) && seen.insert(row) {
484                    indices.push(idx + probe_offset);
485                }
486            }
487        }
488        result_offsets.push(last_offset + OffsetSize::usize_as(seen.len()));
489    }
490
491    // Gather distinct values by index from the combined values array.
492    // Use UInt64Array for LargeList to support values arrays exceeding u32::MAX.
493    let final_values = if indices.is_empty() {
494        new_empty_array(&l.value_type())
495    } else if OffsetSize::IS_LARGE {
496        let indices =
497            UInt64Array::from(indices.into_iter().map(|i| i as u64).collect::<Vec<_>>());
498        take(combined_values.as_ref(), &indices, None)?
499    } else {
500        let indices =
501            UInt32Array::from(indices.into_iter().map(|i| i as u32).collect::<Vec<_>>());
502        take(combined_values.as_ref(), &indices, None)?
503    };
504
505    let arr = GenericListArray::<OffsetSize>::try_new(
506        field,
507        OffsetBuffer::new(result_offsets.into()),
508        final_values,
509        NullBuffer::union(l.nulls(), r.nulls()),
510    )?;
511    Ok(Arc::new(arr))
512}
513
514fn general_set_op(
515    array1: &ArrayRef,
516    array2: &ArrayRef,
517    set_op: SetOp,
518) -> Result<ArrayRef> {
519    let len = array1.len();
520    match (array1.data_type(), array2.data_type()) {
521        (Null, Null) => Ok(new_null_array(&DataType::new_list(Null, true), len)),
522        (Null, dt @ List(_))
523        | (Null, dt @ LargeList(_))
524        | (dt @ List(_), Null)
525        | (dt @ LargeList(_), Null) => Ok(new_null_array(dt, len)),
526        (List(field), List(_)) => {
527            let array1 = as_list_array(&array1)?;
528            let array2 = as_list_array(&array2)?;
529            generic_set_lists::<i32>(array1, array2, Arc::clone(field), set_op)
530        }
531        (LargeList(field), LargeList(_)) => {
532            let array1 = as_large_list_array(&array1)?;
533            let array2 = as_large_list_array(&array2)?;
534            generic_set_lists::<i64>(array1, array2, Arc::clone(field), set_op)
535        }
536        (data_type1, data_type2) => {
537            internal_err!(
538                "{set_op} does not support types '{data_type1:?}' and '{data_type2:?}'"
539            )
540        }
541    }
542}
543
544fn array_union_inner(args: &[ArrayRef]) -> Result<ArrayRef> {
545    let [array1, array2] = take_function_args("array_union", args)?;
546    general_set_op(array1, array2, SetOp::Union)
547}
548
549fn array_intersect_inner(args: &[ArrayRef]) -> Result<ArrayRef> {
550    let [array1, array2] = take_function_args("array_intersect", args)?;
551    general_set_op(array1, array2, SetOp::Intersect)
552}
553
554fn general_array_distinct<OffsetSize: OffsetSizeTrait>(
555    array: &GenericListArray<OffsetSize>,
556    field: &FieldRef,
557) -> Result<ArrayRef> {
558    if array.is_empty() {
559        return Ok(Arc::new(array.clone()) as ArrayRef);
560    }
561    let value_offsets = array.value_offsets();
562    let dt = array.value_type();
563    let mut offsets = Vec::with_capacity(array.len() + 1);
564    offsets.push(OffsetSize::usize_as(0));
565
566    let converter = RowConverter::new(vec![SortField::new(dt.clone())])?;
567
568    // Normalize -0.0 → +0.0 so RowConverter (which uses IEEE 754 totalOrder
569    // and treats ±0 as distinct) groups them together, and so the output
570    // carries the canonical sign.
571    let values_norm = normalize_float_zero(array.values());
572
573    // Only convert the visible portion of the values array. For sliced
574    // ListArrays, values() returns the full underlying array but only
575    // elements between the first and last offset are referenced.
576    let first_offset = value_offsets[0].as_usize();
577    let visible_len = value_offsets[array.len()].as_usize() - first_offset;
578    let rows =
579        converter.convert_columns(&[values_norm.slice(first_offset, visible_len)])?;
580
581    let mut indices: Vec<usize> = Vec::with_capacity(rows.num_rows());
582    let mut seen = HashSet::new();
583    for i in 0..array.len() {
584        let last_offset = *offsets.last().unwrap();
585
586        // Null list entries produce no output; just carry forward the offset.
587        if array.is_null(i) {
588            offsets.push(last_offset);
589            continue;
590        }
591
592        let start = value_offsets[i].as_usize() - first_offset;
593        let end = value_offsets[i + 1].as_usize() - first_offset;
594        seen.clear();
595        seen.reserve(end - start);
596
597        // Walk the sub-array and keep only the first occurrence of each value.
598        for idx in start..end {
599            let row = rows.row(idx);
600            if seen.insert(row) {
601                indices.push(idx + first_offset);
602            }
603        }
604        offsets.push(last_offset + OffsetSize::usize_as(seen.len()));
605    }
606
607    // Gather distinct values in a single pass, using the computed `indices`.
608    // Indices are absolute positions in the (normalized) values array, so we
609    // can take directly from the full values.
610    // Use UInt64Array for LargeList to support values arrays exceeding u32::MAX.
611    let final_values = if indices.is_empty() {
612        new_empty_array(&dt)
613    } else if OffsetSize::IS_LARGE {
614        let indices =
615            UInt64Array::from(indices.into_iter().map(|i| i as u64).collect::<Vec<_>>());
616        take(values_norm.as_ref(), &indices, None)?
617    } else {
618        let indices =
619            UInt32Array::from(indices.into_iter().map(|i| i as u32).collect::<Vec<_>>());
620        take(values_norm.as_ref(), &indices, None)?
621    };
622
623    Ok(Arc::new(GenericListArray::<OffsetSize>::try_new(
624        Arc::clone(field),
625        OffsetBuffer::new(offsets.into()),
626        final_values,
627        // Keep the list nulls
628        array.nulls().cloned(),
629    )?))
630}
631
632#[cfg(test)]
633mod tests {
634    use std::sync::Arc;
635
636    use arrow::{
637        array::{Array, AsArray, Int32Array, ListArray},
638        buffer::OffsetBuffer,
639        datatypes::{DataType, Field, Int32Type},
640    };
641    use datafusion_common::{DataFusionError, Result, config::ConfigOptions};
642    use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl};
643
644    use crate::set_ops::{ArrayDistinct, ArrayIntersect, ArrayUnion, array_distinct_udf};
645
646    /// Build two sliced ListArrays and return them along with the shared list
647    /// field.
648    ///
649    /// l: [[1,2], [3,4], [5,6], [7,8]]  →  slice(1,2)  →  [[3,4], [5,6]]
650    /// r: [[1,3], [3,5], [5,7], [7,1]]  →  slice(1,2)  →  [[3,5], [5,7]]
651    fn make_sliced_pair() -> (ListArray, ListArray, Arc<Field>) {
652        let l = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![
653            Some(vec![Some(1), Some(2)]),
654            Some(vec![Some(3), Some(4)]),
655            Some(vec![Some(5), Some(6)]),
656            Some(vec![Some(7), Some(8)]),
657        ]);
658        let r = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![
659            Some(vec![Some(1), Some(3)]),
660            Some(vec![Some(3), Some(5)]),
661            Some(vec![Some(5), Some(7)]),
662            Some(vec![Some(7), Some(1)]),
663        ]);
664        let field = Arc::new(Field::new("item", l.data_type().clone(), true));
665        (l.slice(1, 2), r.slice(1, 2), field)
666    }
667
668    fn collect_i32_list(list: &ListArray) -> Vec<Vec<i32>> {
669        (0..list.len())
670            .map(|i| {
671                let arr = list.value(i);
672                arr.as_any()
673                    .downcast_ref::<Int32Array>()
674                    .unwrap()
675                    .values()
676                    .to_vec()
677            })
678            .collect()
679    }
680
681    #[test]
682    fn test_array_union_sliced_lists() -> Result<()> {
683        let (l, r, field) = make_sliced_pair();
684
685        let result = ArrayUnion::new().invoke_with_args(ScalarFunctionArgs {
686            args: vec![
687                ColumnarValue::Array(Arc::new(l)),
688                ColumnarValue::Array(Arc::new(r)),
689            ],
690            arg_fields: vec![Arc::clone(&field), Arc::clone(&field)],
691            number_rows: 2,
692            return_field: Arc::clone(&field),
693            config_options: Arc::new(ConfigOptions::default()),
694        })?;
695
696        let output = result.into_array(2)?;
697        let output = output.as_list::<i32>();
698        let rows = collect_i32_list(output);
699
700        // Row 0: union([3,4], [3,5]) = [3,4,5]
701        assert_eq!(rows[0], vec![3, 4, 5]);
702        // Row 1: union([5,6], [5,7]) = [5,6,7]
703        assert_eq!(rows[1], vec![5, 6, 7]);
704        Ok(())
705    }
706
707    #[test]
708    fn test_array_intersect_sliced_lists() -> Result<()> {
709        let (l, r, field) = make_sliced_pair();
710
711        let result = ArrayIntersect::new().invoke_with_args(ScalarFunctionArgs {
712            args: vec![
713                ColumnarValue::Array(Arc::new(l)),
714                ColumnarValue::Array(Arc::new(r)),
715            ],
716            arg_fields: vec![Arc::clone(&field), Arc::clone(&field)],
717            number_rows: 2,
718            return_field: Arc::clone(&field),
719            config_options: Arc::new(ConfigOptions::default()),
720        })?;
721
722        let output = result.into_array(2)?;
723        let output = output.as_list::<i32>();
724        let rows = collect_i32_list(output);
725
726        // Row 0: intersect([3,4], [3,5]) = [3]
727        assert_eq!(rows[0], vec![3]);
728        // Row 1: intersect([5,6], [5,7]) = [5]
729        assert_eq!(rows[1], vec![5]);
730        Ok(())
731    }
732
733    #[test]
734    fn test_array_distinct_sliced_list() -> Result<()> {
735        // [[1,1], [3,3,4], [5,5,6], [7,7]]  →  slice(1,2)  →  [[3,3,4], [5,5,6]]
736        let list = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![
737            Some(vec![Some(1), Some(1)]),
738            Some(vec![Some(3), Some(3), Some(4)]),
739            Some(vec![Some(5), Some(5), Some(6)]),
740            Some(vec![Some(7), Some(7)]),
741        ]);
742        let sliced = list.slice(1, 2);
743        let field = Arc::new(Field::new("item", sliced.data_type().clone(), true));
744
745        let result = ArrayDistinct::new().invoke_with_args(ScalarFunctionArgs {
746            args: vec![ColumnarValue::Array(Arc::new(sliced))],
747            arg_fields: vec![Arc::clone(&field)],
748            number_rows: 2,
749            return_field: field,
750            config_options: Arc::new(ConfigOptions::default()),
751        })?;
752
753        let output = result.into_array(2)?;
754        let output = output.as_list::<i32>();
755        let rows = collect_i32_list(output);
756
757        // Row 0: distinct([3,3,4]) = [3,4]
758        assert_eq!(rows[0], vec![3, 4]);
759        // Row 1: distinct([5,5,6]) = [5,6]
760        assert_eq!(rows[1], vec![5, 6]);
761        Ok(())
762    }
763
764    #[test]
765    fn test_array_distinct_inner_nullability_result_type_match_return_type()
766    -> Result<(), DataFusionError> {
767        let udf = array_distinct_udf();
768
769        for inner_nullable in [true, false] {
770            let inner_field = Field::new_list_field(DataType::Int32, inner_nullable);
771            let input_field =
772                Field::new_list("input", Arc::new(inner_field.clone()), true);
773
774            // [[1, 1, 2]]
775            let input_array = ListArray::new(
776                inner_field.into(),
777                OffsetBuffer::new(vec![0, 3].into()),
778                Arc::new(Int32Array::new(vec![1, 1, 2].into(), None)),
779                None,
780            );
781
782            let input_array = ColumnarValue::Array(Arc::new(input_array));
783
784            let result = udf.invoke_with_args(ScalarFunctionArgs {
785                args: vec![input_array],
786                arg_fields: vec![input_field.clone().into()],
787                number_rows: 1,
788                return_field: input_field.clone().into(),
789                config_options: Arc::new(ConfigOptions::default()),
790            })?;
791
792            assert_eq!(
793                result.data_type(),
794                udf.return_type(&[input_field.data_type().clone()])?
795            );
796        }
797        Ok(())
798    }
799}