Jefffrey commented on code in PR #10436:
URL: https://github.com/apache/arrow-rs/pull/10436#discussion_r3789570998


##########
arrow-cast/src/cast/dictionary.rs:
##########
@@ -34,23 +34,126 @@ pub(crate) fn dictionary_cast<K: ArrowDictionaryKeyType>(
         (_, Dictionary(to_index_type, to_value_type)) => {
             dictionary_to_dictionary_cast(array, to_index_type, to_value_type, 
cast_options)
         }
-        // `unpack_dictionary` can handle Utf8View/BinaryView types, but 
incurs unnecessary data
-        // copy of the value buffer. Fast path which avoids copying underlying 
values buffer.
-        // TODO: handle LargeUtf8/LargeBinary -> View (need to check offsets 
can fit)
-        // TODO: handle cross types (String -> BinaryView, Binary -> 
StringView)
-        //       (need to validate utf8?)
-        (Utf8, Utf8View) => view_from_dict_values::<K, Utf8Type, 
StringViewType>(
-            array.keys(),
-            array.values().as_string::<i32>(),
-        ),
-        (Binary, BinaryView) => view_from_dict_values::<K, BinaryType, 
BinaryViewType>(
+        // There are two ways to produce a view array from a dictionary, and 
neither of them
+        // copies the values buffer:
+        //
+        //  (a) `unpack_dictionary` builds one view per *dictionary value* and 
then uses `take`,
+        //      which gathers 16-byte views and passes the data buffers 
through untouched.
+        //  (b) `view_from_dict_values` builds one view per *row* directly 
against the values
+        //      buffer, skipping the intermediate array entirely.
+        //
+        // (a) is memory-bandwidth-bound -- the gather in `take` benchmarks at 
or below the cost
+        // of a hand-written `u128` gather -- so it wins once rows reach 
roughly 0.6x the
+        // dictionary size. (b) reads the payload bytes of every row out of 
the values buffer to
+        // build each view, which is far more expensive per row, and only pays 
off when the
+        // dictionary is substantially larger than the array is long: there 
(a) spends most of
+        // its time building views that no row ever references.
+        //
+        // So take (b) only when it actually wins. Everything else falls 
through to (a) below.

Review Comment:
   ```suggestion
           // `unpack_dictionary` operates per dictionary value before using 
take kernel to form
           // final view. `view_from_dict_values` builds output view directly 
per row (key index).
           // Based on benchmarking, `view_from_dict_values` is more efficient 
for sparse dictionaries
           // (more dictionary values than there are rows/keys), whilst 
`unpack_dictionary` is
           // more efficient for dense dictionaries, where a sparse dictionary 
is when rows reach
           // roughly 0.6x the dictionary size.
           //
           // Therefore delegate to the faster method based on the density of 
the input dictionary.
   ```
   
   My attempt at cutting down the verbosity a bit, feel free to edit or make 
suggestions if it doesn't seem as clear or is inaccurate at bits



##########
arrow-cast/src/cast/dictionary.rs:
##########
@@ -126,6 +229,73 @@ fn dictionary_to_dictionary_cast<K: 
ArrowDictionaryKeyType>(
     Ok(new_array)
 }
 
+/// Cast `Dict<K, Binary>` or `Dict<K, LargeBinary>` to `Utf8View`, validating 
UTF-8 for each
+/// dictionary value.
+///
+/// Fast path when all values are valid UTF-8: reuses the values buffer 
without copying.
+/// When some values are invalid and `cast_options.safe` is true, rows 
pointing to those
+/// values become null. When `cast_options.safe` is false, returns an error 
immediately.
+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(_) => {
+            // All dictionary values are valid UTF-8: reuse the buffer 
zero-copy.
+            view_from_dict_values::<K, GenericBinaryType<O>, 
StringViewType>(keys, values)
+        }
+        Err(e) => {
+            if !cast_options.safe {
+                return Err(e);

Review Comment:
   do we need to consider if potentially invalid utf8 is actually masked out by 
the null buffer of the byte array? this could be overly restrictive for such 
niche inputs



##########
arrow-cast/src/cast/dictionary.rs:
##########
@@ -34,23 +34,126 @@ pub(crate) fn dictionary_cast<K: ArrowDictionaryKeyType>(
         (_, Dictionary(to_index_type, to_value_type)) => {
             dictionary_to_dictionary_cast(array, to_index_type, to_value_type, 
cast_options)
         }
-        // `unpack_dictionary` can handle Utf8View/BinaryView types, but 
incurs unnecessary data
-        // copy of the value buffer. Fast path which avoids copying underlying 
values buffer.
-        // TODO: handle LargeUtf8/LargeBinary -> View (need to check offsets 
can fit)
-        // TODO: handle cross types (String -> BinaryView, Binary -> 
StringView)
-        //       (need to validate utf8?)
-        (Utf8, Utf8View) => view_from_dict_values::<K, Utf8Type, 
StringViewType>(
-            array.keys(),
-            array.values().as_string::<i32>(),
-        ),
-        (Binary, BinaryView) => view_from_dict_values::<K, BinaryType, 
BinaryViewType>(
+        // There are two ways to produce a view array from a dictionary, and 
neither of them
+        // copies the values buffer:
+        //
+        //  (a) `unpack_dictionary` builds one view per *dictionary value* and 
then uses `take`,
+        //      which gathers 16-byte views and passes the data buffers 
through untouched.
+        //  (b) `view_from_dict_values` builds one view per *row* directly 
against the values
+        //      buffer, skipping the intermediate array entirely.
+        //
+        // (a) is memory-bandwidth-bound -- the gather in `take` benchmarks at 
or below the cost
+        // of a hand-written `u128` gather -- so it wins once rows reach 
roughly 0.6x the
+        // dictionary size. (b) reads the payload bytes of every row out of 
the values buffer to
+        // build each view, which is far more expensive per row, and only pays 
off when the
+        // dictionary is substantially larger than the array is long: there 
(a) spends most of
+        // its time building views that no row ever references.
+        //
+        // So take (b) only when it actually wins. Everything else falls 
through to (a) below.
+        (Utf8, Utf8View) if prefer_direct_views(array) => {
+            view_from_dict_values::<K, Utf8Type, StringViewType>(
+                array.keys(),
+                array.values().as_string::<i32>(),
+            )
+        }
+        (Binary, BinaryView) if prefer_direct_views(array) => {
+            view_from_dict_values::<K, BinaryType, BinaryViewType>(
+                array.keys(),
+                array.values().as_binary::<i32>(),
+            )
+        }
+        // LargeUtf8/LargeBinary additionally require a values buffer small 
enough to be addressed
+        // by the u32 offset of a view.
+        (LargeUtf8, Utf8View)
+            if prefer_direct_views(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 prefer_direct_views(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>(),
+            )
+        }
+        // Cross casts to a binary view need no validation: valid UTF-8 is 
valid binary.
+        (Utf8, BinaryView) if prefer_direct_views(array) => {
+            view_from_dict_values::<K, Utf8Type, BinaryViewType>(
+                array.keys(),
+                array.values().as_string::<i32>(),
+            )
+        }
+        (LargeUtf8, BinaryView)
+            if prefer_direct_views(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>(),
+            )
+        }
+        // Cross casts to a string view require UTF-8 validation of the 
dictionary values.
+        (Binary, Utf8View) if prefer_direct_views(array) => 
binary_dict_to_string_view::<K, i32>(
             array.keys(),
             array.values().as_binary::<i32>(),
+            cast_options,
         ),
+        (LargeBinary, Utf8View)
+            if prefer_direct_views(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),
     }
 }
 
+/// Whether building views per row beats `unpack_dictionary` for this array.
+///
+/// `unpack_dictionary` costs `O(values)` to build the intermediate view array 
plus `O(keys)` for
+/// a bandwidth-bound gather; building views directly costs `O(keys)` but with 
a much larger
+/// constant, since every row has to read its payload out of the values 
buffer. The direct path
+/// therefore only wins when the dictionary is substantially larger than the 
array is long.
+///
+/// The measured crossover sits near `keys ~= 0.6 * values`, so this 
deliberately switches short
+/// of it rather than at the crossover itself: at `keys * 2 == values` the 
direct path is still
+/// ahead by 15-30%, which leaves margin for the exact crossover moving with 
cache size or
+/// microarchitecture. Being wrong in this direction merely forgoes a win; 
being wrong the other
+/// way is a large regression on the common case.
+#[inline]
+fn prefer_direct_views<K: ArrowDictionaryKeyType>(array: &DictionaryArray<K>) 
-> bool {

Review Comment:
   ```suggestion
   #[inline]
   fn is_sparse<K: ArrowDictionaryKeyType>(array: &DictionaryArray<K>) -> bool {
   ```
   
   I feel the doc comment is too verbose and repeats what is said above; maybe 
we can move this method as an inner method to `dictionary_cast` to try colocate 
the comments to avoid redundancy
   
   also a name like `is_sparse` to me reads better than `prefer_direct_views` 
because we dont exactly know what `direct_views` means here



##########
arrow-cast/src/cast/mod.rs:
##########
@@ -7639,6 +7639,316 @@ mod tests {
         assert_eq!(casted_binary_array.as_ref(), &binary_view_array);
     }
 
+    /// Casting a dictionary to a view type has two implementations: building 
one view per row
+    /// directly against the values buffer, and `unpack_dictionary`. Which one 
runs depends on
+    /// how the row count compares to the dictionary size, so these helpers 
pin both branches of
+    /// that choice for each arm.
+    ///
+    /// `values` must have 6 entries; the returned key sets sit either side of 
the threshold.
+    fn keys_taking_direct_path() -> Int32Array {
+        // 2 keys < 6/2 values -> views are built directly per row
+        Int32Array::from_iter([Some(0), Some(3)])
+    }

Review Comment:
   it might be better to move this test code into cast/dictionary.rs file, so 
it lives closer together



##########
arrow-cast/src/cast/dictionary.rs:
##########
@@ -34,23 +34,126 @@ pub(crate) fn dictionary_cast<K: ArrowDictionaryKeyType>(
         (_, Dictionary(to_index_type, to_value_type)) => {
             dictionary_to_dictionary_cast(array, to_index_type, to_value_type, 
cast_options)
         }
-        // `unpack_dictionary` can handle Utf8View/BinaryView types, but 
incurs unnecessary data
-        // copy of the value buffer. Fast path which avoids copying underlying 
values buffer.
-        // TODO: handle LargeUtf8/LargeBinary -> View (need to check offsets 
can fit)
-        // TODO: handle cross types (String -> BinaryView, Binary -> 
StringView)
-        //       (need to validate utf8?)
-        (Utf8, Utf8View) => view_from_dict_values::<K, Utf8Type, 
StringViewType>(
-            array.keys(),
-            array.values().as_string::<i32>(),
-        ),
-        (Binary, BinaryView) => view_from_dict_values::<K, BinaryType, 
BinaryViewType>(
+        // There are two ways to produce a view array from a dictionary, and 
neither of them
+        // copies the values buffer:
+        //
+        //  (a) `unpack_dictionary` builds one view per *dictionary value* and 
then uses `take`,
+        //      which gathers 16-byte views and passes the data buffers 
through untouched.
+        //  (b) `view_from_dict_values` builds one view per *row* directly 
against the values
+        //      buffer, skipping the intermediate array entirely.
+        //
+        // (a) is memory-bandwidth-bound -- the gather in `take` benchmarks at 
or below the cost
+        // of a hand-written `u128` gather -- so it wins once rows reach 
roughly 0.6x the
+        // dictionary size. (b) reads the payload bytes of every row out of 
the values buffer to
+        // build each view, which is far more expensive per row, and only pays 
off when the
+        // dictionary is substantially larger than the array is long: there 
(a) spends most of
+        // its time building views that no row ever references.
+        //
+        // So take (b) only when it actually wins. Everything else falls 
through to (a) below.
+        (Utf8, Utf8View) if prefer_direct_views(array) => {
+            view_from_dict_values::<K, Utf8Type, StringViewType>(
+                array.keys(),
+                array.values().as_string::<i32>(),
+            )
+        }
+        (Binary, BinaryView) if prefer_direct_views(array) => {
+            view_from_dict_values::<K, BinaryType, BinaryViewType>(
+                array.keys(),
+                array.values().as_binary::<i32>(),
+            )
+        }
+        // LargeUtf8/LargeBinary additionally require a values buffer small 
enough to be addressed
+        // by the u32 offset of a view.
+        (LargeUtf8, Utf8View)
+            if prefer_direct_views(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 prefer_direct_views(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>(),
+            )
+        }
+        // Cross casts to a binary view need no validation: valid UTF-8 is 
valid binary.
+        (Utf8, BinaryView) if prefer_direct_views(array) => {
+            view_from_dict_values::<K, Utf8Type, BinaryViewType>(
+                array.keys(),
+                array.values().as_string::<i32>(),
+            )
+        }
+        (LargeUtf8, BinaryView)
+            if prefer_direct_views(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>(),
+            )
+        }
+        // Cross casts to a string view require UTF-8 validation of the 
dictionary values.
+        (Binary, Utf8View) if prefer_direct_views(array) => 
binary_dict_to_string_view::<K, i32>(
             array.keys(),
             array.values().as_binary::<i32>(),
+            cast_options,
         ),
+        (LargeBinary, Utf8View)
+            if prefer_direct_views(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),
     }
 }
 
+/// Whether building views per row beats `unpack_dictionary` for this array.
+///
+/// `unpack_dictionary` costs `O(values)` to build the intermediate view array 
plus `O(keys)` for
+/// a bandwidth-bound gather; building views directly costs `O(keys)` but with 
a much larger
+/// constant, since every row has to read its payload out of the values 
buffer. The direct path
+/// therefore only wins when the dictionary is substantially larger than the 
array is long.
+///
+/// The measured crossover sits near `keys ~= 0.6 * values`, so this 
deliberately switches short
+/// of it rather than at the crossover itself: at `keys * 2 == values` the 
direct path is still
+/// ahead by 15-30%, which leaves margin for the exact crossover moving with 
cache size or
+/// microarchitecture. Being wrong in this direction merely forgoes a win; 
being wrong the other
+/// way is a large regression on the common case.
+#[inline]
+fn prefer_direct_views<K: ArrowDictionaryKeyType>(array: &DictionaryArray<K>) 
-> bool {
+    array.keys().len() < array.values().len() / 2
+}
+
+/// Whether the values buffer can back a view array, i.e. it is smaller than 
4GiB.
+///
+/// Required because views address their data with a `u32` offset, and
+/// [`GenericByteViewBuilder::append_block`] asserts the block it is handed is 
smaller than
+/// `u32::MAX`. Failing this check is therefore a panic in the direct path, 
not an error.
+///
+/// `unpack_dictionary` has no such limit: it reaches
+/// `impl From<&GenericByteArray> for GenericByteViewArray`, which makes the 
same test and falls
+/// back to copying via `from_iter` when the buffer is too large. So this 
guards the direct path
+/// only, and the fallback is strictly more capable rather than an equivalent 
failure.
+///
+/// This deliberately measures the whole buffer rather than the largest live 
offset: slicing a
+/// byte array slices its offsets but keeps `value_data` intact, so a slice 
can have small offsets
+/// and still carry an oversized buffer -- and it is the buffer that 
`append_block` rejects.
+#[inline]
+fn values_buffer_fits_in_view<T: ByteArrayType>(values: &GenericByteArray<T>) 
-> bool {
+    values.values().len() < u32::MAX as usize

Review Comment:
   ```suggestion
       values.values().len() < i32::MAX as usize
   ```
   
   - https://github.com/apache/arrow-rs/issues/6172



##########
arrow-cast/src/cast/dictionary.rs:
##########
@@ -34,23 +34,126 @@ pub(crate) fn dictionary_cast<K: ArrowDictionaryKeyType>(
         (_, Dictionary(to_index_type, to_value_type)) => {
             dictionary_to_dictionary_cast(array, to_index_type, to_value_type, 
cast_options)
         }
-        // `unpack_dictionary` can handle Utf8View/BinaryView types, but 
incurs unnecessary data
-        // copy of the value buffer. Fast path which avoids copying underlying 
values buffer.
-        // TODO: handle LargeUtf8/LargeBinary -> View (need to check offsets 
can fit)
-        // TODO: handle cross types (String -> BinaryView, Binary -> 
StringView)
-        //       (need to validate utf8?)
-        (Utf8, Utf8View) => view_from_dict_values::<K, Utf8Type, 
StringViewType>(
-            array.keys(),
-            array.values().as_string::<i32>(),
-        ),
-        (Binary, BinaryView) => view_from_dict_values::<K, BinaryType, 
BinaryViewType>(
+        // There are two ways to produce a view array from a dictionary, and 
neither of them
+        // copies the values buffer:
+        //
+        //  (a) `unpack_dictionary` builds one view per *dictionary value* and 
then uses `take`,
+        //      which gathers 16-byte views and passes the data buffers 
through untouched.
+        //  (b) `view_from_dict_values` builds one view per *row* directly 
against the values
+        //      buffer, skipping the intermediate array entirely.
+        //
+        // (a) is memory-bandwidth-bound -- the gather in `take` benchmarks at 
or below the cost
+        // of a hand-written `u128` gather -- so it wins once rows reach 
roughly 0.6x the
+        // dictionary size. (b) reads the payload bytes of every row out of 
the values buffer to
+        // build each view, which is far more expensive per row, and only pays 
off when the
+        // dictionary is substantially larger than the array is long: there 
(a) spends most of
+        // its time building views that no row ever references.
+        //
+        // So take (b) only when it actually wins. Everything else falls 
through to (a) below.
+        (Utf8, Utf8View) if prefer_direct_views(array) => {
+            view_from_dict_values::<K, Utf8Type, StringViewType>(
+                array.keys(),
+                array.values().as_string::<i32>(),
+            )
+        }
+        (Binary, BinaryView) if prefer_direct_views(array) => {
+            view_from_dict_values::<K, BinaryType, BinaryViewType>(
+                array.keys(),
+                array.values().as_binary::<i32>(),
+            )
+        }
+        // LargeUtf8/LargeBinary additionally require a values buffer small 
enough to be addressed
+        // by the u32 offset of a view.

Review Comment:
   ```suggestion
           // `view_from_dict_values` directly appends the values buffer as a 
block using
           // `GenericByteViewBuilder::append_block` which asserts length of 
the buffer; we must
           // ensure this assertion holds to use it for large variants which 
may exceed the max allowable length.
           // If we exceed the length we can simply fallback to 
`unpack_dictionary` which still builds it
           // correctly.
   ```



##########
arrow-cast/src/cast/dictionary.rs:
##########
@@ -34,23 +34,126 @@ pub(crate) fn dictionary_cast<K: ArrowDictionaryKeyType>(
         (_, Dictionary(to_index_type, to_value_type)) => {
             dictionary_to_dictionary_cast(array, to_index_type, to_value_type, 
cast_options)
         }
-        // `unpack_dictionary` can handle Utf8View/BinaryView types, but 
incurs unnecessary data
-        // copy of the value buffer. Fast path which avoids copying underlying 
values buffer.
-        // TODO: handle LargeUtf8/LargeBinary -> View (need to check offsets 
can fit)
-        // TODO: handle cross types (String -> BinaryView, Binary -> 
StringView)
-        //       (need to validate utf8?)
-        (Utf8, Utf8View) => view_from_dict_values::<K, Utf8Type, 
StringViewType>(
-            array.keys(),
-            array.values().as_string::<i32>(),
-        ),
-        (Binary, BinaryView) => view_from_dict_values::<K, BinaryType, 
BinaryViewType>(
+        // There are two ways to produce a view array from a dictionary, and 
neither of them
+        // copies the values buffer:
+        //
+        //  (a) `unpack_dictionary` builds one view per *dictionary value* and 
then uses `take`,
+        //      which gathers 16-byte views and passes the data buffers 
through untouched.
+        //  (b) `view_from_dict_values` builds one view per *row* directly 
against the values
+        //      buffer, skipping the intermediate array entirely.
+        //
+        // (a) is memory-bandwidth-bound -- the gather in `take` benchmarks at 
or below the cost
+        // of a hand-written `u128` gather -- so it wins once rows reach 
roughly 0.6x the
+        // dictionary size. (b) reads the payload bytes of every row out of 
the values buffer to
+        // build each view, which is far more expensive per row, and only pays 
off when the
+        // dictionary is substantially larger than the array is long: there 
(a) spends most of
+        // its time building views that no row ever references.
+        //
+        // So take (b) only when it actually wins. Everything else falls 
through to (a) below.
+        (Utf8, Utf8View) if prefer_direct_views(array) => {
+            view_from_dict_values::<K, Utf8Type, StringViewType>(
+                array.keys(),
+                array.values().as_string::<i32>(),
+            )
+        }
+        (Binary, BinaryView) if prefer_direct_views(array) => {
+            view_from_dict_values::<K, BinaryType, BinaryViewType>(
+                array.keys(),
+                array.values().as_binary::<i32>(),
+            )
+        }
+        // LargeUtf8/LargeBinary additionally require a values buffer small 
enough to be addressed
+        // by the u32 offset of a view.
+        (LargeUtf8, Utf8View)
+            if prefer_direct_views(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 prefer_direct_views(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>(),
+            )
+        }
+        // Cross casts to a binary view need no validation: valid UTF-8 is 
valid binary.
+        (Utf8, BinaryView) if prefer_direct_views(array) => {
+            view_from_dict_values::<K, Utf8Type, BinaryViewType>(
+                array.keys(),
+                array.values().as_string::<i32>(),
+            )
+        }
+        (LargeUtf8, BinaryView)
+            if prefer_direct_views(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>(),
+            )
+        }
+        // Cross casts to a string view require UTF-8 validation of the 
dictionary values.
+        (Binary, Utf8View) if prefer_direct_views(array) => 
binary_dict_to_string_view::<K, i32>(
             array.keys(),
             array.values().as_binary::<i32>(),
+            cast_options,
         ),
+        (LargeBinary, Utf8View)
+            if prefer_direct_views(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),
     }
 }
 
+/// Whether building views per row beats `unpack_dictionary` for this array.
+///
+/// `unpack_dictionary` costs `O(values)` to build the intermediate view array 
plus `O(keys)` for
+/// a bandwidth-bound gather; building views directly costs `O(keys)` but with 
a much larger
+/// constant, since every row has to read its payload out of the values 
buffer. The direct path
+/// therefore only wins when the dictionary is substantially larger than the 
array is long.
+///
+/// The measured crossover sits near `keys ~= 0.6 * values`, so this 
deliberately switches short
+/// of it rather than at the crossover itself: at `keys * 2 == values` the 
direct path is still
+/// ahead by 15-30%, which leaves margin for the exact crossover moving with 
cache size or
+/// microarchitecture. Being wrong in this direction merely forgoes a win; 
being wrong the other
+/// way is a large regression on the common case.
+#[inline]
+fn prefer_direct_views<K: ArrowDictionaryKeyType>(array: &DictionaryArray<K>) 
-> bool {
+    array.keys().len() < array.values().len() / 2
+}
+
+/// Whether the values buffer can back a view array, i.e. it is smaller than 
4GiB.
+///
+/// Required because views address their data with a `u32` offset, and
+/// [`GenericByteViewBuilder::append_block`] asserts the block it is handed is 
smaller than
+/// `u32::MAX`. Failing this check is therefore a panic in the direct path, 
not an error.
+///
+/// `unpack_dictionary` has no such limit: it reaches
+/// `impl From<&GenericByteArray> for GenericByteViewArray`, which makes the 
same test and falls
+/// back to copying via `from_iter` when the buffer is too large. So this 
guards the direct path
+/// only, and the fallback is strictly more capable rather than an equivalent 
failure.
+///
+/// This deliberately measures the whole buffer rather than the largest live 
offset: slicing a
+/// byte array slices its offsets but keeps `value_data` intact, so a slice 
can have small offsets
+/// and still carry an oversized buffer -- and it is the buffer that 
`append_block` rejects.
+#[inline]

Review Comment:
   ```suggestion
   ```
   
   same here, dont need to repeat here, easier to just keep it within the 
actual match arms above per my previous comment



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to