blob: 979f787b5966c8039a4f733f6dc96c2be1d07899 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! This includes utilities for hashing and murmur3 hashing.
#[macro_export]
macro_rules! hash_array {
($array_type: ident, $column: ident, $hashes: ident, $hash_method: ident) => {
let array = $column
.as_any()
.downcast_ref::<$array_type>()
.unwrap_or_else(|| {
panic!(
"Failed to downcast column to {}. Actual data type: {:?}.",
stringify!($array_type),
$column.data_type()
)
});
if array.null_count() == 0 {
// Fast path: no nulls, use direct indexing
for i in 0..$hashes.len() {
$hashes[i] = $hash_method(&array.value(i), $hashes[i]);
}
} else {
// Slow path: check nulls
for i in 0..$hashes.len() {
if !array.is_null(i) {
$hashes[i] = $hash_method(&array.value(i), $hashes[i]);
}
}
}
};
}
#[macro_export]
macro_rules! hash_array_boolean {
($array_type: ident, $column: ident, $hash_input_type: ident, $hashes: ident, $hash_method: ident) => {
let array = $column
.as_any()
.downcast_ref::<$array_type>()
.unwrap_or_else(|| {
panic!(
"Failed to downcast column to {}. Actual data type: {:?}.",
stringify!($array_type),
$column.data_type()
)
});
if array.null_count() == 0 {
// Fast path: no nulls, use direct indexing
for i in 0..$hashes.len() {
$hashes[i] = $hash_method(
$hash_input_type::from(array.value(i)).to_le_bytes(),
$hashes[i],
);
}
} else {
// Slow path: check nulls
for i in 0..$hashes.len() {
if !array.is_null(i) {
$hashes[i] = $hash_method(
$hash_input_type::from(array.value(i)).to_le_bytes(),
$hashes[i],
);
}
}
}
};
}
#[macro_export]
macro_rules! hash_array_primitive {
($array_type: ident, $column: ident, $ty: ident, $hashes: ident, $hash_method: ident) => {
let array = $column
.as_any()
.downcast_ref::<$array_type>()
.unwrap_or_else(|| {
panic!(
"Failed to downcast column to {}. Actual data type: {:?}.",
stringify!($array_type),
$column.data_type()
)
});
let values = array.values();
if array.null_count() == 0 {
// Fast path: no nulls, use direct indexing
for i in 0..values.len() {
$hashes[i] = $hash_method((values[i] as $ty).to_le_bytes(), $hashes[i]);
}
} else {
// Slow path: check nulls
for i in 0..values.len() {
if !array.is_null(i) {
$hashes[i] = $hash_method((values[i] as $ty).to_le_bytes(), $hashes[i]);
}
}
}
};
}
#[macro_export]
macro_rules! hash_array_primitive_float {
($array_type: ident, $column: ident, $ty: ident, $ty2: ident, $hashes: ident, $hash_method: ident) => {
let array = $column
.as_any()
.downcast_ref::<$array_type>()
.unwrap_or_else(|| {
panic!(
"Failed to downcast column to {}. Actual data type: {:?}.",
stringify!($array_type),
$column.data_type()
)
});
let values = array.values();
if array.null_count() == 0 {
// Fast path: no nulls, use direct indexing
for i in 0..values.len() {
let value = values[i];
// Spark uses 0 as hash for -0.0, see `Murmur3Hash` expression.
if value == 0.0 && value.is_sign_negative() {
$hashes[i] = $hash_method((0 as $ty2).to_le_bytes(), $hashes[i]);
} else {
$hashes[i] = $hash_method((value as $ty).to_le_bytes(), $hashes[i]);
}
}
} else {
// Slow path: check nulls
for i in 0..values.len() {
if !array.is_null(i) {
let value = values[i];
// Spark uses 0 as hash for -0.0, see `Murmur3Hash` expression.
if value == 0.0 && value.is_sign_negative() {
$hashes[i] = $hash_method((0 as $ty2).to_le_bytes(), $hashes[i]);
} else {
$hashes[i] = $hash_method((value as $ty).to_le_bytes(), $hashes[i]);
}
}
}
}
};
}
#[macro_export]
macro_rules! hash_array_small_decimal {
($array_type:ident, $column: ident, $hashes: ident, $hash_method: ident) => {
let array = $column
.as_any()
.downcast_ref::<$array_type>()
.unwrap_or_else(|| {
panic!(
"Failed to downcast column to {}. Actual data type: {:?}.",
stringify!($array_type),
$column.data_type()
)
});
if array.null_count() == 0 {
// Fast path: no nulls, use direct indexing
for i in 0..$hashes.len() {
$hashes[i] = $hash_method(
i64::try_from(array.value(i))
.map(|v| v.to_le_bytes())
.map_err(|e| DataFusionError::Execution(e.to_string()))?,
$hashes[i],
);
}
} else {
// Slow path: check nulls
for i in 0..$hashes.len() {
if !array.is_null(i) {
$hashes[i] = $hash_method(
i64::try_from(array.value(i))
.map(|v| v.to_le_bytes())
.map_err(|e| DataFusionError::Execution(e.to_string()))?,
$hashes[i],
);
}
}
}
};
}
#[macro_export]
macro_rules! hash_array_decimal {
($array_type:ident, $column: ident, $hashes: ident, $hash_method: ident) => {
let array = $column
.as_any()
.downcast_ref::<$array_type>()
.unwrap_or_else(|| {
panic!(
"Failed to downcast column to {}. Actual data type: {:?}.",
stringify!($array_type),
$column.data_type()
)
});
if array.null_count() == 0 {
// Fast path: no nulls, use direct indexing
for i in 0..$hashes.len() {
$hashes[i] = $hash_method(array.value(i).to_le_bytes(), $hashes[i]);
}
} else {
// Slow path: check nulls
for i in 0..$hashes.len() {
if !array.is_null(i) {
$hashes[i] = $hash_method(array.value(i).to_le_bytes(), $hashes[i]);
}
}
}
};
}
/// Hash a list array with primitive elements by directly accessing the underlying buffer.
/// This avoids the overhead of slicing and recursive calls for common cases.
/// Supports both variable-length lists (with offsets) and fixed-size lists.
#[macro_export]
macro_rules! hash_list_primitive {
// Variable-length list variant (List/LargeList)
(offsets: $offsets:expr, $list_array:ident, $elem_array:ident, $hashes:ident, $hash_method:ident, $value_transform:expr) => {
if $list_array.null_count() == 0 && $elem_array.null_count() == 0 {
for (row_idx, hash) in $hashes.iter_mut().enumerate() {
let start = $offsets[row_idx] as usize;
let end = $offsets[row_idx + 1] as usize;
for elem_idx in start..end {
let value = $elem_array.value(elem_idx);
*hash = $hash_method($value_transform(value), *hash);
}
}
} else {
for (row_idx, hash) in $hashes.iter_mut().enumerate() {
if !$list_array.is_null(row_idx) {
let start = $offsets[row_idx] as usize;
let end = $offsets[row_idx + 1] as usize;
for elem_idx in start..end {
if !$elem_array.is_null(elem_idx) {
let value = $elem_array.value(elem_idx);
*hash = $hash_method($value_transform(value), *hash);
}
}
}
}
}
};
// Fixed-size list variant
(fixed_size: $list_size:expr, $list_array:ident, $elem_array:ident, $hashes:ident, $hash_method:ident, $value_transform:expr) => {
if $list_array.null_count() == 0 && $elem_array.null_count() == 0 {
for (row_idx, hash) in $hashes.iter_mut().enumerate() {
let start = row_idx * $list_size;
for elem_idx in 0..$list_size {
let value = $elem_array.value(start + elem_idx);
*hash = $hash_method($value_transform(value), *hash);
}
}
} else {
for (row_idx, hash) in $hashes.iter_mut().enumerate() {
if !$list_array.is_null(row_idx) {
let start = row_idx * $list_size;
for elem_idx in 0..$list_size {
if !$elem_array.is_null(start + elem_idx) {
let value = $elem_array.value(start + elem_idx);
*hash = $hash_method($value_transform(value), *hash);
}
}
}
}
}
};
}
/// Hash a list array by recursively hashing each element.
/// For each row, we hash all elements in the list.
/// Spark hashes arrays by recursively hashing each element, where each
/// element's hash is computed using the previous element's hash as the seed.
/// This creates a chain: hash(elem_n, hash(elem_n-1, ... hash(elem_0, seed)...))
/// Dispatches hash operations for List/LargeList/FixedSizeList arrays with primitive element types.
/// This macro eliminates duplication by handling the type-to-array mapping for all supported primitives.
#[macro_export]
macro_rules! hash_list_with_primitive_elements {
// Variant for List/LargeList with offsets
(offsets: $list_array_type:ident, $list_array:ident, $values:ident, $offsets:ident, $field:expr, $hashes_buffer:ident, $hash_method:ident, $recursive_hash_method:ident, $fallback_offset_type:ty, $col:ident) => {
match $field.data_type() {
DataType::Int8 => {
let elem_array = $values.as_any().downcast_ref::<Int8Array>().unwrap();
$crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i8| (v as i32).to_le_bytes());
}
DataType::Int16 => {
let elem_array = $values.as_any().downcast_ref::<Int16Array>().unwrap();
$crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i16| (v as i32).to_le_bytes());
}
DataType::Int32 => {
let elem_array = $values.as_any().downcast_ref::<Int32Array>().unwrap();
$crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i32| v.to_le_bytes());
}
DataType::Int64 => {
let elem_array = $values.as_any().downcast_ref::<Int64Array>().unwrap();
$crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i64| v.to_le_bytes());
}
DataType::Float32 => {
let elem_array = $values.as_any().downcast_ref::<Float32Array>().unwrap();
$crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method,
|v: f32| if v == 0.0 && v.is_sign_negative() { (0_i32).to_le_bytes() } else { v.to_le_bytes() });
}
DataType::Float64 => {
let elem_array = $values.as_any().downcast_ref::<Float64Array>().unwrap();
$crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method,
|v: f64| if v == 0.0 && v.is_sign_negative() { (0_i64).to_le_bytes() } else { v.to_le_bytes() });
}
DataType::Boolean => {
let elem_array = $values.as_any().downcast_ref::<BooleanArray>().unwrap();
$crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: bool| (i32::from(v)).to_le_bytes());
}
DataType::Utf8 => {
let elem_array = $values.as_any().downcast_ref::<StringArray>().unwrap();
if $list_array.null_count() == 0 && elem_array.null_count() == 0 {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
let start = $offsets[row_idx] as usize;
let end = $offsets[row_idx + 1] as usize;
for elem_idx in start..end {
*hash = $hash_method(elem_array.value(elem_idx), *hash);
}
}
} else {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
if !$list_array.is_null(row_idx) {
let start = $offsets[row_idx] as usize;
let end = $offsets[row_idx + 1] as usize;
for elem_idx in start..end {
if !elem_array.is_null(elem_idx) {
*hash = $hash_method(elem_array.value(elem_idx), *hash);
}
}
}
}
}
}
DataType::Binary => {
let elem_array = $values.as_any().downcast_ref::<BinaryArray>().unwrap();
if $list_array.null_count() == 0 && elem_array.null_count() == 0 {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
let start = $offsets[row_idx] as usize;
let end = $offsets[row_idx + 1] as usize;
for elem_idx in start..end {
*hash = $hash_method(elem_array.value(elem_idx), *hash);
}
}
} else {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
if !$list_array.is_null(row_idx) {
let start = $offsets[row_idx] as usize;
let end = $offsets[row_idx + 1] as usize;
for elem_idx in start..end {
if !elem_array.is_null(elem_idx) {
*hash = $hash_method(elem_array.value(elem_idx), *hash);
}
}
}
}
}
}
DataType::Date32 => {
let elem_array = $values.as_any().downcast_ref::<Date32Array>().unwrap();
$crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i32| v.to_le_bytes());
}
DataType::Timestamp(TimeUnit::Microsecond, _) => {
let elem_array = $values.as_any().downcast_ref::<TimestampMicrosecondArray>().unwrap();
$crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i64| v.to_le_bytes());
}
DataType::Time64(TimeUnit::Nanosecond) => {
let elem_array = $values.as_any().downcast_ref::<Time64NanosecondArray>().unwrap();
$crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i64| v.to_le_bytes());
}
_ => {
// Fall back to recursive approach for complex element types
$crate::hash_list_array!($list_array_type, $fallback_offset_type, $col, $hashes_buffer, $recursive_hash_method);
}
}
};
// Variant for FixedSizeList with fixed size
(fixed_size: $list_array:ident, $values:ident, $list_size:ident, $field:expr, $hashes_buffer:ident, $hash_method:ident, $recursive_hash_method:ident) => {
match $field.data_type() {
DataType::Int8 => {
let elem_array = $values.as_any().downcast_ref::<Int8Array>().unwrap();
$crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i8| (v as i32).to_le_bytes());
}
DataType::Int16 => {
let elem_array = $values.as_any().downcast_ref::<Int16Array>().unwrap();
$crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i16| (v as i32).to_le_bytes());
}
DataType::Int32 => {
let elem_array = $values.as_any().downcast_ref::<Int32Array>().unwrap();
$crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i32| v.to_le_bytes());
}
DataType::Int64 => {
let elem_array = $values.as_any().downcast_ref::<Int64Array>().unwrap();
$crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i64| v.to_le_bytes());
}
DataType::Float32 => {
let elem_array = $values.as_any().downcast_ref::<Float32Array>().unwrap();
$crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method,
|v: f32| if v == 0.0 && v.is_sign_negative() { (0_i32).to_le_bytes() } else { v.to_le_bytes() });
}
DataType::Float64 => {
let elem_array = $values.as_any().downcast_ref::<Float64Array>().unwrap();
$crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method,
|v: f64| if v == 0.0 && v.is_sign_negative() { (0_i64).to_le_bytes() } else { v.to_le_bytes() });
}
DataType::Boolean => {
let elem_array = $values.as_any().downcast_ref::<BooleanArray>().unwrap();
$crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: bool| (i32::from(v)).to_le_bytes());
}
DataType::Utf8 => {
let elem_array = $values.as_any().downcast_ref::<StringArray>().unwrap();
if $list_array.null_count() == 0 && elem_array.null_count() == 0 {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
let start = row_idx * $list_size;
for elem_idx in 0..$list_size {
*hash = $hash_method(elem_array.value(start + elem_idx), *hash);
}
}
} else {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
if !$list_array.is_null(row_idx) {
let start = row_idx * $list_size;
for elem_idx in 0..$list_size {
if !elem_array.is_null(start + elem_idx) {
*hash = $hash_method(elem_array.value(start + elem_idx), *hash);
}
}
}
}
}
}
DataType::Binary => {
let elem_array = $values.as_any().downcast_ref::<BinaryArray>().unwrap();
if $list_array.null_count() == 0 && elem_array.null_count() == 0 {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
let start = row_idx * $list_size;
for elem_idx in 0..$list_size {
*hash = $hash_method(elem_array.value(start + elem_idx), *hash);
}
}
} else {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
if !$list_array.is_null(row_idx) {
let start = row_idx * $list_size;
for elem_idx in 0..$list_size {
if !elem_array.is_null(start + elem_idx) {
*hash = $hash_method(elem_array.value(start + elem_idx), *hash);
}
}
}
}
}
}
DataType::Date32 => {
let elem_array = $values.as_any().downcast_ref::<Date32Array>().unwrap();
$crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i32| v.to_le_bytes());
}
DataType::Timestamp(TimeUnit::Microsecond, _) => {
let elem_array = $values.as_any().downcast_ref::<TimestampMicrosecondArray>().unwrap();
$crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i64| v.to_le_bytes());
}
DataType::Time64(TimeUnit::Nanosecond) => {
let elem_array = $values.as_any().downcast_ref::<Time64NanosecondArray>().unwrap();
$crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i64| v.to_le_bytes());
}
_ => {
// Fall back to recursive approach for complex element types
if $list_array.null_count() == 0 {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
let start = row_idx * $list_size;
for elem_idx in 0..$list_size {
let elem_array = $values.slice(start + elem_idx, 1);
let mut single_hash = [*hash];
$recursive_hash_method(&[elem_array], &mut single_hash)?;
*hash = single_hash[0];
}
}
} else {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
if !$list_array.is_null(row_idx) {
let start = row_idx * $list_size;
for elem_idx in 0..$list_size {
let elem_array = $values.slice(start + elem_idx, 1);
let mut single_hash = [*hash];
$recursive_hash_method(&[elem_array], &mut single_hash)?;
*hash = single_hash[0];
}
}
}
}
}
}
};
}
#[macro_export]
macro_rules! hash_list_array {
($array_type:ident, $offset_type:ty, $column: ident, $hashes: ident, $recursive_hash_method: ident) => {
let list_array = $column
.as_any()
.downcast_ref::<$array_type>()
.unwrap_or_else(|| {
panic!(
"Failed to downcast column to {}. Actual data type: {:?}.",
stringify!($array_type),
$column.data_type()
)
});
let values = list_array.values();
let offsets = list_array.offsets();
if list_array.null_count() == 0 {
// Fast path: no nulls, skip null checks
for (row_idx, hash) in $hashes.iter_mut().enumerate() {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
let len = end - start;
// Hash each element in sequence, chaining the hash values
for elem_idx in 0..len {
let elem_array = values.slice(start + elem_idx, 1);
let mut single_hash = [*hash];
$recursive_hash_method(&[elem_array], &mut single_hash)?;
*hash = single_hash[0];
}
}
} else {
// Slow path: array has nulls, check each row
for (row_idx, hash) in $hashes.iter_mut().enumerate() {
if !list_array.is_null(row_idx) {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
let len = end - start;
// Hash each element in sequence, chaining the hash values
for elem_idx in 0..len {
let elem_array = values.slice(start + elem_idx, 1);
let mut single_hash = [*hash];
$recursive_hash_method(&[elem_array], &mut single_hash)?;
*hash = single_hash[0];
}
}
}
}
};
}
/// Creates hash values for every row, based on the values in the
/// columns.
///
/// The number of rows to hash is determined by `hashes_buffer.len()`.
/// `hashes_buffer` should be pre-sized appropriately
///
/// `hash_method` is the hash function to use.
/// `create_dictionary_hash_method` is the function to create hashes for dictionary arrays input.
/// `recursive_hash_method` is the function to call for recursive hashing of complex types.
#[macro_export]
macro_rules! create_hashes_internal {
($arrays: ident, $hashes_buffer: ident, $hash_method: ident, $create_dictionary_hash_method: ident, $recursive_hash_method: ident) => {
use arrow::datatypes::{DataType, TimeUnit};
use arrow::array::{types::*, *};
for (i, col) in $arrays.iter().enumerate() {
let first_col = i == 0;
match col.data_type() {
DataType::Boolean => {
$crate::hash_array_boolean!(
BooleanArray,
col,
i32,
$hashes_buffer,
$hash_method
);
}
DataType::Int8 => {
$crate::hash_array_primitive!(
Int8Array,
col,
i32,
$hashes_buffer,
$hash_method
);
}
DataType::Int16 => {
$crate::hash_array_primitive!(
Int16Array,
col,
i32,
$hashes_buffer,
$hash_method
);
}
DataType::Int32 => {
$crate::hash_array_primitive!(
Int32Array,
col,
i32,
$hashes_buffer,
$hash_method
);
}
DataType::Int64 => {
$crate::hash_array_primitive!(
Int64Array,
col,
i64,
$hashes_buffer,
$hash_method
);
}
DataType::Float32 => {
$crate::hash_array_primitive_float!(
Float32Array,
col,
f32,
i32,
$hashes_buffer,
$hash_method
);
}
DataType::Float64 => {
$crate::hash_array_primitive_float!(
Float64Array,
col,
f64,
i64,
$hashes_buffer,
$hash_method
);
}
DataType::Timestamp(TimeUnit::Second, _) => {
$crate::hash_array_primitive!(
TimestampSecondArray,
col,
i64,
$hashes_buffer,
$hash_method
);
}
DataType::Timestamp(TimeUnit::Millisecond, _) => {
$crate::hash_array_primitive!(
TimestampMillisecondArray,
col,
i64,
$hashes_buffer,
$hash_method
);
}
DataType::Timestamp(TimeUnit::Microsecond, _) => {
$crate::hash_array_primitive!(
TimestampMicrosecondArray,
col,
i64,
$hashes_buffer,
$hash_method
);
}
DataType::Timestamp(TimeUnit::Nanosecond, _) => {
$crate::hash_array_primitive!(
TimestampNanosecondArray,
col,
i64,
$hashes_buffer,
$hash_method
);
}
DataType::Date32 => {
$crate::hash_array_primitive!(
Date32Array,
col,
i32,
$hashes_buffer,
$hash_method
);
}
DataType::Date64 => {
$crate::hash_array_primitive!(
Date64Array,
col,
i64,
$hashes_buffer,
$hash_method
);
}
DataType::Time64(TimeUnit::Nanosecond) => {
$crate::hash_array_primitive!(
Time64NanosecondArray,
col,
i64,
$hashes_buffer,
$hash_method
);
}
DataType::Utf8 => {
$crate::hash_array!(StringArray, col, $hashes_buffer, $hash_method);
}
DataType::LargeUtf8 => {
$crate::hash_array!(LargeStringArray, col, $hashes_buffer, $hash_method);
}
DataType::Binary => {
$crate::hash_array!(BinaryArray, col, $hashes_buffer, $hash_method);
}
DataType::LargeBinary => {
$crate::hash_array!(LargeBinaryArray, col, $hashes_buffer, $hash_method);
}
DataType::FixedSizeBinary(_) => {
$crate::hash_array!(FixedSizeBinaryArray, col, $hashes_buffer, $hash_method);
}
// Apache Spark: if it's a small decimal, i.e. precision <= 18, turn it into long and hash it.
// Else, turn it into bytes and hash it.
DataType::Decimal128(precision, _) if *precision <= 18 => {
$crate::hash_array_small_decimal!(Decimal128Array, col, $hashes_buffer, $hash_method);
}
DataType::Decimal128(_, _) => {
$crate::hash_array_decimal!(Decimal128Array, col, $hashes_buffer, $hash_method);
}
DataType::Dictionary(index_type, _) => match **index_type {
DataType::Int8 => {
$create_dictionary_hash_method::<Int8Type>(col, $hashes_buffer, first_col)?;
}
DataType::Int16 => {
$create_dictionary_hash_method::<Int16Type>(
col,
$hashes_buffer,
first_col,
)?;
}
DataType::Int32 => {
$create_dictionary_hash_method::<Int32Type>(
col,
$hashes_buffer,
first_col,
)?;
}
DataType::Int64 => {
$create_dictionary_hash_method::<Int64Type>(
col,
$hashes_buffer,
first_col,
)?;
}
DataType::UInt8 => {
$create_dictionary_hash_method::<UInt8Type>(
col,
$hashes_buffer,
first_col,
)?;
}
DataType::UInt16 => {
$create_dictionary_hash_method::<UInt16Type>(
col,
$hashes_buffer,
first_col,
)?;
}
DataType::UInt32 => {
$create_dictionary_hash_method::<UInt32Type>(
col,
$hashes_buffer,
first_col,
)?;
}
DataType::UInt64 => {
$create_dictionary_hash_method::<UInt64Type>(
col,
$hashes_buffer,
first_col,
)?;
}
_ => {
return Err(DataFusionError::Internal(format!(
"Unsupported dictionary type in hasher hashing: {}",
col.data_type(),
)))
}
},
DataType::List(field) => {
let list_array = col.as_any().downcast_ref::<ListArray>().unwrap();
let values = list_array.values();
let offsets = list_array.offsets();
$crate::hash_list_with_primitive_elements!(offsets: ListArray, list_array, values, offsets, field, $hashes_buffer, $hash_method, $recursive_hash_method, i32, col);
}
DataType::LargeList(field) => {
let list_array = col.as_any().downcast_ref::<LargeListArray>().unwrap();
let values = list_array.values();
let offsets = list_array.offsets();
$crate::hash_list_with_primitive_elements!(offsets: LargeListArray, list_array, values, offsets, field, $hashes_buffer, $hash_method, $recursive_hash_method, i64, col);
}
DataType::FixedSizeList(field, size) => {
let list_array = col.as_any().downcast_ref::<FixedSizeListArray>().unwrap();
let values = list_array.values();
let list_size = *size as usize;
$crate::hash_list_with_primitive_elements!(fixed_size: list_array, values, list_size, field, $hashes_buffer, $hash_method, $recursive_hash_method);
}
DataType::Struct(_) => {
let struct_array = col.as_any().downcast_ref::<StructArray>().unwrap();
// Hash each field of the struct - Spark hashes all fields recursively
let columns: Vec<ArrayRef> = struct_array.columns().to_vec();
if !columns.is_empty() {
$recursive_hash_method(&columns, $hashes_buffer)?;
}
}
DataType::Map(field, _) => {
let map_array = col.as_any().downcast_ref::<MapArray>().unwrap();
let keys = map_array.keys();
let values = map_array.values();
let offsets = map_array.offsets();
// Get key and value types from the struct field
if let DataType::Struct(fields) = field.data_type() {
let key_type = &fields[0].data_type();
let value_type = &fields[1].data_type();
// Specialize for common map key/value combinations
match (key_type, value_type) {
(DataType::Utf8, DataType::Int32) => {
let key_array = keys.as_any().downcast_ref::<StringArray>().unwrap();
let value_array = values.as_any().downcast_ref::<Int32Array>().unwrap();
if map_array.null_count() == 0 && key_array.null_count() == 0 && value_array.null_count() == 0 {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
for entry_idx in start..end {
*hash = $hash_method(key_array.value(entry_idx), *hash);
*hash = $hash_method(value_array.value(entry_idx).to_le_bytes(), *hash);
}
}
} else {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
if !map_array.is_null(row_idx) {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
for entry_idx in start..end {
if !key_array.is_null(entry_idx) {
*hash = $hash_method(key_array.value(entry_idx), *hash);
}
if !value_array.is_null(entry_idx) {
*hash = $hash_method(value_array.value(entry_idx).to_le_bytes(), *hash);
}
}
}
}
}
}
(DataType::Int32, DataType::Utf8) => {
let key_array = keys.as_any().downcast_ref::<Int32Array>().unwrap();
let value_array = values.as_any().downcast_ref::<StringArray>().unwrap();
if map_array.null_count() == 0 && key_array.null_count() == 0 && value_array.null_count() == 0 {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
for entry_idx in start..end {
*hash = $hash_method(key_array.value(entry_idx).to_le_bytes(), *hash);
*hash = $hash_method(value_array.value(entry_idx), *hash);
}
}
} else {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
if !map_array.is_null(row_idx) {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
for entry_idx in start..end {
if !key_array.is_null(entry_idx) {
*hash = $hash_method(key_array.value(entry_idx).to_le_bytes(), *hash);
}
if !value_array.is_null(entry_idx) {
*hash = $hash_method(value_array.value(entry_idx), *hash);
}
}
}
}
}
}
(DataType::Utf8, DataType::Utf8) => {
let key_array = keys.as_any().downcast_ref::<StringArray>().unwrap();
let value_array = values.as_any().downcast_ref::<StringArray>().unwrap();
if map_array.null_count() == 0 && key_array.null_count() == 0 && value_array.null_count() == 0 {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
for entry_idx in start..end {
*hash = $hash_method(key_array.value(entry_idx), *hash);
*hash = $hash_method(value_array.value(entry_idx), *hash);
}
}
} else {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
if !map_array.is_null(row_idx) {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
for entry_idx in start..end {
if !key_array.is_null(entry_idx) {
*hash = $hash_method(key_array.value(entry_idx), *hash);
}
if !value_array.is_null(entry_idx) {
*hash = $hash_method(value_array.value(entry_idx), *hash);
}
}
}
}
}
}
(DataType::Int32, DataType::Int32) => {
let key_array = keys.as_any().downcast_ref::<Int32Array>().unwrap();
let value_array = values.as_any().downcast_ref::<Int32Array>().unwrap();
if map_array.null_count() == 0 && key_array.null_count() == 0 && value_array.null_count() == 0 {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
for entry_idx in start..end {
*hash = $hash_method(key_array.value(entry_idx).to_le_bytes(), *hash);
*hash = $hash_method(value_array.value(entry_idx).to_le_bytes(), *hash);
}
}
} else {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
if !map_array.is_null(row_idx) {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
for entry_idx in start..end {
if !key_array.is_null(entry_idx) {
*hash = $hash_method(key_array.value(entry_idx).to_le_bytes(), *hash);
}
if !value_array.is_null(entry_idx) {
*hash = $hash_method(value_array.value(entry_idx).to_le_bytes(), *hash);
}
}
}
}
}
}
_ => {
// Fall back to recursive approach for other type combinations
if map_array.null_count() == 0 {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
for entry_idx in start..end {
let key_array = keys.slice(entry_idx, 1);
let mut single_hash = [*hash];
$recursive_hash_method(&[key_array], &mut single_hash)?;
*hash = single_hash[0];
let value_array = values.slice(entry_idx, 1);
single_hash = [*hash];
$recursive_hash_method(&[value_array], &mut single_hash)?;
*hash = single_hash[0];
}
}
} else {
for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
if !map_array.is_null(row_idx) {
let start = offsets[row_idx] as usize;
let end = offsets[row_idx + 1] as usize;
for entry_idx in start..end {
let key_array = keys.slice(entry_idx, 1);
let mut single_hash = [*hash];
$recursive_hash_method(&[key_array], &mut single_hash)?;
*hash = single_hash[0];
let value_array = values.slice(entry_idx, 1);
single_hash = [*hash];
$recursive_hash_method(&[value_array], &mut single_hash)?;
*hash = single_hash[0];
}
}
}
}
}
}
} else {
return Err(DataFusionError::Internal(format!(
"Map field type must be a struct, got: {}",
field.data_type()
)));
}
}
_ => {
// This is internal because we should have caught this before.
return Err(DataFusionError::Internal(format!(
"Unsupported data type in hasher: {}",
col.data_type()
)));
}
}
}
};
}
pub(crate) mod test_utils {
#[macro_export]
macro_rules! test_hashes_internal {
($hash_method: ident, $input: expr, $initial_seeds: expr, $expected: expr) => {
let i = $input;
let mut hashes = $initial_seeds.clone();
$hash_method(&[i], &mut hashes).unwrap();
assert_eq!(hashes, $expected);
};
}
#[macro_export]
macro_rules! test_hashes_with_nulls {
($method: ident, $t: ty, $values: ident, $expected: ident, $seed_type: ty) => {
// copied before inserting nulls
let mut input_with_nulls = $values.clone();
let mut expected_with_nulls = $expected.clone();
// test before inserting nulls
let len = $values.len();
let initial_seeds = vec![42 as $seed_type; len];
let i = Arc::new(<$t>::from($values)) as ArrayRef;
$crate::test_hashes_internal!($method, i, initial_seeds, $expected);
// test with nulls
let median = len / 2;
input_with_nulls.insert(0, None);
input_with_nulls.insert(median, None);
expected_with_nulls.insert(0, 42 as $seed_type);
expected_with_nulls.insert(median, 42 as $seed_type);
let len_with_nulls = len + 2;
let initial_seeds_with_nulls = vec![42 as $seed_type; len_with_nulls];
let nullable_input = Arc::new(<$t>::from(input_with_nulls)) as ArrayRef;
$crate::test_hashes_internal!(
$method,
nullable_input,
initial_seeds_with_nulls,
expected_with_nulls
);
};
}
}