use crate::cast::*;
pub(crate) fn dictionary_cast<K: ArrowDictionaryKeyType>(
array: &dyn Array,
to_type: &DataType,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError> {
use DataType::*;
#[inline]
fn is_sparse<K: ArrowDictionaryKeyType>(array: &DictionaryArray<K>) -> bool {
array.keys().len() < array.values().len() / 2
}
#[inline]
fn values_buffer_fits_in_view<T: ByteArrayType>(values: &GenericByteArray<T>) -> bool {
values.values().len() < i32::MAX as usize
}
let array = array.as_dictionary::<K>();
let from_child_type = array.values().data_type();
match (from_child_type, to_type) {
(_, Dictionary(to_index_type, to_value_type)) => {
dictionary_to_dictionary_cast(array, to_index_type, to_value_type, cast_options)
}
(Utf8, Utf8View) if is_sparse(array) => {
view_from_dict_values::<K, Utf8Type, StringViewType>(
array.keys(),
array.values().as_string::<i32>(),
)
}
(Binary, BinaryView) if is_sparse(array) => {
view_from_dict_values::<K, BinaryType, BinaryViewType>(
array.keys(),
array.values().as_binary::<i32>(),
)
}
(LargeUtf8, Utf8View)
if is_sparse(array)
&& values_buffer_fits_in_view(array.values().as_string::<i64>()) =>
{
view_from_dict_values::<K, LargeUtf8Type, StringViewType>(
array.keys(),
array.values().as_string::<i64>(),
)
}
(LargeBinary, BinaryView)
if is_sparse(array)
&& values_buffer_fits_in_view(array.values().as_binary::<i64>()) =>
{
view_from_dict_values::<K, LargeBinaryType, BinaryViewType>(
array.keys(),
array.values().as_binary::<i64>(),
)
}
(Utf8, BinaryView) if is_sparse(array) => {
view_from_dict_values::<K, Utf8Type, BinaryViewType>(
array.keys(),
array.values().as_string::<i32>(),
)
}
(LargeUtf8, BinaryView)
if is_sparse(array)
&& values_buffer_fits_in_view(array.values().as_string::<i64>()) =>
{
view_from_dict_values::<K, LargeUtf8Type, BinaryViewType>(
array.keys(),
array.values().as_string::<i64>(),
)
}
(Binary, Utf8View) if is_sparse(array) => binary_dict_to_string_view::<K, i32>(
array.keys(),
array.values().as_binary::<i32>(),
cast_options,
),
(LargeBinary, Utf8View)
if is_sparse(array)
&& values_buffer_fits_in_view(array.values().as_binary::<i64>()) =>
{
binary_dict_to_string_view::<K, i64>(
array.keys(),
array.values().as_binary::<i64>(),
cast_options,
)
}
_ => unpack_dictionary(array, to_type, cast_options),
}
}
fn dictionary_to_dictionary_cast<K: ArrowDictionaryKeyType>(
array: &DictionaryArray<K>,
to_index_type: &DataType,
to_value_type: &DataType,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError> {
use DataType::*;
if array.values().data_type() == to_value_type
&& !matches!(array.values().data_type(), Dictionary(_, _))
{
return cast_keys_only(array, to_index_type, cast_options);
}
if matches!(array.values().data_type(), Dictionary(_, _)) {
let flattened = take(array.values().as_ref(), array.keys(), None)?;
return cast_with_options(
&flattened,
&Dictionary(
Box::new(to_index_type.clone()),
Box::new(to_value_type.clone()),
),
cast_options,
);
}
let keys_array: ArrayRef = Arc::new(PrimitiveArray::<K>::from(array.keys().to_data()));
let values_array = array.values();
let cast_keys = cast_with_options(&keys_array, to_index_type, cast_options)?;
let cast_values = cast_with_options(values_array, to_value_type, cast_options)?;
if cast_keys.null_count() > keys_array.null_count() {
return Err(ArrowError::ComputeError(format!(
"Could not convert {} dictionary indexes from {:?} to {:?}",
cast_keys.null_count() - keys_array.null_count(),
keys_array.data_type(),
to_index_type
)));
}
let data = cast_keys.into_data();
let builder = data
.into_builder()
.data_type(Dictionary(
Box::new(to_index_type.clone()),
Box::new(to_value_type.clone()),
))
.child_data(vec![cast_values.into_data()]);
let data = unsafe { builder.build_unchecked() };
let new_array: ArrayRef = match to_index_type {
Int8 => Arc::new(DictionaryArray::<Int8Type>::from(data)),
Int16 => Arc::new(DictionaryArray::<Int16Type>::from(data)),
Int32 => Arc::new(DictionaryArray::<Int32Type>::from(data)),
Int64 => Arc::new(DictionaryArray::<Int64Type>::from(data)),
UInt8 => Arc::new(DictionaryArray::<UInt8Type>::from(data)),
UInt16 => Arc::new(DictionaryArray::<UInt16Type>::from(data)),
UInt32 => Arc::new(DictionaryArray::<UInt32Type>::from(data)),
UInt64 => Arc::new(DictionaryArray::<UInt64Type>::from(data)),
_ => {
return Err(ArrowError::CastError(format!(
"Unsupported type {to_index_type} for dictionary index"
)));
}
};
Ok(new_array)
}
fn finish_keys_only<K2: ArrowDictionaryKeyType>(
cast_keys: ArrayRef,
values: ArrayRef,
) -> Result<ArrayRef, ArrowError> {
let new_keys = cast_keys
.as_any()
.downcast_ref::<PrimitiveArray<K2>>()
.ok_or_else(|| {
ArrowError::ComputeError(
"Internal Error: cast keys did not downcast to target key type".to_string(),
)
})?
.clone();
Ok(Arc::new(unsafe {
DictionaryArray::<K2>::new_unchecked(new_keys, values)
}))
}
fn cast_keys_only<K: ArrowDictionaryKeyType>(
array: &DictionaryArray<K>,
to_index_type: &DataType,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError> {
let source_keys = array.keys();
let cast_keys = cast_with_options(&source_keys, to_index_type, cast_options)?;
if cast_keys.null_count() > source_keys.null_count() {
return Err(ArrowError::ComputeError(format!(
"Could not convert {} dictionary indexes from {:?} to {:?}",
cast_keys.null_count() - source_keys.null_count(),
source_keys.data_type(),
to_index_type
)));
}
let values = array.values().clone();
match to_index_type {
DataType::Int8 => finish_keys_only::<Int8Type>(cast_keys, values),
DataType::Int16 => finish_keys_only::<Int16Type>(cast_keys, values),
DataType::Int32 => finish_keys_only::<Int32Type>(cast_keys, values),
DataType::Int64 => finish_keys_only::<Int64Type>(cast_keys, values),
DataType::UInt8 => finish_keys_only::<UInt8Type>(cast_keys, values),
DataType::UInt16 => finish_keys_only::<UInt16Type>(cast_keys, values),
DataType::UInt32 => finish_keys_only::<UInt32Type>(cast_keys, values),
DataType::UInt64 => finish_keys_only::<UInt64Type>(cast_keys, values),
_ => Err(ArrowError::CastError(format!(
"Unsupported type {to_index_type} for dictionary index"
))),
}
}
fn binary_dict_to_string_view<K: ArrowDictionaryKeyType, O: OffsetSizeTrait>(
keys: &PrimitiveArray<K>,
values: &GenericByteArray<GenericBinaryType<O>>,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError> {
match GenericStringArray::<O>::try_from_binary(values.clone()) {
Ok(_) => {
view_from_dict_values::<K, GenericBinaryType<O>, StringViewType>(keys, values)
}
Err(e) => {
if !cast_options.safe {
return Err(e);
}
let valid: Vec<bool> = (0..values.len())
.map(|i| !values.is_null(i) && std::str::from_utf8(values.value(i)).is_ok())
.collect();
let value_buffer = values.values();
let value_offsets = values.value_offsets();
let mut builder = StringViewBuilder::with_capacity(keys.len());
builder.append_block(value_buffer.clone());
for key in keys {
match key {
Some(v) => {
let idx = v.to_usize().ok_or_else(|| {
ArrowError::ComputeError("Invalid dictionary index".to_string())
})?;
let is_valid = *valid.get(idx).ok_or_else(|| {
ArrowError::InvalidArgumentError(format!(
"Dictionary key {idx} out of bounds for dictionary values of length {}",
valid.len()
))
})?;
if is_valid {
unsafe {
let offset = value_offsets.get_unchecked(idx).as_usize();
let end = value_offsets.get_unchecked(idx + 1).as_usize();
let length = end - offset;
builder.append_view_unchecked(0, offset as u32, length as u32);
}
} else {
builder.append_null();
}
}
None => builder.append_null(),
}
}
Ok(Arc::new(builder.finish()))
}
}
}
fn view_from_dict_values<K: ArrowDictionaryKeyType, V: ByteArrayType, T: ByteViewType>(
keys: &PrimitiveArray<K>,
values: &GenericByteArray<V>,
) -> Result<ArrayRef, ArrowError> {
let value_buffer = values.values();
let value_offsets = values.value_offsets();
let values_have_nulls = values.null_count() != 0;
let mut builder = GenericByteViewBuilder::<T>::with_capacity(keys.len());
builder.append_block(value_buffer.clone());
for i in keys {
match i {
Some(v) => {
let idx = v.to_usize().ok_or_else(|| {
ArrowError::ComputeError("Invalid dictionary index".to_string())
})?;
if values_have_nulls && values.is_null(idx) {
builder.append_null();
continue;
}
unsafe {
let offset = value_offsets.get_unchecked(idx).as_usize();
let end = value_offsets.get_unchecked(idx + 1).as_usize();
let length = end - offset;
builder.append_view_unchecked(0, offset as u32, length as u32)
}
}
None => {
builder.append_null();
}
}
}
Ok(Arc::new(builder.finish()))
}
pub(crate) fn unpack_dictionary<K: ArrowDictionaryKeyType>(
array: &DictionaryArray<K>,
to_type: &DataType,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError> {
let cast_dict_values = cast_with_options(array.values(), to_type, cast_options)?;
take(cast_dict_values.as_ref(), array.keys(), None)
}
pub(crate) fn pack_array_to_dictionary_via_primitive<K: ArrowDictionaryKeyType>(
array: &dyn Array,
primitive_type: DataType,
dict_value_type: &DataType,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError> {
let primitive = cast_with_options(array, &primitive_type, cast_options)?;
let dict = cast_with_options(
primitive.as_ref(),
&DataType::Dictionary(Box::new(K::DATA_TYPE), Box::new(primitive_type)),
cast_options,
)?;
cast_with_options(
dict.as_ref(),
&DataType::Dictionary(Box::new(K::DATA_TYPE), Box::new(dict_value_type.clone())),
cast_options,
)
}
pub(crate) fn cast_to_dictionary<K: ArrowDictionaryKeyType>(
array: &dyn Array,
dict_value_type: &DataType,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError> {
use DataType::*;
match *dict_value_type {
Int8 => pack_numeric_to_dictionary::<K, Int8Type>(array, dict_value_type, cast_options),
Int16 => pack_numeric_to_dictionary::<K, Int16Type>(array, dict_value_type, cast_options),
Int32 => pack_numeric_to_dictionary::<K, Int32Type>(array, dict_value_type, cast_options),
Int64 => pack_numeric_to_dictionary::<K, Int64Type>(array, dict_value_type, cast_options),
UInt8 => pack_numeric_to_dictionary::<K, UInt8Type>(array, dict_value_type, cast_options),
UInt16 => pack_numeric_to_dictionary::<K, UInt16Type>(array, dict_value_type, cast_options),
UInt32 => pack_numeric_to_dictionary::<K, UInt32Type>(array, dict_value_type, cast_options),
UInt64 => pack_numeric_to_dictionary::<K, UInt64Type>(array, dict_value_type, cast_options),
Decimal32(p, s) => pack_decimal_to_dictionary::<K, Decimal32Type>(
array,
dict_value_type,
p,
s,
cast_options,
),
Decimal64(p, s) => pack_decimal_to_dictionary::<K, Decimal64Type>(
array,
dict_value_type,
p,
s,
cast_options,
),
Decimal128(p, s) => pack_decimal_to_dictionary::<K, Decimal128Type>(
array,
dict_value_type,
p,
s,
cast_options,
),
Decimal256(p, s) => pack_decimal_to_dictionary::<K, Decimal256Type>(
array,
dict_value_type,
p,
s,
cast_options,
),
Float16 => {
pack_numeric_to_dictionary::<K, Float16Type>(array, dict_value_type, cast_options)
}
Float32 => {
pack_numeric_to_dictionary::<K, Float32Type>(array, dict_value_type, cast_options)
}
Float64 => {
pack_numeric_to_dictionary::<K, Float64Type>(array, dict_value_type, cast_options)
}
Date32 => pack_array_to_dictionary_via_primitive::<K>(
array,
DataType::Int32,
dict_value_type,
cast_options,
),
Date64 => pack_array_to_dictionary_via_primitive::<K>(
array,
DataType::Int64,
dict_value_type,
cast_options,
),
Time32(_) => pack_array_to_dictionary_via_primitive::<K>(
array,
DataType::Int32,
dict_value_type,
cast_options,
),
Time64(_) => pack_array_to_dictionary_via_primitive::<K>(
array,
DataType::Int64,
dict_value_type,
cast_options,
),
Timestamp(_, _) => pack_array_to_dictionary_via_primitive::<K>(
array,
DataType::Int64,
dict_value_type,
cast_options,
),
Utf8 => {
if array.data_type() == &DataType::Utf8View {
return string_view_to_dictionary::<K, i32>(array);
}
pack_byte_to_dictionary::<K, GenericStringType<i32>>(array, cast_options)
}
LargeUtf8 => {
if array.data_type() == &DataType::Utf8View {
return string_view_to_dictionary::<K, i64>(array);
}
pack_byte_to_dictionary::<K, GenericStringType<i64>>(array, cast_options)
}
Utf8View => {
let base_value_type = match array.data_type() {
DataType::LargeUtf8 | DataType::Utf8View => DataType::LargeUtf8,
_ => DataType::Utf8,
};
let dict_base = cast_to_dictionary::<K>(array, &base_value_type, cast_options)?;
dictionary_cast::<K>(
dict_base.as_ref(),
&DataType::Dictionary(Box::new(K::DATA_TYPE), Box::new(DataType::Utf8View)),
cast_options,
)
}
Binary => {
if array.data_type() == &DataType::BinaryView {
return binary_view_to_dictionary::<K, i32>(array);
}
pack_byte_to_dictionary::<K, GenericBinaryType<i32>>(array, cast_options)
}
LargeBinary => {
if array.data_type() == &DataType::BinaryView {
return binary_view_to_dictionary::<K, i64>(array);
}
pack_byte_to_dictionary::<K, GenericBinaryType<i64>>(array, cast_options)
}
BinaryView => {
let base_value_type = match array.data_type() {
DataType::LargeBinary | DataType::BinaryView => DataType::LargeBinary,
_ => DataType::Binary,
};
let dict_base = cast_to_dictionary::<K>(array, &base_value_type, cast_options)?;
dictionary_cast::<K>(
dict_base.as_ref(),
&DataType::Dictionary(Box::new(K::DATA_TYPE), Box::new(DataType::BinaryView)),
cast_options,
)
}
FixedSizeBinary(byte_size) => {
pack_byte_to_fixed_size_dictionary::<K>(array, cast_options, byte_size)
}
Struct(_) => pack_struct_to_dictionary::<K>(array, dict_value_type, cast_options),
_ => Err(ArrowError::CastError(format!(
"Unsupported output type for dictionary packing: {dict_value_type}"
))),
}
}
fn pack_struct_to_dictionary<K: ArrowDictionaryKeyType>(
array: &dyn Array,
dict_value_type: &DataType,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError> {
let cast_values = cast_with_options(array, dict_value_type, cast_options)?;
let len = cast_values.len();
let mut builder = PrimitiveBuilder::<K>::with_capacity(len);
for i in 0..len {
if cast_values.is_null(i) {
builder.append_null();
} else {
let key = K::Native::from_usize(i).ok_or_else(|| {
ArrowError::CastError(format!(
"Cannot fit {len} dictionary keys in {:?}",
K::DATA_TYPE,
))
})?;
builder.append_value(key);
}
}
let keys = builder.finish();
Ok(Arc::new(DictionaryArray::<K>::try_new(keys, cast_values)?))
}
pub(crate) fn pack_numeric_to_dictionary<K, V>(
array: &dyn Array,
dict_value_type: &DataType,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError>
where
K: ArrowDictionaryKeyType,
V: ArrowPrimitiveType,
{
let cast_values = cast_with_options(array, dict_value_type, cast_options)?;
let values = cast_values.as_primitive::<V>();
let mut b = PrimitiveDictionaryBuilder::<K, V>::with_capacity(values.len(), values.len());
for i in 0..values.len() {
if values.is_null(i) {
b.append_null();
} else {
b.append(values.value(i))?;
}
}
Ok(Arc::new(b.finish()))
}
pub(crate) fn pack_decimal_to_dictionary<K, D>(
array: &dyn Array,
dict_value_type: &DataType,
precision: u8,
scale: i8,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError>
where
K: ArrowDictionaryKeyType,
D: DecimalType + ArrowPrimitiveType,
{
let dict = pack_numeric_to_dictionary::<K, D>(array, dict_value_type, cast_options)?;
let dict = dict.as_dictionary::<K>();
let typed = dict.downcast_dict::<PrimitiveArray<D>>().ok_or_else(|| {
ArrowError::ComputeError(format!(
"Internal Error: Cannot cast dict to {}Array",
D::PREFIX
))
})?;
let value = typed
.values()
.clone()
.with_precision_and_scale(precision, scale)?;
Ok(Arc::new(dict.with_values(Arc::new(value))))
}
pub(crate) fn string_view_to_dictionary<K, O: OffsetSizeTrait>(
array: &dyn Array,
) -> Result<ArrayRef, ArrowError>
where
K: ArrowDictionaryKeyType,
{
let mut b = GenericByteDictionaryBuilder::<K, GenericStringType<O>>::with_capacity(
array.len(),
1024,
1024,
);
let string_view = array
.as_any()
.downcast_ref::<StringViewArray>()
.ok_or_else(|| {
ArrowError::ComputeError("Internal Error: Cannot cast to StringViewArray".to_string())
})?;
for v in string_view {
match v {
Some(v) => {
b.append(v)?;
}
None => {
b.append_null();
}
}
}
Ok(Arc::new(b.finish()))
}
pub(crate) fn binary_view_to_dictionary<K, O: OffsetSizeTrait>(
array: &dyn Array,
) -> Result<ArrayRef, ArrowError>
where
K: ArrowDictionaryKeyType,
{
let mut b = GenericByteDictionaryBuilder::<K, GenericBinaryType<O>>::with_capacity(
array.len(),
1024,
1024,
);
let binary_view = array
.as_any()
.downcast_ref::<BinaryViewArray>()
.ok_or_else(|| {
ArrowError::ComputeError("Internal Error: Cannot cast to BinaryViewArray".to_string())
})?;
for v in binary_view {
match v {
Some(v) => {
b.append(v)?;
}
None => {
b.append_null();
}
}
}
Ok(Arc::new(b.finish()))
}
pub(crate) fn pack_byte_to_dictionary<K, T>(
array: &dyn Array,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError>
where
K: ArrowDictionaryKeyType,
T: ByteArrayType,
{
let cast_values = cast_with_options(array, &T::DATA_TYPE, cast_options)?;
let values = cast_values
.as_any()
.downcast_ref::<GenericByteArray<T>>()
.ok_or_else(|| {
ArrowError::ComputeError("Internal Error: Cannot cast to GenericByteArray".to_string())
})?;
let mut b = GenericByteDictionaryBuilder::<K, T>::with_capacity(values.len(), 1024, 1024);
b.append_array(values)?;
Ok(Arc::new(b.finish()))
}
pub(crate) fn pack_byte_to_fixed_size_dictionary<K>(
array: &dyn Array,
cast_options: &CastOptions,
byte_width: i32,
) -> Result<ArrayRef, ArrowError>
where
K: ArrowDictionaryKeyType,
{
let cast_values =
cast_with_options(array, &DataType::FixedSizeBinary(byte_width), cast_options)?;
let values = cast_values
.as_any()
.downcast_ref::<FixedSizeBinaryArray>()
.ok_or_else(|| {
ArrowError::ComputeError("Internal Error: Cannot cast to GenericByteArray".to_string())
})?;
let mut b = FixedSizeBinaryDictionaryBuilder::<K>::with_capacity(1024, 1024, byte_width);
for i in 0..values.len() {
if values.is_null(i) {
b.append_null();
} else {
b.append(values.value(i))?;
}
}
Ok(Arc::new(b.finish()))
}
#[cfg(test)]
mod tests {
use super::*;
use DataType::*;
fn sparse_keys(values: &ArrayRef) -> Int32Array {
let keys = Int32Array::from(vec![Some(0), Some(2), None, Some(3)]);
assert!(
keys.len() < values.len() / 2,
"keys must reach the direct path"
);
keys
}
fn dense_keys(values: &ArrayRef) -> Int32Array {
let keys = Int32Array::from(vec![
Some(0),
Some(2),
None,
Some(3),
Some(1),
Some(0),
Some(4),
Some(2),
Some(3),
Some(5),
]);
assert!(
keys.len() >= values.len() / 2,
"keys must reach unpack_dictionary"
);
keys
}
fn make_dict(keys: &Int32Array, values: &ArrayRef) -> DictionaryArray<Int32Type> {
DictionaryArray::try_new(keys.clone(), values.clone()).unwrap()
}
#[test]
fn test_dict_to_view_matches_take_then_cast() {
let long = "a value over twelve bytes";
let utf8: ArrayRef = Arc::new(StringArray::from(vec![
Some("aa"),
Some("bb"),
Some(long),
None,
Some("ee"),
Some("ff"),
Some("gg"),
Some("hh"),
Some("ii"),
Some("jj"),
]));
for from in [Utf8, LargeUtf8, Binary, LargeBinary] {
let values = cast(&utf8, &from).unwrap();
for to in [Utf8View, BinaryView] {
for keys in [sparse_keys(&values), dense_keys(&values)] {
let expected = cast(&take(&values, &keys, None).unwrap(), &to).unwrap();
let casted = cast(&make_dict(&keys, &values), &to).unwrap();
assert_eq!(casted.as_ref(), expected.as_ref(), "{from:?} -> {to:?}");
}
}
}
}
#[test]
fn test_dict_binary_to_utf8view_invalid_utf8() {
let bytes: Vec<&[u8]> = vec![
b"aa",
b"bb",
&[0xFF, 0xFE],
b"dd",
b"ee",
b"ff",
b"gg",
b"hh",
b"ii",
b"jj",
];
let strict = CastOptions {
safe: false,
..Default::default()
};
let safe = CastOptions {
safe: true,
..Default::default()
};
for values in [
Arc::new(BinaryArray::from_vec(bytes.clone())) as ArrayRef,
Arc::new(LargeBinaryArray::from_vec(bytes.clone())) as ArrayRef,
] {
for (keys, expected) in [
(
sparse_keys(&values),
vec![Some("aa"), None, None, Some("dd")],
),
(
dense_keys(&values),
vec![
Some("aa"),
None,
None,
Some("dd"),
Some("bb"),
Some("aa"),
Some("ee"),
None,
Some("dd"),
Some("ff"),
],
),
] {
let dict = make_dict(&keys, &values);
assert!(cast_with_options(&dict, &Utf8View, &strict).is_err());
let casted = cast_with_options(&dict, &Utf8View, &safe).unwrap();
assert_eq!(casted.as_string_view(), &StringViewArray::from(expected));
}
}
}
}