use super::{AsDynArray, HASH_BUFFER, MAX_BUFFER_SIZE};
#[cfg(not(feature = "force_hash_collisions"))]
use super::{
ChildHashing, combine_hashes, hash_dictionary_with_child_hashing,
hash_fixed_list_array, hash_list_array, hash_list_view_array, hash_map_array,
hash_run_array, hash_struct_array, hash_union_array,
};
#[cfg(not(feature = "force_hash_collisions"))]
use crate::cast::{
as_binary_view_array, as_boolean_array, as_fixed_size_list_array,
as_generic_binary_array, as_large_list_array, as_large_list_view_array,
as_list_array, as_list_view_array, as_map_array, as_string_array,
as_string_view_array, as_struct_array, as_union_array,
};
use crate::error::Result;
use crate::error::{_internal_datafusion_err, _internal_err};
#[cfg(feature = "force_hash_collisions")]
use arrow::array::Array;
#[cfg(not(feature = "force_hash_collisions"))]
use arrow::array::types::{IntervalDayTime, IntervalMonthDayNano};
#[cfg(not(feature = "force_hash_collisions"))]
use arrow::array::*;
#[cfg(not(feature = "force_hash_collisions"))]
use arrow::datatypes::*;
#[cfg(not(feature = "force_hash_collisions"))]
use arrow::{downcast_dictionary_array, downcast_primitive_array};
use std::hash::BuildHasher;
pub(super) fn with_hashes_with_hasher<I, T, F, R, S>(
arrays: I,
hash_builder: &S,
callback: F,
) -> Result<R>
where
I: IntoIterator<Item = T>,
T: AsDynArray,
F: FnOnce(&[u64]) -> Result<R>,
S: BuildHasher,
{
let mut iter = arrays.into_iter().peekable();
let required_size = match iter.peek() {
Some(arr) => arr.as_dyn_array().len(),
None => {
return _internal_err!("with_hashes_with_hasher requires at least one array");
}
};
HASH_BUFFER.try_with(|cell| {
let mut buffer = cell.try_borrow_mut().map_err(|_| {
_internal_datafusion_err!(
"with_hashes_with_hasher cannot be called reentrantly on the same thread"
)
})?;
buffer.clear();
buffer.resize(required_size, 0);
create_hashes_with_hasher_impl(iter, hash_builder, &mut buffer[..required_size])?;
let result = callback(&buffer[..required_size])?;
if buffer.capacity() > MAX_BUFFER_SIZE {
buffer.truncate(MAX_BUFFER_SIZE);
buffer.shrink_to_fit();
}
Ok(result)
}).map_err(|_| {
_internal_datafusion_err!(
"with_hashes_with_hasher cannot access thread-local storage during or after thread destruction"
)
})?
}
pub(super) fn create_hashes_with_hasher<'a, I, T, S>(
arrays: I,
hash_builder: &S,
hashes_buffer: &'a mut [u64],
) -> Result<&'a mut [u64]>
where
I: IntoIterator<Item = T>,
T: AsDynArray,
S: BuildHasher,
{
create_hashes_with_hasher_impl(arrays, hash_builder, hashes_buffer)
}
fn create_hashes_with_hasher_impl<'a, I, T, S>(
arrays: I,
hash_builder: &S,
hashes_buffer: &'a mut [u64],
) -> Result<&'a mut [u64]>
where
I: IntoIterator<Item = T>,
T: AsDynArray,
S: BuildHasher,
{
for (i, array) in arrays.into_iter().enumerate() {
let rehash = i >= 1;
hash_single_array_with_hasher(
array.as_dyn_array(),
hash_builder,
hashes_buffer,
rehash,
)?;
}
Ok(hashes_buffer)
}
#[cfg(not(feature = "force_hash_collisions"))]
struct BuildHasherChildHashing<'a, S> {
hash_builder: &'a S,
}
#[cfg(not(feature = "force_hash_collisions"))]
impl<S: BuildHasher> ChildHashing for BuildHasherChildHashing<'_, S> {
fn create_hashes<I, T>(&self, arrays: I, hashes_buffer: &mut [u64]) -> Result<()>
where
I: IntoIterator<Item = T>,
T: AsDynArray,
{
create_hashes_with_hasher_impl(arrays, self.hash_builder, hashes_buffer)
.map(|_| ())
}
}
#[cfg(not(feature = "force_hash_collisions"))]
trait BuildHasherHashValue {
fn hash_one_with_hasher<S: BuildHasher>(&self, state: &S) -> u64;
}
#[cfg(not(feature = "force_hash_collisions"))]
impl<T: BuildHasherHashValue + ?Sized> BuildHasherHashValue for &T {
fn hash_one_with_hasher<S: BuildHasher>(&self, state: &S) -> u64 {
T::hash_one_with_hasher(self, state)
}
}
macro_rules! build_hasher_hash_value {
($($t:ty),+) => {
$(#[cfg(not(feature = "force_hash_collisions"))]
impl BuildHasherHashValue for $t {
fn hash_one_with_hasher<S: BuildHasher>(&self, state: &S) -> u64 {
state.hash_one(self)
}
})+
};
}
build_hasher_hash_value!(i8, i16, i32, i64, i128, i256, u8, u16, u32, u64, u128);
build_hasher_hash_value!(bool, str, [u8], IntervalDayTime, IntervalMonthDayNano);
macro_rules! build_hasher_hash_float_value {
($(($t:ty, $i:ty)),+) => {
$(#[cfg(not(feature = "force_hash_collisions"))]
impl BuildHasherHashValue for $t {
fn hash_one_with_hasher<S: BuildHasher>(&self, state: &S) -> u64 {
let bits = <$i>::from_ne_bytes(self.to_ne_bytes());
let bits = if bits << 1 == 0 { 0 } else { bits };
state.hash_one(bits)
}
})+
};
}
build_hasher_hash_float_value!((half::f16, u16), (f32, u32), (f64, u64));
#[cfg(not(feature = "force_hash_collisions"))]
fn hash_null_with_hasher<S: BuildHasher>(
hash_builder: &S,
hashes_buffer: &mut [u64],
multi_col: bool,
) {
if hashes_buffer.is_empty() {
return;
}
let null_hash = hash_builder.hash_one(1);
if multi_col {
hashes_buffer.iter_mut().for_each(|hash| {
*hash = combine_hashes(null_hash, *hash);
})
} else {
hashes_buffer.fill(null_hash);
}
}
#[cfg(not(feature = "force_hash_collisions"))]
fn hash_array_primitive_with_hasher<T, S>(
array: &PrimitiveArray<T>,
hash_builder: &S,
hashes_buffer: &mut [u64],
rehash: bool,
) where
T: ArrowPrimitiveType<Native: BuildHasherHashValue>,
S: BuildHasher,
{
assert_eq!(
hashes_buffer.len(),
array.len(),
"hashes_buffer and array should be of equal length"
);
if array.null_count() == 0 {
if rehash {
for (hash, &value) in hashes_buffer.iter_mut().zip(array.values().iter()) {
*hash = combine_hashes(value.hash_one_with_hasher(hash_builder), *hash);
}
} else {
for (hash, &value) in hashes_buffer.iter_mut().zip(array.values().iter()) {
*hash = value.hash_one_with_hasher(hash_builder);
}
}
} else if rehash {
for i in array.nulls().unwrap().valid_indices() {
let value = unsafe { array.value_unchecked(i) };
hashes_buffer[i] = combine_hashes(
value.hash_one_with_hasher(hash_builder),
hashes_buffer[i],
);
}
} else {
for i in array.nulls().unwrap().valid_indices() {
let value = unsafe { array.value_unchecked(i) };
hashes_buffer[i] = value.hash_one_with_hasher(hash_builder);
}
}
}
#[cfg(not(feature = "force_hash_collisions"))]
fn hash_array_with_hasher<T, S>(
array: &T,
hash_builder: &S,
hashes_buffer: &mut [u64],
rehash: bool,
) where
T: ArrayAccessor,
T::Item: BuildHasherHashValue,
S: BuildHasher,
{
assert_eq!(
hashes_buffer.len(),
array.len(),
"hashes_buffer and array should be of equal length"
);
if array.null_count() == 0 {
if rehash {
for (i, hash) in hashes_buffer.iter_mut().enumerate() {
let value = unsafe { array.value_unchecked(i) };
*hash = combine_hashes(value.hash_one_with_hasher(hash_builder), *hash);
}
} else {
for (i, hash) in hashes_buffer.iter_mut().enumerate() {
let value = unsafe { array.value_unchecked(i) };
*hash = value.hash_one_with_hasher(hash_builder);
}
}
} else if rehash {
for i in array.nulls().unwrap().valid_indices() {
let value = unsafe { array.value_unchecked(i) };
hashes_buffer[i] = combine_hashes(
value.hash_one_with_hasher(hash_builder),
hashes_buffer[i],
);
}
} else {
for i in array.nulls().unwrap().valid_indices() {
let value = unsafe { array.value_unchecked(i) };
hashes_buffer[i] = value.hash_one_with_hasher(hash_builder);
}
}
}
#[cfg(not(feature = "force_hash_collisions"))]
#[inline(never)]
fn hash_string_view_array_inner_with_hasher<
T: ByteViewType,
S: BuildHasher,
const HAS_NULLS: bool,
const HAS_BUFFERS: bool,
const REHASH: bool,
>(
array: &GenericByteViewArray<T>,
hash_builder: &S,
hashes_buffer: &mut [u64],
) {
assert_eq!(
hashes_buffer.len(),
array.len(),
"hashes_buffer and array should be of equal length"
);
let buffers = array.data_buffers();
let view_bytes = |view_len: u32, view: u128| {
let view = ByteView::from(view);
let offset = view.offset as usize;
unsafe {
let data = buffers.get_unchecked(view.buffer_index as usize);
data.get_unchecked(offset..offset + view_len as usize)
}
};
let hashes_and_views = hashes_buffer.iter_mut().zip(array.views().iter());
for (i, (hash, &v)) in hashes_and_views.enumerate() {
if HAS_NULLS && array.is_null(i) {
continue;
}
let view_len = v as u32;
if !HAS_BUFFERS || view_len <= 12 {
if REHASH {
*hash = combine_hashes(v.hash_one_with_hasher(hash_builder), *hash);
} else {
*hash = v.hash_one_with_hasher(hash_builder);
}
continue;
}
let value = view_bytes(view_len, v);
if REHASH {
*hash = combine_hashes(value.hash_one_with_hasher(hash_builder), *hash);
} else {
*hash = value.hash_one_with_hasher(hash_builder);
}
}
}
#[cfg(not(feature = "force_hash_collisions"))]
fn hash_generic_byte_view_array_with_hasher<T: ByteViewType, S: BuildHasher>(
array: &GenericByteViewArray<T>,
hash_builder: &S,
hashes_buffer: &mut [u64],
rehash: bool,
) {
match (
array.null_count() != 0,
!array.data_buffers().is_empty(),
rehash,
) {
(false, false, false) => {
for (hash, &view) in hashes_buffer.iter_mut().zip(array.views().iter()) {
*hash = view.hash_one_with_hasher(hash_builder);
}
}
(false, false, true) => {
for (hash, &view) in hashes_buffer.iter_mut().zip(array.views().iter()) {
*hash = combine_hashes(view.hash_one_with_hasher(hash_builder), *hash);
}
}
(false, true, false) => {
hash_string_view_array_inner_with_hasher::<T, S, false, true, false>(
array,
hash_builder,
hashes_buffer,
)
}
(false, true, true) => {
hash_string_view_array_inner_with_hasher::<T, S, false, true, true>(
array,
hash_builder,
hashes_buffer,
)
}
(true, false, false) => {
hash_string_view_array_inner_with_hasher::<T, S, true, false, false>(
array,
hash_builder,
hashes_buffer,
)
}
(true, false, true) => {
hash_string_view_array_inner_with_hasher::<T, S, true, false, true>(
array,
hash_builder,
hashes_buffer,
)
}
(true, true, false) => {
hash_string_view_array_inner_with_hasher::<T, S, true, true, false>(
array,
hash_builder,
hashes_buffer,
)
}
(true, true, true) => {
hash_string_view_array_inner_with_hasher::<T, S, true, true, true>(
array,
hash_builder,
hashes_buffer,
)
}
}
}
#[cfg(not(feature = "force_hash_collisions"))]
fn hash_single_array_with_hasher<S: BuildHasher>(
array: &dyn Array,
hash_builder: &S,
hashes_buffer: &mut [u64],
rehash: bool,
) -> Result<()> {
let child_hashing = BuildHasherChildHashing { hash_builder };
downcast_primitive_array! {
array => hash_array_primitive_with_hasher(array, hash_builder, hashes_buffer, rehash),
DataType::Null => hash_null_with_hasher(hash_builder, hashes_buffer, rehash),
DataType::Boolean => hash_array_with_hasher(&as_boolean_array(array)?, hash_builder, hashes_buffer, rehash),
DataType::Utf8 => hash_array_with_hasher(&as_string_array(array)?, hash_builder, hashes_buffer, rehash),
DataType::Utf8View => hash_generic_byte_view_array_with_hasher(as_string_view_array(array)?, hash_builder, hashes_buffer, rehash),
DataType::LargeUtf8 => hash_array_with_hasher(&as_largestring_array(array), hash_builder, hashes_buffer, rehash),
DataType::Binary => hash_array_with_hasher(&as_generic_binary_array::<i32>(array)?, hash_builder, hashes_buffer, rehash),
DataType::BinaryView => hash_generic_byte_view_array_with_hasher(as_binary_view_array(array)?, hash_builder, hashes_buffer, rehash),
DataType::LargeBinary => hash_array_with_hasher(&as_generic_binary_array::<i64>(array)?, hash_builder, hashes_buffer, rehash),
DataType::FixedSizeBinary(_) => {
let array: &FixedSizeBinaryArray = array.as_any().downcast_ref().unwrap();
hash_array_with_hasher(&array, hash_builder, hashes_buffer, rehash)
}
DataType::Dictionary(_, _) => downcast_dictionary_array! {
array => hash_dictionary_with_child_hashing(array, &child_hashing, hashes_buffer, rehash)?,
_ => unreachable!()
}
DataType::Struct(_) => {
let array = as_struct_array(array)?;
hash_struct_array(array, &child_hashing, hashes_buffer)?;
}
DataType::List(_) => {
let array = as_list_array(array)?;
hash_list_array(array, &child_hashing, hashes_buffer)?;
}
DataType::LargeList(_) => {
let array = as_large_list_array(array)?;
hash_list_array(array, &child_hashing, hashes_buffer)?;
}
DataType::ListView(_) => {
let array = as_list_view_array(array)?;
hash_list_view_array(array, &child_hashing, hashes_buffer)?;
}
DataType::LargeListView(_) => {
let array = as_large_list_view_array(array)?;
hash_list_view_array(array, &child_hashing, hashes_buffer)?;
}
DataType::Map(_, _) => {
let array = as_map_array(array)?;
hash_map_array(array, &child_hashing, hashes_buffer)?;
}
DataType::FixedSizeList(_,_) => {
let array = as_fixed_size_list_array(array)?;
hash_fixed_list_array(array, &child_hashing, hashes_buffer)?;
}
DataType::Union(_, _) => {
let array = as_union_array(array)?;
hash_union_array(array, &child_hashing, hashes_buffer)?;
}
DataType::RunEndEncoded(_, _) => downcast_run_array! {
array => hash_run_array(array, &child_hashing, hashes_buffer, rehash)?,
_ => unreachable!()
}
_ => {
return _internal_err!(
"Unsupported data type in hasher: {}",
array.data_type()
);
}
}
Ok(())
}
#[cfg(feature = "force_hash_collisions")]
fn hash_single_array_with_hasher<S: BuildHasher>(
_array: &dyn Array,
_hash_builder: &S,
hashes_buffer: &mut [u64],
_rehash: bool,
) -> Result<()> {
for hash in hashes_buffer.iter_mut() {
*hash = 0;
}
Ok(())
}