Skip to main content

datafusion_common/
hash_utils.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
18//! Functionality used both on logical and physical plans
19
20use arrow::array::types::{IntervalDayTime, IntervalMonthDayNano};
21use arrow::array::*;
22#[cfg(not(feature = "force_hash_collisions"))]
23use arrow::compute::take;
24use arrow::datatypes::*;
25#[cfg(not(feature = "force_hash_collisions"))]
26use arrow::{downcast_dictionary_array, downcast_primitive_array};
27use foldhash::fast::FixedState;
28#[cfg(not(feature = "force_hash_collisions"))]
29use itertools::Itertools;
30#[cfg(not(feature = "force_hash_collisions"))]
31use std::collections::HashMap;
32use std::hash::{BuildHasher, Hash, Hasher};
33
34/// [`RandomState`] is optimized for speed and suitable for hash tables and
35/// bloom filters. [`QualityRandomState`] is optimized for statistical quality
36/// and suitable for algorithms such as HyperLogLog. The tradeoff is that the
37/// fast variant gives up some statistical quality, while the quality variant
38/// is slightly slower.
39///
40/// See: <https://docs.rs/foldhash/0.2.0/src/foldhash/lib.rs.html#17-21>
41pub type RandomState = FixedState;
42pub type QualityRandomState = foldhash::quality::FixedState;
43
44/// Fixed quality hash state used by HyperLogLog sketches.
45///
46/// The seed is part of the HLL wire/storage semantics: serialized sketches only
47/// remain mergeable if every producer uses the same hash state.
48pub const HLL_RANDOM_STATE: QualityRandomState = QualityRandomState::with_seed(0);
49
50/// Hash state used by [`create_hashes`].
51///
52/// Multi-column hashing folds the previous column hash into a fresh hasher
53/// before hashing the next column. This trait keeps that seeded hasher in the
54/// same foldhash tier as the top-level hash state.
55pub trait HashState: BuildHasher {
56    type SeededState: BuildHasher;
57
58    fn seeded_state(&self, seed: u64) -> Self::SeededState;
59}
60
61impl HashState for FixedState {
62    type SeededState = foldhash::fast::SeedableRandomState;
63
64    fn seeded_state(&self, seed: u64) -> Self::SeededState {
65        foldhash::fast::SeedableRandomState::with_seed(
66            seed,
67            foldhash::SharedSeed::global_fixed(),
68        )
69    }
70}
71
72impl HashState for foldhash::quality::FixedState {
73    type SeededState = foldhash::quality::SeedableRandomState;
74
75    fn seeded_state(&self, seed: u64) -> Self::SeededState {
76        foldhash::quality::SeedableRandomState::with_seed(
77            seed,
78            foldhash::SharedSeed::global_fixed(),
79        )
80    }
81}
82
83#[cfg(not(feature = "force_hash_collisions"))]
84use crate::cast::{
85    as_binary_view_array, as_boolean_array, as_fixed_size_list_array,
86    as_generic_binary_array, as_large_list_array, as_large_list_view_array,
87    as_list_array, as_list_view_array, as_map_array, as_string_array,
88    as_string_view_array, as_struct_array, as_union_array,
89};
90use crate::error::Result;
91use crate::error::{_internal_datafusion_err, _internal_err};
92use std::cell::RefCell;
93
94mod build_hasher;
95
96// Combines two hashes into one hash
97#[inline]
98pub fn combine_hashes(l: u64, r: u64) -> u64 {
99    let hash = (17 * 37u64).wrapping_add(l);
100    hash.wrapping_mul(37).wrapping_add(r)
101}
102
103/// Maximum size for the thread-local hash buffer before truncation (4MB = 524,288 u64 elements).
104/// The goal of this is to avoid unbounded memory growth that would appear as a memory leak.
105/// We allow temporary allocations beyond this size, but after use the buffer is truncated
106/// to this size.
107const MAX_BUFFER_SIZE: usize = 524_288;
108
109thread_local! {
110    /// Thread-local buffer for hash computations to avoid repeated allocations.
111    /// The buffer is reused across calls and truncated if it exceeds MAX_BUFFER_SIZE.
112    /// Defaults to a capacity of 8192 u64 elements which is the default batch size.
113    /// This corresponds to 64KB of memory.
114    static HASH_BUFFER: RefCell<Vec<u64>> = const { RefCell::new(Vec::new()) };
115}
116
117/// Creates hashes for the given arrays using a thread-local buffer, then calls the provided callback
118/// with an immutable reference to the computed hashes.
119///
120/// This function manages a thread-local buffer to avoid repeated allocations. The buffer is automatically
121/// truncated if it exceeds `MAX_BUFFER_SIZE` after use.
122///
123/// # Arguments
124/// * `arrays` - The arrays to hash (must contain at least one array)
125/// * `random_state` - The random state for hashing
126/// * `callback` - A function that receives an immutable reference to the hash slice and returns a result
127///
128/// # Errors
129/// Returns an error if:
130/// - No arrays are provided
131/// - The function is called reentrantly (i.e., the callback invokes `with_hashes` again on the same thread)
132/// - The function is called during or after thread destruction
133///
134/// # Example
135/// ```ignore
136/// use datafusion_common::hash_utils::{with_hashes, RandomState};
137/// use arrow::array::{Int32Array, ArrayRef};
138/// use std::sync::Arc;
139///
140/// let array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3]));
141/// let random_state = RandomState::default();
142///
143/// let result = with_hashes([&array], &random_state, |hashes| {
144///     // Use the hashes here
145///     Ok(hashes.len())
146/// })?;
147/// ```
148pub fn with_hashes<I, T, F, R>(
149    arrays: I,
150    random_state: &impl HashState,
151    callback: F,
152) -> Result<R>
153where
154    I: IntoIterator<Item = T>,
155    T: AsDynArray,
156    F: FnOnce(&[u64]) -> Result<R>,
157{
158    // Peek at the first array to determine buffer size without fully collecting
159    let mut iter = arrays.into_iter().peekable();
160
161    // Get the required size from the first array
162    let required_size = match iter.peek() {
163        Some(arr) => arr.as_dyn_array().len(),
164        None => return _internal_err!("with_hashes requires at least one array"),
165    };
166
167    HASH_BUFFER.try_with(|cell| {
168        let mut buffer = cell.try_borrow_mut()
169            .map_err(|_| _internal_datafusion_err!("with_hashes cannot be called reentrantly on the same thread"))?;
170
171        // Ensure buffer has sufficient length, clearing old values
172        buffer.clear();
173        buffer.resize(required_size, 0);
174
175        // Create hashes in the buffer - this consumes the iterator
176        create_hashes(iter, random_state, &mut buffer[..required_size])?;
177
178        // Execute the callback with an immutable slice
179        let result = callback(&buffer[..required_size])?;
180
181        // Cleanup: truncate if buffer grew too large
182        if buffer.capacity() > MAX_BUFFER_SIZE {
183            buffer.truncate(MAX_BUFFER_SIZE);
184            buffer.shrink_to_fit();
185        }
186
187        Ok(result)
188    }).map_err(|_| _internal_datafusion_err!("with_hashes cannot access thread-local storage during or after thread destruction"))?
189}
190
191/// Creates hashes for the given arrays using a thread-local buffer and a custom
192/// hash builder, then calls the provided callback with the computed hashes.
193///
194/// Hash compatibility with [`with_hashes`] follows the rules documented on
195/// [`create_hashes_with_hasher`].
196pub fn with_hashes_with_hasher<I, T, F, R, S>(
197    arrays: I,
198    hash_builder: &S,
199    callback: F,
200) -> Result<R>
201where
202    I: IntoIterator<Item = T>,
203    T: AsDynArray,
204    F: FnOnce(&[u64]) -> Result<R>,
205    S: BuildHasher,
206{
207    build_hasher::with_hashes_with_hasher(arrays, hash_builder, callback)
208}
209
210#[cfg(not(feature = "force_hash_collisions"))]
211fn hash_null<S: HashState>(
212    random_state: &S,
213    hashes_buffer: &'_ mut [u64],
214    multi_col: bool,
215) {
216    if multi_col {
217        hashes_buffer.iter_mut().for_each(|hash| {
218            // stable hash for null value
219            *hash = combine_hashes(random_state.hash_one(1), *hash);
220        })
221    } else {
222        hashes_buffer.iter_mut().for_each(|hash| {
223            *hash = random_state.hash_one(1);
224        })
225    }
226}
227
228pub trait HashValue {
229    fn hash_one<S: BuildHasher>(&self, state: &S) -> u64;
230    /// Write this value into an existing hasher (same data as `hash_one`).
231    fn hash_write(&self, hasher: &mut impl Hasher);
232}
233
234impl<T: HashValue + ?Sized> HashValue for &T {
235    fn hash_one<S: BuildHasher>(&self, state: &S) -> u64 {
236        T::hash_one(self, state)
237    }
238    fn hash_write(&self, hasher: &mut impl Hasher) {
239        T::hash_write(self, hasher)
240    }
241}
242
243macro_rules! hash_value {
244    ($($t:ty),+) => {
245        $(impl HashValue for $t {
246            fn hash_one<S: BuildHasher>(&self, state: &S) -> u64 {
247                state.hash_one(self)
248            }
249            fn hash_write(&self, hasher: &mut impl Hasher) {
250                Hash::hash(self, hasher)
251            }
252        })+
253    };
254}
255hash_value!(i8, i16, i32, i64, i128, i256, u8, u16, u32, u64, u128);
256hash_value!(bool, str, [u8], IntervalDayTime, IntervalMonthDayNano);
257
258macro_rules! hash_float_value {
259    ($(($t:ty, $i:ty)),+) => {
260        $(impl HashValue for $t {
261            fn hash_one<S: BuildHasher>(&self, state: &S) -> u64 {
262                // +0.0 and -0.0 differ only in the sign bit but compare equal
263                // under IEEE 754; normalize -0.0 → +0.0 so Hash agrees with Eq.
264                let bits = <$i>::from_ne_bytes(self.to_ne_bytes());
265                let bits = if bits << 1 == 0 { 0 } else { bits };
266                state.hash_one(bits)
267            }
268            fn hash_write(&self, hasher: &mut impl Hasher) {
269                let bits = <$i>::from_ne_bytes(self.to_ne_bytes());
270                let bits: $i = if bits << 1 == 0 { 0 } else { bits };
271                hasher.write(&bits.to_ne_bytes())
272            }
273        })+
274    };
275}
276hash_float_value!((half::f16, u16), (f32, u32), (f64, u64));
277
278#[cfg(not(feature = "force_hash_collisions"))]
279trait ChildHashing {
280    fn create_hashes<I, T>(&self, arrays: I, hashes_buffer: &mut [u64]) -> Result<()>
281    where
282        I: IntoIterator<Item = T>,
283        T: AsDynArray;
284}
285
286#[cfg(not(feature = "force_hash_collisions"))]
287struct HashStateChildHashing<'a, S> {
288    hash_state: &'a S,
289}
290
291#[cfg(not(feature = "force_hash_collisions"))]
292impl<S: HashState> ChildHashing for HashStateChildHashing<'_, S> {
293    fn create_hashes<I, T>(&self, arrays: I, hashes_buffer: &mut [u64]) -> Result<()>
294    where
295        I: IntoIterator<Item = T>,
296        T: AsDynArray,
297    {
298        create_hashes(arrays, self.hash_state, hashes_buffer).map(|_| ())
299    }
300}
301
302/// Builds hash values of PrimitiveArray and writes them into `hashes_buffer`
303/// If `rehash==true` this folds the existing hash into the hasher state
304/// and hashes only the new value (avoiding a separate combine step).
305#[cfg(not(feature = "force_hash_collisions"))]
306fn hash_array_primitive<T>(
307    array: &PrimitiveArray<T>,
308    random_state: &impl HashState,
309    hashes_buffer: &mut [u64],
310    rehash: bool,
311) where
312    T: ArrowPrimitiveType<Native: HashValue>,
313{
314    assert_eq!(
315        hashes_buffer.len(),
316        array.len(),
317        "hashes_buffer and array should be of equal length"
318    );
319
320    if array.null_count() == 0 {
321        if rehash {
322            for (hash, &value) in hashes_buffer.iter_mut().zip(array.values().iter()) {
323                let mut hasher = random_state.seeded_state(*hash).build_hasher();
324                value.hash_write(&mut hasher);
325                *hash = hasher.finish();
326            }
327        } else {
328            for (hash, &value) in hashes_buffer.iter_mut().zip(array.values().iter()) {
329                *hash = value.hash_one(random_state);
330            }
331        }
332    } else if rehash {
333        for i in array.nulls().unwrap().valid_indices() {
334            let value = unsafe { array.value_unchecked(i) };
335            let mut hasher = random_state.seeded_state(hashes_buffer[i]).build_hasher();
336            value.hash_write(&mut hasher);
337            hashes_buffer[i] = hasher.finish();
338        }
339    } else {
340        for i in array.nulls().unwrap().valid_indices() {
341            let value = unsafe { array.value_unchecked(i) };
342            hashes_buffer[i] = value.hash_one(random_state);
343        }
344    }
345}
346
347/// Hashes one array into the `hashes_buffer`
348/// If `rehash==true` this combines the previous hash value in the buffer
349/// with the new hash using `combine_hashes`
350#[cfg(not(feature = "force_hash_collisions"))]
351fn hash_array<T>(
352    array: &T,
353    random_state: &impl HashState,
354    hashes_buffer: &mut [u64],
355    rehash: bool,
356) where
357    T: ArrayAccessor,
358    T::Item: HashValue,
359{
360    assert_eq!(
361        hashes_buffer.len(),
362        array.len(),
363        "hashes_buffer and array should be of equal length"
364    );
365
366    if array.null_count() == 0 {
367        if rehash {
368            for (i, hash) in hashes_buffer.iter_mut().enumerate() {
369                let value = unsafe { array.value_unchecked(i) };
370                *hash = combine_hashes(value.hash_one(random_state), *hash);
371            }
372        } else {
373            for (i, hash) in hashes_buffer.iter_mut().enumerate() {
374                let value = unsafe { array.value_unchecked(i) };
375                *hash = value.hash_one(random_state);
376            }
377        }
378    } else if rehash {
379        for i in array.nulls().unwrap().valid_indices() {
380            let value = unsafe { array.value_unchecked(i) };
381            hashes_buffer[i] =
382                combine_hashes(value.hash_one(random_state), hashes_buffer[i]);
383        }
384    } else {
385        for i in array.nulls().unwrap().valid_indices() {
386            let value = unsafe { array.value_unchecked(i) };
387            hashes_buffer[i] = value.hash_one(random_state);
388        }
389    }
390}
391
392/// Hash a StringView or BytesView array
393///
394/// Templated to optimize inner loop based on presence of nulls and external buffers.
395///
396/// HAS_NULLS: do we have to check null in the inner loop
397/// HAS_BUFFERS: if true, array has external buffers; if false, all strings are inlined/ less then 12 bytes
398/// REHASH: if true, combining with existing hash, otherwise initializing
399#[cfg(not(feature = "force_hash_collisions"))]
400#[inline(never)]
401fn hash_string_view_array_inner<
402    T: ByteViewType,
403    const HAS_NULLS: bool,
404    const HAS_BUFFERS: bool,
405    const REHASH: bool,
406>(
407    array: &GenericByteViewArray<T>,
408    random_state: &impl HashState,
409    hashes_buffer: &mut [u64],
410) {
411    assert_eq!(
412        hashes_buffer.len(),
413        array.len(),
414        "hashes_buffer and array should be of equal length"
415    );
416
417    let buffers = array.data_buffers();
418    let view_bytes = |view_len: u32, view: u128| {
419        let view = ByteView::from(view);
420        let offset = view.offset as usize;
421        // SAFETY: view is a valid view as it came from the array
422        unsafe {
423            let data = buffers.get_unchecked(view.buffer_index as usize);
424            data.get_unchecked(offset..offset + view_len as usize)
425        }
426    };
427
428    let hashes_and_views = hashes_buffer.iter_mut().zip(array.views().iter());
429    for (i, (hash, &v)) in hashes_and_views.enumerate() {
430        if HAS_NULLS && array.is_null(i) {
431            continue;
432        }
433        let view_len = v as u32;
434        // all views are inlined, no need to access external buffers
435        if !HAS_BUFFERS || view_len <= 12 {
436            if REHASH {
437                let mut hasher = random_state.seeded_state(*hash).build_hasher();
438                v.hash_write(&mut hasher);
439                *hash = hasher.finish();
440            } else {
441                *hash = v.hash_one(random_state);
442            }
443            continue;
444        }
445        // view is not inlined, so we need to hash the bytes as well
446        let value = view_bytes(view_len, v);
447        if REHASH {
448            let mut hasher = random_state.seeded_state(*hash).build_hasher();
449            value.hash_write(&mut hasher);
450            *hash = hasher.finish();
451        } else {
452            *hash = value.hash_one(random_state);
453        }
454    }
455}
456
457/// Builds hash values for array views and writes them into `hashes_buffer`
458/// If `rehash==true` this combines the previous hash value in the buffer
459/// with the new hash using `combine_hashes`
460#[cfg(not(feature = "force_hash_collisions"))]
461fn hash_generic_byte_view_array<T: ByteViewType>(
462    array: &GenericByteViewArray<T>,
463    random_state: &impl HashState,
464    hashes_buffer: &mut [u64],
465    rehash: bool,
466) {
467    // instantiate the correct version based on presence of nulls and external buffers
468    match (
469        array.null_count() != 0,
470        !array.data_buffers().is_empty(),
471        rehash,
472    ) {
473        // no nulls or buffers ==> hash the inlined views directly
474        // don't call the inner function as Rust seems better able to inline this simpler code (2-3% faster)
475        (false, false, false) => {
476            for (hash, &view) in hashes_buffer.iter_mut().zip(array.views().iter()) {
477                *hash = view.hash_one(random_state);
478            }
479        }
480        (false, false, true) => {
481            for (hash, &view) in hashes_buffer.iter_mut().zip(array.views().iter()) {
482                let mut hasher = random_state.seeded_state(*hash).build_hasher();
483                view.hash_write(&mut hasher);
484                *hash = hasher.finish();
485            }
486        }
487        (false, true, false) => hash_string_view_array_inner::<T, false, true, false>(
488            array,
489            random_state,
490            hashes_buffer,
491        ),
492        (false, true, true) => hash_string_view_array_inner::<T, false, true, true>(
493            array,
494            random_state,
495            hashes_buffer,
496        ),
497        (true, false, false) => hash_string_view_array_inner::<T, true, false, false>(
498            array,
499            random_state,
500            hashes_buffer,
501        ),
502        (true, false, true) => hash_string_view_array_inner::<T, true, false, true>(
503            array,
504            random_state,
505            hashes_buffer,
506        ),
507        (true, true, false) => hash_string_view_array_inner::<T, true, true, false>(
508            array,
509            random_state,
510            hashes_buffer,
511        ),
512        (true, true, true) => hash_string_view_array_inner::<T, true, true, true>(
513            array,
514            random_state,
515            hashes_buffer,
516        ),
517    }
518}
519
520/// Scatter precomputed dictionary value hashes to key positions.
521///
522/// Uses const generics to eliminate runtime branching in the hot loop:
523/// - `HAS_NULL_KEYS`: Whether to check for null dictionary keys
524/// - `HAS_NULL_VALUES`: Whether to check for null dictionary values
525/// - `MULTI_COL`: Whether to combine with existing hash (true) or initialize (false)
526#[cfg(not(feature = "force_hash_collisions"))]
527#[inline(never)]
528fn hash_dictionary_scatter<
529    K: ArrowDictionaryKeyType,
530    const HAS_NULL_KEYS: bool,
531    const HAS_NULL_VALUES: bool,
532    const MULTI_COL: bool,
533>(
534    array: &DictionaryArray<K>,
535    dict_hashes: &[u64],
536    hashes_buffer: &mut [u64],
537) {
538    let dict_values = array.values();
539    if HAS_NULL_KEYS {
540        for (hash, key) in hashes_buffer.iter_mut().zip(array.keys().iter()) {
541            if let Some(key) = key {
542                let idx = key.as_usize();
543                if !HAS_NULL_VALUES || dict_values.is_valid(idx) {
544                    if MULTI_COL {
545                        *hash = combine_hashes(dict_hashes[idx], *hash);
546                    } else {
547                        *hash = dict_hashes[idx];
548                    }
549                }
550            }
551        }
552    } else {
553        for (hash, key) in hashes_buffer.iter_mut().zip(array.keys().values()) {
554            let idx = key.as_usize();
555            if !HAS_NULL_VALUES || dict_values.is_valid(idx) {
556                if MULTI_COL {
557                    *hash = combine_hashes(dict_hashes[idx], *hash);
558                } else {
559                    *hash = dict_hashes[idx];
560                }
561            }
562        }
563    }
564}
565
566#[cfg(not(feature = "force_hash_collisions"))]
567fn dispatch_dictionary_scatter<K: ArrowDictionaryKeyType>(
568    array: &DictionaryArray<K>,
569    dict_hashes: &[u64],
570    hashes_buffer: &mut [u64],
571    multi_col: bool,
572) {
573    let has_null_keys = array.keys().null_count() != 0;
574    let has_null_values = array.values().null_count() != 0;
575
576    match (has_null_keys, has_null_values, multi_col) {
577        (false, false, false) => hash_dictionary_scatter::<K, false, false, false>(
578            array,
579            dict_hashes,
580            hashes_buffer,
581        ),
582        (false, false, true) => hash_dictionary_scatter::<K, false, false, true>(
583            array,
584            dict_hashes,
585            hashes_buffer,
586        ),
587        (false, true, false) => hash_dictionary_scatter::<K, false, true, false>(
588            array,
589            dict_hashes,
590            hashes_buffer,
591        ),
592        (false, true, true) => hash_dictionary_scatter::<K, false, true, true>(
593            array,
594            dict_hashes,
595            hashes_buffer,
596        ),
597        (true, false, false) => hash_dictionary_scatter::<K, true, false, false>(
598            array,
599            dict_hashes,
600            hashes_buffer,
601        ),
602        (true, false, true) => hash_dictionary_scatter::<K, true, false, true>(
603            array,
604            dict_hashes,
605            hashes_buffer,
606        ),
607        (true, true, false) => hash_dictionary_scatter::<K, true, true, false>(
608            array,
609            dict_hashes,
610            hashes_buffer,
611        ),
612        (true, true, true) => hash_dictionary_scatter::<K, true, true, true>(
613            array,
614            dict_hashes,
615            hashes_buffer,
616        ),
617    }
618}
619
620/// Hash the values in a dictionary array.
621#[cfg(not(feature = "force_hash_collisions"))]
622fn hash_dictionary<K: ArrowDictionaryKeyType>(
623    array: &DictionaryArray<K>,
624    random_state: &impl HashState,
625    hashes_buffer: &mut [u64],
626    multi_col: bool,
627) -> Result<()> {
628    // Hash each dictionary value once, and then use that computed
629    // hash for each key value to avoid a potentially expensive
630    // redundant hashing for large dictionary elements (e.g. strings)
631    let dict_values = array.values();
632    let mut dict_hashes = vec![0; dict_values.len()];
633    create_hashes([dict_values], random_state, &mut dict_hashes)?;
634    dispatch_dictionary_scatter(array, &dict_hashes, hashes_buffer, multi_col);
635    Ok(())
636}
637
638#[cfg(not(feature = "force_hash_collisions"))]
639fn hash_dictionary_with_child_hashing<K: ArrowDictionaryKeyType>(
640    array: &DictionaryArray<K>,
641    child_hashing: &impl ChildHashing,
642    hashes_buffer: &mut [u64],
643    multi_col: bool,
644) -> Result<()> {
645    let dict_values = array.values();
646    let mut dict_hashes = vec![0; dict_values.len()];
647    child_hashing.create_hashes([dict_values], &mut dict_hashes)?;
648    dispatch_dictionary_scatter(array, &dict_hashes, hashes_buffer, multi_col);
649    Ok(())
650}
651
652#[cfg(not(feature = "force_hash_collisions"))]
653fn hash_struct_array(
654    array: &StructArray,
655    child_hashing: &impl ChildHashing,
656    hashes_buffer: &mut [u64],
657) -> Result<()> {
658    let nulls = array.nulls();
659    let row_len = array.len();
660
661    // Create hashes for each row that combines the hashes over all the column at that row.
662    let mut values_hashes = vec![0u64; row_len];
663    child_hashing.create_hashes(array.columns(), &mut values_hashes)?;
664
665    // Separate paths to avoid allocating Vec when there are no nulls
666    if let Some(nulls) = nulls {
667        for i in nulls.valid_indices() {
668            let hash = &mut hashes_buffer[i];
669            *hash = combine_hashes(*hash, values_hashes[i]);
670        }
671    } else {
672        for i in 0..row_len {
673            let hash = &mut hashes_buffer[i];
674            *hash = combine_hashes(*hash, values_hashes[i]);
675        }
676    }
677
678    Ok(())
679}
680
681// only adding this `cfg` b/c this function is only used with this `cfg`
682#[cfg(not(feature = "force_hash_collisions"))]
683fn hash_map_array(
684    array: &MapArray,
685    child_hashing: &impl ChildHashing,
686    hashes_buffer: &mut [u64],
687) -> Result<()> {
688    let nulls = array.nulls();
689    let offsets = array.offsets();
690
691    // Create hashes for each entry in each row
692    let first_offset = offsets.first().copied().unwrap_or_default() as usize;
693    let last_offset = offsets.last().copied().unwrap_or_default() as usize;
694    let entries_len = last_offset - first_offset;
695
696    // Only hash the entries that are actually referenced
697    let mut values_hashes = vec![0u64; entries_len];
698    let entries = array.entries();
699    let sliced_columns: Vec<ArrayRef> = entries
700        .columns()
701        .iter()
702        .map(|col| col.slice(first_offset, entries_len))
703        .collect();
704    child_hashing.create_hashes(&sliced_columns, &mut values_hashes)?;
705
706    // Combine the hashes for entries on each row with each other and previous hash for that row
707    // Adjust indices by first_offset since values_hashes is sliced starting from first_offset
708    if let Some(nulls) = nulls {
709        for (i, (start, stop)) in offsets.iter().zip(offsets.iter().skip(1)).enumerate() {
710            if nulls.is_valid(i) {
711                let hash = &mut hashes_buffer[i];
712                for values_hash in &values_hashes
713                    [start.as_usize() - first_offset..stop.as_usize() - first_offset]
714                {
715                    *hash = combine_hashes(*hash, *values_hash);
716                }
717            }
718        }
719    } else {
720        for (i, (start, stop)) in offsets.iter().zip(offsets.iter().skip(1)).enumerate() {
721            let hash = &mut hashes_buffer[i];
722            for values_hash in &values_hashes
723                [start.as_usize() - first_offset..stop.as_usize() - first_offset]
724            {
725                *hash = combine_hashes(*hash, *values_hash);
726            }
727        }
728    }
729
730    Ok(())
731}
732
733#[cfg(not(feature = "force_hash_collisions"))]
734fn hash_list_array<OffsetSize>(
735    array: &GenericListArray<OffsetSize>,
736    child_hashing: &impl ChildHashing,
737    hashes_buffer: &mut [u64],
738) -> Result<()>
739where
740    OffsetSize: OffsetSizeTrait,
741{
742    // In case values is sliced, hash only the bytes used by the offsets of this ListArray
743    let first_offset = array.value_offsets().first().cloned().unwrap_or_default();
744    let last_offset = array.value_offsets().last().cloned().unwrap_or_default();
745    let value_bytes_len = (last_offset - first_offset).as_usize();
746    let mut values_hashes = vec![0u64; value_bytes_len];
747    child_hashing.create_hashes(
748        [array
749            .values()
750            .slice(first_offset.as_usize(), value_bytes_len)],
751        &mut values_hashes,
752    )?;
753
754    if array.null_count() > 0 {
755        for (i, (start, stop)) in array.value_offsets().iter().tuple_windows().enumerate()
756        {
757            if array.is_valid(i) {
758                let hash = &mut hashes_buffer[i];
759                for values_hash in &values_hashes[(*start - first_offset).as_usize()
760                    ..(*stop - first_offset).as_usize()]
761                {
762                    *hash = combine_hashes(*hash, *values_hash);
763                }
764            }
765        }
766    } else {
767        for ((start, stop), hash) in array
768            .value_offsets()
769            .iter()
770            .tuple_windows()
771            .zip(hashes_buffer.iter_mut())
772        {
773            for values_hash in &values_hashes
774                [(*start - first_offset).as_usize()..(*stop - first_offset).as_usize()]
775            {
776                *hash = combine_hashes(*hash, *values_hash);
777            }
778        }
779    }
780    Ok(())
781}
782
783#[cfg(not(feature = "force_hash_collisions"))]
784fn hash_list_view_array<OffsetSize>(
785    array: &GenericListViewArray<OffsetSize>,
786    child_hashing: &impl ChildHashing,
787    hashes_buffer: &mut [u64],
788) -> Result<()>
789where
790    OffsetSize: OffsetSizeTrait,
791{
792    let values = array.values();
793    let offsets = array.value_offsets();
794    let sizes = array.value_sizes();
795    let nulls = array.nulls();
796    let mut values_hashes = vec![0u64; values.len()];
797    child_hashing.create_hashes([values], &mut values_hashes)?;
798    if let Some(nulls) = nulls {
799        for (i, (offset, size)) in offsets.iter().zip(sizes.iter()).enumerate() {
800            if nulls.is_valid(i) {
801                let hash = &mut hashes_buffer[i];
802                let start = offset.as_usize();
803                let end = start + size.as_usize();
804                for values_hash in &values_hashes[start..end] {
805                    *hash = combine_hashes(*hash, *values_hash);
806                }
807            }
808        }
809    } else {
810        for (i, (offset, size)) in offsets.iter().zip(sizes.iter()).enumerate() {
811            let hash = &mut hashes_buffer[i];
812            let start = offset.as_usize();
813            let end = start + size.as_usize();
814            for values_hash in &values_hashes[start..end] {
815                *hash = combine_hashes(*hash, *values_hash);
816            }
817        }
818    }
819    Ok(())
820}
821
822#[cfg(not(feature = "force_hash_collisions"))]
823fn hash_union_array(
824    array: &UnionArray,
825    child_hashing: &impl ChildHashing,
826    hashes_buffer: &mut [u64],
827) -> Result<()> {
828    let DataType::Union(union_fields, _mode) = array.data_type() else {
829        unreachable!()
830    };
831
832    if array.is_dense() {
833        // Dense union: children only contain values of their type, so they're already compact.
834        // Use the default hashing approach which is efficient for dense unions.
835        hash_union_array_default(array, union_fields, child_hashing, hashes_buffer)
836    } else {
837        // Sparse union: each child has the same length as the union array.
838        // Optimization: only hash the elements that are actually referenced by type_ids,
839        // instead of hashing all K*N elements (where K = num types, N = array length).
840        hash_sparse_union_array(array, union_fields, child_hashing, hashes_buffer)
841    }
842}
843
844/// Default hashing for union arrays - hashes all elements of each child array fully.
845///
846/// This approach works for both dense and sparse union arrays:
847/// - Dense unions: children are compact (each child only contains values of that type)
848/// - Sparse unions: children have the same length as the union array
849///
850/// For sparse unions with 3+ types, the optimized take/scatter approach in
851/// `hash_sparse_union_array` is more efficient, but for 1-2 types or dense unions,
852/// this simpler approach is preferred.
853#[cfg(not(feature = "force_hash_collisions"))]
854fn hash_union_array_default(
855    array: &UnionArray,
856    union_fields: &UnionFields,
857    child_hashing: &impl ChildHashing,
858    hashes_buffer: &mut [u64],
859) -> Result<()> {
860    let mut child_hashes: HashMap<i8, Vec<u64>> =
861        HashMap::with_capacity(union_fields.len());
862
863    // Hash each child array fully
864    for (type_id, _field) in union_fields.iter() {
865        let child = array.child(type_id);
866        let mut child_hash_buffer = vec![0; child.len()];
867        child_hashing.create_hashes([child], &mut child_hash_buffer)?;
868
869        child_hashes.insert(type_id, child_hash_buffer);
870    }
871
872    // Combine hashes for each row using the appropriate child offset
873    // For dense unions: value_offset points to the actual position in the child
874    // For sparse unions: value_offset equals the row index
875    #[expect(clippy::needless_range_loop)]
876    for i in 0..array.len() {
877        let type_id = array.type_id(i);
878        let child_offset = array.value_offset(i);
879
880        let child_hash = child_hashes.get(&type_id).expect("invalid type_id");
881        hashes_buffer[i] = combine_hashes(hashes_buffer[i], child_hash[child_offset]);
882    }
883
884    Ok(())
885}
886
887/// Hash a sparse union array.
888/// Sparse unions have child arrays with the same length as the union array.
889/// For 3+ types, we optimize by only hashing the N elements that are actually used
890/// (via take/scatter), instead of hashing all K*N elements.
891///
892/// For 1-2 types, the overhead of take/scatter outweighs the benefit, so we use
893/// the default approach of hashing all children (same as dense unions).
894#[cfg(not(feature = "force_hash_collisions"))]
895fn hash_sparse_union_array(
896    array: &UnionArray,
897    union_fields: &UnionFields,
898    child_hashing: &impl ChildHashing,
899    hashes_buffer: &mut [u64],
900) -> Result<()> {
901    use std::collections::HashMap;
902
903    // For 1-2 types, the take/scatter overhead isn't worth it.
904    // Fall back to the default approach (same as dense union).
905    if union_fields.len() <= 2 {
906        return hash_union_array_default(
907            array,
908            union_fields,
909            child_hashing,
910            hashes_buffer,
911        );
912    }
913
914    let type_ids = array.type_ids();
915
916    // Group indices by type_id
917    let mut indices_by_type: HashMap<i8, Vec<u32>> = HashMap::new();
918    for (i, &type_id) in type_ids.iter().enumerate() {
919        indices_by_type.entry(type_id).or_default().push(i as u32);
920    }
921
922    // For each type, extract only the needed elements, hash them, and scatter back
923    for (type_id, _field) in union_fields.iter() {
924        if let Some(indices) = indices_by_type.get(&type_id) {
925            if indices.is_empty() {
926                continue;
927            }
928
929            let child = array.child(type_id);
930            let indices_array = UInt32Array::from(indices.clone());
931
932            // Extract only the elements we need using take()
933            let filtered = take(child.as_ref(), &indices_array, None)?;
934
935            // Hash the filtered array
936            let mut filtered_hashes = vec![0u64; filtered.len()];
937            child_hashing.create_hashes([&filtered], &mut filtered_hashes)?;
938
939            // Scatter hashes back to correct positions
940            for (hash, &idx) in filtered_hashes.iter().zip(indices.iter()) {
941                hashes_buffer[idx as usize] =
942                    combine_hashes(hashes_buffer[idx as usize], *hash);
943            }
944        }
945    }
946
947    Ok(())
948}
949
950#[cfg(not(feature = "force_hash_collisions"))]
951fn hash_fixed_list_array(
952    array: &FixedSizeListArray,
953    child_hashing: &impl ChildHashing,
954    hashes_buffer: &mut [u64],
955) -> Result<()> {
956    let values = array.values();
957    let value_length = array.value_length() as usize;
958    let nulls = array.nulls();
959    let mut values_hashes = vec![0u64; values.len()];
960    child_hashing.create_hashes([values], &mut values_hashes)?;
961    if let Some(nulls) = nulls {
962        for i in 0..array.len() {
963            if nulls.is_valid(i) {
964                let hash = &mut hashes_buffer[i];
965                for values_hash in
966                    &values_hashes[i * value_length..(i + 1) * value_length]
967                {
968                    *hash = combine_hashes(*hash, *values_hash);
969                }
970            }
971        }
972    } else {
973        for i in 0..array.len() {
974            let hash = &mut hashes_buffer[i];
975            for values_hash in &values_hashes[i * value_length..(i + 1) * value_length] {
976                *hash = combine_hashes(*hash, *values_hash);
977            }
978        }
979    }
980    Ok(())
981}
982
983/// Inner hash function for RunArray
984#[inline(never)]
985#[cfg(not(feature = "force_hash_collisions"))]
986fn hash_run_array_inner<
987    R: RunEndIndexType,
988    C: ChildHashing + ?Sized,
989    const HAS_NULL_VALUES: bool,
990    const REHASH: bool,
991>(
992    array: &RunArray<R>,
993    child_hashing: &C,
994    hashes_buffer: &mut [u64],
995) -> Result<()> {
996    // We find the relevant runs that cover potentially sliced arrays, so we can only hash those
997    // values. Then we find the runs that refer to the original runs and ensure that we apply
998    // hashes correctly to the sliced, whether sliced at the start, end, or both.
999    let array_offset = array.offset();
1000    let array_len = array.len();
1001
1002    if array_len == 0 {
1003        return Ok(());
1004    }
1005
1006    let run_ends = array.run_ends();
1007    let run_ends_values = run_ends.values();
1008    let values = array.values();
1009
1010    let start_physical_index = array.get_start_physical_index();
1011    // get_end_physical_index returns the inclusive last index, but we need the exclusive range end
1012    // for the operations we use below.
1013    let end_physical_index = array.get_end_physical_index() + 1;
1014
1015    let sliced_values = values.slice(
1016        start_physical_index,
1017        end_physical_index - start_physical_index,
1018    );
1019    let mut values_hashes = vec![0u64; sliced_values.len()];
1020    child_hashing
1021        .create_hashes(std::slice::from_ref(&sliced_values), &mut values_hashes)?;
1022
1023    let mut start_in_slice = 0;
1024    for (adjusted_physical_index, &absolute_run_end) in run_ends_values
1025        [start_physical_index..end_physical_index]
1026        .iter()
1027        .enumerate()
1028    {
1029        let absolute_run_end = absolute_run_end.as_usize();
1030        let end_in_slice = (absolute_run_end - array_offset).min(array_len);
1031
1032        if HAS_NULL_VALUES && sliced_values.is_null(adjusted_physical_index) {
1033            start_in_slice = end_in_slice;
1034            continue;
1035        }
1036
1037        let value_hash = values_hashes[adjusted_physical_index];
1038        let run_slice = &mut hashes_buffer[start_in_slice..end_in_slice];
1039
1040        if REHASH {
1041            for hash in run_slice.iter_mut() {
1042                *hash = combine_hashes(value_hash, *hash);
1043            }
1044        } else {
1045            run_slice.fill(value_hash);
1046        }
1047
1048        start_in_slice = end_in_slice;
1049    }
1050
1051    Ok(())
1052}
1053
1054#[cfg(not(feature = "force_hash_collisions"))]
1055fn hash_run_array<R: RunEndIndexType>(
1056    array: &RunArray<R>,
1057    child_hashing: &impl ChildHashing,
1058    hashes_buffer: &mut [u64],
1059    rehash: bool,
1060) -> Result<()> {
1061    let has_null_values = array.values().null_count() != 0;
1062
1063    match (has_null_values, rehash) {
1064        (false, false) => hash_run_array_inner::<R, _, false, false>(
1065            array,
1066            child_hashing,
1067            hashes_buffer,
1068        ),
1069        (false, true) => {
1070            hash_run_array_inner::<R, _, false, true>(array, child_hashing, hashes_buffer)
1071        }
1072        (true, false) => {
1073            hash_run_array_inner::<R, _, true, false>(array, child_hashing, hashes_buffer)
1074        }
1075        (true, true) => {
1076            hash_run_array_inner::<R, _, true, true>(array, child_hashing, hashes_buffer)
1077        }
1078    }
1079}
1080
1081/// Internal helper function that hashes a single array and either initializes or combines
1082/// the hash values in the buffer.
1083#[cfg(not(feature = "force_hash_collisions"))]
1084fn hash_single_array(
1085    array: &dyn Array,
1086    random_state: &impl HashState,
1087    hashes_buffer: &mut [u64],
1088    rehash: bool,
1089) -> Result<()> {
1090    downcast_primitive_array! {
1091        array => hash_array_primitive(array, random_state, hashes_buffer, rehash),
1092        DataType::Null => hash_null(random_state, hashes_buffer, rehash),
1093        DataType::Boolean => hash_array(&as_boolean_array(array)?, random_state, hashes_buffer, rehash),
1094        DataType::Utf8 => hash_array(&as_string_array(array)?, random_state, hashes_buffer, rehash),
1095        DataType::Utf8View => hash_generic_byte_view_array(as_string_view_array(array)?, random_state, hashes_buffer, rehash),
1096        DataType::LargeUtf8 => hash_array(&as_largestring_array(array), random_state, hashes_buffer, rehash),
1097        DataType::Binary => hash_array(&as_generic_binary_array::<i32>(array)?, random_state, hashes_buffer, rehash),
1098        DataType::BinaryView => hash_generic_byte_view_array(as_binary_view_array(array)?, random_state, hashes_buffer, rehash),
1099        DataType::LargeBinary => hash_array(&as_generic_binary_array::<i64>(array)?, random_state, hashes_buffer, rehash),
1100        DataType::FixedSizeBinary(_) => {
1101            let array: &FixedSizeBinaryArray = array.as_any().downcast_ref().unwrap();
1102            hash_array(&array, random_state, hashes_buffer, rehash)
1103        }
1104        DataType::Dictionary(_, _) => downcast_dictionary_array! {
1105            array => hash_dictionary(array, random_state, hashes_buffer, rehash)?,
1106            _ => unreachable!()
1107        }
1108        DataType::Struct(_) => {
1109            let array = as_struct_array(array)?;
1110            let child_hashing = HashStateChildHashing {
1111                hash_state: random_state,
1112            };
1113            hash_struct_array(array, &child_hashing, hashes_buffer)?;
1114        }
1115        DataType::List(_) => {
1116            let array = as_list_array(array)?;
1117            let child_hashing = HashStateChildHashing {
1118                hash_state: random_state,
1119            };
1120            hash_list_array(array, &child_hashing, hashes_buffer)?;
1121        }
1122        DataType::LargeList(_) => {
1123            let array = as_large_list_array(array)?;
1124            let child_hashing = HashStateChildHashing {
1125                hash_state: random_state,
1126            };
1127            hash_list_array(array, &child_hashing, hashes_buffer)?;
1128        }
1129        DataType::ListView(_) => {
1130            let array = as_list_view_array(array)?;
1131            let child_hashing = HashStateChildHashing {
1132                hash_state: random_state,
1133            };
1134            hash_list_view_array(array, &child_hashing, hashes_buffer)?;
1135        }
1136        DataType::LargeListView(_) => {
1137            let array = as_large_list_view_array(array)?;
1138            let child_hashing = HashStateChildHashing {
1139                hash_state: random_state,
1140            };
1141            hash_list_view_array(array, &child_hashing, hashes_buffer)?;
1142        }
1143        DataType::Map(_, _) => {
1144            let array = as_map_array(array)?;
1145            let child_hashing = HashStateChildHashing {
1146                hash_state: random_state,
1147            };
1148            hash_map_array(array, &child_hashing, hashes_buffer)?;
1149        }
1150        DataType::FixedSizeList(_,_) => {
1151            let array = as_fixed_size_list_array(array)?;
1152            let child_hashing = HashStateChildHashing {
1153                hash_state: random_state,
1154            };
1155            hash_fixed_list_array(array, &child_hashing, hashes_buffer)?;
1156        }
1157        DataType::Union(_, _) => {
1158            let array = as_union_array(array)?;
1159            let child_hashing = HashStateChildHashing {
1160                hash_state: random_state,
1161            };
1162            hash_union_array(array, &child_hashing, hashes_buffer)?;
1163        }
1164        DataType::RunEndEncoded(_, _) => downcast_run_array! {
1165            array => {
1166                let child_hashing = HashStateChildHashing {
1167                    hash_state: random_state,
1168                };
1169                hash_run_array(array, &child_hashing, hashes_buffer, rehash)?
1170            },
1171            _ => unreachable!()
1172        }
1173        _ => {
1174            // This is internal because we should have caught this before.
1175            return _internal_err!(
1176                "Unsupported data type in hasher: {}",
1177                array.data_type()
1178            );
1179        }
1180    }
1181    Ok(())
1182}
1183
1184/// Test version of `hash_single_array` that forces all hashes to collide to zero.
1185#[cfg(feature = "force_hash_collisions")]
1186fn hash_single_array(
1187    _array: &dyn Array,
1188    _random_state: &impl HashState,
1189    hashes_buffer: &mut [u64],
1190    _rehash: bool,
1191) -> Result<()> {
1192    for hash in hashes_buffer.iter_mut() {
1193        *hash = 0
1194    }
1195    Ok(())
1196}
1197
1198/// Something that can be returned as a `&dyn Array`.
1199///
1200/// We want `create_hashes` to accept either `&dyn Array` or `ArrayRef`,
1201/// and this seems the best way to do so.
1202///
1203/// We tried having it accept `AsRef<dyn Array>`
1204/// but that is not implemented for and cannot be implemented for
1205/// `&dyn Array` so callers that have the latter would not be able
1206/// to call `create_hashes` directly. This shim trait makes it possible.
1207pub trait AsDynArray {
1208    fn as_dyn_array(&self) -> &dyn Array;
1209}
1210
1211impl AsDynArray for dyn Array {
1212    fn as_dyn_array(&self) -> &dyn Array {
1213        self
1214    }
1215}
1216
1217impl AsDynArray for &dyn Array {
1218    fn as_dyn_array(&self) -> &dyn Array {
1219        *self
1220    }
1221}
1222
1223impl AsDynArray for ArrayRef {
1224    fn as_dyn_array(&self) -> &dyn Array {
1225        self.as_ref()
1226    }
1227}
1228
1229impl AsDynArray for &ArrayRef {
1230    fn as_dyn_array(&self) -> &dyn Array {
1231        self.as_ref()
1232    }
1233}
1234
1235/// Creates hash values for every row, based on the values in the columns.
1236///
1237/// The number of rows to hash is determined by `hashes_buffer.len()`.
1238/// `hashes_buffer` should be pre-sized appropriately.
1239pub fn create_hashes<'a, I, T>(
1240    arrays: I,
1241    random_state: &impl HashState,
1242    hashes_buffer: &'a mut [u64],
1243) -> Result<&'a mut [u64]>
1244where
1245    I: IntoIterator<Item = T>,
1246    T: AsDynArray,
1247{
1248    for (i, array) in arrays.into_iter().enumerate() {
1249        // combine hashes with `combine_hashes` for all columns besides the first
1250        let rehash = i >= 1;
1251        hash_single_array(array.as_dyn_array(), random_state, hashes_buffer, rehash)?;
1252    }
1253    Ok(hashes_buffer)
1254}
1255
1256/// Creates hash values for every row using a caller-provided hash builder.
1257///
1258/// The number of rows to hash is determined by `hashes_buffer.len()`.
1259/// `hashes_buffer` should be pre-sized appropriately.
1260///
1261/// # Hash compatibility
1262///
1263/// Hash values are not guaranteed to be bit-for-bit identical to those from
1264/// [`create_hashes`], even when `hash_builder` also implements [`HashState`].
1265/// The optimized [`HashState`] path seeds the hasher from the previous hash
1266/// when rehashing some primitive and byte-view values, whereas this function
1267/// combines independently computed hashes. Use one API consistently if hashes
1268/// are persisted or exchanged.
1269pub fn create_hashes_with_hasher<'a, I, T, S>(
1270    arrays: I,
1271    hash_builder: &S,
1272    hashes_buffer: &'a mut [u64],
1273) -> Result<&'a mut [u64]>
1274where
1275    I: IntoIterator<Item = T>,
1276    T: AsDynArray,
1277    S: BuildHasher,
1278{
1279    build_hasher::create_hashes_with_hasher(arrays, hash_builder, hashes_buffer)
1280}
1281
1282#[cfg(test)]
1283mod tests {
1284    #[cfg(not(feature = "force_hash_collisions"))]
1285    use std::hash::{BuildHasherDefault, Hasher};
1286    use std::sync::Arc;
1287
1288    use arrow::array::*;
1289    #[cfg(not(feature = "force_hash_collisions"))]
1290    use arrow::datatypes::*;
1291
1292    use super::*;
1293
1294    #[cfg(not(feature = "force_hash_collisions"))]
1295    #[derive(Default)]
1296    struct TestHasher(u64);
1297
1298    #[cfg(not(feature = "force_hash_collisions"))]
1299    impl Hasher for TestHasher {
1300        fn finish(&self) -> u64 {
1301            self.0
1302        }
1303
1304        fn write(&mut self, bytes: &[u8]) {
1305            for byte in bytes {
1306                self.0 = self.0.wrapping_mul(37).wrapping_add(u64::from(*byte));
1307            }
1308        }
1309    }
1310
1311    #[test]
1312    fn create_hashes_for_decimal_array() -> Result<()> {
1313        let array = vec![1, 2, 3, 4]
1314            .into_iter()
1315            .map(Some)
1316            .collect::<Decimal128Array>()
1317            .with_precision_and_scale(20, 3)
1318            .unwrap();
1319        let array_ref: ArrayRef = Arc::new(array);
1320        let random_state = RandomState::with_seed(0);
1321        let hashes_buff = &mut vec![0; array_ref.len()];
1322        let hashes = create_hashes(&[array_ref], &random_state, hashes_buff)?;
1323        assert_eq!(hashes.len(), 4);
1324        Ok(())
1325    }
1326
1327    #[test]
1328    fn create_hashes_for_empty_fixed_size_lit() -> Result<()> {
1329        let empty_array = FixedSizeListBuilder::new(StringBuilder::new(), 1).finish();
1330        let random_state = RandomState::with_seed(0);
1331        let hashes_buff = &mut [0; 0];
1332        let hashes = create_hashes(
1333            &[Arc::new(empty_array) as ArrayRef],
1334            &random_state,
1335            hashes_buff,
1336        )?;
1337        assert_eq!(hashes, &Vec::<u64>::new());
1338        Ok(())
1339    }
1340
1341    #[test]
1342    fn create_hashes_for_float_arrays() -> Result<()> {
1343        let f32_arr: ArrayRef =
1344            Arc::new(Float32Array::from(vec![0.12, 0.5, 1f32, 444.7]));
1345        let f64_arr: ArrayRef =
1346            Arc::new(Float64Array::from(vec![0.12, 0.5, 1f64, 444.7]));
1347
1348        let random_state = RandomState::with_seed(0);
1349        let hashes_buff = &mut vec![0; f32_arr.len()];
1350        let hashes = create_hashes(&[f32_arr], &random_state, hashes_buff)?;
1351        assert_eq!(hashes.len(), 4,);
1352
1353        let hashes = create_hashes(&[f64_arr], &random_state, hashes_buff)?;
1354        assert_eq!(hashes.len(), 4,);
1355
1356        Ok(())
1357    }
1358
1359    macro_rules! create_hash_binary {
1360        ($NAME:ident, $ARRAY:ty) => {
1361            #[cfg(not(feature = "force_hash_collisions"))]
1362            #[test]
1363            fn $NAME() {
1364                let binary = [
1365                    Some(b"short".to_byte_slice()),
1366                    None,
1367                    Some(b"long but different 12 bytes string"),
1368                    Some(b"short2"),
1369                    Some(b"Longer than 12 bytes string"),
1370                    Some(b"short"),
1371                    Some(b"Longer than 12 bytes string"),
1372                ];
1373
1374                let binary_array: ArrayRef =
1375                    Arc::new(binary.iter().cloned().collect::<$ARRAY>());
1376
1377                let random_state = RandomState::with_seed(0);
1378
1379                let mut binary_hashes = vec![0; binary.len()];
1380                create_hashes(&[binary_array], &random_state, &mut binary_hashes)
1381                    .unwrap();
1382
1383                // Null values result in a zero hash,
1384                for (val, hash) in binary.iter().zip(binary_hashes.iter()) {
1385                    match val {
1386                        Some(_) => assert_ne!(*hash, 0),
1387                        None => assert_eq!(*hash, 0),
1388                    }
1389                }
1390
1391                // Same values should map to same hash values
1392                assert_eq!(binary[0], binary[5]);
1393                assert_eq!(binary[4], binary[6]);
1394
1395                // different binary should map to different hash values
1396                assert_ne!(binary[0], binary[2]);
1397            }
1398        };
1399    }
1400
1401    create_hash_binary!(binary_array, BinaryArray);
1402    create_hash_binary!(large_binary_array, LargeBinaryArray);
1403    create_hash_binary!(binary_view_array, BinaryViewArray);
1404
1405    #[test]
1406    fn create_hashes_fixed_size_binary() -> Result<()> {
1407        let input_arg = vec![vec![1, 2], vec![5, 6], vec![5, 6]];
1408        let fixed_size_binary_array: ArrayRef =
1409            Arc::new(FixedSizeBinaryArray::try_from_iter(input_arg.into_iter()).unwrap());
1410
1411        let random_state = RandomState::with_seed(0);
1412        let hashes_buff = &mut vec![0; fixed_size_binary_array.len()];
1413        let hashes =
1414            create_hashes(&[fixed_size_binary_array], &random_state, hashes_buff)?;
1415        assert_eq!(hashes.len(), 3,);
1416
1417        Ok(())
1418    }
1419
1420    macro_rules! create_hash_string {
1421        ($NAME:ident, $ARRAY:ty) => {
1422            #[cfg(not(feature = "force_hash_collisions"))]
1423            #[test]
1424            fn $NAME() {
1425                let strings = [
1426                    Some("short"),
1427                    None,
1428                    Some("long but different 12 bytes string"),
1429                    Some("short2"),
1430                    Some("Longer than 12 bytes string"),
1431                    Some("short"),
1432                    Some("Longer than 12 bytes string"),
1433                ];
1434
1435                let string_array: ArrayRef =
1436                    Arc::new(strings.iter().cloned().collect::<$ARRAY>());
1437                let dict_array: ArrayRef = Arc::new(
1438                    strings
1439                        .iter()
1440                        .cloned()
1441                        .collect::<DictionaryArray<Int8Type>>(),
1442                );
1443
1444                let random_state = RandomState::with_seed(0);
1445
1446                let mut string_hashes = vec![0; strings.len()];
1447                create_hashes(&[string_array], &random_state, &mut string_hashes)
1448                    .unwrap();
1449
1450                let mut dict_hashes = vec![0; strings.len()];
1451                create_hashes(&[dict_array], &random_state, &mut dict_hashes).unwrap();
1452
1453                // Null values result in a zero hash,
1454                for (val, hash) in strings.iter().zip(string_hashes.iter()) {
1455                    match val {
1456                        Some(_) => assert_ne!(*hash, 0),
1457                        None => assert_eq!(*hash, 0),
1458                    }
1459                }
1460
1461                // same logical values should hash to the same hash value
1462                assert_eq!(string_hashes, dict_hashes);
1463
1464                // Same values should map to same hash values
1465                assert_eq!(strings[0], strings[5]);
1466                assert_eq!(strings[4], strings[6]);
1467
1468                // different strings should map to different hash values
1469                assert_ne!(strings[0], strings[2]);
1470            }
1471        };
1472    }
1473
1474    create_hash_string!(string_array, StringArray);
1475    create_hash_string!(large_string_array, LargeStringArray);
1476    create_hash_string!(string_view_array, StringArray);
1477    create_hash_string!(dict_string_array, DictionaryArray<Int8Type>);
1478
1479    #[test]
1480    #[cfg(not(feature = "force_hash_collisions"))]
1481    fn create_hashes_for_run_array() -> Result<()> {
1482        let values = Arc::new(Int32Array::from(vec![10, 20, 30]));
1483        let run_ends = Arc::new(Int32Array::from(vec![2, 5, 7]));
1484        let array = Arc::new(RunArray::try_new(&run_ends, values.as_ref()).unwrap());
1485
1486        let random_state = RandomState::with_seed(0);
1487        let hashes_buff = &mut vec![0; array.len()];
1488        let hashes = create_hashes(
1489            &[Arc::clone(&array) as ArrayRef],
1490            &random_state,
1491            hashes_buff,
1492        )?;
1493
1494        assert_eq!(hashes.len(), 7);
1495        assert_eq!(hashes[0], hashes[1]);
1496        assert_eq!(hashes[2], hashes[3]);
1497        assert_eq!(hashes[3], hashes[4]);
1498        assert_eq!(hashes[5], hashes[6]);
1499        assert_ne!(hashes[0], hashes[2]);
1500        assert_ne!(hashes[2], hashes[5]);
1501        assert_ne!(hashes[0], hashes[5]);
1502
1503        Ok(())
1504    }
1505
1506    #[test]
1507    #[cfg(not(feature = "force_hash_collisions"))]
1508    fn create_multi_column_hash_with_run_array() -> Result<()> {
1509        let int_array = Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7]));
1510        let values = Arc::new(StringArray::from(vec!["foo", "bar", "baz"]));
1511        let run_ends = Arc::new(Int32Array::from(vec![2, 5, 7]));
1512        let run_array = Arc::new(RunArray::try_new(&run_ends, values.as_ref()).unwrap());
1513
1514        let random_state = RandomState::with_seed(0);
1515        let mut one_col_hashes = vec![0; int_array.len()];
1516        create_hashes(
1517            &[Arc::clone(&int_array) as ArrayRef],
1518            &random_state,
1519            &mut one_col_hashes,
1520        )?;
1521
1522        let mut two_col_hashes = vec![0; int_array.len()];
1523        create_hashes(
1524            &[
1525                Arc::clone(&int_array) as ArrayRef,
1526                Arc::clone(&run_array) as ArrayRef,
1527            ],
1528            &random_state,
1529            &mut two_col_hashes,
1530        )?;
1531
1532        assert_eq!(one_col_hashes.len(), 7);
1533        assert_eq!(two_col_hashes.len(), 7);
1534        assert_ne!(one_col_hashes, two_col_hashes);
1535
1536        let diff_0_vs_1_one_col = one_col_hashes[0] != one_col_hashes[1];
1537        let diff_0_vs_1_two_col = two_col_hashes[0] != two_col_hashes[1];
1538        assert_eq!(diff_0_vs_1_one_col, diff_0_vs_1_two_col);
1539
1540        let diff_2_vs_3_one_col = one_col_hashes[2] != one_col_hashes[3];
1541        let diff_2_vs_3_two_col = two_col_hashes[2] != two_col_hashes[3];
1542        assert_eq!(diff_2_vs_3_one_col, diff_2_vs_3_two_col);
1543
1544        Ok(())
1545    }
1546
1547    #[test]
1548    #[cfg(not(feature = "force_hash_collisions"))]
1549    fn test_create_hashes_with_custom_hasher() {
1550        let array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 1, 4]));
1551        let hash_builder = BuildHasherDefault::<TestHasher>::default();
1552
1553        let mut custom_hashes = vec![0; array.len()];
1554        create_hashes_with_hasher([&array], &hash_builder, &mut custom_hashes).unwrap();
1555
1556        let random_state = RandomState::with_seed(0);
1557        let mut default_hashes = vec![0; array.len()];
1558        create_hashes([&array], &random_state, &mut default_hashes).unwrap();
1559
1560        assert_eq!(custom_hashes[0], custom_hashes[2]);
1561        assert_ne!(custom_hashes[0], custom_hashes[1]);
1562        assert_ne!(custom_hashes, default_hashes);
1563    }
1564
1565    #[test]
1566    #[cfg(not(feature = "force_hash_collisions"))]
1567    fn test_create_hashes_with_custom_hasher_normalizes_negative_zero() {
1568        let array: ArrayRef = Arc::new(Float64Array::from(vec![0.0, -0.0]));
1569        let hash_builder = BuildHasherDefault::<TestHasher>::default();
1570        let mut hashes = vec![0; array.len()];
1571
1572        create_hashes_with_hasher([&array], &hash_builder, &mut hashes).unwrap();
1573
1574        assert_eq!(hashes[0], hashes[1]);
1575    }
1576
1577    #[test]
1578    #[cfg(not(feature = "force_hash_collisions"))]
1579    fn test_create_hashes_dictionary_with_custom_hasher() {
1580        let strings = [Some("foo"), None, Some("bar"), Some("foo"), None];
1581        let string_array: ArrayRef =
1582            Arc::new(strings.iter().cloned().collect::<StringArray>());
1583        let dict_array: ArrayRef = Arc::new(
1584            strings
1585                .iter()
1586                .cloned()
1587                .collect::<DictionaryArray<Int8Type>>(),
1588        );
1589        let hash_builder = BuildHasherDefault::<TestHasher>::default();
1590
1591        let mut string_hashes = vec![0; strings.len()];
1592        create_hashes_with_hasher([&string_array], &hash_builder, &mut string_hashes)
1593            .unwrap();
1594
1595        let mut dict_hashes = vec![0; strings.len()];
1596        create_hashes_with_hasher([&dict_array], &hash_builder, &mut dict_hashes)
1597            .unwrap();
1598
1599        assert_eq!(string_hashes, dict_hashes);
1600    }
1601
1602    #[test]
1603    #[cfg(not(feature = "force_hash_collisions"))]
1604    fn test_create_hashes_struct_with_custom_hasher() {
1605        let struct_array = StructArray::from(vec![
1606            (
1607                Arc::new(Field::new("int", DataType::Int32, false)),
1608                Arc::new(Int32Array::from(vec![1, 2, 1, 3])) as ArrayRef,
1609            ),
1610            (
1611                Arc::new(Field::new("string", DataType::Utf8, false)),
1612                Arc::new(StringArray::from(vec!["alpha", "beta", "alpha", "alpha"]))
1613                    as ArrayRef,
1614            ),
1615        ]);
1616        let hash_builder = BuildHasherDefault::<TestHasher>::default();
1617
1618        let mut child_hashes = vec![0; struct_array.len()];
1619        create_hashes_with_hasher(
1620            struct_array.columns(),
1621            &hash_builder,
1622            &mut child_hashes,
1623        )
1624        .unwrap();
1625        let expected_hashes = child_hashes
1626            .into_iter()
1627            .map(|hash| combine_hashes(0, hash))
1628            .collect::<Vec<_>>();
1629
1630        let array: ArrayRef = Arc::new(struct_array);
1631        let mut actual_hashes = vec![0; array.len()];
1632        create_hashes_with_hasher([&array], &hash_builder, &mut actual_hashes).unwrap();
1633
1634        assert_eq!(actual_hashes, expected_hashes);
1635        assert_eq!(actual_hashes[0], actual_hashes[2]);
1636        assert_ne!(actual_hashes[0], actual_hashes[3]);
1637    }
1638
1639    #[test]
1640    #[cfg(not(feature = "force_hash_collisions"))]
1641    fn test_create_hashes_long_utf8_view_with_custom_hasher() {
1642        let values = vec![
1643            Some("this string is longer than twelve bytes"),
1644            None,
1645            Some("another string longer than twelve bytes"),
1646            Some("this string is longer than twelve bytes"),
1647        ];
1648        let view_array = StringViewArray::from(values.clone());
1649        assert!(!view_array.data_buffers().is_empty());
1650        let view_array: ArrayRef = Arc::new(view_array);
1651        let hash_builder = BuildHasherDefault::<TestHasher>::default();
1652
1653        let mut view_hashes = vec![0; view_array.len()];
1654        create_hashes_with_hasher([&view_array], &hash_builder, &mut view_hashes)
1655            .unwrap();
1656        let expected_hashes = values
1657            .iter()
1658            .map(|value| {
1659                value
1660                    .map(|value| hash_builder.hash_one(value.as_bytes()))
1661                    .unwrap_or_default()
1662            })
1663            .collect::<Vec<_>>();
1664        assert_eq!(view_hashes, expected_hashes);
1665
1666        let prefix_array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3, 1]));
1667        let mut expected_hashes = vec![0; prefix_array.len()];
1668        create_hashes_with_hasher([&prefix_array], &hash_builder, &mut expected_hashes)
1669            .unwrap();
1670        for (hash, value) in expected_hashes.iter_mut().zip(&values) {
1671            if let Some(value) = value {
1672                *hash = combine_hashes(hash_builder.hash_one(value.as_bytes()), *hash);
1673            }
1674        }
1675
1676        let mut view_hashes = vec![0; view_array.len()];
1677        create_hashes_with_hasher(
1678            [&prefix_array, &view_array],
1679            &hash_builder,
1680            &mut view_hashes,
1681        )
1682        .unwrap();
1683        assert_eq!(view_hashes, expected_hashes);
1684    }
1685
1686    #[test]
1687    #[cfg(not(feature = "force_hash_collisions"))]
1688    fn test_single_column_leaf_hashes_match_with_same_hasher() {
1689        let arrays: Vec<ArrayRef> = vec![
1690            Arc::new(Int32Array::from(vec![Some(1), None, Some(-1)])),
1691            Arc::new(Float64Array::from(vec![Some(0.0), Some(-0.0), None])),
1692            Arc::new(StringArray::from(vec![Some("foo"), None, Some("bar")])),
1693            Arc::new(BinaryArray::from(vec![
1694                Some(&b"short"[..]),
1695                None,
1696                Some(&b"longer than twelve bytes"[..]),
1697            ])),
1698            Arc::new(StringViewArray::from(vec![
1699                Some("short"),
1700                None,
1701                Some("longer than twelve bytes"),
1702            ])),
1703        ];
1704        let random_state = RandomState::with_seed(0);
1705
1706        for array in arrays {
1707            let mut default_hashes = vec![0; array.len()];
1708            create_hashes([&array], &random_state, &mut default_hashes).unwrap();
1709
1710            let mut custom_hashes = vec![0; array.len()];
1711            create_hashes_with_hasher([&array], &random_state, &mut custom_hashes)
1712                .unwrap();
1713
1714            assert_eq!(
1715                custom_hashes,
1716                default_hashes,
1717                "single-column parity failed for {}",
1718                array.data_type()
1719            );
1720        }
1721    }
1722
1723    #[test]
1724    #[cfg(not(feature = "force_hash_collisions"))]
1725    fn test_with_hashes_with_custom_hasher() {
1726        let int_array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3]));
1727        let str_array: ArrayRef = Arc::new(StringArray::from(vec!["a", "b", "c"]));
1728        let hash_builder = BuildHasherDefault::<TestHasher>::default();
1729
1730        let mut expected_hashes = vec![0; int_array.len()];
1731        create_hashes_with_hasher(
1732            [&int_array, &str_array],
1733            &hash_builder,
1734            &mut expected_hashes,
1735        )
1736        .unwrap();
1737
1738        let actual_hashes =
1739            with_hashes_with_hasher([&int_array, &str_array], &hash_builder, |hashes| {
1740                Ok(hashes.to_vec())
1741            })
1742            .unwrap();
1743
1744        assert_eq!(actual_hashes, expected_hashes);
1745    }
1746
1747    #[test]
1748    // Tests actual values of hashes, which are different if forcing collisions
1749    #[cfg(not(feature = "force_hash_collisions"))]
1750    fn create_hashes_for_dict_arrays() {
1751        let strings = [Some("foo"), None, Some("bar"), Some("foo"), None];
1752
1753        let string_array: ArrayRef =
1754            Arc::new(strings.iter().cloned().collect::<StringArray>());
1755        let dict_array: ArrayRef = Arc::new(
1756            strings
1757                .iter()
1758                .cloned()
1759                .collect::<DictionaryArray<Int8Type>>(),
1760        );
1761
1762        let random_state = RandomState::with_seed(0);
1763
1764        let mut string_hashes = vec![0; strings.len()];
1765        create_hashes(&[string_array], &random_state, &mut string_hashes).unwrap();
1766
1767        let mut dict_hashes = vec![0; strings.len()];
1768        create_hashes(&[dict_array], &random_state, &mut dict_hashes).unwrap();
1769
1770        // Null values result in a zero hash,
1771        for (val, hash) in strings.iter().zip(string_hashes.iter()) {
1772            match val {
1773                Some(_) => assert_ne!(*hash, 0),
1774                None => assert_eq!(*hash, 0),
1775            }
1776        }
1777
1778        // same logical values should hash to the same hash value
1779        assert_eq!(string_hashes, dict_hashes);
1780
1781        // Same values should map to same hash values
1782        assert_eq!(strings[1], strings[4]);
1783        assert_eq!(dict_hashes[1], dict_hashes[4]);
1784        assert_eq!(strings[0], strings[3]);
1785        assert_eq!(dict_hashes[0], dict_hashes[3]);
1786
1787        // different strings should map to different hash values
1788        assert_ne!(strings[0], strings[2]);
1789        assert_ne!(dict_hashes[0], dict_hashes[2]);
1790    }
1791
1792    #[test]
1793    // Tests actual values of hashes, which are different if forcing collisions
1794    #[cfg(not(feature = "force_hash_collisions"))]
1795    fn create_hashes_for_list_arrays() {
1796        let data = vec![
1797            Some(vec![Some(0), Some(1), Some(2)]),
1798            None,
1799            Some(vec![Some(3), None, Some(5)]),
1800            Some(vec![Some(3), None, Some(5)]),
1801            None,
1802            Some(vec![Some(0), Some(1), Some(2)]),
1803            Some(vec![]),
1804        ];
1805        let list_array =
1806            Arc::new(ListArray::from_iter_primitive::<Int32Type, _, _>(data)) as ArrayRef;
1807        let random_state = RandomState::with_seed(0);
1808        let mut hashes = vec![0; list_array.len()];
1809        create_hashes(&[list_array], &random_state, &mut hashes).unwrap();
1810        assert_eq!(hashes[0], hashes[5]);
1811        assert_eq!(hashes[1], hashes[4]);
1812        assert_eq!(hashes[2], hashes[3]);
1813        assert_eq!(hashes[1], hashes[6]); // null vs empty list
1814    }
1815
1816    #[test]
1817    #[cfg(not(feature = "force_hash_collisions"))]
1818    fn create_hashes_for_sliced_list_arrays() {
1819        let data = vec![
1820            Some(vec![Some(0), Some(1), Some(2)]),
1821            None,
1822            // Slice from here
1823            Some(vec![Some(3), None, Some(5)]),
1824            Some(vec![Some(3), None, Some(5)]),
1825            None,
1826            // To here
1827            Some(vec![Some(0), Some(1), Some(2)]),
1828            Some(vec![]),
1829        ];
1830        let list_array =
1831            Arc::new(ListArray::from_iter_primitive::<Int32Type, _, _>(data)) as ArrayRef;
1832        let list_array = list_array.slice(2, 3);
1833        let random_state = RandomState::with_seed(0);
1834        let mut hashes = vec![0; list_array.len()];
1835        create_hashes(&[list_array], &random_state, &mut hashes).unwrap();
1836        assert_eq!(hashes[0], hashes[1]);
1837        assert_ne!(hashes[1], hashes[2]);
1838    }
1839
1840    #[test]
1841    // Tests actual values of hashes, which are different if forcing collisions
1842    #[cfg(not(feature = "force_hash_collisions"))]
1843    fn create_hashes_for_list_view_arrays() {
1844        use arrow::buffer::{NullBuffer, ScalarBuffer};
1845
1846        // Create values array: [0, 1, 2, 3, null, 5]
1847        let values = Arc::new(Int32Array::from(vec![
1848            Some(0),
1849            Some(1),
1850            Some(2),
1851            Some(3),
1852            None,
1853            Some(5),
1854        ])) as ArrayRef;
1855        let field = Arc::new(Field::new("item", DataType::Int32, true));
1856
1857        // Create ListView with the following logical structure:
1858        // Row 0: [0, 1, 2]        (offset=0, size=3)
1859        // Row 1: null             (null bit set)
1860        // Row 2: [3, null, 5]     (offset=3, size=3)
1861        // Row 3: [3, null, 5]     (offset=3, size=3) - same as row 2
1862        // Row 4: null             (null bit set)
1863        // Row 5: [0, 1, 2]        (offset=0, size=3) - same as row 0
1864        // Row 6: []               (offset=0, size=0) - empty list
1865        let offsets = ScalarBuffer::from(vec![0i32, 0, 3, 3, 0, 0, 0]);
1866        let sizes = ScalarBuffer::from(vec![3i32, 0, 3, 3, 0, 3, 0]);
1867        let nulls = Some(NullBuffer::from(vec![
1868            true, false, true, true, false, true, true,
1869        ]));
1870
1871        let list_view_array =
1872            Arc::new(ListViewArray::new(field, offsets, sizes, values, nulls))
1873                as ArrayRef;
1874
1875        let random_state = RandomState::with_seed(0);
1876        let mut hashes = vec![0; list_view_array.len()];
1877        create_hashes(&[list_view_array], &random_state, &mut hashes).unwrap();
1878
1879        assert_eq!(hashes[0], hashes[5]); // same content [0, 1, 2]
1880        assert_eq!(hashes[1], hashes[4]); // both null
1881        assert_eq!(hashes[2], hashes[3]); // same content [3, null, 5]
1882        assert_eq!(hashes[1], hashes[6]); // null vs empty list
1883
1884        // Negative tests: different content should produce different hashes
1885        assert_ne!(hashes[0], hashes[2]); // [0, 1, 2] vs [3, null, 5]
1886        assert_ne!(hashes[0], hashes[6]); // [0, 1, 2] vs []
1887        assert_ne!(hashes[2], hashes[6]); // [3, null, 5] vs []
1888    }
1889
1890    #[test]
1891    // Tests actual values of hashes, which are different if forcing collisions
1892    #[cfg(not(feature = "force_hash_collisions"))]
1893    fn create_hashes_for_large_list_view_arrays() {
1894        use arrow::buffer::{NullBuffer, ScalarBuffer};
1895
1896        // Create values array: [0, 1, 2, 3, null, 5]
1897        let values = Arc::new(Int32Array::from(vec![
1898            Some(0),
1899            Some(1),
1900            Some(2),
1901            Some(3),
1902            None,
1903            Some(5),
1904        ])) as ArrayRef;
1905        let field = Arc::new(Field::new("item", DataType::Int32, true));
1906
1907        // Create LargeListView with the following logical structure:
1908        // Row 0: [0, 1, 2]        (offset=0, size=3)
1909        // Row 1: null             (null bit set)
1910        // Row 2: [3, null, 5]     (offset=3, size=3)
1911        // Row 3: [3, null, 5]     (offset=3, size=3) - same as row 2
1912        // Row 4: null             (null bit set)
1913        // Row 5: [0, 1, 2]        (offset=0, size=3) - same as row 0
1914        // Row 6: []               (offset=0, size=0) - empty list
1915        let offsets = ScalarBuffer::from(vec![0i64, 0, 3, 3, 0, 0, 0]);
1916        let sizes = ScalarBuffer::from(vec![3i64, 0, 3, 3, 0, 3, 0]);
1917        let nulls = Some(NullBuffer::from(vec![
1918            true, false, true, true, false, true, true,
1919        ]));
1920
1921        let large_list_view_array = Arc::new(LargeListViewArray::new(
1922            field, offsets, sizes, values, nulls,
1923        )) as ArrayRef;
1924
1925        let random_state = RandomState::with_seed(0);
1926        let mut hashes = vec![0; large_list_view_array.len()];
1927        create_hashes(&[large_list_view_array], &random_state, &mut hashes).unwrap();
1928
1929        assert_eq!(hashes[0], hashes[5]); // same content [0, 1, 2]
1930        assert_eq!(hashes[1], hashes[4]); // both null
1931        assert_eq!(hashes[2], hashes[3]); // same content [3, null, 5]
1932        assert_eq!(hashes[1], hashes[6]); // null vs empty list
1933
1934        // Negative tests: different content should produce different hashes
1935        assert_ne!(hashes[0], hashes[2]); // [0, 1, 2] vs [3, null, 5]
1936        assert_ne!(hashes[0], hashes[6]); // [0, 1, 2] vs []
1937        assert_ne!(hashes[2], hashes[6]); // [3, null, 5] vs []
1938    }
1939
1940    #[test]
1941    // Tests actual values of hashes, which are different if forcing collisions
1942    #[cfg(not(feature = "force_hash_collisions"))]
1943    fn create_hashes_for_fixed_size_list_arrays() {
1944        let data = vec![
1945            Some(vec![Some(0), Some(1), Some(2)]),
1946            None,
1947            Some(vec![Some(3), None, Some(5)]),
1948            Some(vec![Some(3), None, Some(5)]),
1949            None,
1950            Some(vec![Some(0), Some(1), Some(2)]),
1951        ];
1952        let list_array =
1953            Arc::new(FixedSizeListArray::from_iter_primitive::<Int32Type, _, _>(
1954                data, 3,
1955            )) as ArrayRef;
1956        let random_state = RandomState::with_seed(0);
1957        let mut hashes = vec![0; list_array.len()];
1958        create_hashes(&[list_array], &random_state, &mut hashes).unwrap();
1959        assert_eq!(hashes[0], hashes[5]);
1960        assert_eq!(hashes[1], hashes[4]);
1961        assert_eq!(hashes[2], hashes[3]);
1962    }
1963
1964    #[test]
1965    // Tests actual values of hashes, which are different if forcing collisions
1966    #[cfg(not(feature = "force_hash_collisions"))]
1967    fn create_hashes_for_struct_arrays() {
1968        use arrow::buffer::Buffer;
1969
1970        let boolarr = Arc::new(BooleanArray::from(vec![
1971            false, false, true, true, true, true,
1972        ]));
1973        let i32arr = Arc::new(Int32Array::from(vec![10, 10, 20, 20, 30, 31]));
1974
1975        let struct_array = StructArray::from((
1976            vec![
1977                (
1978                    Arc::new(Field::new("bool", DataType::Boolean, false)),
1979                    Arc::clone(&boolarr) as ArrayRef,
1980                ),
1981                (
1982                    Arc::new(Field::new("i32", DataType::Int32, false)),
1983                    Arc::clone(&i32arr) as ArrayRef,
1984                ),
1985                (
1986                    Arc::new(Field::new("i32", DataType::Int32, false)),
1987                    Arc::clone(&i32arr) as ArrayRef,
1988                ),
1989                (
1990                    Arc::new(Field::new("bool", DataType::Boolean, false)),
1991                    Arc::clone(&boolarr) as ArrayRef,
1992                ),
1993            ],
1994            Buffer::from(&[0b001011]),
1995        ));
1996
1997        assert!(struct_array.is_valid(0));
1998        assert!(struct_array.is_valid(1));
1999        assert!(struct_array.is_null(2));
2000        assert!(struct_array.is_valid(3));
2001        assert!(struct_array.is_null(4));
2002        assert!(struct_array.is_null(5));
2003
2004        let array = Arc::new(struct_array) as ArrayRef;
2005
2006        let random_state = RandomState::with_seed(0);
2007        let mut hashes = vec![0; array.len()];
2008        create_hashes(&[array], &random_state, &mut hashes).unwrap();
2009        assert_eq!(hashes[0], hashes[1]);
2010        // same value but the third row ( hashes[2] ) is null
2011        assert_ne!(hashes[2], hashes[3]);
2012        // different values but both are null
2013        assert_eq!(hashes[4], hashes[5]);
2014    }
2015
2016    #[test]
2017    // Tests actual values of hashes, which are different if forcing collisions
2018    #[cfg(not(feature = "force_hash_collisions"))]
2019    fn create_hashes_for_struct_arrays_more_column_than_row() {
2020        let struct_array = StructArray::from(vec![
2021            (
2022                Arc::new(Field::new("bool", DataType::Boolean, false)),
2023                Arc::new(BooleanArray::from(vec![false, false])) as ArrayRef,
2024            ),
2025            (
2026                Arc::new(Field::new("i32-1", DataType::Int32, false)),
2027                Arc::new(Int32Array::from(vec![10, 10])) as ArrayRef,
2028            ),
2029            (
2030                Arc::new(Field::new("i32-2", DataType::Int32, false)),
2031                Arc::new(Int32Array::from(vec![10, 10])) as ArrayRef,
2032            ),
2033            (
2034                Arc::new(Field::new("i32-3", DataType::Int32, false)),
2035                Arc::new(Int32Array::from(vec![10, 10])) as ArrayRef,
2036            ),
2037        ]);
2038
2039        assert!(struct_array.is_valid(0));
2040        assert!(struct_array.is_valid(1));
2041
2042        let array = Arc::new(struct_array) as ArrayRef;
2043        let random_state = RandomState::with_seed(0);
2044        let mut hashes = vec![0; array.len()];
2045        create_hashes(&[array], &random_state, &mut hashes).unwrap();
2046        assert_eq!(hashes[0], hashes[1]);
2047    }
2048
2049    #[test]
2050    // Tests actual values of hashes, which are different if forcing collisions
2051    #[cfg(not(feature = "force_hash_collisions"))]
2052    fn create_hashes_for_map_arrays() {
2053        let mut builder =
2054            MapBuilder::new(None, StringBuilder::new(), Int32Builder::new());
2055        // Row 0
2056        builder.keys().append_value("key1");
2057        builder.keys().append_value("key2");
2058        builder.values().append_value(1);
2059        builder.values().append_value(2);
2060        builder.append(true).unwrap();
2061        // Row 1
2062        builder.keys().append_value("key1");
2063        builder.keys().append_value("key2");
2064        builder.values().append_value(1);
2065        builder.values().append_value(2);
2066        builder.append(true).unwrap();
2067        // Row 2
2068        builder.keys().append_value("key1");
2069        builder.keys().append_value("key2");
2070        builder.values().append_value(1);
2071        builder.values().append_value(3);
2072        builder.append(true).unwrap();
2073        // Row 3
2074        builder.keys().append_value("key1");
2075        builder.keys().append_value("key3");
2076        builder.values().append_value(1);
2077        builder.values().append_value(2);
2078        builder.append(true).unwrap();
2079        // Row 4
2080        builder.keys().append_value("key1");
2081        builder.values().append_value(1);
2082        builder.append(true).unwrap();
2083        // Row 5
2084        builder.keys().append_value("key1");
2085        builder.values().append_null();
2086        builder.append(true).unwrap();
2087        // Row 6
2088        builder.append(true).unwrap();
2089        // Row 7
2090        builder.keys().append_value("key1");
2091        builder.values().append_value(1);
2092        builder.append(false).unwrap();
2093
2094        let array = Arc::new(builder.finish()) as ArrayRef;
2095
2096        let random_state = RandomState::with_seed(0);
2097        let mut hashes = vec![0; array.len()];
2098        create_hashes(&[array], &random_state, &mut hashes).unwrap();
2099        assert_eq!(hashes[0], hashes[1]); // same value
2100        assert_ne!(hashes[0], hashes[2]); // different value
2101        assert_ne!(hashes[0], hashes[3]); // different key
2102        assert_ne!(hashes[0], hashes[4]); // missing an entry
2103        assert_ne!(hashes[4], hashes[5]); // filled vs null value
2104        assert_eq!(hashes[6], hashes[7]); // empty vs null map
2105    }
2106
2107    #[test]
2108    // Tests actual values of hashes, which are different if forcing collisions
2109    #[cfg(not(feature = "force_hash_collisions"))]
2110    fn create_multi_column_hash_for_dict_arrays() {
2111        let strings1 = [Some("foo"), None, Some("bar")];
2112        let strings2 = [Some("blarg"), Some("blah"), None];
2113
2114        let string_array: ArrayRef =
2115            Arc::new(strings1.iter().cloned().collect::<StringArray>());
2116        let dict_array: ArrayRef = Arc::new(
2117            strings2
2118                .iter()
2119                .cloned()
2120                .collect::<DictionaryArray<Int32Type>>(),
2121        );
2122
2123        let random_state = RandomState::with_seed(0);
2124
2125        let mut one_col_hashes = vec![0; strings1.len()];
2126        create_hashes(
2127            &[Arc::clone(&dict_array) as ArrayRef],
2128            &random_state,
2129            &mut one_col_hashes,
2130        )
2131        .unwrap();
2132
2133        let mut two_col_hashes = vec![0; strings1.len()];
2134        create_hashes(
2135            &[dict_array, string_array],
2136            &random_state,
2137            &mut two_col_hashes,
2138        )
2139        .unwrap();
2140
2141        assert_eq!(one_col_hashes.len(), 3);
2142        assert_eq!(two_col_hashes.len(), 3);
2143
2144        assert_ne!(one_col_hashes, two_col_hashes);
2145    }
2146
2147    #[test]
2148    fn test_create_hashes_from_arrays() {
2149        let int_array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3, 4]));
2150        let float_array: ArrayRef =
2151            Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0]));
2152
2153        let random_state = RandomState::with_seed(0);
2154        let hashes_buff = &mut vec![0; int_array.len()];
2155        let hashes =
2156            create_hashes(&[int_array, float_array], &random_state, hashes_buff).unwrap();
2157        assert_eq!(hashes.len(), 4,);
2158    }
2159
2160    #[test]
2161    fn test_create_hashes_from_dyn_arrays() {
2162        let int_array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3, 4]));
2163        let float_array: ArrayRef =
2164            Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0]));
2165
2166        // Verify that we can call create_hashes with only &dyn Array
2167        fn test(arr1: &dyn Array, arr2: &dyn Array) {
2168            let random_state = RandomState::with_seed(0);
2169            let hashes_buff = &mut vec![0; arr1.len()];
2170            let hashes = create_hashes([arr1, arr2], &random_state, hashes_buff).unwrap();
2171            assert_eq!(hashes.len(), 4,);
2172        }
2173        test(&*int_array, &*float_array);
2174    }
2175
2176    #[test]
2177    fn test_create_hashes_equivalence() {
2178        let array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3, 4]));
2179        let random_state = RandomState::with_seed(0);
2180
2181        let mut hashes1 = vec![0; array.len()];
2182        create_hashes(
2183            &[Arc::clone(&array) as ArrayRef],
2184            &random_state,
2185            &mut hashes1,
2186        )
2187        .unwrap();
2188
2189        let mut hashes2 = vec![0; array.len()];
2190        create_hashes([array], &random_state, &mut hashes2).unwrap();
2191
2192        assert_eq!(hashes1, hashes2);
2193    }
2194
2195    #[test]
2196    #[cfg(not(feature = "force_hash_collisions"))]
2197    fn test_create_hashes_with_quality_hash_state() {
2198        let int_array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3, 4]));
2199        let str_array: ArrayRef = Arc::new(StringArray::from(vec!["a", "b", "c", "d"]));
2200        let quality_state = foldhash::quality::FixedState::with_seed(0);
2201
2202        let mut one_col_hashes = vec![0; int_array.len()];
2203        create_hashes([&int_array], &quality_state, &mut one_col_hashes).unwrap();
2204        let expected_hashes: Vec<_> = [1i32, 2, 3, 4]
2205            .iter()
2206            .map(|value| quality_state.hash_one(value))
2207            .collect();
2208        assert_eq!(one_col_hashes, expected_hashes);
2209
2210        let mut two_col_hashes = vec![0; int_array.len()];
2211        create_hashes(
2212            [&int_array, &str_array],
2213            &quality_state,
2214            &mut two_col_hashes,
2215        )
2216        .unwrap();
2217        assert_ne!(two_col_hashes, one_col_hashes);
2218    }
2219
2220    #[test]
2221    fn test_with_hashes() {
2222        let array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3, 4]));
2223        let random_state = RandomState::with_seed(0);
2224
2225        // Test that with_hashes produces the same results as create_hashes
2226        let mut expected_hashes = vec![0; array.len()];
2227        create_hashes([&array], &random_state, &mut expected_hashes).unwrap();
2228
2229        let result = with_hashes([&array], &random_state, |hashes| {
2230            assert_eq!(hashes.len(), 4);
2231            // Verify hashes match expected values
2232            assert_eq!(hashes, &expected_hashes[..]);
2233            // Return a copy of the hashes
2234            Ok(hashes.to_vec())
2235        })
2236        .unwrap();
2237
2238        // Verify callback result is returned correctly
2239        assert_eq!(result, expected_hashes);
2240    }
2241
2242    #[test]
2243    fn test_with_hashes_multi_column() {
2244        let int_array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3]));
2245        let str_array: ArrayRef = Arc::new(StringArray::from(vec!["a", "b", "c"]));
2246        let random_state = RandomState::with_seed(0);
2247
2248        // Test multi-column hashing
2249        let mut expected_hashes = vec![0; int_array.len()];
2250        create_hashes(
2251            [&int_array, &str_array],
2252            &random_state,
2253            &mut expected_hashes,
2254        )
2255        .unwrap();
2256
2257        with_hashes([&int_array, &str_array], &random_state, |hashes| {
2258            assert_eq!(hashes.len(), 3);
2259            assert_eq!(hashes, &expected_hashes[..]);
2260            Ok(())
2261        })
2262        .unwrap();
2263    }
2264
2265    #[test]
2266    fn test_with_hashes_empty_arrays() {
2267        let random_state = RandomState::with_seed(0);
2268
2269        // Test that passing no arrays returns an error
2270        let empty: [&ArrayRef; 0] = [];
2271        let result = with_hashes(empty, &random_state, |_hashes| Ok(()));
2272
2273        assert!(result.is_err());
2274        assert!(
2275            result
2276                .unwrap_err()
2277                .to_string()
2278                .contains("requires at least one array")
2279        );
2280    }
2281
2282    #[test]
2283    fn test_with_hashes_reentrancy() {
2284        let array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3]));
2285        let array2: ArrayRef = Arc::new(Int32Array::from(vec![4, 5, 6]));
2286        let random_state = RandomState::with_seed(0);
2287
2288        // Test that reentrant calls return an error instead of panicking
2289        let result = with_hashes([&array], &random_state, |_hashes| {
2290            // Try to call with_hashes again inside the callback
2291            with_hashes([&array2], &random_state, |_inner_hashes| Ok(()))
2292        });
2293
2294        assert!(result.is_err());
2295        let err_msg = result.unwrap_err().to_string();
2296        assert!(
2297            err_msg.contains("reentrantly") || err_msg.contains("cannot be called"),
2298            "Error message should mention reentrancy: {err_msg}",
2299        );
2300    }
2301
2302    #[test]
2303    #[cfg(not(feature = "force_hash_collisions"))]
2304    fn create_hashes_for_sparse_union_arrays() {
2305        // logical array: [int(5), str("foo"), int(10), int(5)]
2306        let int_array = Int32Array::from(vec![Some(5), None, Some(10), Some(5)]);
2307        let str_array = StringArray::from(vec![None, Some("foo"), None, None]);
2308
2309        let type_ids = vec![0_i8, 1, 0, 0].into();
2310        let children = vec![
2311            Arc::new(int_array) as ArrayRef,
2312            Arc::new(str_array) as ArrayRef,
2313        ];
2314
2315        let union_fields = [
2316            (0, Arc::new(Field::new("a", DataType::Int32, true))),
2317            (1, Arc::new(Field::new("b", DataType::Utf8, true))),
2318        ]
2319        .into_iter()
2320        .collect();
2321
2322        let array = UnionArray::try_new(union_fields, type_ids, None, children).unwrap();
2323        let array_ref = Arc::new(array) as ArrayRef;
2324
2325        let random_state = RandomState::with_seed(0);
2326        let mut hashes = vec![0; array_ref.len()];
2327        create_hashes(&[array_ref], &random_state, &mut hashes).unwrap();
2328
2329        // Rows 0 and 3 both have type_id=0 (int) with value 5
2330        assert_eq!(hashes[0], hashes[3]);
2331        // Row 0 (int 5) vs Row 2 (int 10) - different values
2332        assert_ne!(hashes[0], hashes[2]);
2333        // Row 0 (int) vs Row 1 (string) - different types
2334        assert_ne!(hashes[0], hashes[1]);
2335    }
2336
2337    #[test]
2338    #[cfg(not(feature = "force_hash_collisions"))]
2339    fn create_hashes_for_sparse_union_arrays_with_nulls() {
2340        // logical array: [int(5), str("foo"), int(null), str(null)]
2341        let int_array = Int32Array::from(vec![Some(5), None, None, None]);
2342        let str_array = StringArray::from(vec![None, Some("foo"), None, None]);
2343
2344        let type_ids = vec![0, 1, 0, 1].into();
2345        let children = vec![
2346            Arc::new(int_array) as ArrayRef,
2347            Arc::new(str_array) as ArrayRef,
2348        ];
2349
2350        let union_fields = [
2351            (0, Arc::new(Field::new("a", DataType::Int32, true))),
2352            (1, Arc::new(Field::new("b", DataType::Utf8, true))),
2353        ]
2354        .into_iter()
2355        .collect();
2356
2357        let array = UnionArray::try_new(union_fields, type_ids, None, children).unwrap();
2358        let array_ref = Arc::new(array) as ArrayRef;
2359
2360        let random_state = RandomState::with_seed(0);
2361        let mut hashes = vec![0; array_ref.len()];
2362        create_hashes(&[array_ref], &random_state, &mut hashes).unwrap();
2363
2364        // row 2 (int null) and row 3 (str null) should have the same hash
2365        // because they are both null values
2366        assert_eq!(hashes[2], hashes[3]);
2367
2368        // row 0 (int 5) vs row 2 (int null) - different (value vs null)
2369        assert_ne!(hashes[0], hashes[2]);
2370
2371        // row 1 (str "foo") vs row 3 (str null) - different (value vs null)
2372        assert_ne!(hashes[1], hashes[3]);
2373    }
2374
2375    #[test]
2376    #[cfg(not(feature = "force_hash_collisions"))]
2377    fn create_hashes_for_dense_union_arrays() {
2378        // creates a dense union array with int and string types
2379        // [67, "norm", 100, "macdonald", 67]
2380        let int_array = Int32Array::from(vec![67, 100, 67]);
2381        let str_array = StringArray::from(vec!["norm", "macdonald"]);
2382
2383        let type_ids = vec![0, 1, 0, 1, 0].into();
2384        let offsets = vec![0, 0, 1, 1, 2].into();
2385        let children = vec![
2386            Arc::new(int_array) as ArrayRef,
2387            Arc::new(str_array) as ArrayRef,
2388        ];
2389
2390        let union_fields = [
2391            (0, Arc::new(Field::new("a", DataType::Int32, false))),
2392            (1, Arc::new(Field::new("b", DataType::Utf8, false))),
2393        ]
2394        .into_iter()
2395        .collect();
2396
2397        let array =
2398            UnionArray::try_new(union_fields, type_ids, Some(offsets), children).unwrap();
2399        let array_ref = Arc::new(array) as ArrayRef;
2400
2401        let random_state = RandomState::with_seed(0);
2402        let mut hashes = vec![0; array_ref.len()];
2403        create_hashes(&[array_ref], &random_state, &mut hashes).unwrap();
2404
2405        // 67 vs "norm"
2406        assert_ne!(hashes[0], hashes[1]);
2407        // 67 vs 100
2408        assert_ne!(hashes[0], hashes[2]);
2409        // "norm" vs "macdonald"
2410        assert_ne!(hashes[1], hashes[3]);
2411        // 100 vs "macdonald"
2412        assert_ne!(hashes[2], hashes[3]);
2413        // 67 vs 67
2414        assert_eq!(hashes[0], hashes[4]);
2415    }
2416
2417    #[test]
2418    #[cfg(not(feature = "force_hash_collisions"))]
2419    fn create_hashes_for_sliced_run_array() -> Result<()> {
2420        let values = Arc::new(Int32Array::from(vec![10, 20, 30]));
2421        let run_ends = Arc::new(Int32Array::from(vec![2, 5, 7]));
2422        let array = Arc::new(RunArray::try_new(&run_ends, values.as_ref()).unwrap());
2423
2424        let random_state = RandomState::with_seed(0);
2425        let mut full_hashes = vec![0; array.len()];
2426        create_hashes(
2427            &[Arc::clone(&array) as ArrayRef],
2428            &random_state,
2429            &mut full_hashes,
2430        )?;
2431
2432        let array_ref: ArrayRef = Arc::clone(&array) as ArrayRef;
2433        let sliced_array = array_ref.slice(2, 3);
2434
2435        let mut sliced_hashes = vec![0; sliced_array.len()];
2436        create_hashes(
2437            std::slice::from_ref(&sliced_array),
2438            &random_state,
2439            &mut sliced_hashes,
2440        )?;
2441
2442        assert_eq!(sliced_hashes.len(), 3);
2443        assert_eq!(sliced_hashes[0], sliced_hashes[1]);
2444        assert_eq!(sliced_hashes[1], sliced_hashes[2]);
2445        assert_eq!(&sliced_hashes, &full_hashes[2..5]);
2446
2447        Ok(())
2448    }
2449
2450    #[test]
2451    #[cfg(not(feature = "force_hash_collisions"))]
2452    fn test_run_array_with_nulls() -> Result<()> {
2453        let values = Arc::new(Int32Array::from(vec![Some(10), None, Some(20)]));
2454        let run_ends = Arc::new(Int32Array::from(vec![2, 4, 6]));
2455        let array = Arc::new(RunArray::try_new(&run_ends, values.as_ref()).unwrap());
2456
2457        let random_state = RandomState::with_seed(0);
2458        let mut hashes = vec![0; array.len()];
2459        create_hashes(
2460            &[Arc::clone(&array) as ArrayRef],
2461            &random_state,
2462            &mut hashes,
2463        )?;
2464
2465        assert_eq!(hashes[0], hashes[1]);
2466        assert_ne!(hashes[0], 0);
2467        assert_eq!(hashes[2], hashes[3]);
2468        assert_eq!(hashes[2], 0);
2469        assert_eq!(hashes[4], hashes[5]);
2470        assert_ne!(hashes[4], 0);
2471        assert_ne!(hashes[0], hashes[4]);
2472
2473        Ok(())
2474    }
2475
2476    #[test]
2477    #[cfg(not(feature = "force_hash_collisions"))]
2478    fn test_run_array_with_nulls_multicolumn() -> Result<()> {
2479        let primitive_array = Arc::new(Int32Array::from(vec![Some(10), None, Some(20)]));
2480        let run_values = Arc::new(Int32Array::from(vec![Some(10), None, Some(20)]));
2481        let run_ends = Arc::new(Int32Array::from(vec![1, 2, 3]));
2482        let run_array =
2483            Arc::new(RunArray::try_new(&run_ends, run_values.as_ref()).unwrap());
2484        let second_col = Arc::new(Int32Array::from(vec![100, 200, 300]));
2485
2486        let random_state = RandomState::with_seed(0);
2487
2488        let mut primitive_hashes = vec![0; 3];
2489        create_hashes(
2490            &[
2491                Arc::clone(&primitive_array) as ArrayRef,
2492                Arc::clone(&second_col) as ArrayRef,
2493            ],
2494            &random_state,
2495            &mut primitive_hashes,
2496        )?;
2497
2498        let mut run_hashes = vec![0; 3];
2499        create_hashes(
2500            &[
2501                Arc::clone(&run_array) as ArrayRef,
2502                Arc::clone(&second_col) as ArrayRef,
2503            ],
2504            &random_state,
2505            &mut run_hashes,
2506        )?;
2507
2508        assert_eq!(primitive_hashes, run_hashes);
2509
2510        Ok(())
2511    }
2512}