1use std::fmt;
18use std::sync::Arc;
19use std::time::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;
26use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
27use datafusion::physical_plan::{DisplayAs, DisplayFormatType};
28use datatypes::schema::SchemaRef;
29use futures::{StreamExt, TryStreamExt};
30use snafu::ensure;
31use store_api::metadata::RegionMetadataRef;
32use store_api::region_engine::{
33 PartitionRange, PrepareRequest, QueryScanContext, RegionScanner, ScannerProperties,
34};
35use store_api::storage::TimeSeriesRowSelector;
36use tokio::sync::Semaphore;
37
38use crate::error::{PartitionOutOfRangeSnafu, Result, TooManyFilesToReadSnafu};
39use crate::metrics::SCAN_MAX_CONCURRENT_FILES;
40use crate::read::flat_dedup::{FlatDedupReader, FlatLastNonNull, FlatLastRow};
41use crate::read::flat_merge::FlatMergeReader;
42use crate::read::last_row::FlatLastRowReader;
43use crate::read::pruner::{PartitionPruner, Pruner};
44use crate::read::range::RangeMeta;
45use crate::read::range_cache::{
46 build_range_cache_key, cache_flat_range_stream, cached_flat_range_stream,
47};
48use crate::read::scan_region::{ScanInput, StreamContext};
49use crate::read::scan_util::{
50 BATCH_SIZE_THRESHOLD, MIN_COMPACTION_SPLIT_ROWS, PartitionMetrics, PartitionMetricsList,
51 SplitRecordBatchStream, compute_parallel_channel_size, scan_flat_file_ranges,
52 scan_flat_mem_ranges, should_split_flat_batches_for_merge,
53};
54use crate::read::stream::{ConvertBatchStream, ScanBatch, ScanBatchStream};
55use crate::read::{BoxedRecordBatchStream, ScannerMetrics, scan_util};
56use crate::region::options::MergeMode;
57use crate::sst::parquet::DEFAULT_READ_BATCH_SIZE;
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub(crate) enum FlatReadMode {
62 Sequential,
64 CrossRangeMerge,
66 Compaction,
68}
69
70pub struct SeqScan {
75 properties: ScannerProperties,
77 stream_ctx: Arc<StreamContext>,
79 pruner: Arc<Pruner>,
81 metrics_list: PartitionMetricsList,
84}
85
86impl SeqScan {
87 pub(crate) fn new(input: ScanInput) -> Self {
90 let mut properties = ScannerProperties::default()
91 .with_append_mode(input.append_mode)
92 .with_total_rows(input.total_rows())
93 .with_total_rows_is_exact(input.append_mode && input.total_rows_is_exact());
94 if let Some(counters) = input.query_stat_counters.clone() {
95 properties.set_query_stat_counters(counters);
96 }
97 let stream_ctx = Arc::new(StreamContext::seq_scan_ctx(input));
98 properties.partitions = vec![stream_ctx.partition_ranges()];
99
100 let num_workers = common_stat::get_total_cpu_cores().max(1);
102 let pruner = Arc::new(Pruner::new(stream_ctx.clone(), num_workers));
103
104 Self {
105 properties,
106 stream_ctx,
107 pruner,
108 metrics_list: PartitionMetricsList::default(),
109 }
110 }
111
112 #[tracing::instrument(
117 skip_all,
118 fields(region_id = %self.stream_ctx.input.mapper.metadata().region_id)
119 )]
120 pub fn build_stream(&self) -> Result<SendableRecordBatchStream, BoxedError> {
121 let metrics_set = ExecutionPlanMetricsSet::new();
122 let ctx = QueryScanContext::default();
123 let streams = (0..self.properties.partitions.len())
124 .map(|partition: usize| self.scan_partition(&ctx, &metrics_set, partition))
125 .collect::<Result<Vec<_>, _>>()?;
126
127 let aggr_stream = ChainedRecordBatchStream::new(streams).map_err(BoxedError::new)?;
128 Ok(Box::pin(aggr_stream))
129 }
130
131 pub(crate) fn scan_all_partitions(&self) -> Result<ScanBatchStream> {
133 let metrics_set = ExecutionPlanMetricsSet::new();
134
135 let streams = (0..self.properties.partitions.len())
136 .map(|partition| {
137 let metrics = self.new_partition_metrics(false, &metrics_set, partition);
138 self.scan_flat_batch_in_partition(partition, metrics)
139 })
140 .collect::<Result<Vec<_>>>()?;
141
142 Ok(Box::pin(futures::stream::iter(streams).flatten()))
143 }
144
145 pub(crate) async fn build_flat_reader_for_compaction(&self) -> Result<BoxedRecordBatchStream> {
150 assert!(self.stream_ctx.input.compaction);
151
152 SCAN_MAX_CONCURRENT_FILES
154 .with_label_values(&["compaction"])
155 .observe(self.stream_ctx.input.num_files() as f64);
156
157 let metrics_set = ExecutionPlanMetricsSet::new();
158 let part_metrics = self.new_partition_metrics(false, &metrics_set, 0);
159 debug_assert_eq!(1, self.properties.partitions.len());
160 let partition_ranges = &self.properties.partitions[0];
161
162 let reader = Self::merge_all_flat_ranges_for_compaction(
163 &self.stream_ctx,
164 partition_ranges,
165 &part_metrics,
166 self.pruner.clone(),
167 )
168 .await?;
169 Ok(reader)
170 }
171
172 async fn merge_all_flat_ranges_for_compaction(
175 stream_ctx: &Arc<StreamContext>,
176 partition_ranges: &[PartitionRange],
177 part_metrics: &PartitionMetrics,
178 pruner: Arc<Pruner>,
179 ) -> Result<BoxedRecordBatchStream> {
180 pruner.add_partition_ranges(partition_ranges);
181 let partition_pruner = Arc::new(PartitionPruner::new(pruner, partition_ranges));
182
183 let mut sources = Vec::new();
184 for part_range in partition_ranges {
185 build_flat_sources(
186 stream_ctx,
187 part_range,
188 FlatReadMode::Compaction,
189 part_metrics,
190 partition_pruner.clone(),
191 &mut sources,
192 None,
193 )
194 .await?;
195 }
196
197 common_telemetry::debug!(
198 "Build flat reader to read all parts, region_id: {}, num_part_ranges: {}, num_sources: {}",
199 stream_ctx.input.mapper.metadata().region_id,
200 partition_ranges.len(),
201 sources.len()
202 );
203 Self::build_flat_reader_from_sources(
204 stream_ctx,
205 sources,
206 None,
207 None,
208 false,
209 compute_parallel_channel_size(DEFAULT_READ_BATCH_SIZE),
210 )
211 .await
212 }
213
214 #[tracing::instrument(level = tracing::Level::DEBUG, skip_all)]
218 pub(crate) async fn build_flat_reader_from_sources(
219 stream_ctx: &StreamContext,
220 mut sources: Vec<BoxedRecordBatchStream>,
221 semaphore: Option<Arc<Semaphore>>,
222 part_metrics: Option<&PartitionMetrics>,
223 skip_dedup: bool,
224 channel_size: usize,
225 ) -> Result<BoxedRecordBatchStream> {
226 if let Some(semaphore) = semaphore.as_ref() {
227 if sources.len() > 1 {
229 sources = stream_ctx.input.create_parallel_flat_sources(
230 sources,
231 semaphore.clone(),
232 channel_size,
233 )?;
234 }
235 }
236
237 let mapper = &stream_ctx.input.mapper;
238 let reader: BoxedRecordBatchStream = if sources.len() == 1 {
239 sources.pop().unwrap()
242 } else {
243 let schema = mapper.input_arrow_schema(stream_ctx.input.compaction);
244 let metrics_reporter = part_metrics.map(|m| m.merge_metrics_reporter());
245 let reader = FlatMergeReader::new(
246 schema,
247 sources,
248 stream_ctx.input.batch_size(),
249 metrics_reporter,
250 )
251 .await?;
252 Box::pin(reader.into_stream())
253 };
254
255 let dedup = !skip_dedup && !stream_ctx.input.append_mode;
256 let dedup_metrics_reporter = part_metrics.map(|m| m.dedup_metrics_reporter());
257 let reader = if dedup {
258 match stream_ctx.input.merge_mode {
259 MergeMode::LastRow => Box::pin(
260 FlatDedupReader::new(
261 reader,
262 FlatLastRow::new(stream_ctx.input.filter_deleted),
263 dedup_metrics_reporter,
264 )
265 .into_stream(),
266 ) as _,
267 MergeMode::LastNonNull => Box::pin(
268 FlatDedupReader::new(
269 reader,
270 FlatLastNonNull::new(
271 mapper.field_column_start(),
272 stream_ctx.input.filter_deleted,
273 ),
274 dedup_metrics_reporter,
275 )
276 .into_stream(),
277 ) as _,
278 }
279 } else {
280 reader
281 };
282
283 let reader = match &stream_ctx.input.series_row_selector {
284 Some(TimeSeriesRowSelector::LastRow { .. }) => {
285 Box::pin(FlatLastRowReader::new(reader).into_stream()) as _
286 }
287 None => reader,
288 };
289
290 Ok(reader)
291 }
292
293 pub(crate) async fn build_flat_partition_range_read(
295 stream_ctx: &Arc<StreamContext>,
296 part_range: &PartitionRange,
297 read_mode: FlatReadMode,
298 part_metrics: &PartitionMetrics,
299 partition_pruner: Arc<PartitionPruner>,
300 file_scan_semaphore: Option<Arc<Semaphore>>,
301 merge_semaphore: Option<Arc<Semaphore>>,
302 ) -> Result<(BoxedRecordBatchStream, usize)> {
303 let cache_key = build_range_cache_key(stream_ctx, part_range).map(|key| {
304 if read_mode == FlatReadMode::CrossRangeMerge {
305 key.for_cross_range_merge()
306 } else {
307 key
308 }
309 });
310
311 if let Some(key) = cache_key.as_ref() {
312 if let Some(value) = stream_ctx.input.cache_strategy.get_range_result(key) {
313 part_metrics.inc_range_cache_hit();
314 return Ok((cached_flat_range_stream(value), DEFAULT_READ_BATCH_SIZE));
315 }
316 part_metrics.inc_range_cache_miss();
317 }
318
319 let mut sources = Vec::new();
320 let split_batch_size = build_flat_sources(
321 stream_ctx,
322 part_range,
323 read_mode,
324 part_metrics,
325 partition_pruner,
326 &mut sources,
327 file_scan_semaphore,
328 )
329 .await?;
330 let estimated_rows_per_batch = split_batch_size.unwrap_or(DEFAULT_READ_BATCH_SIZE);
331 let channel_size = compute_parallel_channel_size(estimated_rows_per_batch);
332 let stream = Self::build_flat_reader_from_sources(
333 stream_ctx,
334 sources,
335 merge_semaphore,
336 Some(part_metrics),
337 false,
338 channel_size,
339 )
340 .await?;
341
342 let stream = match cache_key {
343 Some(key) => cache_flat_range_stream(
344 stream,
345 stream_ctx.input.cache_strategy.clone(),
346 key,
347 part_metrics.clone(),
348 ),
349 None => stream,
350 };
351
352 Ok((stream, estimated_rows_per_batch))
353 }
354
355 fn scan_partition_impl(
358 &self,
359 ctx: &QueryScanContext,
360 metrics_set: &ExecutionPlanMetricsSet,
361 partition: usize,
362 ) -> Result<SendableRecordBatchStream> {
363 if ctx.explain_verbose {
364 common_telemetry::info!(
365 "SeqScan partition {}, region_id: {}",
366 partition,
367 self.stream_ctx.input.region_metadata().region_id
368 );
369 }
370
371 let metrics = self.new_partition_metrics(ctx.explain_verbose, metrics_set, partition);
372 let input = &self.stream_ctx.input;
373
374 let batch_stream = self.scan_flat_batch_in_partition(partition, metrics.clone())?;
375 let record_batch_stream = ConvertBatchStream::new(
376 batch_stream,
377 input.mapper.clone(),
378 input.cache_strategy.clone(),
379 metrics,
380 );
381
382 Ok(Box::pin(RecordBatchStreamWrapper::new(
383 input.mapper.output_schema(),
384 Box::pin(record_batch_stream),
385 )))
386 }
387
388 #[tracing::instrument(
389 skip_all,
390 fields(
391 region_id = %self.stream_ctx.input.mapper.metadata().region_id,
392 partition = partition
393 )
394 )]
395 fn scan_flat_batch_in_partition(
396 &self,
397 partition: usize,
398 part_metrics: PartitionMetrics,
399 ) -> Result<ScanBatchStream> {
400 ensure!(
401 partition < self.properties.partitions.len(),
402 PartitionOutOfRangeSnafu {
403 given: partition,
404 all: self.properties.partitions.len(),
405 }
406 );
407
408 if self.properties.partitions[partition].is_empty() {
409 return Ok(Box::pin(futures::stream::empty()));
410 }
411
412 let stream_ctx = self.stream_ctx.clone();
413 let semaphore = self.new_semaphore();
414 let partition_ranges = self.properties.partitions[partition].clone();
415 let compaction = self.stream_ctx.input.compaction;
416 let file_scan_semaphore = if compaction { None } else { semaphore.clone() };
417 let pruner = self.pruner.clone();
418 pruner.add_partition_ranges(&partition_ranges);
423 let partition_pruner = Arc::new(PartitionPruner::new(pruner, &partition_ranges));
424
425 let stream = try_stream! {
426 part_metrics.on_first_poll();
427 let mut fetch_start = Instant::now();
430
431 for part_range in partition_ranges {
433 let (mut reader, _) = Self::build_flat_partition_range_read(
434 &stream_ctx,
435 &part_range,
436 if compaction {
437 FlatReadMode::Compaction
438 } else {
439 FlatReadMode::Sequential
440 },
441 &part_metrics,
442 partition_pruner.clone(),
443 file_scan_semaphore.clone(),
444 semaphore.clone(),
445 )
446 .await?;
447
448 let mut metrics = ScannerMetrics {
449 scan_cost: fetch_start.elapsed(),
450 ..Default::default()
451 };
452 fetch_start = Instant::now();
453
454 while let Some(record_batch) = reader.try_next().await? {
455 metrics.scan_cost += fetch_start.elapsed();
456 metrics.num_batches += 1;
457 metrics.num_rows += record_batch.num_rows();
458
459 debug_assert!(record_batch.num_rows() > 0);
460 if record_batch.num_rows() == 0 {
461 fetch_start = Instant::now();
462 continue;
463 }
464
465 let yield_start = Instant::now();
466 yield ScanBatch::RecordBatch(record_batch);
467 metrics.yield_cost += yield_start.elapsed();
468
469 fetch_start = Instant::now();
470 }
471
472 metrics.scan_cost += fetch_start.elapsed();
473 fetch_start = Instant::now();
474 part_metrics.merge_metrics(&metrics);
475 }
476
477 part_metrics.on_finish();
478 };
479 Ok(Box::pin(stream))
480 }
481
482 fn new_semaphore(&self) -> Option<Arc<Semaphore>> {
483 if self.properties.target_partitions() > self.properties.num_partitions() {
484 Some(Arc::new(Semaphore::new(
490 self.properties.target_partitions() - self.properties.num_partitions() + 1,
491 )))
492 } else {
493 None
494 }
495 }
496
497 fn new_partition_metrics(
500 &self,
501 explain_verbose: bool,
502 metrics_set: &ExecutionPlanMetricsSet,
503 partition: usize,
504 ) -> PartitionMetrics {
505 let metrics = PartitionMetrics::new(
506 self.stream_ctx.input.mapper.metadata().region_id,
507 partition,
508 get_scanner_type(self.stream_ctx.input.compaction),
509 self.stream_ctx.query_start,
510 explain_verbose,
511 metrics_set,
512 );
513
514 if !self.stream_ctx.input.compaction {
515 self.metrics_list.set(partition, metrics.clone());
516 }
517
518 metrics
519 }
520
521 fn max_files_in_partition(ranges: &[RangeMeta], partition_ranges: &[PartitionRange]) -> usize {
523 partition_ranges
524 .iter()
525 .map(|part_range| {
526 let range_meta = &ranges[part_range.identifier];
527 range_meta.indices.len()
528 })
529 .max()
530 .unwrap_or(0)
531 }
532
533 pub(crate) fn check_scan_limit(&self) -> Result<()> {
535 let total_max_files: usize = self
537 .properties
538 .partitions
539 .iter()
540 .map(|partition| Self::max_files_in_partition(&self.stream_ctx.ranges, partition))
541 .sum();
542
543 if !self.stream_ctx.input.compaction {
544 SCAN_MAX_CONCURRENT_FILES
545 .with_label_values(&["query"])
546 .observe(total_max_files as f64);
547 }
548 if self.stream_ctx.input.uses_scan_memory_limit() {
549 return Ok(());
550 }
551
552 let max_concurrent_files = self.stream_ctx.input.max_concurrent_scan_files;
553 if total_max_files > max_concurrent_files {
554 return TooManyFilesToReadSnafu {
555 actual: total_max_files,
556 max: max_concurrent_files,
557 }
558 .fail();
559 }
560
561 Ok(())
562 }
563}
564
565impl RegionScanner for SeqScan {
566 fn name(&self) -> &str {
567 "SeqScan"
568 }
569
570 fn properties(&self) -> &ScannerProperties {
571 &self.properties
572 }
573
574 fn schema(&self) -> SchemaRef {
575 self.stream_ctx.input.mapper.output_schema()
576 }
577
578 fn metadata(&self) -> RegionMetadataRef {
579 self.stream_ctx.input.mapper.metadata().clone()
580 }
581
582 fn scan_partition(
583 &self,
584 ctx: &QueryScanContext,
585 metrics_set: &ExecutionPlanMetricsSet,
586 partition: usize,
587 ) -> Result<SendableRecordBatchStream, BoxedError> {
588 self.scan_partition_impl(ctx, metrics_set, partition)
589 .map_err(BoxedError::new)
590 }
591
592 fn prepare(&mut self, request: PrepareRequest) -> Result<(), BoxedError> {
593 self.properties.prepare(request);
594
595 self.check_scan_limit().map_err(BoxedError::new)?;
596
597 Ok(())
598 }
599
600 fn has_predicate_without_region(&self) -> bool {
601 let predicate = self
602 .stream_ctx
603 .input
604 .predicate_group()
605 .predicate_without_region();
606 predicate.is_some()
607 }
608
609 fn add_dyn_filter_to_predicate(
610 &mut self,
611 filter_exprs: Vec<Arc<dyn datafusion::physical_plan::PhysicalExpr>>,
612 ) -> Vec<bool> {
613 self.stream_ctx.add_dyn_filter_to_predicate(filter_exprs)
614 }
615
616 fn reset_state(&mut self) {
617 self.stream_ctx.input.predicate.clear_dyn_filters();
618 let num_workers = common_stat::get_total_cpu_cores().max(1);
619 self.pruner = Arc::new(Pruner::new(self.stream_ctx.clone(), num_workers));
620 self.metrics_list = PartitionMetricsList::default();
621 }
622
623 fn set_logical_region(&mut self, logical_region: bool) {
624 self.properties.set_logical_region(logical_region);
625 }
626
627 fn set_query_load_region_id(&mut self, region_id: store_api::storage::RegionId) {
628 self.properties.set_query_load_region_id(region_id);
629 }
630
631 fn snapshot_sequence(&self) -> Option<u64> {
632 self.stream_ctx.input.snapshot_sequence
633 }
634}
635
636impl DisplayAs for SeqScan {
637 fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
638 write!(
639 f,
640 "SeqScan: region={}, ",
641 self.stream_ctx.input.mapper.metadata().region_id
642 )?;
643 match t {
644 DisplayFormatType::Default | DisplayFormatType::TreeRender => {
646 self.stream_ctx.format_for_explain(false, f)
647 }
648 DisplayFormatType::Verbose => {
649 self.stream_ctx.format_for_explain(true, f)?;
650 self.metrics_list.format_verbose_metrics(f)
651 }
652 }
653 }
654}
655
656impl fmt::Debug for SeqScan {
657 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
658 f.debug_struct("SeqScan")
659 .field("num_ranges", &self.stream_ctx.ranges.len())
660 .finish()
661 }
662}
663
664pub(crate) async fn build_flat_sources(
667 stream_ctx: &Arc<StreamContext>,
668 part_range: &PartitionRange,
669 read_mode: FlatReadMode,
670 part_metrics: &PartitionMetrics,
671 partition_pruner: Arc<PartitionPruner>,
672 sources: &mut Vec<BoxedRecordBatchStream>,
673 semaphore: Option<Arc<Semaphore>>,
674) -> Result<Option<usize>> {
675 let compaction = read_mode == FlatReadMode::Compaction;
676 let range_meta = &stream_ctx.ranges[part_range.identifier];
678 #[cfg(debug_assertions)]
679 if compaction {
680 debug_assert_eq!(range_meta.indices.len(), range_meta.row_group_indices.len());
682 for (i, row_group_idx) in range_meta.row_group_indices.iter().enumerate() {
683 debug_assert_eq!(
685 -1, row_group_idx.row_group_index,
686 "Expect {} range scan all row groups, given: {}",
687 i, row_group_idx.row_group_index,
688 );
689 }
690 }
691
692 let read_type = if compaction {
693 "compaction"
694 } else {
695 "seq_scan_files"
696 };
697 let num_indices = range_meta.row_group_indices.len();
698 if num_indices == 0 {
699 return Ok(None);
700 }
701
702 sources.reserve(num_indices);
703 let mut ordered_sources = Vec::with_capacity(num_indices);
704 ordered_sources.resize_with(num_indices, || None);
705 let mut file_scan_tasks = Vec::new();
706 let pre_filter_mode = stream_ctx.range_pre_filter_mode(part_range);
707
708 for (position, index) in range_meta.row_group_indices.iter().enumerate() {
709 if stream_ctx.is_mem_range_index(*index) {
710 let stream = scan_flat_mem_ranges(
711 stream_ctx.clone(),
712 part_metrics.clone(),
713 *index,
714 range_meta.time_range,
715 );
716 ordered_sources[position] = Some(Box::pin(stream) as _);
717 } else if stream_ctx.is_file_range_index(*index) {
718 if !compaction
721 && partition_pruner.try_skip_manifest_pruned_file_range(*index, part_metrics)
722 {
723 continue;
724 }
725 if let Some(semaphore_ref) = semaphore.as_ref() {
726 let stream_ctx = stream_ctx.clone();
728 let part_metrics = part_metrics.clone();
729 let partition_pruner = partition_pruner.clone();
730 let semaphore = Arc::clone(semaphore_ref);
731 let row_group_index = *index;
732 file_scan_tasks.push(async move {
733 let _permit = semaphore.acquire().await.unwrap();
734 let stream = scan_flat_file_ranges(
735 stream_ctx,
736 part_metrics,
737 row_group_index,
738 read_type,
739 partition_pruner,
740 )
741 .await?;
742 Ok((position, Box::pin(stream) as _))
743 });
744 } else {
745 let stream = scan_flat_file_ranges(
747 stream_ctx.clone(),
748 part_metrics.clone(),
749 *index,
750 read_type,
751 partition_pruner.clone(),
752 )
753 .await?;
754 ordered_sources[position] = Some(Box::pin(stream) as _);
755 }
756 } else {
757 let stream = scan_util::maybe_scan_flat_other_ranges(
758 stream_ctx,
759 *index,
760 part_metrics,
761 pre_filter_mode,
762 )
763 .await?;
764 ordered_sources[position] = Some(stream);
765 }
766 }
767
768 if !file_scan_tasks.is_empty() {
769 let results = futures::future::try_join_all(file_scan_tasks).await?;
770 for (position, stream) in results {
771 ordered_sources[position] = Some(stream);
772 }
773 }
774
775 let needs_merge =
778 ordered_sources.iter().flatten().count() > 1 || (compaction && stream_ctx.ranges.len() > 1);
779 let split_batch_size = if needs_merge || read_mode == FlatReadMode::CrossRangeMerge {
780 should_split_flat_batches_for_merge(stream_ctx, range_meta).filter(|rows_per_series| {
781 if !needs_merge && *rows_per_series < BATCH_SIZE_THRESHOLD as usize {
784 return false;
785 }
786 !compaction || *rows_per_series >= MIN_COMPACTION_SPLIT_ROWS
789 })
790 } else {
791 None
792 };
793 let should_split = split_batch_size.is_some();
794
795 for stream in ordered_sources.into_iter().flatten() {
796 if should_split {
797 sources.push(Box::pin(SplitRecordBatchStream::new(stream)));
798 } else {
799 sources.push(stream);
800 }
801 }
802
803 if should_split {
804 common_telemetry::debug!(
805 "Splitting record batches, region: {}, sources: {}, part_range: {:?}",
806 stream_ctx.input.region_metadata().region_id,
807 sources.len(),
808 part_range,
809 );
810 }
811
812 Ok(split_batch_size)
813}
814
815#[cfg(test)]
816impl SeqScan {
817 pub(crate) fn input(&self) -> &ScanInput {
819 &self.stream_ctx.input
820 }
821}
822
823fn get_scanner_type(compaction: bool) -> &'static str {
825 if compaction {
826 "SeqScan(compaction)"
827 } else {
828 "SeqScan"
829 }
830}