Skip to content

Commit c5ed78b

Browse files
authored
Preserve projection metadata and dialect-aware CREATE parsing (#46)
* Delegate CREATE statements to configured dialect * Preserve metadata-changing projections * Align DF55 compatibility tests with fork behavior * Keep spill partition hashing independent under collision tests
1 parent 3506840 commit c5ed78b

10 files changed

Lines changed: 417 additions & 84 deletions

File tree

datafusion/common/src/hash_utils.rs

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,11 @@
1919
2020
use arrow::array::types::{IntervalDayTime, IntervalMonthDayNano};
2121
use arrow::array::*;
22-
#[cfg(not(feature = "force_hash_collisions"))]
2322
use arrow::compute::take;
2423
use arrow::datatypes::*;
25-
#[cfg(not(feature = "force_hash_collisions"))]
2624
use arrow::{downcast_dictionary_array, downcast_primitive_array};
2725
use foldhash::fast::FixedState;
28-
#[cfg(not(feature = "force_hash_collisions"))]
2926
use itertools::Itertools;
30-
#[cfg(not(feature = "force_hash_collisions"))]
3127
use std::collections::HashMap;
3228
use 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"))]
8479
use 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"))]
211205
fn hash_null<S: HashState>(
212206
random_state: &S,
213207
hashes_buffer: &'_ mut [u64],
@@ -275,34 +269,30 @@ macro_rules! hash_float_value {
275269
}
276270
hash_float_value!((half::f16, u16), (f32, u32), (f64, u64));
277271

278-
#[cfg(not(feature = "force_hash_collisions"))]
279272
trait 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"))]
287279
struct HashStateChildHashing<'a, S> {
288280
hash_state: &'a S,
289281
}
290282

291-
#[cfg(not(feature = "force_hash_collisions"))]
292283
impl<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"))]
306296
fn 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"))]
351340
fn 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)]
401389
fn 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"))]
461448
fn 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)]
528514
fn hash_dictionary_scatter<
529515
K: ArrowDictionaryKeyType,
@@ -563,7 +549,6 @@ fn hash_dictionary_scatter<
563549
}
564550
}
565551

566-
#[cfg(not(feature = "force_hash_collisions"))]
567552
fn 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"))]
622606
fn 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"))]
653636
fn 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"))]
683664
fn 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"))]
734714
fn 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"))]
784763
fn 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"))]
823801
fn 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"))]
854831
fn 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"))]
895871
fn 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"))]
951926
fn 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"))]
986960
fn 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"))]
10551028
fn 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")]
11861168
fn 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]

datafusion/physical-expr/src/expressions/binary.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4798,7 +4798,7 @@ mod tests {
47984798
Field::new("b", DataType::Decimal128(10, 2), true),
47994799
]));
48004800
let expect = Arc::new(create_decimal_array(
4801-
&[Some(1000000), None, Some(1008196), Some(1000000)],
4801+
&[Some(1000000), None, Some(1008197), Some(1000000)],
48024802
16,
48034803
4,
48044804
)) as ArrayRef;

datafusion/physical-plan/src/joins/cross_join.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ impl ExecutionPlan for CrossJoinExec {
452452

453453
let (new_left, new_right) = new_join_children(
454454
&projection_as_columns,
455+
projection.schema().as_ref(),
455456
far_right_left_col_ind,
456457
far_left_right_col_ind,
457458
self.left(),

datafusion/physical-plan/src/joins/hash_join/spill.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
//! and per-partition write buffers; held until the spill join completes.
3434
//! - `HashJoinSpillPartition[p.k]`: per partition-pair; covers the loaded
3535
//! build batches plus the hash table built from them (moved into the
36-
//! pair's [`JoinLeftData`]); dropped when the pair finishes.
36+
//! pair's in-memory join state); dropped when the pair finishes.
3737
//!
3838
//! The scatter hash uses seeds distinct from both `RepartitionExec`'s
3939
//! `(0,0,0,0)` routing seeds and the join hash map's `HASH_JOIN_SEED`
@@ -45,7 +45,7 @@ use std::sync::Arc;
4545
use std::sync::atomic::AtomicUsize;
4646
use std::task::{Context, Poll};
4747

48-
use crate::hash_utils::create_hashes;
48+
use crate::hash_utils::create_hashes_for_partitioning;
4949
use crate::joins::PartitionMode;
5050
use crate::joins::SharedBitmapBuilder;
5151
use crate::joins::hash_join::exec::{
@@ -429,7 +429,11 @@ impl SideScatter {
429429
let keys = evaluate_expressions_to_arrays(&self.on_exprs, batch)?;
430430
self.hashes_buffer.clear();
431431
self.hashes_buffer.resize(num_rows, 0);
432-
create_hashes(&keys, &self.random_state, &mut self.hashes_buffer)?;
432+
create_hashes_for_partitioning(
433+
&keys,
434+
&self.random_state,
435+
&mut self.hashes_buffer,
436+
)?;
433437

434438
let partition_count = self.writers.len() as u64;
435439
let mut indices: Vec<Vec<u32>> = vec![Vec::new(); self.writers.len()];
@@ -1711,6 +1715,7 @@ fn shared_build_loader(
17111715
#[cfg(test)]
17121716
mod tests {
17131717
use super::*;
1718+
use crate::hash_utils::create_hashes;
17141719
use crate::joins::HashJoinExec;
17151720
use crate::joins::PartitionMode;
17161721
use crate::metrics::SpillMetrics;

datafusion/physical-plan/src/joins/sort_merge_join/exec.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@ impl ExecutionPlan for SortMergeJoinExec {
662662

663663
let (new_left, new_right) = new_join_children(
664664
&projection_as_columns,
665+
projection.schema().as_ref(),
665666
far_right_left_col_ind,
666667
far_left_right_col_ind,
667668
self.children()[0],

0 commit comments

Comments
 (0)