1use crate::filter::{FilterBuilder, FilterPredicate, FilterSelection};
24use crate::interleave::interleave_record_batch;
25use crate::take::take_record_batch;
26use arrow_array::types::{BinaryViewType, StringViewType};
27use arrow_array::{Array, ArrayRef, BooleanArray, RecordBatch, downcast_primitive};
28use arrow_schema::{ArrowError, DataType, SchemaRef};
29use std::collections::VecDeque;
30use std::sync::Arc;
31mod byte_view;
35mod fixed_size_binary;
36mod generic;
37mod primitive;
38
39use byte_view::InProgressByteViewArray;
40use fixed_size_binary::InProgressFixedSizeBinaryArray;
41use generic::GenericInProgressArray;
42use primitive::InProgressPrimitiveArray;
43
44fn has_sparse_filter_copy(data_type: &DataType) -> bool {
45 data_type.is_primitive() || matches!(data_type, DataType::Utf8View | DataType::BinaryView)
46}
47
48const SPARSE_FILTER_COPY_MAX_SELECTIVITY_DENOMINATOR: usize = 16;
54
55fn should_use_sparse_filter_copy(filter_len: usize, selected_count: usize) -> bool {
56 selected_count <= filter_len / SPARSE_FILTER_COPY_MAX_SELECTIVITY_DENOMINATOR
57}
58
59#[derive(Debug)]
151pub struct BatchCoalescer {
152 schema: SchemaRef,
154 target_batch_size: usize,
158 in_progress_arrays: Vec<Box<dyn InProgressArray>>,
160 has_non_specialized_filter_columns: bool,
162 buffered_rows: usize,
164 completed: VecDeque<RecordBatch>,
166 biggest_coalesce_batch_size: Option<usize>,
168}
169
170impl BatchCoalescer {
171 pub fn new(schema: SchemaRef, target_batch_size: usize) -> Self {
179 let has_non_specialized_filter_columns = schema
180 .fields()
181 .iter()
182 .any(|field| !has_sparse_filter_copy(field.data_type()));
183 let in_progress_arrays = schema
184 .fields()
185 .iter()
186 .map(|field| create_in_progress_array(field.data_type(), target_batch_size))
187 .collect::<Vec<_>>();
188
189 Self {
190 schema,
191 target_batch_size,
192 in_progress_arrays,
193 has_non_specialized_filter_columns,
194 completed: VecDeque::with_capacity(1),
196 buffered_rows: 0,
197 biggest_coalesce_batch_size: None,
198 }
199 }
200
201 pub fn with_biggest_coalesce_batch_size(mut self, limit: Option<usize>) -> Self {
214 self.biggest_coalesce_batch_size = limit;
215 self
216 }
217
218 pub fn biggest_coalesce_batch_size(&self) -> Option<usize> {
222 self.biggest_coalesce_batch_size
223 }
224
225 pub fn set_biggest_coalesce_batch_size(&mut self, limit: Option<usize>) {
229 self.biggest_coalesce_batch_size = limit;
230 }
231
232 pub fn schema(&self) -> SchemaRef {
234 Arc::clone(&self.schema)
235 }
236
237 pub fn push_batch_with_filter(
266 &mut self,
267 batch: RecordBatch,
268 filter: &BooleanArray,
269 ) -> Result<(), ArrowError> {
270 self.push_batch_with_filter_builder(batch, FilterBuilder::new(filter))
271 }
272
273 pub fn push_batch_with_filter_builder(
303 &mut self,
304 batch: RecordBatch,
305 filter_builder: FilterBuilder,
306 ) -> Result<(), ArrowError> {
307 let predicate = Self::filter_predicate_for_batch(&batch, filter_builder);
308 self.push_batch_with_filtered_columns(batch, &predicate)
309 }
310
311 pub fn push_batch_with_indices(
335 &mut self,
336 batch: RecordBatch,
337 indices: &dyn Array,
338 ) -> Result<(), ArrowError> {
339 let taken_batch = take_record_batch(&batch, indices)?;
341 self.push_batch(taken_batch)
342 }
343
344 pub fn push_batch_interleaved(
371 &mut self,
372 batches: &[RecordBatch],
373 indices: &[(usize, usize)],
374 ) -> Result<(), ArrowError> {
375 if batches.is_empty() {
376 return Err(ArrowError::InvalidArgumentError(
377 "push_batch_interleaved requires at least one input batch".to_string(),
378 ));
379 }
380
381 let refs: Vec<&RecordBatch> = batches.iter().collect();
383 let interleaved = interleave_record_batch(&refs, indices)?;
384 self.push_batch(interleaved)
385 }
386
387 pub fn push_batch(&mut self, batch: RecordBatch) -> Result<(), ArrowError> {
414 let batch_size = batch.num_rows();
533
534 if batch_size == 0 {
536 return Ok(());
537 }
538
539 if let Some(limit) = self.biggest_coalesce_batch_size
541 && batch_size > limit
542 {
543 if self.buffered_rows == 0 {
546 self.completed.push_back(batch);
547 return Ok(());
548 }
549
550 if self.buffered_rows > limit {
555 self.finish_buffered_batch()?;
556 self.completed.push_back(batch);
557 return Ok(());
558 }
559
560 }
565
566 let (_schema, arrays, mut num_rows) = batch.into_parts();
567
568 if arrays.len() != self.in_progress_arrays.len() {
570 return Err(ArrowError::InvalidArgumentError(format!(
571 "Batch has {} columns but BatchCoalescer expects {}",
572 arrays.len(),
573 self.in_progress_arrays.len()
574 )));
575 }
576 self.in_progress_arrays
577 .iter_mut()
578 .zip(arrays)
579 .for_each(|(in_progress, array)| {
580 in_progress.set_source(Some(array));
581 });
582
583 let mut offset = 0;
586 while num_rows > (self.target_batch_size - self.buffered_rows) {
587 let remaining_rows = self.target_batch_size - self.buffered_rows;
588 debug_assert!(remaining_rows > 0);
589
590 for in_progress in &mut self.in_progress_arrays {
592 in_progress.copy_rows(offset, remaining_rows)?;
593 }
594
595 self.buffered_rows += remaining_rows;
596 offset += remaining_rows;
597 num_rows -= remaining_rows;
598
599 self.finish_buffered_batch()?;
600 }
601
602 self.buffered_rows += num_rows;
604 if num_rows > 0 {
605 for in_progress in &mut self.in_progress_arrays {
606 in_progress.copy_rows(offset, num_rows)?;
607 }
608 }
609
610 if self.buffered_rows >= self.target_batch_size {
612 self.finish_buffered_batch()?;
613 }
614
615 for in_progress in &mut self.in_progress_arrays {
617 in_progress.set_source(None);
618 }
619
620 Ok(())
621 }
622
623 pub fn get_buffered_rows(&self) -> usize {
625 self.buffered_rows
626 }
627
628 pub fn finish_buffered_batch(&mut self) -> Result<(), ArrowError> {
636 if self.buffered_rows == 0 {
637 return Ok(());
638 }
639 let new_arrays = self
640 .in_progress_arrays
641 .iter_mut()
642 .map(|array| array.finish())
643 .collect::<Result<Vec<_>, ArrowError>>()?;
644
645 for (array, field) in new_arrays.iter().zip(self.schema.fields().iter()) {
646 debug_assert_eq!(array.data_type(), field.data_type());
647 debug_assert_eq!(array.len(), self.buffered_rows);
648 }
649
650 let batch = unsafe {
652 RecordBatch::new_unchecked(Arc::clone(&self.schema), new_arrays, self.buffered_rows)
653 };
654
655 self.buffered_rows = 0;
656 self.completed.push_back(batch);
657 Ok(())
658 }
659
660 pub fn is_empty(&self) -> bool {
662 self.buffered_rows == 0 && self.completed.is_empty()
663 }
664
665 pub fn has_completed_batch(&self) -> bool {
667 !self.completed.is_empty()
668 }
669
670 pub fn next_completed_batch(&mut self) -> Option<RecordBatch> {
672 self.completed.pop_front()
673 }
674
675 pub fn size(&self) -> usize {
677 self.in_progress_arrays.capacity() * size_of::<Box<dyn InProgressArray>>()
678 + self
679 .in_progress_arrays
680 .iter()
681 .map(|array| array.size())
682 .sum::<usize>()
683 + self.completed.capacity() * size_of::<RecordBatch>()
684 + self
685 .completed
686 .iter()
687 .map(|batch| batch.get_array_memory_size())
688 .sum::<usize>()
689 }
690}
691
692impl BatchCoalescer {
693 fn filter_predicate_for_batch(
694 batch: &RecordBatch,
695 mut filter_builder: FilterBuilder,
696 ) -> FilterPredicate {
697 if batch.num_columns() > 1
698 || (batch.num_columns() > 0
699 && FilterBuilder::is_optimize_beneficial(batch.schema_ref().field(0).data_type()))
700 {
701 filter_builder = filter_builder.optimize();
702 }
703 filter_builder.build()
704 }
705
706 fn push_batch_with_filtered_columns(
707 &mut self,
708 batch: RecordBatch,
709 predicate: &FilterPredicate,
710 ) -> Result<(), ArrowError> {
711 let filter_len = predicate.filter_len();
712 let selected_count = predicate.count();
713 let batch_num_rows = batch.num_rows();
714 let batch_num_columns = batch.num_columns();
715
716 if filter_len > batch_num_rows {
717 return Err(ArrowError::InvalidArgumentError(format!(
718 "Filter predicate of length {filter_len} is larger than target array of length {batch_num_rows}"
719 )));
720 }
721
722 if selected_count == 0 {
723 return Ok(());
724 }
725
726 if selected_count == batch_num_rows && filter_len == batch_num_rows {
727 return self.push_batch(batch);
728 }
729
730 if batch_num_columns != self.in_progress_arrays.len() {
731 return Err(ArrowError::InvalidArgumentError(format!(
732 "Batch has {} columns but BatchCoalescer expects {}",
733 batch_num_columns,
734 self.in_progress_arrays.len()
735 )));
736 }
737
738 let exceeds_coalesce_limit = self
739 .biggest_coalesce_batch_size
740 .is_some_and(|limit| selected_count > limit);
741 let does_not_fit_buffer = selected_count > self.target_batch_size - self.buffered_rows;
742 let should_materialize_filter = exceeds_coalesce_limit
743 || self.has_non_specialized_filter_columns
744 || does_not_fit_buffer
745 || !should_use_sparse_filter_copy(filter_len, selected_count);
746
747 if should_materialize_filter {
748 let filtered_batch = predicate.filter_record_batch(&batch)?;
750 return self.push_batch(filtered_batch);
751 }
752
753 let (_schema, arrays, _num_rows) = batch.into_parts();
754
755 for (in_progress, array) in self.in_progress_arrays.iter_mut().zip(arrays) {
756 in_progress.copy_rows_by_filter_from(array, predicate)?;
757 }
758
759 self.buffered_rows += selected_count;
760 if self.buffered_rows >= self.target_batch_size {
761 self.finish_buffered_batch()?;
762 }
763
764 Ok(())
765 }
766}
767
768fn create_in_progress_array(data_type: &DataType, batch_size: usize) -> Box<dyn InProgressArray> {
770 macro_rules! instantiate_primitive {
771 ($t:ty) => {
772 Box::new(InProgressPrimitiveArray::<$t>::new(
773 batch_size,
774 data_type.clone(),
775 ))
776 };
777 }
778
779 downcast_primitive! {
780 data_type => (instantiate_primitive),
782 DataType::Utf8View => Box::new(InProgressByteViewArray::<StringViewType>::new(batch_size)),
783 DataType::BinaryView => {
784 Box::new(InProgressByteViewArray::<BinaryViewType>::new(batch_size))
785 }
786 DataType::FixedSizeBinary(size) => {
787 Box::new(InProgressFixedSizeBinaryArray::new(*size, batch_size))
788 }
789 _ => Box::new(GenericInProgressArray::new()),
790 }
791}
792
793trait InProgressArray: std::fmt::Debug + Send + Sync {
803 fn set_source(&mut self, source: Option<ArrayRef>);
808
809 fn copy_rows(&mut self, offset: usize, len: usize) -> Result<(), ArrowError>;
815
816 fn copy_rows_by_filter(&mut self, filter: &FilterPredicate) -> Result<(), ArrowError> {
820 self.copy_rows_by_selection(filter.selection())
821 }
822
823 fn copy_rows_by_filter_from(
830 &mut self,
831 source: ArrayRef,
832 filter: &FilterPredicate,
833 ) -> Result<(), ArrowError> {
834 self.set_source(Some(source));
835 let result = self.copy_rows_by_filter(filter);
836 self.set_source(None);
837 result
838 }
839
840 fn copy_rows_by_selection(&mut self, selection: FilterSelection<'_>) -> Result<(), ArrowError> {
846 match selection {
847 FilterSelection::None => Ok(()),
848 FilterSelection::All { len } => self.copy_rows(0, len),
849 FilterSelection::Slices(slices) => {
850 slices.try_for_each(|(start, end)| self.copy_rows(start, end - start))
851 }
852 FilterSelection::Indices(indices) => indices.try_for_each(|idx| self.copy_rows(idx, 1)),
853 }
854 }
855
856 fn finish(&mut self) -> Result<ArrayRef, ArrowError>;
858
859 fn size(&self) -> usize;
861}
862
863#[cfg(test)]
864mod tests {
865 use super::*;
866 use crate::concat::concat_batches;
867 use crate::filter::filter_record_batch;
868 use arrow_array::builder::StringViewBuilder;
869 use arrow_array::cast::AsArray;
870 use arrow_array::types::Int32Type;
871 use arrow_array::{
872 BinaryViewArray, Int32Array, Int64Array, RecordBatchOptions, StringArray, StringViewArray,
873 TimestampNanosecondArray, UInt32Array, UInt64Array, make_array, record_batch,
874 };
875 use arrow_buffer::BooleanBufferBuilder;
876 use arrow_schema::{DataType, Field, Schema};
877 use rand::{RngExt, SeedableRng};
878 use std::ops::Range;
879
880 #[test]
881 fn test_coalesce() {
882 let batch = uint32_batch(0..8);
883 Test::new("coalesce")
884 .with_batches(std::iter::repeat_n(batch, 10))
885 .with_batch_size(21)
887 .with_expected_output_sizes(vec![21, 21, 21, 17])
888 .run();
889 }
890
891 #[test]
892 fn test_coalesce_one_by_one() {
893 let batch = uint32_batch(0..1); Test::new("coalesce_one_by_one")
895 .with_batches(std::iter::repeat_n(batch, 97))
896 .with_batch_size(20)
898 .with_expected_output_sizes(vec![20, 20, 20, 20, 17])
899 .run();
900 }
901
902 #[test]
903 fn test_coalesce_empty() {
904 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, false)]));
905
906 Test::new("coalesce_empty")
907 .with_batches(vec![])
908 .with_schema(schema)
909 .with_batch_size(21)
910 .with_expected_output_sizes(vec![])
911 .run();
912 }
913
914 #[test]
915 fn test_sparse_filter_copy_threshold() {
916 assert!(should_use_sparse_filter_copy(8192, 8));
917 assert!(should_use_sparse_filter_copy(8192, 81));
918 assert!(!should_use_sparse_filter_copy(8192, 819));
919 assert!(!should_use_sparse_filter_copy(8192, 6553));
920 }
921
922 #[test]
923 fn test_single_large_batch_greater_than_target() {
924 let batch = uint32_batch(0..4096);
926 Test::new("coalesce_single_large_batch_greater_than_target")
927 .with_batch(batch)
928 .with_batch_size(1000)
929 .with_expected_output_sizes(vec![1000, 1000, 1000, 1000, 96])
930 .run();
931 }
932
933 #[test]
934 fn test_single_large_batch_smaller_than_target() {
935 let batch = uint32_batch(0..4096);
937 Test::new("coalesce_single_large_batch_smaller_than_target")
938 .with_batch(batch)
939 .with_batch_size(8192)
940 .with_expected_output_sizes(vec![4096])
941 .run();
942 }
943
944 #[test]
945 fn test_single_large_batch_equal_to_target() {
946 let batch = uint32_batch(0..4096);
948 Test::new("coalesce_single_large_batch_equal_to_target")
949 .with_batch(batch)
950 .with_batch_size(4096)
951 .with_expected_output_sizes(vec![4096])
952 .run();
953 }
954
955 #[test]
956 fn test_single_large_batch_equally_divisible_in_target() {
957 let batch = uint32_batch(0..4096);
959 Test::new("coalesce_single_large_batch_equally_divisible_in_target")
960 .with_batch(batch)
961 .with_batch_size(1024)
962 .with_expected_output_sizes(vec![1024, 1024, 1024, 1024])
963 .run();
964 }
965
966 #[test]
967 fn test_empty_schema() {
968 let schema = Schema::empty();
969 let batch = RecordBatch::new_empty(schema.into());
970 Test::new("coalesce_empty_schema")
971 .with_batch(batch)
972 .with_expected_output_sizes(vec![])
973 .run();
974 }
975
976 #[test]
978 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_001() {
980 let mut filter_builder = RandomFilterBuilder {
981 num_rows: 8000,
982 selectivity: 0.001,
983 seed: 0,
984 };
985
986 let mut test = Test::new("coalesce_filtered_001");
990 for _ in 0..10 {
991 test = test
992 .with_batch(multi_column_batch(0..8000))
993 .with_filter(filter_builder.next_filter())
994 }
995 test.with_batch_size(15)
996 .with_expected_output_sizes(vec![15, 15, 15, 13])
997 .run();
998 }
999
1000 #[test]
1002 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_01() {
1004 let mut filter_builder = RandomFilterBuilder {
1005 num_rows: 8000,
1006 selectivity: 0.01,
1007 seed: 0,
1008 };
1009
1010 let mut test = Test::new("coalesce_filtered_01");
1014 for _ in 0..10 {
1015 test = test
1016 .with_batch(multi_column_batch(0..8000))
1017 .with_filter(filter_builder.next_filter())
1018 }
1019 test.with_batch_size(128)
1020 .with_expected_output_sizes(vec![128, 128, 128, 128, 128, 128, 15])
1021 .run();
1022 }
1023
1024 #[test]
1026 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_10() {
1028 let mut filter_builder = RandomFilterBuilder {
1029 num_rows: 8000,
1030 selectivity: 0.1,
1031 seed: 0,
1032 };
1033
1034 let mut test = Test::new("coalesce_filtered_10");
1038 for _ in 0..10 {
1039 test = test
1040 .with_batch(multi_column_batch(0..8000))
1041 .with_filter(filter_builder.next_filter())
1042 }
1043 test.with_batch_size(1024)
1044 .with_expected_output_sizes(vec![1024, 1024, 1024, 1024, 1024, 1024, 1024, 840])
1045 .run();
1046 }
1047
1048 #[test]
1050 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_90() {
1052 let mut filter_builder = RandomFilterBuilder {
1053 num_rows: 800,
1054 selectivity: 0.90,
1055 seed: 0,
1056 };
1057
1058 let mut test = Test::new("coalesce_filtered_90");
1062 for _ in 0..10 {
1063 test = test
1064 .with_batch(multi_column_batch(0..800))
1065 .with_filter(filter_builder.next_filter())
1066 }
1067 test.with_batch_size(1024)
1068 .with_expected_output_sizes(vec![1024, 1024, 1024, 1024, 1024, 1024, 1024, 13])
1069 .run();
1070 }
1071
1072 #[test]
1074 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_mixed() {
1076 let mut filter_builder = RandomFilterBuilder {
1077 num_rows: 800,
1078 selectivity: 0.90,
1079 seed: 0,
1080 };
1081
1082 let mut test = Test::new("coalesce_filtered_mixed");
1083 for _ in 0..3 {
1084 let mut all_filter_builder = BooleanBufferBuilder::new(1000);
1087 all_filter_builder.append_n(500, true);
1088 all_filter_builder.append_n(1, false);
1089 all_filter_builder.append_n(499, false);
1090 let all_filter = all_filter_builder.build();
1091
1092 test = test
1093 .with_batch(multi_column_batch(0..1000))
1094 .with_filter(BooleanArray::from(all_filter))
1095 .with_batch(multi_column_batch(0..800))
1096 .with_filter(filter_builder.next_filter());
1097 filter_builder.selectivity *= 0.6;
1099 }
1100
1101 test.with_batch_size(250)
1104 .with_expected_output_sizes(vec![
1105 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 179,
1106 ])
1107 .run();
1108 }
1109
1110 #[test]
1111 fn test_push_batch_with_filter_builder() {
1112 let batch = record_batch!(("a", Int32, [1, 2, 3, 4])).unwrap();
1113 let filter = BooleanArray::from(vec![Some(true), None, Some(false), Some(true)]);
1115 let expected = record_batch!(("a", Int32, [1, 4])).unwrap();
1116
1117 let with_count = unsafe { FilterBuilder::new(&filter).with_count(2) };
1119 let optimized = FilterBuilder::new(&filter).optimize();
1122 for filter_builder in [with_count, optimized] {
1123 let mut coalescer = BatchCoalescer::new(batch.schema(), 10);
1124 coalescer
1125 .push_batch_with_filter_builder(batch.clone(), filter_builder)
1126 .unwrap();
1127 coalescer.finish_buffered_batch().unwrap();
1128 assert_eq!(coalescer.next_completed_batch().unwrap(), expected);
1129 assert!(coalescer.next_completed_batch().is_none());
1130 }
1131 }
1132
1133 #[test]
1134 fn test_coalesce_non_null() {
1135 Test::new("coalesce_non_null")
1136 .with_batch(uint32_batch_non_null(0..3000))
1138 .with_batch(uint32_batch_non_null(0..1040))
1139 .with_batch_size(1024)
1140 .with_expected_output_sizes(vec![1024, 1024, 1024, 968])
1141 .run();
1142 }
1143 #[test]
1144 #[cfg_attr(miri, ignore)] fn test_utf8_split() {
1146 Test::new("coalesce_utf8")
1147 .with_batch(utf8_batch(0..3000))
1149 .with_batch(utf8_batch(0..1040))
1150 .with_batch_size(1024)
1151 .with_expected_output_sizes(vec![1024, 1024, 1024, 968])
1152 .run();
1153 }
1154
1155 #[test]
1156 fn test_string_view_no_views() {
1157 let output_batches = Test::new("coalesce_string_view_no_views")
1158 .with_batch(stringview_batch([Some("foo"), Some("bar")]))
1160 .with_batch(stringview_batch([Some("baz"), Some("qux")]))
1161 .with_expected_output_sizes(vec![4])
1162 .run();
1163
1164 expect_buffer_layout(
1165 col_as_string_view("c0", output_batches.first().unwrap()),
1166 vec![],
1167 );
1168 }
1169
1170 #[test]
1171 fn test_string_view_batch_small_no_compact() {
1172 let batch = stringview_batch_repeated(1000, [Some("a"), Some("b"), Some("c")]);
1174 let output_batches = Test::new("coalesce_string_view_batch_small_no_compact")
1175 .with_batch(batch.clone())
1176 .with_expected_output_sizes(vec![1000])
1177 .run();
1178
1179 let array = col_as_string_view("c0", &batch);
1180 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1181 assert_eq!(array.data_buffers().len(), 0);
1182 assert_eq!(array.data_buffers().len(), gc_array.data_buffers().len()); expect_buffer_layout(gc_array, vec![]);
1185 }
1186
1187 #[test]
1188 #[cfg_attr(miri, ignore)] fn test_string_view_batch_large_no_compact() {
1190 let batch = stringview_batch_repeated(1000, [Some("This string is longer than 12 bytes")]);
1192 let output_batches = Test::new("coalesce_string_view_batch_large_no_compact")
1193 .with_batch(batch.clone())
1194 .with_batch_size(1000)
1195 .with_expected_output_sizes(vec![1000])
1196 .run();
1197
1198 let array = col_as_string_view("c0", &batch);
1199 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1200 assert_eq!(array.data_buffers().len(), 5);
1201 assert_eq!(array.data_buffers().len(), gc_array.data_buffers().len()); expect_buffer_layout(
1204 gc_array,
1205 vec![
1206 ExpectedLayout {
1207 len: 8190,
1208 capacity: 8192,
1209 },
1210 ExpectedLayout {
1211 len: 8190,
1212 capacity: 8192,
1213 },
1214 ExpectedLayout {
1215 len: 8190,
1216 capacity: 8192,
1217 },
1218 ExpectedLayout {
1219 len: 8190,
1220 capacity: 8192,
1221 },
1222 ExpectedLayout {
1223 len: 2240,
1224 capacity: 8192,
1225 },
1226 ],
1227 );
1228 }
1229
1230 #[test]
1231 fn test_string_view_batch_small_with_buffers_no_compact() {
1232 let short_strings = std::iter::repeat(Some("SmallString"));
1234 let long_strings = std::iter::once(Some("This string is longer than 12 bytes"));
1235 let values = short_strings.take(20).chain(long_strings);
1237 let batch = stringview_batch_repeated(1000, values)
1238 .slice(5, 10);
1240 let output_batches = Test::new("coalesce_string_view_batch_small_with_buffers_no_compact")
1241 .with_batch(batch.clone())
1242 .with_batch_size(1000)
1243 .with_expected_output_sizes(vec![10])
1244 .run();
1245
1246 let array = col_as_string_view("c0", &batch);
1247 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1248 assert_eq!(array.data_buffers().len(), 1); assert_eq!(gc_array.data_buffers().len(), 0); }
1251
1252 #[test]
1253 fn test_string_view_batch_large_slice_compact() {
1254 let batch = stringview_batch_repeated(1000, [Some("This string is longer than 12 bytes")])
1256 .slice(11, 22);
1258
1259 let output_batches = Test::new("coalesce_string_view_batch_large_slice_compact")
1260 .with_batch(batch.clone())
1261 .with_batch_size(1000)
1262 .with_expected_output_sizes(vec![22])
1263 .run();
1264
1265 let array = col_as_string_view("c0", &batch);
1266 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1267 assert_eq!(array.data_buffers().len(), 5);
1268
1269 expect_buffer_layout(
1270 gc_array,
1271 vec![ExpectedLayout {
1272 len: 770,
1273 capacity: 8192,
1274 }],
1275 );
1276 }
1277
1278 #[test]
1279 #[cfg_attr(miri, ignore)] fn test_string_view_mixed() {
1281 let large_view_batch =
1282 stringview_batch_repeated(1000, [Some("This string is longer than 12 bytes")]);
1283 let small_view_batch = stringview_batch_repeated(1000, [Some("SmallString")]);
1284 let mixed_batch = stringview_batch_repeated(
1285 1000,
1286 [Some("This string is longer than 12 bytes"), Some("Small")],
1287 );
1288 let mixed_batch_nulls = stringview_batch_repeated(
1289 1000,
1290 [
1291 Some("This string is longer than 12 bytes"),
1292 Some("Small"),
1293 None,
1294 ],
1295 );
1296
1297 let output_batches = Test::new("coalesce_string_view_mixed")
1300 .with_batch(large_view_batch.clone())
1301 .with_batch(small_view_batch)
1302 .with_batch(large_view_batch.slice(10, 20))
1304 .with_batch(mixed_batch_nulls)
1305 .with_batch(large_view_batch.slice(10, 20))
1307 .with_batch(mixed_batch)
1308 .with_expected_output_sizes(vec![1024, 1024, 1024, 968])
1309 .run();
1310
1311 expect_buffer_layout(
1312 col_as_string_view("c0", output_batches.first().unwrap()),
1313 vec![
1314 ExpectedLayout {
1315 len: 8190,
1316 capacity: 8192,
1317 },
1318 ExpectedLayout {
1319 len: 8190,
1320 capacity: 8192,
1321 },
1322 ExpectedLayout {
1323 len: 8190,
1324 capacity: 8192,
1325 },
1326 ExpectedLayout {
1327 len: 8190,
1328 capacity: 8192,
1329 },
1330 ExpectedLayout {
1331 len: 2240,
1332 capacity: 8192,
1333 },
1334 ],
1335 );
1336 }
1337
1338 #[test]
1339 #[cfg_attr(miri, ignore)] fn test_string_view_many_small_compact() {
1341 let batch = stringview_batch_repeated(
1344 200,
1345 [Some("This string is 28 bytes long"), Some("small string")],
1346 );
1347 let output_batches = Test::new("coalesce_string_view_many_small_compact")
1348 .with_batch(batch.clone())
1351 .with_batch(batch.clone())
1352 .with_batch(batch.clone())
1353 .with_batch(batch.clone())
1354 .with_batch(batch.clone())
1355 .with_batch(batch.clone())
1356 .with_batch(batch.clone())
1357 .with_batch(batch.clone())
1358 .with_batch(batch.clone())
1359 .with_batch(batch.clone())
1360 .with_batch_size(8000)
1361 .with_expected_output_sizes(vec![2000]) .run();
1363
1364 expect_buffer_layout(
1366 col_as_string_view("c0", output_batches.first().unwrap()),
1367 vec![
1368 ExpectedLayout {
1369 len: 8176,
1370 capacity: 8192,
1371 },
1372 ExpectedLayout {
1373 len: 16380,
1374 capacity: 16384,
1375 },
1376 ExpectedLayout {
1377 len: 3444,
1378 capacity: 32768,
1379 },
1380 ],
1381 );
1382 }
1383
1384 #[test]
1385 #[cfg_attr(miri, ignore)] fn test_string_view_many_small_boundary() {
1387 let batch = stringview_batch_repeated(100, [Some("This string is a power of two=32")]);
1389 let output_batches = Test::new("coalesce_string_view_many_small_boundary")
1390 .with_batches(std::iter::repeat_n(batch, 20))
1391 .with_batch_size(900)
1392 .with_expected_output_sizes(vec![900, 900, 200])
1393 .run();
1394
1395 expect_buffer_layout(
1397 col_as_string_view("c0", output_batches.first().unwrap()),
1398 vec![
1399 ExpectedLayout {
1400 len: 8192,
1401 capacity: 8192,
1402 },
1403 ExpectedLayout {
1404 len: 16384,
1405 capacity: 16384,
1406 },
1407 ExpectedLayout {
1408 len: 4224,
1409 capacity: 32768,
1410 },
1411 ],
1412 );
1413 }
1414
1415 #[test]
1416 #[cfg_attr(miri, ignore)] fn test_string_view_large_small() {
1418 let mixed_batch = stringview_batch_repeated(
1420 200,
1421 [Some("This string is 28 bytes long"), Some("small string")],
1422 );
1423 let all_large = stringview_batch_repeated(
1425 50,
1426 [Some(
1427 "This buffer has only large strings in it so there are no buffer copies",
1428 )],
1429 );
1430
1431 let output_batches = Test::new("coalesce_string_view_large_small")
1432 .with_batch(mixed_batch.clone())
1435 .with_batch(mixed_batch.clone())
1436 .with_batch(all_large.clone())
1437 .with_batch(mixed_batch.clone())
1438 .with_batch(all_large.clone())
1439 .with_batch(mixed_batch.clone())
1440 .with_batch(mixed_batch.clone())
1441 .with_batch(all_large.clone())
1442 .with_batch(mixed_batch.clone())
1443 .with_batch(all_large.clone())
1444 .with_batch_size(8000)
1445 .with_expected_output_sizes(vec![1400])
1446 .run();
1447
1448 expect_buffer_layout(
1449 col_as_string_view("c0", output_batches.first().unwrap()),
1450 vec![
1451 ExpectedLayout {
1452 len: 8190,
1453 capacity: 8192,
1454 },
1455 ExpectedLayout {
1456 len: 16366,
1457 capacity: 16384,
1458 },
1459 ExpectedLayout {
1460 len: 6244,
1461 capacity: 32768,
1462 },
1463 ],
1464 );
1465 }
1466
1467 #[test]
1468 #[cfg_attr(miri, ignore)] fn test_binary_view() {
1470 let values: Vec<Option<&[u8]>> = vec![
1471 Some(b"foo"),
1472 None,
1473 Some(b"A longer string that is more than 12 bytes"),
1474 ];
1475
1476 let binary_view =
1477 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1478 let batch =
1479 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1480
1481 Test::new("coalesce_binary_view")
1482 .with_batch(batch.clone())
1483 .with_batch(batch.clone())
1484 .with_batch_size(512)
1485 .with_expected_output_sizes(vec![512, 512, 512, 464])
1486 .run();
1487 }
1488
1489 #[test]
1490 fn test_binary_view_filtered() {
1491 let values: Vec<Option<&[u8]>> = vec![
1492 Some(b"foo"),
1493 None,
1494 Some(b"A longer string that is more than 12 bytes"),
1495 ];
1496
1497 let binary_view =
1498 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1499 let batch =
1500 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1501 let filter = sparse_filter(1000);
1502
1503 Test::new("coalesce_binary_view_filtered")
1504 .with_batch(batch.clone())
1505 .with_filter(filter.clone())
1506 .with_batch(batch)
1507 .with_filter(filter)
1508 .with_batch_size(256)
1509 .with_expected_output_sizes(vec![250])
1510 .run();
1511 }
1512
1513 #[test]
1514 fn test_binary_view_filtered_inline() {
1515 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1516
1517 let binary_view =
1518 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1519 let batch =
1520 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1521 let filter = sparse_filter(1000);
1522
1523 Test::new("coalesce_binary_view_filtered_inline")
1524 .with_batch(batch.clone())
1525 .with_filter(filter.clone())
1526 .with_batch(batch)
1527 .with_filter(filter)
1528 .with_batch_size(300)
1529 .with_expected_output_sizes(vec![250])
1530 .run();
1531 }
1532
1533 #[test]
1534 fn test_string_view_filtered_inline() {
1535 let values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1536
1537 let string_view =
1538 StringViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1539 let batch =
1540 RecordBatch::try_from_iter(vec![("c0", Arc::new(string_view) as ArrayRef)]).unwrap();
1541 let filter = sparse_filter(1000);
1542
1543 Test::new("coalesce_string_view_filtered_inline")
1544 .with_batch(batch.clone())
1545 .with_filter(filter.clone())
1546 .with_batch(batch)
1547 .with_filter(filter)
1548 .with_batch_size(300)
1549 .with_expected_output_sizes(vec![250])
1550 .run();
1551 }
1552
1553 #[test]
1554 fn test_mixed_inline_binary_view_filtered() {
1555 let int_values =
1556 Int32Array::from_iter((0..1000).map(|v| if v % 5 == 0 { None } else { Some(v) }));
1557 let float_values = arrow_array::Float64Array::from_iter((0..1000).map(|v| Some(v as f64)));
1558 let binary_values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1559 let binary_view = BinaryViewArray::from_iter(
1560 std::iter::repeat(binary_values.iter()).flatten().take(1000),
1561 );
1562
1563 let batch = RecordBatch::try_from_iter(vec![
1564 ("i", Arc::new(int_values) as ArrayRef),
1565 ("f", Arc::new(float_values) as ArrayRef),
1566 ("b", Arc::new(binary_view) as ArrayRef),
1567 ])
1568 .unwrap();
1569
1570 let filter = sparse_filter(1000);
1571
1572 Test::new("coalesce_mixed_inline_binary_view_filtered")
1573 .with_batch(batch.clone())
1574 .with_filter(filter.clone())
1575 .with_batch(batch)
1576 .with_filter(filter)
1577 .with_batch_size(300)
1578 .with_expected_output_sizes(vec![250])
1579 .run();
1580 }
1581
1582 #[test]
1583 fn test_mixed_inline_string_view_filtered() {
1584 let int_values =
1585 Int32Array::from_iter((0..1000).map(|v| if v % 5 == 0 { None } else { Some(v) }));
1586 let float_values = arrow_array::Float64Array::from_iter((0..1000).map(|v| Some(v as f64)));
1587 let string_values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1588 let string_view = StringViewArray::from_iter(
1589 std::iter::repeat(string_values.iter()).flatten().take(1000),
1590 );
1591
1592 let batch = RecordBatch::try_from_iter(vec![
1593 ("i", Arc::new(int_values) as ArrayRef),
1594 ("f", Arc::new(float_values) as ArrayRef),
1595 ("s", Arc::new(string_view) as ArrayRef),
1596 ])
1597 .unwrap();
1598
1599 let filter = sparse_filter(1000);
1600
1601 Test::new("coalesce_mixed_inline_string_view_filtered")
1602 .with_batch(batch.clone())
1603 .with_filter(filter.clone())
1604 .with_batch(batch)
1605 .with_filter(filter)
1606 .with_batch_size(300)
1607 .with_expected_output_sizes(vec![250])
1608 .run();
1609 }
1610
1611 #[test]
1612 fn test_inline_binary_view_sparse() {
1613 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1615 let binary_view =
1616 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1617 let batch =
1618 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1619 let filter = very_sparse_filter(1000);
1620
1621 Test::new("inline_binary_view_sparse")
1622 .with_batch(batch.clone())
1623 .with_filter(filter.clone())
1624 .with_batch(batch)
1625 .with_filter(filter)
1626 .with_batch_size(1024)
1627 .with_expected_output_sizes(vec![100])
1628 .run();
1629 }
1630
1631 #[test]
1632 fn test_inline_string_view_sparse() {
1633 let values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1634 let string_view =
1635 StringViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1636 let batch =
1637 RecordBatch::try_from_iter(vec![("c0", Arc::new(string_view) as ArrayRef)]).unwrap();
1638 let filter = very_sparse_filter(1000);
1639
1640 Test::new("inline_string_view_sparse")
1641 .with_batch(batch.clone())
1642 .with_filter(filter.clone())
1643 .with_batch(batch)
1644 .with_filter(filter)
1645 .with_batch_size(1024)
1646 .with_expected_output_sizes(vec![100])
1647 .run();
1648 }
1649
1650 #[test]
1651 fn test_inline_mixed_sparse() {
1652 let int_values =
1655 Int32Array::from_iter((0..1000).map(|v| if v % 5 == 0 { None } else { Some(v) }));
1656 let float_values = arrow_array::Float64Array::from_iter((0..1000).map(|v| Some(v as f64)));
1657 let string_values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1658 let string_view = StringViewArray::from_iter(
1659 std::iter::repeat(string_values.iter()).flatten().take(1000),
1660 );
1661 let binary_values: Vec<Option<&[u8]>> = vec![Some(b"x"), None, Some(b"abcdef")];
1662 let binary_view = BinaryViewArray::from_iter(
1663 std::iter::repeat(binary_values.iter()).flatten().take(1000),
1664 );
1665
1666 let batch = RecordBatch::try_from_iter(vec![
1667 ("i", Arc::new(int_values) as ArrayRef),
1668 ("f", Arc::new(float_values) as ArrayRef),
1669 ("s", Arc::new(string_view) as ArrayRef),
1670 ("b", Arc::new(binary_view) as ArrayRef),
1671 ])
1672 .unwrap();
1673 let filter = very_sparse_filter(1000);
1674
1675 Test::new("inline_mixed_sparse")
1676 .with_batch(batch.clone())
1677 .with_filter(filter.clone())
1678 .with_batch(batch)
1679 .with_filter(filter)
1680 .with_batch_size(1024)
1681 .with_expected_output_sizes(vec![100])
1682 .run();
1683 }
1684
1685 #[test]
1686 fn test_inline_crosses_target_batch_size() {
1687 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1691 let make_batch = || {
1692 let binary_view =
1693 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1694 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap()
1695 };
1696 let filter = very_sparse_filter(1000);
1697
1698 Test::new("inline_crosses_target_batch_size")
1699 .with_batch(make_batch())
1700 .with_filter(filter.clone())
1701 .with_batch(make_batch())
1702 .with_filter(filter.clone())
1703 .with_batch(make_batch())
1704 .with_filter(filter)
1705 .with_batch_size(100)
1706 .with_expected_output_sizes(vec![100, 50])
1707 .run();
1708 }
1709
1710 #[test]
1711 fn test_inline_filter_rejects_filter_longer_than_batch() {
1712 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), Some(b"bar")];
1713 let binary_view = BinaryViewArray::from_iter(values);
1714 let batch =
1715 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1716 let filter = BooleanArray::from(vec![true, false, true]);
1717
1718 let mut coalescer = BatchCoalescer::new(batch.schema(), 100);
1719 let result = coalescer.push_batch_with_filter(batch, &filter);
1720 assert!(result.is_err());
1721 let err = result.unwrap_err().to_string();
1722 assert!(
1723 err.contains("Filter predicate of length 3 is larger than target array of length 2"),
1724 "unexpected error: {err}"
1725 );
1726 }
1727
1728 #[test]
1729 fn test_mixed_boolean_inline_string_view_filtered() {
1730 let bool_values = BooleanArray::from_iter((0..1000).map(|v| Some(v % 3 == 0)));
1731 let string_values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1732 let string_view = StringViewArray::from_iter(
1733 std::iter::repeat(string_values.iter()).flatten().take(1000),
1734 );
1735
1736 let batch = RecordBatch::try_from_iter(vec![
1737 ("b", Arc::new(bool_values) as ArrayRef),
1738 ("s", Arc::new(string_view) as ArrayRef),
1739 ])
1740 .unwrap();
1741
1742 let filter = sparse_filter(1000);
1743
1744 Test::new("coalesce_mixed_boolean_inline_string_view_filtered")
1745 .with_batch(batch.clone())
1746 .with_filter(filter.clone())
1747 .with_batch(batch)
1748 .with_filter(filter)
1749 .with_batch_size(300)
1750 .with_expected_output_sizes(vec![250])
1751 .run();
1752 }
1753
1754 #[test]
1755 fn test_filter_fast_path_schema_capability() {
1756 let supported = Arc::new(Schema::new(vec![
1757 Field::new("primitive", DataType::UInt32, false),
1758 Field::new("utf8_view", DataType::Utf8View, true),
1759 Field::new("binary_view", DataType::BinaryView, true),
1760 ]));
1761 let coalescer = BatchCoalescer::new(supported, 100);
1762 assert!(!coalescer.has_non_specialized_filter_columns);
1763
1764 let utf8 = Arc::new(Schema::new(vec![Field::new("utf8", DataType::Utf8, true)]));
1765 let coalescer = BatchCoalescer::new(utf8, 100);
1766 assert!(coalescer.has_non_specialized_filter_columns);
1767
1768 let boolean = Arc::new(Schema::new(vec![Field::new(
1769 "boolean",
1770 DataType::Boolean,
1771 true,
1772 )]));
1773 let coalescer = BatchCoalescer::new(boolean, 100);
1774 assert!(coalescer.has_non_specialized_filter_columns);
1775 }
1776
1777 #[derive(Debug, Clone, PartialEq)]
1778 struct ExpectedLayout {
1779 len: usize,
1780 capacity: usize,
1781 }
1782
1783 fn expect_buffer_layout(array: &StringViewArray, expected: Vec<ExpectedLayout>) {
1785 let actual = array
1786 .data_buffers()
1787 .iter()
1788 .map(|b| ExpectedLayout {
1789 len: b.len(),
1790 capacity: b.capacity(),
1791 })
1792 .collect::<Vec<_>>();
1793
1794 assert_eq!(
1795 actual, expected,
1796 "Expected buffer layout {expected:#?} but got {actual:#?}"
1797 );
1798 }
1799
1800 #[derive(Debug, Clone)]
1807 struct Test {
1808 name: String,
1810 input_batches: Vec<RecordBatch>,
1812 filters: Vec<BooleanArray>,
1817 schema: Option<SchemaRef>,
1819 expected_output_sizes: Vec<usize>,
1821 target_batch_size: usize,
1823 }
1824
1825 impl Default for Test {
1826 fn default() -> Self {
1827 Self {
1828 name: String::new(),
1829 input_batches: vec![],
1830 filters: vec![],
1831 schema: None,
1832 expected_output_sizes: vec![],
1833 target_batch_size: 1024,
1834 }
1835 }
1836 }
1837
1838 impl Test {
1839 fn new(name: impl Into<String>) -> Self {
1840 Self {
1841 name: name.into(),
1842 ..Self::default()
1843 }
1844 }
1845
1846 fn with_description(mut self, description: &str) -> Self {
1848 self.name.push_str(": ");
1849 self.name.push_str(description);
1850 self
1851 }
1852
1853 fn with_batch_size(mut self, target_batch_size: usize) -> Self {
1855 self.target_batch_size = target_batch_size;
1856 self
1857 }
1858
1859 fn with_batch(mut self, batch: RecordBatch) -> Self {
1861 self.input_batches.push(batch);
1862 self
1863 }
1864
1865 fn with_filter(mut self, filter: BooleanArray) -> Self {
1867 self.filters.push(filter);
1868 self
1869 }
1870
1871 fn with_batches(mut self, batches: impl IntoIterator<Item = RecordBatch>) -> Self {
1873 self.input_batches = batches.into_iter().collect();
1874 self
1875 }
1876
1877 fn with_schema(mut self, schema: SchemaRef) -> Self {
1879 self.schema = Some(schema);
1880 self
1881 }
1882
1883 fn with_expected_output_sizes(mut self, sizes: impl IntoIterator<Item = usize>) -> Self {
1885 self.expected_output_sizes.extend(sizes);
1886 self
1887 }
1888
1889 fn run(self) -> Vec<RecordBatch> {
1893 let mut extra_tests = vec![];
1898 extra_tests.push(self.clone().make_half_non_nullable());
1899 extra_tests.push(self.clone().insert_empty_batches());
1900 let single_column_tests = self.make_single_column_tests();
1901 for test in single_column_tests {
1902 extra_tests.push(test.clone().make_half_non_nullable());
1903 extra_tests.push(test);
1904 }
1905
1906 let results = self.run_inner();
1909 for extra in extra_tests {
1911 extra.run_inner();
1912 }
1913
1914 results
1915 }
1916
1917 fn run_inner(self) -> Vec<RecordBatch> {
1919 let expected_output = self.expected_output();
1920 let schema = self.schema();
1921
1922 let Self {
1923 name,
1924 input_batches,
1925 filters,
1926 schema: _,
1927 target_batch_size,
1928 expected_output_sizes,
1929 } = self;
1930
1931 println!("Running test '{name}'");
1932
1933 let had_input = input_batches.iter().any(|b| b.num_rows() > 0);
1934
1935 let mut coalescer = BatchCoalescer::new(Arc::clone(&schema), target_batch_size);
1936
1937 let mut filters = filters.into_iter();
1939 for batch in input_batches {
1940 if let Some(filter) = filters.next() {
1941 coalescer.push_batch_with_filter(batch, &filter).unwrap();
1942 } else {
1943 coalescer.push_batch(batch).unwrap();
1944 }
1945 }
1946 assert_eq!(schema, coalescer.schema());
1947
1948 if had_input {
1949 assert!(!coalescer.is_empty(), "Coalescer should not be empty");
1950 } else {
1951 assert!(coalescer.is_empty(), "Coalescer should be empty");
1952 }
1953
1954 coalescer.finish_buffered_batch().unwrap();
1955 if had_input {
1956 assert!(
1957 coalescer.has_completed_batch(),
1958 "Coalescer should have completed batches"
1959 );
1960 }
1961
1962 let mut output_batches = vec![];
1963 while let Some(batch) = coalescer.next_completed_batch() {
1964 output_batches.push(batch);
1965 }
1966
1967 let mut starting_idx = 0;
1969 let actual_output_sizes: Vec<usize> =
1970 output_batches.iter().map(|b| b.num_rows()).collect();
1971 assert_eq!(
1972 expected_output_sizes, actual_output_sizes,
1973 "Unexpected number of rows in output batches\n\
1974 Expected\n{expected_output_sizes:#?}\nActual:{actual_output_sizes:#?}"
1975 );
1976 let iter = expected_output_sizes
1977 .iter()
1978 .zip(output_batches.iter())
1979 .enumerate();
1980
1981 for (i, (expected_size, batch)) in iter {
1983 let expected_batch = expected_output.slice(starting_idx, *expected_size);
1986 let expected_batch = normalize_batch(expected_batch);
1987 let batch = normalize_batch(batch.clone());
1988 assert_eq!(
1989 expected_batch, batch,
1990 "Unexpected content in batch {i}:\
1991 \n\nExpected:\n{expected_batch:#?}\n\nActual:\n{batch:#?}"
1992 );
1993 starting_idx += *expected_size;
1994 }
1995 output_batches
1996 }
1997
1998 fn schema(&self) -> SchemaRef {
2001 self.schema
2002 .clone()
2003 .unwrap_or_else(|| Arc::clone(&self.input_batches[0].schema()))
2004 }
2005
2006 fn expected_output(&self) -> RecordBatch {
2008 let schema = self.schema();
2009 if self.filters.is_empty() {
2010 return concat_batches(&schema, &self.input_batches).unwrap();
2011 }
2012
2013 let mut filters = self.filters.iter();
2014 let filtered_batches = self
2015 .input_batches
2016 .iter()
2017 .map(|batch| {
2018 if let Some(filter) = filters.next() {
2019 filter_record_batch(batch, filter).unwrap()
2020 } else {
2021 batch.clone()
2022 }
2023 })
2024 .collect::<Vec<_>>();
2025 concat_batches(&schema, &filtered_batches).unwrap()
2026 }
2027
2028 fn make_half_non_nullable(mut self) -> Self {
2031 self.input_batches = self
2033 .input_batches
2034 .iter()
2035 .enumerate()
2036 .map(|(i, batch)| {
2037 if i % 2 == 1 {
2038 batch.clone()
2039 } else {
2040 Self::remove_nulls_from_batch(batch)
2041 }
2042 })
2043 .collect();
2044 self.with_description("non-nullable")
2045 }
2046
2047 fn insert_empty_batches(mut self) -> Self {
2049 let empty_batch = RecordBatch::new_empty(self.schema());
2050 self.input_batches = self
2051 .input_batches
2052 .into_iter()
2053 .flat_map(|batch| [empty_batch.clone(), batch])
2054 .collect();
2055 let empty_filters = BooleanArray::builder(0).finish();
2056 self.filters = self
2057 .filters
2058 .into_iter()
2059 .flat_map(|filter| [empty_filters.clone(), filter])
2060 .collect();
2061 self.with_description("empty batches inserted")
2062 }
2063
2064 fn remove_nulls_from_batch(batch: &RecordBatch) -> RecordBatch {
2066 let new_columns = batch
2067 .columns()
2068 .iter()
2069 .map(Self::remove_nulls_from_array)
2070 .collect::<Vec<_>>();
2071 let options = RecordBatchOptions::new().with_row_count(Some(batch.num_rows()));
2072 RecordBatch::try_new_with_options(batch.schema(), new_columns, &options).unwrap()
2073 }
2074
2075 fn remove_nulls_from_array(array: &ArrayRef) -> ArrayRef {
2076 make_array(array.to_data().into_builder().nulls(None).build().unwrap())
2077 }
2078
2079 fn make_single_column_tests(&self) -> Vec<Self> {
2085 let original_schema = self.schema();
2086 let mut new_tests = vec![];
2087 for column in original_schema.fields() {
2088 let single_column_schema = Arc::new(Schema::new(vec![column.clone()]));
2089
2090 let single_column_batches = self.input_batches.iter().map(|batch| {
2091 let single_column = batch.column_by_name(column.name()).unwrap();
2092 RecordBatch::try_new(
2093 Arc::clone(&single_column_schema),
2094 vec![single_column.clone()],
2095 )
2096 .unwrap()
2097 });
2098
2099 let single_column_test = self
2100 .clone()
2101 .with_schema(Arc::clone(&single_column_schema))
2102 .with_batches(single_column_batches)
2103 .with_description("single column")
2104 .with_description(column.name());
2105
2106 new_tests.push(single_column_test);
2107 }
2108 new_tests
2109 }
2110 }
2111
2112 fn uint32_batch<T: std::iter::Iterator<Item = u32>>(range: T) -> RecordBatch {
2115 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, true)]));
2116
2117 let array = UInt32Array::from_iter(range.map(|i| if i % 3 == 0 { None } else { Some(i) }));
2118 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2119 }
2120
2121 fn uint32_batch_non_null<T: std::iter::Iterator<Item = u32>>(range: T) -> RecordBatch {
2123 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, false)]));
2124
2125 let array = UInt32Array::from_iter_values(range);
2126 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2127 }
2128
2129 fn uint64_batch_non_null<T: std::iter::Iterator<Item = u64>>(range: T) -> RecordBatch {
2131 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt64, false)]));
2132
2133 let array = UInt64Array::from_iter_values(range);
2134 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2135 }
2136
2137 fn utf8_batch(range: Range<u32>) -> RecordBatch {
2140 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::Utf8, true)]));
2141
2142 let array = StringArray::from_iter(range.map(|i| {
2143 if i % 3 == 0 {
2144 None
2145 } else {
2146 Some(format!("value{i}"))
2147 }
2148 }));
2149
2150 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2151 }
2152
2153 fn stringview_batch<'a>(values: impl IntoIterator<Item = Option<&'a str>>) -> RecordBatch {
2155 let schema = Arc::new(Schema::new(vec![Field::new(
2156 "c0",
2157 DataType::Utf8View,
2158 false,
2159 )]));
2160
2161 let array = StringViewArray::from_iter(values);
2162 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2163 }
2164
2165 fn stringview_batch_repeated<'a>(
2168 num_rows: usize,
2169 values: impl IntoIterator<Item = Option<&'a str>>,
2170 ) -> RecordBatch {
2171 let schema = Arc::new(Schema::new(vec![Field::new(
2172 "c0",
2173 DataType::Utf8View,
2174 true,
2175 )]));
2176
2177 let values: Vec<_> = values.into_iter().collect();
2179 let values_iter = std::iter::repeat(values.iter())
2180 .flatten()
2181 .copied()
2182 .take(num_rows);
2183
2184 let mut builder = StringViewBuilder::with_capacity(100).with_fixed_block_size(8192);
2185 for val in values_iter {
2186 builder.append_option(val);
2187 }
2188
2189 let array = builder.finish();
2190 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2191 }
2192
2193 fn multi_column_batch(range: Range<i32>) -> RecordBatch {
2195 let int64_array = Int64Array::from_iter(
2196 range
2197 .clone()
2198 .map(|v| if v % 5 == 0 { None } else { Some(v as i64) }),
2199 );
2200 let string_view_array = StringViewArray::from_iter(range.clone().map(|v| {
2201 if v % 5 == 0 {
2202 None
2203 } else if v % 7 == 0 {
2204 Some(format!("This is a string longer than 12 bytes{v}"))
2205 } else {
2206 Some(format!("Short {v}"))
2207 }
2208 }));
2209 let string_array = StringArray::from_iter(range.clone().map(|v| {
2210 if v % 11 == 0 {
2211 None
2212 } else {
2213 Some(format!("Value {v}"))
2214 }
2215 }));
2216 let timestamp_array = TimestampNanosecondArray::from_iter(range.map(|v| {
2217 if v % 3 == 0 {
2218 None
2219 } else {
2220 Some(v as i64 * 1000) }
2222 }))
2223 .with_timezone("America/New_York");
2224
2225 RecordBatch::try_from_iter(vec![
2226 ("int64", Arc::new(int64_array) as ArrayRef),
2227 ("stringview", Arc::new(string_view_array) as ArrayRef),
2228 ("string", Arc::new(string_array) as ArrayRef),
2229 ("timestamp", Arc::new(timestamp_array) as ArrayRef),
2230 ])
2231 .unwrap()
2232 }
2233
2234 #[derive(Debug)]
2240 struct RandomFilterBuilder {
2241 num_rows: usize,
2243 selectivity: f64,
2246 seed: u64,
2249 }
2250 impl RandomFilterBuilder {
2251 fn next_filter(&mut self) -> BooleanArray {
2254 assert!(self.selectivity >= 0.0 && self.selectivity <= 1.0);
2255 let mut rng = rand::rngs::StdRng::seed_from_u64(self.seed);
2256 self.seed += 1;
2257 BooleanArray::from_iter(
2258 (0..self.num_rows)
2259 .map(|_| rng.random_bool(self.selectivity))
2260 .map(Some),
2261 )
2262 }
2263 }
2264
2265 fn col_as_string_view<'b>(name: &str, batch: &'b RecordBatch) -> &'b StringViewArray {
2267 batch
2268 .column_by_name(name)
2269 .expect("column not found")
2270 .as_string_view_opt()
2271 .expect("column is not a string view")
2272 }
2273
2274 fn sparse_filter(len: usize) -> BooleanArray {
2276 BooleanArray::from_iter((0..len).map(|idx| Some(idx % 8 == 0)))
2277 }
2278
2279 fn very_sparse_filter(len: usize) -> BooleanArray {
2283 BooleanArray::from_iter((0..len).map(|idx| Some(idx % 20 == 0)))
2284 }
2285
2286 fn normalize_batch(batch: RecordBatch) -> RecordBatch {
2289 let (schema, mut columns, row_count) = batch.into_parts();
2291
2292 for column in &mut columns {
2293 if let Some(string_view) = column.as_string_view_opt() {
2294 let mut builder = StringViewBuilder::new();
2297 for s in string_view {
2298 builder.append_option(s);
2299 }
2300 *column = Arc::new(builder.finish());
2301 continue;
2302 }
2303
2304 if let Some(binary_view) = column.as_binary_view_opt() {
2305 *column = Arc::new(BinaryViewArray::from_iter(binary_view.iter()));
2306 }
2307 }
2308
2309 let options = RecordBatchOptions::new().with_row_count(Some(row_count));
2310 RecordBatch::try_new_with_options(schema, columns, &options).unwrap()
2311 }
2312
2313 fn create_test_batch(num_rows: usize) -> RecordBatch {
2315 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)]));
2316 let array = Int32Array::from_iter_values(0..num_rows as i32);
2317 RecordBatch::try_new(schema, vec![Arc::new(array)]).unwrap()
2318 }
2319 #[test]
2320 fn test_biggest_coalesce_batch_size_none_default() {
2321 let mut coalescer = BatchCoalescer::new(
2323 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2324 100,
2325 );
2326
2327 let large_batch = create_test_batch(1000);
2329 coalescer.push_batch(large_batch).unwrap();
2330
2331 let mut output_batches = vec![];
2333 while let Some(batch) = coalescer.next_completed_batch() {
2334 output_batches.push(batch);
2335 }
2336
2337 coalescer.finish_buffered_batch().unwrap();
2338 while let Some(batch) = coalescer.next_completed_batch() {
2339 output_batches.push(batch);
2340 }
2341
2342 assert_eq!(output_batches.len(), 10);
2344 for batch in output_batches {
2345 assert_eq!(batch.num_rows(), 100);
2346 }
2347 }
2348
2349 #[test]
2350 fn test_biggest_coalesce_batch_size_bypass_large_batch() {
2351 let mut coalescer = BatchCoalescer::new(
2353 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2354 100,
2355 );
2356 coalescer.set_biggest_coalesce_batch_size(Some(500));
2357
2358 let large_batch = create_test_batch(1000);
2360 coalescer.push_batch(large_batch.clone()).unwrap();
2361
2362 assert!(coalescer.has_completed_batch());
2364 let output_batch = coalescer.next_completed_batch().unwrap();
2365 assert_eq!(output_batch.num_rows(), 1000);
2366
2367 assert!(!coalescer.has_completed_batch());
2369 assert_eq!(coalescer.get_buffered_rows(), 0);
2370 }
2371
2372 #[test]
2373 fn test_biggest_coalesce_batch_size_coalesce_small_batch() {
2374 let mut coalescer = BatchCoalescer::new(
2376 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2377 100,
2378 )
2379 .with_biggest_coalesce_batch_size(Some(500));
2380
2381 let small_batch = create_test_batch(50);
2383 coalescer.push_batch(small_batch.clone()).unwrap();
2384
2385 assert!(!coalescer.has_completed_batch());
2387 assert_eq!(coalescer.get_buffered_rows(), 50);
2388
2389 coalescer.push_batch(small_batch).unwrap();
2391
2392 assert!(coalescer.has_completed_batch());
2394 let output_batch = coalescer.next_completed_batch().unwrap();
2395 let size = output_batch
2396 .column(0)
2397 .as_primitive::<Int32Type>()
2398 .get_buffer_memory_size();
2399 assert_eq!(size, 400); assert_eq!(output_batch.num_rows(), 100);
2401
2402 assert_eq!(coalescer.get_buffered_rows(), 0);
2403 }
2404
2405 #[test]
2406 fn test_biggest_coalesce_batch_size_equal_boundary() {
2407 let mut coalescer = BatchCoalescer::new(
2409 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2410 100,
2411 );
2412 coalescer.set_biggest_coalesce_batch_size(Some(500));
2413
2414 let boundary_batch = create_test_batch(500);
2416 coalescer.push_batch(boundary_batch).unwrap();
2417
2418 let mut output_count = 0;
2420 while coalescer.next_completed_batch().is_some() {
2421 output_count += 1;
2422 }
2423
2424 coalescer.finish_buffered_batch().unwrap();
2425 while coalescer.next_completed_batch().is_some() {
2426 output_count += 1;
2427 }
2428
2429 assert_eq!(output_count, 5);
2431 }
2432
2433 #[test]
2434 fn test_biggest_coalesce_batch_size_first_large_then_consecutive_bypass() {
2435 let mut coalescer = BatchCoalescer::new(
2438 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2439 100,
2440 );
2441 coalescer.set_biggest_coalesce_batch_size(Some(200));
2442
2443 let small_batch = create_test_batch(50);
2444
2445 coalescer.push_batch(small_batch).unwrap();
2447 assert_eq!(coalescer.get_buffered_rows(), 50);
2448 assert!(!coalescer.has_completed_batch());
2449
2450 let large_batch1 = create_test_batch(250);
2452 coalescer.push_batch(large_batch1).unwrap();
2453
2454 let mut completed_batches = vec![];
2456 while let Some(batch) = coalescer.next_completed_batch() {
2457 completed_batches.push(batch);
2458 }
2459 assert_eq!(completed_batches.len(), 3);
2460 assert_eq!(coalescer.get_buffered_rows(), 0);
2461
2462 let large_batch2 = create_test_batch(300);
2464 let large_batch3 = create_test_batch(400);
2465
2466 coalescer.push_batch(large_batch2).unwrap();
2468 assert!(coalescer.has_completed_batch());
2469 let output = coalescer.next_completed_batch().unwrap();
2470 assert_eq!(output.num_rows(), 300); assert_eq!(coalescer.get_buffered_rows(), 0);
2472
2473 coalescer.push_batch(large_batch3).unwrap();
2475 assert!(coalescer.has_completed_batch());
2476 let output = coalescer.next_completed_batch().unwrap();
2477 assert_eq!(output.num_rows(), 400); assert_eq!(coalescer.get_buffered_rows(), 0);
2479 }
2480
2481 #[test]
2482 fn test_biggest_coalesce_batch_size_empty_batch() {
2483 let mut coalescer = BatchCoalescer::new(
2485 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2486 100,
2487 );
2488 coalescer.set_biggest_coalesce_batch_size(Some(50));
2489
2490 let empty_batch = create_test_batch(0);
2491 coalescer.push_batch(empty_batch).unwrap();
2492
2493 assert!(!coalescer.has_completed_batch());
2495 assert_eq!(coalescer.get_buffered_rows(), 0);
2496 }
2497
2498 #[test]
2499 fn test_biggest_coalesce_batch_size_with_buffered_data_no_bypass() {
2500 let mut coalescer = BatchCoalescer::new(
2502 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2503 100,
2504 );
2505 coalescer.set_biggest_coalesce_batch_size(Some(200));
2506
2507 let small_batch = create_test_batch(30);
2509 coalescer.push_batch(small_batch.clone()).unwrap();
2510 coalescer.push_batch(small_batch).unwrap();
2511 assert_eq!(coalescer.get_buffered_rows(), 60);
2512
2513 let large_batch = create_test_batch(250);
2515 coalescer.push_batch(large_batch).unwrap();
2516
2517 let mut completed_batches = vec![];
2522 while let Some(batch) = coalescer.next_completed_batch() {
2523 completed_batches.push(batch);
2524 }
2525
2526 assert_eq!(completed_batches.len(), 3);
2527 for batch in &completed_batches {
2528 assert_eq!(batch.num_rows(), 100);
2529 }
2530 assert_eq!(coalescer.get_buffered_rows(), 10);
2531 }
2532
2533 #[test]
2534 fn test_biggest_coalesce_batch_size_zero_limit() {
2535 let mut coalescer = BatchCoalescer::new(
2537 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2538 100,
2539 );
2540 coalescer.set_biggest_coalesce_batch_size(Some(0));
2541
2542 let tiny_batch = create_test_batch(1);
2544 coalescer.push_batch(tiny_batch).unwrap();
2545
2546 assert!(coalescer.has_completed_batch());
2547 let output = coalescer.next_completed_batch().unwrap();
2548 assert_eq!(output.num_rows(), 1);
2549 }
2550
2551 #[test]
2552 fn test_biggest_coalesce_batch_size_bypass_only_when_no_buffer() {
2553 let mut coalescer = BatchCoalescer::new(
2555 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2556 100,
2557 );
2558 coalescer.set_biggest_coalesce_batch_size(Some(200));
2559
2560 let large_batch = create_test_batch(300);
2562 coalescer.push_batch(large_batch.clone()).unwrap();
2563
2564 assert!(coalescer.has_completed_batch());
2565 let output = coalescer.next_completed_batch().unwrap();
2566 assert_eq!(output.num_rows(), 300); assert_eq!(coalescer.get_buffered_rows(), 0);
2568
2569 let small_batch = create_test_batch(50);
2571 coalescer.push_batch(small_batch).unwrap();
2572 assert_eq!(coalescer.get_buffered_rows(), 50);
2573
2574 coalescer.push_batch(large_batch).unwrap();
2576
2577 let mut completed_batches = vec![];
2580 while let Some(batch) = coalescer.next_completed_batch() {
2581 completed_batches.push(batch);
2582 }
2583
2584 assert_eq!(completed_batches.len(), 3);
2585 for batch in &completed_batches {
2586 assert_eq!(batch.num_rows(), 100);
2587 }
2588 assert_eq!(coalescer.get_buffered_rows(), 50);
2589 }
2590
2591 #[test]
2592 fn test_biggest_coalesce_batch_size_consecutive_large_batches_scenario() {
2593 let mut coalescer = BatchCoalescer::new(
2595 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2596 1000,
2597 );
2598 coalescer.set_biggest_coalesce_batch_size(Some(500));
2599
2600 coalescer.push_batch(create_test_batch(20)).unwrap();
2602 coalescer.push_batch(create_test_batch(20)).unwrap();
2603 coalescer.push_batch(create_test_batch(30)).unwrap();
2604
2605 assert_eq!(coalescer.get_buffered_rows(), 70);
2606 assert!(!coalescer.has_completed_batch());
2607
2608 coalescer.push_batch(create_test_batch(700)).unwrap();
2610
2611 assert_eq!(coalescer.get_buffered_rows(), 770);
2613 assert!(!coalescer.has_completed_batch());
2614
2615 coalescer.push_batch(create_test_batch(600)).unwrap();
2617
2618 let mut outputs = vec![];
2620 while let Some(batch) = coalescer.next_completed_batch() {
2621 outputs.push(batch);
2622 }
2623 assert_eq!(outputs.len(), 2); assert_eq!(outputs[0].num_rows(), 770);
2625 assert_eq!(outputs[1].num_rows(), 600);
2626 assert_eq!(coalescer.get_buffered_rows(), 0);
2627
2628 let remaining_batches = [700, 900, 700, 600];
2630 for &size in &remaining_batches {
2631 coalescer.push_batch(create_test_batch(size)).unwrap();
2632
2633 assert!(coalescer.has_completed_batch());
2634 let output = coalescer.next_completed_batch().unwrap();
2635 assert_eq!(output.num_rows(), size);
2636 assert_eq!(coalescer.get_buffered_rows(), 0);
2637 }
2638 }
2639
2640 #[test]
2641 fn test_biggest_coalesce_batch_size_truly_consecutive_large_bypass() {
2642 let mut coalescer = BatchCoalescer::new(
2645 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2646 100,
2647 );
2648 coalescer.set_biggest_coalesce_batch_size(Some(200));
2649
2650 let large_batches = vec![
2652 create_test_batch(300),
2653 create_test_batch(400),
2654 create_test_batch(350),
2655 create_test_batch(500),
2656 ];
2657
2658 let mut all_outputs = vec![];
2659
2660 for (i, large_batch) in large_batches.into_iter().enumerate() {
2661 let expected_size = large_batch.num_rows();
2662
2663 assert_eq!(
2665 coalescer.get_buffered_rows(),
2666 0,
2667 "Buffer should be empty before batch {i}"
2668 );
2669
2670 coalescer.push_batch(large_batch).unwrap();
2671
2672 assert!(
2674 coalescer.has_completed_batch(),
2675 "Should have completed batch after pushing batch {i}"
2676 );
2677
2678 let output = coalescer.next_completed_batch().unwrap();
2679 assert_eq!(
2680 output.num_rows(),
2681 expected_size,
2682 "Batch {i} should have bypassed with original size"
2683 );
2684
2685 assert!(
2687 !coalescer.has_completed_batch(),
2688 "Should have no more completed batches after batch {i}"
2689 );
2690 assert_eq!(
2691 coalescer.get_buffered_rows(),
2692 0,
2693 "Buffer should be empty after batch {i}"
2694 );
2695
2696 all_outputs.push(output);
2697 }
2698
2699 assert_eq!(all_outputs.len(), 4);
2701 assert_eq!(all_outputs[0].num_rows(), 300);
2702 assert_eq!(all_outputs[1].num_rows(), 400);
2703 assert_eq!(all_outputs[2].num_rows(), 350);
2704 assert_eq!(all_outputs[3].num_rows(), 500);
2705 }
2706
2707 #[test]
2708 fn test_biggest_coalesce_batch_size_reset_consecutive_on_small_batch() {
2709 let mut coalescer = BatchCoalescer::new(
2711 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2712 100,
2713 );
2714 coalescer.set_biggest_coalesce_batch_size(Some(200));
2715
2716 coalescer.push_batch(create_test_batch(300)).unwrap();
2718 let output = coalescer.next_completed_batch().unwrap();
2719 assert_eq!(output.num_rows(), 300);
2720
2721 coalescer.push_batch(create_test_batch(400)).unwrap();
2723 let output = coalescer.next_completed_batch().unwrap();
2724 assert_eq!(output.num_rows(), 400);
2725
2726 coalescer.push_batch(create_test_batch(50)).unwrap();
2728 assert_eq!(coalescer.get_buffered_rows(), 50);
2729
2730 coalescer.push_batch(create_test_batch(350)).unwrap();
2732
2733 let mut outputs = vec![];
2735 while let Some(batch) = coalescer.next_completed_batch() {
2736 outputs.push(batch);
2737 }
2738 assert_eq!(outputs.len(), 4);
2739 for batch in outputs {
2740 assert_eq!(batch.num_rows(), 100);
2741 }
2742 assert_eq!(coalescer.get_buffered_rows(), 0);
2743 }
2744
2745 #[test]
2746 fn test_coalasce_push_batch_with_indices() {
2747 const MID_POINT: u32 = 2333;
2748 const TOTAL_ROWS: u32 = 23333;
2749 let batch1 = uint32_batch_non_null(0..MID_POINT);
2750 let batch2 = uint32_batch_non_null((MID_POINT..TOTAL_ROWS).rev());
2751
2752 let mut coalescer = BatchCoalescer::new(
2753 Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, false)])),
2754 TOTAL_ROWS as usize,
2755 );
2756 coalescer.push_batch(batch1).unwrap();
2757
2758 let rev_indices = (0..((TOTAL_ROWS - MID_POINT) as u64)).rev();
2759 let reversed_indices_batch = uint64_batch_non_null(rev_indices);
2760
2761 let reverse_indices = UInt64Array::from(reversed_indices_batch.column(0).to_data());
2762 coalescer
2763 .push_batch_with_indices(batch2, &reverse_indices)
2764 .unwrap();
2765
2766 coalescer.finish_buffered_batch().unwrap();
2767 let actual = coalescer.next_completed_batch().unwrap();
2768
2769 let expected = uint32_batch_non_null(0..TOTAL_ROWS);
2770
2771 assert_eq!(expected, actual);
2772 }
2773
2774 #[test]
2775 fn test_push_batch_interleaved() {
2776 let batch1 = record_batch!(("a", Int32, [10, 11, 12])).unwrap();
2777 let batch2 = record_batch!(("a", Int32, [20, 21, 22])).unwrap();
2778 let indices = [(0, 0), (1, 2), (0, 2), (1, 0)];
2779
2780 let mut coalescer = BatchCoalescer::new(batch1.schema(), 1000);
2781 coalescer
2782 .push_batch_interleaved(&[batch1, batch2], &indices)
2783 .unwrap();
2784 coalescer.finish_buffered_batch().unwrap();
2785
2786 let expected = record_batch!(("a", Int32, [10, 22, 12, 20])).unwrap();
2787 assert_eq!(coalescer.next_completed_batch().unwrap(), expected);
2788 }
2789
2790 #[test]
2791 fn test_push_batch_interleaved_matches_interleave_then_push() {
2792 let batch1 = record_batch!(("a", Int32, [1, 2, 3, 4])).unwrap();
2793 let batch2 = record_batch!(("a", Int32, [5, 6, 7])).unwrap();
2794 let indices = [(1, 2), (0, 0), (1, 0), (0, 3), (1, 1)];
2795
2796 let mut via_method = BatchCoalescer::new(batch1.schema(), 1000);
2797 via_method
2798 .push_batch_interleaved(&[batch1.clone(), batch2.clone()], &indices)
2799 .unwrap();
2800 via_method.finish_buffered_batch().unwrap();
2801
2802 let mut via_manual = BatchCoalescer::new(batch1.schema(), 1000);
2803 let interleaved = interleave_record_batch(&[&batch1, &batch2], &indices).unwrap();
2804 via_manual.push_batch(interleaved).unwrap();
2805 via_manual.finish_buffered_batch().unwrap();
2806
2807 assert_eq!(
2808 via_method.next_completed_batch().unwrap(),
2809 via_manual.next_completed_batch().unwrap()
2810 );
2811 }
2812
2813 #[test]
2814 fn test_push_batch_interleaved_rejects_empty_batches() {
2815 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
2816 let mut coalescer = BatchCoalescer::new(schema, 1000);
2817
2818 let err = coalescer.push_batch_interleaved(&[], &[]).unwrap_err();
2819 assert_eq!(
2820 err.to_string(),
2821 "Invalid argument error: push_batch_interleaved requires at least one input batch"
2822 );
2823 }
2824
2825 #[test]
2826 fn test_push_batch_schema_mismatch_fewer_columns() {
2827 let empty_schema = Arc::new(Schema::empty());
2829 let mut coalescer = BatchCoalescer::new(empty_schema, 100);
2830 let batch = uint32_batch(0..5);
2831 let result = coalescer.push_batch(batch);
2832 assert!(result.is_err());
2833 let err = result.unwrap_err().to_string();
2834 assert!(
2835 err.contains("Batch has 1 columns but BatchCoalescer expects 0"),
2836 "unexpected error: {err}"
2837 );
2838 }
2839
2840 #[test]
2841 fn test_push_batch_schema_mismatch_more_columns() {
2842 let schema = Arc::new(Schema::new(vec![
2844 Field::new("c0", DataType::UInt32, false),
2845 Field::new("c1", DataType::UInt32, false),
2846 ]));
2847 let mut coalescer = BatchCoalescer::new(schema, 100);
2848 let batch = uint32_batch(0..5);
2849 let result = coalescer.push_batch(batch);
2850 assert!(result.is_err());
2851 let err = result.unwrap_err().to_string();
2852 assert!(
2853 err.contains("Batch has 1 columns but BatchCoalescer expects 2"),
2854 "unexpected error: {err}"
2855 );
2856 }
2857
2858 #[test]
2859 fn test_push_batch_schema_mismatch_two_vs_zero() {
2860 let empty_schema = Arc::new(Schema::empty());
2862 let mut coalescer = BatchCoalescer::new(empty_schema, 100);
2863 let schema = Arc::new(Schema::new(vec![
2864 Field::new("c0", DataType::UInt32, false),
2865 Field::new("c1", DataType::UInt32, false),
2866 ]));
2867 let batch = RecordBatch::try_new(
2868 schema,
2869 vec![
2870 Arc::new(UInt32Array::from(vec![1, 2, 3])),
2871 Arc::new(UInt32Array::from(vec![4, 5, 6])),
2872 ],
2873 )
2874 .unwrap();
2875 let result = coalescer.push_batch(batch);
2876 assert!(result.is_err());
2877 let err = result.unwrap_err().to_string();
2878 assert!(
2879 err.contains("Batch has 2 columns but BatchCoalescer expects 0"),
2880 "unexpected error: {err}"
2881 );
2882 }
2883
2884 #[test]
2885 fn test_size_grows_with_buffering_and_shrinks_when_draining() {
2886 let batch = uint32_batch(0..8);
2887 let mut coalescer = BatchCoalescer::new(batch.schema(), 21);
2888 let baseline = coalescer.size();
2889 assert!(baseline > 0, "size includes container capacities");
2890
2891 coalescer.push_batch(batch.clone()).unwrap();
2893 assert!(coalescer.next_completed_batch().is_none());
2894 let buffered = coalescer.size();
2895 assert!(
2896 buffered > baseline,
2897 "buffering rows should grow size ({buffered} > {baseline})"
2898 );
2899
2900 for _ in 0..10 {
2902 coalescer.push_batch(batch.clone()).unwrap();
2903 }
2904 let peak = coalescer.size();
2905 assert!(peak > buffered);
2906
2907 let mut prev = peak;
2909 let mut drained_any = false;
2910 while coalescer.next_completed_batch().is_some() {
2911 drained_any = true;
2912 let now = coalescer.size();
2913 assert!(now <= prev, "size grew while draining: {now} > {prev}");
2914 prev = now;
2915 }
2916 assert!(drained_any);
2917 assert!(
2918 prev < peak,
2919 "draining completed batches should release memory"
2920 );
2921 }
2922
2923 #[test]
2924 fn test_size_string_view_buffers_released_after_drain() {
2925 let batch = stringview_batch_repeated(
2928 1000,
2929 [Some("this string is definitely longer than 12 bytes")],
2930 );
2931 let mut coalescer = BatchCoalescer::new(batch.schema(), 4096);
2932 let baseline = coalescer.size();
2933
2934 for _ in 0..20 {
2935 coalescer.push_batch(batch.clone()).unwrap();
2936 }
2937 let peak = coalescer.size();
2938 assert!(
2939 peak > baseline,
2940 "buffered string view data should grow size ({peak} > {baseline})"
2941 );
2942
2943 coalescer.finish_buffered_batch().unwrap();
2944 while coalescer.next_completed_batch().is_some() {}
2945
2946 let drained = coalescer.size();
2948 assert!(
2949 drained < peak,
2950 "draining should release buffered data ({drained} < {peak})"
2951 );
2952 }
2953
2954 #[test]
2958 fn test_size_accounting_conserved_across_cycles() {
2959 let batch = uint32_batch(0..8);
2963 let mut coalescer = BatchCoalescer::new(batch.schema(), 4096);
2964
2965 let run_cycle = |coalescer: &mut BatchCoalescer| {
2966 for _ in 0..20 {
2967 coalescer.push_batch(batch.clone()).unwrap();
2968 }
2969 coalescer.finish_buffered_batch().unwrap();
2970 let peak = coalescer.size();
2971 while coalescer.next_completed_batch().is_some() {}
2972 (peak, coalescer.size())
2973 };
2974
2975 let (peak1, drained1) = run_cycle(&mut coalescer);
2976 let (peak2, drained2) = run_cycle(&mut coalescer);
2977
2978 assert_eq!(
2979 peak1, peak2,
2980 "identical work must report identical peak size"
2981 );
2982 assert_eq!(
2983 drained1, drained2,
2984 "fully-drained size must be stable across cycles (no accounting leak)"
2985 );
2986 assert!(
2987 drained1 < peak1,
2988 "draining must release the accounted memory"
2989 );
2990 }
2991}