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