@@ -495,99 +495,124 @@ fn take_bytes<T: ByteArrayType, IndexType: ArrowPrimitiveType>(
495495 array : & GenericByteArray < T > ,
496496 indices : & PrimitiveArray < IndexType > ,
497497) -> Result < GenericByteArray < T > , ArrowError > {
498+ let mut values: Vec < u8 > = Vec :: new ( ) ;
498499 let mut offsets = Vec :: with_capacity ( indices. len ( ) + 1 ) ;
499500 offsets. push ( T :: Offset :: default ( ) ) ;
500501
501502 let input_offsets = array. value_offsets ( ) ;
502503 let mut capacity = 0 ;
503504 let nulls = take_nulls ( array. nulls ( ) , indices) ;
504505
505- let ( offsets, values) = if array. null_count ( ) == 0 && indices. null_count ( ) == 0 {
506- offsets. reserve ( indices. len ( ) ) ;
507- for index in indices. values ( ) {
508- let index = index. as_usize ( ) ;
509- capacity += input_offsets[ index + 1 ] . as_usize ( ) - input_offsets[ index] . as_usize ( ) ;
510- offsets. push (
511- T :: Offset :: from_usize ( capacity)
512- . ok_or_else ( || ArrowError :: OffsetOverflowError ( capacity) ) ?,
513- ) ;
514- }
515- let mut values = Vec :: with_capacity ( capacity) ;
516-
517- for index in indices. values ( ) {
518- values. extend_from_slice ( array. value ( index. as_usize ( ) ) . as_ref ( ) ) ;
519- }
520- ( offsets, values)
521- } else if indices. null_count ( ) == 0 {
522- offsets. reserve ( indices. len ( ) ) ;
523- for index in indices. values ( ) {
524- let index = index. as_usize ( ) ;
525- if array. is_valid ( index) {
526- capacity += input_offsets[ index + 1 ] . as_usize ( ) - input_offsets[ index] . as_usize ( ) ;
506+ // Branch on output nulls — `None` means every output slot is valid.
507+ match nulls. as_ref ( ) . filter ( |n| n. null_count ( ) > 0 ) {
508+ // Fast path: no nulls in output, every index is valid.
509+ None => {
510+ for index in indices. values ( ) {
511+ let index = index. as_usize ( ) ;
512+ let start = input_offsets[ index] . as_usize ( ) ;
513+ let end = input_offsets[ index + 1 ] . as_usize ( ) ;
514+ capacity += end - start;
515+ offsets. push (
516+ T :: Offset :: from_usize ( capacity)
517+ . ok_or_else ( || ArrowError :: OffsetOverflowError ( capacity) ) ?,
518+ ) ;
527519 }
528- offsets. push (
529- T :: Offset :: from_usize ( capacity)
530- . ok_or_else ( || ArrowError :: OffsetOverflowError ( capacity) ) ?,
531- ) ;
532- }
533- let mut values = Vec :: with_capacity ( capacity) ;
534520
535- for index in indices. values ( ) {
536- let index = index. as_usize ( ) ;
537- if array. is_valid ( index) {
538- values. extend_from_slice ( array. value ( index) . as_ref ( ) ) ;
539- }
540- }
541- ( offsets, values)
542- } else if array. null_count ( ) == 0 {
543- offsets. reserve ( indices. len ( ) ) ;
544- for ( i, index) in indices. values ( ) . iter ( ) . enumerate ( ) {
545- let index = index. as_usize ( ) ;
546- if indices. is_valid ( i) {
547- capacity += input_offsets[ index + 1 ] . as_usize ( ) - input_offsets[ index] . as_usize ( ) ;
521+ values. reserve ( capacity) ;
522+
523+ let dst = values. spare_capacity_mut ( ) ;
524+ debug_assert ! ( dst. len( ) >= capacity) ;
525+ let mut offset = 0 ;
526+
527+ for index in indices. values ( ) {
528+ // SAFETY: in-bounds proven by the first loop's bounds-checked offset access.
529+ // dst asserted above to include the required capacity.
530+ unsafe {
531+ let data: & [ u8 ] = array. value_unchecked ( index. as_usize ( ) ) . as_ref ( ) ;
532+ std:: ptr:: copy_nonoverlapping (
533+ data. as_ptr ( ) ,
534+ dst. get_unchecked_mut ( offset..) . as_mut_ptr ( ) . cast :: < u8 > ( ) ,
535+ data. len ( ) ,
536+ ) ;
537+ offset += data. len ( ) ;
538+ }
548539 }
549- offsets. push (
550- T :: Offset :: from_usize ( capacity)
551- . ok_or_else ( || ArrowError :: OffsetOverflowError ( capacity) ) ?,
552- ) ;
553- }
554- let mut values = Vec :: with_capacity ( capacity) ;
555540
556- for ( i , index ) in indices . values ( ) . iter ( ) . enumerate ( ) {
557- if indices . is_valid ( i ) {
558- values. extend_from_slice ( array . value ( index . as_usize ( ) ) . as_ref ( ) ) ;
541+ // SAFETY: wrote exactly `capacity` bytes above; reserved on line above.
542+ unsafe {
543+ values. set_len ( capacity ) ;
559544 }
560545 }
561- ( offsets, values)
562- } else {
563- let nulls = nulls. as_ref ( ) . unwrap ( ) ;
564- offsets. reserve ( indices. len ( ) ) ;
565- for ( i, index) in indices. values ( ) . iter ( ) . enumerate ( ) {
566- let index = index. as_usize ( ) ;
567- if nulls. is_valid ( i) {
568- capacity += input_offsets[ index + 1 ] . as_usize ( ) - input_offsets[ index] . as_usize ( ) ;
546+ // Nullable path: only process valid (non-null) output positions.
547+ Some ( output_nulls) => {
548+ let mut source_ranges = Vec :: with_capacity ( indices. len ( ) - output_nulls. null_count ( ) ) ;
549+ let mut last_filled = 0 ;
550+
551+ // Pre-fill offsets; we overwrite valid positions below.
552+ offsets. resize ( indices. len ( ) + 1 , T :: Offset :: default ( ) ) ;
553+
554+ // Pass 1: find all valid ranges that need to be copied.
555+ for i in output_nulls. valid_indices ( ) {
556+ let current_offset = T :: Offset :: from_usize ( capacity)
557+ . ok_or_else ( || ArrowError :: OffsetOverflowError ( capacity) ) ?;
558+ // Fill offsets for skipped null slots so they get zero-length ranges.
559+ if last_filled < i {
560+ offsets[ last_filled + 1 ..=i] . fill ( current_offset) ;
561+ }
562+
563+ // SAFETY: `i` comes from a validity bitmap over `indices`, so it is in-bounds.
564+ let index = unsafe { indices. value_unchecked ( i) } . as_usize ( ) ;
565+ let start = input_offsets[ index] . as_usize ( ) ;
566+ let end = input_offsets[ index + 1 ] . as_usize ( ) ;
567+ capacity += end - start;
568+ offsets[ i + 1 ] = T :: Offset :: from_usize ( capacity)
569+ . ok_or_else ( || ArrowError :: OffsetOverflowError ( capacity) ) ?;
570+
571+ source_ranges. push ( ( start, end) ) ;
572+ last_filled = i + 1 ;
569573 }
570- offsets. push (
571- T :: Offset :: from_usize ( capacity)
572- . ok_or_else ( || ArrowError :: OffsetOverflowError ( capacity) ) ?,
574+
575+ // Fill trailing null offsets after the last valid position.
576+ let final_offset = T :: Offset :: from_usize ( capacity)
577+ . ok_or_else ( || ArrowError :: OffsetOverflowError ( capacity) ) ?;
578+ offsets[ last_filled + 1 ..] . fill ( final_offset) ;
579+ // Pass 2: copy byte data for all collected ranges.
580+ values. reserve ( capacity) ;
581+ debug_assert_eq ! (
582+ source_ranges. iter( ) . map( |( s, e) | e - s) . sum:: <usize >( ) ,
583+ capacity,
584+ "capacity must equal total bytes across all ranges"
573585 ) ;
574- }
575- let mut values = Vec :: with_capacity ( capacity) ;
576-
577- for ( i, index) in indices. values ( ) . iter ( ) . enumerate ( ) {
578- // check index is valid before using index. The value in
579- // NULL index slots may not be within bounds of array
580- let index = index. as_usize ( ) ;
581- if nulls. is_valid ( i) {
582- values. extend_from_slice ( array. value ( index) . as_ref ( ) ) ;
586+
587+ let src = array. value_data ( ) ;
588+ let src = src. as_ptr ( ) ;
589+ let dst = values. spare_capacity_mut ( ) ;
590+ debug_assert ! ( dst. len( ) >= capacity) ;
591+
592+ let mut offset = 0 ;
593+
594+ for ( start, end) in source_ranges. into_iter ( ) {
595+ let value_len = end - start;
596+ // SAFETY: caller guarantees each (start, end) is in-bounds of `src`.
597+ // `dst` asserted above to include the required capacity.
598+ // The regions don't overlap (src is input, dst is a fresh allocation).
599+ unsafe {
600+ std:: ptr:: copy_nonoverlapping (
601+ src. add ( start) ,
602+ dst. get_unchecked_mut ( offset..) . as_mut_ptr ( ) . cast :: < u8 > ( ) ,
603+ value_len,
604+ ) ;
605+ offset += value_len;
606+ }
583607 }
608+ // SAFETY: caller guarantees `capacity` == total bytes across all ranges,
609+ // so the loop above wrote exactly `capacity` bytes.
610+ unsafe { values. set_len ( capacity) } ;
584611 }
585- ( offsets, values)
586612 } ;
587613
588- T :: Offset :: from_usize ( values. len ( ) )
589- . ok_or_else ( || ArrowError :: OffsetOverflowError ( values. len ( ) ) ) ?;
590-
614+ // SAFETY: offsets are monotonically increasing and in-bounds of `values`,
615+ // and `nulls` (if present) has length == `indices.len()`.
591616 let array = unsafe {
592617 let offsets = OffsetBuffer :: new_unchecked ( offsets. into ( ) ) ;
593618 GenericByteArray :: < T > :: new_unchecked ( offsets, values. into ( ) , nulls)
@@ -1728,6 +1753,41 @@ mod tests {
17281753 assert_eq ! ( result. as_ref( ) , & expected) ;
17291754 }
17301755
1756+ /// Take from a *sliced* byte array, i.e. one whose value offsets do not
1757+ /// start at zero. This exercises copying byte data out of an array with a
1758+ /// non-zero base offset for both the no-null fast path and the nullable
1759+ /// path (null indices and selected null values).
1760+ #[ test]
1761+ fn test_take_bytes_sliced_values ( ) {
1762+ let values = StringArray :: from ( vec ! [
1763+ Some ( "aaa" ) ,
1764+ Some ( "bbb" ) ,
1765+ None ,
1766+ Some ( "ccccc" ) ,
1767+ Some ( "dd" ) ,
1768+ None ,
1769+ Some ( "eeee" ) ,
1770+ ] ) ;
1771+ // Slice so the underlying value offsets no longer start at 0:
1772+ // sliced == [None, "ccccc", "dd", None, "eeee"]
1773+ let sliced = values. slice ( 2 , 5 ) ;
1774+
1775+ // Fast path: every output slot is valid (no null indices, no null
1776+ // values selected).
1777+ let indices = Int32Array :: from ( vec ! [ 1 , 2 , 4 , 1 ] ) ;
1778+ let result = take ( & sliced, & indices, None ) . unwrap ( ) ;
1779+ let expected =
1780+ StringArray :: from ( vec ! [ Some ( "ccccc" ) , Some ( "dd" ) , Some ( "eeee" ) , Some ( "ccccc" ) ] ) ;
1781+ assert_eq ! ( result. as_string:: <i32 >( ) , & expected) ;
1782+
1783+ // Nullable path: a null index (position 1) and selected null values
1784+ // (sliced indices 0 and 3 are null).
1785+ let indices = Int32Array :: from ( vec ! [ Some ( 1 ) , None , Some ( 0 ) , Some ( 4 ) , Some ( 3 ) ] ) ;
1786+ let result = take ( & sliced, & indices, None ) . unwrap ( ) ;
1787+ let expected = StringArray :: from ( vec ! [ Some ( "ccccc" ) , None , None , Some ( "eeee" ) , None ] ) ;
1788+ assert_eq ! ( result. as_string:: <i32 >( ) , & expected) ;
1789+ }
1790+
17311791 fn _test_byte_view < T > ( )
17321792 where
17331793 T : ByteViewType ,
@@ -2808,11 +2868,38 @@ mod tests {
28082868 assert_eq ! ( array. len( ) , 3 ) ;
28092869 }
28102870
2871+ /// Fixture for the offset-overflow tests: a single large value plus the
2872+ /// number of times it must be selected so the cumulative offset exceeds
2873+ /// `i32::MAX`. Using a large value keeps the index count (and the test
2874+ /// runtime) small.
2875+ fn offset_overflow_fixture ( ) -> ( StringArray , usize ) {
2876+ let value_len = 1_000_000 ;
2877+ let values = StringArray :: from ( vec ! [ Some ( "a" . repeat( value_len) ) ] ) ;
2878+ let n = i32:: MAX as usize / value_len + 1 ;
2879+ ( values, n)
2880+ }
2881+
28112882 #[ test]
28122883 fn test_take_bytes_offset_overflow ( ) {
2813- let indices = Int32Array :: from ( vec ! [ 0 ; ( i32 :: MAX >> 4 ) as usize ] ) ;
2814- let text = ( 'a' ..='z' ) . collect :: < String > ( ) ;
2815- let values = StringArray :: from ( vec ! [ Some ( text. clone( ) ) ] ) ;
2884+ let ( values, n) = offset_overflow_fixture ( ) ;
2885+ let indices = Int32Array :: from ( vec ! [ 0 ; n] ) ;
2886+ assert ! ( matches!(
2887+ take( & values, & indices, None ) ,
2888+ Err ( ArrowError :: OffsetOverflowError ( _) )
2889+ ) ) ;
2890+ }
2891+
2892+ /// The offset-overflow error must also be produced on the nullable code
2893+ /// path (when the output contains nulls), not only on the no-null fast path.
2894+ #[ test]
2895+ fn test_take_bytes_offset_overflow_nullable ( ) {
2896+ let ( values, n) = offset_overflow_fixture ( ) ;
2897+ // A null index forces the output to contain nulls, exercising the
2898+ // nullable code path.
2899+ let validity =
2900+ NullBuffer :: from_iter ( std:: iter:: once ( false ) . chain ( std:: iter:: repeat_n ( true , n) ) ) ;
2901+ let indices = Int32Array :: new ( vec ! [ 0i32 ; n + 1 ] . into ( ) , Some ( validity) ) ;
2902+
28162903 assert ! ( matches!(
28172904 take( & values, & indices, None ) ,
28182905 Err ( ArrowError :: OffsetOverflowError ( _) )
0 commit comments