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, downcast_integer};
23use arrow_array::{
24    ArrayRef, FixedSizeBinaryArray, OffsetSizeTrait, cast::AsArray, make_array,
25    types::ArrowDictionaryKeyType,
26};
27use arrow_buffer::{ArrowNativeType, Buffer, MutableBuffer};
28use arrow_data::ArrayDataBuilder;
29use arrow_schema::DataType as ArrowType;
30use hashbrown::HashMap as HbHashMap;
31use hashbrown::hash_map::Entry;
32use std::hash::{BuildHasher, Hasher};
33use std::mem::size_of;
34use std::ptr::write_unaligned;
35use std::slice::{from_raw_parts, from_raw_parts_mut};
36use std::sync::Arc;
37
38/// An array of variable length byte arrays that are potentially dictionary encoded
39/// and can be converted into a corresponding [`ArrayRef`]
40pub enum DictionaryBuffer<K: ArrowNativeType, V: OffsetSizeTrait> {
41    Dict { keys: Vec<K>, values: ArrayRef },
42    Values { values: OffsetBuffer<V> },
43}
44
45impl<K: ArrowNativeType + Ord, V: OffsetSizeTrait> DictionaryBuffer<K, V> {
46    #[cfg_attr(not(test), expect(unused))]
47    pub fn len(&self) -> usize {
48        match self {
49            Self::Dict { keys, .. } => keys.len(),
50            Self::Values { values } => values.len(),
51        }
52    }
53
54    /// Returns a mutable reference to a keys array
55    ///
56    /// Returns None if the dictionary needs to be recomputed
57    ///
58    /// # Panics
59    ///
60    /// Panics if the dictionary is too large for `K`
61    pub fn as_keys(&mut self, dictionary: &ArrayRef) -> Option<&mut Vec<K>> {
62        assert!(K::from_usize(dictionary.len()).is_some());
63
64        match self {
65            Self::Dict { keys, values } => {
66                // Need to discard fat pointer for equality check
67                // - https://stackoverflow.com/a/67114787
68                // - https://github.com/rust-lang/rust/issues/46139
69                let values_ptr = std::ptr::from_ref(values.as_ref()).cast::<()>();
70                let dict_ptr = std::ptr::from_ref(dictionary.as_ref()).cast::<()>();
71                if values_ptr == dict_ptr {
72                    Some(keys)
73                } else if keys.is_empty() {
74                    *values = Arc::clone(dictionary);
75                    Some(keys)
76                } else {
77                    None
78                }
79            }
80            Self::Values { values } if values.is_empty() => {
81                *self = Self::Dict {
82                    keys: Default::default(),
83                    values: Arc::clone(dictionary),
84                };
85                match self {
86                    Self::Dict { keys, .. } => Some(keys),
87                    Self::Values { .. } => unreachable!(),
88                }
89            }
90            Self::Values { .. } => None,
91        }
92    }
93
94    /// Returns a mutable reference to a values array
95    ///
96    /// If this is currently dictionary encoded, this will convert from the
97    /// dictionary encoded representation
98    pub fn spill_values(&mut self) -> Result<&mut OffsetBuffer<V>> {
99        match self {
100            Self::Values { values } => Ok(values),
101            Self::Dict { keys, values } => {
102                let mut spilled = OffsetBuffer::with_capacity(0);
103                let data = values.to_data();
104                let dict_buffers = data.buffers();
105                let dict_offsets = dict_buffers[0].typed_data::<V>();
106                let dict_values = dict_buffers[1].as_slice();
107
108                if values.is_empty() {
109                    // If dictionary is empty, zero pad offsets
110                    spilled.offsets.resize(keys.len() + 1, V::default());
111                } else {
112                    // Note: at this point null positions will have arbitrary dictionary keys
113                    // and this will hydrate them to the corresponding byte array. This is
114                    // likely sub-optimal, as we would prefer zero length null "slots", but
115                    // spilling is already a degenerate case and so it is unclear if this is
116                    // worth optimising for, e.g. by keeping a null mask around
117                    spilled.extend_from_dictionary(keys.as_slice(), dict_offsets, dict_values)?;
118                }
119
120                *self = Self::Values { values: spilled };
121                match self {
122                    Self::Values { values } => Ok(values),
123                    Self::Dict { .. } => unreachable!(),
124                }
125            }
126        }
127    }
128
129    /// Converts this into an [`ArrayRef`] with the provided `data_type` and `null_buffer`
130    pub fn into_array(
131        self,
132        null_buffer: Option<Buffer>,
133        data_type: &ArrowType,
134        hash_scratch: &mut MutableBuffer,
135    ) -> Result<ArrayRef> {
136        assert!(matches!(data_type, ArrowType::Dictionary(_, _)));
137
138        match self {
139            Self::Dict { keys, values } => {
140                // Validate keys unless dictionary is empty
141                if !values.is_empty() {
142                    let min = K::from_usize(0).unwrap();
143                    let max = K::from_usize(values.len()).unwrap();
144
145                    // using copied and fold gets auto-vectorized since rust 1.70
146                    // all/any would allow early exit on invalid values
147                    // but in the happy case all values have to be checked anyway
148                    if !keys
149                        .as_slice()
150                        .iter()
151                        .copied()
152                        .fold(true, |a, x| a && x >= min && x < max)
153                    {
154                        return Err(general_err!(
155                            "dictionary key beyond bounds of dictionary: 0..{}",
156                            values.len()
157                        ));
158                    }
159                }
160
161                let ArrowType::Dictionary(_, value_type) = data_type else {
162                    unreachable!()
163                };
164                let values = if let ArrowType::FixedSizeBinary(size) = **value_type {
165                    let binary = values.as_binary::<i32>();
166                    Arc::new(FixedSizeBinaryArray::new(
167                        size,
168                        binary.values().clone(),
169                        binary.nulls().cloned(),
170                    )) as _
171                } else {
172                    values
173                };
174
175                let builder = ArrayDataBuilder::new(data_type.clone())
176                    .len(keys.len())
177                    .add_buffer(Buffer::from_vec(keys))
178                    .add_child_data(values.into_data())
179                    .null_bit_buffer(null_buffer);
180
181                let data = match cfg!(debug_assertions) {
182                    true => builder.build().unwrap(),
183                    false => unsafe { builder.build_unchecked() },
184                };
185
186                Ok(make_array(data))
187            }
188            Self::Values { values } => {
189                let (key_type, value_type) = match data_type {
190                    ArrowType::Dictionary(k, v) => (k, v.as_ref().clone()),
191                    _ => unreachable!(),
192                };
193
194                hash_byte_slices(&values.offsets, &values.values, hash_scratch);
195                let hashes = hashes_as_u64(hash_scratch);
196
197                pack_values_from_offsets(key_type, &value_type, &values, hashes, null_buffer)
198            }
199        }
200    }
201}
202
203impl<K: ArrowNativeType, V: OffsetSizeTrait> ValuesBuffer for DictionaryBuffer<K, V> {
204    fn with_capacity(capacity: usize) -> Self {
205        Self::Values {
206            values: OffsetBuffer::with_capacity(capacity),
207        }
208    }
209
210    fn reserve_exact(&mut self, additional: usize) {
211        match self {
212            Self::Dict { keys, .. } => keys.reserve_exact(additional),
213            Self::Values { values, .. } => values.reserve_exact(additional),
214        }
215    }
216
217    fn pad_nulls(
218        &mut self,
219        read_offset: usize,
220        values_read: usize,
221        levels_read: usize,
222        valid_mask: &[u8],
223    ) -> Result<()> {
224        match self {
225            Self::Dict { keys, .. } => {
226                keys.resize(read_offset + levels_read, K::default());
227                keys.pad_nulls(read_offset, values_read, levels_read, valid_mask)
228            }
229            Self::Values { values, .. } => {
230                values.pad_nulls(read_offset, values_read, levels_read, valid_mask)
231            }
232        }
233    }
234}
235
236macro_rules! offsets_dict_helper {
237    ($k:ty, $key_type:ident, $value_type:ident, $values:ident, $hashes:ident, $null_buffer:ident) => {
238        pack_values_from_offsets_impl::<$k, _>(
239            $values,
240            $hashes,
241            $null_buffer,
242            $key_type,
243            $value_type,
244        )
245    };
246}
247
248fn pack_values_from_offsets<V: OffsetSizeTrait>(
249    key_type: &ArrowType,
250    value_type: &ArrowType,
251    values: &OffsetBuffer<V>,
252    hashes: &[u64],
253    null_buffer: Option<Buffer>,
254) -> Result<ArrayRef> {
255    downcast_integer! {
256        key_type => (offsets_dict_helper, key_type, value_type, values, hashes, null_buffer),
257        _ => unreachable!(),
258    }
259}
260
261// Avoids double-hashing: keys are already high-quality u64 hashes from ahash,
262// so we pass them through directly rather than re-hashing inside the HashMap.
263struct PassthroughHasher(u64);
264impl std::hash::Hasher for PassthroughHasher {
265    fn finish(&self) -> u64 {
266        self.0
267    }
268    fn write(&mut self, _: &[u8]) {
269        unreachable!()
270    }
271    fn write_u64(&mut self, value: u64) {
272        self.0 = value;
273    }
274}
275#[derive(Default)]
276struct BuildPassthroughHasher;
277impl std::hash::BuildHasher for BuildPassthroughHasher {
278    type Hasher = PassthroughHasher;
279    fn build_hasher(&self) -> PassthroughHasher {
280        PassthroughHasher(0)
281    }
282}
283
284/// Builds a [`DictionaryArray`] directly from a flat [`OffsetBuffer`] using pre-computed
285/// hashes to deduplicate values in a single pass, avoiding the intermediate StringArray
286/// materialization
287fn pack_values_from_offsets_impl<K: ArrowDictionaryKeyType, V: OffsetSizeTrait>(
288    offset_buffer: &OffsetBuffer<V>,
289    hashes: &[u64],
290    null_buffer: Option<Buffer>,
291    key_type: &ArrowType,
292    value_type: &ArrowType,
293) -> Result<ArrayRef> {
294    let dict_type = ArrowType::Dictionary(Box::new(key_type.clone()), Box::new(value_type.clone()));
295    let num_values = offset_buffer.len();
296
297    let mut keys: Vec<K::Native> = Vec::with_capacity(num_values);
298    let mut unique_offsets: Vec<V> = Vec::with_capacity(num_values + 1);
299    unique_offsets.push(V::default());
300    let mut unique_bytes: Vec<u8> = Vec::with_capacity(offset_buffer.values.len());
301
302    let mut dedup: HbHashMap<u64, (usize, usize), BuildPassthroughHasher> =
303        HbHashMap::with_capacity_and_hasher(num_values, BuildPassthroughHasher);
304
305    // Tracks colliding values (same hash, different bytes) so repeated occurrences
306    // are deduplicated rather than inserted as new entries. Empty in the common case.
307    let mut collision_overflow: Vec<(u64, usize, usize)> = Vec::new();
308
309    for (input_idx, &hash) in hashes.iter().enumerate() {
310        let byte_start = offset_buffer.offsets[input_idx].as_usize();
311        let byte_end = offset_buffer.offsets[input_idx + 1].as_usize();
312        let bytes = &offset_buffer.values[byte_start..byte_end];
313
314        let output_idx = match dedup.entry(hash) {
315            Entry::Occupied(entry) => {
316                let (first_input_idx, existing_output_idx) = *entry.get();
317                let first_start = offset_buffer.offsets[first_input_idx].as_usize();
318                let first_end = offset_buffer.offsets[first_input_idx + 1].as_usize();
319                if &offset_buffer.values[first_start..first_end] == bytes {
320                    existing_output_idx
321                } else {
322                    // True hash collision — check overflow list before inserting.
323                    let existing = collision_overflow
324                        .iter()
325                        .find(|&&(entry_hash, collision_input_idx, _)| {
326                            if entry_hash != hash {
327                                return false;
328                            }
329                            let collision_start =
330                                offset_buffer.offsets[collision_input_idx].as_usize();
331                            let collision_end =
332                                offset_buffer.offsets[collision_input_idx + 1].as_usize();
333                            &offset_buffer.values[collision_start..collision_end] == bytes
334                        })
335                        .map(|&(_, _, collision_output_idx)| collision_output_idx);
336
337                    match existing {
338                        Some(collision_output_idx) => collision_output_idx,
339                        None => {
340                            let new_output_idx = unique_offsets.len() - 1;
341                            unique_bytes.extend_from_slice(bytes);
342                            let new_end = V::from_usize(unique_bytes.len()).ok_or_else(|| {
343                                general_err!("offset overflow building dictionary")
344                            })?;
345                            unique_offsets.push(new_end);
346                            collision_overflow.push((hash, input_idx, new_output_idx));
347                            new_output_idx
348                        }
349                    }
350                }
351            }
352            Entry::Vacant(entry) => {
353                let output_idx = unique_offsets.len() - 1;
354                unique_bytes.extend_from_slice(bytes);
355                let new_end = V::from_usize(unique_bytes.len())
356                    .ok_or_else(|| general_err!("offset overflow building dictionary"))?;
357                unique_offsets.push(new_end);
358                entry.insert((input_idx, output_idx));
359                output_idx
360            }
361        };
362
363        let key = K::Native::from_usize(output_idx)
364            .ok_or_else(|| general_err!("dictionary key overflow"))?;
365        keys.push(key);
366    }
367
368    let num_unique = unique_offsets.len() - 1;
369
370    // SAFETY: buffers are constructed directly from typed Vecs above; offsets are
371    // monotonically non-decreasing and bounded by unique_bytes.len(), and all
372    // key values are within 0..num_unique, so the invariants Arrow requires hold.
373    let value_data = unsafe {
374        arrow_data::ArrayData::builder(value_type.clone())
375            .len(num_unique)
376            .add_buffer(Buffer::from_vec(unique_offsets))
377            .add_buffer(Buffer::from_vec(unique_bytes))
378            .build_unchecked()
379    };
380
381    // SAFETY: keys are within 0..num_unique and value_data is valid.
382    let dict_array: DictionaryArray<K> = unsafe {
383        arrow_data::ArrayData::builder(dict_type)
384            .len(keys.len())
385            .add_buffer(Buffer::from_vec(keys))
386            .add_child_data(value_data)
387            .null_bit_buffer(null_buffer)
388            .build_unchecked()
389            .into()
390    };
391
392    Ok(Arc::new(dict_array))
393}
394
395fn hash_byte_slices<I: ArrowNativeType>(offsets: &[I], values: &[u8], scratch: &mut MutableBuffer) {
396    let count = offsets.len().saturating_sub(1);
397    scratch.clear();
398    scratch.resize(count * size_of::<u64>(), 0u8);
399
400    let state = RandomState::new();
401
402    // SAFETY: MutableBuffer is 64-byte aligned; scratch is sized to exactly count * size_of::<u64>()
403    #[expect(
404        clippy::cast_ptr_alignment,
405        reason = "MutableBuffer is 64-byte aligned"
406    )]
407    let hash_slots = unsafe { from_raw_parts_mut(scratch.as_mut_ptr().cast::<u64>(), count) };
408
409    for idx in 0..count {
410        let start = offsets[idx].as_usize();
411        let end = offsets[idx + 1].as_usize();
412        let mut hasher = state.build_hasher();
413        hasher.write(&values[start..end]);
414        // SAFETY: idx is within 0..count
415        unsafe { write_unaligned(hash_slots.as_mut_ptr().add(idx), hasher.finish()) };
416    }
417}
418
419#[inline]
420fn hashes_as_u64(scratch: &[u8]) -> &[u64] {
421    let n = scratch.len() / size_of::<u64>();
422    // SAFETY: scratch was written as u64s by hash_byte_slices, and MutableBuffer is 64-byte aligned
423    #[expect(
424        clippy::cast_ptr_alignment,
425        reason = "MutableBuffer is 64-byte aligned"
426    )]
427    unsafe {
428        from_raw_parts(scratch.as_ptr().cast::<u64>(), n)
429    }
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435    use arrow::compute::cast;
436    use arrow_array::StringArray;
437    use arrow_array::types::*;
438
439    #[test]
440    fn test_dictionary_buffer() {
441        let dict_type =
442            ArrowType::Dictionary(Box::new(ArrowType::Int32), Box::new(ArrowType::Utf8));
443
444        let d1: ArrayRef = Arc::new(StringArray::from(vec!["hello", "world", "", "a", "b"]));
445
446        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
447
448        // Read some data preserving the dictionary
449        let values = &[1, 0, 3, 2, 4];
450        buffer.as_keys(&d1).unwrap().extend_from_slice(values);
451
452        let mut valid = vec![false, false, true, true, false, true, true, true];
453        let valid_buffer = Buffer::from_iter(valid.iter().copied());
454        buffer
455            .pad_nulls(0, values.len(), valid.len(), valid_buffer.as_slice())
456            .unwrap();
457
458        // Read some data not preserving the dictionary
459
460        let values = buffer.spill_values().unwrap();
461        let read_offset = values.len();
462        values.try_push(b"bingo", false).unwrap();
463        values.try_push(b"bongo", false).unwrap();
464
465        valid.extend_from_slice(&[false, false, true, false, true]);
466        let null_buffer = Buffer::from_iter(valid.iter().copied());
467        buffer
468            .pad_nulls(read_offset, 2, 5, null_buffer.as_slice())
469            .unwrap();
470
471        assert_eq!(buffer.len(), 13);
472        let split = std::mem::replace(&mut buffer, DictionaryBuffer::with_capacity(0));
473
474        let array = split
475            .into_array(Some(null_buffer), &dict_type, &mut MutableBuffer::new(0))
476            .unwrap();
477        assert_eq!(array.data_type(), &dict_type);
478
479        let strings = cast(&array, &ArrowType::Utf8).unwrap();
480        let strings = strings.as_any().downcast_ref::<StringArray>().unwrap();
481        assert_eq!(
482            strings.iter().collect::<Vec<_>>(),
483            vec![
484                None,
485                None,
486                Some("world"),
487                Some("hello"),
488                None,
489                Some("a"),
490                Some(""),
491                Some("b"),
492                None,
493                None,
494                Some("bingo"),
495                None,
496                Some("bongo")
497            ]
498        );
499
500        // Can recreate with new dictionary as values is empty
501        assert!(matches!(&buffer, DictionaryBuffer::Values { .. }));
502        assert_eq!(buffer.len(), 0);
503        let d2 = Arc::new(StringArray::from(vec!["bingo", ""])) as ArrayRef;
504        buffer
505            .as_keys(&d2)
506            .unwrap()
507            .extend_from_slice(&[0, 1, 0, 1]);
508
509        let array = std::mem::replace(&mut buffer, DictionaryBuffer::with_capacity(0))
510            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
511            .unwrap();
512        assert_eq!(array.data_type(), &dict_type);
513
514        let strings = cast(&array, &ArrowType::Utf8).unwrap();
515        let strings = strings.as_any().downcast_ref::<StringArray>().unwrap();
516        assert_eq!(
517            strings.iter().collect::<Vec<_>>(),
518            vec![Some("bingo"), Some(""), Some("bingo"), Some("")]
519        );
520
521        // Can recreate with new dictionary as keys empty
522        assert!(matches!(&buffer, DictionaryBuffer::Values { .. }));
523        assert_eq!(buffer.len(), 0);
524        let d3 = Arc::new(StringArray::from(vec!["bongo"])) as ArrayRef;
525        buffer.as_keys(&d3).unwrap().extend_from_slice(&[0, 0]);
526
527        // Cannot change dictionary as keys not empty
528        let d4 = Arc::new(StringArray::from(vec!["bananas"])) as ArrayRef;
529        assert!(buffer.as_keys(&d4).is_none());
530    }
531
532    #[test]
533    fn test_validates_keys() {
534        let dict_type =
535            ArrowType::Dictionary(Box::new(ArrowType::Int32), Box::new(ArrowType::Utf8));
536
537        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
538        let d = Arc::new(StringArray::from(vec!["", "f"])) as ArrayRef;
539        buffer.as_keys(&d).unwrap().extend_from_slice(&[0, 2, 0]);
540
541        let err = buffer
542            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
543            .unwrap_err()
544            .to_string();
545        assert!(
546            err.contains("dictionary key beyond bounds of dictionary: 0..2"),
547            "{}",
548            err
549        );
550
551        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
552        let d = Arc::new(StringArray::from(vec![""])) as ArrayRef;
553        buffer.as_keys(&d).unwrap().extend_from_slice(&[0, 1, 0]);
554
555        let err = buffer.spill_values().unwrap_err().to_string();
556        assert!(
557            err.contains("dictionary key beyond bounds of dictionary: 0..1"),
558            "{}",
559            err
560        );
561    }
562
563    /// A dictionary requested with LargeUtf8 values must come back with LargeUtf8, not Utf8.
564    #[test]
565    fn test_values_path_large_utf8_type() {
566        let dict_type =
567            ArrowType::Dictionary(Box::new(ArrowType::Int32), Box::new(ArrowType::LargeUtf8));
568        let mut buffer = DictionaryBuffer::<i32, i64>::with_capacity(0);
569        let values = buffer.spill_values().unwrap();
570        for s in ["foo", "bar", "foo"] {
571            values.try_push(s.as_bytes(), false).unwrap();
572        }
573
574        let array = buffer
575            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
576            .unwrap();
577        let dict = array
578            .as_any()
579            .downcast_ref::<DictionaryArray<Int32Type>>()
580            .unwrap();
581
582        assert_eq!(dict.data_type(), &dict_type);
583        assert_eq!(dict.values().data_type(), &ArrowType::LargeUtf8);
584    }
585
586    /// 128 unique strings fill Int8 keys to capacity (last index = 127 = i8::MAX).
587    /// A 129th unique string overflows and must return an error.
588    #[test]
589    fn test_int8_key_overflow_boundary() {
590        let dict_type = ArrowType::Dictionary(Box::new(ArrowType::Int8), Box::new(ArrowType::Utf8));
591        let mut scratch = MutableBuffer::new(0);
592
593        {
594            let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
595            let values = buffer.spill_values().unwrap();
596            for i in 0u32..128 {
597                values
598                    .try_push(format!("val{i}").as_bytes(), false)
599                    .unwrap();
600            }
601            buffer
602                .into_array(None, &dict_type, &mut scratch)
603                .expect("128 unique strings must fit: last index is 127 = i8::MAX");
604        }
605
606        {
607            let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
608            let values = buffer.spill_values().unwrap();
609            for i in 0u32..129 {
610                values
611                    .try_push(format!("val{i}").as_bytes(), false)
612                    .unwrap();
613            }
614            let err = buffer
615                .into_array(None, &dict_type, &mut scratch)
616                .unwrap_err()
617                .to_string();
618            assert!(
619                err.contains("dictionary key overflow"),
620                "expected 'dictionary key overflow', got: {err}"
621            );
622        }
623    }
624
625    /// A colliding string repeated after its initial collision must reuse its key,
626    /// not be inserted again as a new dictionary entry.
627    #[test]
628    fn test_hash_collision_deduplication() {
629        let key_type = ArrowType::Int32;
630        let value_type = ArrowType::Utf8;
631
632        // "alpha", "beta", "beta" — all forced to share the same hash.
633        let mut ob = OffsetBuffer::<i32>::with_capacity(3);
634        ob.try_push(b"alpha", false).unwrap();
635        ob.try_push(b"beta", false).unwrap();
636        ob.try_push(b"beta", false).unwrap();
637        let hashes = [0xdeadbeef_u64; 3];
638
639        let array = pack_values_from_offsets_impl::<Int32Type, i32>(
640            &ob,
641            &hashes,
642            None,
643            &key_type,
644            &value_type,
645        )
646        .unwrap();
647
648        let dict = array
649            .as_any()
650            .downcast_ref::<DictionaryArray<Int32Type>>()
651            .unwrap();
652
653        assert_eq!(dict.values().len(), 2);
654        let keys: Vec<i32> = dict.keys().values().iter().copied().collect();
655        assert_eq!(keys, vec![0, 1, 1]);
656    }
657
658    /// A dictionary requested with Binary values must come back with Binary, not Utf8.
659    #[test]
660    fn test_values_path_binary_type() {
661        let dict_type =
662            ArrowType::Dictionary(Box::new(ArrowType::Int32), Box::new(ArrowType::Binary));
663        let mut buffer = DictionaryBuffer::<i32, i32>::with_capacity(0);
664        let values = buffer.spill_values().unwrap();
665        for s in [b"abc".as_ref(), b"\x00\xff", b"abc"] {
666            values.try_push(s, false).unwrap();
667        }
668
669        let array = buffer
670            .into_array(None, &dict_type, &mut MutableBuffer::new(0))
671            .unwrap();
672        let dict = array
673            .as_any()
674            .downcast_ref::<DictionaryArray<Int32Type>>()
675            .unwrap();
676
677        assert_eq!(dict.data_type(), &dict_type);
678        assert_eq!(dict.values().data_type(), &ArrowType::Binary);
679    }
680}