@@ -22,8 +22,8 @@ use std::sync::{Arc, LazyLock};
2222use std:: task:: { Context , Poll } ;
2323
2424use arrow:: array:: {
25- Array , ArrayRef , BinaryViewArray , BufferSpec , GenericByteViewArray , StringViewArray ,
26- layout, make_array,
25+ Array , ArrayRef , AsArray , BinaryViewArray , BufferSpec , GenericByteViewArray ,
26+ StringViewArray , layout, make_array,
2727} ;
2828use arrow:: buffer:: { Buffer , ScalarBuffer } ;
2929use arrow:: datatypes:: DataType ;
@@ -38,6 +38,7 @@ use arrow_data::{ArrayDataBuilder, ByteView, MAX_INLINE_VIEW_LEN};
3838#[ cfg( test) ]
3939use arrow_ipc:: writer:: StreamWriter ;
4040use arrow_ipc:: { CompressionType , root_as_message} ;
41+ use arrow_select:: dictionary:: garbage_collect_any_dictionary;
4142
4243use datafusion_common:: config:: SpillCompression ;
4344use datafusion_common:: { Result , internal_datafusion_err} ;
@@ -772,33 +773,45 @@ const VIEW_SIZE_BYTES: usize = 16;
772773/// `List`, `Map`, `Union`, `Dictionary`, and other child-bearing arrays are compacted too
773774/// - The Arrow `gc()` method itself is optimized and only copies referenced data
774775pub ( crate ) fn gc_view_arrays ( batch : & RecordBatch ) -> Result < RecordBatch > {
775- gc_view_arrays_of_columns ( batch, |_| true )
776+ gc_arrays_of_columns ( batch, |_, _ | true , false )
776777}
777778
778- /// Like [`gc_view_arrays`], but only compacts the view arrays of nested
779- /// columns, such as lists of strings.
779+ /// Like [`gc_view_arrays`], but only compacts `columns`, and also removes the
780+ /// dictionary values that no key of theirs references.
781+ ///
782+ /// `take` and `slice` keep every value of a dictionary, so a few rows taken
783+ /// from a large batch carry the values of all its rows.
784+ pub ( crate ) fn gc_columns ( batch : & RecordBatch , columns : & [ usize ] ) -> Result < RecordBatch > {
785+ gc_arrays_of_columns ( batch, |index, _| columns. contains ( & index) , true )
786+ }
787+
788+ /// Like [`gc_view_arrays`], but only compacts nested columns, such as lists of
789+ /// strings, and also removes the dictionary values that no key of theirs
790+ /// references.
780791///
781792/// A batch built by `interleave`, such as the output of a sort-preserving
782- /// merge, references the whole data buffers of the batches it takes rows from.
783- /// An accumulator that keeps the arrays it merges, such as `array_agg`, keeps
784- /// those buffers alive with each group. Top-level columns are left as they
785- /// are, since group keys and the accumulators of top-level states typically
786- /// copy the values they keep.
787- pub ( crate ) fn gc_nested_view_arrays ( batch : & RecordBatch ) -> Result < RecordBatch > {
788- gc_view_arrays_of_columns ( batch, |array| array. data_type ( ) . is_nested ( ) )
793+ /// merge, references the whole data buffers of the batches it takes rows
794+ /// from, and can hold all their dictionary values. An accumulator that keeps
795+ /// the arrays it merges, such as `array_agg`, keeps them alive with each
796+ /// group. Top-level columns are left as they are , since group keys and the
797+ /// accumulators of top-level states typically copy the values they keep.
798+ pub ( crate ) fn gc_nested_arrays ( batch : & RecordBatch ) -> Result < RecordBatch > {
799+ gc_arrays_of_columns ( batch, |_ , array| array. data_type ( ) . is_nested ( ) , true )
789800}
790801
791- /// Compacts the view arrays of the columns of `batch` that `include` selects.
792- fn gc_view_arrays_of_columns (
802+ /// Compacts the view arrays of the columns of `batch` that `include` selects
803+ /// by index, and their dictionary arrays if `dictionaries` is set.
804+ fn gc_arrays_of_columns (
793805 batch : & RecordBatch ,
794- include : impl Fn ( & ArrayRef ) -> bool ,
806+ include : impl Fn ( usize , & ArrayRef ) -> bool ,
807+ dictionaries : bool ,
795808) -> Result < RecordBatch > {
796809 let mut mutated = false ;
797810 let mut new_columns: Vec < Arc < dyn Array > > = Vec :: with_capacity ( batch. num_columns ( ) ) ;
798811
799- for array in batch. columns ( ) {
800- let ( gc_array, array_mutated) = if include ( array) {
801- gc_array ( array) ?
812+ for ( index , array) in batch. columns ( ) . iter ( ) . enumerate ( ) {
813+ let ( gc_array, array_mutated) = if include ( index , array) {
814+ gc_array ( array, dictionaries ) ?
802815 } else {
803816 ( Arc :: clone ( array) , false )
804817 } ;
@@ -813,7 +826,9 @@ fn gc_view_arrays_of_columns(
813826 }
814827}
815828
816- fn gc_array ( array : & ArrayRef ) -> Result < ( ArrayRef , bool ) > {
829+ /// Compacts the view arrays in `array`, and its dictionary arrays if
830+ /// `dictionaries` is set. Also returns whether anything was compacted.
831+ fn gc_array ( array : & ArrayRef , dictionaries : bool ) -> Result < ( ArrayRef , bool ) > {
817832 match array. data_type ( ) {
818833 DataType :: Utf8View => {
819834 let string_view = array
@@ -835,7 +850,18 @@ fn gc_array(array: &ArrayRef) -> Result<(ArrayRef, bool)> {
835850 None => ( Arc :: clone ( array) , false ) ,
836851 } )
837852 }
838- _ => gc_array_children ( array) ,
853+ DataType :: Dictionary ( _, _) if dictionaries => {
854+ let dictionary = array. as_any_dictionary ( ) ;
855+ let gc = garbage_collect_any_dictionary ( dictionary) ?;
856+ let gc = gc. as_any_dictionary ( ) ;
857+ let shrunk = gc. values ( ) . len ( ) < dictionary. values ( ) . len ( ) ;
858+ let ( values, values_mutated) = gc_array ( gc. values ( ) , dictionaries) ?;
859+ if !shrunk && !values_mutated {
860+ return Ok ( ( Arc :: clone ( array) , false ) ) ;
861+ }
862+ Ok ( ( gc. with_values ( values) , true ) )
863+ }
864+ _ => gc_array_children ( array, dictionaries) ,
839865 }
840866}
841867
@@ -920,7 +946,7 @@ fn dedup_buffer_entries(buffers: &[Buffer]) -> (Vec<u32>, Vec<Buffer>) {
920946 ( remap, unique)
921947}
922948
923- fn gc_array_children ( array : & ArrayRef ) -> Result < ( ArrayRef , bool ) > {
949+ fn gc_array_children ( array : & ArrayRef , dictionaries : bool ) -> Result < ( ArrayRef , bool ) > {
924950 let data = array. to_data ( ) ;
925951 if data. child_data ( ) . is_empty ( ) {
926952 return Ok ( ( Arc :: clone ( array) , false ) ) ;
@@ -930,7 +956,7 @@ fn gc_array_children(array: &ArrayRef) -> Result<(ArrayRef, bool)> {
930956 let mut child_data = Vec :: with_capacity ( data. child_data ( ) . len ( ) ) ;
931957 for child in data. child_data ( ) {
932958 let child_array = make_array ( child. clone ( ) ) ;
933- let ( gc_child, child_mutated) = gc_array ( & child_array) ?;
959+ let ( gc_child, child_mutated) = gc_array ( & child_array, dictionaries ) ?;
934960 mutated |= child_mutated;
935961 child_data. push ( gc_child. to_data ( ) ) ;
936962 }
@@ -2719,7 +2745,7 @@ mod tests {
27192745 }
27202746
27212747 #[ test]
2722- fn test_gc_nested_view_arrays_skips_top_level_columns ( ) -> Result < ( ) > {
2748+ fn test_gc_nested_arrays_skips_top_level_columns ( ) -> Result < ( ) > {
27232749 use arrow:: buffer:: Buffer ;
27242750
27252751 let strings: Vec < String > = ( 0 ..400 )
@@ -2749,9 +2775,9 @@ mod tests {
27492775 let batch =
27502776 RecordBatch :: try_new ( schema, vec ! [ Arc :: clone( & top_level) , list_array] ) ?;
27512777
2752- // `gc_view_arrays` compacts the top-level column, `gc_nested_view_arrays ` does not
2778+ // `gc_view_arrays` compacts the top-level column, `gc_nested_arrays ` does not
27532779 assert ! ( !Arc :: ptr_eq( gc_view_arrays( & batch) ?. column( 0 ) , & top_level) ) ;
2754- let gc_batch = gc_nested_view_arrays ( & batch) ?;
2780+ let gc_batch = gc_nested_arrays ( & batch) ?;
27552781 assert ! ( Arc :: ptr_eq( gc_batch. column( 0 ) , & top_level) ) ;
27562782
27572783 let gc_list_values =
@@ -2768,6 +2794,67 @@ mod tests {
27682794 Ok ( ( ) )
27692795 }
27702796
2797+ #[ test]
2798+ fn test_gc_columns_compacts_dictionaries ( ) -> Result < ( ) > {
2799+ use arrow:: array:: { DictionaryArray , ListArray } ;
2800+ use arrow:: buffer:: OffsetBuffer ;
2801+ use arrow:: datatypes:: Int32Type ;
2802+
2803+ // Every column references a few of the same 100 values
2804+ let values: ArrayRef = Arc :: new ( StringArray :: from_iter_values (
2805+ ( 0 ..100 ) . map ( |i| format ! ( "value {i}" ) ) ,
2806+ ) ) ;
2807+ let dictionary = |keys : Vec < i32 > | {
2808+ DictionaryArray :: new ( Int32Array :: from ( keys) , Arc :: clone ( & values) )
2809+ } ;
2810+ let decode = |array : & ArrayRef | -> Vec < String > {
2811+ let dictionary = array. as_dictionary :: < Int32Type > ( ) ;
2812+ let strings = dictionary. downcast_dict :: < StringArray > ( ) . unwrap ( ) ;
2813+ strings
2814+ . into_iter ( )
2815+ . map ( |s| s. unwrap ( ) . to_string ( ) )
2816+ . collect ( )
2817+ } ;
2818+ let list = ListArray :: new (
2819+ Arc :: new ( Field :: new_list_field (
2820+ DataType :: Dictionary ( Box :: new ( DataType :: Int32 ) , Box :: new ( DataType :: Utf8 ) ) ,
2821+ true ,
2822+ ) ) ,
2823+ OffsetBuffer :: from_lengths ( [ 2 , 1 ] ) ,
2824+ Arc :: new ( dictionary ( vec ! [ 7 , 3 , 7 ] ) ) ,
2825+ None ,
2826+ ) ;
2827+ let batch = RecordBatch :: try_from_iter ( vec ! [
2828+ ( "compacted" , Arc :: new( dictionary( vec![ 5 , 9 ] ) ) as ArrayRef ) ,
2829+ ( "other" , Arc :: new( dictionary( vec![ 1 , 2 ] ) ) as ArrayRef ) ,
2830+ ( "list" , Arc :: new( list) as ArrayRef ) ,
2831+ ] ) ?;
2832+
2833+ // `gc_view_arrays` leaves dictionaries as they are
2834+ let gc_batch = gc_view_arrays ( & batch) ?;
2835+ for ( column, gc_column) in batch. columns ( ) . iter ( ) . zip ( gc_batch. columns ( ) ) {
2836+ assert ! ( Arc :: ptr_eq( column, gc_column) ) ;
2837+ }
2838+
2839+ // `gc_columns` keeps only the values that the given columns reference
2840+ let gc_batch = gc_columns ( & batch, & [ 0 , 2 ] ) ?;
2841+ let compacted = gc_batch. column ( 0 ) ;
2842+ assert_eq ! ( compacted. as_any_dictionary( ) . values( ) . len( ) , 2 ) ;
2843+ assert_eq ! ( decode( compacted) , [ "value 5" , "value 9" ] ) ;
2844+ assert ! ( Arc :: ptr_eq( gc_batch. column( 1 ) , batch. column( 1 ) ) ) ;
2845+ let list_values = gc_batch. column ( 2 ) . as_list :: < i32 > ( ) . values ( ) ;
2846+ assert_eq ! ( list_values. as_any_dictionary( ) . values( ) . len( ) , 2 ) ;
2847+ assert_eq ! ( decode( list_values) , [ "value 7" , "value 3" , "value 7" ] ) ;
2848+
2849+ // `gc_nested_arrays` compacts only the nested column
2850+ let gc_batch = gc_nested_arrays ( & batch) ?;
2851+ assert ! ( Arc :: ptr_eq( gc_batch. column( 0 ) , batch. column( 0 ) ) ) ;
2852+ let list_values = gc_batch. column ( 2 ) . as_list :: < i32 > ( ) . values ( ) ;
2853+ assert_eq ! ( list_values. as_any_dictionary( ) . values( ) . len( ) , 2 ) ;
2854+
2855+ Ok ( ( ) )
2856+ }
2857+
27712858 #[ tokio:: test]
27722859 async fn test_spill_file_size_gc_verification_string_view ( ) -> Result < ( ) > {
27732860 use arrow:: array:: StringViewArray ;
0 commit comments