1use std::fmt;
18use std::sync::{Arc, Mutex};
19use std::time::{Duration, Instant};
20
21use async_stream::try_stream;
22use common_error::ext::BoxedError;
23use common_recordbatch::util::ChainedRecordBatchStream;
24use common_recordbatch::{RecordBatchStreamWrapper, SendableRecordBatchStream};
25use common_telemetry::tracing::{self, Instrument};
26use common_telemetry::warn;
27use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
28use datafusion::physical_plan::{DisplayAs, DisplayFormatType};
29use datatypes::arrow::array::BinaryArray;
30use datatypes::arrow::record_batch::RecordBatch;
31use datatypes::schema::SchemaRef;
32use futures::{StreamExt, TryStreamExt};
33use smallvec::SmallVec;
34use snafu::{OptionExt, ResultExt, ensure};
35use store_api::metadata::RegionMetadataRef;
36use store_api::region_engine::{
37 PartitionRange, PrepareRequest, QueryScanContext, RegionScanner, ScannerProperties,
38};
39use tokio::sync::Semaphore;
40use tokio::sync::mpsc::error::{SendTimeoutError, TrySendError};
41use tokio::sync::mpsc::{self, Receiver, Sender};
42
43use crate::error::{
44 Error, InvalidSenderSnafu, JoinSnafu, PartitionOutOfRangeSnafu, Result, ScanMultiTimesSnafu,
45 ScanSeriesSnafu, TooManyFilesToReadSnafu,
46};
47use crate::read::ScannerMetrics;
48use crate::read::pruner::{PartitionPruner, Pruner};
49use crate::read::scan_region::{ScanInput, StreamContext};
50use crate::read::scan_util::{
51 PartitionMetrics, PartitionMetricsList, SeriesDistributorMetrics, compute_average_batch_size,
52 compute_parallel_channel_size,
53};
54use crate::read::seq_scan::SeqScan;
55use crate::read::stream::{ConvertBatchStream, ScanBatch, ScanBatchStream};
56use crate::sst::parquet::flat_format::primary_key_column_index;
57use crate::sst::parquet::format::PrimaryKeyArray;
58
59const SEND_TIMEOUT: Duration = Duration::from_micros(100);
61
62type ReceiverList = Vec<Option<Receiver<Result<SeriesBatch>>>>;
64
65pub struct SeriesScan {
71 properties: ScannerProperties,
73 stream_ctx: Arc<StreamContext>,
75 pruner: Arc<Pruner>,
77 receivers: Mutex<ReceiverList>,
79 metrics_list: Arc<PartitionMetricsList>,
82}
83
84impl SeriesScan {
85 pub(crate) fn new(input: ScanInput) -> Self {
87 let mut properties = ScannerProperties::default()
88 .with_append_mode(input.append_mode)
89 .with_total_rows(input.total_rows());
90 if let Some(counters) = input.query_stat_counters.clone() {
91 properties.set_query_stat_counters(counters);
92 }
93 let stream_ctx = Arc::new(StreamContext::seq_scan_ctx(input));
94 properties.partitions = vec![stream_ctx.partition_ranges()];
95
96 let num_workers = common_stat::get_total_cpu_cores().max(1);
98 let pruner = Arc::new(Pruner::new(stream_ctx.clone(), num_workers));
99
100 Self {
101 properties,
102 stream_ctx,
103 pruner,
104 receivers: Mutex::new(Vec::new()),
105 metrics_list: Arc::new(PartitionMetricsList::default()),
106 }
107 }
108
109 #[tracing::instrument(
110 skip_all,
111 fields(
112 region_id = %self.stream_ctx.input.mapper.metadata().region_id,
113 partition = partition
114 )
115 )]
116 fn scan_partition_impl(
117 &self,
118 ctx: &QueryScanContext,
119 metrics_set: &ExecutionPlanMetricsSet,
120 partition: usize,
121 ) -> Result<SendableRecordBatchStream> {
122 let metrics = new_partition_metrics(
123 &self.stream_ctx,
124 ctx.explain_verbose,
125 metrics_set,
126 partition,
127 &self.metrics_list,
128 );
129
130 let batch_stream =
131 self.scan_batch_in_partition(ctx, partition, metrics.clone(), metrics_set)?;
132
133 let input = &self.stream_ctx.input;
134 let record_batch_stream = ConvertBatchStream::new(
135 batch_stream,
136 input.mapper.clone(),
137 input.cache_strategy.clone(),
138 metrics,
139 );
140
141 Ok(Box::pin(RecordBatchStreamWrapper::new(
142 input.mapper.output_schema(),
143 Box::pin(record_batch_stream),
144 )))
145 }
146
147 #[tracing::instrument(
148 skip_all,
149 fields(
150 region_id = %self.stream_ctx.input.mapper.metadata().region_id,
151 partition = partition
152 )
153 )]
154 fn scan_batch_in_partition(
155 &self,
156 ctx: &QueryScanContext,
157 partition: usize,
158 part_metrics: PartitionMetrics,
159 metrics_set: &ExecutionPlanMetricsSet,
160 ) -> Result<ScanBatchStream> {
161 if ctx.explain_verbose {
162 common_telemetry::info!(
163 "SeriesScan partition {}, region_id: {}",
164 partition,
165 self.stream_ctx.input.region_metadata().region_id
166 );
167 }
168
169 ensure!(
170 partition < self.properties.num_partitions(),
171 PartitionOutOfRangeSnafu {
172 given: partition,
173 all: self.properties.num_partitions(),
174 }
175 );
176
177 self.maybe_start_distributor(metrics_set, &self.metrics_list, ctx.explain_verbose);
178
179 let mut receiver = self.take_receiver(partition)?;
180 let stream = try_stream! {
181 part_metrics.on_first_poll();
182
183 let mut fetch_start = Instant::now();
184 while let Some(series) = receiver.recv().await {
185 let series = series?;
186
187 let mut metrics = ScannerMetrics::default();
188 metrics.scan_cost += fetch_start.elapsed();
189 fetch_start = Instant::now();
190
191 metrics.num_batches += series.num_batches();
192 metrics.num_rows += series.num_rows();
193
194 let yield_start = Instant::now();
195 yield ScanBatch::Series(series);
196 metrics.yield_cost += yield_start.elapsed();
197
198 part_metrics.merge_metrics(&metrics);
199 }
200
201 part_metrics.on_finish();
202 };
203 Ok(Box::pin(stream))
204 }
205
206 fn take_receiver(&self, partition: usize) -> Result<Receiver<Result<SeriesBatch>>> {
208 let mut rx_list = self.receivers.lock().unwrap();
209 rx_list[partition]
210 .take()
211 .context(ScanMultiTimesSnafu { partition })
212 }
213
214 #[tracing::instrument(
216 skip(self, metrics_set, metrics_list),
217 fields(region_id = %self.stream_ctx.input.mapper.metadata().region_id)
218 )]
219 fn maybe_start_distributor(
220 &self,
221 metrics_set: &ExecutionPlanMetricsSet,
222 metrics_list: &Arc<PartitionMetricsList>,
223 explain_verbose: bool,
224 ) {
225 let mut rx_list = self.receivers.lock().unwrap();
226 if !rx_list.is_empty() {
227 return;
228 }
229
230 let (senders, receivers) = new_channel_list(self.properties.num_partitions());
231 let mut distributor = SeriesDistributor {
232 stream_ctx: self.stream_ctx.clone(),
233 range_semaphore: Some(Arc::new(Semaphore::new(self.properties.num_partitions()))),
234 final_merge_semaphore: Some(Arc::new(Semaphore::new(self.properties.num_partitions()))),
235 partitions: self.properties.partitions.clone(),
236 pruner: self.pruner.clone(),
237 senders,
238 metrics_set: metrics_set.clone(),
239 metrics_list: metrics_list.clone(),
240 explain_verbose,
241 };
242 let region_id = distributor.stream_ctx.input.mapper.metadata().region_id;
243 let span = tracing::info_span!("SeriesScan::distributor", region_id = %region_id);
244 common_runtime::spawn_query(
245 async move {
246 distributor.execute().await;
247 }
248 .instrument(span),
249 );
250
251 *rx_list = receivers;
252 }
253
254 #[tracing::instrument(
256 skip_all,
257 fields(region_id = %self.stream_ctx.input.mapper.metadata().region_id)
258 )]
259 pub(crate) async fn build_stream(&self) -> Result<SendableRecordBatchStream, BoxedError> {
260 let part_num = self.properties.num_partitions();
261 let metrics_set = ExecutionPlanMetricsSet::default();
262 let streams = (0..part_num)
263 .map(|i| self.scan_partition(&QueryScanContext::default(), &metrics_set, i))
264 .collect::<Result<Vec<_>, BoxedError>>()?;
265 let chained_stream = ChainedRecordBatchStream::new(streams).map_err(BoxedError::new)?;
266 Ok(Box::pin(chained_stream))
267 }
268
269 pub(crate) fn scan_all_partitions(&self) -> Result<ScanBatchStream> {
271 let metrics_set = ExecutionPlanMetricsSet::new();
272
273 let streams = (0..self.properties.partitions.len())
274 .map(|partition| {
275 let metrics = new_partition_metrics(
276 &self.stream_ctx,
277 false,
278 &metrics_set,
279 partition,
280 &self.metrics_list,
281 );
282
283 self.scan_batch_in_partition(
284 &QueryScanContext::default(),
285 partition,
286 metrics,
287 &metrics_set,
288 )
289 })
290 .collect::<Result<Vec<_>>>()?;
291
292 Ok(Box::pin(futures::stream::iter(streams).flatten()))
293 }
294
295 pub(crate) fn check_scan_limit(&self) -> Result<()> {
297 let total_files: usize = self
299 .properties
300 .partitions
301 .iter()
302 .flat_map(|partition| partition.iter())
303 .map(|part_range| {
304 let range_meta = &self.stream_ctx.ranges[part_range.identifier];
305 range_meta.indices.len()
306 })
307 .sum();
308
309 let max_concurrent_files = self.stream_ctx.input.max_concurrent_scan_files;
310 if total_files > max_concurrent_files {
311 return TooManyFilesToReadSnafu {
312 actual: total_files,
313 max: max_concurrent_files,
314 }
315 .fail();
316 }
317
318 Ok(())
319 }
320}
321
322fn new_channel_list(num_partitions: usize) -> (SenderList, ReceiverList) {
323 let (senders, receivers): (Vec<_>, Vec<_>) = (0..num_partitions)
324 .map(|_| {
325 let (sender, receiver) = mpsc::channel(1);
326 (Some(sender), Some(receiver))
327 })
328 .unzip();
329 (SenderList::new(senders), receivers)
330}
331
332impl RegionScanner for SeriesScan {
333 fn name(&self) -> &str {
334 "SeriesScan"
335 }
336
337 fn properties(&self) -> &ScannerProperties {
338 &self.properties
339 }
340
341 fn schema(&self) -> SchemaRef {
342 self.stream_ctx.input.mapper.output_schema()
343 }
344
345 fn metadata(&self) -> RegionMetadataRef {
346 self.stream_ctx.input.mapper.metadata().clone()
347 }
348
349 fn scan_partition(
350 &self,
351 ctx: &QueryScanContext,
352 metrics_set: &ExecutionPlanMetricsSet,
353 partition: usize,
354 ) -> Result<SendableRecordBatchStream, BoxedError> {
355 self.scan_partition_impl(ctx, metrics_set, partition)
356 .map_err(BoxedError::new)
357 }
358
359 fn prepare(&mut self, request: PrepareRequest) -> Result<(), BoxedError> {
360 self.properties.prepare(request);
361
362 self.check_scan_limit().map_err(BoxedError::new)?;
363
364 Ok(())
365 }
366
367 fn has_predicate_without_region(&self) -> bool {
368 let predicate = self
369 .stream_ctx
370 .input
371 .predicate_group()
372 .predicate_without_region();
373 predicate.is_some()
374 }
375
376 fn add_dyn_filter_to_predicate(
377 &mut self,
378 filter_exprs: Vec<Arc<dyn datafusion::physical_plan::PhysicalExpr>>,
379 ) -> Vec<bool> {
380 self.stream_ctx.add_dyn_filter_to_predicate(filter_exprs)
381 }
382
383 fn set_logical_region(&mut self, logical_region: bool) {
384 self.properties.set_logical_region(logical_region);
385 }
386
387 fn set_query_load_region_id(&mut self, region_id: store_api::storage::RegionId) {
388 self.properties.set_query_load_region_id(region_id);
389 }
390
391 fn snapshot_sequence(&self) -> Option<u64> {
392 self.stream_ctx.input.snapshot_sequence
393 }
394}
395
396impl DisplayAs for SeriesScan {
397 fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
398 write!(
399 f,
400 "SeriesScan: region={}, ",
401 self.stream_ctx.input.mapper.metadata().region_id
402 )?;
403 match t {
404 DisplayFormatType::Default | DisplayFormatType::TreeRender => {
405 self.stream_ctx.format_for_explain(false, f)
406 }
407 DisplayFormatType::Verbose => {
408 self.stream_ctx.format_for_explain(true, f)?;
409 self.metrics_list.format_verbose_metrics(f)
410 }
411 }
412 }
413}
414
415impl fmt::Debug for SeriesScan {
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 f.debug_struct("SeriesScan")
418 .field("num_ranges", &self.stream_ctx.ranges.len())
419 .finish()
420 }
421}
422
423#[cfg(test)]
424impl SeriesScan {
425 pub(crate) fn input(&self) -> &ScanInput {
427 &self.stream_ctx.input
428 }
429}
430
431struct SeriesDistributor {
433 stream_ctx: Arc<StreamContext>,
435 range_semaphore: Option<Arc<Semaphore>>,
437 final_merge_semaphore: Option<Arc<Semaphore>>,
442 partitions: Vec<Vec<PartitionRange>>,
444 pruner: Arc<Pruner>,
446 senders: SenderList,
448 metrics_set: ExecutionPlanMetricsSet,
454 metrics_list: Arc<PartitionMetricsList>,
455 explain_verbose: bool,
457}
458
459impl SeriesDistributor {
460 #[tracing::instrument(
462 skip_all,
463 fields(region_id = %self.stream_ctx.input.mapper.metadata().region_id)
464 )]
465 async fn execute(&mut self) {
466 let result = self.scan_partitions_flat().await;
467
468 if let Err(e) = result {
469 self.senders.send_error(e).await;
470 }
471 }
472
473 #[tracing::instrument(
475 skip_all,
476 fields(region_id = %self.stream_ctx.input.mapper.metadata().region_id)
477 )]
478 async fn scan_partitions_flat(&mut self) -> Result<()> {
479 for partition_ranges in &self.partitions {
481 self.pruner.add_partition_ranges(partition_ranges);
482 }
483
484 let all_partition_ranges: Vec<_> = self.partitions.iter().flatten().cloned().collect();
486 let partition_pruner = Arc::new(PartitionPruner::new(
487 self.pruner.clone(),
488 &all_partition_ranges,
489 ));
490
491 let part_metrics = new_partition_metrics(
492 &self.stream_ctx,
493 self.explain_verbose,
494 &self.metrics_set,
495 self.partitions.len(),
496 &self.metrics_list,
497 );
498 part_metrics.on_first_poll();
499 let mut fetch_start = Instant::now();
502
503 let build_start = Instant::now();
505 let mut tasks = Vec::new();
506 for partition in &self.partitions {
507 for part_range in partition {
508 let stream_ctx = self.stream_ctx.clone();
509 let part_range = *part_range;
510 let part_metrics = part_metrics.clone();
511 let partition_pruner = partition_pruner.clone();
512 let file_scan_semaphore = self.range_semaphore.clone();
513 let merge_semaphore = self.range_semaphore.clone();
514 tasks.push(common_runtime::spawn_query(async move {
515 SeqScan::build_flat_partition_range_read(
516 &stream_ctx,
517 &part_range,
518 false,
519 &part_metrics,
520 partition_pruner,
521 file_scan_semaphore,
522 merge_semaphore,
523 )
524 .await
525 }));
526 }
527 }
528 let mut range_streams = Vec::with_capacity(tasks.len());
529 let mut estimated_batch_sizes = Vec::with_capacity(tasks.len());
530 for task in tasks {
531 let (stream, estimated_batch_size) = task.await.context(JoinSnafu)??;
532 range_streams.push(stream);
533 estimated_batch_sizes.push(estimated_batch_size);
534 }
535 let channel_size =
536 compute_parallel_channel_size(compute_average_batch_size(estimated_batch_sizes));
537 common_telemetry::debug!(
538 "SeriesDistributor built {} range_streams, region: {}, build cost: {:?}, channel_size: {}",
539 range_streams.len(),
540 self.stream_ctx.input.region_metadata().region_id,
541 build_start.elapsed(),
542 channel_size,
543 );
544
545 let mut reader = SeqScan::build_flat_reader_from_sources(
549 &self.stream_ctx,
550 range_streams,
551 self.final_merge_semaphore.clone(),
552 Some(&part_metrics),
553 true,
554 channel_size,
555 )
556 .await?;
557 let mut metrics = SeriesDistributorMetrics::default();
558
559 let mut divider = FlatSeriesBatchDivider::default();
560 while let Some(record_batch) = reader.try_next().await? {
561 metrics.scan_cost += fetch_start.elapsed();
562 metrics.num_batches += 1;
563 metrics.num_rows += record_batch.num_rows();
564
565 debug_assert!(record_batch.num_rows() > 0);
566 if record_batch.num_rows() == 0 {
567 fetch_start = Instant::now();
568 continue;
569 }
570
571 let divider_start = Instant::now();
573 let series_batch = divider.push(record_batch);
574 metrics.divider_cost += divider_start.elapsed();
575 if let Some(series_batch) = series_batch {
576 let yield_start = Instant::now();
577 self.senders
578 .send_batch(SeriesBatch::Flat(series_batch))
579 .await?;
580 metrics.yield_cost += yield_start.elapsed();
581 }
582 fetch_start = Instant::now();
583 }
584
585 let divider_start = Instant::now();
587 let series_batch = divider.finish();
588 metrics.divider_cost += divider_start.elapsed();
589 if let Some(series_batch) = series_batch {
590 let yield_start = Instant::now();
591 self.senders
592 .send_batch(SeriesBatch::Flat(series_batch))
593 .await?;
594 metrics.yield_cost += yield_start.elapsed();
595 }
596
597 metrics.scan_cost += fetch_start.elapsed();
598 metrics.num_series_send_timeout = self.senders.num_timeout;
599 metrics.num_series_send_full = self.senders.num_full;
600 part_metrics.set_distributor_metrics(&metrics);
601
602 part_metrics.on_finish();
603
604 Ok(())
605 }
606}
607
608#[derive(Debug)]
610pub enum SeriesBatch {
611 Flat(FlatSeriesBatch),
612}
613
614impl SeriesBatch {
615 pub fn num_batches(&self) -> usize {
617 match self {
618 SeriesBatch::Flat(flat_batch) => flat_batch.batches.len(),
619 }
620 }
621
622 pub fn num_rows(&self) -> usize {
624 match self {
625 SeriesBatch::Flat(flat_batch) => flat_batch.batches.iter().map(|x| x.num_rows()).sum(),
626 }
627 }
628}
629
630#[derive(Default, Debug)]
632pub struct FlatSeriesBatch {
633 pub batches: SmallVec<[RecordBatch; 4]>,
634}
635
636struct SenderList {
638 senders: Vec<Option<Sender<Result<SeriesBatch>>>>,
639 num_nones: usize,
641 sender_idx: usize,
643 num_timeout: usize,
645 num_full: usize,
647}
648
649impl SenderList {
650 fn new(senders: Vec<Option<Sender<Result<SeriesBatch>>>>) -> Self {
651 let num_nones = senders.iter().filter(|sender| sender.is_none()).count();
652 Self {
653 senders,
654 num_nones,
655 sender_idx: 0,
656 num_timeout: 0,
657 num_full: 0,
658 }
659 }
660
661 fn try_send_batch(&mut self, mut batch: SeriesBatch) -> Result<Option<SeriesBatch>> {
664 for _ in 0..self.senders.len() {
665 ensure!(self.num_nones < self.senders.len(), InvalidSenderSnafu);
666
667 let sender_idx = self.fetch_add_sender_idx();
668 let Some(sender) = &self.senders[sender_idx] else {
669 continue;
670 };
671
672 match sender.try_send(Ok(batch)) {
673 Ok(()) => return Ok(None),
674 Err(TrySendError::Full(res)) => {
675 self.num_full += 1;
676 batch = res.unwrap();
678 }
679 Err(TrySendError::Closed(res)) => {
680 self.senders[sender_idx] = None;
681 self.num_nones += 1;
682 batch = res.unwrap();
684 }
685 }
686 }
687
688 Ok(Some(batch))
689 }
690
691 async fn send_batch(&mut self, mut batch: SeriesBatch) -> Result<()> {
693 match self.try_send_batch(batch)? {
695 Some(b) => {
696 batch = b;
698 }
699 None => {
700 return Ok(());
701 }
702 }
703
704 loop {
705 ensure!(self.num_nones < self.senders.len(), InvalidSenderSnafu);
706
707 let sender_idx = self.fetch_add_sender_idx();
708 let Some(sender) = &self.senders[sender_idx] else {
709 continue;
710 };
711 match sender.send_timeout(Ok(batch), SEND_TIMEOUT).await {
716 Ok(()) => break,
717 Err(SendTimeoutError::Timeout(res)) => {
718 self.num_timeout += 1;
719 batch = res.unwrap();
721 }
722 Err(SendTimeoutError::Closed(res)) => {
723 self.senders[sender_idx] = None;
724 self.num_nones += 1;
725 batch = res.unwrap();
727 }
728 }
729 }
730
731 Ok(())
732 }
733
734 async fn send_error(&self, error: Error) {
735 let error = Arc::new(error);
736 for sender in self.senders.iter().flatten() {
737 let result = Err(error.clone()).context(ScanSeriesSnafu);
738 let _ = sender.send(result).await;
739 }
740 }
741
742 fn fetch_add_sender_idx(&mut self) -> usize {
743 let sender_idx = self.sender_idx;
744 self.sender_idx = (self.sender_idx + 1) % self.senders.len();
745 sender_idx
746 }
747}
748
749fn new_partition_metrics(
750 stream_ctx: &StreamContext,
751 explain_verbose: bool,
752 metrics_set: &ExecutionPlanMetricsSet,
753 partition: usize,
754 metrics_list: &PartitionMetricsList,
755) -> PartitionMetrics {
756 let metrics = PartitionMetrics::new(
757 stream_ctx.input.mapper.metadata().region_id,
758 partition,
759 "SeriesScan",
760 stream_ctx.query_start,
761 explain_verbose,
762 metrics_set,
763 );
764
765 metrics_list.set(partition, metrics.clone());
766 metrics
767}
768
769#[derive(Default)]
774struct FlatSeriesBatchDivider {
775 buffer: FlatSeriesBatch,
776}
777
778impl FlatSeriesBatchDivider {
779 fn push(&mut self, batch: RecordBatch) -> Option<FlatSeriesBatch> {
783 if self.buffer.batches.is_empty() {
785 self.buffer.batches.push(batch);
786 return None;
787 }
788
789 let pk_column_idx = primary_key_column_index(batch.num_columns());
791 let batch_pk_column = batch.column(pk_column_idx);
792 let batch_pk_array = batch_pk_column
793 .as_any()
794 .downcast_ref::<PrimaryKeyArray>()
795 .unwrap();
796 let batch_pk_values = batch_pk_array
797 .values()
798 .as_any()
799 .downcast_ref::<BinaryArray>()
800 .unwrap();
801 let batch_last_pk =
803 primary_key_at(batch_pk_array, batch_pk_values, batch_pk_array.len() - 1);
804 let buffer_last_batch = self.buffer.batches.last().unwrap();
807 let buffer_pk_column = buffer_last_batch.column(pk_column_idx);
808 let buffer_pk_array = buffer_pk_column
809 .as_any()
810 .downcast_ref::<PrimaryKeyArray>()
811 .unwrap();
812 let buffer_pk_values = buffer_pk_array
813 .values()
814 .as_any()
815 .downcast_ref::<BinaryArray>()
816 .unwrap();
817 let buffer_last_pk =
818 primary_key_at(buffer_pk_array, buffer_pk_values, buffer_pk_array.len() - 1);
819
820 if batch_last_pk == buffer_last_pk {
822 self.buffer.batches.push(batch);
823 return None;
824 }
825 let batch_pk_keys = batch_pk_array.keys();
828 let pk_indices = batch_pk_keys.values();
829 let mut change_offset = 0;
830 for (i, &key) in pk_indices.iter().enumerate() {
831 let batch_pk = batch_pk_values.value(key as usize);
832
833 if buffer_last_pk != batch_pk {
834 change_offset = i;
835 break;
836 }
837 }
838
839 let (first_part, remaining_part) = if change_offset > 0 {
841 let first_part = batch.slice(0, change_offset);
842 let remaining_part = batch.slice(change_offset, batch.num_rows() - change_offset);
843 (Some(first_part), Some(remaining_part))
844 } else {
845 (None, Some(batch))
846 };
847
848 let mut result = std::mem::take(&mut self.buffer);
850 if let Some(first_part) = first_part {
851 result.batches.push(first_part);
852 }
853
854 if let Some(remaining_part) = remaining_part {
856 self.buffer.batches.push(remaining_part);
857 }
858
859 Some(result)
860 }
861
862 fn finish(&mut self) -> Option<FlatSeriesBatch> {
864 if self.buffer.batches.is_empty() {
865 None
866 } else {
867 Some(std::mem::take(&mut self.buffer))
868 }
869 }
870}
871
872fn primary_key_at<'a>(
874 primary_key: &PrimaryKeyArray,
875 primary_key_values: &'a BinaryArray,
876 index: usize,
877) -> &'a [u8] {
878 let key = primary_key.keys().value(index);
879 primary_key_values.value(key as usize)
880}
881
882#[cfg(test)]
883mod tests {
884 use std::sync::Arc;
885
886 use api::v1::OpType;
887 use datatypes::arrow::array::{
888 ArrayRef, BinaryDictionaryBuilder, Int64Array, StringDictionaryBuilder,
889 TimestampMillisecondArray, UInt8Array, UInt64Array,
890 };
891 use datatypes::arrow::datatypes::{DataType, Field, Schema, SchemaRef, TimeUnit, UInt32Type};
892 use datatypes::arrow::record_batch::RecordBatch;
893
894 use super::*;
895
896 fn new_test_record_batch(
897 primary_keys: &[&[u8]],
898 timestamps: &[i64],
899 sequences: &[u64],
900 op_types: &[OpType],
901 fields: &[u64],
902 ) -> RecordBatch {
903 let num_rows = timestamps.len();
904 debug_assert_eq!(sequences.len(), num_rows);
905 debug_assert_eq!(op_types.len(), num_rows);
906 debug_assert_eq!(fields.len(), num_rows);
907 debug_assert_eq!(primary_keys.len(), num_rows);
908
909 let columns: Vec<ArrayRef> = vec![
910 build_test_pk_string_dict_array(primary_keys),
911 Arc::new(Int64Array::from_iter(
912 fields.iter().map(|v| Some(*v as i64)),
913 )),
914 Arc::new(TimestampMillisecondArray::from_iter_values(
915 timestamps.iter().copied(),
916 )),
917 build_test_pk_array(primary_keys),
918 Arc::new(UInt64Array::from_iter_values(sequences.iter().copied())),
919 Arc::new(UInt8Array::from_iter_values(
920 op_types.iter().map(|v| *v as u8),
921 )),
922 ];
923
924 RecordBatch::try_new(build_test_flat_schema(), columns).unwrap()
925 }
926
927 fn build_test_pk_string_dict_array(primary_keys: &[&[u8]]) -> ArrayRef {
928 let mut builder = StringDictionaryBuilder::<UInt32Type>::new();
929 for &pk in primary_keys {
930 let pk_str = std::str::from_utf8(pk).unwrap();
931 builder.append(pk_str).unwrap();
932 }
933 Arc::new(builder.finish())
934 }
935
936 fn build_test_pk_array(primary_keys: &[&[u8]]) -> ArrayRef {
937 let mut builder = BinaryDictionaryBuilder::<UInt32Type>::new();
938 for &pk in primary_keys {
939 builder.append(pk).unwrap();
940 }
941 Arc::new(builder.finish())
942 }
943
944 fn build_test_flat_schema() -> SchemaRef {
945 let fields = vec![
946 Field::new(
947 "k0",
948 DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Utf8)),
949 false,
950 ),
951 Field::new("field0", DataType::Int64, true),
952 Field::new(
953 "ts",
954 DataType::Timestamp(TimeUnit::Millisecond, None),
955 false,
956 ),
957 Field::new(
958 "__primary_key",
959 DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Binary)),
960 false,
961 ),
962 Field::new("__sequence", DataType::UInt64, false),
963 Field::new("__op_type", DataType::UInt8, false),
964 ];
965 Arc::new(Schema::new(fields))
966 }
967
968 #[test]
969 fn test_empty_buffer_first_push() {
970 let mut divider = FlatSeriesBatchDivider::default();
971 let result = divider.finish();
972 assert!(result.is_none());
973
974 let mut divider = FlatSeriesBatchDivider::default();
975 let batch = new_test_record_batch(
976 &[b"series1", b"series1"],
977 &[1000, 2000],
978 &[1, 2],
979 &[OpType::Put, OpType::Put],
980 &[10, 20],
981 );
982 let result = divider.push(batch);
983 assert!(result.is_none());
984 assert_eq!(divider.buffer.batches.len(), 1);
985 }
986
987 #[test]
988 fn test_same_series_accumulation() {
989 let mut divider = FlatSeriesBatchDivider::default();
990
991 let batch1 = new_test_record_batch(
992 &[b"series1", b"series1"],
993 &[1000, 2000],
994 &[1, 2],
995 &[OpType::Put, OpType::Put],
996 &[10, 20],
997 );
998
999 let batch2 = new_test_record_batch(
1000 &[b"series1", b"series1"],
1001 &[3000, 4000],
1002 &[3, 4],
1003 &[OpType::Put, OpType::Put],
1004 &[30, 40],
1005 );
1006
1007 divider.push(batch1);
1008 let result = divider.push(batch2);
1009 assert!(result.is_none());
1010 let series_batch = divider.finish().unwrap();
1011 assert_eq!(series_batch.batches.len(), 2);
1012 }
1013
1014 #[test]
1015 fn test_series_boundary_detection() {
1016 let mut divider = FlatSeriesBatchDivider::default();
1017
1018 let batch1 = new_test_record_batch(
1019 &[b"series1", b"series1"],
1020 &[1000, 2000],
1021 &[1, 2],
1022 &[OpType::Put, OpType::Put],
1023 &[10, 20],
1024 );
1025
1026 let batch2 = new_test_record_batch(
1027 &[b"series2", b"series2"],
1028 &[3000, 4000],
1029 &[3, 4],
1030 &[OpType::Put, OpType::Put],
1031 &[30, 40],
1032 );
1033
1034 divider.push(batch1);
1035 let series_batch = divider.push(batch2).unwrap();
1036 assert_eq!(series_batch.batches.len(), 1);
1037
1038 assert_eq!(divider.buffer.batches.len(), 1);
1039 }
1040
1041 #[test]
1042 fn test_series_boundary_within_batch() {
1043 let mut divider = FlatSeriesBatchDivider::default();
1044
1045 let batch1 = new_test_record_batch(
1046 &[b"series1", b"series1"],
1047 &[1000, 2000],
1048 &[1, 2],
1049 &[OpType::Put, OpType::Put],
1050 &[10, 20],
1051 );
1052
1053 let batch2 = new_test_record_batch(
1054 &[b"series1", b"series2"],
1055 &[3000, 4000],
1056 &[3, 4],
1057 &[OpType::Put, OpType::Put],
1058 &[30, 40],
1059 );
1060
1061 divider.push(batch1);
1062 let series_batch = divider.push(batch2).unwrap();
1063 assert_eq!(series_batch.batches.len(), 2);
1064 assert_eq!(series_batch.batches[0].num_rows(), 2);
1065 assert_eq!(series_batch.batches[1].num_rows(), 1);
1066
1067 assert_eq!(divider.buffer.batches.len(), 1);
1068 assert_eq!(divider.buffer.batches[0].num_rows(), 1);
1069 }
1070
1071 #[test]
1072 fn test_series_splitting() {
1073 let mut divider = FlatSeriesBatchDivider::default();
1074
1075 let batch1 = new_test_record_batch(&[b"series1"], &[1000], &[1], &[OpType::Put], &[10]);
1076
1077 let batch2 = new_test_record_batch(
1078 &[b"series1", b"series2", b"series2", b"series3"],
1079 &[2000, 3000, 4000, 5000],
1080 &[2, 3, 4, 5],
1081 &[OpType::Put, OpType::Put, OpType::Put, OpType::Put],
1082 &[20, 30, 40, 50],
1083 );
1084
1085 divider.push(batch1);
1086 let series_batch = divider.push(batch2).unwrap();
1087 assert_eq!(series_batch.batches.len(), 2);
1088
1089 let total_rows: usize = series_batch.batches.iter().map(|b| b.num_rows()).sum();
1090 assert_eq!(total_rows, 2);
1091
1092 let final_batch = divider.finish().unwrap();
1093 assert_eq!(final_batch.batches.len(), 1);
1094 assert_eq!(final_batch.batches[0].num_rows(), 3);
1095 }
1096}