Common logic to build a MapArray from decomposed list arrays
(
keys: &[ArrayRef],
values: &[ArrayRef],
keys_data_type: &DataType,
values_data_type: &DataType,
original_len: usize,
nulls_bitmap: Option<arrow::buffer::NullBuffer>,
)
| 565 | |
| 566 | /// Common logic to build a MapArray from decomposed list arrays |
| 567 | fn build_map_array( |
| 568 | keys: &[ArrayRef], |
| 569 | values: &[ArrayRef], |
| 570 | keys_data_type: &DataType, |
| 571 | values_data_type: &DataType, |
| 572 | original_len: usize, |
| 573 | nulls_bitmap: Option<arrow::buffer::NullBuffer>, |
| 574 | ) -> Result<ColumnarValue> { |
| 575 | if keys.len() != values.len() { |
| 576 | return exec_err!("map requires key and value lists to have the same length"); |
| 577 | } |
| 578 | |
| 579 | let mut key_array_vec = vec![]; |
| 580 | let mut value_array_vec = vec![]; |
| 581 | for (k, v) in keys.iter().zip(values.iter()) { |
| 582 | key_array_vec.push(k.as_ref()); |
| 583 | value_array_vec.push(v.as_ref()); |
| 584 | } |
| 585 | |
| 586 | // Build offset buffer that accounts for NULL maps |
| 587 | // For each row, if it's NULL, the offset stays the same (empty range) |
| 588 | // If it's not NULL, the offset advances by the number of entries in that map |
| 589 | // NOTE: MapArray always requires i32 offsets, regardless of input list type |
| 590 | let mut running_offset = 0i32; |
| 591 | let mut offset_buffer = vec![running_offset]; |
| 592 | let mut non_null_idx = 0; |
| 593 | for i in 0..original_len { |
| 594 | let is_null = nulls_bitmap.as_ref().is_some_and(|nulls| nulls.is_null(i)); |
| 595 | if !is_null { |
| 596 | let entry_count = keys[non_null_idx].len(); |
| 597 | // Validate that we won't overflow i32 when converting from potentially i64 offsets |
| 598 | let entry_count_i32 = i32::try_from(entry_count).map_err(|_| { |
| 599 | datafusion_common::DataFusionError::Execution(format!( |
| 600 | "Map offset overflow: entry count {entry_count} at index {i} exceeds i32::MAX", |
| 601 | )) |
| 602 | })?; |
| 603 | running_offset = |
| 604 | running_offset.checked_add(entry_count_i32).ok_or_else(|| { |
| 605 | datafusion_common::DataFusionError::Execution(format!( |
| 606 | "Map offset overflow: cumulative offset exceeds i32::MAX at index {i}", |
| 607 | )) |
| 608 | })?; |
| 609 | non_null_idx += 1; |
| 610 | } |
| 611 | offset_buffer.push(running_offset); |
| 612 | } |
| 613 | |
| 614 | // concatenate all the arrays |
| 615 | // If key_array_vec is empty, it means all maps were NULL (list elements were NULL). |
| 616 | // In this case, we need to create empty arrays with the correct data type. |
| 617 | let (flattened_keys, flattened_values) = if key_array_vec.is_empty() { |
| 618 | // All maps are NULL - create empty arrays |
| 619 | // We need to infer the data type from the original keys/values arrays |
| 620 | let key_type = get_element_type(keys_data_type)?; |
| 621 | let value_type = get_element_type(values_data_type)?; |
| 622 | |
| 623 | ( |
| 624 | arrow::array::new_empty_array(key_type), |
no test coverage detected
searching dependent graphs…