1use crate::filter::{FilterBuilder, FilterPredicate, FilterSelection};
24use crate::take::take_record_batch;
25use arrow_array::types::{BinaryViewType, StringViewType};
26use arrow_array::{Array, ArrayRef, BooleanArray, RecordBatch, downcast_primitive};
27use arrow_schema::{ArrowError, DataType, SchemaRef};
28use std::collections::VecDeque;
29use std::sync::Arc;
30mod byte_view;
34mod generic;
35mod primitive;
36
37use byte_view::InProgressByteViewArray;
38use generic::GenericInProgressArray;
39use primitive::InProgressPrimitiveArray;
40
41fn has_sparse_filter_copy(data_type: &DataType) -> bool {
42 data_type.is_primitive() || matches!(data_type, DataType::Utf8View | DataType::BinaryView)
43}
44
45const SPARSE_FILTER_COPY_MAX_SELECTIVITY_DENOMINATOR: usize = 16;
51
52fn should_use_sparse_filter_copy(filter_len: usize, selected_count: usize) -> bool {
53 selected_count <= filter_len / SPARSE_FILTER_COPY_MAX_SELECTIVITY_DENOMINATOR
54}
55
56#[derive(Debug)]
148pub struct BatchCoalescer {
149 schema: SchemaRef,
151 target_batch_size: usize,
155 in_progress_arrays: Vec<Box<dyn InProgressArray>>,
157 has_non_specialized_filter_columns: bool,
159 buffered_rows: usize,
161 completed: VecDeque<RecordBatch>,
163 biggest_coalesce_batch_size: Option<usize>,
165}
166
167impl BatchCoalescer {
168 pub fn new(schema: SchemaRef, target_batch_size: usize) -> Self {
176 let has_non_specialized_filter_columns = schema
177 .fields()
178 .iter()
179 .any(|field| !has_sparse_filter_copy(field.data_type()));
180 let in_progress_arrays = schema
181 .fields()
182 .iter()
183 .map(|field| create_in_progress_array(field.data_type(), target_batch_size))
184 .collect::<Vec<_>>();
185
186 Self {
187 schema,
188 target_batch_size,
189 in_progress_arrays,
190 has_non_specialized_filter_columns,
191 completed: VecDeque::with_capacity(1),
193 buffered_rows: 0,
194 biggest_coalesce_batch_size: None,
195 }
196 }
197
198 pub fn with_biggest_coalesce_batch_size(mut self, limit: Option<usize>) -> Self {
211 self.biggest_coalesce_batch_size = limit;
212 self
213 }
214
215 pub fn biggest_coalesce_batch_size(&self) -> Option<usize> {
219 self.biggest_coalesce_batch_size
220 }
221
222 pub fn set_biggest_coalesce_batch_size(&mut self, limit: Option<usize>) {
226 self.biggest_coalesce_batch_size = limit;
227 }
228
229 pub fn schema(&self) -> SchemaRef {
231 Arc::clone(&self.schema)
232 }
233
234 pub fn push_batch_with_filter(
259 &mut self,
260 batch: RecordBatch,
261 filter: &BooleanArray,
262 ) -> Result<(), ArrowError> {
263 self.push_batch_with_filtered_columns(batch, filter)
264 }
265
266 pub fn push_batch_with_indices(
290 &mut self,
291 batch: RecordBatch,
292 indices: &dyn Array,
293 ) -> Result<(), ArrowError> {
294 let taken_batch = take_record_batch(&batch, indices)?;
296 self.push_batch(taken_batch)
297 }
298
299 pub fn push_batch(&mut self, batch: RecordBatch) -> Result<(), ArrowError> {
326 let batch_size = batch.num_rows();
445
446 if batch_size == 0 {
448 return Ok(());
449 }
450
451 if let Some(limit) = self.biggest_coalesce_batch_size
453 && batch_size > limit
454 {
455 if self.buffered_rows == 0 {
458 self.completed.push_back(batch);
459 return Ok(());
460 }
461
462 if self.buffered_rows > limit {
467 self.finish_buffered_batch()?;
468 self.completed.push_back(batch);
469 return Ok(());
470 }
471
472 }
477
478 let (_schema, arrays, mut num_rows) = batch.into_parts();
479
480 if arrays.len() != self.in_progress_arrays.len() {
482 return Err(ArrowError::InvalidArgumentError(format!(
483 "Batch has {} columns but BatchCoalescer expects {}",
484 arrays.len(),
485 self.in_progress_arrays.len()
486 )));
487 }
488 self.in_progress_arrays
489 .iter_mut()
490 .zip(arrays)
491 .for_each(|(in_progress, array)| {
492 in_progress.set_source(Some(array));
493 });
494
495 let mut offset = 0;
498 while num_rows > (self.target_batch_size - self.buffered_rows) {
499 let remaining_rows = self.target_batch_size - self.buffered_rows;
500 debug_assert!(remaining_rows > 0);
501
502 for in_progress in &mut self.in_progress_arrays {
504 in_progress.copy_rows(offset, remaining_rows)?;
505 }
506
507 self.buffered_rows += remaining_rows;
508 offset += remaining_rows;
509 num_rows -= remaining_rows;
510
511 self.finish_buffered_batch()?;
512 }
513
514 self.buffered_rows += num_rows;
516 if num_rows > 0 {
517 for in_progress in &mut self.in_progress_arrays {
518 in_progress.copy_rows(offset, num_rows)?;
519 }
520 }
521
522 if self.buffered_rows >= self.target_batch_size {
524 self.finish_buffered_batch()?;
525 }
526
527 for in_progress in &mut self.in_progress_arrays {
529 in_progress.set_source(None);
530 }
531
532 Ok(())
533 }
534
535 pub fn get_buffered_rows(&self) -> usize {
537 self.buffered_rows
538 }
539
540 pub fn finish_buffered_batch(&mut self) -> Result<(), ArrowError> {
548 if self.buffered_rows == 0 {
549 return Ok(());
550 }
551 let new_arrays = self
552 .in_progress_arrays
553 .iter_mut()
554 .map(|array| array.finish())
555 .collect::<Result<Vec<_>, ArrowError>>()?;
556
557 for (array, field) in new_arrays.iter().zip(self.schema.fields().iter()) {
558 debug_assert_eq!(array.data_type(), field.data_type());
559 debug_assert_eq!(array.len(), self.buffered_rows);
560 }
561
562 let batch = unsafe {
564 RecordBatch::new_unchecked(Arc::clone(&self.schema), new_arrays, self.buffered_rows)
565 };
566
567 self.buffered_rows = 0;
568 self.completed.push_back(batch);
569 Ok(())
570 }
571
572 pub fn is_empty(&self) -> bool {
574 self.buffered_rows == 0 && self.completed.is_empty()
575 }
576
577 pub fn has_completed_batch(&self) -> bool {
579 !self.completed.is_empty()
580 }
581
582 pub fn next_completed_batch(&mut self) -> Option<RecordBatch> {
584 self.completed.pop_front()
585 }
586
587 pub fn size(&self) -> usize {
589 self.in_progress_arrays.capacity() * size_of::<Box<dyn InProgressArray>>()
590 + self
591 .in_progress_arrays
592 .iter()
593 .map(|array| array.size())
594 .sum::<usize>()
595 + self.completed.capacity() * size_of::<RecordBatch>()
596 + self
597 .completed
598 .iter()
599 .map(|batch| batch.get_array_memory_size())
600 .sum::<usize>()
601 }
602}
603
604impl BatchCoalescer {
605 fn filter_predicate_for_batch(
606 batch: &RecordBatch,
607 filter: &BooleanArray,
608 selected_count: usize,
609 ) -> FilterPredicate {
610 let mut filter_builder = FilterBuilder::new_with_count(filter, selected_count);
611 if batch.num_columns() > 1
612 || (batch.num_columns() > 0
613 && FilterBuilder::is_optimize_beneficial(batch.schema_ref().field(0).data_type()))
614 {
615 filter_builder = filter_builder.optimize();
616 }
617 filter_builder.build()
618 }
619
620 fn push_batch_with_filtered_columns(
621 &mut self,
622 batch: RecordBatch,
623 filter: &BooleanArray,
624 ) -> Result<(), ArrowError> {
625 let filter_len = filter.len();
626 let batch_num_rows = batch.num_rows();
627 let batch_num_columns = batch.num_columns();
628
629 if filter_len > batch_num_rows {
630 return Err(ArrowError::InvalidArgumentError(format!(
631 "Filter predicate of length {filter_len} is larger than target array of length {batch_num_rows}"
632 )));
633 }
634
635 let selected_count = filter.true_count();
636 if selected_count == 0 {
637 return Ok(());
638 }
639
640 if selected_count == batch_num_rows && filter_len == batch_num_rows {
641 return self.push_batch(batch);
642 }
643
644 if batch_num_columns != self.in_progress_arrays.len() {
645 return Err(ArrowError::InvalidArgumentError(format!(
646 "Batch has {} columns but BatchCoalescer expects {}",
647 batch_num_columns,
648 self.in_progress_arrays.len()
649 )));
650 }
651
652 let exceeds_coalesce_limit = self
653 .biggest_coalesce_batch_size
654 .is_some_and(|limit| selected_count > limit);
655 let does_not_fit_buffer = selected_count > self.target_batch_size - self.buffered_rows;
656 let should_materialize_filter = exceeds_coalesce_limit
657 || self.has_non_specialized_filter_columns
658 || does_not_fit_buffer
659 || !should_use_sparse_filter_copy(filter_len, selected_count);
660
661 if should_materialize_filter {
662 let predicate = Self::filter_predicate_for_batch(&batch, filter, selected_count);
664 let filtered_batch = predicate.filter_record_batch(&batch)?;
665 return self.push_batch(filtered_batch);
666 }
667
668 let predicate = Self::filter_predicate_for_batch(&batch, filter, selected_count);
669 let (_schema, arrays, _num_rows) = batch.into_parts();
670
671 for (in_progress, array) in self.in_progress_arrays.iter_mut().zip(arrays) {
672 in_progress.copy_rows_by_filter_from(array, &predicate)?;
673 }
674
675 self.buffered_rows += selected_count;
676 if self.buffered_rows >= self.target_batch_size {
677 self.finish_buffered_batch()?;
678 }
679
680 Ok(())
681 }
682}
683
684fn create_in_progress_array(data_type: &DataType, batch_size: usize) -> Box<dyn InProgressArray> {
686 macro_rules! instantiate_primitive {
687 ($t:ty) => {
688 Box::new(InProgressPrimitiveArray::<$t>::new(
689 batch_size,
690 data_type.clone(),
691 ))
692 };
693 }
694
695 downcast_primitive! {
696 data_type => (instantiate_primitive),
698 DataType::Utf8View => Box::new(InProgressByteViewArray::<StringViewType>::new(batch_size)),
699 DataType::BinaryView => {
700 Box::new(InProgressByteViewArray::<BinaryViewType>::new(batch_size))
701 }
702 _ => Box::new(GenericInProgressArray::new()),
703 }
704}
705
706trait InProgressArray: std::fmt::Debug + Send + Sync {
716 fn set_source(&mut self, source: Option<ArrayRef>);
721
722 fn copy_rows(&mut self, offset: usize, len: usize) -> Result<(), ArrowError>;
728
729 fn copy_rows_by_filter(&mut self, filter: &FilterPredicate) -> Result<(), ArrowError> {
733 self.copy_rows_by_selection(filter.selection())
734 }
735
736 fn copy_rows_by_filter_from(
743 &mut self,
744 source: ArrayRef,
745 filter: &FilterPredicate,
746 ) -> Result<(), ArrowError> {
747 self.set_source(Some(source));
748 let result = self.copy_rows_by_filter(filter);
749 self.set_source(None);
750 result
751 }
752
753 fn copy_rows_by_selection(&mut self, selection: FilterSelection<'_>) -> Result<(), ArrowError> {
759 match selection {
760 FilterSelection::None => Ok(()),
761 FilterSelection::All { len } => self.copy_rows(0, len),
762 FilterSelection::Slices(slices) => {
763 slices.try_for_each(|(start, end)| self.copy_rows(start, end - start))
764 }
765 FilterSelection::Indices(indices) => indices.try_for_each(|idx| self.copy_rows(idx, 1)),
766 }
767 }
768
769 fn finish(&mut self) -> Result<ArrayRef, ArrowError>;
771
772 fn size(&self) -> usize;
774}
775
776#[cfg(test)]
777mod tests {
778 use super::*;
779 use crate::concat::concat_batches;
780 use crate::filter::filter_record_batch;
781 use arrow_array::builder::StringViewBuilder;
782 use arrow_array::cast::AsArray;
783 use arrow_array::types::Int32Type;
784 use arrow_array::{
785 BinaryViewArray, Int32Array, Int64Array, RecordBatchOptions, StringArray, StringViewArray,
786 TimestampNanosecondArray, UInt32Array, UInt64Array, make_array,
787 };
788 use arrow_buffer::BooleanBufferBuilder;
789 use arrow_schema::{DataType, Field, Schema};
790 use rand::{RngExt, SeedableRng};
791 use std::ops::Range;
792
793 #[test]
794 fn test_coalesce() {
795 let batch = uint32_batch(0..8);
796 Test::new("coalesce")
797 .with_batches(std::iter::repeat_n(batch, 10))
798 .with_batch_size(21)
800 .with_expected_output_sizes(vec![21, 21, 21, 17])
801 .run();
802 }
803
804 #[test]
805 fn test_coalesce_one_by_one() {
806 let batch = uint32_batch(0..1); Test::new("coalesce_one_by_one")
808 .with_batches(std::iter::repeat_n(batch, 97))
809 .with_batch_size(20)
811 .with_expected_output_sizes(vec![20, 20, 20, 20, 17])
812 .run();
813 }
814
815 #[test]
816 fn test_coalesce_empty() {
817 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, false)]));
818
819 Test::new("coalesce_empty")
820 .with_batches(vec![])
821 .with_schema(schema)
822 .with_batch_size(21)
823 .with_expected_output_sizes(vec![])
824 .run();
825 }
826
827 #[test]
828 fn test_sparse_filter_copy_threshold() {
829 assert!(should_use_sparse_filter_copy(8192, 8));
830 assert!(should_use_sparse_filter_copy(8192, 81));
831 assert!(!should_use_sparse_filter_copy(8192, 819));
832 assert!(!should_use_sparse_filter_copy(8192, 6553));
833 }
834
835 #[test]
836 fn test_single_large_batch_greater_than_target() {
837 let batch = uint32_batch(0..4096);
839 Test::new("coalesce_single_large_batch_greater_than_target")
840 .with_batch(batch)
841 .with_batch_size(1000)
842 .with_expected_output_sizes(vec![1000, 1000, 1000, 1000, 96])
843 .run();
844 }
845
846 #[test]
847 fn test_single_large_batch_smaller_than_target() {
848 let batch = uint32_batch(0..4096);
850 Test::new("coalesce_single_large_batch_smaller_than_target")
851 .with_batch(batch)
852 .with_batch_size(8192)
853 .with_expected_output_sizes(vec![4096])
854 .run();
855 }
856
857 #[test]
858 fn test_single_large_batch_equal_to_target() {
859 let batch = uint32_batch(0..4096);
861 Test::new("coalesce_single_large_batch_equal_to_target")
862 .with_batch(batch)
863 .with_batch_size(4096)
864 .with_expected_output_sizes(vec![4096])
865 .run();
866 }
867
868 #[test]
869 fn test_single_large_batch_equally_divisible_in_target() {
870 let batch = uint32_batch(0..4096);
872 Test::new("coalesce_single_large_batch_equally_divisible_in_target")
873 .with_batch(batch)
874 .with_batch_size(1024)
875 .with_expected_output_sizes(vec![1024, 1024, 1024, 1024])
876 .run();
877 }
878
879 #[test]
880 fn test_empty_schema() {
881 let schema = Schema::empty();
882 let batch = RecordBatch::new_empty(schema.into());
883 Test::new("coalesce_empty_schema")
884 .with_batch(batch)
885 .with_expected_output_sizes(vec![])
886 .run();
887 }
888
889 #[test]
891 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_001() {
893 let mut filter_builder = RandomFilterBuilder {
894 num_rows: 8000,
895 selectivity: 0.001,
896 seed: 0,
897 };
898
899 let mut test = Test::new("coalesce_filtered_001");
903 for _ in 0..10 {
904 test = test
905 .with_batch(multi_column_batch(0..8000))
906 .with_filter(filter_builder.next_filter())
907 }
908 test.with_batch_size(15)
909 .with_expected_output_sizes(vec![15, 15, 15, 13])
910 .run();
911 }
912
913 #[test]
915 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_01() {
917 let mut filter_builder = RandomFilterBuilder {
918 num_rows: 8000,
919 selectivity: 0.01,
920 seed: 0,
921 };
922
923 let mut test = Test::new("coalesce_filtered_01");
927 for _ in 0..10 {
928 test = test
929 .with_batch(multi_column_batch(0..8000))
930 .with_filter(filter_builder.next_filter())
931 }
932 test.with_batch_size(128)
933 .with_expected_output_sizes(vec![128, 128, 128, 128, 128, 128, 15])
934 .run();
935 }
936
937 #[test]
939 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_10() {
941 let mut filter_builder = RandomFilterBuilder {
942 num_rows: 8000,
943 selectivity: 0.1,
944 seed: 0,
945 };
946
947 let mut test = Test::new("coalesce_filtered_10");
951 for _ in 0..10 {
952 test = test
953 .with_batch(multi_column_batch(0..8000))
954 .with_filter(filter_builder.next_filter())
955 }
956 test.with_batch_size(1024)
957 .with_expected_output_sizes(vec![1024, 1024, 1024, 1024, 1024, 1024, 1024, 840])
958 .run();
959 }
960
961 #[test]
963 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_90() {
965 let mut filter_builder = RandomFilterBuilder {
966 num_rows: 800,
967 selectivity: 0.90,
968 seed: 0,
969 };
970
971 let mut test = Test::new("coalesce_filtered_90");
975 for _ in 0..10 {
976 test = test
977 .with_batch(multi_column_batch(0..800))
978 .with_filter(filter_builder.next_filter())
979 }
980 test.with_batch_size(1024)
981 .with_expected_output_sizes(vec![1024, 1024, 1024, 1024, 1024, 1024, 1024, 13])
982 .run();
983 }
984
985 #[test]
987 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_mixed() {
989 let mut filter_builder = RandomFilterBuilder {
990 num_rows: 800,
991 selectivity: 0.90,
992 seed: 0,
993 };
994
995 let mut test = Test::new("coalesce_filtered_mixed");
996 for _ in 0..3 {
997 let mut all_filter_builder = BooleanBufferBuilder::new(1000);
1000 all_filter_builder.append_n(500, true);
1001 all_filter_builder.append_n(1, false);
1002 all_filter_builder.append_n(499, false);
1003 let all_filter = all_filter_builder.build();
1004
1005 test = test
1006 .with_batch(multi_column_batch(0..1000))
1007 .with_filter(BooleanArray::from(all_filter))
1008 .with_batch(multi_column_batch(0..800))
1009 .with_filter(filter_builder.next_filter());
1010 filter_builder.selectivity *= 0.6;
1012 }
1013
1014 test.with_batch_size(250)
1017 .with_expected_output_sizes(vec![
1018 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 179,
1019 ])
1020 .run();
1021 }
1022
1023 #[test]
1024 fn test_coalesce_non_null() {
1025 Test::new("coalesce_non_null")
1026 .with_batch(uint32_batch_non_null(0..3000))
1028 .with_batch(uint32_batch_non_null(0..1040))
1029 .with_batch_size(1024)
1030 .with_expected_output_sizes(vec![1024, 1024, 1024, 968])
1031 .run();
1032 }
1033 #[test]
1034 #[cfg_attr(miri, ignore)] fn test_utf8_split() {
1036 Test::new("coalesce_utf8")
1037 .with_batch(utf8_batch(0..3000))
1039 .with_batch(utf8_batch(0..1040))
1040 .with_batch_size(1024)
1041 .with_expected_output_sizes(vec![1024, 1024, 1024, 968])
1042 .run();
1043 }
1044
1045 #[test]
1046 fn test_string_view_no_views() {
1047 let output_batches = Test::new("coalesce_string_view_no_views")
1048 .with_batch(stringview_batch([Some("foo"), Some("bar")]))
1050 .with_batch(stringview_batch([Some("baz"), Some("qux")]))
1051 .with_expected_output_sizes(vec![4])
1052 .run();
1053
1054 expect_buffer_layout(
1055 col_as_string_view("c0", output_batches.first().unwrap()),
1056 vec![],
1057 );
1058 }
1059
1060 #[test]
1061 fn test_string_view_batch_small_no_compact() {
1062 let batch = stringview_batch_repeated(1000, [Some("a"), Some("b"), Some("c")]);
1064 let output_batches = Test::new("coalesce_string_view_batch_small_no_compact")
1065 .with_batch(batch.clone())
1066 .with_expected_output_sizes(vec![1000])
1067 .run();
1068
1069 let array = col_as_string_view("c0", &batch);
1070 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1071 assert_eq!(array.data_buffers().len(), 0);
1072 assert_eq!(array.data_buffers().len(), gc_array.data_buffers().len()); expect_buffer_layout(gc_array, vec![]);
1075 }
1076
1077 #[test]
1078 #[cfg_attr(miri, ignore)] fn test_string_view_batch_large_no_compact() {
1080 let batch = stringview_batch_repeated(1000, [Some("This string is longer than 12 bytes")]);
1082 let output_batches = Test::new("coalesce_string_view_batch_large_no_compact")
1083 .with_batch(batch.clone())
1084 .with_batch_size(1000)
1085 .with_expected_output_sizes(vec![1000])
1086 .run();
1087
1088 let array = col_as_string_view("c0", &batch);
1089 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1090 assert_eq!(array.data_buffers().len(), 5);
1091 assert_eq!(array.data_buffers().len(), gc_array.data_buffers().len()); expect_buffer_layout(
1094 gc_array,
1095 vec![
1096 ExpectedLayout {
1097 len: 8190,
1098 capacity: 8192,
1099 },
1100 ExpectedLayout {
1101 len: 8190,
1102 capacity: 8192,
1103 },
1104 ExpectedLayout {
1105 len: 8190,
1106 capacity: 8192,
1107 },
1108 ExpectedLayout {
1109 len: 8190,
1110 capacity: 8192,
1111 },
1112 ExpectedLayout {
1113 len: 2240,
1114 capacity: 8192,
1115 },
1116 ],
1117 );
1118 }
1119
1120 #[test]
1121 fn test_string_view_batch_small_with_buffers_no_compact() {
1122 let short_strings = std::iter::repeat(Some("SmallString"));
1124 let long_strings = std::iter::once(Some("This string is longer than 12 bytes"));
1125 let values = short_strings.take(20).chain(long_strings);
1127 let batch = stringview_batch_repeated(1000, values)
1128 .slice(5, 10);
1130 let output_batches = Test::new("coalesce_string_view_batch_small_with_buffers_no_compact")
1131 .with_batch(batch.clone())
1132 .with_batch_size(1000)
1133 .with_expected_output_sizes(vec![10])
1134 .run();
1135
1136 let array = col_as_string_view("c0", &batch);
1137 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1138 assert_eq!(array.data_buffers().len(), 1); assert_eq!(gc_array.data_buffers().len(), 0); }
1141
1142 #[test]
1143 fn test_string_view_batch_large_slice_compact() {
1144 let batch = stringview_batch_repeated(1000, [Some("This string is longer than 12 bytes")])
1146 .slice(11, 22);
1148
1149 let output_batches = Test::new("coalesce_string_view_batch_large_slice_compact")
1150 .with_batch(batch.clone())
1151 .with_batch_size(1000)
1152 .with_expected_output_sizes(vec![22])
1153 .run();
1154
1155 let array = col_as_string_view("c0", &batch);
1156 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1157 assert_eq!(array.data_buffers().len(), 5);
1158
1159 expect_buffer_layout(
1160 gc_array,
1161 vec![ExpectedLayout {
1162 len: 770,
1163 capacity: 8192,
1164 }],
1165 );
1166 }
1167
1168 #[test]
1169 #[cfg_attr(miri, ignore)] fn test_string_view_mixed() {
1171 let large_view_batch =
1172 stringview_batch_repeated(1000, [Some("This string is longer than 12 bytes")]);
1173 let small_view_batch = stringview_batch_repeated(1000, [Some("SmallString")]);
1174 let mixed_batch = stringview_batch_repeated(
1175 1000,
1176 [Some("This string is longer than 12 bytes"), Some("Small")],
1177 );
1178 let mixed_batch_nulls = stringview_batch_repeated(
1179 1000,
1180 [
1181 Some("This string is longer than 12 bytes"),
1182 Some("Small"),
1183 None,
1184 ],
1185 );
1186
1187 let output_batches = Test::new("coalesce_string_view_mixed")
1190 .with_batch(large_view_batch.clone())
1191 .with_batch(small_view_batch)
1192 .with_batch(large_view_batch.slice(10, 20))
1194 .with_batch(mixed_batch_nulls)
1195 .with_batch(large_view_batch.slice(10, 20))
1197 .with_batch(mixed_batch)
1198 .with_expected_output_sizes(vec![1024, 1024, 1024, 968])
1199 .run();
1200
1201 expect_buffer_layout(
1202 col_as_string_view("c0", output_batches.first().unwrap()),
1203 vec![
1204 ExpectedLayout {
1205 len: 8190,
1206 capacity: 8192,
1207 },
1208 ExpectedLayout {
1209 len: 8190,
1210 capacity: 8192,
1211 },
1212 ExpectedLayout {
1213 len: 8190,
1214 capacity: 8192,
1215 },
1216 ExpectedLayout {
1217 len: 8190,
1218 capacity: 8192,
1219 },
1220 ExpectedLayout {
1221 len: 2240,
1222 capacity: 8192,
1223 },
1224 ],
1225 );
1226 }
1227
1228 #[test]
1229 #[cfg_attr(miri, ignore)] fn test_string_view_many_small_compact() {
1231 let batch = stringview_batch_repeated(
1234 200,
1235 [Some("This string is 28 bytes long"), Some("small string")],
1236 );
1237 let output_batches = Test::new("coalesce_string_view_many_small_compact")
1238 .with_batch(batch.clone())
1241 .with_batch(batch.clone())
1242 .with_batch(batch.clone())
1243 .with_batch(batch.clone())
1244 .with_batch(batch.clone())
1245 .with_batch(batch.clone())
1246 .with_batch(batch.clone())
1247 .with_batch(batch.clone())
1248 .with_batch(batch.clone())
1249 .with_batch(batch.clone())
1250 .with_batch_size(8000)
1251 .with_expected_output_sizes(vec![2000]) .run();
1253
1254 expect_buffer_layout(
1256 col_as_string_view("c0", output_batches.first().unwrap()),
1257 vec![
1258 ExpectedLayout {
1259 len: 8176,
1260 capacity: 8192,
1261 },
1262 ExpectedLayout {
1263 len: 16380,
1264 capacity: 16384,
1265 },
1266 ExpectedLayout {
1267 len: 3444,
1268 capacity: 32768,
1269 },
1270 ],
1271 );
1272 }
1273
1274 #[test]
1275 #[cfg_attr(miri, ignore)] fn test_string_view_many_small_boundary() {
1277 let batch = stringview_batch_repeated(100, [Some("This string is a power of two=32")]);
1279 let output_batches = Test::new("coalesce_string_view_many_small_boundary")
1280 .with_batches(std::iter::repeat_n(batch, 20))
1281 .with_batch_size(900)
1282 .with_expected_output_sizes(vec![900, 900, 200])
1283 .run();
1284
1285 expect_buffer_layout(
1287 col_as_string_view("c0", output_batches.first().unwrap()),
1288 vec![
1289 ExpectedLayout {
1290 len: 8192,
1291 capacity: 8192,
1292 },
1293 ExpectedLayout {
1294 len: 16384,
1295 capacity: 16384,
1296 },
1297 ExpectedLayout {
1298 len: 4224,
1299 capacity: 32768,
1300 },
1301 ],
1302 );
1303 }
1304
1305 #[test]
1306 #[cfg_attr(miri, ignore)] fn test_string_view_large_small() {
1308 let mixed_batch = stringview_batch_repeated(
1310 200,
1311 [Some("This string is 28 bytes long"), Some("small string")],
1312 );
1313 let all_large = stringview_batch_repeated(
1315 50,
1316 [Some(
1317 "This buffer has only large strings in it so there are no buffer copies",
1318 )],
1319 );
1320
1321 let output_batches = Test::new("coalesce_string_view_large_small")
1322 .with_batch(mixed_batch.clone())
1325 .with_batch(mixed_batch.clone())
1326 .with_batch(all_large.clone())
1327 .with_batch(mixed_batch.clone())
1328 .with_batch(all_large.clone())
1329 .with_batch(mixed_batch.clone())
1330 .with_batch(mixed_batch.clone())
1331 .with_batch(all_large.clone())
1332 .with_batch(mixed_batch.clone())
1333 .with_batch(all_large.clone())
1334 .with_batch_size(8000)
1335 .with_expected_output_sizes(vec![1400])
1336 .run();
1337
1338 expect_buffer_layout(
1339 col_as_string_view("c0", output_batches.first().unwrap()),
1340 vec![
1341 ExpectedLayout {
1342 len: 8190,
1343 capacity: 8192,
1344 },
1345 ExpectedLayout {
1346 len: 16366,
1347 capacity: 16384,
1348 },
1349 ExpectedLayout {
1350 len: 6244,
1351 capacity: 32768,
1352 },
1353 ],
1354 );
1355 }
1356
1357 #[test]
1358 #[cfg_attr(miri, ignore)] fn test_binary_view() {
1360 let values: Vec<Option<&[u8]>> = vec![
1361 Some(b"foo"),
1362 None,
1363 Some(b"A longer string that is more than 12 bytes"),
1364 ];
1365
1366 let binary_view =
1367 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1368 let batch =
1369 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1370
1371 Test::new("coalesce_binary_view")
1372 .with_batch(batch.clone())
1373 .with_batch(batch.clone())
1374 .with_batch_size(512)
1375 .with_expected_output_sizes(vec![512, 512, 512, 464])
1376 .run();
1377 }
1378
1379 #[test]
1380 fn test_binary_view_filtered() {
1381 let values: Vec<Option<&[u8]>> = vec![
1382 Some(b"foo"),
1383 None,
1384 Some(b"A longer string that is more than 12 bytes"),
1385 ];
1386
1387 let binary_view =
1388 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1389 let batch =
1390 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1391 let filter = sparse_filter(1000);
1392
1393 Test::new("coalesce_binary_view_filtered")
1394 .with_batch(batch.clone())
1395 .with_filter(filter.clone())
1396 .with_batch(batch)
1397 .with_filter(filter)
1398 .with_batch_size(256)
1399 .with_expected_output_sizes(vec![250])
1400 .run();
1401 }
1402
1403 #[test]
1404 fn test_binary_view_filtered_inline() {
1405 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1406
1407 let binary_view =
1408 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1409 let batch =
1410 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1411 let filter = sparse_filter(1000);
1412
1413 Test::new("coalesce_binary_view_filtered_inline")
1414 .with_batch(batch.clone())
1415 .with_filter(filter.clone())
1416 .with_batch(batch)
1417 .with_filter(filter)
1418 .with_batch_size(300)
1419 .with_expected_output_sizes(vec![250])
1420 .run();
1421 }
1422
1423 #[test]
1424 fn test_string_view_filtered_inline() {
1425 let values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1426
1427 let string_view =
1428 StringViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1429 let batch =
1430 RecordBatch::try_from_iter(vec![("c0", Arc::new(string_view) as ArrayRef)]).unwrap();
1431 let filter = sparse_filter(1000);
1432
1433 Test::new("coalesce_string_view_filtered_inline")
1434 .with_batch(batch.clone())
1435 .with_filter(filter.clone())
1436 .with_batch(batch)
1437 .with_filter(filter)
1438 .with_batch_size(300)
1439 .with_expected_output_sizes(vec![250])
1440 .run();
1441 }
1442
1443 #[test]
1444 fn test_mixed_inline_binary_view_filtered() {
1445 let int_values =
1446 Int32Array::from_iter((0..1000).map(|v| if v % 5 == 0 { None } else { Some(v) }));
1447 let float_values = arrow_array::Float64Array::from_iter((0..1000).map(|v| Some(v as f64)));
1448 let binary_values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1449 let binary_view = BinaryViewArray::from_iter(
1450 std::iter::repeat(binary_values.iter()).flatten().take(1000),
1451 );
1452
1453 let batch = RecordBatch::try_from_iter(vec![
1454 ("i", Arc::new(int_values) as ArrayRef),
1455 ("f", Arc::new(float_values) as ArrayRef),
1456 ("b", Arc::new(binary_view) as ArrayRef),
1457 ])
1458 .unwrap();
1459
1460 let filter = sparse_filter(1000);
1461
1462 Test::new("coalesce_mixed_inline_binary_view_filtered")
1463 .with_batch(batch.clone())
1464 .with_filter(filter.clone())
1465 .with_batch(batch)
1466 .with_filter(filter)
1467 .with_batch_size(300)
1468 .with_expected_output_sizes(vec![250])
1469 .run();
1470 }
1471
1472 #[test]
1473 fn test_mixed_inline_string_view_filtered() {
1474 let int_values =
1475 Int32Array::from_iter((0..1000).map(|v| if v % 5 == 0 { None } else { Some(v) }));
1476 let float_values = arrow_array::Float64Array::from_iter((0..1000).map(|v| Some(v as f64)));
1477 let string_values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1478 let string_view = StringViewArray::from_iter(
1479 std::iter::repeat(string_values.iter()).flatten().take(1000),
1480 );
1481
1482 let batch = RecordBatch::try_from_iter(vec![
1483 ("i", Arc::new(int_values) as ArrayRef),
1484 ("f", Arc::new(float_values) as ArrayRef),
1485 ("s", Arc::new(string_view) as ArrayRef),
1486 ])
1487 .unwrap();
1488
1489 let filter = sparse_filter(1000);
1490
1491 Test::new("coalesce_mixed_inline_string_view_filtered")
1492 .with_batch(batch.clone())
1493 .with_filter(filter.clone())
1494 .with_batch(batch)
1495 .with_filter(filter)
1496 .with_batch_size(300)
1497 .with_expected_output_sizes(vec![250])
1498 .run();
1499 }
1500
1501 #[test]
1502 fn test_inline_binary_view_sparse() {
1503 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1505 let binary_view =
1506 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1507 let batch =
1508 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1509 let filter = very_sparse_filter(1000);
1510
1511 Test::new("inline_binary_view_sparse")
1512 .with_batch(batch.clone())
1513 .with_filter(filter.clone())
1514 .with_batch(batch)
1515 .with_filter(filter)
1516 .with_batch_size(1024)
1517 .with_expected_output_sizes(vec![100])
1518 .run();
1519 }
1520
1521 #[test]
1522 fn test_inline_string_view_sparse() {
1523 let values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1524 let string_view =
1525 StringViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1526 let batch =
1527 RecordBatch::try_from_iter(vec![("c0", Arc::new(string_view) as ArrayRef)]).unwrap();
1528 let filter = very_sparse_filter(1000);
1529
1530 Test::new("inline_string_view_sparse")
1531 .with_batch(batch.clone())
1532 .with_filter(filter.clone())
1533 .with_batch(batch)
1534 .with_filter(filter)
1535 .with_batch_size(1024)
1536 .with_expected_output_sizes(vec![100])
1537 .run();
1538 }
1539
1540 #[test]
1541 fn test_inline_mixed_sparse() {
1542 let int_values =
1545 Int32Array::from_iter((0..1000).map(|v| if v % 5 == 0 { None } else { Some(v) }));
1546 let float_values = arrow_array::Float64Array::from_iter((0..1000).map(|v| Some(v as f64)));
1547 let string_values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1548 let string_view = StringViewArray::from_iter(
1549 std::iter::repeat(string_values.iter()).flatten().take(1000),
1550 );
1551 let binary_values: Vec<Option<&[u8]>> = vec![Some(b"x"), None, Some(b"abcdef")];
1552 let binary_view = BinaryViewArray::from_iter(
1553 std::iter::repeat(binary_values.iter()).flatten().take(1000),
1554 );
1555
1556 let batch = RecordBatch::try_from_iter(vec![
1557 ("i", Arc::new(int_values) as ArrayRef),
1558 ("f", Arc::new(float_values) as ArrayRef),
1559 ("s", Arc::new(string_view) as ArrayRef),
1560 ("b", Arc::new(binary_view) as ArrayRef),
1561 ])
1562 .unwrap();
1563 let filter = very_sparse_filter(1000);
1564
1565 Test::new("inline_mixed_sparse")
1566 .with_batch(batch.clone())
1567 .with_filter(filter.clone())
1568 .with_batch(batch)
1569 .with_filter(filter)
1570 .with_batch_size(1024)
1571 .with_expected_output_sizes(vec![100])
1572 .run();
1573 }
1574
1575 #[test]
1576 fn test_inline_crosses_target_batch_size() {
1577 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1581 let make_batch = || {
1582 let binary_view =
1583 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1584 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap()
1585 };
1586 let filter = very_sparse_filter(1000);
1587
1588 Test::new("inline_crosses_target_batch_size")
1589 .with_batch(make_batch())
1590 .with_filter(filter.clone())
1591 .with_batch(make_batch())
1592 .with_filter(filter.clone())
1593 .with_batch(make_batch())
1594 .with_filter(filter)
1595 .with_batch_size(100)
1596 .with_expected_output_sizes(vec![100, 50])
1597 .run();
1598 }
1599
1600 #[test]
1601 fn test_inline_filter_rejects_filter_longer_than_batch() {
1602 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), Some(b"bar")];
1603 let binary_view = BinaryViewArray::from_iter(values);
1604 let batch =
1605 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1606 let filter = BooleanArray::from(vec![true, false, true]);
1607
1608 let mut coalescer = BatchCoalescer::new(batch.schema(), 100);
1609 let result = coalescer.push_batch_with_filter(batch, &filter);
1610 assert!(result.is_err());
1611 let err = result.unwrap_err().to_string();
1612 assert!(
1613 err.contains("Filter predicate of length 3 is larger than target array of length 2"),
1614 "unexpected error: {err}"
1615 );
1616 }
1617
1618 #[test]
1619 fn test_mixed_boolean_inline_string_view_filtered() {
1620 let bool_values = BooleanArray::from_iter((0..1000).map(|v| Some(v % 3 == 0)));
1621 let string_values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1622 let string_view = StringViewArray::from_iter(
1623 std::iter::repeat(string_values.iter()).flatten().take(1000),
1624 );
1625
1626 let batch = RecordBatch::try_from_iter(vec![
1627 ("b", Arc::new(bool_values) as ArrayRef),
1628 ("s", Arc::new(string_view) as ArrayRef),
1629 ])
1630 .unwrap();
1631
1632 let filter = sparse_filter(1000);
1633
1634 Test::new("coalesce_mixed_boolean_inline_string_view_filtered")
1635 .with_batch(batch.clone())
1636 .with_filter(filter.clone())
1637 .with_batch(batch)
1638 .with_filter(filter)
1639 .with_batch_size(300)
1640 .with_expected_output_sizes(vec![250])
1641 .run();
1642 }
1643
1644 #[test]
1645 fn test_filter_fast_path_schema_capability() {
1646 let supported = Arc::new(Schema::new(vec![
1647 Field::new("primitive", DataType::UInt32, false),
1648 Field::new("utf8_view", DataType::Utf8View, true),
1649 Field::new("binary_view", DataType::BinaryView, true),
1650 ]));
1651 let coalescer = BatchCoalescer::new(supported, 100);
1652 assert!(!coalescer.has_non_specialized_filter_columns);
1653
1654 let utf8 = Arc::new(Schema::new(vec![Field::new("utf8", DataType::Utf8, true)]));
1655 let coalescer = BatchCoalescer::new(utf8, 100);
1656 assert!(coalescer.has_non_specialized_filter_columns);
1657
1658 let boolean = Arc::new(Schema::new(vec![Field::new(
1659 "boolean",
1660 DataType::Boolean,
1661 true,
1662 )]));
1663 let coalescer = BatchCoalescer::new(boolean, 100);
1664 assert!(coalescer.has_non_specialized_filter_columns);
1665 }
1666
1667 #[derive(Debug, Clone, PartialEq)]
1668 struct ExpectedLayout {
1669 len: usize,
1670 capacity: usize,
1671 }
1672
1673 fn expect_buffer_layout(array: &StringViewArray, expected: Vec<ExpectedLayout>) {
1675 let actual = array
1676 .data_buffers()
1677 .iter()
1678 .map(|b| ExpectedLayout {
1679 len: b.len(),
1680 capacity: b.capacity(),
1681 })
1682 .collect::<Vec<_>>();
1683
1684 assert_eq!(
1685 actual, expected,
1686 "Expected buffer layout {expected:#?} but got {actual:#?}"
1687 );
1688 }
1689
1690 #[derive(Debug, Clone)]
1697 struct Test {
1698 name: String,
1700 input_batches: Vec<RecordBatch>,
1702 filters: Vec<BooleanArray>,
1707 schema: Option<SchemaRef>,
1709 expected_output_sizes: Vec<usize>,
1711 target_batch_size: usize,
1713 }
1714
1715 impl Default for Test {
1716 fn default() -> Self {
1717 Self {
1718 name: String::new(),
1719 input_batches: vec![],
1720 filters: vec![],
1721 schema: None,
1722 expected_output_sizes: vec![],
1723 target_batch_size: 1024,
1724 }
1725 }
1726 }
1727
1728 impl Test {
1729 fn new(name: impl Into<String>) -> Self {
1730 Self {
1731 name: name.into(),
1732 ..Self::default()
1733 }
1734 }
1735
1736 fn with_description(mut self, description: &str) -> Self {
1738 self.name.push_str(": ");
1739 self.name.push_str(description);
1740 self
1741 }
1742
1743 fn with_batch_size(mut self, target_batch_size: usize) -> Self {
1745 self.target_batch_size = target_batch_size;
1746 self
1747 }
1748
1749 fn with_batch(mut self, batch: RecordBatch) -> Self {
1751 self.input_batches.push(batch);
1752 self
1753 }
1754
1755 fn with_filter(mut self, filter: BooleanArray) -> Self {
1757 self.filters.push(filter);
1758 self
1759 }
1760
1761 fn with_batches(mut self, batches: impl IntoIterator<Item = RecordBatch>) -> Self {
1763 self.input_batches = batches.into_iter().collect();
1764 self
1765 }
1766
1767 fn with_schema(mut self, schema: SchemaRef) -> Self {
1769 self.schema = Some(schema);
1770 self
1771 }
1772
1773 fn with_expected_output_sizes(mut self, sizes: impl IntoIterator<Item = usize>) -> Self {
1775 self.expected_output_sizes.extend(sizes);
1776 self
1777 }
1778
1779 fn run(self) -> Vec<RecordBatch> {
1783 let mut extra_tests = vec![];
1788 extra_tests.push(self.clone().make_half_non_nullable());
1789 extra_tests.push(self.clone().insert_empty_batches());
1790 let single_column_tests = self.make_single_column_tests();
1791 for test in single_column_tests {
1792 extra_tests.push(test.clone().make_half_non_nullable());
1793 extra_tests.push(test);
1794 }
1795
1796 let results = self.run_inner();
1799 for extra in extra_tests {
1801 extra.run_inner();
1802 }
1803
1804 results
1805 }
1806
1807 fn run_inner(self) -> Vec<RecordBatch> {
1809 let expected_output = self.expected_output();
1810 let schema = self.schema();
1811
1812 let Self {
1813 name,
1814 input_batches,
1815 filters,
1816 schema: _,
1817 target_batch_size,
1818 expected_output_sizes,
1819 } = self;
1820
1821 println!("Running test '{name}'");
1822
1823 let had_input = input_batches.iter().any(|b| b.num_rows() > 0);
1824
1825 let mut coalescer = BatchCoalescer::new(Arc::clone(&schema), target_batch_size);
1826
1827 let mut filters = filters.into_iter();
1829 for batch in input_batches {
1830 if let Some(filter) = filters.next() {
1831 coalescer.push_batch_with_filter(batch, &filter).unwrap();
1832 } else {
1833 coalescer.push_batch(batch).unwrap();
1834 }
1835 }
1836 assert_eq!(schema, coalescer.schema());
1837
1838 if had_input {
1839 assert!(!coalescer.is_empty(), "Coalescer should not be empty");
1840 } else {
1841 assert!(coalescer.is_empty(), "Coalescer should be empty");
1842 }
1843
1844 coalescer.finish_buffered_batch().unwrap();
1845 if had_input {
1846 assert!(
1847 coalescer.has_completed_batch(),
1848 "Coalescer should have completed batches"
1849 );
1850 }
1851
1852 let mut output_batches = vec![];
1853 while let Some(batch) = coalescer.next_completed_batch() {
1854 output_batches.push(batch);
1855 }
1856
1857 let mut starting_idx = 0;
1859 let actual_output_sizes: Vec<usize> =
1860 output_batches.iter().map(|b| b.num_rows()).collect();
1861 assert_eq!(
1862 expected_output_sizes, actual_output_sizes,
1863 "Unexpected number of rows in output batches\n\
1864 Expected\n{expected_output_sizes:#?}\nActual:{actual_output_sizes:#?}"
1865 );
1866 let iter = expected_output_sizes
1867 .iter()
1868 .zip(output_batches.iter())
1869 .enumerate();
1870
1871 for (i, (expected_size, batch)) in iter {
1873 let expected_batch = expected_output.slice(starting_idx, *expected_size);
1876 let expected_batch = normalize_batch(expected_batch);
1877 let batch = normalize_batch(batch.clone());
1878 assert_eq!(
1879 expected_batch, batch,
1880 "Unexpected content in batch {i}:\
1881 \n\nExpected:\n{expected_batch:#?}\n\nActual:\n{batch:#?}"
1882 );
1883 starting_idx += *expected_size;
1884 }
1885 output_batches
1886 }
1887
1888 fn schema(&self) -> SchemaRef {
1891 self.schema
1892 .clone()
1893 .unwrap_or_else(|| Arc::clone(&self.input_batches[0].schema()))
1894 }
1895
1896 fn expected_output(&self) -> RecordBatch {
1898 let schema = self.schema();
1899 if self.filters.is_empty() {
1900 return concat_batches(&schema, &self.input_batches).unwrap();
1901 }
1902
1903 let mut filters = self.filters.iter();
1904 let filtered_batches = self
1905 .input_batches
1906 .iter()
1907 .map(|batch| {
1908 if let Some(filter) = filters.next() {
1909 filter_record_batch(batch, filter).unwrap()
1910 } else {
1911 batch.clone()
1912 }
1913 })
1914 .collect::<Vec<_>>();
1915 concat_batches(&schema, &filtered_batches).unwrap()
1916 }
1917
1918 fn make_half_non_nullable(mut self) -> Self {
1921 self.input_batches = self
1923 .input_batches
1924 .iter()
1925 .enumerate()
1926 .map(|(i, batch)| {
1927 if i % 2 == 1 {
1928 batch.clone()
1929 } else {
1930 Self::remove_nulls_from_batch(batch)
1931 }
1932 })
1933 .collect();
1934 self.with_description("non-nullable")
1935 }
1936
1937 fn insert_empty_batches(mut self) -> Self {
1939 let empty_batch = RecordBatch::new_empty(self.schema());
1940 self.input_batches = self
1941 .input_batches
1942 .into_iter()
1943 .flat_map(|batch| [empty_batch.clone(), batch])
1944 .collect();
1945 let empty_filters = BooleanArray::builder(0).finish();
1946 self.filters = self
1947 .filters
1948 .into_iter()
1949 .flat_map(|filter| [empty_filters.clone(), filter])
1950 .collect();
1951 self.with_description("empty batches inserted")
1952 }
1953
1954 fn remove_nulls_from_batch(batch: &RecordBatch) -> RecordBatch {
1956 let new_columns = batch
1957 .columns()
1958 .iter()
1959 .map(Self::remove_nulls_from_array)
1960 .collect::<Vec<_>>();
1961 let options = RecordBatchOptions::new().with_row_count(Some(batch.num_rows()));
1962 RecordBatch::try_new_with_options(batch.schema(), new_columns, &options).unwrap()
1963 }
1964
1965 fn remove_nulls_from_array(array: &ArrayRef) -> ArrayRef {
1966 make_array(array.to_data().into_builder().nulls(None).build().unwrap())
1967 }
1968
1969 fn make_single_column_tests(&self) -> Vec<Self> {
1975 let original_schema = self.schema();
1976 let mut new_tests = vec![];
1977 for column in original_schema.fields() {
1978 let single_column_schema = Arc::new(Schema::new(vec![column.clone()]));
1979
1980 let single_column_batches = self.input_batches.iter().map(|batch| {
1981 let single_column = batch.column_by_name(column.name()).unwrap();
1982 RecordBatch::try_new(
1983 Arc::clone(&single_column_schema),
1984 vec![single_column.clone()],
1985 )
1986 .unwrap()
1987 });
1988
1989 let single_column_test = self
1990 .clone()
1991 .with_schema(Arc::clone(&single_column_schema))
1992 .with_batches(single_column_batches)
1993 .with_description("single column")
1994 .with_description(column.name());
1995
1996 new_tests.push(single_column_test);
1997 }
1998 new_tests
1999 }
2000 }
2001
2002 fn uint32_batch<T: std::iter::Iterator<Item = u32>>(range: T) -> RecordBatch {
2005 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, true)]));
2006
2007 let array = UInt32Array::from_iter(range.map(|i| if i % 3 == 0 { None } else { Some(i) }));
2008 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2009 }
2010
2011 fn uint32_batch_non_null<T: std::iter::Iterator<Item = u32>>(range: T) -> RecordBatch {
2013 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, false)]));
2014
2015 let array = UInt32Array::from_iter_values(range);
2016 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2017 }
2018
2019 fn uint64_batch_non_null<T: std::iter::Iterator<Item = u64>>(range: T) -> RecordBatch {
2021 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt64, false)]));
2022
2023 let array = UInt64Array::from_iter_values(range);
2024 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2025 }
2026
2027 fn utf8_batch(range: Range<u32>) -> RecordBatch {
2030 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::Utf8, true)]));
2031
2032 let array = StringArray::from_iter(range.map(|i| {
2033 if i % 3 == 0 {
2034 None
2035 } else {
2036 Some(format!("value{i}"))
2037 }
2038 }));
2039
2040 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2041 }
2042
2043 fn stringview_batch<'a>(values: impl IntoIterator<Item = Option<&'a str>>) -> RecordBatch {
2045 let schema = Arc::new(Schema::new(vec![Field::new(
2046 "c0",
2047 DataType::Utf8View,
2048 false,
2049 )]));
2050
2051 let array = StringViewArray::from_iter(values);
2052 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2053 }
2054
2055 fn stringview_batch_repeated<'a>(
2058 num_rows: usize,
2059 values: impl IntoIterator<Item = Option<&'a str>>,
2060 ) -> RecordBatch {
2061 let schema = Arc::new(Schema::new(vec![Field::new(
2062 "c0",
2063 DataType::Utf8View,
2064 true,
2065 )]));
2066
2067 let values: Vec<_> = values.into_iter().collect();
2069 let values_iter = std::iter::repeat(values.iter())
2070 .flatten()
2071 .copied()
2072 .take(num_rows);
2073
2074 let mut builder = StringViewBuilder::with_capacity(100).with_fixed_block_size(8192);
2075 for val in values_iter {
2076 builder.append_option(val);
2077 }
2078
2079 let array = builder.finish();
2080 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2081 }
2082
2083 fn multi_column_batch(range: Range<i32>) -> RecordBatch {
2085 let int64_array = Int64Array::from_iter(
2086 range
2087 .clone()
2088 .map(|v| if v % 5 == 0 { None } else { Some(v as i64) }),
2089 );
2090 let string_view_array = StringViewArray::from_iter(range.clone().map(|v| {
2091 if v % 5 == 0 {
2092 None
2093 } else if v % 7 == 0 {
2094 Some(format!("This is a string longer than 12 bytes{v}"))
2095 } else {
2096 Some(format!("Short {v}"))
2097 }
2098 }));
2099 let string_array = StringArray::from_iter(range.clone().map(|v| {
2100 if v % 11 == 0 {
2101 None
2102 } else {
2103 Some(format!("Value {v}"))
2104 }
2105 }));
2106 let timestamp_array = TimestampNanosecondArray::from_iter(range.map(|v| {
2107 if v % 3 == 0 {
2108 None
2109 } else {
2110 Some(v as i64 * 1000) }
2112 }))
2113 .with_timezone("America/New_York");
2114
2115 RecordBatch::try_from_iter(vec![
2116 ("int64", Arc::new(int64_array) as ArrayRef),
2117 ("stringview", Arc::new(string_view_array) as ArrayRef),
2118 ("string", Arc::new(string_array) as ArrayRef),
2119 ("timestamp", Arc::new(timestamp_array) as ArrayRef),
2120 ])
2121 .unwrap()
2122 }
2123
2124 #[derive(Debug)]
2130 struct RandomFilterBuilder {
2131 num_rows: usize,
2133 selectivity: f64,
2136 seed: u64,
2139 }
2140 impl RandomFilterBuilder {
2141 fn next_filter(&mut self) -> BooleanArray {
2144 assert!(self.selectivity >= 0.0 && self.selectivity <= 1.0);
2145 let mut rng = rand::rngs::StdRng::seed_from_u64(self.seed);
2146 self.seed += 1;
2147 BooleanArray::from_iter(
2148 (0..self.num_rows)
2149 .map(|_| rng.random_bool(self.selectivity))
2150 .map(Some),
2151 )
2152 }
2153 }
2154
2155 fn col_as_string_view<'b>(name: &str, batch: &'b RecordBatch) -> &'b StringViewArray {
2157 batch
2158 .column_by_name(name)
2159 .expect("column not found")
2160 .as_string_view_opt()
2161 .expect("column is not a string view")
2162 }
2163
2164 fn sparse_filter(len: usize) -> BooleanArray {
2166 BooleanArray::from_iter((0..len).map(|idx| Some(idx % 8 == 0)))
2167 }
2168
2169 fn very_sparse_filter(len: usize) -> BooleanArray {
2173 BooleanArray::from_iter((0..len).map(|idx| Some(idx % 20 == 0)))
2174 }
2175
2176 fn normalize_batch(batch: RecordBatch) -> RecordBatch {
2179 let (schema, mut columns, row_count) = batch.into_parts();
2181
2182 for column in &mut columns {
2183 if let Some(string_view) = column.as_string_view_opt() {
2184 let mut builder = StringViewBuilder::new();
2187 for s in string_view {
2188 builder.append_option(s);
2189 }
2190 *column = Arc::new(builder.finish());
2191 continue;
2192 }
2193
2194 if let Some(binary_view) = column.as_binary_view_opt() {
2195 *column = Arc::new(BinaryViewArray::from_iter(binary_view.iter()));
2196 }
2197 }
2198
2199 let options = RecordBatchOptions::new().with_row_count(Some(row_count));
2200 RecordBatch::try_new_with_options(schema, columns, &options).unwrap()
2201 }
2202
2203 fn create_test_batch(num_rows: usize) -> RecordBatch {
2205 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)]));
2206 let array = Int32Array::from_iter_values(0..num_rows as i32);
2207 RecordBatch::try_new(schema, vec![Arc::new(array)]).unwrap()
2208 }
2209 #[test]
2210 fn test_biggest_coalesce_batch_size_none_default() {
2211 let mut coalescer = BatchCoalescer::new(
2213 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2214 100,
2215 );
2216
2217 let large_batch = create_test_batch(1000);
2219 coalescer.push_batch(large_batch).unwrap();
2220
2221 let mut output_batches = vec![];
2223 while let Some(batch) = coalescer.next_completed_batch() {
2224 output_batches.push(batch);
2225 }
2226
2227 coalescer.finish_buffered_batch().unwrap();
2228 while let Some(batch) = coalescer.next_completed_batch() {
2229 output_batches.push(batch);
2230 }
2231
2232 assert_eq!(output_batches.len(), 10);
2234 for batch in output_batches {
2235 assert_eq!(batch.num_rows(), 100);
2236 }
2237 }
2238
2239 #[test]
2240 fn test_biggest_coalesce_batch_size_bypass_large_batch() {
2241 let mut coalescer = BatchCoalescer::new(
2243 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2244 100,
2245 );
2246 coalescer.set_biggest_coalesce_batch_size(Some(500));
2247
2248 let large_batch = create_test_batch(1000);
2250 coalescer.push_batch(large_batch.clone()).unwrap();
2251
2252 assert!(coalescer.has_completed_batch());
2254 let output_batch = coalescer.next_completed_batch().unwrap();
2255 assert_eq!(output_batch.num_rows(), 1000);
2256
2257 assert!(!coalescer.has_completed_batch());
2259 assert_eq!(coalescer.get_buffered_rows(), 0);
2260 }
2261
2262 #[test]
2263 fn test_biggest_coalesce_batch_size_coalesce_small_batch() {
2264 let mut coalescer = BatchCoalescer::new(
2266 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2267 100,
2268 )
2269 .with_biggest_coalesce_batch_size(Some(500));
2270
2271 let small_batch = create_test_batch(50);
2273 coalescer.push_batch(small_batch.clone()).unwrap();
2274
2275 assert!(!coalescer.has_completed_batch());
2277 assert_eq!(coalescer.get_buffered_rows(), 50);
2278
2279 coalescer.push_batch(small_batch).unwrap();
2281
2282 assert!(coalescer.has_completed_batch());
2284 let output_batch = coalescer.next_completed_batch().unwrap();
2285 let size = output_batch
2286 .column(0)
2287 .as_primitive::<Int32Type>()
2288 .get_buffer_memory_size();
2289 assert_eq!(size, 400); assert_eq!(output_batch.num_rows(), 100);
2291
2292 assert_eq!(coalescer.get_buffered_rows(), 0);
2293 }
2294
2295 #[test]
2296 fn test_biggest_coalesce_batch_size_equal_boundary() {
2297 let mut coalescer = BatchCoalescer::new(
2299 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2300 100,
2301 );
2302 coalescer.set_biggest_coalesce_batch_size(Some(500));
2303
2304 let boundary_batch = create_test_batch(500);
2306 coalescer.push_batch(boundary_batch).unwrap();
2307
2308 let mut output_count = 0;
2310 while coalescer.next_completed_batch().is_some() {
2311 output_count += 1;
2312 }
2313
2314 coalescer.finish_buffered_batch().unwrap();
2315 while coalescer.next_completed_batch().is_some() {
2316 output_count += 1;
2317 }
2318
2319 assert_eq!(output_count, 5);
2321 }
2322
2323 #[test]
2324 fn test_biggest_coalesce_batch_size_first_large_then_consecutive_bypass() {
2325 let mut coalescer = BatchCoalescer::new(
2328 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2329 100,
2330 );
2331 coalescer.set_biggest_coalesce_batch_size(Some(200));
2332
2333 let small_batch = create_test_batch(50);
2334
2335 coalescer.push_batch(small_batch).unwrap();
2337 assert_eq!(coalescer.get_buffered_rows(), 50);
2338 assert!(!coalescer.has_completed_batch());
2339
2340 let large_batch1 = create_test_batch(250);
2342 coalescer.push_batch(large_batch1).unwrap();
2343
2344 let mut completed_batches = vec![];
2346 while let Some(batch) = coalescer.next_completed_batch() {
2347 completed_batches.push(batch);
2348 }
2349 assert_eq!(completed_batches.len(), 3);
2350 assert_eq!(coalescer.get_buffered_rows(), 0);
2351
2352 let large_batch2 = create_test_batch(300);
2354 let large_batch3 = create_test_batch(400);
2355
2356 coalescer.push_batch(large_batch2).unwrap();
2358 assert!(coalescer.has_completed_batch());
2359 let output = coalescer.next_completed_batch().unwrap();
2360 assert_eq!(output.num_rows(), 300); assert_eq!(coalescer.get_buffered_rows(), 0);
2362
2363 coalescer.push_batch(large_batch3).unwrap();
2365 assert!(coalescer.has_completed_batch());
2366 let output = coalescer.next_completed_batch().unwrap();
2367 assert_eq!(output.num_rows(), 400); assert_eq!(coalescer.get_buffered_rows(), 0);
2369 }
2370
2371 #[test]
2372 fn test_biggest_coalesce_batch_size_empty_batch() {
2373 let mut coalescer = BatchCoalescer::new(
2375 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2376 100,
2377 );
2378 coalescer.set_biggest_coalesce_batch_size(Some(50));
2379
2380 let empty_batch = create_test_batch(0);
2381 coalescer.push_batch(empty_batch).unwrap();
2382
2383 assert!(!coalescer.has_completed_batch());
2385 assert_eq!(coalescer.get_buffered_rows(), 0);
2386 }
2387
2388 #[test]
2389 fn test_biggest_coalesce_batch_size_with_buffered_data_no_bypass() {
2390 let mut coalescer = BatchCoalescer::new(
2392 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2393 100,
2394 );
2395 coalescer.set_biggest_coalesce_batch_size(Some(200));
2396
2397 let small_batch = create_test_batch(30);
2399 coalescer.push_batch(small_batch.clone()).unwrap();
2400 coalescer.push_batch(small_batch).unwrap();
2401 assert_eq!(coalescer.get_buffered_rows(), 60);
2402
2403 let large_batch = create_test_batch(250);
2405 coalescer.push_batch(large_batch).unwrap();
2406
2407 let mut completed_batches = vec![];
2412 while let Some(batch) = coalescer.next_completed_batch() {
2413 completed_batches.push(batch);
2414 }
2415
2416 assert_eq!(completed_batches.len(), 3);
2417 for batch in &completed_batches {
2418 assert_eq!(batch.num_rows(), 100);
2419 }
2420 assert_eq!(coalescer.get_buffered_rows(), 10);
2421 }
2422
2423 #[test]
2424 fn test_biggest_coalesce_batch_size_zero_limit() {
2425 let mut coalescer = BatchCoalescer::new(
2427 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2428 100,
2429 );
2430 coalescer.set_biggest_coalesce_batch_size(Some(0));
2431
2432 let tiny_batch = create_test_batch(1);
2434 coalescer.push_batch(tiny_batch).unwrap();
2435
2436 assert!(coalescer.has_completed_batch());
2437 let output = coalescer.next_completed_batch().unwrap();
2438 assert_eq!(output.num_rows(), 1);
2439 }
2440
2441 #[test]
2442 fn test_biggest_coalesce_batch_size_bypass_only_when_no_buffer() {
2443 let mut coalescer = BatchCoalescer::new(
2445 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2446 100,
2447 );
2448 coalescer.set_biggest_coalesce_batch_size(Some(200));
2449
2450 let large_batch = create_test_batch(300);
2452 coalescer.push_batch(large_batch.clone()).unwrap();
2453
2454 assert!(coalescer.has_completed_batch());
2455 let output = coalescer.next_completed_batch().unwrap();
2456 assert_eq!(output.num_rows(), 300); assert_eq!(coalescer.get_buffered_rows(), 0);
2458
2459 let small_batch = create_test_batch(50);
2461 coalescer.push_batch(small_batch).unwrap();
2462 assert_eq!(coalescer.get_buffered_rows(), 50);
2463
2464 coalescer.push_batch(large_batch).unwrap();
2466
2467 let mut completed_batches = vec![];
2470 while let Some(batch) = coalescer.next_completed_batch() {
2471 completed_batches.push(batch);
2472 }
2473
2474 assert_eq!(completed_batches.len(), 3);
2475 for batch in &completed_batches {
2476 assert_eq!(batch.num_rows(), 100);
2477 }
2478 assert_eq!(coalescer.get_buffered_rows(), 50);
2479 }
2480
2481 #[test]
2482 fn test_biggest_coalesce_batch_size_consecutive_large_batches_scenario() {
2483 let mut coalescer = BatchCoalescer::new(
2485 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2486 1000,
2487 );
2488 coalescer.set_biggest_coalesce_batch_size(Some(500));
2489
2490 coalescer.push_batch(create_test_batch(20)).unwrap();
2492 coalescer.push_batch(create_test_batch(20)).unwrap();
2493 coalescer.push_batch(create_test_batch(30)).unwrap();
2494
2495 assert_eq!(coalescer.get_buffered_rows(), 70);
2496 assert!(!coalescer.has_completed_batch());
2497
2498 coalescer.push_batch(create_test_batch(700)).unwrap();
2500
2501 assert_eq!(coalescer.get_buffered_rows(), 770);
2503 assert!(!coalescer.has_completed_batch());
2504
2505 coalescer.push_batch(create_test_batch(600)).unwrap();
2507
2508 let mut outputs = vec![];
2510 while let Some(batch) = coalescer.next_completed_batch() {
2511 outputs.push(batch);
2512 }
2513 assert_eq!(outputs.len(), 2); assert_eq!(outputs[0].num_rows(), 770);
2515 assert_eq!(outputs[1].num_rows(), 600);
2516 assert_eq!(coalescer.get_buffered_rows(), 0);
2517
2518 let remaining_batches = [700, 900, 700, 600];
2520 for &size in &remaining_batches {
2521 coalescer.push_batch(create_test_batch(size)).unwrap();
2522
2523 assert!(coalescer.has_completed_batch());
2524 let output = coalescer.next_completed_batch().unwrap();
2525 assert_eq!(output.num_rows(), size);
2526 assert_eq!(coalescer.get_buffered_rows(), 0);
2527 }
2528 }
2529
2530 #[test]
2531 fn test_biggest_coalesce_batch_size_truly_consecutive_large_bypass() {
2532 let mut coalescer = BatchCoalescer::new(
2535 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2536 100,
2537 );
2538 coalescer.set_biggest_coalesce_batch_size(Some(200));
2539
2540 let large_batches = vec![
2542 create_test_batch(300),
2543 create_test_batch(400),
2544 create_test_batch(350),
2545 create_test_batch(500),
2546 ];
2547
2548 let mut all_outputs = vec![];
2549
2550 for (i, large_batch) in large_batches.into_iter().enumerate() {
2551 let expected_size = large_batch.num_rows();
2552
2553 assert_eq!(
2555 coalescer.get_buffered_rows(),
2556 0,
2557 "Buffer should be empty before batch {i}"
2558 );
2559
2560 coalescer.push_batch(large_batch).unwrap();
2561
2562 assert!(
2564 coalescer.has_completed_batch(),
2565 "Should have completed batch after pushing batch {i}"
2566 );
2567
2568 let output = coalescer.next_completed_batch().unwrap();
2569 assert_eq!(
2570 output.num_rows(),
2571 expected_size,
2572 "Batch {i} should have bypassed with original size"
2573 );
2574
2575 assert!(
2577 !coalescer.has_completed_batch(),
2578 "Should have no more completed batches after batch {i}"
2579 );
2580 assert_eq!(
2581 coalescer.get_buffered_rows(),
2582 0,
2583 "Buffer should be empty after batch {i}"
2584 );
2585
2586 all_outputs.push(output);
2587 }
2588
2589 assert_eq!(all_outputs.len(), 4);
2591 assert_eq!(all_outputs[0].num_rows(), 300);
2592 assert_eq!(all_outputs[1].num_rows(), 400);
2593 assert_eq!(all_outputs[2].num_rows(), 350);
2594 assert_eq!(all_outputs[3].num_rows(), 500);
2595 }
2596
2597 #[test]
2598 fn test_biggest_coalesce_batch_size_reset_consecutive_on_small_batch() {
2599 let mut coalescer = BatchCoalescer::new(
2601 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2602 100,
2603 );
2604 coalescer.set_biggest_coalesce_batch_size(Some(200));
2605
2606 coalescer.push_batch(create_test_batch(300)).unwrap();
2608 let output = coalescer.next_completed_batch().unwrap();
2609 assert_eq!(output.num_rows(), 300);
2610
2611 coalescer.push_batch(create_test_batch(400)).unwrap();
2613 let output = coalescer.next_completed_batch().unwrap();
2614 assert_eq!(output.num_rows(), 400);
2615
2616 coalescer.push_batch(create_test_batch(50)).unwrap();
2618 assert_eq!(coalescer.get_buffered_rows(), 50);
2619
2620 coalescer.push_batch(create_test_batch(350)).unwrap();
2622
2623 let mut outputs = vec![];
2625 while let Some(batch) = coalescer.next_completed_batch() {
2626 outputs.push(batch);
2627 }
2628 assert_eq!(outputs.len(), 4);
2629 for batch in outputs {
2630 assert_eq!(batch.num_rows(), 100);
2631 }
2632 assert_eq!(coalescer.get_buffered_rows(), 0);
2633 }
2634
2635 #[test]
2636 fn test_coalasce_push_batch_with_indices() {
2637 const MID_POINT: u32 = 2333;
2638 const TOTAL_ROWS: u32 = 23333;
2639 let batch1 = uint32_batch_non_null(0..MID_POINT);
2640 let batch2 = uint32_batch_non_null((MID_POINT..TOTAL_ROWS).rev());
2641
2642 let mut coalescer = BatchCoalescer::new(
2643 Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, false)])),
2644 TOTAL_ROWS as usize,
2645 );
2646 coalescer.push_batch(batch1).unwrap();
2647
2648 let rev_indices = (0..((TOTAL_ROWS - MID_POINT) as u64)).rev();
2649 let reversed_indices_batch = uint64_batch_non_null(rev_indices);
2650
2651 let reverse_indices = UInt64Array::from(reversed_indices_batch.column(0).to_data());
2652 coalescer
2653 .push_batch_with_indices(batch2, &reverse_indices)
2654 .unwrap();
2655
2656 coalescer.finish_buffered_batch().unwrap();
2657 let actual = coalescer.next_completed_batch().unwrap();
2658
2659 let expected = uint32_batch_non_null(0..TOTAL_ROWS);
2660
2661 assert_eq!(expected, actual);
2662 }
2663
2664 #[test]
2665 fn test_push_batch_schema_mismatch_fewer_columns() {
2666 let empty_schema = Arc::new(Schema::empty());
2668 let mut coalescer = BatchCoalescer::new(empty_schema, 100);
2669 let batch = uint32_batch(0..5);
2670 let result = coalescer.push_batch(batch);
2671 assert!(result.is_err());
2672 let err = result.unwrap_err().to_string();
2673 assert!(
2674 err.contains("Batch has 1 columns but BatchCoalescer expects 0"),
2675 "unexpected error: {err}"
2676 );
2677 }
2678
2679 #[test]
2680 fn test_push_batch_schema_mismatch_more_columns() {
2681 let schema = Arc::new(Schema::new(vec![
2683 Field::new("c0", DataType::UInt32, false),
2684 Field::new("c1", DataType::UInt32, false),
2685 ]));
2686 let mut coalescer = BatchCoalescer::new(schema, 100);
2687 let batch = uint32_batch(0..5);
2688 let result = coalescer.push_batch(batch);
2689 assert!(result.is_err());
2690 let err = result.unwrap_err().to_string();
2691 assert!(
2692 err.contains("Batch has 1 columns but BatchCoalescer expects 2"),
2693 "unexpected error: {err}"
2694 );
2695 }
2696
2697 #[test]
2698 fn test_push_batch_schema_mismatch_two_vs_zero() {
2699 let empty_schema = Arc::new(Schema::empty());
2701 let mut coalescer = BatchCoalescer::new(empty_schema, 100);
2702 let schema = Arc::new(Schema::new(vec![
2703 Field::new("c0", DataType::UInt32, false),
2704 Field::new("c1", DataType::UInt32, false),
2705 ]));
2706 let batch = RecordBatch::try_new(
2707 schema,
2708 vec![
2709 Arc::new(UInt32Array::from(vec![1, 2, 3])),
2710 Arc::new(UInt32Array::from(vec![4, 5, 6])),
2711 ],
2712 )
2713 .unwrap();
2714 let result = coalescer.push_batch(batch);
2715 assert!(result.is_err());
2716 let err = result.unwrap_err().to_string();
2717 assert!(
2718 err.contains("Batch has 2 columns but BatchCoalescer expects 0"),
2719 "unexpected error: {err}"
2720 );
2721 }
2722
2723 #[test]
2724 fn test_size_grows_with_buffering_and_shrinks_when_draining() {
2725 let batch = uint32_batch(0..8);
2726 let mut coalescer = BatchCoalescer::new(batch.schema(), 21);
2727 let baseline = coalescer.size();
2728 assert!(baseline > 0, "size includes container capacities");
2729
2730 coalescer.push_batch(batch.clone()).unwrap();
2732 assert!(coalescer.next_completed_batch().is_none());
2733 let buffered = coalescer.size();
2734 assert!(
2735 buffered > baseline,
2736 "buffering rows should grow size ({buffered} > {baseline})"
2737 );
2738
2739 for _ in 0..10 {
2741 coalescer.push_batch(batch.clone()).unwrap();
2742 }
2743 let peak = coalescer.size();
2744 assert!(peak > buffered);
2745
2746 let mut prev = peak;
2748 let mut drained_any = false;
2749 while coalescer.next_completed_batch().is_some() {
2750 drained_any = true;
2751 let now = coalescer.size();
2752 assert!(now <= prev, "size grew while draining: {now} > {prev}");
2753 prev = now;
2754 }
2755 assert!(drained_any);
2756 assert!(
2757 prev < peak,
2758 "draining completed batches should release memory"
2759 );
2760 }
2761
2762 #[test]
2763 fn test_size_string_view_buffers_released_after_drain() {
2764 let batch = stringview_batch_repeated(
2767 1000,
2768 [Some("this string is definitely longer than 12 bytes")],
2769 );
2770 let mut coalescer = BatchCoalescer::new(batch.schema(), 4096);
2771 let baseline = coalescer.size();
2772
2773 for _ in 0..20 {
2774 coalescer.push_batch(batch.clone()).unwrap();
2775 }
2776 let peak = coalescer.size();
2777 assert!(
2778 peak > baseline,
2779 "buffered string view data should grow size ({peak} > {baseline})"
2780 );
2781
2782 coalescer.finish_buffered_batch().unwrap();
2783 while coalescer.next_completed_batch().is_some() {}
2784
2785 let drained = coalescer.size();
2787 assert!(
2788 drained < peak,
2789 "draining should release buffered data ({drained} < {peak})"
2790 );
2791 }
2792
2793 #[test]
2797 fn test_size_accounting_conserved_across_cycles() {
2798 let batch = uint32_batch(0..8);
2802 let mut coalescer = BatchCoalescer::new(batch.schema(), 4096);
2803
2804 let run_cycle = |coalescer: &mut BatchCoalescer| {
2805 for _ in 0..20 {
2806 coalescer.push_batch(batch.clone()).unwrap();
2807 }
2808 coalescer.finish_buffered_batch().unwrap();
2809 let peak = coalescer.size();
2810 while coalescer.next_completed_batch().is_some() {}
2811 (peak, coalescer.size())
2812 };
2813
2814 let (peak1, drained1) = run_cycle(&mut coalescer);
2815 let (peak2, drained2) = run_cycle(&mut coalescer);
2816
2817 assert_eq!(
2818 peak1, peak2,
2819 "identical work must report identical peak size"
2820 );
2821 assert_eq!(
2822 drained1, drained2,
2823 "fully-drained size must be stable across cycles (no accounting leak)"
2824 );
2825 assert!(
2826 drained1 < peak1,
2827 "draining must release the accounted memory"
2828 );
2829 }
2830}