Skip to main content

parquet/arrow/buffer/
dictionary_buffer.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 crate::arrow::buffer::offset_buffer::OffsetBuffer;
19use crate::arrow::record_reader::buffer::ValuesBuffer;
20use crate::errors::{ParquetError, Result};
21use ahash::RandomState;
22use arrow_array::{Array, DictionaryArray, PrimitiveArray, downcast_integer};
23use arrow_array::{
24    ArrayRef, FixedSizeBinaryArray, GenericBinaryArray, OffsetSizeTrait,
25    builder::FixedSizeBinaryDictionaryBuilder,
26    cast::AsArray,
27    types::{
28        ArrowDictionaryKeyType, ArrowPrimitiveType, Int8Type, Int16Type, Int32Type, Int64Type,
29        UInt8Type, UInt16Type, UInt32Type, UInt64Type,
30    },
31};
32use arrow_buffer::{
33    ArrowNativeType, BooleanBuffer, Buffer, MutableBuffer, NullBuffer, ScalarBuffer,
34};
35use arrow_schema::DataType as ArrowType;
36use hashbrown::HashMap as HbHashMap;
37use hashbrown::hash_map::Entry;
38use std::hash::{BuildHasher, Hasher};
39use std::mem::size_of;
40use std::ptr::write_unaligned;
41use std::slice::{from_raw_parts, from_raw_parts_mut};
42use std::sync::Arc;
43
44/// An array of variable length byte arrays that are potentially dictionary encoded
45/// and can be converted into a corresponding [`ArrayRef`]
46pub enum DictionaryBuffer<K: ArrowNativeType, V: OffsetSizeTrait> {
47    Dict {
48        keys: Vec<K>,
49        values: ArrayRef,
50        /// Exclusive upper bound for decoded keys.
51        key_upper_bound: Option<u64>,
52    },
53    Values {
54        values: OffsetBuffer<V>,
55    },
56}
57
58fn widen_key_bound(existing: &mut Option<u64>, incoming: Option<u64>) {
59    *existing = match (*existing, incoming) {
60        (Some(current), Some(new)) => Some(current.max(new)),
61        _ => None,
62    };
63}
64
65impl<K: ArrowNativeType + Ord, V: OffsetSizeTrait> DictionaryBuffer<K, V> {
66    #[cfg_attr(not(test), expect(unused))]
67    pub fn len(&self) -> usize {
68        match self {
69            Self::Dict { keys, .. } => keys.len(),
70            Self::Values { values } => values.len(),
71        }
72    }
73
74    /// Returns the key buffer or `None` if the dictionary must be rebuilt.
75    ///
76    /// Records an exclusive bound derived from `rle_bit_width`. A missing width
77    /// discards any previously recorded bound.
78    ///
79    /// # Panics
80    ///
81    /// Panics if the dictionary is too large for `K`.
82    pub fn as_keys_with_bit_width(
83        &mut self,
84        dictionary: &ArrayRef,
85        rle_bit_width: Option<u8>,
86    ) -> Option<&mut Vec<K>> {
87        assert!(K::from_usize(dictionary.len()).is_some());
88
89        // Dictionary RLE bit widths are validated to be at most 32.
90        let incoming_bound = rle_bit_width.map(|bit_width| 1u64 << bit_width);
91
92        match self {
93            Self::Dict {
94                keys,
95                values,
96                key_upper_bound,
97            } => {
98                // Need to discard fat pointer for equality check
99                // - https://stackoverflow.com/a/67114787
100                // - https://github.com/rust-lang/rust/issues/46139
101                let values_ptr = std::ptr::from_ref(values.as_ref()).cast::<()>();
102                let dict_ptr = std::ptr::from_ref(dictionary.as_ref()).cast::<()>();
103                if values_ptr == dict_ptr {
104                    widen_key_bound(key_upper_bound, incoming_bound);
105                    Some(keys)
106                } else if keys.is_empty() {
107                    *values = Arc::clone(dictionary);
108                    *key_upper_bound = incoming_bound;
109                    Some(keys)
110                } else {
111                    None
112                }
113            }
114            Self::Values { values } if values.is_empty() => {
115                *self = Self::Dict {
116                    keys: Default::default(),
117                    values: Arc::clone(dictionary),
118                    key_upper_bound: incoming_bound,
119                };
120                match self {
121                    Self::Dict { keys, .. } => Some(keys),
122                    Self::Values { .. } => unreachable!(),
123                }
124            }
125            Self::Values { .. } => None,
126        }
127    }
128
129    /// Returns a mutable reference to a values array
130    ///
131    /// If this is currently dictionary encoded, this will convert from the
132    /// dictionary encoded representation
133    pub fn spill_values(&mut self) -> Result<&mut OffsetBuffer<V>> {
134        match self {
135            Self::Values { values } => Ok(values),
136            Self::Dict { keys, values, .. } => {
137                let mut spilled = OffsetBuffer::with_capacity(0);
138                let data = values.to_data();
139                let dict_buffers = data.buffers();
140                let dict_offsets = dict_buffers[0].typed_data::<V>();
141                let dict_values = dict_buffers[1].as_slice();
142
143                if values.is_empty() {
144                    // If dictionary is empty, zero pad offsets
145                    spilled.offsets.resize(keys.len() + 1, V::default());
146                } else {
147                    // Note: at this point null positions will have arbitrary dictionary keys
148                    // and this will hydrate them to the corresponding byte array. This is
149                    // likely sub-optimal, as we would prefer zero length null "slots", but
150                    // spilling is already a degenerate case and so it is unclear if this is
151                    // worth optimising for, e.g. by keeping a null mask around
152                    spilled.extend_from_dictionary(keys.as_slice(), dict_offsets, dict_values)?;
153                }
154
155                *self = Self::Values { values: spilled };
156                match self {
157                    Self::Values { values } => Ok(values),
158                    Self::Dict { .. } => unreachable!(),
159                }
160            }
161        }
162    }
163
164    /// Converts this into an [`ArrayRef`] with the provided `data_type` and `null_buffer`
165    pub fn into_array(
166        self,
167        null_buffer: Option<Buffer>,
168        data_type: &ArrowType,
169        hash_scratch: &mut MutableBuffer,
170    ) -> Result<ArrayRef> {
171        assert!(matches!(data_type, ArrowType::Dictionary(_, _)));
172
173        match self {
174            Self::Dict {
175                keys,
176                values,
177                key_upper_bound,
178            } => {
179                let keys_provably_in_range = key_upper_bound
180                    .map(|bound| bound <= values.len() as u64)
181                    .unwrap_or(false);
182
183                if !values.is_empty() && !keys_provably_in_range {
184                    let min = K::from_usize(0).unwrap();
185                    let max = K::from_usize(values.len()).unwrap();
186
187                    let (observed_min, observed_max) = keys.as_slice().iter().copied().fold(
188                        (max, min),
189                        |(current_min, current_max), key| {
190                            (current_min.min(key), current_max.max(key))
191                        },
192                    );
193
194                    if observed_min < min || observed_max >= max {
195                        return Err(general_err!(
196                            "dictionary key beyond bounds of dictionary: 0..{}",
197                            values.len()
198                        ));
199                    }
200                }
201
202                let ArrowType::Dictionary(key_type, value_type) = data_type else {
203                    unreachable!()
204                };
205                let values = if let ArrowType::FixedSizeBinary(size) = **value_type {
206                    let binary = values.as_binary::<i32>();
207                    Arc::new(FixedSizeBinaryArray::new(
208                        size,
209                        binary.values().clone(),
210                        binary.nulls().cloned(),
211                    )) as _
212                } else {
213                    values
214                };
215
216                let num_keys = keys.len();
217                let keys_buffer = Buffer::from_vec(keys);
218                let null_buffer =
219                    null_buffer.map(|b| NullBuffer::new(BooleanBuffer::new(b, 0, num_keys)));
220
221                build_dictionary_array(key_type, keys_buffer, num_keys, null_buffer, values)
222            }
223            Self::Values { values } => {
224                let (key_type, value_type) = match data_type {
225                    ArrowType::Dictionary(k, v) => (k, v.as_ref().clone()),
226                    _ => unreachable!(),
227                };
228
229                // Plain byte-array pages are decoded into an offset buffer. Convert
230                // this representation explicitly before rebuilding an FSB dictionary,
231                // as FixedSizeBinary arrays have no offsets buffer.
232                if let ArrowType::FixedSizeBinary(size) = value_type {
233                    let binary = values.into_array(null_buffer, GenericBinaryArray::<V>::DATA_TYPE);
234                    let binary = binary.as_binary::<V>();
235                    let array =
236                        FixedSizeBinaryArray::try_from_sparse_iter_with_size(binary.iter(), size)?;
237                    return pack_fixed_values(key_type, &array);
238                }
239
240                hash_byte_slices(&values.offsets, &values.values, hash_scratch);
241                let hashes = hashes_as_u64(hash_scratch);
242
243                pack_values_from_offsets(key_type, &value_type, &values, hashes, null_buffer)
244            }
245        }
246    }
247}
248
249impl<K: ArrowNativeType, V: OffsetSizeTrait> ValuesBuffer for DictionaryBuffer<K, V> {
250    fn with_capacity(capacity: usize) -> Self {
251        Self::Values {
252            values: OffsetBuffer::with_capacity(capacity),
253        }
254    }
255
256    fn reserve_exact(&mut self, additional: usize) {
257        match self {
258            Self::Dict { keys, .. } => keys.reserve_exact(additional),
259            Self::Values { values, .. } => values.reserve_exact(additional),
260        }
261    }
262
263    fn pad_nulls(
264        &mut self,
265        read_offset: usize,
266        values_read: usize,
267        levels_read: usize,
268        valid_mask: &[u8],
269    ) -> Result<()> {
270        match self {
271            Self::Dict { keys, .. } => {
272                keys.resize(read_offset + levels_read, K::default());
273                keys.pad_nulls(read_offset, values_read, levels_read, valid_mask)
274            }
275            Self::Values { values, .. } => {
276                values.pad_nulls(read_offset, values_read, levels_read, valid_mask)
277            }
278        }
279    }
280}
281
282/// Build a dictionary array from a raw keys buffer and an already-decoded values
283/// `ArrayRef`, skipping the ArrayData round-trip.
284fn build_dictionary_array(
285    key_type: &ArrowType,
286    keys_buffer: Buffer,
287    num_keys: usize,
288    null_buffer: Option<NullBuffer>,
289    values: ArrayRef,
290) -> Result<ArrayRef> {
291    macro_rules! build {
292        ($kt:ty) => {{
293            let scalars =
294                ScalarBuffer::<<$kt as ArrowPrimitiveType>::Native>::new(keys_buffer, 0, num_keys);
295            let keys = PrimitiveArray::<$kt>::new(scalars, null_buffer);
296            // SAFETY: key bounds already validated by caller
297            let dict = unsafe { DictionaryArray::<$kt>::new_unchecked(keys, values) };
298            Ok(Arc::new(dict) as ArrayRef)
299        }};
300    }
301    match key_type {
302        ArrowType::Int8 => build!(Int8Type),
303        ArrowType::Int16 => build!(Int16Type),
304        ArrowType::Int32 => build!(Int32Type),
305        ArrowType::Int64 => build!(Int64Type),
306        ArrowType::UInt8 => build!(UInt8Type),
307        ArrowType::UInt16 => build!(UInt16Type),
308        ArrowType::UInt32 => build!(UInt32Type),
309        ArrowType::UInt64 => build!(UInt64Type),
310        _ => Err(general_err!("unsupported dictionary key type: {key_type}")),
311    }
312}
313
314macro_rules! fixed_dict_helper {
315    ($k:ty, $array:ident) => {
316        pack_fixed_values_impl::<$k>($array)
317    };
318}
319
320fn pack_fixed_values(key_type: &ArrowType, array: &FixedSizeBinaryArray) -> Result<ArrayRef> {
321    downcast_integer! {
322        key_type => (fixed_dict_helper, array),
323        _ => unreachable!(),
324    }
325}
326
327fn pack_fixed_values_impl<K: ArrowDictionaryKeyType>(
328    array: &FixedSizeBinaryArray,
329) -> Result<ArrayRef> {
330    let mut builder = FixedSizeBinaryDictionaryBuilder::<K>::with_capacity(
331        array.len(),
332        1024,
333        array.value_length(),
334    );
335    for x in array {
336        match x {
337            Some(x) => {
338                builder.append(x)?;
339            }
340            None => builder.append_null(),
341        }
342    }
343    Ok(Arc::new(builder.finish()))
344}
345
346macro_rules! offsets_dict_helper {
347    ($k:ty, $key_type:ident, $value_type:ident, $values:ident, $hashes:ident, $null_buffer:ident) => {
348        pack_values_from_offsets_impl::<$k, _>(
349            $values,
350            $hashes,
351            $null_buffer,
352            $key_type,
353            $value_type,
354        )
355    };
356}
357
358fn pack_values_from_offsets<V: OffsetSizeTrait>(
359    key_type: &ArrowType,
360    value_type: &ArrowType,
361    values: &OffsetBuffer<V>,
362    hashes: &[u64],
363    null_buffer: Option<Buffer>,
364) -> Result<ArrayRef> {
365    downcast_integer! {
366        key_type => (offsets_dict_helper, key_type, value_type, values, hashes, null_buffer),
367        _ => unreachable!(),
368    }
369}
370
371// Avoids double-hashing: keys are already high-quality u64 hashes from ahash,
372// so we pass them through directly rather than re-hashing inside the HashMap.
373struct PassthroughHasher(u64);
374impl std::hash::Hasher for PassthroughHasher {
375    fn finish(&self) -> u64 {
376        self.0
377    }
378    fn write(&mut self, _: &[u8]) {
379        unreachable!()
380    }
381    fn write_u64(&mut self, value: u64) {
382        self.0 = value;
383    }
384}
385#[derive(Default)]
386struct BuildPassthroughHasher;
387impl std::hash::BuildHasher for BuildPassthroughHasher {
388    type Hasher = PassthroughHasher;
389    fn build_hasher(&self) -> PassthroughHasher {
390        PassthroughHasher(0)
391    }
392}
393
394/// Builds a [`DictionaryArray`] directly from a flat [`OffsetBuffer`] using pre-computed
395/// hashes to deduplicate values in a single pass, avoiding the intermediate StringArray
396/// materialization
397fn pack_values_from_offsets_impl<K: ArrowDictionaryKeyType, V: OffsetSizeTrait>(
398    offset_buffer: &OffsetBuffer<V>,
399    hashes: &[u64],
400    null_buffer: Option<Buffer>,
401    key_type: &ArrowType,
402    value_type: &ArrowType,
403) -> Result<ArrayRef> {
404    let dict_type = ArrowType::Dictionary(Box::new(key_type.clone()), Box::new(value_type.clone()));
405    let num_values = offset_buffer.len();
406
407    let mut keys: Vec<K::Native> = Vec::with_capacity(num_values);
408    let mut unique_offsets: Vec<V> = Vec::with_capacity(num_values + 1);
409    unique_offsets.push(V::default());
410    let mut unique_bytes: Vec<u8> = Vec::with_capacity(offset_buffer.values.len());
411
412    let mut dedup: HbHashMap<u64, (usize, usize), BuildPassthroughHasher> =
413        HbHashMap::with_capacity_and_hasher(num_values, BuildPassthroughHasher);
414
415    // Tracks colliding values (same hash, different bytes) so repeated occurrences
416    // are deduplicated rather than inserted as new entries. Empty in the common case.
417    let mut collision_overflow: Vec<(u64, usize, usize)> = Vec::new();
418
419    for (input_idx, &hash) in hashes.iter().enumerate() {
420        let byte_start = offset_buffer.offsets[input_idx].as_usize();
421        let byte_end = offset_buffer.offsets[input_idx + 1].as_usize();
422        let bytes = &offset_buffer.values[byte_start..byte_end];
423
424        let output_idx = match dedup.entry(hash) {
425            Entry::Occupied(entry) => {
426                let (first_input_idx, existing_output_idx) = *entry.get();
427                let first_start = offset_buffer.offsets[first_input_idx].as_usize();
428                let first_end = offset_buffer.offsets[first_input_idx + 1].as_usize();
429                if &offset_buffer.values[first_start..first_end] == bytes {
430                    existing_output_idx
431                } else {
432                    // True hash collision — check overflow list before inserting.
433                    let existing = collision_overflow
434                        .iter()
435                        .find(|&&(entry_hash, collision_input_idx, _)| {
436                            if entry_hash != hash {
437                                return false;
438                            }
439                            let collision_start =
440                                offset_buffer.offsets[collision_input_idx].as_usize();
441                            let collision_end =
442                                offset_buffer.offsets[collision_input_idx + 1].as_usize();
443                            &offset_buffer.values[collision_start..collision_end] == bytes
444                        })
445                        .map(|&(_, _, collision_output_idx)| collision_output_idx);
446
447                    match existing {
448                        Some(collision_output_idx) => collision_output_idx,
449                        None => {
450                            let new_output_idx = unique_offsets.len() - 1;
451                            unique_bytes.extend_from_slice(bytes);
452                            let new_end = V::from_usize(unique_bytes.len()).ok_or_else(|| {
453                                general_err!("offset overflow building dictionary")
454                            })?;
455                            unique_offsets.push(new_end);
456                            collision_overflow.push((hash, input_idx, new_output_idx));
457                            new_output_idx
458                        }
459                    }
460                }
461            }
462            Entry::Vacant(entry) => {
463                let output_idx = unique_offsets.len() - 1;
464                unique_bytes.extend_from_slice(bytes);
465                let new_end = V::from_usize(unique_bytes.len())
466                    .ok_or_else(|| general_err!("offset overflow building dictionary"))?;
467                unique_offsets.push(new_end);
468                entry.insert((input_idx, output_idx));
469                output_idx
470            }
471        };
472
473        let key = K::Native::from_usize(output_idx)
474            .ok_or_else(|| general_err!("dictionary key overflow"))?;
475        keys.push(key);
476    }
477
478    let num_unique = unique_offsets.len() - 1;
479
480    // SAFETY: buffers are constructed directly from typed Vecs above; offsets are
481    // monotonically non-decreasing and bounded by unique_bytes.len(), and all
482    // key values are within 0..num_unique, so the invariants Arrow requires hold.
483    let value_data = unsafe {
484        arrow_data::ArrayData::builder(value_type.clone())
485            .len(num_unique)
486            .add_buffer(Buffer::from_vec(unique_offsets))
487            .add_buffer(Buffer::from_vec(unique_bytes))
488            .build_unchecked()
489    };
490
491    // SAFETY: keys are within 0..num_unique and value_data is valid.
492    let dict_array: DictionaryArray<K> = unsafe {
493        arrow_data::ArrayData::builder(dict_type)
494            .len(keys.len())
495            .add_buffer(Buffer::from_vec(keys))
496            .add_child_data(value_data)
497            .null_bit_buffer(null_buffer)
498            .build_unchecked()
499            .into()
500    };
501
502    Ok(Arc::new(dict_array))
503}
504
505fn hash_byte_slices<I: ArrowNativeType>(offsets: &[I], values: &[u8], scratch: &mut MutableBuffer) {
506    let count = offsets.len().saturating_sub(1);
507    scratch.clear();
508    scratch.resize(count * size_of::<u64>(), 0u8);
509
510    let state = RandomState::new();
511
512    // SAFETY: MutableBuffer is 64-byte aligned; scratch is sized to exactly count * size_of::<u64>()
513    #[expect(
514        clippy::cast_ptr_alignment,
515        reason = "MutableBuffer is 64-byte aligned"
516    )]
517    let hash_slots = unsafe { from_raw_parts_mut(scratch.as_mut_ptr().cast::<u64>(), count) };
518
519    for idx in 0..count {
520        let start = offsets[idx].as_usize();
521        let end = offsets[idx + 1].as_usize();
522        let mut hasher = state.build_hasher();
523        hasher.write(&values[start..end]);
524        // SAFETY: idx is within 0..count
525        unsafe { write_unaligned(hash_slots.as_mut_ptr().add(idx), hasher.finish()) };
526    }
527}
528
529#[inline]
530fn hashes_as_u64(scratch: &[u8]) -> &[u64] {
531    let n = scratch.len() / size_of::<u64>();
532    // SAFETY: scratch was written as u64s by hash_byte_slices, and MutableBuffer is 64-byte aligned
533    #[expect(
534        clippy::cast_ptr_alignment,
535        reason = "MutableBuffer is 64-byte aligned"
536    )]
537    unsafe {
538        from_raw_parts(scratch.as_ptr().cast::<u64>(), n)
539    }
540}
541
542#[cfg(test)]
543mod tests {
544    use super::*;
545    use arrow::compute::cast;
546    use arrow_array::StringArray;
547
548    #[test]
549    fn test_dictionary_buffer() {
550        let dict_type =
551            ArrowType::Dictionary(Box::new(ArrowType::Int32), Box::new(ArrowType::Utf8));
552
553        let d1: ArrayRef = Arc::new(StringArray::from(vec!["hello", "world", "", "a", "b"]));
554
555        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
556
557        // Read some data preserving the dictionary
558        let values = &[1, 0, 3, 2, 4];
559        buffer
560            .as_keys_with_bit_width(&d1, None)
561            .unwrap()
562            .extend_from_slice(values);
563
564        let mut valid = vec![false, false, true, true, false, true, true, true];
565        let valid_buffer = Buffer::from_iter(valid.iter().copied());
566        buffer
567            .pad_nulls(0, values.len(), valid.len(), valid_buffer.as_slice())
568            .unwrap();
569
570        // Read some data not preserving the dictionary
571
572        let values = buffer.spill_values().unwrap();
573        let read_offset = values.len();
574        values.try_push(b"bingo", false).unwrap();
575        values.try_push(b"bongo", false).unwrap();
576
577        valid.extend_from_slice(&[false, false, true, false, true]);
578        let null_buffer = Buffer::from_iter(valid.iter().copied());
579        buffer
580            .pad_nulls(read_offset, 2, 5, null_buffer.as_slice())
581            .unwrap();
582
583        assert_eq!(buffer.len(), 13);
584        let split = std::mem::replace(&mut buffer, DictionaryBuffer::with_capacity(0));
585
586        let array = split
587            .into_array(Some(null_buffer), &dict_type, &mut MutableBuffer::new(0))
588            .unwrap();
589        assert_eq!(array.data_type(), &dict_type);
590
591        let strings = cast(&array, &ArrowType::Utf8).unwrap();
592        let strings = strings.as_any().downcast_ref::<StringArray>().unwrap();
593        assert_eq!(
594            strings.iter().collect::<Vec<_>>(),
595            vec![
596                None,
597                None,
598                Some("world"),
599                Some("hello"),
600                None,
601                Some("a"),
602                Some(""),
603                Some("b"),
604                None,
605                None,
606                Some("bingo"),
607                None,
608                Some("bongo")
609            ]
610        );
611
612        // Can recreate with new dictionary as values is empty
613        assert!(matches!(&buffer, DictionaryBuffer::Values { .. }));
614        assert_eq!(buffer.len(), 0);
615        let d2 = Arc::new(StringArray::from(vec!["bingo", ""])) as ArrayRef;
616        buffer
617            .as_keys_with_bit_width(&d2, None)
618            .unwrap()
619            .extend_from_slice(&[0, 1, 0, 1]);
620
621        let array = std::mem::replace(&mut buffer, DictionaryBuffer::with_capacity(0))
622            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
623            .unwrap();
624        assert_eq!(array.data_type(), &dict_type);
625
626        let strings = cast(&array, &ArrowType::Utf8).unwrap();
627        let strings = strings.as_any().downcast_ref::<StringArray>().unwrap();
628        assert_eq!(
629            strings.iter().collect::<Vec<_>>(),
630            vec![Some("bingo"), Some(""), Some("bingo"), Some("")]
631        );
632
633        // Can recreate with new dictionary as keys empty
634        assert!(matches!(&buffer, DictionaryBuffer::Values { .. }));
635        assert_eq!(buffer.len(), 0);
636        let d3 = Arc::new(StringArray::from(vec!["bongo"])) as ArrayRef;
637        buffer
638            .as_keys_with_bit_width(&d3, None)
639            .unwrap()
640            .extend_from_slice(&[0, 0]);
641
642        // Cannot change dictionary as keys not empty
643        let d4 = Arc::new(StringArray::from(vec!["bananas"])) as ArrayRef;
644        assert!(buffer.as_keys_with_bit_width(&d4, None).is_none());
645    }
646
647    #[test]
648    fn test_validates_keys() {
649        let dict_type =
650            ArrowType::Dictionary(Box::new(ArrowType::Int32), Box::new(ArrowType::Utf8));
651
652        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
653        let d = Arc::new(StringArray::from(vec!["", "f"])) as ArrayRef;
654        buffer
655            .as_keys_with_bit_width(&d, None)
656            .unwrap()
657            .extend_from_slice(&[0, 2, 0]);
658
659        let err = buffer
660            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
661            .unwrap_err()
662            .to_string();
663        assert!(
664            err.contains("dictionary key beyond bounds of dictionary: 0..2"),
665            "{}",
666            err
667        );
668
669        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
670        let d = Arc::new(StringArray::from(vec![""])) as ArrayRef;
671        buffer
672            .as_keys_with_bit_width(&d, None)
673            .unwrap()
674            .extend_from_slice(&[0, 1, 0]);
675
676        let err = buffer.spill_values().unwrap_err().to_string();
677        assert!(
678            err.contains("dictionary key beyond bounds of dictionary: 0..1"),
679            "{}",
680            err
681        );
682    }
683
684    /// A dictionary requested with LargeUtf8 values must come back with LargeUtf8, not Utf8.
685    #[test]
686    fn test_values_path_large_utf8_type() {
687        let dict_type =
688            ArrowType::Dictionary(Box::new(ArrowType::Int32), Box::new(ArrowType::LargeUtf8));
689        let mut buffer = DictionaryBuffer::<i32, i64>::with_capacity(0);
690        let values = buffer.spill_values().unwrap();
691        for s in ["foo", "bar", "foo"] {
692            values.try_push(s.as_bytes(), false).unwrap();
693        }
694
695        let array = buffer
696            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
697            .unwrap();
698        let dict = array
699            .as_any()
700            .downcast_ref::<DictionaryArray<Int32Type>>()
701            .unwrap();
702
703        assert_eq!(dict.data_type(), &dict_type);
704        assert_eq!(dict.values().data_type(), &ArrowType::LargeUtf8);
705    }
706
707    /// 128 unique strings fill Int8 keys to capacity (last index = 127 = i8::MAX).
708    /// A 129th unique string overflows and must return an error.
709    #[test]
710    fn test_int8_key_overflow_boundary() {
711        let dict_type = ArrowType::Dictionary(Box::new(ArrowType::Int8), Box::new(ArrowType::Utf8));
712        let mut scratch = MutableBuffer::new(0);
713
714        {
715            let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
716            let values = buffer.spill_values().unwrap();
717            for i in 0u32..128 {
718                values
719                    .try_push(format!("val{i}").as_bytes(), false)
720                    .unwrap();
721            }
722            buffer
723                .into_array(None, &dict_type, &mut scratch)
724                .expect("128 unique strings must fit: last index is 127 = i8::MAX");
725        }
726
727        {
728            let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
729            let values = buffer.spill_values().unwrap();
730            for i in 0u32..129 {
731                values
732                    .try_push(format!("val{i}").as_bytes(), false)
733                    .unwrap();
734            }
735            let err = buffer
736                .into_array(None, &dict_type, &mut scratch)
737                .unwrap_err()
738                .to_string();
739            assert!(
740                err.contains("dictionary key overflow"),
741                "expected 'dictionary key overflow', got: {err}"
742            );
743        }
744    }
745
746    #[test]
747    fn test_fixed_size_binary_key_overflow_boundary() {
748        for (key_type, capacity) in [(ArrowType::Int8, 128u32), (ArrowType::UInt8, 256)] {
749            for unique in [capacity, capacity + 1] {
750                let values = (0..unique)
751                    .map(|value| Some(value.to_le_bytes()))
752                    .chain([None, Some(0u32.to_le_bytes())]);
753                let values =
754                    FixedSizeBinaryArray::try_from_sparse_iter_with_size(values, 4).unwrap();
755                let result = pack_fixed_values(&key_type, &values);
756                if unique > capacity {
757                    let error = result.unwrap_err();
758                    let ParquetError::External(source) = &error else {
759                        panic!("expected an Arrow dictionary overflow error, got {error}");
760                    };
761                    assert!(
762                        matches!(
763                            source.downcast_ref::<arrow_schema::ArrowError>(),
764                            Some(arrow_schema::ArrowError::DictionaryKeyOverflowError)
765                        ),
766                        "{error}"
767                    );
768                } else {
769                    let array = result.unwrap();
770                    assert_eq!(array.len(), unique as usize + 2);
771                    assert_eq!(array.null_count(), 1);
772                    assert!(array.is_null(unique as usize));
773                    assert!(array.is_valid(unique as usize + 1));
774                    let dictionary = array.as_any_dictionary();
775                    assert_eq!(dictionary.values().len(), unique as usize);
776                    assert_eq!(
777                        dictionary.values().data_type(),
778                        &ArrowType::FixedSizeBinary(4)
779                    );
780                }
781            }
782        }
783    }
784
785    /// A colliding string repeated after its initial collision must reuse its key,
786    /// not be inserted again as a new dictionary entry.
787    #[test]
788    fn test_hash_collision_deduplication() {
789        let key_type = ArrowType::Int32;
790        let value_type = ArrowType::Utf8;
791
792        // "alpha", "beta", "beta" — all forced to share the same hash.
793        let mut ob = OffsetBuffer::<i32>::with_capacity(3);
794        ob.try_push(b"alpha", false).unwrap();
795        ob.try_push(b"beta", false).unwrap();
796        ob.try_push(b"beta", false).unwrap();
797        let hashes = [0xdeadbeef_u64; 3];
798
799        let array = pack_values_from_offsets_impl::<Int32Type, i32>(
800            &ob,
801            &hashes,
802            None,
803            &key_type,
804            &value_type,
805        )
806        .unwrap();
807
808        let dict = array
809            .as_any()
810            .downcast_ref::<DictionaryArray<Int32Type>>()
811            .unwrap();
812
813        assert_eq!(dict.values().len(), 2);
814        let keys: Vec<i32> = dict.keys().values().iter().copied().collect();
815        assert_eq!(keys, vec![0, 1, 1]);
816    }
817
818    /// A dictionary requested with Binary values must come back with Binary, not Utf8.
819    #[test]
820    fn test_values_path_binary_type() {
821        let dict_type =
822            ArrowType::Dictionary(Box::new(ArrowType::Int32), Box::new(ArrowType::Binary));
823        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
824        let values = buffer.spill_values().unwrap();
825        for s in [b"abc".as_ref(), b"\x00\xff", b"abc"] {
826            values.try_push(s, false).unwrap();
827        }
828
829        let array = buffer
830            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
831            .unwrap();
832        let dict = array
833            .as_any()
834            .downcast_ref::<DictionaryArray<Int32Type>>()
835            .unwrap();
836
837        assert_eq!(dict.data_type(), &dict_type);
838        assert_eq!(dict.values().data_type(), &ArrowType::Binary);
839    }
840
841    #[test]
842    fn test_key_upper_bound_skip_scan_when_bound_fits_otherwise_scan() {
843        let dict_type =
844            ArrowType::Dictionary(Box::new(ArrowType::Int32), Box::new(ArrowType::Utf8));
845        let dict_5 = Arc::new(StringArray::from(vec!["a", "b", "c", "d", "e"])) as ArrayRef;
846
847        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
848        buffer
849            .as_keys_with_bit_width(&dict_5, Some(2))
850            .unwrap()
851            .extend_from_slice(&[0, 1, 2, 3]);
852        assert!(matches!(
853            buffer,
854            DictionaryBuffer::Dict {
855                key_upper_bound: Some(4),
856                ..
857            }
858        ));
859        let array = buffer
860            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
861            .unwrap();
862        assert_eq!(array.len(), 4);
863
864        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
865        buffer
866            .as_keys_with_bit_width(&dict_5, Some(4))
867            .unwrap()
868            .extend_from_slice(&[0, 10]);
869        let err = buffer
870            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
871            .unwrap_err()
872            .to_string();
873        assert!(err.contains("dictionary key beyond bounds"), "{err}");
874
875        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
876        buffer
877            .as_keys_with_bit_width(&dict_5, None)
878            .unwrap()
879            .extend_from_slice(&[0, 7]);
880        buffer
881            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
882            .unwrap_err();
883
884        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
885        buffer
886            .as_keys_with_bit_width(&dict_5, Some(2))
887            .unwrap()
888            .extend_from_slice(&[0, 1]);
889        buffer
890            .as_keys_with_bit_width(&dict_5, Some(4))
891            .unwrap()
892            .extend_from_slice(&[10]);
893        assert!(matches!(
894            buffer,
895            DictionaryBuffer::Dict {
896                key_upper_bound: Some(16),
897                ..
898            }
899        ));
900        buffer
901            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
902            .unwrap_err();
903    }
904}