blob: b21082f5d100206c8fd02e1a22c18232701cb80d [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.
use crate::spec::{
batch_build_binary_rows, BinaryRow, BucketFunctionType, DataField, DataType, Datum,
};
use arrow_array::RecordBatch;
pub(crate) fn validate_bucket_function(
bucket_function_type: BucketFunctionType,
bucket_key_fields: &[DataField],
) -> crate::Result<()> {
if bucket_function_type == BucketFunctionType::Mod {
if bucket_key_fields.len() != 1 {
return Err(crate::Error::ConfigInvalid {
message: "bucket key must have exactly one field in mod bucket function"
.to_string(),
});
}
let data_type = bucket_key_fields[0].data_type();
if !matches!(data_type, DataType::Int(_) | DataType::BigInt(_)) {
return Err(crate::Error::ConfigInvalid {
message: format!(
"bucket key type must be INT or BIGINT in mod bucket function, but got {data_type:?}"
),
});
}
}
Ok(())
}
pub(crate) fn batch_bucket_ids(
batch: &RecordBatch,
field_indices: &[usize],
fields: &[DataField],
bucket_function_type: BucketFunctionType,
total_buckets: i32,
) -> crate::Result<Vec<i32>> {
let rows = batch_build_binary_rows(batch, field_indices, fields)?;
let bucket_key_fields: Vec<DataField> = field_indices
.iter()
.map(|&idx| fields[idx].clone())
.collect();
rows.iter()
.map(|row| bucket_for_row(row, &bucket_key_fields, bucket_function_type, total_buckets))
.collect()
}
pub(crate) fn bucket_for_datums(
datums: &[(Option<&Datum>, &DataType)],
bucket_function_type: BucketFunctionType,
total_buckets: i32,
) -> crate::Result<i32> {
match bucket_function_type {
BucketFunctionType::Default => {
Ok(BinaryRow::compute_bucket_from_datums(datums, total_buckets))
}
BucketFunctionType::Mod => mod_bucket_from_datums(datums, total_buckets),
BucketFunctionType::Hive => hive_bucket_from_datums(datums, total_buckets),
}
}
fn bucket_for_row(
row: &BinaryRow,
bucket_key_fields: &[DataField],
bucket_function_type: BucketFunctionType,
total_buckets: i32,
) -> crate::Result<i32> {
match bucket_function_type {
BucketFunctionType::Default => Ok(default_bucket(row.hash_code(), total_buckets)),
BucketFunctionType::Mod => mod_bucket_from_row(row, bucket_key_fields, total_buckets),
BucketFunctionType::Hive => hive_bucket_from_row(row, bucket_key_fields, total_buckets),
}
}
fn default_bucket(hash: i32, total_buckets: i32) -> i32 {
(hash % total_buckets).wrapping_abs()
}
fn floor_mod_i64(value: i64, divisor: i32) -> i32 {
value.rem_euclid(divisor as i64) as i32
}
fn mod_bucket_from_row(
row: &BinaryRow,
bucket_key_fields: &[DataField],
total_buckets: i32,
) -> crate::Result<i32> {
validate_bucket_function(BucketFunctionType::Mod, bucket_key_fields)?;
if row.is_null_at(0) {
return Ok(0);
}
match bucket_key_fields[0].data_type() {
DataType::Int(_) => Ok(floor_mod_i64(row.get_int(0)? as i64, total_buckets)),
DataType::BigInt(_) => Ok(floor_mod_i64(row.get_long(0)?, total_buckets)),
other => Err(crate::Error::Unsupported {
message: format!("bucket key type must be INT or BIGINT, but got {other:?}"),
}),
}
}
fn mod_bucket_from_datums(
datums: &[(Option<&Datum>, &DataType)],
total_buckets: i32,
) -> crate::Result<i32> {
if datums.len() != 1 {
return Err(crate::Error::ConfigInvalid {
message: "bucket key must have exactly one field in mod bucket function".to_string(),
});
}
match datums[0] {
(None, DataType::Int(_) | DataType::BigInt(_)) => Ok(0),
(Some(Datum::Int(v)), DataType::Int(_)) => Ok(floor_mod_i64(*v as i64, total_buckets)),
(Some(Datum::Long(v)), DataType::BigInt(_)) => Ok(floor_mod_i64(*v, total_buckets)),
(_, data_type) => Err(crate::Error::Unsupported {
message: format!("bucket key type must be INT or BIGINT, but got {data_type:?}"),
}),
}
}
fn hive_bucket_from_row(
row: &BinaryRow,
bucket_key_fields: &[DataField],
total_buckets: i32,
) -> crate::Result<i32> {
let mut hash = 0_i32;
for (pos, field) in bucket_key_fields.iter().enumerate() {
let datum = row.get_datum(pos, field.data_type())?;
hash = hash
.wrapping_mul(31)
.wrapping_add(hive_hash_datum(datum.as_ref(), field.data_type())?);
}
Ok(positive_mod(hash, total_buckets))
}
fn hive_bucket_from_datums(
datums: &[(Option<&Datum>, &DataType)],
total_buckets: i32,
) -> crate::Result<i32> {
let mut hash = 0_i32;
for (datum, data_type) in datums {
hash = hash
.wrapping_mul(31)
.wrapping_add(hive_hash_datum(*datum, data_type)?);
}
Ok(positive_mod(hash, total_buckets))
}
fn positive_mod(hash: i32, total_buckets: i32) -> i32 {
((hash as u32 & 0x7fff_ffff) % total_buckets as u32) as i32
}
fn hive_hash_datum(datum: Option<&Datum>, data_type: &DataType) -> crate::Result<i32> {
let Some(datum) = datum else {
return Ok(0);
};
match (datum, data_type) {
(Datum::Bool(v), DataType::Boolean(_)) => Ok(i32::from(*v)),
(Datum::TinyInt(v), DataType::TinyInt(_)) => Ok(*v as i32),
(Datum::SmallInt(v), DataType::SmallInt(_)) => Ok(*v as i32),
(Datum::Int(v), DataType::Int(_)) => Ok(*v),
(Datum::Long(v), DataType::BigInt(_)) => Ok(java_long_hash(*v)),
(Datum::Float(v), DataType::Float(_)) => Ok(java_float_bits(*v) as i32),
(Datum::Double(v), DataType::Double(_)) => Ok(java_long_hash(java_double_bits(*v) as i64)),
(Datum::String(v), DataType::Char(_) | DataType::VarChar(_)) => {
Ok(hive_hash_bytes(v.as_bytes()))
}
(Datum::Bytes(v), DataType::Binary(_) | DataType::VarBinary(_)) => Ok(hive_hash_bytes(v)),
(
Datum::Decimal {
unscaled, scale, ..
},
DataType::Decimal(_),
) => {
let (unscaled, scale) = normalize_decimal(*unscaled, *scale);
Ok(java_big_decimal_hash(unscaled, scale))
}
(Datum::Date(v), DataType::Date(_)) => Ok(*v),
(Datum::Time(v), DataType::Time(_)) => Ok(*v),
_ => Err(crate::Error::Unsupported {
message: format!("Unsupported type as bucket key type {data_type:?}"),
}),
}
}
fn hive_hash_bytes(bytes: &[u8]) -> i32 {
bytes.iter().fold(0_i32, |hash, byte| {
hash.wrapping_mul(31).wrapping_add(*byte as i8 as i32)
})
}
fn java_long_hash(value: i64) -> i32 {
let bits = value as u64;
(bits ^ (bits >> 32)) as u32 as i32
}
fn java_float_bits(value: f32) -> u32 {
if value == 0.0 {
0
} else if value.is_nan() {
0x7fc0_0000
} else {
value.to_bits()
}
}
fn java_double_bits(value: f64) -> u64 {
if value == 0.0 {
0
} else if value.is_nan() {
0x7ff8_0000_0000_0000
} else {
value.to_bits()
}
}
fn normalize_decimal(mut unscaled: i128, mut scale: u32) -> (i128, u32) {
if unscaled == 0 {
return (0, 0);
}
while scale > 0 && unscaled % 10 == 0 {
unscaled /= 10;
scale -= 1;
}
(unscaled, scale)
}
fn java_big_decimal_hash(unscaled: i128, scale: u32) -> i32 {
if let Ok(compact) = i64::try_from(unscaled) {
if compact != i64::MIN {
let val = if compact < 0 {
compact.wrapping_neg() as u64
} else {
compact as u64
};
let temp = ((val >> 32) as i32)
.wrapping_mul(31)
.wrapping_add(val as u32 as i32);
let signed_temp = if compact < 0 {
temp.wrapping_neg()
} else {
temp
};
return signed_temp.wrapping_mul(31).wrapping_add(scale as i32);
}
}
java_big_integer_hash(unscaled)
.wrapping_mul(31)
.wrapping_add(scale as i32)
}
fn java_big_integer_hash(value: i128) -> i32 {
if value == 0 {
return 0;
}
let sign = if value < 0 { -1_i32 } else { 1_i32 };
let mut magnitude = value.unsigned_abs();
let mut words = Vec::new();
while magnitude != 0 {
words.push((magnitude & 0xffff_ffff) as u32);
magnitude >>= 32;
}
words.reverse();
let hash = words.into_iter().fold(0_i32, |hash, word| {
hash.wrapping_mul(31).wrapping_add(word as i32)
});
hash.wrapping_mul(sign)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::spec::{BigIntType, BooleanType, DecimalType, IntType, VarBinaryType, VarCharType};
#[test]
fn mod_bucket_uses_floor_mod_for_int_and_bigint() {
let int_type = DataType::Int(IntType::new());
let long_type = DataType::BigInt(BigIntType::new());
assert_eq!(
bucket_for_datums(
&[(Some(&Datum::Int(-3)), &int_type)],
BucketFunctionType::Mod,
5,
)
.unwrap(),
2
);
assert_eq!(
bucket_for_datums(
&[(Some(&Datum::Long(17)), &long_type)],
BucketFunctionType::Mod,
5,
)
.unwrap(),
2
);
}
#[test]
fn hive_bucket_matches_java_reference_case() {
let bool_type = DataType::Boolean(BooleanType::new());
let int_type = DataType::Int(IntType::new());
let string_type = DataType::VarChar(VarCharType::default());
let bytes_type = DataType::VarBinary(VarBinaryType::default());
let decimal_type = DataType::Decimal(DecimalType::new(10, 4).unwrap());
let bucket = bucket_for_datums(
&[
(Some(&Datum::Bool(true)), &bool_type),
(Some(&Datum::Int(7)), &int_type),
(Some(&Datum::String("hello".into())), &string_type),
(Some(&Datum::Bytes(vec![1, 2, 3])), &bytes_type),
(
Some(&Datum::Decimal {
unscaled: 123400,
precision: 10,
scale: 4,
}),
&decimal_type,
),
],
BucketFunctionType::Hive,
8,
)
.unwrap();
let expected_hash = 31_i32
.wrapping_mul(
31_i32
.wrapping_mul(
31_i32
.wrapping_mul(31_i32.wrapping_mul(1).wrapping_add(7))
.wrapping_add(99_162_322),
)
.wrapping_add(1_026),
)
.wrapping_add(38_256);
assert_eq!(bucket, positive_mod(expected_hash, 8));
}
#[test]
fn hive_decimal_hash_trims_trailing_zeros_like_big_decimal() {
assert_eq!(normalize_decimal(123400, 4), (1234, 2));
assert_eq!(normalize_decimal(0, 8), (0, 0));
}
}