Skip to main content

arrow_cast/cast/
dictionary.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::cast::*;
19
20/// Attempts to cast an `ArrayDictionary` with index type K into
21/// `to_type` for supported types.
22///
23/// K is the key type
24pub(crate) fn dictionary_cast<K: ArrowDictionaryKeyType>(
25    array: &dyn Array,
26    to_type: &DataType,
27    cast_options: &CastOptions,
28) -> Result<ArrayRef, ArrowError> {
29    use DataType::*;
30
31    let array = array.as_dictionary::<K>();
32    let from_child_type = array.values().data_type();
33    match (from_child_type, to_type) {
34        (_, Dictionary(to_index_type, to_value_type)) => {
35            dictionary_to_dictionary_cast(array, to_index_type, to_value_type, cast_options)
36        }
37        // `unpack_dictionary` can handle Utf8View/BinaryView types, but incurs unnecessary data
38        // copy of the value buffer. Fast path which avoids copying underlying values buffer.
39        // TODO: handle LargeUtf8/LargeBinary -> View (need to check offsets can fit)
40        // TODO: handle cross types (String -> BinaryView, Binary -> StringView)
41        //       (need to validate utf8?)
42        (Utf8, Utf8View) => view_from_dict_values::<K, Utf8Type, StringViewType>(
43            array.keys(),
44            array.values().as_string::<i32>(),
45        ),
46        (Binary, BinaryView) => view_from_dict_values::<K, BinaryType, BinaryViewType>(
47            array.keys(),
48            array.values().as_binary::<i32>(),
49        ),
50        _ => unpack_dictionary(array, to_type, cast_options),
51    }
52}
53
54fn dictionary_to_dictionary_cast<K: ArrowDictionaryKeyType>(
55    array: &DictionaryArray<K>,
56    to_index_type: &DataType,
57    to_value_type: &DataType,
58    cast_options: &CastOptions,
59) -> Result<ArrayRef, ArrowError> {
60    use DataType::*;
61
62    // Fast path for a nested dictionary source (`Dictionary<K, Dictionary<K2, V>>`).
63    // Both layers index into the same inner values, so the two index layers can
64    // be composed into one rather than materializing the values: `take` gathers
65    // the inner keys through the outer keys and reuses the inner values buffer
66    // untouched, so no value data is rewritten. The flattened single-level
67    // dictionary is then cast to the requested index/value types.
68    if matches!(array.values().data_type(), Dictionary(_, _)) {
69        let flattened = take(array.values().as_ref(), array.keys(), None)?;
70        return cast_with_options(
71            &flattened,
72            &Dictionary(
73                Box::new(to_index_type.clone()),
74                Box::new(to_value_type.clone()),
75            ),
76            cast_options,
77        );
78    }
79
80    let keys_array: ArrayRef = Arc::new(PrimitiveArray::<K>::from(array.keys().to_data()));
81    let values_array = array.values();
82    let cast_keys = cast_with_options(&keys_array, to_index_type, cast_options)?;
83    let cast_values = cast_with_options(values_array, to_value_type, cast_options)?;
84
85    // Failure to cast keys (because they don't fit in the
86    // target type) results in NULL values;
87    if cast_keys.null_count() > keys_array.null_count() {
88        return Err(ArrowError::ComputeError(format!(
89            "Could not convert {} dictionary indexes from {:?} to {:?}",
90            cast_keys.null_count() - keys_array.null_count(),
91            keys_array.data_type(),
92            to_index_type
93        )));
94    }
95
96    let data = cast_keys.into_data();
97    let builder = data
98        .into_builder()
99        .data_type(Dictionary(
100            Box::new(to_index_type.clone()),
101            Box::new(to_value_type.clone()),
102        ))
103        .child_data(vec![cast_values.into_data()]);
104
105    // Safety
106    // Cast keys are still valid
107    let data = unsafe { builder.build_unchecked() };
108
109    // create the appropriate array type
110    let new_array: ArrayRef = match to_index_type {
111        Int8 => Arc::new(DictionaryArray::<Int8Type>::from(data)),
112        Int16 => Arc::new(DictionaryArray::<Int16Type>::from(data)),
113        Int32 => Arc::new(DictionaryArray::<Int32Type>::from(data)),
114        Int64 => Arc::new(DictionaryArray::<Int64Type>::from(data)),
115        UInt8 => Arc::new(DictionaryArray::<UInt8Type>::from(data)),
116        UInt16 => Arc::new(DictionaryArray::<UInt16Type>::from(data)),
117        UInt32 => Arc::new(DictionaryArray::<UInt32Type>::from(data)),
118        UInt64 => Arc::new(DictionaryArray::<UInt64Type>::from(data)),
119        _ => {
120            return Err(ArrowError::CastError(format!(
121                "Unsupported type {to_index_type} for dictionary index"
122            )));
123        }
124    };
125
126    Ok(new_array)
127}
128
129fn view_from_dict_values<K: ArrowDictionaryKeyType, V: ByteArrayType, T: ByteViewType>(
130    keys: &PrimitiveArray<K>,
131    values: &GenericByteArray<V>,
132) -> Result<ArrayRef, ArrowError> {
133    let value_buffer = values.values();
134    let value_offsets = values.value_offsets();
135    // A null *value* must produce a null row, not the empty slice its offsets happen to span.
136    let values_have_nulls = values.null_count() != 0;
137    let mut builder = GenericByteViewBuilder::<T>::with_capacity(keys.len());
138    builder.append_block(value_buffer.clone());
139    for i in keys.iter() {
140        match i {
141            Some(v) => {
142                let idx = v.to_usize().ok_or_else(|| {
143                    ArrowError::ComputeError("Invalid dictionary index".to_string())
144                })?;
145
146                if values_have_nulls && values.is_null(idx) {
147                    builder.append_null();
148                    continue;
149                }
150
151                // Safety
152                // (1) The index is within bounds as they are offsets
153                // (2) The append_view is safe
154                unsafe {
155                    let offset = value_offsets.get_unchecked(idx).as_usize();
156                    let end = value_offsets.get_unchecked(idx + 1).as_usize();
157                    let length = end - offset;
158                    builder.append_view_unchecked(0, offset as u32, length as u32)
159                }
160            }
161            None => {
162                builder.append_null();
163            }
164        }
165    }
166    Ok(Arc::new(builder.finish()))
167}
168
169// Unpack a dictionary into a flattened array of type to_type
170pub(crate) fn unpack_dictionary<K: ArrowDictionaryKeyType>(
171    array: &DictionaryArray<K>,
172    to_type: &DataType,
173    cast_options: &CastOptions,
174) -> Result<ArrayRef, ArrowError> {
175    let cast_dict_values = cast_with_options(array.values(), to_type, cast_options)?;
176    take(cast_dict_values.as_ref(), array.keys(), None)
177}
178
179/// Pack a data type into a dictionary array passing the values through a primitive array
180pub(crate) fn pack_array_to_dictionary_via_primitive<K: ArrowDictionaryKeyType>(
181    array: &dyn Array,
182    primitive_type: DataType,
183    dict_value_type: &DataType,
184    cast_options: &CastOptions,
185) -> Result<ArrayRef, ArrowError> {
186    let primitive = cast_with_options(array, &primitive_type, cast_options)?;
187    let dict = cast_with_options(
188        primitive.as_ref(),
189        &DataType::Dictionary(Box::new(K::DATA_TYPE), Box::new(primitive_type)),
190        cast_options,
191    )?;
192    cast_with_options(
193        dict.as_ref(),
194        &DataType::Dictionary(Box::new(K::DATA_TYPE), Box::new(dict_value_type.clone())),
195        cast_options,
196    )
197}
198
199/// Attempts to encode an array into an `ArrayDictionary` with index
200/// type K and value (dictionary) type value_type
201///
202/// K is the key type
203pub(crate) fn cast_to_dictionary<K: ArrowDictionaryKeyType>(
204    array: &dyn Array,
205    dict_value_type: &DataType,
206    cast_options: &CastOptions,
207) -> Result<ArrayRef, ArrowError> {
208    use DataType::*;
209
210    match *dict_value_type {
211        Int8 => pack_numeric_to_dictionary::<K, Int8Type>(array, dict_value_type, cast_options),
212        Int16 => pack_numeric_to_dictionary::<K, Int16Type>(array, dict_value_type, cast_options),
213        Int32 => pack_numeric_to_dictionary::<K, Int32Type>(array, dict_value_type, cast_options),
214        Int64 => pack_numeric_to_dictionary::<K, Int64Type>(array, dict_value_type, cast_options),
215        UInt8 => pack_numeric_to_dictionary::<K, UInt8Type>(array, dict_value_type, cast_options),
216        UInt16 => pack_numeric_to_dictionary::<K, UInt16Type>(array, dict_value_type, cast_options),
217        UInt32 => pack_numeric_to_dictionary::<K, UInt32Type>(array, dict_value_type, cast_options),
218        UInt64 => pack_numeric_to_dictionary::<K, UInt64Type>(array, dict_value_type, cast_options),
219        Decimal32(p, s) => pack_decimal_to_dictionary::<K, Decimal32Type>(
220            array,
221            dict_value_type,
222            p,
223            s,
224            cast_options,
225        ),
226        Decimal64(p, s) => pack_decimal_to_dictionary::<K, Decimal64Type>(
227            array,
228            dict_value_type,
229            p,
230            s,
231            cast_options,
232        ),
233        Decimal128(p, s) => pack_decimal_to_dictionary::<K, Decimal128Type>(
234            array,
235            dict_value_type,
236            p,
237            s,
238            cast_options,
239        ),
240        Decimal256(p, s) => pack_decimal_to_dictionary::<K, Decimal256Type>(
241            array,
242            dict_value_type,
243            p,
244            s,
245            cast_options,
246        ),
247        Float16 => {
248            pack_numeric_to_dictionary::<K, Float16Type>(array, dict_value_type, cast_options)
249        }
250        Float32 => {
251            pack_numeric_to_dictionary::<K, Float32Type>(array, dict_value_type, cast_options)
252        }
253        Float64 => {
254            pack_numeric_to_dictionary::<K, Float64Type>(array, dict_value_type, cast_options)
255        }
256        Date32 => pack_array_to_dictionary_via_primitive::<K>(
257            array,
258            DataType::Int32,
259            dict_value_type,
260            cast_options,
261        ),
262        Date64 => pack_array_to_dictionary_via_primitive::<K>(
263            array,
264            DataType::Int64,
265            dict_value_type,
266            cast_options,
267        ),
268        Time32(_) => pack_array_to_dictionary_via_primitive::<K>(
269            array,
270            DataType::Int32,
271            dict_value_type,
272            cast_options,
273        ),
274        Time64(_) => pack_array_to_dictionary_via_primitive::<K>(
275            array,
276            DataType::Int64,
277            dict_value_type,
278            cast_options,
279        ),
280        Timestamp(_, _) => pack_array_to_dictionary_via_primitive::<K>(
281            array,
282            DataType::Int64,
283            dict_value_type,
284            cast_options,
285        ),
286        Utf8 => {
287            // If the input is a view type, we can avoid casting (thus copying) the data
288            if array.data_type() == &DataType::Utf8View {
289                return string_view_to_dictionary::<K, i32>(array);
290            }
291            pack_byte_to_dictionary::<K, GenericStringType<i32>>(array, cast_options)
292        }
293        LargeUtf8 => {
294            // If the input is a view type, we can avoid casting (thus copying) the data
295            if array.data_type() == &DataType::Utf8View {
296                return string_view_to_dictionary::<K, i64>(array);
297            }
298            pack_byte_to_dictionary::<K, GenericStringType<i64>>(array, cast_options)
299        }
300        Utf8View => {
301            let base_value_type = match array.data_type() {
302                DataType::LargeUtf8 | DataType::Utf8View => DataType::LargeUtf8,
303                _ => DataType::Utf8,
304            };
305
306            let dict_base = cast_to_dictionary::<K>(array, &base_value_type, cast_options)?;
307            dictionary_cast::<K>(
308                dict_base.as_ref(),
309                &DataType::Dictionary(Box::new(K::DATA_TYPE), Box::new(DataType::Utf8View)),
310                cast_options,
311            )
312        }
313        Binary => {
314            // If the input is a view type, we can avoid casting (thus copying) the data
315            if array.data_type() == &DataType::BinaryView {
316                return binary_view_to_dictionary::<K, i32>(array);
317            }
318            pack_byte_to_dictionary::<K, GenericBinaryType<i32>>(array, cast_options)
319        }
320        LargeBinary => {
321            // If the input is a view type, we can avoid casting (thus copying) the data
322            if array.data_type() == &DataType::BinaryView {
323                return binary_view_to_dictionary::<K, i64>(array);
324            }
325            pack_byte_to_dictionary::<K, GenericBinaryType<i64>>(array, cast_options)
326        }
327        BinaryView => {
328            let base_value_type = match array.data_type() {
329                DataType::LargeBinary | DataType::BinaryView => DataType::LargeBinary,
330                _ => DataType::Binary,
331            };
332
333            let dict_base = cast_to_dictionary::<K>(array, &base_value_type, cast_options)?;
334            dictionary_cast::<K>(
335                dict_base.as_ref(),
336                &DataType::Dictionary(Box::new(K::DATA_TYPE), Box::new(DataType::BinaryView)),
337                cast_options,
338            )
339        }
340        FixedSizeBinary(byte_size) => {
341            pack_byte_to_fixed_size_dictionary::<K>(array, cast_options, byte_size)
342        }
343        Struct(_) => pack_struct_to_dictionary::<K>(array, dict_value_type, cast_options),
344        _ => Err(ArrowError::CastError(format!(
345            "Unsupported output type for dictionary packing: {dict_value_type}"
346        ))),
347    }
348}
349
350/// Wrap a struct-valued array as a `DictionaryArray<K, Struct>` with identity
351/// keys `[0, 1, ..., len-1]`. Unlike the primitive / byte packers above, no
352/// deduplication is performed, since struct values have no general hash/equality
353/// builder in arrow-rs.
354///
355/// Each child field of the source is recursively cast to the matching field of
356/// `dict_value_type` via `cast_with_options` before keys are emitted. If any
357/// child cast fails, the whole pack fails, the same contract as the primitive
358/// packers above.
359fn pack_struct_to_dictionary<K: ArrowDictionaryKeyType>(
360    array: &dyn Array,
361    dict_value_type: &DataType,
362    cast_options: &CastOptions,
363) -> Result<ArrayRef, ArrowError> {
364    let cast_values = cast_with_options(array, dict_value_type, cast_options)?;
365    let len = cast_values.len();
366
367    // Identity keys `[0, 1, ..., len-1]`, with null entries wherever the
368    // source row is null so the dictionary's logical null mask matches.
369    let mut builder = PrimitiveBuilder::<K>::with_capacity(len);
370    for i in 0..len {
371        if cast_values.is_null(i) {
372            builder.append_null();
373        } else {
374            let key = K::Native::from_usize(i).ok_or_else(|| {
375                ArrowError::CastError(format!(
376                    "Cannot fit {len} dictionary keys in {:?}",
377                    K::DATA_TYPE,
378                ))
379            })?;
380            builder.append_value(key);
381        }
382    }
383    let keys = builder.finish();
384
385    Ok(Arc::new(DictionaryArray::<K>::try_new(keys, cast_values)?))
386}
387
388// Packs the data from the primitive array of type <V> to a
389// DictionaryArray with keys of type K and values of value_type V
390pub(crate) fn pack_numeric_to_dictionary<K, V>(
391    array: &dyn Array,
392    dict_value_type: &DataType,
393    cast_options: &CastOptions,
394) -> Result<ArrayRef, ArrowError>
395where
396    K: ArrowDictionaryKeyType,
397    V: ArrowPrimitiveType,
398{
399    // attempt to cast the source array values to the target value type (the dictionary values type)
400    let cast_values = cast_with_options(array, dict_value_type, cast_options)?;
401    let values = cast_values.as_primitive::<V>();
402
403    let mut b = PrimitiveDictionaryBuilder::<K, V>::with_capacity(values.len(), values.len());
404
405    // copy each element one at a time
406    for i in 0..values.len() {
407        if values.is_null(i) {
408            b.append_null();
409        } else {
410            b.append(values.value(i))?;
411        }
412    }
413    Ok(Arc::new(b.finish()))
414}
415
416pub(crate) fn pack_decimal_to_dictionary<K, D>(
417    array: &dyn Array,
418    dict_value_type: &DataType,
419    precision: u8,
420    scale: i8,
421    cast_options: &CastOptions,
422) -> Result<ArrayRef, ArrowError>
423where
424    K: ArrowDictionaryKeyType,
425    D: DecimalType + ArrowPrimitiveType,
426{
427    let dict = pack_numeric_to_dictionary::<K, D>(array, dict_value_type, cast_options)?;
428    let dict = dict.as_dictionary::<K>();
429    let typed = dict.downcast_dict::<PrimitiveArray<D>>().ok_or_else(|| {
430        ArrowError::ComputeError(format!(
431            "Internal Error: Cannot cast dict to {}Array",
432            D::PREFIX
433        ))
434    })?;
435    let value = typed
436        .values()
437        .clone()
438        .with_precision_and_scale(precision, scale)?;
439    Ok(Arc::new(dict.with_values(Arc::new(value))))
440}
441
442pub(crate) fn string_view_to_dictionary<K, O: OffsetSizeTrait>(
443    array: &dyn Array,
444) -> Result<ArrayRef, ArrowError>
445where
446    K: ArrowDictionaryKeyType,
447{
448    let mut b = GenericByteDictionaryBuilder::<K, GenericStringType<O>>::with_capacity(
449        array.len(),
450        1024,
451        1024,
452    );
453    let string_view = array
454        .as_any()
455        .downcast_ref::<StringViewArray>()
456        .ok_or_else(|| {
457            ArrowError::ComputeError("Internal Error: Cannot cast to StringViewArray".to_string())
458        })?;
459    for v in string_view.iter() {
460        match v {
461            Some(v) => {
462                b.append(v)?;
463            }
464            None => {
465                b.append_null();
466            }
467        }
468    }
469
470    Ok(Arc::new(b.finish()))
471}
472
473pub(crate) fn binary_view_to_dictionary<K, O: OffsetSizeTrait>(
474    array: &dyn Array,
475) -> Result<ArrayRef, ArrowError>
476where
477    K: ArrowDictionaryKeyType,
478{
479    let mut b = GenericByteDictionaryBuilder::<K, GenericBinaryType<O>>::with_capacity(
480        array.len(),
481        1024,
482        1024,
483    );
484    let binary_view = array
485        .as_any()
486        .downcast_ref::<BinaryViewArray>()
487        .ok_or_else(|| {
488            ArrowError::ComputeError("Internal Error: Cannot cast to BinaryViewArray".to_string())
489        })?;
490    for v in binary_view.iter() {
491        match v {
492            Some(v) => {
493                b.append(v)?;
494            }
495            None => {
496                b.append_null();
497            }
498        }
499    }
500
501    Ok(Arc::new(b.finish()))
502}
503
504// Packs the data as a GenericByteDictionaryBuilder, if possible, with the
505// key types of K
506pub(crate) fn pack_byte_to_dictionary<K, T>(
507    array: &dyn Array,
508    cast_options: &CastOptions,
509) -> Result<ArrayRef, ArrowError>
510where
511    K: ArrowDictionaryKeyType,
512    T: ByteArrayType,
513{
514    let cast_values = cast_with_options(array, &T::DATA_TYPE, cast_options)?;
515    let values = cast_values
516        .as_any()
517        .downcast_ref::<GenericByteArray<T>>()
518        .ok_or_else(|| {
519            ArrowError::ComputeError("Internal Error: Cannot cast to GenericByteArray".to_string())
520        })?;
521    let mut b = GenericByteDictionaryBuilder::<K, T>::with_capacity(values.len(), 1024, 1024);
522
523    // copy each element one at a time
524    for i in 0..values.len() {
525        if values.is_null(i) {
526            b.append_null();
527        } else {
528            b.append(values.value(i))?;
529        }
530    }
531    Ok(Arc::new(b.finish()))
532}
533
534// Packs the data as a GenericByteDictionaryBuilder, if possible, with the
535// key types of K
536pub(crate) fn pack_byte_to_fixed_size_dictionary<K>(
537    array: &dyn Array,
538    cast_options: &CastOptions,
539    byte_width: i32,
540) -> Result<ArrayRef, ArrowError>
541where
542    K: ArrowDictionaryKeyType,
543{
544    let cast_values =
545        cast_with_options(array, &DataType::FixedSizeBinary(byte_width), cast_options)?;
546    let values = cast_values
547        .as_any()
548        .downcast_ref::<FixedSizeBinaryArray>()
549        .ok_or_else(|| {
550            ArrowError::ComputeError("Internal Error: Cannot cast to GenericByteArray".to_string())
551        })?;
552    let mut b = FixedSizeBinaryDictionaryBuilder::<K>::with_capacity(1024, 1024, byte_width);
553
554    // copy each element one at a time
555    for i in 0..values.len() {
556        if values.is_null(i) {
557            b.append_null();
558        } else {
559            b.append(values.value(i))?;
560        }
561    }
562    Ok(Arc::new(b.finish()))
563}