Skip to main content

datafusion_functions_nested/
resize.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_resize function.
19
20use crate::utils::make_scalar_function;
21use arrow::array::{
22    Array, ArrayRef, Capacities, GenericListArray, Int64Array, MutableArrayData,
23    NullBufferBuilder, OffsetSizeTrait, new_null_array,
24};
25use arrow::buffer::OffsetBuffer;
26use arrow::datatypes::DataType;
27use arrow::datatypes::{ArrowNativeType, Field};
28use arrow::datatypes::{
29    DataType::{LargeList, List},
30    FieldRef,
31};
32use datafusion_common::cast::{as_int64_array, as_large_list_array, as_list_array};
33use datafusion_common::utils::ListCoercion;
34use datafusion_common::{Result, ScalarValue, exec_err, internal_datafusion_err};
35use datafusion_expr::{
36    ArrayFunctionArgument, ArrayFunctionSignature, ColumnarValue, Documentation,
37    ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature, Volatility,
38};
39use datafusion_macros::user_doc;
40use std::sync::Arc;
41
42make_udf_expr_and_func!(
43    ArrayResize,
44    array_resize,
45    array size value,
46    "returns an array with the specified size filled with the given value.",
47    array_resize_udf
48);
49
50#[user_doc(
51    doc_section(label = "Array Functions"),
52    description = "Resizes the list to contain size elements.",
53    syntax_example = "array_resize(array, size[, value])",
54    sql_example = r#"```sql
55> select array_resize([1, 2, 3], 5, 0);
56+-------------------------------------+
57| array_resize(List([1,2,3],5,0))     |
58+-------------------------------------+
59| [1, 2, 3, 0, 0]                     |
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(name = "size", description = "New size of given array."),
67    argument(
68        name = "value",
69        description = "If expanding the array, defines the values to fill in. Defaults to null."
70    )
71)]
72#[derive(Debug, PartialEq, Eq, Hash)]
73pub struct ArrayResize {
74    signature: Signature,
75    aliases: Vec<String>,
76}
77
78impl Default for ArrayResize {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl ArrayResize {
85    pub fn new() -> Self {
86        Self {
87            signature: Signature::one_of(
88                vec![
89                    TypeSignature::ArraySignature(ArrayFunctionSignature::Array {
90                        arguments: vec![
91                            ArrayFunctionArgument::Array,
92                            ArrayFunctionArgument::Index,
93                        ],
94                        array_coercion: Some(ListCoercion::FixedSizedListToList),
95                    }),
96                    TypeSignature::ArraySignature(ArrayFunctionSignature::Array {
97                        arguments: vec![
98                            ArrayFunctionArgument::Array,
99                            ArrayFunctionArgument::Index,
100                            ArrayFunctionArgument::Element,
101                        ],
102                        array_coercion: Some(ListCoercion::FixedSizedListToList),
103                    }),
104                ],
105                Volatility::Immutable,
106            ),
107            aliases: vec!["list_resize".to_string()],
108        }
109    }
110}
111
112impl ScalarUDFImpl for ArrayResize {
113    fn name(&self) -> &str {
114        "array_resize"
115    }
116
117    fn signature(&self) -> &Signature {
118        &self.signature
119    }
120
121    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
122        match &arg_types[0] {
123            List(field) => Ok(List(Arc::clone(field))),
124            LargeList(field) => Ok(LargeList(Arc::clone(field))),
125            DataType::Null => {
126                Ok(List(Arc::new(Field::new_list_field(DataType::Int64, true))))
127            }
128            _ => exec_err!(
129                "Not reachable, data_type should be List, LargeList or FixedSizeList"
130            ),
131        }
132    }
133
134    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
135        make_scalar_function(array_resize_inner)(&args.args)
136    }
137
138    fn aliases(&self) -> &[String] {
139        &self.aliases
140    }
141
142    fn documentation(&self) -> Option<&Documentation> {
143        self.doc()
144    }
145}
146
147fn array_resize_inner(arg: &[ArrayRef]) -> Result<ArrayRef> {
148    if arg.len() < 2 || arg.len() > 3 {
149        return exec_err!("array_resize needs two or three arguments");
150    }
151
152    let array = &arg[0];
153
154    // Checks if entire array is null
155    if array.logical_null_count() == array.len() {
156        let return_type = match array.data_type() {
157            List(field) => List(Arc::clone(field)),
158            LargeList(field) => LargeList(Arc::clone(field)),
159            _ => {
160                return exec_err!(
161                    "array_resize does not support type '{:?}'.",
162                    array.data_type()
163                );
164            }
165        };
166        return Ok(new_null_array(&return_type, array.len()));
167    }
168
169    let new_len = as_int64_array(&arg[1])?;
170    let new_element = if arg.len() == 3 {
171        Some(Arc::clone(&arg[2]))
172    } else {
173        None
174    };
175
176    match &arg[0].data_type() {
177        List(field) => {
178            let array = as_list_array(&arg[0])?;
179            general_list_resize::<i32>(array, new_len, field, new_element)
180        }
181        LargeList(field) => {
182            let array = as_large_list_array(&arg[0])?;
183            general_list_resize::<i64>(array, new_len, field, new_element)
184        }
185        array_type => exec_err!("array_resize does not support type '{array_type}'."),
186    }
187}
188
189/// array_resize keep the original array and append the default element to the end
190fn general_list_resize<O: OffsetSizeTrait + TryInto<i64>>(
191    array: &GenericListArray<O>,
192    count_array: &Int64Array,
193    field: &FieldRef,
194    default_element: Option<ArrayRef>,
195) -> Result<ArrayRef> {
196    let data_type = array.value_type();
197
198    let values = array.values();
199    let original_data = values.to_data();
200
201    // Track the largest per-row growth so the uniform-fill fast path can
202    // materialize one reusable fill buffer of the required size.
203    let mut max_extra: usize = 0;
204    let mut output_values_len: usize = 0;
205    for (row_index, offset_window) in array.offsets().windows(2).enumerate() {
206        if array.is_null(row_index) || count_array.is_null(row_index) {
207            continue;
208        }
209        let target_count = count_array.value(row_index).to_usize().ok_or_else(|| {
210            internal_datafusion_err!("array_resize: failed to convert size to usize")
211        })?;
212        output_values_len =
213            output_values_len.checked_add(target_count).ok_or_else(|| {
214                internal_datafusion_err!("array_resize: output size overflow")
215            })?;
216        let current_len = (offset_window[1] - offset_window[0]).to_usize().unwrap();
217        if target_count > current_len {
218            max_extra = max_extra.max(target_count - current_len);
219        }
220    }
221
222    if output_values_len > max_resize_values(&data_type)
223        || O::from_usize(output_values_len).is_none()
224    {
225        return exec_err!(
226            "array_resize: resulting array of {output_values_len} elements exceeds the maximum array size"
227        );
228    }
229
230    // The fast path is valid when at least one row grows and every row would
231    // use the same fill value.
232    let use_bulk_fill = max_extra > 0
233        && match &default_element {
234            None => true,
235            Some(fill_array) => {
236                let len = fill_array.len();
237                let null_count = fill_array.logical_null_count();
238
239                len <= 1
240                    || null_count == len
241                    || (null_count == 0 && {
242                        let first = fill_array.slice(0, 1);
243                        (1..len)
244                            .all(|i| fill_array.slice(i, 1).as_ref() == first.as_ref())
245                    })
246            }
247        };
248
249    if use_bulk_fill {
250        // Fast path: materialize one reusable fill buffer for all grown rows.
251        let fill_scalar = match &default_element {
252            None => ScalarValue::try_from(&data_type)?,
253            Some(fill_array) if fill_array.logical_null_count() == fill_array.len() => {
254                ScalarValue::try_from(&data_type)?
255            }
256            Some(fill_array) => ScalarValue::try_from_array(fill_array.as_ref(), 0)?,
257        };
258        let fill_values = fill_scalar.to_array_of_size(max_extra)?;
259        let default_value_data = fill_values.to_data();
260        build_resized_list(
261            array,
262            count_array,
263            field,
264            &original_data,
265            &default_value_data,
266            output_values_len,
267            |mutable, _, extra_count| Ok(mutable.try_extend(1, 0, extra_count)?),
268        )
269    } else {
270        // Slow path: rows may need different fill values, so append from the
271        // corresponding slot in the input fill array for each grown element.
272        let fill_values = match default_element {
273            Some(fill_values) => fill_values,
274            None => {
275                let null_scalar = ScalarValue::try_from(&data_type)?;
276                null_scalar.to_array_of_size(original_data.len())?
277            }
278        };
279        let default_value_data = fill_values.to_data();
280        build_resized_list(
281            array,
282            count_array,
283            field,
284            &original_data,
285            &default_value_data,
286            output_values_len,
287            |mutable, row_index, extra_count| {
288                for _ in 0..extra_count {
289                    mutable.try_extend(1, row_index, row_index + 1)?;
290                }
291                Ok(())
292            },
293        )
294    }
295}
296
297fn build_resized_list<O, F>(
298    array: &GenericListArray<O>,
299    count_array: &Int64Array,
300    field: &FieldRef,
301    original_data: &arrow::array::ArrayData,
302    default_value_data: &arrow::array::ArrayData,
303    output_values_len: usize,
304    mut append_fill_values: F,
305) -> Result<ArrayRef>
306where
307    O: OffsetSizeTrait + TryInto<i64>,
308    F: FnMut(&mut MutableArrayData, usize, usize) -> Result<()>,
309{
310    let capacity = Capacities::Array(output_values_len);
311    let mut offsets = vec![O::usize_as(0)];
312    let mut mutable = MutableArrayData::with_capacities(
313        vec![original_data, default_value_data],
314        false,
315        capacity,
316    );
317    let mut null_builder = NullBufferBuilder::new(array.len());
318
319    for (row_index, offset_window) in array.offsets().windows(2).enumerate() {
320        if array.is_null(row_index) || count_array.is_null(row_index) {
321            null_builder.append_null();
322            offsets.push(offsets[row_index]);
323            continue;
324        }
325        null_builder.append_non_null();
326
327        let count = count_array.value(row_index).to_usize().ok_or_else(|| {
328            internal_datafusion_err!("array_resize: failed to convert size to usize")
329        })?;
330        let count = O::usize_as(count);
331        let start = offset_window[0];
332        if start + count > offset_window[1] {
333            let extra_count = (start + count - offset_window[1]).to_usize().unwrap();
334            let end = offset_window[1];
335            mutable.try_extend(0, start.to_usize().unwrap(), end.to_usize().unwrap())?;
336            append_fill_values(&mut mutable, row_index, extra_count)?;
337        } else {
338            let end = start + count;
339            mutable.try_extend(0, start.to_usize().unwrap(), end.to_usize().unwrap())?;
340        };
341        offsets.push(offsets[row_index] + count);
342    }
343
344    let data = mutable.freeze();
345
346    Ok(Arc::new(GenericListArray::<O>::try_new(
347        Arc::clone(field),
348        OffsetBuffer::<O>::new(offsets.into()),
349        arrow::array::make_array(data),
350        null_builder.finish(),
351    )?))
352}
353
354/// Largest element count whose eager value buffer stays within `isize::MAX`
355/// bytes, so `array_resize` rejects oversized results instead of panicking.
356/// Only primitive and `FixedSizeBinary` leaves are byte-exact.
357fn max_resize_values(value_type: &DataType) -> usize {
358    let element_width = match value_type {
359        DataType::FixedSizeBinary(size) if *size > 0 => *size as usize,
360        _ => value_type.primitive_width().unwrap_or(size_of::<u128>()),
361    };
362
363    (isize::MAX as usize) / element_width.max(1)
364}
365
366#[cfg(test)]
367mod tests {
368    use super::array_resize_inner;
369    use arrow::array::{
370        ArrayRef, AsArray, FixedSizeBinaryArray, Int64Array, LargeListArray, ListArray,
371    };
372    use arrow::buffer::{NullBuffer, OffsetBuffer, ScalarBuffer};
373    use arrow::datatypes::{DataType, Field, Int32Type, Int64Type};
374    use datafusion_common::Result;
375    use std::sync::Arc;
376
377    #[test]
378    fn test_array_resize_null_size_returns_null() -> Result<()> {
379        let array: ArrayRef =
380            Arc::new(ListArray::from_iter_primitive::<Int32Type, _, _>(vec![
381                Some(vec![Some(1), Some(2), Some(3)]),
382                Some(vec![Some(4), Some(5)]),
383            ]));
384        let size: ArrayRef = Arc::new(Int64Array::new(
385            ScalarBuffer::from(vec![2, 1]),
386            Some(NullBuffer::from(vec![true, false])),
387        ));
388
389        let result = array_resize_inner(&[array, size])?;
390        let expected = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![
391            Some(vec![Some(1), Some(2)]),
392            None,
393        ]);
394
395        assert_eq!(result.as_list::<i32>(), &expected);
396
397        Ok(())
398    }
399
400    #[test]
401    fn test_array_resize_large_size_errors_without_panicking() {
402        let array: ArrayRef =
403            Arc::new(ListArray::from_iter_primitive::<Int64Type, _, _>(vec![
404                Some(vec![Some(1)]),
405            ]));
406        let size: ArrayRef = Arc::new(Int64Array::from(vec![i64::MAX]));
407        let fill: ArrayRef = Arc::new(Int64Array::from(vec![0]));
408
409        let err = array_resize_inner(&[array, size, fill]).unwrap_err();
410        assert!(
411            err.to_string().contains("exceeds the maximum array size"),
412            "unexpected error: {err}"
413        );
414    }
415
416    #[test]
417    fn test_array_resize_fixed_size_binary_large_size_errors_without_panicking() {
418        let values =
419            FixedSizeBinaryArray::try_from_iter(vec![vec![0u8; 32]].into_iter()).unwrap();
420        let elem_field =
421            Arc::new(Field::new_list_field(DataType::FixedSizeBinary(32), true));
422        let offsets = OffsetBuffer::<i64>::new(vec![0i64, 1].into());
423        let array: ArrayRef = Arc::new(LargeListArray::new(
424            elem_field,
425            offsets,
426            Arc::new(values) as ArrayRef,
427            None,
428        ));
429        // Passes the width-16 bound (isize::MAX / 16) but overflows at width 32.
430        let size: ArrayRef = Arc::new(Int64Array::from(vec![400_000_000_000_000_000i64]));
431
432        let err = array_resize_inner(&[array, size]).unwrap_err();
433        assert!(
434            err.to_string().contains("exceeds the maximum array size"),
435            "unexpected error: {err}"
436        );
437    }
438
439    #[test]
440    fn test_array_resize_accumulates_values_across_rows() {
441        // Each row's target (6e17) is individually under the width-8 cap
442        // (isize::MAX / 8), but their sum (1.2e18) exceeds it, so the guard
443        // must reject based on the accumulated total rather than per row.
444        let values = Int64Array::from(vec![1, 2]);
445        let offsets = OffsetBuffer::<i64>::new(vec![0i64, 1, 2].into());
446        let elem_field = Arc::new(Field::new_list_field(DataType::Int64, true));
447        let array: ArrayRef = Arc::new(LargeListArray::new(
448            elem_field,
449            offsets,
450            Arc::new(values) as ArrayRef,
451            None,
452        ));
453        let size: ArrayRef = Arc::new(Int64Array::from(vec![
454            600_000_000_000_000_000i64,
455            600_000_000_000_000_000i64,
456        ]));
457
458        let err = array_resize_inner(&[array, size]).unwrap_err();
459        assert!(
460            err.to_string().contains("1200000000000000000"),
461            "expected accumulated total in error: {err}"
462        );
463        assert!(
464            err.to_string().contains("exceeds the maximum array size"),
465            "unexpected error: {err}"
466        );
467    }
468}