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