1use crate::filter::{FilterBuilder, FilterPredicate, FilterSelection};
11use crate::interleave::interleave_record_batch;
12use crate::take::take_record_batch;
13use arrow_array::types::{BinaryViewType, GenericBinaryType, GenericStringType, StringViewType};
14use arrow_array::{Array, ArrayRef, BooleanArray, RecordBatch, downcast_primitive};
15use arrow_schema::{ArrowError, DataType, SchemaRef};
16use std::collections::VecDeque;
17use std::sync::Arc;
18mod boolean;
22mod byte;
23mod byte_view;
24mod dictionary;
25mod fixed_size_binary;
26mod generic;
27mod list;
28mod primitive;
29
30use boolean::InProgressBooleanArray;
31use byte::InProgressByteArray;
32use byte_view::InProgressByteViewArray;
33use dictionary::create_in_progress_dictionary;
34use fixed_size_binary::InProgressFixedSizeBinaryArray;
35use generic::GenericInProgressArray;
36use list::InProgressListArray;
37use primitive::InProgressPrimitiveArray;
38
39fn has_sparse_filter_copy(data_type: &DataType) -> bool {
40 data_type.is_primitive()
41 || matches!(
42 data_type,
43 DataType::Boolean
44 | DataType::Utf8
45 | DataType::LargeUtf8
46 | DataType::Binary
47 | DataType::LargeBinary
48 | DataType::Utf8View
50 | DataType::BinaryView
51 | DataType::List(_)
52 | DataType::LargeList(_)
53 )
54}
55
56const SPARSE_FILTER_COPY_MAX_SELECTIVITY_DENOMINATOR: usize = 16;
62
63fn should_use_sparse_filter_copy(filter_len: usize, selected_count: usize) -> bool {
64 selected_count <= filter_len / SPARSE_FILTER_COPY_MAX_SELECTIVITY_DENOMINATOR
65}
66
67const BYTE_ARRAY_DIRECT_COPY_MAX_SELECTIVITY_DENOMINATOR: usize = 1024;
70
71fn should_use_byte_array_direct_copy(filter_len: usize, selected_count: usize) -> bool {
72 selected_count <= filter_len / BYTE_ARRAY_DIRECT_COPY_MAX_SELECTIVITY_DENOMINATOR
73}
74
75#[derive(Debug)]
167pub struct BatchCoalescer {
168 schema: SchemaRef,
170 target_batch_size: usize,
174 in_progress_arrays: Vec<Box<dyn InProgressArray>>,
176 has_non_specialized_filter_columns: bool,
178 has_byte_array_columns: bool,
180 buffered_rows: usize,
182 completed: VecDeque<RecordBatch>,
184 biggest_coalesce_batch_size: Option<usize>,
186}
187
188impl BatchCoalescer {
189 pub fn new(schema: SchemaRef, target_batch_size: usize) -> Self {
197 let has_non_specialized_filter_columns = schema
198 .fields()
199 .iter()
200 .any(|field| !has_sparse_filter_copy(field.data_type()));
201 let has_byte_array_columns = schema.fields().iter().any(|field| {
202 matches!(
203 field.data_type(),
204 DataType::Utf8 | DataType::LargeUtf8 | DataType::Binary | DataType::LargeBinary
205 )
206 });
207 let in_progress_arrays = schema
208 .fields()
209 .iter()
210 .map(|field| create_in_progress_array(field.data_type(), target_batch_size))
211 .collect::<Vec<_>>();
212
213 Self {
214 schema,
215 target_batch_size,
216 in_progress_arrays,
217 has_non_specialized_filter_columns,
218 has_byte_array_columns,
219 completed: VecDeque::with_capacity(1),
221 buffered_rows: 0,
222 biggest_coalesce_batch_size: None,
223 }
224 }
225
226 pub fn with_biggest_coalesce_batch_size(mut self, limit: Option<usize>) -> Self {
239 self.biggest_coalesce_batch_size = limit;
240 self
241 }
242
243 pub fn biggest_coalesce_batch_size(&self) -> Option<usize> {
247 self.biggest_coalesce_batch_size
248 }
249
250 pub fn set_biggest_coalesce_batch_size(&mut self, limit: Option<usize>) {
254 self.biggest_coalesce_batch_size = limit;
255 }
256
257 pub fn schema(&self) -> SchemaRef {
259 Arc::clone(&self.schema)
260 }
261
262 pub fn push_batch_with_filter(
291 &mut self,
292 batch: RecordBatch,
293 filter: &BooleanArray,
294 ) -> Result<(), ArrowError> {
295 self.push_batch_with_filter_builder(batch, FilterBuilder::new(filter))
296 }
297
298 pub fn push_batch_with_filter_builder(
328 &mut self,
329 batch: RecordBatch,
330 filter_builder: FilterBuilder,
331 ) -> Result<(), ArrowError> {
332 let predicate = Self::filter_predicate_for_batch(&batch, filter_builder);
333 self.push_batch_with_filtered_columns(batch, &predicate)
334 }
335
336 pub fn push_batch_with_indices(
360 &mut self,
361 batch: RecordBatch,
362 indices: &dyn Array,
363 ) -> Result<(), ArrowError> {
364 let taken_batch = take_record_batch(&batch, indices)?;
366 self.push_batch(taken_batch)
367 }
368
369 pub fn push_batch_interleaved(
396 &mut self,
397 batches: &[RecordBatch],
398 indices: &[(usize, usize)],
399 ) -> Result<(), ArrowError> {
400 if batches.is_empty() {
401 return Err(ArrowError::InvalidArgumentError(
402 "push_batch_interleaved requires at least one input batch".to_string(),
403 ));
404 }
405
406 let refs: Vec<&RecordBatch> = batches.iter().collect();
408 let interleaved = interleave_record_batch(&refs, indices)?;
409 self.push_batch(interleaved)
410 }
411
412 pub fn push_batch(&mut self, batch: RecordBatch) -> Result<(), ArrowError> {
439 let batch_size = batch.num_rows();
558
559 if batch_size == 0 {
561 return Ok(());
562 }
563
564 if let Some(limit) = self.biggest_coalesce_batch_size
566 && batch_size > limit
567 {
568 if self.buffered_rows == 0 {
571 self.completed.push_back(batch);
572 return Ok(());
573 }
574
575 if self.buffered_rows > limit {
580 self.finish_buffered_batch()?;
581 self.completed.push_back(batch);
582 return Ok(());
583 }
584
585 }
590
591 let (_schema, arrays, mut num_rows) = batch.into_parts();
592
593 if arrays.len() != self.in_progress_arrays.len() {
595 return Err(ArrowError::InvalidArgumentError(format!(
596 "Batch has {} columns but BatchCoalescer expects {}",
597 arrays.len(),
598 self.in_progress_arrays.len()
599 )));
600 }
601 self.in_progress_arrays
602 .iter_mut()
603 .zip(arrays)
604 .for_each(|(in_progress, array)| {
605 in_progress.set_source(Some(array));
606 });
607
608 let mut offset = 0;
611 while num_rows > (self.target_batch_size - self.buffered_rows) {
612 let remaining_rows = self.target_batch_size - self.buffered_rows;
613 debug_assert!(remaining_rows > 0);
614
615 for in_progress in &mut self.in_progress_arrays {
617 in_progress.copy_rows(offset, remaining_rows)?;
618 }
619
620 self.buffered_rows += remaining_rows;
621 offset += remaining_rows;
622 num_rows -= remaining_rows;
623
624 self.finish_buffered_batch()?;
625 }
626
627 self.buffered_rows += num_rows;
629 if num_rows > 0 {
630 for in_progress in &mut self.in_progress_arrays {
631 in_progress.copy_rows(offset, num_rows)?;
632 }
633 }
634
635 if self.buffered_rows >= self.target_batch_size {
637 self.finish_buffered_batch()?;
638 }
639
640 for in_progress in &mut self.in_progress_arrays {
642 in_progress.set_source(None);
643 }
644
645 Ok(())
646 }
647
648 pub fn get_buffered_rows(&self) -> usize {
650 self.buffered_rows
651 }
652
653 pub fn finish_buffered_batch(&mut self) -> Result<(), ArrowError> {
661 if self.buffered_rows == 0 {
662 return Ok(());
663 }
664 let new_arrays = self
665 .in_progress_arrays
666 .iter_mut()
667 .zip(self.schema.fields())
668 .map(|(array, field)| {
669 array.finish().map_err(|e| {
670 ArrowError::ComputeError(format!(
671 "Failed to concatenate column '{}': {e}",
672 field.name()
673 ))
674 })
675 })
676 .collect::<Result<Vec<_>, ArrowError>>()?;
677
678 for (array, field) in new_arrays.iter().zip(self.schema.fields().iter()) {
679 debug_assert_eq!(array.data_type(), field.data_type());
680 debug_assert_eq!(array.len(), self.buffered_rows);
681 }
682
683 let batch = unsafe {
685 RecordBatch::new_unchecked(Arc::clone(&self.schema), new_arrays, self.buffered_rows)
686 };
687
688 self.buffered_rows = 0;
689 self.completed.push_back(batch);
690 Ok(())
691 }
692
693 pub fn is_empty(&self) -> bool {
695 self.buffered_rows == 0 && self.completed.is_empty()
696 }
697
698 pub fn has_completed_batch(&self) -> bool {
700 !self.completed.is_empty()
701 }
702
703 pub fn next_completed_batch(&mut self) -> Option<RecordBatch> {
705 self.completed.pop_front()
706 }
707
708 pub fn size(&self) -> usize {
710 self.in_progress_arrays.capacity() * size_of::<Box<dyn InProgressArray>>()
711 + self
712 .in_progress_arrays
713 .iter()
714 .map(|array| array.size())
715 .sum::<usize>()
716 + self.completed.capacity() * size_of::<RecordBatch>()
717 + self
718 .completed
719 .iter()
720 .map(|batch| batch.get_array_memory_size())
721 .sum::<usize>()
722 }
723}
724
725impl BatchCoalescer {
726 fn filter_predicate_for_batch(
727 batch: &RecordBatch,
728 mut filter_builder: FilterBuilder,
729 ) -> FilterPredicate {
730 if batch.num_columns() > 1
731 || (batch.num_columns() > 0
732 && FilterBuilder::is_optimize_beneficial(batch.schema_ref().field(0).data_type()))
733 {
734 filter_builder = filter_builder.optimize();
735 }
736 filter_builder.build()
737 }
738
739 fn push_batch_with_filtered_columns(
740 &mut self,
741 batch: RecordBatch,
742 predicate: &FilterPredicate,
743 ) -> Result<(), ArrowError> {
744 let filter_len = predicate.filter_len();
745 let selected_count = predicate.count();
746 let batch_num_rows = batch.num_rows();
747 let batch_num_columns = batch.num_columns();
748
749 if filter_len > batch_num_rows {
750 return Err(ArrowError::InvalidArgumentError(format!(
751 "Filter predicate of length {filter_len} is larger than target array of length {batch_num_rows}"
752 )));
753 }
754
755 if selected_count == 0 {
756 return Ok(());
757 }
758
759 if selected_count == batch_num_rows && filter_len == batch_num_rows {
760 return self.push_batch(batch);
761 }
762
763 if batch_num_columns != self.in_progress_arrays.len() {
764 return Err(ArrowError::InvalidArgumentError(format!(
765 "Batch has {} columns but BatchCoalescer expects {}",
766 batch_num_columns,
767 self.in_progress_arrays.len()
768 )));
769 }
770
771 let exceeds_coalesce_limit = self
772 .biggest_coalesce_batch_size
773 .is_some_and(|limit| selected_count > limit);
774 let does_not_fit_buffer = selected_count > self.target_batch_size - self.buffered_rows;
775 let should_materialize_filter = exceeds_coalesce_limit
776 || self.has_non_specialized_filter_columns
777 || does_not_fit_buffer
778 || (self.has_byte_array_columns
779 && !should_use_byte_array_direct_copy(filter_len, selected_count))
780 || !should_use_sparse_filter_copy(filter_len, selected_count);
781
782 if should_materialize_filter {
783 let filtered_batch = predicate.filter_record_batch(&batch)?;
785 return self.push_batch(filtered_batch);
786 }
787
788 let (_schema, arrays, _num_rows) = batch.into_parts();
789
790 for (in_progress, array) in self.in_progress_arrays.iter_mut().zip(arrays) {
791 in_progress.copy_rows_by_filter_from(array, predicate)?;
792 }
793
794 self.buffered_rows += selected_count;
795 if self.buffered_rows >= self.target_batch_size {
796 self.finish_buffered_batch()?;
797 }
798
799 Ok(())
800 }
801}
802
803fn create_in_progress_array(data_type: &DataType, batch_size: usize) -> Box<dyn InProgressArray> {
805 macro_rules! instantiate_primitive {
806 ($t:ty) => {
807 Box::new(InProgressPrimitiveArray::<$t>::new(
808 batch_size,
809 data_type.clone(),
810 ))
811 };
812 }
813
814 downcast_primitive! {
815 data_type => (instantiate_primitive),
817 DataType::Utf8View => Box::new(InProgressByteViewArray::<StringViewType>::new(batch_size)),
818 DataType::BinaryView => {
819 Box::new(InProgressByteViewArray::<BinaryViewType>::new(batch_size))
820 }
821 DataType::Boolean => Box::new(InProgressBooleanArray::new(batch_size)),
822 DataType::Utf8 => Box::new(InProgressByteArray::<GenericStringType<i32>>::new(batch_size)),
823 DataType::LargeUtf8 => {
824 Box::new(InProgressByteArray::<GenericStringType<i64>>::new(batch_size))
825 }
826 DataType::Binary => Box::new(InProgressByteArray::<GenericBinaryType<i32>>::new(batch_size)),
827 DataType::LargeBinary => {
828 Box::new(InProgressByteArray::<GenericBinaryType<i64>>::new(batch_size))
829 }
830 DataType::Dictionary(key, value) => {
831 create_in_progress_dictionary(key, value)
832 }
833 DataType::FixedSizeBinary(size) => {
834 Box::new(InProgressFixedSizeBinaryArray::new(*size, batch_size))
835 }
836 DataType::List(_) | DataType::LargeList(_) => {
837 Box::new(InProgressListArray::new(data_type.clone()))
838 }
839 _ => Box::new(GenericInProgressArray::new()),
840 }
841}
842
843fn check_filter_len(filter: &FilterPredicate, source_len: usize) -> Result<(), ArrowError> {
849 let filter_len = filter.filter_len();
850 if filter_len > source_len {
851 return Err(ArrowError::InvalidArgumentError(format!(
852 "Filter predicate of length {filter_len} is larger than source array of length {source_len}"
853 )));
854 }
855 Ok(())
856}
857
858trait InProgressArray: std::fmt::Debug + Send + Sync {
868 fn set_source(&mut self, source: Option<ArrayRef>);
873
874 fn copy_rows(&mut self, offset: usize, len: usize) -> Result<(), ArrowError>;
880
881 fn copy_rows_by_filter(&mut self, filter: &FilterPredicate) -> Result<(), ArrowError> {
885 self.copy_rows_by_selection(filter.selection())
886 }
887
888 fn copy_rows_by_filter_from(
895 &mut self,
896 source: ArrayRef,
897 filter: &FilterPredicate,
898 ) -> Result<(), ArrowError> {
899 self.set_source(Some(source));
900 let result = self.copy_rows_by_filter(filter);
901 self.set_source(None);
902 result
903 }
904
905 fn copy_rows_by_selection(&mut self, selection: FilterSelection<'_>) -> Result<(), ArrowError> {
911 match selection {
912 FilterSelection::None => Ok(()),
913 FilterSelection::All { len } => self.copy_rows(0, len),
914 FilterSelection::Slices(slices) => {
915 slices.try_for_each(|(start, end)| self.copy_rows(start, end - start))
916 }
917 FilterSelection::Indices(indices) => indices.try_for_each(|idx| self.copy_rows(idx, 1)),
918 }
919 }
920
921 fn finish(&mut self) -> Result<ArrayRef, ArrowError>;
923
924 fn size(&self) -> usize;
926}
927
928#[cfg(test)]
929mod tests {
930 use super::*;
931 use crate::concat::concat_batches;
932 use crate::filter::filter_record_batch;
933 use arrow_array::builder::StringViewBuilder;
934 use arrow_array::cast::AsArray;
935 use arrow_array::types::{Int32Type, UInt8Type};
936 use arrow_array::{
937 BinaryViewArray, DictionaryArray, Int32Array, Int64Array, RecordBatchOptions, StringArray,
938 StringViewArray, TimestampNanosecondArray, UInt8Array, UInt32Array, UInt64Array,
939 make_array, record_batch,
940 };
941 use arrow_buffer::BooleanBufferBuilder;
942 use arrow_schema::{DataType, Field, Schema};
943 use rand::{RngExt, SeedableRng};
944 use std::ops::Range;
945
946 #[test]
947 fn test_coalesce() {
948 let batch = uint32_batch(0..8);
949 Test::new("coalesce")
950 .with_batches(std::iter::repeat_n(batch, 10))
951 .with_batch_size(21)
953 .with_expected_output_sizes(vec![21, 21, 21, 17])
954 .run();
955 }
956
957 #[test]
958 fn test_coalesce_one_by_one() {
959 let batch = uint32_batch(0..1); Test::new("coalesce_one_by_one")
961 .with_batches(std::iter::repeat_n(batch, 97))
962 .with_batch_size(20)
964 .with_expected_output_sizes(vec![20, 20, 20, 20, 17])
965 .run();
966 }
967
968 #[test]
969 fn test_coalesce_empty() {
970 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, false)]));
971
972 Test::new("coalesce_empty")
973 .with_batches(vec![])
974 .with_schema(schema)
975 .with_batch_size(21)
976 .with_expected_output_sizes(vec![])
977 .run();
978 }
979
980 #[test]
981 fn test_coalesce_error_includes_column_name() {
982 let schema = Arc::new(Schema::new(vec![
983 Field::new("a", DataType::Utf8, false),
984 Field::new(
985 "b",
986 DataType::Dictionary(Box::new(DataType::UInt8), Box::new(DataType::Utf8)),
987 false,
988 ),
989 ]));
990 let batch = RecordBatch::try_new(
991 Arc::clone(&schema),
992 vec![
993 Arc::new(StringArray::from_iter_values(std::iter::repeat_n("a", 256))),
994 Arc::new(DictionaryArray::<UInt8Type>::new(
995 UInt8Array::from_iter_values(0..=u8::MAX),
996 Arc::new(StringArray::from_iter_values(
997 (0..=u8::MAX).map(|i| i.to_string()),
998 )),
999 )),
1000 ],
1001 )
1002 .unwrap();
1003 let overflow = RecordBatch::try_new(
1004 Arc::clone(&schema),
1005 vec![
1006 Arc::new(StringArray::from(vec!["a"])),
1007 Arc::new(DictionaryArray::<UInt8Type>::new(
1008 UInt8Array::from(vec![0]),
1009 Arc::new(StringArray::from(vec!["overflow"])),
1010 )),
1011 ],
1012 )
1013 .unwrap();
1014
1015 let mut coalescer = BatchCoalescer::new(schema, 258);
1016 coalescer.push_batch(batch).unwrap();
1017 coalescer.push_batch(overflow).unwrap();
1018 let err = coalescer.finish_buffered_batch().unwrap_err();
1019
1020 assert_eq!(
1021 err.to_string(),
1022 "Compute error: Failed to concatenate column 'b': Dictionary key bigger than the key type"
1023 );
1024 }
1025
1026 #[test]
1027 fn test_sparse_filter_copy_threshold() {
1028 assert!(should_use_sparse_filter_copy(8192, 8));
1029 assert!(should_use_sparse_filter_copy(8192, 81));
1030 assert!(!should_use_sparse_filter_copy(8192, 819));
1031 assert!(!should_use_sparse_filter_copy(8192, 6553));
1032 }
1033
1034 #[test]
1035 fn test_single_large_batch_greater_than_target() {
1036 let batch = uint32_batch(0..4096);
1038 Test::new("coalesce_single_large_batch_greater_than_target")
1039 .with_batch(batch)
1040 .with_batch_size(1000)
1041 .with_expected_output_sizes(vec![1000, 1000, 1000, 1000, 96])
1042 .run();
1043 }
1044
1045 #[test]
1046 fn test_single_large_batch_smaller_than_target() {
1047 let batch = uint32_batch(0..4096);
1049 Test::new("coalesce_single_large_batch_smaller_than_target")
1050 .with_batch(batch)
1051 .with_batch_size(8192)
1052 .with_expected_output_sizes(vec![4096])
1053 .run();
1054 }
1055
1056 #[test]
1057 fn test_single_large_batch_equal_to_target() {
1058 let batch = uint32_batch(0..4096);
1060 Test::new("coalesce_single_large_batch_equal_to_target")
1061 .with_batch(batch)
1062 .with_batch_size(4096)
1063 .with_expected_output_sizes(vec![4096])
1064 .run();
1065 }
1066
1067 #[test]
1068 fn test_single_large_batch_equally_divisible_in_target() {
1069 let batch = uint32_batch(0..4096);
1071 Test::new("coalesce_single_large_batch_equally_divisible_in_target")
1072 .with_batch(batch)
1073 .with_batch_size(1024)
1074 .with_expected_output_sizes(vec![1024, 1024, 1024, 1024])
1075 .run();
1076 }
1077
1078 #[test]
1079 fn test_empty_schema() {
1080 let schema = Schema::empty();
1081 let batch = RecordBatch::new_empty(schema.into());
1082 Test::new("coalesce_empty_schema")
1083 .with_batch(batch)
1084 .with_expected_output_sizes(vec![])
1085 .run();
1086 }
1087
1088 #[test]
1090 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_001() {
1092 let mut filter_builder = RandomFilterBuilder {
1093 num_rows: 8000,
1094 selectivity: 0.001,
1095 seed: 0,
1096 };
1097
1098 let mut test = Test::new("coalesce_filtered_001");
1102 for _ in 0..10 {
1103 test = test
1104 .with_batch(multi_column_batch(0..8000))
1105 .with_filter(filter_builder.next_filter())
1106 }
1107 test.with_batch_size(15)
1108 .with_expected_output_sizes(vec![15, 15, 15, 13])
1109 .run();
1110 }
1111
1112 #[test]
1114 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_01() {
1116 let mut filter_builder = RandomFilterBuilder {
1117 num_rows: 8000,
1118 selectivity: 0.01,
1119 seed: 0,
1120 };
1121
1122 let mut test = Test::new("coalesce_filtered_01");
1126 for _ in 0..10 {
1127 test = test
1128 .with_batch(multi_column_batch(0..8000))
1129 .with_filter(filter_builder.next_filter())
1130 }
1131 test.with_batch_size(128)
1132 .with_expected_output_sizes(vec![128, 128, 128, 128, 128, 128, 15])
1133 .run();
1134 }
1135
1136 #[test]
1138 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_10() {
1140 let mut filter_builder = RandomFilterBuilder {
1141 num_rows: 8000,
1142 selectivity: 0.1,
1143 seed: 0,
1144 };
1145
1146 let mut test = Test::new("coalesce_filtered_10");
1150 for _ in 0..10 {
1151 test = test
1152 .with_batch(multi_column_batch(0..8000))
1153 .with_filter(filter_builder.next_filter())
1154 }
1155 test.with_batch_size(1024)
1156 .with_expected_output_sizes(vec![1024, 1024, 1024, 1024, 1024, 1024, 1024, 840])
1157 .run();
1158 }
1159
1160 #[test]
1162 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_90() {
1164 let mut filter_builder = RandomFilterBuilder {
1165 num_rows: 800,
1166 selectivity: 0.90,
1167 seed: 0,
1168 };
1169
1170 let mut test = Test::new("coalesce_filtered_90");
1174 for _ in 0..10 {
1175 test = test
1176 .with_batch(multi_column_batch(0..800))
1177 .with_filter(filter_builder.next_filter())
1178 }
1179 test.with_batch_size(1024)
1180 .with_expected_output_sizes(vec![1024, 1024, 1024, 1024, 1024, 1024, 1024, 13])
1181 .run();
1182 }
1183
1184 #[test]
1186 #[cfg_attr(miri, ignore)] fn test_coalesce_filtered_mixed() {
1188 let mut filter_builder = RandomFilterBuilder {
1189 num_rows: 800,
1190 selectivity: 0.90,
1191 seed: 0,
1192 };
1193
1194 let mut test = Test::new("coalesce_filtered_mixed");
1195 for _ in 0..3 {
1196 let mut all_filter_builder = BooleanBufferBuilder::new(1000);
1199 all_filter_builder.append_n(500, true);
1200 all_filter_builder.append_n(1, false);
1201 all_filter_builder.append_n(499, false);
1202 let all_filter = all_filter_builder.build();
1203
1204 test = test
1205 .with_batch(multi_column_batch(0..1000))
1206 .with_filter(BooleanArray::from(all_filter))
1207 .with_batch(multi_column_batch(0..800))
1208 .with_filter(filter_builder.next_filter());
1209 filter_builder.selectivity *= 0.6;
1211 }
1212
1213 test.with_batch_size(250)
1216 .with_expected_output_sizes(vec![
1217 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 179,
1218 ])
1219 .run();
1220 }
1221
1222 #[test]
1223 fn test_push_batch_with_filter_builder() {
1224 let batch = record_batch!(("a", Int32, [1, 2, 3, 4])).unwrap();
1225 let filter = BooleanArray::from(vec![Some(true), None, Some(false), Some(true)]);
1227 let expected = record_batch!(("a", Int32, [1, 4])).unwrap();
1228
1229 let with_count = unsafe { FilterBuilder::new(&filter).with_count(2) };
1231 let optimized = FilterBuilder::new(&filter).optimize();
1234 for filter_builder in [with_count, optimized] {
1235 let mut coalescer = BatchCoalescer::new(batch.schema(), 10);
1236 coalescer
1237 .push_batch_with_filter_builder(batch.clone(), filter_builder)
1238 .unwrap();
1239 coalescer.finish_buffered_batch().unwrap();
1240 assert_eq!(coalescer.next_completed_batch().unwrap(), expected);
1241 assert!(coalescer.next_completed_batch().is_none());
1242 }
1243 }
1244
1245 #[test]
1246 fn test_coalesce_non_null() {
1247 Test::new("coalesce_non_null")
1248 .with_batch(uint32_batch_non_null(0..3000))
1250 .with_batch(uint32_batch_non_null(0..1040))
1251 .with_batch_size(1024)
1252 .with_expected_output_sizes(vec![1024, 1024, 1024, 968])
1253 .run();
1254 }
1255 #[test]
1256 #[cfg_attr(miri, ignore)] fn test_utf8_split() {
1258 Test::new("coalesce_utf8")
1259 .with_batch(utf8_batch(0..3000))
1261 .with_batch(utf8_batch(0..1040))
1262 .with_batch_size(1024)
1263 .with_expected_output_sizes(vec![1024, 1024, 1024, 968])
1264 .run();
1265 }
1266
1267 #[test]
1268 fn test_string_view_no_views() {
1269 let output_batches = Test::new("coalesce_string_view_no_views")
1270 .with_batch(stringview_batch([Some("foo"), Some("bar")]))
1272 .with_batch(stringview_batch([Some("baz"), Some("qux")]))
1273 .with_expected_output_sizes(vec![4])
1274 .run();
1275
1276 expect_buffer_layout(
1277 col_as_string_view("c0", output_batches.first().unwrap()),
1278 vec![],
1279 );
1280 }
1281
1282 #[test]
1283 fn test_string_view_batch_small_no_compact() {
1284 let batch = stringview_batch_repeated(1000, [Some("a"), Some("b"), Some("c")]);
1286 let output_batches = Test::new("coalesce_string_view_batch_small_no_compact")
1287 .with_batch(batch.clone())
1288 .with_expected_output_sizes(vec![1000])
1289 .run();
1290
1291 let array = col_as_string_view("c0", &batch);
1292 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1293 assert_eq!(array.data_buffers().len(), 0);
1294 assert_eq!(array.data_buffers().len(), gc_array.data_buffers().len()); expect_buffer_layout(gc_array, vec![]);
1297 }
1298
1299 #[test]
1300 #[cfg_attr(miri, ignore)] fn test_string_view_batch_large_no_compact() {
1302 let batch = stringview_batch_repeated(1000, [Some("This string is longer than 12 bytes")]);
1304 let output_batches = Test::new("coalesce_string_view_batch_large_no_compact")
1305 .with_batch(batch.clone())
1306 .with_batch_size(1000)
1307 .with_expected_output_sizes(vec![1000])
1308 .run();
1309
1310 let array = col_as_string_view("c0", &batch);
1311 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1312 assert_eq!(array.data_buffers().len(), 5);
1313 assert_eq!(array.data_buffers().len(), gc_array.data_buffers().len()); expect_buffer_layout(
1316 gc_array,
1317 vec![
1318 ExpectedLayout {
1319 len: 8190,
1320 capacity: 8192,
1321 },
1322 ExpectedLayout {
1323 len: 8190,
1324 capacity: 8192,
1325 },
1326 ExpectedLayout {
1327 len: 8190,
1328 capacity: 8192,
1329 },
1330 ExpectedLayout {
1331 len: 8190,
1332 capacity: 8192,
1333 },
1334 ExpectedLayout {
1335 len: 2240,
1336 capacity: 8192,
1337 },
1338 ],
1339 );
1340 }
1341
1342 #[test]
1343 fn test_string_view_batch_small_with_buffers_no_compact() {
1344 let short_strings = std::iter::repeat(Some("SmallString"));
1346 let long_strings = std::iter::once(Some("This string is longer than 12 bytes"));
1347 let values = short_strings.take(20).chain(long_strings);
1349 let batch = stringview_batch_repeated(1000, values)
1350 .slice(5, 10);
1352 let output_batches = Test::new("coalesce_string_view_batch_small_with_buffers_no_compact")
1353 .with_batch(batch.clone())
1354 .with_batch_size(1000)
1355 .with_expected_output_sizes(vec![10])
1356 .run();
1357
1358 let array = col_as_string_view("c0", &batch);
1359 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1360 assert_eq!(array.data_buffers().len(), 1); assert_eq!(gc_array.data_buffers().len(), 0); }
1363
1364 #[test]
1365 fn test_string_view_batch_large_slice_compact() {
1366 let batch = stringview_batch_repeated(1000, [Some("This string is longer than 12 bytes")])
1368 .slice(11, 22);
1370
1371 let output_batches = Test::new("coalesce_string_view_batch_large_slice_compact")
1372 .with_batch(batch.clone())
1373 .with_batch_size(1000)
1374 .with_expected_output_sizes(vec![22])
1375 .run();
1376
1377 let array = col_as_string_view("c0", &batch);
1378 let gc_array = col_as_string_view("c0", output_batches.first().unwrap());
1379 assert_eq!(array.data_buffers().len(), 5);
1380
1381 expect_buffer_layout(
1382 gc_array,
1383 vec![ExpectedLayout {
1384 len: 770,
1385 capacity: 8192,
1386 }],
1387 );
1388 }
1389
1390 #[test]
1391 #[cfg_attr(miri, ignore)] fn test_string_view_mixed() {
1393 let large_view_batch =
1394 stringview_batch_repeated(1000, [Some("This string is longer than 12 bytes")]);
1395 let small_view_batch = stringview_batch_repeated(1000, [Some("SmallString")]);
1396 let mixed_batch = stringview_batch_repeated(
1397 1000,
1398 [Some("This string is longer than 12 bytes"), Some("Small")],
1399 );
1400 let mixed_batch_nulls = stringview_batch_repeated(
1401 1000,
1402 [
1403 Some("This string is longer than 12 bytes"),
1404 Some("Small"),
1405 None,
1406 ],
1407 );
1408
1409 let output_batches = Test::new("coalesce_string_view_mixed")
1412 .with_batch(large_view_batch.clone())
1413 .with_batch(small_view_batch)
1414 .with_batch(large_view_batch.slice(10, 20))
1416 .with_batch(mixed_batch_nulls)
1417 .with_batch(large_view_batch.slice(10, 20))
1419 .with_batch(mixed_batch)
1420 .with_expected_output_sizes(vec![1024, 1024, 1024, 968])
1421 .run();
1422
1423 expect_buffer_layout(
1424 col_as_string_view("c0", output_batches.first().unwrap()),
1425 vec![
1426 ExpectedLayout {
1427 len: 8190,
1428 capacity: 8192,
1429 },
1430 ExpectedLayout {
1431 len: 8190,
1432 capacity: 8192,
1433 },
1434 ExpectedLayout {
1435 len: 8190,
1436 capacity: 8192,
1437 },
1438 ExpectedLayout {
1439 len: 8190,
1440 capacity: 8192,
1441 },
1442 ExpectedLayout {
1443 len: 2240,
1444 capacity: 8192,
1445 },
1446 ],
1447 );
1448 }
1449
1450 #[test]
1451 #[cfg_attr(miri, ignore)] fn test_string_view_many_small_compact() {
1453 let batch = stringview_batch_repeated(
1456 200,
1457 [Some("This string is 28 bytes long"), Some("small string")],
1458 );
1459 let output_batches = Test::new("coalesce_string_view_many_small_compact")
1460 .with_batch(batch.clone())
1463 .with_batch(batch.clone())
1464 .with_batch(batch.clone())
1465 .with_batch(batch.clone())
1466 .with_batch(batch.clone())
1467 .with_batch(batch.clone())
1468 .with_batch(batch.clone())
1469 .with_batch(batch.clone())
1470 .with_batch(batch.clone())
1471 .with_batch(batch.clone())
1472 .with_batch_size(8000)
1473 .with_expected_output_sizes(vec![2000]) .run();
1475
1476 expect_buffer_layout(
1478 col_as_string_view("c0", output_batches.first().unwrap()),
1479 vec![
1480 ExpectedLayout {
1481 len: 8176,
1482 capacity: 8192,
1483 },
1484 ExpectedLayout {
1485 len: 16380,
1486 capacity: 16384,
1487 },
1488 ExpectedLayout {
1489 len: 3444,
1490 capacity: 32768,
1491 },
1492 ],
1493 );
1494 }
1495
1496 #[test]
1497 #[cfg_attr(miri, ignore)] fn test_string_view_many_small_boundary() {
1499 let batch = stringview_batch_repeated(100, [Some("This string is a power of two=32")]);
1501 let output_batches = Test::new("coalesce_string_view_many_small_boundary")
1502 .with_batches(std::iter::repeat_n(batch, 20))
1503 .with_batch_size(900)
1504 .with_expected_output_sizes(vec![900, 900, 200])
1505 .run();
1506
1507 expect_buffer_layout(
1509 col_as_string_view("c0", output_batches.first().unwrap()),
1510 vec![
1511 ExpectedLayout {
1512 len: 8192,
1513 capacity: 8192,
1514 },
1515 ExpectedLayout {
1516 len: 16384,
1517 capacity: 16384,
1518 },
1519 ExpectedLayout {
1520 len: 4224,
1521 capacity: 32768,
1522 },
1523 ],
1524 );
1525 }
1526
1527 #[test]
1528 #[cfg_attr(miri, ignore)] fn test_string_view_large_small() {
1530 let mixed_batch = stringview_batch_repeated(
1532 200,
1533 [Some("This string is 28 bytes long"), Some("small string")],
1534 );
1535 let all_large = stringview_batch_repeated(
1537 50,
1538 [Some(
1539 "This buffer has only large strings in it so there are no buffer copies",
1540 )],
1541 );
1542
1543 let output_batches = Test::new("coalesce_string_view_large_small")
1544 .with_batch(mixed_batch.clone())
1547 .with_batch(mixed_batch.clone())
1548 .with_batch(all_large.clone())
1549 .with_batch(mixed_batch.clone())
1550 .with_batch(all_large.clone())
1551 .with_batch(mixed_batch.clone())
1552 .with_batch(mixed_batch.clone())
1553 .with_batch(all_large.clone())
1554 .with_batch(mixed_batch.clone())
1555 .with_batch(all_large.clone())
1556 .with_batch_size(8000)
1557 .with_expected_output_sizes(vec![1400])
1558 .run();
1559
1560 expect_buffer_layout(
1561 col_as_string_view("c0", output_batches.first().unwrap()),
1562 vec![
1563 ExpectedLayout {
1564 len: 8190,
1565 capacity: 8192,
1566 },
1567 ExpectedLayout {
1568 len: 16366,
1569 capacity: 16384,
1570 },
1571 ExpectedLayout {
1572 len: 6244,
1573 capacity: 32768,
1574 },
1575 ],
1576 );
1577 }
1578
1579 #[test]
1580 #[cfg_attr(miri, ignore)] fn test_binary_view() {
1582 let values: Vec<Option<&[u8]>> = vec![
1583 Some(b"foo"),
1584 None,
1585 Some(b"A longer string that is more than 12 bytes"),
1586 ];
1587
1588 let binary_view =
1589 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1590 let batch =
1591 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1592
1593 Test::new("coalesce_binary_view")
1594 .with_batch(batch.clone())
1595 .with_batch(batch.clone())
1596 .with_batch_size(512)
1597 .with_expected_output_sizes(vec![512, 512, 512, 464])
1598 .run();
1599 }
1600
1601 #[test]
1602 fn test_binary_view_filtered() {
1603 let values: Vec<Option<&[u8]>> = vec![
1604 Some(b"foo"),
1605 None,
1606 Some(b"A longer string that is more than 12 bytes"),
1607 ];
1608
1609 let binary_view =
1610 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1611 let batch =
1612 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1613 let filter = sparse_filter(1000);
1614
1615 Test::new("coalesce_binary_view_filtered")
1616 .with_batch(batch.clone())
1617 .with_filter(filter.clone())
1618 .with_batch(batch)
1619 .with_filter(filter)
1620 .with_batch_size(256)
1621 .with_expected_output_sizes(vec![250])
1622 .run();
1623 }
1624
1625 #[test]
1626 fn test_binary_view_filtered_inline() {
1627 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1628
1629 let binary_view =
1630 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1631 let batch =
1632 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1633 let filter = sparse_filter(1000);
1634
1635 Test::new("coalesce_binary_view_filtered_inline")
1636 .with_batch(batch.clone())
1637 .with_filter(filter.clone())
1638 .with_batch(batch)
1639 .with_filter(filter)
1640 .with_batch_size(300)
1641 .with_expected_output_sizes(vec![250])
1642 .run();
1643 }
1644
1645 #[test]
1646 fn test_string_view_filtered_inline() {
1647 let values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1648
1649 let string_view =
1650 StringViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1651 let batch =
1652 RecordBatch::try_from_iter(vec![("c0", Arc::new(string_view) as ArrayRef)]).unwrap();
1653 let filter = sparse_filter(1000);
1654
1655 Test::new("coalesce_string_view_filtered_inline")
1656 .with_batch(batch.clone())
1657 .with_filter(filter.clone())
1658 .with_batch(batch)
1659 .with_filter(filter)
1660 .with_batch_size(300)
1661 .with_expected_output_sizes(vec![250])
1662 .run();
1663 }
1664
1665 #[test]
1666 fn test_mixed_inline_binary_view_filtered() {
1667 let int_values =
1668 Int32Array::from_iter((0..1000).map(|v| if v % 5 == 0 { None } else { Some(v) }));
1669 let float_values = arrow_array::Float64Array::from_iter((0..1000).map(|v| Some(v as f64)));
1670 let binary_values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1671 let binary_view = BinaryViewArray::from_iter(
1672 std::iter::repeat(binary_values.iter()).flatten().take(1000),
1673 );
1674
1675 let batch = RecordBatch::try_from_iter(vec![
1676 ("i", Arc::new(int_values) as ArrayRef),
1677 ("f", Arc::new(float_values) as ArrayRef),
1678 ("b", Arc::new(binary_view) as ArrayRef),
1679 ])
1680 .unwrap();
1681
1682 let filter = sparse_filter(1000);
1683
1684 Test::new("coalesce_mixed_inline_binary_view_filtered")
1685 .with_batch(batch.clone())
1686 .with_filter(filter.clone())
1687 .with_batch(batch)
1688 .with_filter(filter)
1689 .with_batch_size(300)
1690 .with_expected_output_sizes(vec![250])
1691 .run();
1692 }
1693
1694 #[test]
1695 fn test_mixed_inline_string_view_filtered() {
1696 let int_values =
1697 Int32Array::from_iter((0..1000).map(|v| if v % 5 == 0 { None } else { Some(v) }));
1698 let float_values = arrow_array::Float64Array::from_iter((0..1000).map(|v| Some(v as f64)));
1699 let string_values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1700 let string_view = StringViewArray::from_iter(
1701 std::iter::repeat(string_values.iter()).flatten().take(1000),
1702 );
1703
1704 let batch = RecordBatch::try_from_iter(vec![
1705 ("i", Arc::new(int_values) as ArrayRef),
1706 ("f", Arc::new(float_values) as ArrayRef),
1707 ("s", Arc::new(string_view) as ArrayRef),
1708 ])
1709 .unwrap();
1710
1711 let filter = sparse_filter(1000);
1712
1713 Test::new("coalesce_mixed_inline_string_view_filtered")
1714 .with_batch(batch.clone())
1715 .with_filter(filter.clone())
1716 .with_batch(batch)
1717 .with_filter(filter)
1718 .with_batch_size(300)
1719 .with_expected_output_sizes(vec![250])
1720 .run();
1721 }
1722
1723 #[test]
1724 fn test_inline_binary_view_sparse() {
1725 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1727 let binary_view =
1728 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1729 let batch =
1730 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1731 let filter = very_sparse_filter(1000);
1732
1733 Test::new("inline_binary_view_sparse")
1734 .with_batch(batch.clone())
1735 .with_filter(filter.clone())
1736 .with_batch(batch)
1737 .with_filter(filter)
1738 .with_batch_size(1024)
1739 .with_expected_output_sizes(vec![100])
1740 .run();
1741 }
1742
1743 #[test]
1744 fn test_inline_string_view_sparse() {
1745 let values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1746 let string_view =
1747 StringViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1748 let batch =
1749 RecordBatch::try_from_iter(vec![("c0", Arc::new(string_view) as ArrayRef)]).unwrap();
1750 let filter = very_sparse_filter(1000);
1751
1752 Test::new("inline_string_view_sparse")
1753 .with_batch(batch.clone())
1754 .with_filter(filter.clone())
1755 .with_batch(batch)
1756 .with_filter(filter)
1757 .with_batch_size(1024)
1758 .with_expected_output_sizes(vec![100])
1759 .run();
1760 }
1761
1762 #[test]
1763 fn test_inline_mixed_sparse() {
1764 let int_values =
1767 Int32Array::from_iter((0..1000).map(|v| if v % 5 == 0 { None } else { Some(v) }));
1768 let float_values = arrow_array::Float64Array::from_iter((0..1000).map(|v| Some(v as f64)));
1769 let string_values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1770 let string_view = StringViewArray::from_iter(
1771 std::iter::repeat(string_values.iter()).flatten().take(1000),
1772 );
1773 let binary_values: Vec<Option<&[u8]>> = vec![Some(b"x"), None, Some(b"abcdef")];
1774 let binary_view = BinaryViewArray::from_iter(
1775 std::iter::repeat(binary_values.iter()).flatten().take(1000),
1776 );
1777
1778 let batch = RecordBatch::try_from_iter(vec![
1779 ("i", Arc::new(int_values) as ArrayRef),
1780 ("f", Arc::new(float_values) as ArrayRef),
1781 ("s", Arc::new(string_view) as ArrayRef),
1782 ("b", Arc::new(binary_view) as ArrayRef),
1783 ])
1784 .unwrap();
1785 let filter = very_sparse_filter(1000);
1786
1787 Test::new("inline_mixed_sparse")
1788 .with_batch(batch.clone())
1789 .with_filter(filter.clone())
1790 .with_batch(batch)
1791 .with_filter(filter)
1792 .with_batch_size(1024)
1793 .with_expected_output_sizes(vec![100])
1794 .run();
1795 }
1796
1797 #[test]
1798 fn test_inline_crosses_target_batch_size() {
1799 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None, Some(b"barbaz")];
1803 let make_batch = || {
1804 let binary_view =
1805 BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
1806 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap()
1807 };
1808 let filter = very_sparse_filter(1000);
1809
1810 Test::new("inline_crosses_target_batch_size")
1811 .with_batch(make_batch())
1812 .with_filter(filter.clone())
1813 .with_batch(make_batch())
1814 .with_filter(filter.clone())
1815 .with_batch(make_batch())
1816 .with_filter(filter)
1817 .with_batch_size(100)
1818 .with_expected_output_sizes(vec![100, 50])
1819 .run();
1820 }
1821
1822 #[test]
1823 fn test_inline_filter_rejects_filter_longer_than_batch() {
1824 let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), Some(b"bar")];
1825 let binary_view = BinaryViewArray::from_iter(values);
1826 let batch =
1827 RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as ArrayRef)]).unwrap();
1828 let filter = BooleanArray::from(vec![true, false, true]);
1829
1830 let mut coalescer = BatchCoalescer::new(batch.schema(), 100);
1831 let result = coalescer.push_batch_with_filter(batch, &filter);
1832 assert!(result.is_err());
1833 let err = result.unwrap_err().to_string();
1834 assert!(
1835 err.contains("Filter predicate of length 3 is larger than target array of length 2"),
1836 "unexpected error: {err}"
1837 );
1838 }
1839
1840 #[test]
1841 fn test_mixed_boolean_inline_string_view_filtered() {
1842 let bool_values = BooleanArray::from_iter((0..1000).map(|v| Some(v % 3 == 0)));
1843 let string_values: Vec<Option<&str>> = vec![Some("foo"), None, Some("barbaz")];
1844 let string_view = StringViewArray::from_iter(
1845 std::iter::repeat(string_values.iter()).flatten().take(1000),
1846 );
1847
1848 let batch = RecordBatch::try_from_iter(vec![
1849 ("b", Arc::new(bool_values) as ArrayRef),
1850 ("s", Arc::new(string_view) as ArrayRef),
1851 ])
1852 .unwrap();
1853
1854 let filter = sparse_filter(1000);
1855
1856 Test::new("coalesce_mixed_boolean_inline_string_view_filtered")
1857 .with_batch(batch.clone())
1858 .with_filter(filter.clone())
1859 .with_batch(batch)
1860 .with_filter(filter)
1861 .with_batch_size(300)
1862 .with_expected_output_sizes(vec![250])
1863 .run();
1864 }
1865
1866 #[test]
1867 fn test_filter_fast_path_schema_capability() {
1868 let supported = Arc::new(Schema::new(vec![
1869 Field::new("primitive", DataType::UInt32, false),
1870 Field::new("utf8_view", DataType::Utf8View, true),
1871 Field::new("binary_view", DataType::BinaryView, true),
1872 Field::new("list", DataType::new_list(DataType::Int32, true), true),
1873 Field::new(
1874 "large_list",
1875 DataType::new_large_list(DataType::Utf8, true),
1876 true,
1877 ),
1878 ]));
1879 let coalescer = BatchCoalescer::new(supported, 100);
1880 assert!(!coalescer.has_non_specialized_filter_columns);
1881 assert!(!coalescer.has_byte_array_columns);
1882
1883 let utf8 = Arc::new(Schema::new(vec![Field::new("utf8", DataType::Utf8, true)]));
1884 let coalescer = BatchCoalescer::new(utf8, 100);
1885 assert!(!coalescer.has_non_specialized_filter_columns);
1886 assert!(coalescer.has_byte_array_columns);
1887
1888 let boolean = Arc::new(Schema::new(vec![Field::new(
1889 "boolean",
1890 DataType::Boolean,
1891 true,
1892 )]));
1893 let coalescer = BatchCoalescer::new(boolean, 100);
1894 assert!(!coalescer.has_non_specialized_filter_columns);
1895 }
1896
1897 fn sparse_filter_byte_array_input() -> (RecordBatch, BooleanArray) {
1898 let mut strings = vec![Some("padding"); 4096];
1899 strings[..4].copy_from_slice(&[Some("zero"), None, Some("two"), Some("three")]);
1900 let batch = RecordBatch::try_from_iter([(
1901 "bytes",
1902 Arc::new(StringArray::from(strings)) as ArrayRef,
1903 )])
1904 .unwrap();
1905 let filter = BooleanArray::from_iter((0..batch.num_rows()).map(|idx| Some(idx < 4)));
1906 (batch, filter)
1907 }
1908
1909 #[test]
1910 fn test_sparse_filter_byte_array_direct_copy() {
1911 let (batch, filter) = sparse_filter_byte_array_input();
1912 assert!(should_use_byte_array_direct_copy(batch.num_rows(), 4));
1913
1914 let fallback_filter =
1915 BooleanArray::from_iter((0..batch.num_rows()).map(|idx| Some(idx < 5)));
1916 assert!(!should_use_byte_array_direct_copy(batch.num_rows(), 5));
1917 let mut coalescer = BatchCoalescer::new(batch.schema(), 6);
1918 coalescer
1919 .push_batch_with_filter(batch.clone(), &fallback_filter)
1920 .unwrap();
1921 coalescer.finish_buffered_batch().unwrap();
1922 let expected = filter_record_batch(&batch, &fallback_filter).unwrap();
1923 assert_eq!(coalescer.next_completed_batch().unwrap(), expected);
1924
1925 Test::new("coalesce_sparse_filter_utf8_direct_copy")
1926 .with_batch(batch)
1927 .with_filter(filter)
1928 .with_batch_size(4)
1929 .with_expected_output_sizes([4])
1930 .run();
1931 }
1932
1933 #[derive(Debug, Clone, PartialEq)]
1934 struct ExpectedLayout {
1935 len: usize,
1936 capacity: usize,
1937 }
1938
1939 fn expect_buffer_layout(array: &StringViewArray, expected: Vec<ExpectedLayout>) {
1941 let actual = array
1942 .data_buffers()
1943 .iter()
1944 .map(|b| ExpectedLayout {
1945 len: b.len(),
1946 capacity: b.capacity(),
1947 })
1948 .collect::<Vec<_>>();
1949
1950 assert_eq!(
1951 actual, expected,
1952 "Expected buffer layout {expected:#?} but got {actual:#?}"
1953 );
1954 }
1955
1956 #[derive(Debug, Clone)]
1963 struct Test {
1964 name: String,
1966 input_batches: Vec<RecordBatch>,
1968 filters: Vec<BooleanArray>,
1973 schema: Option<SchemaRef>,
1975 expected_output_sizes: Vec<usize>,
1977 target_batch_size: usize,
1979 }
1980
1981 impl Default for Test {
1982 fn default() -> Self {
1983 Self {
1984 name: String::new(),
1985 input_batches: vec![],
1986 filters: vec![],
1987 schema: None,
1988 expected_output_sizes: vec![],
1989 target_batch_size: 1024,
1990 }
1991 }
1992 }
1993
1994 impl Test {
1995 fn new(name: impl Into<String>) -> Self {
1996 Self {
1997 name: name.into(),
1998 ..Self::default()
1999 }
2000 }
2001
2002 fn with_description(mut self, description: &str) -> Self {
2004 self.name.push_str(": ");
2005 self.name.push_str(description);
2006 self
2007 }
2008
2009 fn with_batch_size(mut self, target_batch_size: usize) -> Self {
2011 self.target_batch_size = target_batch_size;
2012 self
2013 }
2014
2015 fn with_batch(mut self, batch: RecordBatch) -> Self {
2017 self.input_batches.push(batch);
2018 self
2019 }
2020
2021 fn with_filter(mut self, filter: BooleanArray) -> Self {
2023 self.filters.push(filter);
2024 self
2025 }
2026
2027 fn with_batches(mut self, batches: impl IntoIterator<Item = RecordBatch>) -> Self {
2029 self.input_batches = batches.into_iter().collect();
2030 self
2031 }
2032
2033 fn with_schema(mut self, schema: SchemaRef) -> Self {
2035 self.schema = Some(schema);
2036 self
2037 }
2038
2039 fn with_expected_output_sizes(mut self, sizes: impl IntoIterator<Item = usize>) -> Self {
2041 self.expected_output_sizes.extend(sizes);
2042 self
2043 }
2044
2045 fn run(self) -> Vec<RecordBatch> {
2049 let mut extra_tests = vec![];
2054 extra_tests.push(self.clone().make_half_non_nullable());
2055 extra_tests.push(self.clone().insert_empty_batches());
2056 let single_column_tests = self.make_single_column_tests();
2057 for test in single_column_tests {
2058 extra_tests.push(test.clone().make_half_non_nullable());
2059 extra_tests.push(test);
2060 }
2061
2062 let results = self.run_inner();
2065 for extra in extra_tests {
2067 extra.run_inner();
2068 }
2069
2070 results
2071 }
2072
2073 fn run_inner(self) -> Vec<RecordBatch> {
2075 let expected_output = self.expected_output();
2076 let schema = self.schema();
2077
2078 let Self {
2079 name,
2080 input_batches,
2081 filters,
2082 schema: _,
2083 target_batch_size,
2084 expected_output_sizes,
2085 } = self;
2086
2087 println!("Running test '{name}'");
2088
2089 let had_input = input_batches.iter().any(|b| b.num_rows() > 0);
2090
2091 let mut coalescer = BatchCoalescer::new(Arc::clone(&schema), target_batch_size);
2092
2093 let mut filters = filters.into_iter();
2095 for batch in input_batches {
2096 if let Some(filter) = filters.next() {
2097 coalescer.push_batch_with_filter(batch, &filter).unwrap();
2098 } else {
2099 coalescer.push_batch(batch).unwrap();
2100 }
2101 }
2102 assert_eq!(schema, coalescer.schema());
2103
2104 if had_input {
2105 assert!(!coalescer.is_empty(), "Coalescer should not be empty");
2106 } else {
2107 assert!(coalescer.is_empty(), "Coalescer should be empty");
2108 }
2109
2110 coalescer.finish_buffered_batch().unwrap();
2111 if had_input {
2112 assert!(
2113 coalescer.has_completed_batch(),
2114 "Coalescer should have completed batches"
2115 );
2116 }
2117
2118 let mut output_batches = vec![];
2119 while let Some(batch) = coalescer.next_completed_batch() {
2120 output_batches.push(batch);
2121 }
2122
2123 let mut starting_idx = 0;
2125 let actual_output_sizes: Vec<usize> =
2126 output_batches.iter().map(|b| b.num_rows()).collect();
2127 assert_eq!(
2128 expected_output_sizes, actual_output_sizes,
2129 "Unexpected number of rows in output batches\n\
2130 Expected\n{expected_output_sizes:#?}\nActual:{actual_output_sizes:#?}"
2131 );
2132 let iter = expected_output_sizes
2133 .iter()
2134 .zip(output_batches.iter())
2135 .enumerate();
2136
2137 for (i, (expected_size, batch)) in iter {
2139 let expected_batch = expected_output.slice(starting_idx, *expected_size);
2142 let expected_batch = normalize_batch(expected_batch);
2143 let batch = normalize_batch(batch.clone());
2144 assert_eq!(
2145 expected_batch, batch,
2146 "Unexpected content in batch {i}:\
2147 \n\nExpected:\n{expected_batch:#?}\n\nActual:\n{batch:#?}"
2148 );
2149 starting_idx += *expected_size;
2150 }
2151 output_batches
2152 }
2153
2154 fn schema(&self) -> SchemaRef {
2157 self.schema
2158 .clone()
2159 .unwrap_or_else(|| Arc::clone(&self.input_batches[0].schema()))
2160 }
2161
2162 fn expected_output(&self) -> RecordBatch {
2164 let schema = self.schema();
2165 if self.filters.is_empty() {
2166 return concat_batches(&schema, &self.input_batches).unwrap();
2167 }
2168
2169 let mut filters = self.filters.iter();
2170 let filtered_batches = self
2171 .input_batches
2172 .iter()
2173 .map(|batch| {
2174 if let Some(filter) = filters.next() {
2175 filter_record_batch(batch, filter).unwrap()
2176 } else {
2177 batch.clone()
2178 }
2179 })
2180 .collect::<Vec<_>>();
2181 concat_batches(&schema, &filtered_batches).unwrap()
2182 }
2183
2184 fn make_half_non_nullable(mut self) -> Self {
2187 self.input_batches = self
2189 .input_batches
2190 .iter()
2191 .enumerate()
2192 .map(|(i, batch)| {
2193 if i % 2 == 1 {
2194 batch.clone()
2195 } else {
2196 Self::remove_nulls_from_batch(batch)
2197 }
2198 })
2199 .collect();
2200 self.with_description("non-nullable")
2201 }
2202
2203 fn insert_empty_batches(mut self) -> Self {
2205 let empty_batch = RecordBatch::new_empty(self.schema());
2206 self.input_batches = self
2207 .input_batches
2208 .into_iter()
2209 .flat_map(|batch| [empty_batch.clone(), batch])
2210 .collect();
2211 let empty_filters = BooleanArray::builder(0).finish();
2212 self.filters = self
2213 .filters
2214 .into_iter()
2215 .flat_map(|filter| [empty_filters.clone(), filter])
2216 .collect();
2217 self.with_description("empty batches inserted")
2218 }
2219
2220 fn remove_nulls_from_batch(batch: &RecordBatch) -> RecordBatch {
2222 let new_columns = batch
2223 .columns()
2224 .iter()
2225 .map(Self::remove_nulls_from_array)
2226 .collect::<Vec<_>>();
2227 let options = RecordBatchOptions::new().with_row_count(Some(batch.num_rows()));
2228 RecordBatch::try_new_with_options(batch.schema(), new_columns, &options).unwrap()
2229 }
2230
2231 fn remove_nulls_from_array(array: &ArrayRef) -> ArrayRef {
2232 make_array(array.to_data().into_builder().nulls(None).build().unwrap())
2233 }
2234
2235 fn make_single_column_tests(&self) -> Vec<Self> {
2241 let original_schema = self.schema();
2242 let mut new_tests = vec![];
2243 for column in original_schema.fields() {
2244 let single_column_schema = Arc::new(Schema::new(vec![column.clone()]));
2245
2246 let single_column_batches = self.input_batches.iter().map(|batch| {
2247 let single_column = batch.column_by_name(column.name()).unwrap();
2248 RecordBatch::try_new(
2249 Arc::clone(&single_column_schema),
2250 vec![single_column.clone()],
2251 )
2252 .unwrap()
2253 });
2254
2255 let single_column_test = self
2256 .clone()
2257 .with_schema(Arc::clone(&single_column_schema))
2258 .with_batches(single_column_batches)
2259 .with_description("single column")
2260 .with_description(column.name());
2261
2262 new_tests.push(single_column_test);
2263 }
2264 new_tests
2265 }
2266 }
2267
2268 fn uint32_batch<T: std::iter::Iterator<Item = u32>>(range: T) -> RecordBatch {
2271 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, true)]));
2272
2273 let array = UInt32Array::from_iter(range.map(|i| if i % 3 == 0 { None } else { Some(i) }));
2274 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2275 }
2276
2277 fn uint32_batch_non_null<T: std::iter::Iterator<Item = u32>>(range: T) -> RecordBatch {
2279 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, false)]));
2280
2281 let array = UInt32Array::from_iter_values(range);
2282 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2283 }
2284
2285 fn uint64_batch_non_null<T: std::iter::Iterator<Item = u64>>(range: T) -> RecordBatch {
2287 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt64, false)]));
2288
2289 let array = UInt64Array::from_iter_values(range);
2290 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2291 }
2292
2293 fn utf8_batch(range: Range<u32>) -> RecordBatch {
2296 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::Utf8, true)]));
2297
2298 let array = StringArray::from_iter(range.map(|i| {
2299 if i % 3 == 0 {
2300 None
2301 } else {
2302 Some(format!("value{i}"))
2303 }
2304 }));
2305
2306 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2307 }
2308
2309 fn stringview_batch<'a>(values: impl IntoIterator<Item = Option<&'a str>>) -> RecordBatch {
2311 let schema = Arc::new(Schema::new(vec![Field::new(
2312 "c0",
2313 DataType::Utf8View,
2314 false,
2315 )]));
2316
2317 let array = StringViewArray::from_iter(values);
2318 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2319 }
2320
2321 fn stringview_batch_repeated<'a>(
2324 num_rows: usize,
2325 values: impl IntoIterator<Item = Option<&'a str>>,
2326 ) -> RecordBatch {
2327 let schema = Arc::new(Schema::new(vec![Field::new(
2328 "c0",
2329 DataType::Utf8View,
2330 true,
2331 )]));
2332
2333 let values: Vec<_> = values.into_iter().collect();
2335 let values_iter = std::iter::repeat(values.iter())
2336 .flatten()
2337 .copied()
2338 .take(num_rows);
2339
2340 let mut builder = StringViewBuilder::with_capacity(100).with_fixed_block_size(8192);
2341 for val in values_iter {
2342 builder.append_option(val);
2343 }
2344
2345 let array = builder.finish();
2346 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(array)]).unwrap()
2347 }
2348
2349 fn multi_column_batch(range: Range<i32>) -> RecordBatch {
2351 let int64_array = Int64Array::from_iter(
2352 range
2353 .clone()
2354 .map(|v| if v % 5 == 0 { None } else { Some(v as i64) }),
2355 );
2356 let string_view_array = StringViewArray::from_iter(range.clone().map(|v| {
2357 if v % 5 == 0 {
2358 None
2359 } else if v % 7 == 0 {
2360 Some(format!("This is a string longer than 12 bytes{v}"))
2361 } else {
2362 Some(format!("Short {v}"))
2363 }
2364 }));
2365 let string_array = StringArray::from_iter(range.clone().map(|v| {
2366 if v % 11 == 0 {
2367 None
2368 } else {
2369 Some(format!("Value {v}"))
2370 }
2371 }));
2372 let timestamp_array = TimestampNanosecondArray::from_iter(range.map(|v| {
2373 if v % 3 == 0 {
2374 None
2375 } else {
2376 Some(v as i64 * 1000) }
2378 }))
2379 .with_timezone("America/New_York");
2380
2381 RecordBatch::try_from_iter(vec![
2382 ("int64", Arc::new(int64_array) as ArrayRef),
2383 ("stringview", Arc::new(string_view_array) as ArrayRef),
2384 ("string", Arc::new(string_array) as ArrayRef),
2385 ("timestamp", Arc::new(timestamp_array) as ArrayRef),
2386 ])
2387 .unwrap()
2388 }
2389
2390 #[derive(Debug)]
2396 struct RandomFilterBuilder {
2397 num_rows: usize,
2399 selectivity: f64,
2402 seed: u64,
2405 }
2406 impl RandomFilterBuilder {
2407 fn next_filter(&mut self) -> BooleanArray {
2410 assert!(self.selectivity >= 0.0 && self.selectivity <= 1.0);
2411 let mut rng = rand::rngs::StdRng::seed_from_u64(self.seed);
2412 self.seed += 1;
2413 BooleanArray::from_iter(
2414 (0..self.num_rows)
2415 .map(|_| rng.random_bool(self.selectivity))
2416 .map(Some),
2417 )
2418 }
2419 }
2420
2421 fn col_as_string_view<'b>(name: &str, batch: &'b RecordBatch) -> &'b StringViewArray {
2423 batch
2424 .column_by_name(name)
2425 .expect("column not found")
2426 .as_string_view_opt()
2427 .expect("column is not a string view")
2428 }
2429
2430 fn sparse_filter(len: usize) -> BooleanArray {
2432 BooleanArray::from_iter((0..len).map(|idx| Some(idx % 8 == 0)))
2433 }
2434
2435 fn very_sparse_filter(len: usize) -> BooleanArray {
2439 BooleanArray::from_iter((0..len).map(|idx| Some(idx % 20 == 0)))
2440 }
2441
2442 fn normalize_batch(batch: RecordBatch) -> RecordBatch {
2445 let (schema, mut columns, row_count) = batch.into_parts();
2447
2448 for column in &mut columns {
2449 if let Some(string_view) = column.as_string_view_opt() {
2450 let mut builder = StringViewBuilder::new();
2453 for s in string_view {
2454 builder.append_option(s);
2455 }
2456 *column = Arc::new(builder.finish());
2457 continue;
2458 }
2459
2460 if let Some(binary_view) = column.as_binary_view_opt() {
2461 *column = Arc::new(BinaryViewArray::from_iter(binary_view.iter()));
2462 }
2463 }
2464
2465 let options = RecordBatchOptions::new().with_row_count(Some(row_count));
2466 RecordBatch::try_new_with_options(schema, columns, &options).unwrap()
2467 }
2468
2469 fn create_test_batch(num_rows: usize) -> RecordBatch {
2471 let schema = Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)]));
2472 let array = Int32Array::from_iter_values(0..num_rows as i32);
2473 RecordBatch::try_new(schema, vec![Arc::new(array)]).unwrap()
2474 }
2475 #[test]
2476 fn test_biggest_coalesce_batch_size_none_default() {
2477 let mut coalescer = BatchCoalescer::new(
2479 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2480 100,
2481 );
2482
2483 let large_batch = create_test_batch(1000);
2485 coalescer.push_batch(large_batch).unwrap();
2486
2487 let mut output_batches = vec![];
2489 while let Some(batch) = coalescer.next_completed_batch() {
2490 output_batches.push(batch);
2491 }
2492
2493 coalescer.finish_buffered_batch().unwrap();
2494 while let Some(batch) = coalescer.next_completed_batch() {
2495 output_batches.push(batch);
2496 }
2497
2498 assert_eq!(output_batches.len(), 10);
2500 for batch in output_batches {
2501 assert_eq!(batch.num_rows(), 100);
2502 }
2503 }
2504
2505 #[test]
2506 fn test_biggest_coalesce_batch_size_bypass_large_batch() {
2507 let mut coalescer = BatchCoalescer::new(
2509 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2510 100,
2511 );
2512 coalescer.set_biggest_coalesce_batch_size(Some(500));
2513
2514 let large_batch = create_test_batch(1000);
2516 coalescer.push_batch(large_batch.clone()).unwrap();
2517
2518 assert!(coalescer.has_completed_batch());
2520 let output_batch = coalescer.next_completed_batch().unwrap();
2521 assert_eq!(output_batch.num_rows(), 1000);
2522
2523 assert!(!coalescer.has_completed_batch());
2525 assert_eq!(coalescer.get_buffered_rows(), 0);
2526 }
2527
2528 #[test]
2529 fn test_biggest_coalesce_batch_size_coalesce_small_batch() {
2530 let mut coalescer = BatchCoalescer::new(
2532 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2533 100,
2534 )
2535 .with_biggest_coalesce_batch_size(Some(500));
2536
2537 let small_batch = create_test_batch(50);
2539 coalescer.push_batch(small_batch.clone()).unwrap();
2540
2541 assert!(!coalescer.has_completed_batch());
2543 assert_eq!(coalescer.get_buffered_rows(), 50);
2544
2545 coalescer.push_batch(small_batch).unwrap();
2547
2548 assert!(coalescer.has_completed_batch());
2550 let output_batch = coalescer.next_completed_batch().unwrap();
2551 let size = output_batch
2552 .column(0)
2553 .as_primitive::<Int32Type>()
2554 .get_buffer_memory_size();
2555 assert_eq!(size, 400); assert_eq!(output_batch.num_rows(), 100);
2557
2558 assert_eq!(coalescer.get_buffered_rows(), 0);
2559 }
2560
2561 #[test]
2562 fn test_biggest_coalesce_batch_size_equal_boundary() {
2563 let mut coalescer = BatchCoalescer::new(
2565 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2566 100,
2567 );
2568 coalescer.set_biggest_coalesce_batch_size(Some(500));
2569
2570 let boundary_batch = create_test_batch(500);
2572 coalescer.push_batch(boundary_batch).unwrap();
2573
2574 let mut output_count = 0;
2576 while coalescer.next_completed_batch().is_some() {
2577 output_count += 1;
2578 }
2579
2580 coalescer.finish_buffered_batch().unwrap();
2581 while coalescer.next_completed_batch().is_some() {
2582 output_count += 1;
2583 }
2584
2585 assert_eq!(output_count, 5);
2587 }
2588
2589 #[test]
2590 fn test_biggest_coalesce_batch_size_first_large_then_consecutive_bypass() {
2591 let mut coalescer = BatchCoalescer::new(
2594 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2595 100,
2596 );
2597 coalescer.set_biggest_coalesce_batch_size(Some(200));
2598
2599 let small_batch = create_test_batch(50);
2600
2601 coalescer.push_batch(small_batch).unwrap();
2603 assert_eq!(coalescer.get_buffered_rows(), 50);
2604 assert!(!coalescer.has_completed_batch());
2605
2606 let large_batch1 = create_test_batch(250);
2608 coalescer.push_batch(large_batch1).unwrap();
2609
2610 let mut completed_batches = vec![];
2612 while let Some(batch) = coalescer.next_completed_batch() {
2613 completed_batches.push(batch);
2614 }
2615 assert_eq!(completed_batches.len(), 3);
2616 assert_eq!(coalescer.get_buffered_rows(), 0);
2617
2618 let large_batch2 = create_test_batch(300);
2620 let large_batch3 = create_test_batch(400);
2621
2622 coalescer.push_batch(large_batch2).unwrap();
2624 assert!(coalescer.has_completed_batch());
2625 let output = coalescer.next_completed_batch().unwrap();
2626 assert_eq!(output.num_rows(), 300); assert_eq!(coalescer.get_buffered_rows(), 0);
2628
2629 coalescer.push_batch(large_batch3).unwrap();
2631 assert!(coalescer.has_completed_batch());
2632 let output = coalescer.next_completed_batch().unwrap();
2633 assert_eq!(output.num_rows(), 400); assert_eq!(coalescer.get_buffered_rows(), 0);
2635 }
2636
2637 #[test]
2638 fn test_biggest_coalesce_batch_size_empty_batch() {
2639 let mut coalescer = BatchCoalescer::new(
2641 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2642 100,
2643 );
2644 coalescer.set_biggest_coalesce_batch_size(Some(50));
2645
2646 let empty_batch = create_test_batch(0);
2647 coalescer.push_batch(empty_batch).unwrap();
2648
2649 assert!(!coalescer.has_completed_batch());
2651 assert_eq!(coalescer.get_buffered_rows(), 0);
2652 }
2653
2654 #[test]
2655 fn test_biggest_coalesce_batch_size_with_buffered_data_no_bypass() {
2656 let mut coalescer = BatchCoalescer::new(
2658 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2659 100,
2660 );
2661 coalescer.set_biggest_coalesce_batch_size(Some(200));
2662
2663 let small_batch = create_test_batch(30);
2665 coalescer.push_batch(small_batch.clone()).unwrap();
2666 coalescer.push_batch(small_batch).unwrap();
2667 assert_eq!(coalescer.get_buffered_rows(), 60);
2668
2669 let large_batch = create_test_batch(250);
2671 coalescer.push_batch(large_batch).unwrap();
2672
2673 let mut completed_batches = vec![];
2678 while let Some(batch) = coalescer.next_completed_batch() {
2679 completed_batches.push(batch);
2680 }
2681
2682 assert_eq!(completed_batches.len(), 3);
2683 for batch in &completed_batches {
2684 assert_eq!(batch.num_rows(), 100);
2685 }
2686 assert_eq!(coalescer.get_buffered_rows(), 10);
2687 }
2688
2689 #[test]
2690 fn test_biggest_coalesce_batch_size_zero_limit() {
2691 let mut coalescer = BatchCoalescer::new(
2693 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2694 100,
2695 );
2696 coalescer.set_biggest_coalesce_batch_size(Some(0));
2697
2698 let tiny_batch = create_test_batch(1);
2700 coalescer.push_batch(tiny_batch).unwrap();
2701
2702 assert!(coalescer.has_completed_batch());
2703 let output = coalescer.next_completed_batch().unwrap();
2704 assert_eq!(output.num_rows(), 1);
2705 }
2706
2707 #[test]
2708 fn test_biggest_coalesce_batch_size_bypass_only_when_no_buffer() {
2709 let mut coalescer = BatchCoalescer::new(
2711 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2712 100,
2713 );
2714 coalescer.set_biggest_coalesce_batch_size(Some(200));
2715
2716 let large_batch = create_test_batch(300);
2718 coalescer.push_batch(large_batch.clone()).unwrap();
2719
2720 assert!(coalescer.has_completed_batch());
2721 let output = coalescer.next_completed_batch().unwrap();
2722 assert_eq!(output.num_rows(), 300); assert_eq!(coalescer.get_buffered_rows(), 0);
2724
2725 let small_batch = create_test_batch(50);
2727 coalescer.push_batch(small_batch).unwrap();
2728 assert_eq!(coalescer.get_buffered_rows(), 50);
2729
2730 coalescer.push_batch(large_batch).unwrap();
2732
2733 let mut completed_batches = vec![];
2736 while let Some(batch) = coalescer.next_completed_batch() {
2737 completed_batches.push(batch);
2738 }
2739
2740 assert_eq!(completed_batches.len(), 3);
2741 for batch in &completed_batches {
2742 assert_eq!(batch.num_rows(), 100);
2743 }
2744 assert_eq!(coalescer.get_buffered_rows(), 50);
2745 }
2746
2747 #[test]
2748 fn test_biggest_coalesce_batch_size_consecutive_large_batches_scenario() {
2749 let mut coalescer = BatchCoalescer::new(
2751 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2752 1000,
2753 );
2754 coalescer.set_biggest_coalesce_batch_size(Some(500));
2755
2756 coalescer.push_batch(create_test_batch(20)).unwrap();
2758 coalescer.push_batch(create_test_batch(20)).unwrap();
2759 coalescer.push_batch(create_test_batch(30)).unwrap();
2760
2761 assert_eq!(coalescer.get_buffered_rows(), 70);
2762 assert!(!coalescer.has_completed_batch());
2763
2764 coalescer.push_batch(create_test_batch(700)).unwrap();
2766
2767 assert_eq!(coalescer.get_buffered_rows(), 770);
2769 assert!(!coalescer.has_completed_batch());
2770
2771 coalescer.push_batch(create_test_batch(600)).unwrap();
2773
2774 let mut outputs = vec![];
2776 while let Some(batch) = coalescer.next_completed_batch() {
2777 outputs.push(batch);
2778 }
2779 assert_eq!(outputs.len(), 2); assert_eq!(outputs[0].num_rows(), 770);
2781 assert_eq!(outputs[1].num_rows(), 600);
2782 assert_eq!(coalescer.get_buffered_rows(), 0);
2783
2784 let remaining_batches = [700, 900, 700, 600];
2786 for &size in &remaining_batches {
2787 coalescer.push_batch(create_test_batch(size)).unwrap();
2788
2789 assert!(coalescer.has_completed_batch());
2790 let output = coalescer.next_completed_batch().unwrap();
2791 assert_eq!(output.num_rows(), size);
2792 assert_eq!(coalescer.get_buffered_rows(), 0);
2793 }
2794 }
2795
2796 #[test]
2797 fn test_biggest_coalesce_batch_size_truly_consecutive_large_bypass() {
2798 let mut coalescer = BatchCoalescer::new(
2801 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2802 100,
2803 );
2804 coalescer.set_biggest_coalesce_batch_size(Some(200));
2805
2806 let large_batches = vec![
2808 create_test_batch(300),
2809 create_test_batch(400),
2810 create_test_batch(350),
2811 create_test_batch(500),
2812 ];
2813
2814 let mut all_outputs = vec![];
2815
2816 for (i, large_batch) in large_batches.into_iter().enumerate() {
2817 let expected_size = large_batch.num_rows();
2818
2819 assert_eq!(
2821 coalescer.get_buffered_rows(),
2822 0,
2823 "Buffer should be empty before batch {i}"
2824 );
2825
2826 coalescer.push_batch(large_batch).unwrap();
2827
2828 assert!(
2830 coalescer.has_completed_batch(),
2831 "Should have completed batch after pushing batch {i}"
2832 );
2833
2834 let output = coalescer.next_completed_batch().unwrap();
2835 assert_eq!(
2836 output.num_rows(),
2837 expected_size,
2838 "Batch {i} should have bypassed with original size"
2839 );
2840
2841 assert!(
2843 !coalescer.has_completed_batch(),
2844 "Should have no more completed batches after batch {i}"
2845 );
2846 assert_eq!(
2847 coalescer.get_buffered_rows(),
2848 0,
2849 "Buffer should be empty after batch {i}"
2850 );
2851
2852 all_outputs.push(output);
2853 }
2854
2855 assert_eq!(all_outputs.len(), 4);
2857 assert_eq!(all_outputs[0].num_rows(), 300);
2858 assert_eq!(all_outputs[1].num_rows(), 400);
2859 assert_eq!(all_outputs[2].num_rows(), 350);
2860 assert_eq!(all_outputs[3].num_rows(), 500);
2861 }
2862
2863 #[test]
2864 fn test_biggest_coalesce_batch_size_reset_consecutive_on_small_batch() {
2865 let mut coalescer = BatchCoalescer::new(
2867 Arc::new(Schema::new(vec![Field::new("c0", DataType::Int32, false)])),
2868 100,
2869 );
2870 coalescer.set_biggest_coalesce_batch_size(Some(200));
2871
2872 coalescer.push_batch(create_test_batch(300)).unwrap();
2874 let output = coalescer.next_completed_batch().unwrap();
2875 assert_eq!(output.num_rows(), 300);
2876
2877 coalescer.push_batch(create_test_batch(400)).unwrap();
2879 let output = coalescer.next_completed_batch().unwrap();
2880 assert_eq!(output.num_rows(), 400);
2881
2882 coalescer.push_batch(create_test_batch(50)).unwrap();
2884 assert_eq!(coalescer.get_buffered_rows(), 50);
2885
2886 coalescer.push_batch(create_test_batch(350)).unwrap();
2888
2889 let mut outputs = vec![];
2891 while let Some(batch) = coalescer.next_completed_batch() {
2892 outputs.push(batch);
2893 }
2894 assert_eq!(outputs.len(), 4);
2895 for batch in outputs {
2896 assert_eq!(batch.num_rows(), 100);
2897 }
2898 assert_eq!(coalescer.get_buffered_rows(), 0);
2899 }
2900
2901 #[test]
2902 fn test_coalasce_push_batch_with_indices() {
2903 const MID_POINT: u32 = 2333;
2904 const TOTAL_ROWS: u32 = 23333;
2905 let batch1 = uint32_batch_non_null(0..MID_POINT);
2906 let batch2 = uint32_batch_non_null((MID_POINT..TOTAL_ROWS).rev());
2907
2908 let mut coalescer = BatchCoalescer::new(
2909 Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, false)])),
2910 TOTAL_ROWS as usize,
2911 );
2912 coalescer.push_batch(batch1).unwrap();
2913
2914 let rev_indices = (0..((TOTAL_ROWS - MID_POINT) as u64)).rev();
2915 let reversed_indices_batch = uint64_batch_non_null(rev_indices);
2916
2917 let reverse_indices = UInt64Array::from(reversed_indices_batch.column(0).to_data());
2918 coalescer
2919 .push_batch_with_indices(batch2, &reverse_indices)
2920 .unwrap();
2921
2922 coalescer.finish_buffered_batch().unwrap();
2923 let actual = coalescer.next_completed_batch().unwrap();
2924
2925 let expected = uint32_batch_non_null(0..TOTAL_ROWS);
2926
2927 assert_eq!(expected, actual);
2928 }
2929
2930 #[test]
2931 fn test_push_batch_interleaved() {
2932 let batch1 = record_batch!(("a", Int32, [10, 11, 12])).unwrap();
2933 let batch2 = record_batch!(("a", Int32, [20, 21, 22])).unwrap();
2934 let indices = [(0, 0), (1, 2), (0, 2), (1, 0)];
2935
2936 let mut coalescer = BatchCoalescer::new(batch1.schema(), 1000);
2937 coalescer
2938 .push_batch_interleaved(&[batch1, batch2], &indices)
2939 .unwrap();
2940 coalescer.finish_buffered_batch().unwrap();
2941
2942 let expected = record_batch!(("a", Int32, [10, 22, 12, 20])).unwrap();
2943 assert_eq!(coalescer.next_completed_batch().unwrap(), expected);
2944 }
2945
2946 #[test]
2947 fn test_push_batch_interleaved_matches_interleave_then_push() {
2948 let batch1 = record_batch!(("a", Int32, [1, 2, 3, 4])).unwrap();
2949 let batch2 = record_batch!(("a", Int32, [5, 6, 7])).unwrap();
2950 let indices = [(1, 2), (0, 0), (1, 0), (0, 3), (1, 1)];
2951
2952 let mut via_method = BatchCoalescer::new(batch1.schema(), 1000);
2953 via_method
2954 .push_batch_interleaved(&[batch1.clone(), batch2.clone()], &indices)
2955 .unwrap();
2956 via_method.finish_buffered_batch().unwrap();
2957
2958 let mut via_manual = BatchCoalescer::new(batch1.schema(), 1000);
2959 let interleaved = interleave_record_batch(&[&batch1, &batch2], &indices).unwrap();
2960 via_manual.push_batch(interleaved).unwrap();
2961 via_manual.finish_buffered_batch().unwrap();
2962
2963 assert_eq!(
2964 via_method.next_completed_batch().unwrap(),
2965 via_manual.next_completed_batch().unwrap()
2966 );
2967 }
2968
2969 #[test]
2970 fn test_push_batch_interleaved_rejects_empty_batches() {
2971 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
2972 let mut coalescer = BatchCoalescer::new(schema, 1000);
2973
2974 let err = coalescer.push_batch_interleaved(&[], &[]).unwrap_err();
2975 assert_eq!(
2976 err.to_string(),
2977 "Invalid argument error: push_batch_interleaved requires at least one input batch"
2978 );
2979 }
2980
2981 #[test]
2982 fn test_push_batch_schema_mismatch_fewer_columns() {
2983 let empty_schema = Arc::new(Schema::empty());
2985 let mut coalescer = BatchCoalescer::new(empty_schema, 100);
2986 let batch = uint32_batch(0..5);
2987 let result = coalescer.push_batch(batch);
2988 assert!(result.is_err());
2989 let err = result.unwrap_err().to_string();
2990 assert!(
2991 err.contains("Batch has 1 columns but BatchCoalescer expects 0"),
2992 "unexpected error: {err}"
2993 );
2994 }
2995
2996 #[test]
2997 fn test_push_batch_schema_mismatch_more_columns() {
2998 let schema = Arc::new(Schema::new(vec![
3000 Field::new("c0", DataType::UInt32, false),
3001 Field::new("c1", DataType::UInt32, false),
3002 ]));
3003 let mut coalescer = BatchCoalescer::new(schema, 100);
3004 let batch = uint32_batch(0..5);
3005 let result = coalescer.push_batch(batch);
3006 assert!(result.is_err());
3007 let err = result.unwrap_err().to_string();
3008 assert!(
3009 err.contains("Batch has 1 columns but BatchCoalescer expects 2"),
3010 "unexpected error: {err}"
3011 );
3012 }
3013
3014 #[test]
3015 fn test_push_batch_schema_mismatch_two_vs_zero() {
3016 let empty_schema = Arc::new(Schema::empty());
3018 let mut coalescer = BatchCoalescer::new(empty_schema, 100);
3019 let schema = Arc::new(Schema::new(vec![
3020 Field::new("c0", DataType::UInt32, false),
3021 Field::new("c1", DataType::UInt32, false),
3022 ]));
3023 let batch = RecordBatch::try_new(
3024 schema,
3025 vec![
3026 Arc::new(UInt32Array::from(vec![1, 2, 3])),
3027 Arc::new(UInt32Array::from(vec![4, 5, 6])),
3028 ],
3029 )
3030 .unwrap();
3031 let result = coalescer.push_batch(batch);
3032 assert!(result.is_err());
3033 let err = result.unwrap_err().to_string();
3034 assert!(
3035 err.contains("Batch has 2 columns but BatchCoalescer expects 0"),
3036 "unexpected error: {err}"
3037 );
3038 }
3039
3040 #[test]
3041 fn test_size_grows_with_buffering_and_shrinks_when_draining() {
3042 let batch = uint32_batch(0..8);
3043 let mut coalescer = BatchCoalescer::new(batch.schema(), 21);
3044 let baseline = coalescer.size();
3045 assert!(baseline > 0, "size includes container capacities");
3046
3047 coalescer.push_batch(batch.clone()).unwrap();
3049 assert!(coalescer.next_completed_batch().is_none());
3050 let buffered = coalescer.size();
3051 assert!(
3052 buffered > baseline,
3053 "buffering rows should grow size ({buffered} > {baseline})"
3054 );
3055
3056 for _ in 0..10 {
3058 coalescer.push_batch(batch.clone()).unwrap();
3059 }
3060 let peak = coalescer.size();
3061 assert!(peak > buffered);
3062
3063 let mut prev = peak;
3065 let mut drained_any = false;
3066 while coalescer.next_completed_batch().is_some() {
3067 drained_any = true;
3068 let now = coalescer.size();
3069 assert!(now <= prev, "size grew while draining: {now} > {prev}");
3070 prev = now;
3071 }
3072 assert!(drained_any);
3073 assert!(
3074 prev < peak,
3075 "draining completed batches should release memory"
3076 );
3077 }
3078
3079 #[test]
3080 fn test_size_string_view_buffers_released_after_drain() {
3081 let batch = stringview_batch_repeated(
3084 1000,
3085 [Some("this string is definitely longer than 12 bytes")],
3086 );
3087 let mut coalescer = BatchCoalescer::new(batch.schema(), 4096);
3088 let baseline = coalescer.size();
3089
3090 for _ in 0..20 {
3091 coalescer.push_batch(batch.clone()).unwrap();
3092 }
3093 let peak = coalescer.size();
3094 assert!(
3095 peak > baseline,
3096 "buffered string view data should grow size ({peak} > {baseline})"
3097 );
3098
3099 coalescer.finish_buffered_batch().unwrap();
3100 while coalescer.next_completed_batch().is_some() {}
3101
3102 let drained = coalescer.size();
3104 assert!(
3105 drained < peak,
3106 "draining should release buffered data ({drained} < {peak})"
3107 );
3108 }
3109
3110 #[test]
3114 fn test_size_accounting_conserved_across_cycles() {
3115 let batch = uint32_batch(0..8);
3119 let mut coalescer = BatchCoalescer::new(batch.schema(), 4096);
3120
3121 let run_cycle = |coalescer: &mut BatchCoalescer| {
3122 for _ in 0..20 {
3123 coalescer.push_batch(batch.clone()).unwrap();
3124 }
3125 coalescer.finish_buffered_batch().unwrap();
3126 let peak = coalescer.size();
3127 while coalescer.next_completed_batch().is_some() {}
3128 (peak, coalescer.size())
3129 };
3130
3131 let (peak1, drained1) = run_cycle(&mut coalescer);
3132 let (peak2, drained2) = run_cycle(&mut coalescer);
3133
3134 assert_eq!(
3135 peak1, peak2,
3136 "identical work must report identical peak size"
3137 );
3138 assert_eq!(
3139 drained1, drained2,
3140 "fully-drained size must be stable across cycles (no accounting leak)"
3141 );
3142 assert!(
3143 drained1 < peak1,
3144 "draining must release the accounted memory"
3145 );
3146 }
3147}