Skip to main content

datafusion_functions_nested/
map.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
18use std::collections::VecDeque;
19use std::hash::Hash;
20use std::sync::Arc;
21
22use arrow::array::{
23    Array, ArrayData, ArrayRef, ArrowPrimitiveType, MapArray, OffsetSizeTrait,
24    StructArray, cast::AsArray,
25};
26use arrow::buffer::Buffer;
27use arrow::datatypes::{
28    DataType, Date32Type, Date64Type, Field, Int8Type, Int16Type, Int32Type, Int64Type,
29    SchemaBuilder, ToByteSlice, UInt8Type, UInt16Type, UInt32Type, UInt64Type,
30};
31
32use datafusion_common::utils::{fixed_size_list_to_arrays, list_to_arrays};
33use datafusion_common::{
34    HashSet, Result, ScalarValue, exec_err, utils::take_function_args,
35};
36use datafusion_expr::expr::ScalarFunction;
37use datafusion_expr::{
38    ColumnarValue, Documentation, Expr, ScalarFunctionArgs, ScalarUDFImpl, Signature,
39    Volatility,
40};
41use datafusion_macros::user_doc;
42
43use crate::make_array::make_array;
44
45/// Returns a map created from a key list and a value list
46pub fn map(keys: Vec<Expr>, values: Vec<Expr>) -> Expr {
47    let keys = make_array(keys);
48    let values = make_array(values);
49    Expr::ScalarFunction(ScalarFunction::new_udf(map_udf(), vec![keys, values]))
50}
51
52create_func!(MapFunc, map_udf);
53
54/// Check if we can evaluate the expr to constant directly.
55///
56/// # Example
57/// ```sql
58/// SELECT make_map('type', 'test') from test
59/// ```
60/// We can evaluate the result of `make_map` directly.
61fn can_evaluate_to_const(args: &[ColumnarValue]) -> bool {
62    args.iter()
63        .all(|arg| matches!(arg, ColumnarValue::Scalar(_)))
64}
65
66fn into_array_and_type(
67    arg: ColumnarValue,
68    rows: usize,
69    expand_scalar: bool,
70) -> Result<(ArrayRef, DataType)> {
71    let data_type = arg.data_type();
72    let array = if expand_scalar {
73        arg.into_array(rows)?
74    } else {
75        get_first_array_ref(&arg)?
76    };
77
78    Ok((array, data_type))
79}
80
81fn make_map_batch(args: Vec<ColumnarValue>, number_rows: usize) -> Result<ColumnarValue> {
82    let can_evaluate_to_const = can_evaluate_to_const(&args);
83    let [keys_arg, values_arg] = take_function_args("make_map", args)?;
84    let expand_scalar = !can_evaluate_to_const;
85
86    let (keys, keys_data_type) =
87        into_array_and_type(keys_arg, number_rows, expand_scalar)?;
88    let (values, _) = into_array_and_type(values_arg, number_rows, expand_scalar)?;
89
90    validate_map_keys_for_data_type(&keys, &keys_data_type, can_evaluate_to_const)?;
91
92    make_map_batch_internal(&keys, &values, can_evaluate_to_const, &keys_data_type)
93}
94
95fn validate_unique_primitive_keys<T: ArrowPrimitiveType>(array: &dyn Array) -> Result<()>
96where
97    T::Native: Copy + Eq + Hash + std::fmt::Display,
98{
99    let primitive_array = array.as_primitive::<T>();
100    if primitive_array.null_count() > 0 {
101        return exec_err!("map key cannot be null");
102    }
103
104    if let Some(value) = find_duplicate_value(
105        primitive_array.len(),
106        primitive_array.values().iter().copied(),
107    ) {
108        return exec_err!("map key must be unique, duplicate key found: {}", value);
109    }
110
111    Ok(())
112}
113
114fn validate_unique_str_keys<'a>(
115    null_count: usize,
116    len: usize,
117    values: impl IntoIterator<Item = &'a str>,
118) -> Result<()> {
119    if null_count > 0 {
120        return exec_err!("map key cannot be null");
121    }
122
123    if let Some(value) = find_duplicate_value(len, values) {
124        return exec_err!("map key must be unique, duplicate key found: {}", value);
125    }
126
127    Ok(())
128}
129
130fn validate_unique_binary_keys<'a>(
131    null_count: usize,
132    len: usize,
133    values: impl IntoIterator<Item = &'a [u8]>,
134) -> Result<()> {
135    if null_count > 0 {
136        return exec_err!("map key cannot be null");
137    }
138
139    if let Some(value) = find_duplicate_value(len, values) {
140        return exec_err!("map key must be unique, duplicate key found: {:?}", value);
141    }
142
143    Ok(())
144}
145
146fn find_duplicate_value<T, I>(len: usize, values: I) -> Option<T>
147where
148    T: Copy + Eq + Hash,
149    I: IntoIterator<Item = T>,
150{
151    let mut seen_keys = HashSet::with_capacity(len);
152    values.into_iter().find(|value| !seen_keys.insert(*value))
153}
154
155fn validate_unique_keys_generic(array: &dyn Array) -> Result<()> {
156    let mut seen_keys = HashSet::with_capacity(array.len());
157
158    for i in 0..array.len() {
159        let key = ScalarValue::try_from_array(array, i)?;
160
161        // Validation 1: Map keys cannot be null
162        if key.is_null() {
163            return exec_err!("map key cannot be null");
164        }
165
166        // Validation 2: Map keys must be unique
167        if seen_keys.contains(&key) {
168            return exec_err!("map key must be unique, duplicate key found: {}", key);
169        }
170        seen_keys.insert(key);
171    }
172    Ok(())
173}
174
175/// Validates that map keys are non-null and unique.
176fn validate_map_keys(array: &dyn Array) -> Result<()> {
177    match array.data_type() {
178        DataType::Int8 => validate_unique_primitive_keys::<Int8Type>(array),
179        DataType::Int16 => validate_unique_primitive_keys::<Int16Type>(array),
180        DataType::Int32 => validate_unique_primitive_keys::<Int32Type>(array),
181        DataType::Int64 => validate_unique_primitive_keys::<Int64Type>(array),
182        DataType::UInt8 => validate_unique_primitive_keys::<UInt8Type>(array),
183        DataType::UInt16 => validate_unique_primitive_keys::<UInt16Type>(array),
184        DataType::UInt32 => validate_unique_primitive_keys::<UInt32Type>(array),
185        DataType::UInt64 => validate_unique_primitive_keys::<UInt64Type>(array),
186        DataType::Date32 => validate_unique_primitive_keys::<Date32Type>(array),
187        DataType::Date64 => validate_unique_primitive_keys::<Date64Type>(array),
188        DataType::Utf8 => {
189            let arr = array.as_string::<i32>();
190            validate_unique_str_keys(arr.null_count(), arr.len(), arr.iter().flatten())
191        }
192        DataType::LargeUtf8 => {
193            let arr = array.as_string::<i64>();
194            validate_unique_str_keys(arr.null_count(), arr.len(), arr.iter().flatten())
195        }
196        DataType::Utf8View => {
197            let arr = array.as_string_view();
198            validate_unique_str_keys(arr.null_count(), arr.len(), arr.iter().flatten())
199        }
200        DataType::Binary => {
201            let arr = array.as_binary::<i32>();
202            validate_unique_binary_keys(arr.null_count(), arr.len(), arr.iter().flatten())
203        }
204        DataType::LargeBinary => {
205            let arr = array.as_binary::<i64>();
206            validate_unique_binary_keys(arr.null_count(), arr.len(), arr.iter().flatten())
207        }
208        DataType::BinaryView => {
209            let arr = array.as_binary_view();
210            validate_unique_binary_keys(arr.null_count(), arr.len(), arr.iter().flatten())
211        }
212        _ => validate_unique_keys_generic(array),
213    }
214}
215
216fn validate_map_keys_for_data_type(
217    keys: &ArrayRef,
218    keys_data_type: &DataType,
219    can_evaluate_to_const: bool,
220) -> Result<()> {
221    if can_evaluate_to_const {
222        return validate_map_keys(keys.as_ref());
223    }
224
225    match keys_data_type {
226        DataType::List(_) => keys
227            .as_list::<i32>()
228            .iter()
229            .flatten()
230            .try_for_each(|row| validate_map_keys(row.as_ref())),
231        DataType::LargeList(_) => keys
232            .as_list::<i64>()
233            .iter()
234            .flatten()
235            .try_for_each(|row| validate_map_keys(row.as_ref())),
236        DataType::FixedSizeList(_, _) => keys
237            .as_fixed_size_list()
238            .iter()
239            .flatten()
240            .try_for_each(|row| validate_map_keys(row.as_ref())),
241        data_type => exec_err!(
242            "Expected list, large_list or fixed_size_list, got {:?}",
243            data_type
244        ),
245    }
246}
247
248fn get_first_array_ref(columnar_value: &ColumnarValue) -> Result<ArrayRef> {
249    match columnar_value {
250        ColumnarValue::Scalar(value) => match value {
251            ScalarValue::List(array) => Ok(array.value(0)),
252            ScalarValue::LargeList(array) => Ok(array.value(0)),
253            ScalarValue::FixedSizeList(array) => Ok(array.value(0)),
254            _ => exec_err!("Expected array, got {}", value),
255        },
256        ColumnarValue::Array(array) => Ok(array.to_owned()),
257    }
258}
259
260fn make_map_batch_internal(
261    keys: &ArrayRef,
262    values: &ArrayRef,
263    can_evaluate_to_const: bool,
264    data_type: &DataType,
265) -> Result<ColumnarValue> {
266    if keys.len() != values.len() {
267        return exec_err!("map requires key and value lists to have the same length");
268    }
269
270    // Use the array path (make_map_array_internal) in these cases:
271    // 1. Not const evaluation (!can_evaluate_to_const) - allows scalar elimination optimization
272    // 2. NULL maps present (keys.null_count() > 0) - fast path doesn't handle NULL list elements
273    if !can_evaluate_to_const || keys.null_count() > 0 {
274        return match data_type {
275            DataType::LargeList(..) => make_map_array_internal::<i64>(keys, values),
276            DataType::List(..) => make_map_array_internal::<i32>(keys, values),
277            DataType::FixedSizeList(..) => {
278                // FixedSizeList doesn't use OffsetSizeTrait, so handle it separately
279                make_map_array_from_fixed_size_list(keys, values)
280            }
281            _ => exec_err!(
282                "Expected List, LargeList, or FixedSizeList, got {:?}",
283                data_type
284            ),
285        };
286    }
287
288    let key_field = Arc::new(Field::new("key", keys.data_type().clone(), false));
289    let value_field = Arc::new(Field::new("value", values.data_type().clone(), true));
290    let mut entry_struct_buffer: VecDeque<(Arc<Field>, ArrayRef)> = VecDeque::new();
291    let mut entry_offsets_buffer = VecDeque::new();
292    entry_offsets_buffer.push_back(0);
293
294    entry_struct_buffer.push_back((Arc::clone(&key_field), Arc::clone(keys)));
295    entry_struct_buffer.push_back((Arc::clone(&value_field), Arc::clone(values)));
296    entry_offsets_buffer.push_back(keys.len() as u32);
297
298    let entry_struct: Vec<(Arc<Field>, ArrayRef)> = entry_struct_buffer.into();
299    let entry_struct = StructArray::from(entry_struct);
300
301    let map_data_type = DataType::Map(
302        Arc::new(Field::new(
303            "entries",
304            entry_struct.data_type().clone(),
305            false,
306        )),
307        false,
308    );
309
310    let entry_offsets: Vec<u32> = entry_offsets_buffer.into();
311    let entry_offsets_buffer = Buffer::from(entry_offsets.to_byte_slice());
312
313    let map_data = ArrayData::builder(map_data_type)
314        .len(entry_offsets.len() - 1)
315        .add_buffer(entry_offsets_buffer)
316        .add_child_data(entry_struct.to_data())
317        .build()?;
318    let map_array = Arc::new(MapArray::from(map_data));
319
320    Ok(if can_evaluate_to_const {
321        ColumnarValue::Scalar(ScalarValue::try_from_array(map_array.as_ref(), 0)?)
322    } else {
323        ColumnarValue::Array(map_array)
324    })
325}
326
327#[user_doc(
328    doc_section(label = "Map Functions"),
329    description = "Returns an Arrow map with the specified key-value pairs.\n\n\
330    The `make_map` function creates a map from two lists: one for keys and one for values. Each key must be unique and non-null.",
331    syntax_example = "map(key, value)\nmap {key: value}\nmake_map(['key1', 'key2'], ['value1', 'value2'])",
332    sql_example = r#"
333```sql
334-- Using map function
335SELECT MAP('type', 'test');
336----
337{type: test}
338
339SELECT MAP(['POST', 'HEAD', 'PATCH'], [41, 33, null]);
340----
341{POST: 41, HEAD: 33, PATCH: NULL}
342
343SELECT MAP([[1,2], [3,4]], ['a', 'b']);
344----
345{[1, 2]: a, [3, 4]: b}
346
347SELECT MAP { 'a': 1, 'b': 2 };
348----
349{a: 1, b: 2}
350
351-- Using make_map function
352SELECT MAKE_MAP(['POST', 'HEAD'], [41, 33]);
353----
354{POST: 41, HEAD: 33}
355
356SELECT MAKE_MAP(['key1', 'key2'], ['value1', null]);
357----
358{key1: value1, key2: }
359```"#,
360    argument(
361        name = "key",
362        description = "For `map`: Expression to be used for key. Can be a constant, column, function, or any combination of arithmetic or string operators.\n\
363                        For `make_map`: The list of keys to be used in the map. Each key must be unique and non-null."
364    ),
365    argument(
366        name = "value",
367        description = "For `map`: Expression to be used for value. Can be a constant, column, function, or any combination of arithmetic or string operators.\n\
368                        For `make_map`: The list of values to be mapped to the corresponding keys."
369    )
370)]
371#[derive(Debug, PartialEq, Eq, Hash)]
372pub struct MapFunc {
373    signature: Signature,
374}
375
376impl Default for MapFunc {
377    fn default() -> Self {
378        Self::new()
379    }
380}
381
382impl MapFunc {
383    pub fn new() -> Self {
384        Self {
385            signature: Signature::variadic_any(Volatility::Immutable),
386        }
387    }
388}
389
390impl ScalarUDFImpl for MapFunc {
391    fn name(&self) -> &str {
392        "map"
393    }
394
395    fn signature(&self) -> &Signature {
396        &self.signature
397    }
398
399    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
400        let [keys_arg, values_arg] = take_function_args(self.name(), arg_types)?;
401        let mut builder = SchemaBuilder::new();
402        builder.push(Field::new(
403            "key",
404            get_element_type(keys_arg)?.clone(),
405            false,
406        ));
407        builder.push(Field::new(
408            "value",
409            get_element_type(values_arg)?.clone(),
410            true,
411        ));
412        let fields = builder.finish().fields;
413        Ok(DataType::Map(
414            Arc::new(Field::new("entries", DataType::Struct(fields), false)),
415            false,
416        ))
417    }
418
419    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
420        make_map_batch(args.args, args.number_rows)
421    }
422
423    fn documentation(&self) -> Option<&Documentation> {
424        self.doc()
425    }
426}
427
428fn get_element_type(data_type: &DataType) -> Result<&DataType> {
429    match data_type {
430        DataType::List(element) => Ok(element.data_type()),
431        DataType::LargeList(element) => Ok(element.data_type()),
432        DataType::FixedSizeList(element, _) => Ok(element.data_type()),
433        _ => exec_err!(
434            "Expected list, large_list or fixed_size_list, got {:?}",
435            data_type
436        ),
437    }
438}
439
440/// Helper function to create MapArray from array of values to support arrays for Map scalar function
441///
442/// ``` text
443/// Format of input KEYS and VALUES column
444///         keys                        values
445/// +---------------------+       +---------------------+
446/// | +-----------------+ |       | +-----------------+ |
447/// | | [k11, k12, k13] | |       | | [v11, v12, v13] | |
448/// | +-----------------+ |       | +-----------------+ |
449/// |                     |       |                     |
450/// | +-----------------+ |       | +-----------------+ |
451/// | | [k21, k22, k23] | |       | | [v21, v22, v23] | |
452/// | +-----------------+ |       | +-----------------+ |
453/// |                     |       |                     |
454/// | +-----------------+ |       | +-----------------+ |
455/// | |[k31, k32, k33]  | |       | |[v31, v32, v33]  | |
456/// | +-----------------+ |       | +-----------------+ |
457/// +---------------------+       +---------------------+
458/// ```
459/// Flattened keys and values array to user create `StructArray`,
460/// which serves as inner child for `MapArray`
461///
462/// ``` text
463/// Flattened           Flattened
464/// Keys                Values
465/// +-----------+      +-----------+
466/// | +-------+ |      | +-------+ |
467/// | |  k11  | |      | |  v11  | |
468/// | +-------+ |      | +-------+ |
469/// | +-------+ |      | +-------+ |
470/// | |  k12  | |      | |  v12  | |
471/// | +-------+ |      | +-------+ |
472/// | +-------+ |      | +-------+ |
473/// | |  k13  | |      | |  v13  | |
474/// | +-------+ |      | +-------+ |
475/// | +-------+ |      | +-------+ |
476/// | |  k21  | |      | |  v21  | |
477/// | +-------+ |      | +-------+ |
478/// | +-------+ |      | +-------+ |
479/// | |  k22  | |      | |  v22  | |
480/// | +-------+ |      | +-------+ |
481/// | +-------+ |      | +-------+ |
482/// | |  k23  | |      | |  v23  | |
483/// | +-------+ |      | +-------+ |
484/// | +-------+ |      | +-------+ |
485/// | |  k31  | |      | |  v31  | |
486/// | +-------+ |      | +-------+ |
487/// | +-------+ |      | +-------+ |
488/// | |  k32  | |      | |  v32  | |
489/// | +-------+ |      | +-------+ |
490/// | +-------+ |      | +-------+ |
491/// | |  k33  | |      | |  v33  | |
492/// | +-------+ |      | +-------+ |
493/// +-----------+      +-----------+
494/// ```text
495fn make_map_array_internal<O: OffsetSizeTrait>(
496    keys: &ArrayRef,
497    values: &ArrayRef,
498) -> Result<ColumnarValue> {
499    // Save original data types and array length before list_to_arrays transforms them
500    let keys_data_type = keys.data_type().clone();
501    let values_data_type = values.data_type().clone();
502    let original_len = keys.len(); // This is the number of rows in the input
503
504    // Save the nulls bitmap from the original keys array (before list_to_arrays)
505    // This tells us which MAP values are NULL (not which keys within maps are null)
506    let nulls_bitmap = keys.nulls().cloned();
507
508    let keys = list_to_arrays::<O>(keys);
509    let values = list_to_arrays_skipping_null_rows::<O>(values, nulls_bitmap.as_ref());
510
511    build_map_array(
512        &keys,
513        &values,
514        &keys_data_type,
515        &values_data_type,
516        original_len,
517        nulls_bitmap,
518    )
519}
520
521/// Helper function specifically for FixedSizeList inputs
522/// Similar to make_map_array_internal but uses fixed_size_list_to_arrays instead of list_to_arrays
523fn make_map_array_from_fixed_size_list(
524    keys: &ArrayRef,
525    values: &ArrayRef,
526) -> Result<ColumnarValue> {
527    // Save original data types and array length
528    let keys_data_type = keys.data_type().clone();
529    let values_data_type = values.data_type().clone();
530    let original_len = keys.len();
531
532    // Save the nulls bitmap from the original keys array
533    let nulls_bitmap = keys.nulls().cloned();
534
535    let keys = fixed_size_list_to_arrays(keys);
536    let values =
537        fixed_size_list_to_arrays_skipping_null_rows(values, nulls_bitmap.as_ref());
538
539    build_map_array(
540        &keys,
541        &values,
542        &keys_data_type,
543        &values_data_type,
544        original_len,
545        nulls_bitmap,
546    )
547}
548fn list_to_arrays_skipping_null_rows<O: OffsetSizeTrait>(
549    array: &ArrayRef,
550    null_rows: Option<&arrow::buffer::NullBuffer>,
551) -> Vec<ArrayRef> {
552    array
553        .as_list::<O>()
554        .iter()
555        .enumerate()
556        .filter_map(|(i, row)| {
557            if null_rows.is_some_and(|nulls| nulls.is_null(i)) {
558                None
559            } else {
560                row
561            }
562        })
563        .collect()
564}
565
566fn fixed_size_list_to_arrays_skipping_null_rows(
567    array: &ArrayRef,
568    null_rows: Option<&arrow::buffer::NullBuffer>,
569) -> Vec<ArrayRef> {
570    array
571        .as_fixed_size_list()
572        .iter()
573        .enumerate()
574        .filter_map(|(i, row)| {
575            if null_rows.is_some_and(|nulls| nulls.is_null(i)) {
576                None
577            } else {
578                row
579            }
580        })
581        .collect()
582}
583
584/// Common logic to build a MapArray from decomposed list arrays
585fn build_map_array(
586    keys: &[ArrayRef],
587    values: &[ArrayRef],
588    keys_data_type: &DataType,
589    values_data_type: &DataType,
590    original_len: usize,
591    nulls_bitmap: Option<arrow::buffer::NullBuffer>,
592) -> Result<ColumnarValue> {
593    if keys.len() != values.len() {
594        return exec_err!("map requires key and value lists to have the same length");
595    }
596
597    let mut key_array_vec = vec![];
598    let mut value_array_vec = vec![];
599    for (k, v) in keys.iter().zip(values.iter()) {
600        key_array_vec.push(k.as_ref());
601        value_array_vec.push(v.as_ref());
602    }
603
604    // Build offset buffer that accounts for NULL maps
605    // For each row, if it's NULL, the offset stays the same (empty range)
606    // If it's not NULL, the offset advances by the number of entries in that map
607    // NOTE: MapArray always requires i32 offsets, regardless of input list type
608    let mut running_offset = 0i32;
609    let mut offset_buffer = vec![running_offset];
610    let mut non_null_idx = 0;
611    for i in 0..original_len {
612        let is_null = nulls_bitmap.as_ref().is_some_and(|nulls| nulls.is_null(i));
613        if !is_null {
614            let entry_count = keys[non_null_idx].len();
615            // Validate that we won't overflow i32 when converting from potentially i64 offsets
616            let entry_count_i32 = i32::try_from(entry_count).map_err(|_| {
617                datafusion_common::DataFusionError::Execution(format!(
618                    "Map offset overflow: entry count {entry_count} at index {i} exceeds i32::MAX",
619                ))
620            })?;
621            running_offset =
622                running_offset.checked_add(entry_count_i32).ok_or_else(|| {
623                    datafusion_common::DataFusionError::Execution(format!(
624                    "Map offset overflow: cumulative offset exceeds i32::MAX at index {i}",
625                ))
626                })?;
627            non_null_idx += 1;
628        }
629        offset_buffer.push(running_offset);
630    }
631
632    // concatenate all the arrays
633    // If key_array_vec is empty, it means all maps were NULL (list elements were NULL).
634    // In this case, we need to create empty arrays with the correct data type.
635    let (flattened_keys, flattened_values) = if key_array_vec.is_empty() {
636        // All maps are NULL - create empty arrays
637        // We need to infer the data type from the original keys/values arrays
638        let key_type = get_element_type(keys_data_type)?;
639        let value_type = get_element_type(values_data_type)?;
640
641        (
642            arrow::array::new_empty_array(key_type),
643            arrow::array::new_empty_array(value_type),
644        )
645    } else {
646        let flattened_keys = arrow::compute::concat(key_array_vec.as_ref())?;
647        if flattened_keys.null_count() > 0 {
648            return exec_err!("keys cannot be null");
649        }
650        let flattened_values = arrow::compute::concat(value_array_vec.as_ref())?;
651        (flattened_keys, flattened_values)
652    };
653
654    let fields = vec![
655        Arc::new(Field::new("key", flattened_keys.data_type().clone(), false)),
656        Arc::new(Field::new(
657            "value",
658            flattened_values.data_type().clone(),
659            true,
660        )),
661    ];
662
663    let struct_data = ArrayData::builder(DataType::Struct(fields.into()))
664        .len(flattened_keys.len())
665        .add_child_data(flattened_keys.to_data())
666        .add_child_data(flattened_values.to_data())
667        .build()?;
668
669    let mut map_data_builder = ArrayData::builder(DataType::Map(
670        Arc::new(Field::new(
671            "entries",
672            struct_data.data_type().clone(),
673            false,
674        )),
675        false,
676    ))
677    .len(original_len) // Use the original number of rows, not the filtered count
678    .add_child_data(struct_data)
679    .add_buffer(Buffer::from_slice_ref(offset_buffer.as_slice()));
680
681    // Add the nulls bitmap if present (to preserve NULL map values)
682    if let Some(nulls) = nulls_bitmap {
683        map_data_builder = map_data_builder.nulls(Some(nulls));
684    }
685
686    let map_data = map_data_builder.build()?;
687    Ok(ColumnarValue::Array(Arc::new(MapArray::from(map_data))))
688}
689
690#[cfg(test)]
691mod tests {
692    use super::*;
693    #[test]
694    fn test_make_map_with_null_maps() {
695        // Test that NULL map values (entire map is NULL) are correctly handled
696        // This test directly calls make_map_batch with a List containing NULL elements
697        //
698        // Background: On main branch, the code would fail with "map key cannot be null"
699        // because it couldn't distinguish between:
700        // - NULL map (entire map is NULL) - should be allowed
701        // - null key within a map - should be rejected
702
703        // Build keys array: [['a'], NULL, ['b']]
704        // The middle NULL represents an entire NULL map, not a null key
705        let mut key_builder =
706            arrow::array::ListBuilder::new(arrow::array::StringBuilder::new());
707
708        // First map: ['a']
709        key_builder.values().append_value("a");
710        key_builder.append(true);
711
712        // Second map: NULL (entire map is NULL)
713        key_builder.append(false);
714
715        // Third map: ['b']
716        key_builder.values().append_value("b");
717        key_builder.append(true);
718
719        let keys_array = Arc::new(key_builder.finish());
720
721        // Build values array: [[1], [2], [3]]
722        let mut value_builder =
723            arrow::array::ListBuilder::new(arrow::array::Int32Builder::new());
724
725        value_builder.values().append_value(1);
726        value_builder.append(true);
727
728        value_builder.values().append_value(2);
729        value_builder.append(true);
730
731        value_builder.values().append_value(3);
732        value_builder.append(true);
733
734        let values_array = Arc::new(value_builder.finish());
735
736        // Call make_map_batch - should succeed
737        let result = make_map_batch(
738            vec![
739                ColumnarValue::Array(keys_array),
740                ColumnarValue::Array(values_array),
741            ],
742            3,
743        );
744
745        assert!(result.is_ok(), "Should handle NULL maps correctly");
746
747        // Verify the result
748        let map_array = match result.unwrap() {
749            ColumnarValue::Array(arr) => arr,
750            _ => panic!("Expected Array result"),
751        };
752
753        assert_eq!(map_array.len(), 3, "Should have 3 maps");
754        assert!(!map_array.is_null(0), "First map should not be NULL");
755        assert!(map_array.is_null(1), "Second map should be NULL");
756        assert!(!map_array.is_null(2), "Third map should not be NULL");
757    }
758
759    #[test]
760    fn test_make_map_with_null_key_within_map_should_fail() {
761        // Test that null keys WITHIN a map are properly rejected
762        // This ensures the fix doesn't accidentally allow invalid null keys
763
764        // Build keys array: [['a', NULL, 'b']]
765        // The NULL here is a null key within the map, which is invalid
766        let mut key_builder =
767            arrow::array::ListBuilder::new(arrow::array::StringBuilder::new());
768
769        key_builder.values().append_value("a");
770        key_builder.values().append_null(); // Invalid: null key
771        key_builder.values().append_value("b");
772        key_builder.append(true);
773
774        let keys_array = Arc::new(key_builder.finish());
775
776        // Build values array: [[1, 2, 3]]
777        let mut value_builder =
778            arrow::array::ListBuilder::new(arrow::array::Int32Builder::new());
779
780        value_builder.values().append_value(1);
781        value_builder.values().append_value(2);
782        value_builder.values().append_value(3);
783        value_builder.append(true);
784
785        let values_array = Arc::new(value_builder.finish());
786
787        // Call make_map_batch - should fail
788        let result = make_map_batch(
789            vec![
790                ColumnarValue::Array(keys_array),
791                ColumnarValue::Array(values_array),
792            ],
793            1,
794        );
795
796        assert!(result.is_err(), "Should reject null keys within maps");
797
798        let err_msg = result.unwrap_err().to_string();
799        assert!(
800            err_msg.contains("cannot be null"),
801            "Error should mention null keys, got: {err_msg}"
802        );
803    }
804
805    #[test]
806    fn test_make_map_with_large_list() {
807        // Test that LargeList inputs work correctly with i32 offset conversion
808        // This verifies the fix for the offset buffer type mismatch issue
809
810        // Build keys array as LargeList: [['a', 'b'], ['c']]
811        let mut key_builder =
812            arrow::array::LargeListBuilder::new(arrow::array::StringBuilder::new());
813
814        // First map: ['a', 'b']
815        key_builder.values().append_value("a");
816        key_builder.values().append_value("b");
817        key_builder.append(true);
818
819        // Second map: ['c']
820        key_builder.values().append_value("c");
821        key_builder.append(true);
822
823        let keys_array = Arc::new(key_builder.finish());
824
825        // Build values array as LargeList: [[1, 2], [3]]
826        let mut value_builder =
827            arrow::array::LargeListBuilder::new(arrow::array::Int32Builder::new());
828
829        value_builder.values().append_value(1);
830        value_builder.values().append_value(2);
831        value_builder.append(true);
832
833        value_builder.values().append_value(3);
834        value_builder.append(true);
835
836        let values_array = Arc::new(value_builder.finish());
837
838        // Call make_map_batch - should succeed
839        let result = make_map_batch(
840            vec![
841                ColumnarValue::Array(keys_array),
842                ColumnarValue::Array(values_array),
843            ],
844            2,
845        );
846
847        assert!(
848            result.is_ok(),
849            "Should handle LargeList inputs correctly: {:?}",
850            result.err()
851        );
852
853        // Verify the result
854        let map_array = match result.unwrap() {
855            ColumnarValue::Array(arr) => arr,
856            _ => panic!("Expected Array result"),
857        };
858
859        assert_eq!(map_array.len(), 2, "Should have 2 maps");
860        assert!(!map_array.is_null(0), "First map should not be NULL");
861        assert!(!map_array.is_null(1), "Second map should not be NULL");
862    }
863
864    #[test]
865    fn test_make_map_with_fixed_size_list() {
866        // Test that FixedSizeList inputs work correctly
867        // This verifies the fix for FixedSizeList support in the data type check
868
869        use arrow::array::FixedSizeListBuilder;
870
871        // Build keys array as FixedSizeList(2): [['a', 'b'], NULL, ['c', 'd']]
872        let key_values_builder = arrow::array::StringBuilder::new();
873        let mut key_builder = FixedSizeListBuilder::new(key_values_builder, 2);
874
875        // First map: ['a', 'b']
876        key_builder.values().append_value("a");
877        key_builder.values().append_value("b");
878        key_builder.append(true);
879
880        // Second map: NULL (entire map is NULL)
881        key_builder.values().append_null();
882        key_builder.values().append_null();
883        key_builder.append(false);
884
885        // Second map: ['c', 'd']
886        key_builder.values().append_value("c");
887        key_builder.values().append_value("d");
888        key_builder.append(true);
889
890        let keys_array = Arc::new(key_builder.finish());
891
892        // Build values array as FixedSizeList(2): [[1, 2], [99, 100], [3, 4]]
893        // The middle row should be ignored because the corresponding key row is NULL.
894        let value_values_builder = arrow::array::Int32Builder::new();
895        let mut value_builder = FixedSizeListBuilder::new(value_values_builder, 2);
896
897        value_builder.values().append_value(1);
898        value_builder.values().append_value(2);
899        value_builder.append(true);
900
901        value_builder.values().append_value(99);
902        value_builder.values().append_value(100);
903        value_builder.append(true);
904
905        value_builder.values().append_value(3);
906        value_builder.values().append_value(4);
907        value_builder.append(true);
908
909        let values_array = Arc::new(value_builder.finish());
910
911        // Call make_map_batch - should succeed
912        let result = make_map_batch(
913            vec![
914                ColumnarValue::Array(keys_array),
915                ColumnarValue::Array(values_array),
916            ],
917            3,
918        );
919
920        assert!(
921            result.is_ok(),
922            "Should handle FixedSizeList inputs correctly: {:?}",
923            result.err()
924        );
925
926        // Verify the result
927        let map_array = match result.unwrap() {
928            ColumnarValue::Array(arr) => arr,
929            _ => panic!("Expected Array result"),
930        };
931
932        assert_eq!(map_array.len(), 3, "Should have 3 maps");
933        assert!(!map_array.is_null(0), "First map should not be NULL");
934        assert!(map_array.is_null(1), "Second map should be NULL");
935        assert!(!map_array.is_null(2), "Third map should not be NULL");
936    }
937}