1use crate::utils::make_scalar_function;
21use arrow::array::{
22 Array, ArrayRef, Capacities, ListArray, MutableArrayData, NullBufferBuilder,
23 StructArray, new_null_array,
24};
25use arrow::buffer::{NullBuffer, OffsetBuffer};
26use arrow::datatypes::DataType::{FixedSizeList, LargeList, List, Null};
27use arrow::datatypes::{DataType, Field, Fields};
28use datafusion_common::cast::{
29 as_fixed_size_list_array, as_large_list_array, as_list_array,
30};
31use datafusion_common::{Result, exec_err};
32use datafusion_expr::{
33 ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
34 Volatility,
35};
36use datafusion_macros::user_doc;
37use std::sync::Arc;
38
39struct ListColumnView {
42 values: ArrayRef,
44 offsets: Vec<usize>,
46 nulls: Option<NullBuffer>,
48}
49
50impl ListColumnView {
51 fn is_null(&self, idx: usize) -> bool {
52 self.nulls.as_ref().is_some_and(|n| n.is_null(idx))
53 }
54}
55
56make_udf_expr_and_func!(
57 ArraysZip,
58 arrays_zip,
59 "combines one or multiple arrays into a single array of structs.",
60 arrays_zip_udf
61);
62
63#[user_doc(
64 doc_section(label = "Array Functions"),
65 description = "Returns an array of structs created by combining the elements of each input array at the same index. If the arrays have different lengths, shorter arrays are padded with NULLs.",
66 syntax_example = "arrays_zip(array1[, ..., array_n])",
67 sql_example = r#"```sql
68> select arrays_zip([1, 2, 3]);
69+---------------------------------------------------+
70| arrays_zip([1, 2, 3]) |
71+---------------------------------------------------+
72| [{1: 1}, {1: 2}, {1: 3}] |
73+---------------------------------------------------+
74> select arrays_zip([1, 2], [3, 4, 5]);
75+---------------------------------------------------+
76| arrays_zip([1, 2], [3, 4, 5]) |
77+---------------------------------------------------+
78| [{1: 1, 2: 3}, {1: 2, 2: 4}, {1: NULL, 2: 5}] |
79+---------------------------------------------------+
80```"#,
81 argument(name = "array1", description = "First array expression."),
82 argument(
83 name = "array_n",
84 description = "Optional additional array expressions."
85 )
86)]
87#[derive(Debug, PartialEq, Eq, Hash)]
88pub struct ArraysZip {
89 signature: Signature,
90 aliases: Vec<String>,
91}
92
93impl Default for ArraysZip {
94 fn default() -> Self {
95 Self::new()
96 }
97}
98
99impl ArraysZip {
100 pub fn new() -> Self {
101 Self {
102 signature: Signature::variadic_any(Volatility::Immutable),
103 aliases: vec![String::from("list_zip")],
104 }
105 }
106}
107
108impl ScalarUDFImpl for ArraysZip {
109 fn name(&self) -> &str {
110 "arrays_zip"
111 }
112
113 fn signature(&self) -> &Signature {
114 &self.signature
115 }
116
117 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
118 if arg_types.is_empty() {
119 return exec_err!("arrays_zip requires at least one argument");
120 }
121
122 let mut fields = Vec::with_capacity(arg_types.len());
123 for (i, arg_type) in arg_types.iter().enumerate() {
124 let element_type = match arg_type {
125 List(field) | LargeList(field) | FixedSizeList(field, _) => {
126 field.data_type().clone()
127 }
128 Null => Null,
129 dt => {
130 return exec_err!("arrays_zip expects array arguments, got {dt}");
131 }
132 };
133 fields.push(Field::new(arrays_zip_field_name(i), element_type, true));
134 }
135
136 Ok(List(Arc::new(Field::new_list_field(
137 DataType::Struct(Fields::from(fields)),
138 true,
139 ))))
140 }
141
142 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
143 make_scalar_function(arrays_zip_inner)(&args.args)
144 }
145
146 fn aliases(&self) -> &[String] {
147 &self.aliases
148 }
149
150 fn documentation(&self) -> Option<&Documentation> {
151 self.doc()
152 }
153}
154
155fn arrays_zip_inner(args: &[ArrayRef]) -> Result<ArrayRef> {
162 if args.is_empty() {
163 return exec_err!("arrays_zip requires at least one argument");
164 }
165
166 let field_names = arrays_zip_field_names(args.len());
167 let num_rows = args[0].len();
168
169 if let Some(result) = try_perfect_list_zip(args, &field_names)? {
170 return Ok(result);
171 }
172
173 let mut views: Vec<Option<ListColumnView>> = Vec::with_capacity(args.len());
176 let mut element_types: Vec<DataType> = Vec::with_capacity(args.len());
177
178 for (i, arg) in args.iter().enumerate() {
179 match arg.data_type() {
180 List(field) => {
181 let arr = as_list_array(arg)?;
182 let raw_offsets = arr.value_offsets();
183 let offsets: Vec<usize> =
184 raw_offsets.iter().map(|&o| o as usize).collect();
185 element_types.push(field.data_type().clone());
186 views.push(Some(ListColumnView {
187 values: Arc::clone(arr.values()),
188 offsets,
189 nulls: arr.nulls().cloned(),
190 }));
191 }
192 LargeList(field) => {
193 let arr = as_large_list_array(arg)?;
194 let raw_offsets = arr.value_offsets();
195 let offsets: Vec<usize> =
196 raw_offsets.iter().map(|&o| o as usize).collect();
197 element_types.push(field.data_type().clone());
198 views.push(Some(ListColumnView {
199 values: Arc::clone(arr.values()),
200 offsets,
201 nulls: arr.nulls().cloned(),
202 }));
203 }
204 FixedSizeList(field, size) => {
205 let arr = as_fixed_size_list_array(arg)?;
206 let size = *size as usize;
207 let offsets: Vec<usize> = (0..=num_rows).map(|row| row * size).collect();
208 element_types.push(field.data_type().clone());
209 views.push(Some(ListColumnView {
210 values: Arc::clone(arr.values()),
211 offsets,
212 nulls: arr.nulls().cloned(),
213 }));
214 }
215 Null => {
216 element_types.push(Null);
217 views.push(None);
218 }
219 dt => {
220 return exec_err!("arrays_zip argument {i} expected list type, got {dt}");
221 }
222 }
223 }
224
225 let values_data: Vec<_> = views
227 .iter()
228 .map(|v| v.as_ref().map(|view| view.values.to_data()))
229 .collect();
230
231 let struct_fields: Fields = element_types
232 .iter()
233 .zip(field_names.iter())
234 .map(|(dt, name)| Field::new(name.clone(), dt.clone(), true))
235 .collect::<Vec<_>>()
236 .into();
237
238 let mut builders: Vec<Option<MutableArrayData>> = values_data
241 .iter()
242 .map(|vd| {
243 vd.as_ref().map(|data| {
244 MutableArrayData::with_capacities(vec![data], true, Capacities::Array(0))
245 })
246 })
247 .collect();
248
249 let mut offsets: Vec<i32> = Vec::with_capacity(num_rows + 1);
250 offsets.push(0);
251 let mut null_builder = NullBufferBuilder::new(num_rows);
252 let mut total_values: usize = 0;
253
254 for row_idx in 0..num_rows {
257 let mut max_len: usize = 0;
258 let mut all_null = true;
259
260 for view in views.iter().flatten() {
261 if !view.is_null(row_idx) {
262 all_null = false;
263 let len = view.offsets[row_idx + 1] - view.offsets[row_idx];
264 max_len = max_len.max(len);
265 }
266 }
267
268 if all_null {
269 null_builder.append_null();
270 offsets.push(*offsets.last().unwrap());
271 continue;
272 }
273 null_builder.append_non_null();
274
275 for (col_idx, view) in views.iter().enumerate() {
277 match view {
278 Some(v) if !v.is_null(row_idx) => {
279 let start = v.offsets[row_idx];
280 let end = v.offsets[row_idx + 1];
281 let len = end - start;
282 let builder = builders[col_idx].as_mut().unwrap();
283 builder.try_extend(0, start, end)?;
284 if len < max_len {
285 builder.try_extend_nulls(max_len - len)?;
286 }
287 }
288 _ => {
289 if let Some(builder) = builders[col_idx].as_mut() {
291 builder.try_extend_nulls(max_len)?;
292 }
293 }
294 }
295 }
296
297 total_values += max_len;
298 let last = *offsets.last().unwrap();
299 offsets.push(last + max_len as i32);
300 }
301
302 let struct_columns: Vec<ArrayRef> = builders
304 .into_iter()
305 .zip(element_types.iter())
306 .map(|(builder, elem_type)| match builder {
307 Some(b) => arrow::array::make_array(b.freeze()),
308 None => new_null_array(
309 if elem_type.is_null() {
310 &Null
311 } else {
312 elem_type
313 },
314 total_values,
315 ),
316 })
317 .collect();
318
319 let struct_array = StructArray::try_new(struct_fields, struct_columns, None)?;
320
321 let null_buffer = null_builder.finish();
322
323 let result = ListArray::try_new(
324 Arc::new(Field::new_list_field(
325 struct_array.data_type().clone(),
326 true,
327 )),
328 OffsetBuffer::new(offsets.into()),
329 Arc::new(struct_array),
330 null_buffer,
331 )?;
332
333 Ok(Arc::new(result))
334}
335
336fn arrays_zip_field_name(index: usize) -> String {
337 (index + 1).to_string()
338}
339
340fn arrays_zip_field_names(len: usize) -> Vec<String> {
341 (0..len).map(arrays_zip_field_name).collect()
342}
343
344fn try_perfect_list_zip(
348 args: &[ArrayRef],
349 field_names: &[String],
350) -> Result<Option<ArrayRef>> {
351 debug_assert_eq!(args.len(), field_names.len());
352
353 let mut list_arrays = Vec::with_capacity(args.len());
354 let mut struct_fields = Vec::with_capacity(args.len());
355
356 for (arg, field_name) in args.iter().zip(field_names) {
357 let arr = match arg.data_type() {
358 List(field) => {
359 struct_fields.push(Field::new(
360 field_name.clone(),
361 field.data_type().clone(),
362 true,
363 ));
364 as_list_array(arg)?
365 }
366 _ => return Ok(None),
367 };
368
369 list_arrays.push(arr);
370 }
371
372 let first = list_arrays[0];
373 let num_rows = first.len();
374 let offsets = first.offsets().clone();
375 let values_len = first.values().len();
376
377 for arr in &list_arrays {
380 if arr.values().len() != values_len || arr.offsets() != &offsets {
381 return Ok(None);
382 }
383 }
384
385 let nulls = if list_arrays.iter().any(|arr| arr.null_count() != 0) {
386 let first_nulls = first.nulls();
387 if list_arrays.iter().all(|arr| arr.nulls() == first_nulls) {
388 first_nulls.cloned()
389 } else {
390 let mut null_builder = NullBufferBuilder::new(num_rows);
395 for row_idx in 0..num_rows {
396 let mut all_null = true;
397
398 for arr in &list_arrays {
399 if arr.is_null(row_idx) {
400 if arr.offsets()[row_idx + 1] != arr.offsets()[row_idx] {
401 return Ok(None);
402 }
403 } else {
404 all_null = false;
405 }
406 }
407
408 if all_null {
409 null_builder.append_null();
410 } else {
411 null_builder.append_non_null();
412 }
413 }
414
415 null_builder.finish()
416 }
417 } else {
418 None
419 };
420
421 let struct_columns = list_arrays
422 .iter()
423 .map(|arr| Arc::clone(arr.values()))
424 .collect::<Vec<_>>();
425 let struct_array =
426 StructArray::try_new(Fields::from(struct_fields), struct_columns, None)?;
427 let result = ListArray::try_new(
428 Arc::new(Field::new_list_field(
429 struct_array.data_type().clone(),
430 true,
431 )),
432 offsets,
433 Arc::new(struct_array),
434 nulls,
435 )?;
436
437 Ok(Some(Arc::new(result)))
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443 use arrow::array::Int64Array;
444 use arrow::buffer::NullBuffer;
445
446 fn list(values: Vec<i64>, offsets: Vec<i32>) -> Arc<ListArray> {
447 list_with_validity(values, offsets, None)
448 }
449
450 fn list_with_validity(
451 values: Vec<i64>,
452 offsets: Vec<i32>,
453 valid: Option<Vec<bool>>,
454 ) -> Arc<ListArray> {
455 Arc::new(
456 ListArray::try_new(
457 Arc::new(Field::new_list_field(DataType::Int64, true)),
458 OffsetBuffer::new(offsets.into()),
459 Arc::new(Int64Array::from(values)),
460 valid.map(NullBuffer::from),
461 )
462 .unwrap(),
463 )
464 }
465
466 #[test]
467 fn perfect_zip_reuses_input_values_and_offsets() {
468 let left = list(vec![1, 2, 3, 4, 5, 6], vec![0, 2, 3, 6]);
469 let right = list(vec![10, 20, 30, 40, 50, 60], vec![0, 2, 3, 6]);
470
471 let result = arrays_zip_inner(&[
472 Arc::clone(&left) as ArrayRef,
473 Arc::clone(&right) as ArrayRef,
474 ])
475 .unwrap();
476 let result = result.as_any().downcast_ref::<ListArray>().unwrap();
477 let values = result
478 .values()
479 .as_any()
480 .downcast_ref::<StructArray>()
481 .unwrap();
482
483 assert!(result.offsets().ptr_eq(left.offsets()));
484 assert!(Arc::ptr_eq(values.column(0), left.values()));
485 assert!(Arc::ptr_eq(values.column(1), right.values()));
486 }
487
488 #[test]
489 fn perfect_zip_uses_supplied_field_names() {
490 let left = list(vec![1, 2, 3], vec![0, 1, 3]);
491 let right = list(vec![10, 20, 30], vec![0, 1, 3]);
492 let field_names = vec!["left".to_string(), "right".to_string()];
493
494 let result = try_perfect_list_zip(
495 &[
496 Arc::clone(&left) as ArrayRef,
497 Arc::clone(&right) as ArrayRef,
498 ],
499 &field_names,
500 )
501 .unwrap()
502 .unwrap();
503 let result = result.as_any().downcast_ref::<ListArray>().unwrap();
504 let values = result
505 .values()
506 .as_any()
507 .downcast_ref::<StructArray>()
508 .unwrap();
509 let names = values
510 .fields()
511 .iter()
512 .map(|field| field.name().as_str())
513 .collect::<Vec<_>>();
514
515 assert_eq!(names, vec!["left", "right"]);
516 }
517
518 #[test]
519 fn perfect_zip_reuses_zero_length_null_rows() {
520 let left = list_with_validity(
521 vec![1, 2, 3, 4],
522 vec![0, 2, 2, 4],
523 Some(vec![true, false, true]),
524 );
525 let right = list_with_validity(
526 vec![10, 20, 30, 40],
527 vec![0, 2, 2, 4],
528 Some(vec![true, false, true]),
529 );
530
531 let result = arrays_zip_inner(&[
532 Arc::clone(&left) as ArrayRef,
533 Arc::clone(&right) as ArrayRef,
534 ])
535 .unwrap();
536 let result = result.as_any().downcast_ref::<ListArray>().unwrap();
537
538 assert!(result.offsets().ptr_eq(left.offsets()));
539 assert!(result.is_null(1));
540 }
541
542 #[test]
543 fn perfect_zip_preserves_mixed_null_empty_rows() {
544 let left =
545 list_with_validity(vec![], vec![0, 0, 0, 0], Some(vec![false, true, false]));
546 let right =
547 list_with_validity(vec![], vec![0, 0, 0, 0], Some(vec![true, false, false]));
548
549 let result = arrays_zip_inner(&[
550 Arc::clone(&left) as ArrayRef,
551 Arc::clone(&right) as ArrayRef,
552 ])
553 .unwrap();
554 let result = result.as_any().downcast_ref::<ListArray>().unwrap();
555
556 assert!(result.offsets().ptr_eq(left.offsets()));
557 assert!(!result.is_null(0));
558 assert!(!result.is_null(1));
559 assert!(result.is_null(2));
560 }
561
562 #[test]
563 fn perfect_zip_reuses_null_rows_with_hidden_values() {
564 let left =
565 list_with_validity(vec![1, 2, 3, 4], vec![0, 2, 4], Some(vec![true, false]));
566 let right = list_with_validity(
567 vec![10, 20, 30, 40],
568 vec![0, 2, 4],
569 Some(vec![true, false]),
570 );
571
572 let result = arrays_zip_inner(&[
573 Arc::clone(&left) as ArrayRef,
574 Arc::clone(&right) as ArrayRef,
575 ])
576 .unwrap();
577 let result = result.as_any().downcast_ref::<ListArray>().unwrap();
578
579 assert!(result.offsets().ptr_eq(left.offsets()));
580 assert_eq!(result.value_offsets(), &[0, 2, 4]);
581 assert!(result.is_null(1));
582 }
583
584 #[test]
585 fn mixed_null_row_with_hidden_values_uses_general_path() {
586 let left =
587 list_with_validity(vec![1, 2, 3, 4], vec![0, 2, 4], Some(vec![true, false]));
588 let right = list_with_validity(
589 vec![10, 20, 30, 40],
590 vec![0, 2, 4],
591 Some(vec![true, true]),
592 );
593
594 let result = arrays_zip_inner(&[
595 Arc::clone(&left) as ArrayRef,
596 Arc::clone(&right) as ArrayRef,
597 ])
598 .unwrap();
599 let result = result.as_any().downcast_ref::<ListArray>().unwrap();
600 let values = result
601 .values()
602 .as_any()
603 .downcast_ref::<StructArray>()
604 .unwrap();
605
606 assert!(!result.offsets().ptr_eq(left.offsets()));
607 assert_eq!(result.value_offsets(), &[0, 2, 4]);
608 assert!(values.column(0).is_null(2));
609 assert!(values.column(0).is_null(3));
610 assert!(!values.column(1).is_null(2));
611 assert!(!values.column(1).is_null(3));
612 }
613}