1919
2020use arrow:: array:: types:: { IntervalDayTime , IntervalMonthDayNano } ;
2121use arrow:: array:: * ;
22- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
2322use arrow:: compute:: take;
2423use arrow:: datatypes:: * ;
25- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
2624use arrow:: { downcast_dictionary_array, downcast_primitive_array} ;
2725use foldhash:: fast:: FixedState ;
28- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
2926use itertools:: Itertools ;
30- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
3127use std:: collections:: HashMap ;
3228use std:: hash:: { BuildHasher , Hash , Hasher } ;
3329
@@ -80,7 +76,6 @@ impl HashState for foldhash::quality::FixedState {
8076 }
8177}
8278
83- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
8479use crate :: cast:: {
8580 as_binary_view_array, as_boolean_array, as_fixed_size_list_array,
8681 as_generic_binary_array, as_large_list_array, as_large_list_view_array,
@@ -207,7 +202,6 @@ where
207202 build_hasher:: with_hashes_with_hasher ( arrays, hash_builder, callback)
208203}
209204
210- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
211205fn hash_null < S : HashState > (
212206 random_state : & S ,
213207 hashes_buffer : & ' _ mut [ u64 ] ,
@@ -275,34 +269,30 @@ macro_rules! hash_float_value {
275269}
276270hash_float_value ! ( ( half:: f16, u16 ) , ( f32 , u32 ) , ( f64 , u64 ) ) ;
277271
278- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
279272trait ChildHashing {
280273 fn create_hashes < I , T > ( & self , arrays : I , hashes_buffer : & mut [ u64 ] ) -> Result < ( ) >
281274 where
282275 I : IntoIterator < Item = T > ,
283276 T : AsDynArray ;
284277}
285278
286- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
287279struct HashStateChildHashing < ' a , S > {
288280 hash_state : & ' a S ,
289281}
290282
291- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
292283impl < S : HashState > ChildHashing for HashStateChildHashing < ' _ , S > {
293284 fn create_hashes < I , T > ( & self , arrays : I , hashes_buffer : & mut [ u64 ] ) -> Result < ( ) >
294285 where
295286 I : IntoIterator < Item = T > ,
296287 T : AsDynArray ,
297288 {
298- create_hashes ( arrays, self . hash_state , hashes_buffer) . map ( |_| ( ) )
289+ create_hashes_for_partitioning ( arrays, self . hash_state , hashes_buffer) . map ( |_| ( ) )
299290 }
300291}
301292
302293/// Builds hash values of PrimitiveArray and writes them into `hashes_buffer`
303294/// If `rehash==true` this folds the existing hash into the hasher state
304295/// and hashes only the new value (avoiding a separate combine step).
305- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
306296fn hash_array_primitive < T > (
307297 array : & PrimitiveArray < T > ,
308298 random_state : & impl HashState ,
@@ -347,7 +337,6 @@ fn hash_array_primitive<T>(
347337/// Hashes one array into the `hashes_buffer`
348338/// If `rehash==true` this combines the previous hash value in the buffer
349339/// with the new hash using `combine_hashes`
350- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
351340fn hash_array < T > (
352341 array : & T ,
353342 random_state : & impl HashState ,
@@ -396,7 +385,6 @@ fn hash_array<T>(
396385/// HAS_NULLS: do we have to check null in the inner loop
397386/// HAS_BUFFERS: if true, array has external buffers; if false, all strings are inlined/ less then 12 bytes
398387/// REHASH: if true, combining with existing hash, otherwise initializing
399- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
400388#[ inline( never) ]
401389fn hash_string_view_array_inner <
402390 T : ByteViewType ,
@@ -457,7 +445,6 @@ fn hash_string_view_array_inner<
457445/// Builds hash values for array views and writes them into `hashes_buffer`
458446/// If `rehash==true` this combines the previous hash value in the buffer
459447/// with the new hash using `combine_hashes`
460- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
461448fn hash_generic_byte_view_array < T : ByteViewType > (
462449 array : & GenericByteViewArray < T > ,
463450 random_state : & impl HashState ,
@@ -523,7 +510,6 @@ fn hash_generic_byte_view_array<T: ByteViewType>(
523510/// - `HAS_NULL_KEYS`: Whether to check for null dictionary keys
524511/// - `HAS_NULL_VALUES`: Whether to check for null dictionary values
525512/// - `MULTI_COL`: Whether to combine with existing hash (true) or initialize (false)
526- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
527513#[ inline( never) ]
528514fn hash_dictionary_scatter <
529515 K : ArrowDictionaryKeyType ,
@@ -563,7 +549,6 @@ fn hash_dictionary_scatter<
563549 }
564550}
565551
566- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
567552fn dispatch_dictionary_scatter < K : ArrowDictionaryKeyType > (
568553 array : & DictionaryArray < K > ,
569554 dict_hashes : & [ u64 ] ,
@@ -618,7 +603,6 @@ fn dispatch_dictionary_scatter<K: ArrowDictionaryKeyType>(
618603}
619604
620605/// Hash the values in a dictionary array.
621- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
622606fn hash_dictionary < K : ArrowDictionaryKeyType > (
623607 array : & DictionaryArray < K > ,
624608 random_state : & impl HashState ,
@@ -630,7 +614,7 @@ fn hash_dictionary<K: ArrowDictionaryKeyType>(
630614 // redundant hashing for large dictionary elements (e.g. strings)
631615 let dict_values = array. values ( ) ;
632616 let mut dict_hashes = vec ! [ 0 ; dict_values. len( ) ] ;
633- create_hashes ( [ dict_values] , random_state, & mut dict_hashes) ?;
617+ create_hashes_for_partitioning ( [ dict_values] , random_state, & mut dict_hashes) ?;
634618 dispatch_dictionary_scatter ( array, & dict_hashes, hashes_buffer, multi_col) ;
635619 Ok ( ( ) )
636620}
@@ -649,7 +633,6 @@ fn hash_dictionary_with_child_hashing<K: ArrowDictionaryKeyType>(
649633 Ok ( ( ) )
650634}
651635
652- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
653636fn hash_struct_array (
654637 array : & StructArray ,
655638 child_hashing : & impl ChildHashing ,
@@ -678,8 +661,6 @@ fn hash_struct_array(
678661 Ok ( ( ) )
679662}
680663
681- // only adding this `cfg` b/c this function is only used with this `cfg`
682- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
683664fn hash_map_array (
684665 array : & MapArray ,
685666 child_hashing : & impl ChildHashing ,
@@ -730,7 +711,6 @@ fn hash_map_array(
730711 Ok ( ( ) )
731712}
732713
733- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
734714fn hash_list_array < OffsetSize > (
735715 array : & GenericListArray < OffsetSize > ,
736716 child_hashing : & impl ChildHashing ,
@@ -780,7 +760,6 @@ where
780760 Ok ( ( ) )
781761}
782762
783- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
784763fn hash_list_view_array < OffsetSize > (
785764 array : & GenericListViewArray < OffsetSize > ,
786765 child_hashing : & impl ChildHashing ,
@@ -819,7 +798,6 @@ where
819798 Ok ( ( ) )
820799}
821800
822- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
823801fn hash_union_array (
824802 array : & UnionArray ,
825803 child_hashing : & impl ChildHashing ,
@@ -850,7 +828,6 @@ fn hash_union_array(
850828/// For sparse unions with 3+ types, the optimized take/scatter approach in
851829/// `hash_sparse_union_array` is more efficient, but for 1-2 types or dense unions,
852830/// this simpler approach is preferred.
853- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
854831fn hash_union_array_default (
855832 array : & UnionArray ,
856833 union_fields : & UnionFields ,
@@ -891,7 +868,6 @@ fn hash_union_array_default(
891868///
892869/// For 1-2 types, the overhead of take/scatter outweighs the benefit, so we use
893870/// the default approach of hashing all children (same as dense unions).
894- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
895871fn hash_sparse_union_array (
896872 array : & UnionArray ,
897873 union_fields : & UnionFields ,
@@ -947,7 +923,6 @@ fn hash_sparse_union_array(
947923 Ok ( ( ) )
948924}
949925
950- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
951926fn hash_fixed_list_array (
952927 array : & FixedSizeListArray ,
953928 child_hashing : & impl ChildHashing ,
@@ -982,7 +957,6 @@ fn hash_fixed_list_array(
982957
983958/// Inner hash function for RunArray
984959#[ inline( never) ]
985- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
986960fn hash_run_array_inner <
987961 R : RunEndIndexType ,
988962 C : ChildHashing + ?Sized ,
@@ -1051,7 +1025,6 @@ fn hash_run_array_inner<
10511025 Ok ( ( ) )
10521026}
10531027
1054- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
10551028fn hash_run_array < R : RunEndIndexType > (
10561029 array : & RunArray < R > ,
10571030 child_hashing : & impl ChildHashing ,
@@ -1080,8 +1053,7 @@ fn hash_run_array<R: RunEndIndexType>(
10801053
10811054/// Internal helper function that hashes a single array and either initializes or combines
10821055/// the hash values in the buffer.
1083- #[ cfg( not( feature = "force_hash_collisions" ) ) ]
1084- fn hash_single_array (
1056+ fn hash_single_array_for_partitioning (
10851057 array : & dyn Array ,
10861058 random_state : & impl HashState ,
10871059 hashes_buffer : & mut [ u64 ] ,
@@ -1181,6 +1153,16 @@ fn hash_single_array(
11811153 Ok ( ( ) )
11821154}
11831155
1156+ #[ cfg( not( feature = "force_hash_collisions" ) ) ]
1157+ fn hash_single_array (
1158+ array : & dyn Array ,
1159+ random_state : & impl HashState ,
1160+ hashes_buffer : & mut [ u64 ] ,
1161+ rehash : bool ,
1162+ ) -> Result < ( ) > {
1163+ hash_single_array_for_partitioning ( array, random_state, hashes_buffer, rehash)
1164+ }
1165+
11841166/// Test version of `hash_single_array` that forces all hashes to collide to zero.
11851167#[ cfg( feature = "force_hash_collisions" ) ]
11861168fn hash_single_array (
@@ -1253,6 +1235,31 @@ where
12531235 Ok ( hashes_buffer)
12541236}
12551237
1238+ /// Creates hashes for partition routing even when collision-forcing tests are enabled.
1239+ ///
1240+ /// The `force_hash_collisions` feature intentionally collapses hashes used by hash
1241+ /// tables. Partition routing must remain independent so a grace hash join can split
1242+ /// the input into bounded partitions before exercising those colliding hash tables.
1243+ pub fn create_hashes_for_partitioning < ' a , I , T > (
1244+ arrays : I ,
1245+ random_state : & impl HashState ,
1246+ hashes_buffer : & ' a mut [ u64 ] ,
1247+ ) -> Result < & ' a mut [ u64 ] >
1248+ where
1249+ I : IntoIterator < Item = T > ,
1250+ T : AsDynArray ,
1251+ {
1252+ for ( i, array) in arrays. into_iter ( ) . enumerate ( ) {
1253+ hash_single_array_for_partitioning (
1254+ array. as_dyn_array ( ) ,
1255+ random_state,
1256+ hashes_buffer,
1257+ i >= 1 ,
1258+ ) ?;
1259+ }
1260+ Ok ( hashes_buffer)
1261+ }
1262+
12561263/// Creates hash values for every row using a caller-provided hash builder.
12571264///
12581265/// The number of rows to hash is determined by `hashes_buffer.len()`.
@@ -1308,6 +1315,23 @@ mod tests {
13081315 }
13091316 }
13101317
1318+ #[ cfg( feature = "force_hash_collisions" ) ]
1319+ #[ test]
1320+ fn partition_hashes_are_not_forced_to_collide ( ) -> Result < ( ) > {
1321+ let array: ArrayRef = Arc :: new ( Int32Array :: from ( vec ! [ 1 , 2 , 1 ] ) ) ;
1322+ let random_state = RandomState :: with_seed ( 0 ) ;
1323+
1324+ let mut collision_hashes = vec ! [ 0 ; array. len( ) ] ;
1325+ create_hashes ( [ & array] , & random_state, & mut collision_hashes) ?;
1326+ assert_eq ! ( collision_hashes, vec![ 0 ; array. len( ) ] ) ;
1327+
1328+ let mut partition_hashes = vec ! [ 0 ; array. len( ) ] ;
1329+ create_hashes_for_partitioning ( [ & array] , & random_state, & mut partition_hashes) ?;
1330+ assert_eq ! ( partition_hashes[ 0 ] , partition_hashes[ 2 ] ) ;
1331+ assert_ne ! ( partition_hashes[ 0 ] , partition_hashes[ 1 ] ) ;
1332+ Ok ( ( ) )
1333+ }
1334+
13111335 #[ test]
13121336 fn create_hashes_for_decimal_array ( ) -> Result < ( ) > {
13131337 let array = vec ! [ 1 , 2 , 3 , 4 ]
0 commit comments