This is an automated email from the ASF dual-hosted git repository.

Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new b266024a8c feat(parquet): support round-trip of Dictionary(_, 
Utf8View/BinaryView) columns (#10831)
b266024a8c is described below

commit b266024a8c277e4330e6b4a5090444a25990a4b7
Author: Adam Reeve <[email protected]>
AuthorDate: Thu Aug 27 14:08:11 2026 +1200

    feat(parquet): support round-trip of Dictionary(_, Utf8View/BinaryView) 
columns (#10831)
    
    # Which issue does this PR close?
    
    - Closes #10830.
    
    # Rationale for this change
    
    Allows reading and writing this data type with Parquet. This is
    particularly useful for working with Polars categorical types, which use
    a `Dictionary(UInt32, Utf8View)` representation.
    
    # What changes are included in this PR?
    
    * Add missing match arms to support downcasting these arrays to a binary
    array when writing Parquet.
    * Add support for `BinaryView` and `Utf8View` value types in
    `ByteArrayDictionaryReader` to allow reading this data back as the same
    type.
    
    # Are these changes tested?
    
    Yes, I've added a new unit test to cover this.
    
    # Are there any user-facing changes?
    
    Yes, this is new user-facing functionality.
---
 .../arrow/array_reader/byte_array_dictionary.rs    | 95 ++++++++++++++++++----
 parquet/src/arrow/arrow_writer/byte_array.rs       |  6 ++
 parquet/src/arrow/arrow_writer/mod.rs              | 26 ++++++
 3 files changed, 111 insertions(+), 16 deletions(-)

diff --git a/parquet/src/arrow/array_reader/byte_array_dictionary.rs 
b/parquet/src/arrow/array_reader/byte_array_dictionary.rs
index 898df57d7e..ae00eac6e3 100644
--- a/parquet/src/arrow/array_reader/byte_array_dictionary.rs
+++ b/parquet/src/arrow/array_reader/byte_array_dictionary.rs
@@ -17,8 +17,12 @@
 
 use std::any::Any;
 use std::marker::PhantomData;
+use std::sync::Arc;
 
-use arrow_array::{Array, ArrayRef, OffsetSizeTrait, new_empty_array};
+use arrow_array::cast::AsArray;
+use arrow_array::{
+    Array, ArrayRef, BinaryViewArray, OffsetSizeTrait, StringViewArray, 
new_empty_array,
+};
 use arrow_buffer::{ArrowNativeType, MutableBuffer};
 use arrow_schema::DataType as ArrowType;
 use bytes::Bytes;
@@ -50,9 +54,9 @@ macro_rules! make_reader {
                     if let Some(threshold) = $padding_threshold {
                         reader.set_padding_threshold(threshold);
                     }
-                    Ok(Box::new(ByteArrayDictionaryReader::<$key_type, 
$value_type>::new(
+                    Ok(Box::new(ByteArrayDictionaryReader::<$key_type, 
$value_type>::try_new(
                         $pages, $data_type, reader,
-                    )))
+                    )?))
                 }
             )+
             _ => Err(general_err!(
@@ -93,21 +97,21 @@ pub fn make_byte_array_dictionary_reader(
         ArrowType::Dictionary(key_type, value_type) => {
             make_reader! {
                 (pages, column_desc, data_type, batch_size, padding_threshold) 
=> match (key_type.as_ref(), value_type.as_ref()) {
-                    (ArrowType::UInt8, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::FixedSizeBinary(_)) => (u8, i32),
+                    (ArrowType::UInt8, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::Utf8View | ArrowType::BinaryView | ArrowType::FixedSizeBinary(_)) => 
(u8, i32),
                     (ArrowType::UInt8, ArrowType::LargeBinary | 
ArrowType::LargeUtf8) => (u8, i64),
-                    (ArrowType::Int8, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::FixedSizeBinary(_)) => (i8, i32),
+                    (ArrowType::Int8, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::Utf8View | ArrowType::BinaryView | ArrowType::FixedSizeBinary(_)) => 
(i8, i32),
                     (ArrowType::Int8, ArrowType::LargeBinary | 
ArrowType::LargeUtf8) => (i8, i64),
-                    (ArrowType::UInt16, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::FixedSizeBinary(_)) => (u16, i32),
+                    (ArrowType::UInt16, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::Utf8View | ArrowType::BinaryView | ArrowType::FixedSizeBinary(_)) => 
(u16, i32),
                     (ArrowType::UInt16, ArrowType::LargeBinary | 
ArrowType::LargeUtf8) => (u16, i64),
-                    (ArrowType::Int16, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::FixedSizeBinary(_)) => (i16, i32),
+                    (ArrowType::Int16, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::Utf8View | ArrowType::BinaryView | ArrowType::FixedSizeBinary(_)) => 
(i16, i32),
                     (ArrowType::Int16, ArrowType::LargeBinary | 
ArrowType::LargeUtf8) => (i16, i64),
-                    (ArrowType::UInt32, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::FixedSizeBinary(_)) => (u32, i32),
+                    (ArrowType::UInt32, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::Utf8View | ArrowType::BinaryView | ArrowType::FixedSizeBinary(_)) => 
(u32, i32),
                     (ArrowType::UInt32, ArrowType::LargeBinary | 
ArrowType::LargeUtf8) => (u32, i64),
-                    (ArrowType::Int32, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::FixedSizeBinary(_)) => (i32, i32),
+                    (ArrowType::Int32, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::Utf8View | ArrowType::BinaryView | ArrowType::FixedSizeBinary(_)) => 
(i32, i32),
                     (ArrowType::Int32, ArrowType::LargeBinary | 
ArrowType::LargeUtf8) => (i32, i64),
-                    (ArrowType::UInt64, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::FixedSizeBinary(_)) => (u64, i32),
+                    (ArrowType::UInt64, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::Utf8View | ArrowType::BinaryView | ArrowType::FixedSizeBinary(_)) => 
(u64, i32),
                     (ArrowType::UInt64, ArrowType::LargeBinary | 
ArrowType::LargeUtf8) => (u64, i64),
-                    (ArrowType::Int64, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::FixedSizeBinary(_)) => (i64, i32),
+                    (ArrowType::Int64, ArrowType::Binary | ArrowType::Utf8 | 
ArrowType::Utf8View | ArrowType::BinaryView | ArrowType::FixedSizeBinary(_)) => 
(i64, i32),
                     (ArrowType::Int64, ArrowType::LargeBinary | 
ArrowType::LargeUtf8) => (i64, i64),
                 }
             }
@@ -119,11 +123,41 @@ pub fn make_byte_array_dictionary_reader(
     }
 }
 
+/// Convert a dictionary-typed array with string or binary typed values
+/// to one with string or binary view typed values.
+fn convert_values_to_view(array: ArrayRef, to_type: &ArrowType) -> 
Result<ArrayRef> {
+    let ArrowType::Dictionary(_, to_value_type) = to_type else {
+        return Err(general_err!(
+            "Cannot convert {} dictionary values to non-dictionary type {}",
+            array.data_type(),
+            to_type
+        ));
+    };
+
+    let array = array.as_any_dictionary();
+    let values = array.values();
+
+    let new_values: ArrayRef = match to_value_type.as_ref() {
+        ArrowType::Utf8View => 
Arc::new(StringViewArray::from(values.as_string::<i32>())),
+        ArrowType::BinaryView => 
Arc::new(BinaryViewArray::from(values.as_binary::<i32>())),
+        other => {
+            return Err(general_err!(
+                "Cannot convert dictionary values to {}",
+                other
+            ));
+        }
+    };
+
+    Ok(array.with_values(new_values))
+}
+
 /// An [`ArrayReader`] for dictionary encoded variable length byte arrays
 ///
 /// Will attempt to preserve any dictionary encoding present in the parquet 
data
 struct ByteArrayDictionaryReader<K: ArrowNativeType, V: OffsetSizeTrait> {
     data_type: ArrowType,
+    /// Type used by the dictionary buffer when it is different from the 
output data type.
+    buffer_type: Option<ArrowType>,
     pages: Box<dyn PageIterator>,
     def_levels_buffer: Option<Vec<i16>>,
     rep_levels_buffer: Option<Vec<i16>>,
@@ -137,19 +171,39 @@ where
     K: FromBitpacked + Ord + ArrowNativeType,
     V: OffsetSizeTrait,
 {
-    fn new(
+    fn try_new(
         pages: Box<dyn PageIterator>,
         data_type: ArrowType,
         record_reader: GenericRecordReader<DictionaryBuffer<K, V>, 
DictionaryDecoder<K, V>>,
-    ) -> Self {
-        Self {
+    ) -> Result<Self> {
+        let ArrowType::Dictionary(key_type, value_type) = &data_type else {
+            return Err(general_err!(
+                "Expected dictionary type, found {:?}",
+                data_type
+            ));
+        };
+
+        let buffer_type = match value_type.as_ref() {
+            ArrowType::Utf8View => Some(ArrowType::Dictionary(
+                key_type.clone(),
+                Box::new(ArrowType::Utf8),
+            )),
+            ArrowType::BinaryView => Some(ArrowType::Dictionary(
+                key_type.clone(),
+                Box::new(ArrowType::Binary),
+            )),
+            _ => None,
+        };
+
+        Ok(Self {
             data_type,
+            buffer_type,
             pages,
             def_levels_buffer: None,
             rep_levels_buffer: None,
             record_reader,
             hash_scratch: MutableBuffer::new(0),
-        }
+        })
     }
 }
 
@@ -184,7 +238,16 @@ where
 
         let buffer = self.record_reader.consume_record_data();
         let null_buffer = self.record_reader.consume_compact_bitmap();
-        let array = buffer.into_array(null_buffer, &self.data_type, &mut 
self.hash_scratch)?;
+
+        let array = match &self.buffer_type {
+            None => buffer.into_array(null_buffer, &self.data_type, &mut 
self.hash_scratch)?,
+            Some(buffer_type) => {
+                let buffer_array =
+                    buffer.into_array(null_buffer, buffer_type, &mut 
self.hash_scratch)?;
+                convert_values_to_view(buffer_array, &self.data_type)?
+            }
+        };
+
         self.record_reader.reset();
 
         Ok(array)
diff --git a/parquet/src/arrow/arrow_writer/byte_array.rs 
b/parquet/src/arrow/arrow_writer/byte_array.rs
index 6346199b16..85f37d6da6 100644
--- a/parquet/src/arrow/arrow_writer/byte_array.rs
+++ b/parquet/src/arrow/arrow_writer/byte_array.rs
@@ -88,10 +88,16 @@ macro_rules! downcast_op {
                 DataType::LargeUtf8 => {
                     downcast_dict_op!(key, LargeStringArray, $array, $op$(, 
$arg)*)
                 }
+                DataType::Utf8View => {
+                    downcast_dict_op!(key, StringViewArray, $array, $op$(, 
$arg)*)
+                }
                 DataType::Binary => downcast_dict_op!(key, BinaryArray, 
$array, $op$(, $arg)*),
                 DataType::LargeBinary => {
                     downcast_dict_op!(key, LargeBinaryArray, $array, $op$(, 
$arg)*)
                 }
+                DataType::BinaryView => {
+                    downcast_dict_op!(key, BinaryViewArray, $array, $op$(, 
$arg)*)
+                }
                 DataType::FixedSizeBinary(_) => {
                     downcast_dict_op!(key, FixedSizeBinaryArray, $array, 
$op$(, $arg)*)
                 }
diff --git a/parquet/src/arrow/arrow_writer/mod.rs 
b/parquet/src/arrow/arrow_writer/mod.rs
index f8acb65044..a359314127 100644
--- a/parquet/src/arrow/arrow_writer/mod.rs
+++ b/parquet/src/arrow/arrow_writer/mod.rs
@@ -2675,6 +2675,32 @@ mod tests {
             .run();
     }
 
+    /// Test round-trip of Dictionary<UInt32, Utf8View> and
+    /// Dictionary<UInt32, BinaryView> typed columns.
+    #[test]
+    fn arrow_writer_string_view_dictionary() {
+        let raw_string_values = vec!["a", "b", "large payload over 12 bytes"];
+        let raw_binary_values = vec![
+            b"a".to_vec(),
+            b"b".to_vec(),
+            b"large payload over 12 bytes".to_vec(),
+        ];
+
+        let keys = UInt32Array::from(vec![Some(0), None, Some(2), Some(1), 
None]);
+
+        let string_view_values = 
Arc::new(StringViewArray::from(raw_string_values));
+        let string_dict: ArrayRef = Arc::new(
+            DictionaryArray::<UInt32Type>::try_new(keys.clone(), 
string_view_values).unwrap(),
+        );
+
+        let binary_view_values = 
Arc::new(BinaryViewArray::from_iter_values(raw_binary_values));
+        let binary_dict: ArrayRef =
+            Arc::new(DictionaryArray::<UInt32Type>::try_new(keys, 
binary_view_values).unwrap());
+
+        RoundTripTest::new(string_dict).run();
+        RoundTripTest::new(binary_dict).run();
+    }
+
     fn get_decimal_batch(precision: u8, scale: i8) -> RecordBatch {
         let decimal_field = Field::new("a", DataType::Decimal128(precision, 
scale), false);
         let schema = Schema::new(vec![decimal_field]);

Reply via email to