1use std::mem;
18use std::sync::Arc;
19
20use async_stream::try_stream;
21use common_telemetry::warn;
22use common_time::Timestamp;
23use common_time::range::TimestampRange;
24use common_time::timestamp::TimeUnit;
25use datafusion_expr::expr::Expr;
26use datafusion_expr::{Between, BinaryExpr, Operator};
27use datatypes::arrow::compute::concat_batches;
28use datatypes::arrow::record_batch::RecordBatch;
29use datatypes::prelude::ConcreteDataType;
30use datatypes::value::scalar_value_to_timestamp;
31use futures::TryStreamExt;
32use snafu::ResultExt;
33use store_api::region_engine::PartitionRange;
34use store_api::storage::{FileId, RegionId, SequenceRange, TimeSeriesRowSelector};
35use table::predicate::is_string_timestamp_literal;
36use tokio::sync::{mpsc, oneshot};
37
38use crate::cache::CacheStrategy;
39use crate::error::{ComputeArrowSnafu, Result};
40use crate::read::BoxedRecordBatchStream;
41use crate::read::read_columns::ReadColumns;
42use crate::read::scan_region::StreamContext;
43use crate::read::scan_util::PartitionMetrics;
44use crate::read::series_reader::SeriesRange;
45use crate::region::options::MergeMode;
46use crate::sst::parquet::DEFAULT_READ_BATCH_SIZE;
47
48const RANGE_CACHE_COMPACT_THRESHOLD_BYTES: usize = 8 * 1024 * 1024;
49
50#[derive(Debug, Clone, PartialEq, Eq, Hash)]
57pub(crate) struct ScanRequestFingerprint {
58 inner: Arc<SharedScanRequestFingerprint>,
60 time_filters: Option<Arc<Vec<String>>>,
62 series_row_selector: Option<TimeSeriesRowSelector>,
63 append_mode: bool,
64 filter_deleted: bool,
65 merge_mode: MergeMode,
66 sequence_range: Option<SequenceRange>,
70 stage: RangeScanStage,
71 partition_expr_version: u64,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
77enum RangeScanStage {
78 Data,
79 CandidateSeries,
80 SeriesData(SeriesRange),
81}
82
83#[derive(Debug)]
84pub(crate) struct ScanRequestFingerprintBuilder {
85 pub(crate) read_columns: ReadColumns,
86 pub(crate) read_column_types: Vec<Option<ConcreteDataType>>,
87 pub(crate) filters: Vec<String>,
88 pub(crate) time_filters: Vec<String>,
89 pub(crate) series_row_selector: Option<TimeSeriesRowSelector>,
90 pub(crate) append_mode: bool,
91 pub(crate) filter_deleted: bool,
92 pub(crate) merge_mode: MergeMode,
93 pub(crate) sequence_range: Option<SequenceRange>,
94 pub(crate) partition_expr_version: u64,
95}
96
97impl ScanRequestFingerprintBuilder {
98 pub(crate) fn build(self) -> ScanRequestFingerprint {
99 let Self {
100 read_columns,
101 read_column_types,
102 filters,
103 time_filters,
104 series_row_selector,
105 append_mode,
106 filter_deleted,
107 merge_mode,
108 sequence_range,
109 partition_expr_version,
110 } = self;
111
112 ScanRequestFingerprint {
113 inner: Arc::new(SharedScanRequestFingerprint {
114 read_columns,
115 read_column_types,
116 filters,
117 }),
118 time_filters: (!time_filters.is_empty()).then(|| Arc::new(time_filters)),
119 series_row_selector,
120 append_mode,
121 filter_deleted,
122 merge_mode,
123 sequence_range,
124 stage: RangeScanStage::Data,
125 partition_expr_version,
126 }
127 }
128}
129
130#[derive(Debug, PartialEq, Eq, Hash)]
132struct SharedScanRequestFingerprint {
133 read_columns: ReadColumns,
135 read_column_types: Vec<Option<ConcreteDataType>>,
138 filters: Vec<String>,
140}
141
142impl ScanRequestFingerprint {
143 #[cfg(test)]
144 pub(crate) fn read_columns(&self) -> &ReadColumns {
145 &self.inner.read_columns
146 }
147
148 #[cfg(test)]
149 pub(crate) fn read_column_types(&self) -> &[Option<ConcreteDataType>] {
150 &self.inner.read_column_types
151 }
152
153 #[cfg(test)]
154 pub(crate) fn filters(&self) -> &[String] {
155 &self.inner.filters
156 }
157
158 #[cfg(test)]
159 pub(crate) fn time_filters(&self) -> &[String] {
160 self.time_filters
161 .as_deref()
162 .map(Vec::as_slice)
163 .unwrap_or(&[])
164 }
165
166 pub(crate) fn without_time_filters(&self) -> Self {
167 Self {
168 inner: Arc::clone(&self.inner),
169 time_filters: None,
170 series_row_selector: self.series_row_selector,
171 append_mode: self.append_mode,
172 filter_deleted: self.filter_deleted,
173 merge_mode: self.merge_mode,
174 sequence_range: self.sequence_range,
175 stage: self.stage,
176 partition_expr_version: self.partition_expr_version,
177 }
178 }
179
180 fn for_candidate_series(&self) -> Self {
181 Self {
182 inner: Arc::clone(&self.inner),
183 time_filters: self.time_filters.clone(),
184 series_row_selector: self.series_row_selector,
185 append_mode: self.append_mode,
186 filter_deleted: self.filter_deleted,
187 merge_mode: self.merge_mode,
188 sequence_range: self.sequence_range,
189 stage: RangeScanStage::CandidateSeries,
190 partition_expr_version: self.partition_expr_version,
191 }
192 }
193
194 fn for_series_data(&self, range: SeriesRange) -> Self {
195 Self {
196 inner: Arc::clone(&self.inner),
197 time_filters: self.time_filters.clone(),
198 series_row_selector: self.series_row_selector,
199 append_mode: self.append_mode,
200 filter_deleted: self.filter_deleted,
201 merge_mode: self.merge_mode,
202 sequence_range: self.sequence_range,
203 stage: RangeScanStage::SeriesData(range),
204 partition_expr_version: self.partition_expr_version,
205 }
206 }
207
208 pub(crate) fn estimated_size(&self) -> usize {
209 mem::size_of::<SharedScanRequestFingerprint>()
210 + self.inner.read_columns.estimated_size()
211 + self.inner.read_column_types.capacity() * mem::size_of::<Option<ConcreteDataType>>()
212 + self.inner.filters.capacity() * mem::size_of::<String>()
213 + self
214 .inner
215 .filters
216 .iter()
217 .map(|filter| filter.capacity())
218 .sum::<usize>()
219 + self.time_filters.as_ref().map_or(0, |filters| {
220 mem::size_of::<Vec<String>>()
221 + filters.capacity() * mem::size_of::<String>()
222 + filters
223 .iter()
224 .map(|filter| filter.capacity())
225 .sum::<usize>()
226 })
227 }
228}
229
230#[derive(Debug, Clone, PartialEq, Eq, Hash)]
232pub(crate) struct RangeScanCacheKey {
233 pub(crate) region_id: RegionId,
234 pub(crate) row_groups: Vec<(FileId, i64)>,
236 pub(crate) scan: ScanRequestFingerprint,
237}
238
239impl RangeScanCacheKey {
240 pub(crate) fn estimated_size(&self) -> usize {
241 mem::size_of::<Self>()
242 + self.row_groups.capacity() * mem::size_of::<(FileId, i64)>()
243 + self.scan.estimated_size()
244 }
245}
246
247#[derive(Debug)]
249pub(crate) struct CachedBatchSlice {
250 batch: RecordBatch,
251 slice_lengths: Vec<usize>,
252}
253
254impl CachedBatchSlice {
255 fn metadata_size(&self) -> usize {
256 self.slice_lengths.capacity() * mem::size_of::<usize>()
257 }
258}
259
260pub(crate) struct RangeScanCacheValue {
261 cached_batches: Vec<CachedBatchSlice>,
262 estimated_batches_size: usize,
264}
265
266impl RangeScanCacheValue {
267 pub(crate) fn new(
268 cached_batches: Vec<CachedBatchSlice>,
269 estimated_batches_size: usize,
270 ) -> Self {
271 Self {
272 cached_batches,
273 estimated_batches_size,
274 }
275 }
276
277 pub(crate) fn estimated_size(&self) -> usize {
278 mem::size_of::<Self>()
279 + self.cached_batches.capacity() * mem::size_of::<CachedBatchSlice>()
280 + self
281 .cached_batches
282 .iter()
283 .map(CachedBatchSlice::metadata_size)
284 .sum::<usize>()
285 + self.estimated_batches_size
286 }
287}
288
289pub(crate) struct PartitionRangeRowGroups {
291 pub(crate) row_groups: Vec<(FileId, i64)>,
293 pub(crate) only_file_sources: bool,
294}
295
296pub(crate) fn collect_partition_range_row_groups(
298 stream_ctx: &StreamContext,
299 part_range: &PartitionRange,
300) -> PartitionRangeRowGroups {
301 let range_meta = &stream_ctx.ranges[part_range.identifier];
302 let mut row_groups = Vec::new();
303 let mut only_file_sources = true;
304
305 for index in &range_meta.row_group_indices {
306 if stream_ctx.is_file_range_index(*index) {
307 let file_id = stream_ctx.input.file_from_index(*index).file_id().file_id();
308 row_groups.push((file_id, index.row_group_index));
309 } else {
310 only_file_sources = false;
311 }
312 }
313
314 row_groups.sort_unstable_by(|a, b| a.0.as_bytes().cmp(b.0.as_bytes()).then(a.1.cmp(&b.1)));
315
316 PartitionRangeRowGroups {
317 row_groups,
318 only_file_sources,
319 }
320}
321
322pub(crate) fn implied_time_range_from_exprs(
340 ts_col_name: &str,
341 ts_col_unit: TimeUnit,
342 exprs: &[&Expr],
343) -> Option<TimestampRange> {
344 if exprs.is_empty() {
345 return None;
346 }
347
348 let mut acc = TimestampRange::min_to_max();
349 for expr in exprs {
350 let r = implied_time_range_from_expr(ts_col_name, ts_col_unit, expr)?;
351 acc = acc.and(&r);
352 }
353 Some(acc)
354}
355
356fn implied_time_range_from_expr(
357 ts_col_name: &str,
358 ts_col_unit: TimeUnit,
359 expr: &Expr,
360) -> Option<TimestampRange> {
361 match expr {
362 Expr::BinaryExpr(BinaryExpr { left, op, right }) => match op {
363 Operator::And => {
364 let l = implied_time_range_from_expr(ts_col_name, ts_col_unit, left)?;
365 let r = implied_time_range_from_expr(ts_col_name, ts_col_unit, right)?;
366 Some(l.and(&r))
367 }
368 Operator::Eq | Operator::Lt | Operator::LtEq | Operator::Gt | Operator::GtEq => {
369 implied_from_cmp(ts_col_name, ts_col_unit, left, *op, right)
370 }
371 _ => None,
375 },
376 Expr::Between(Between {
377 expr,
378 negated,
379 low,
380 high,
381 }) => {
382 if *negated {
383 return None;
384 }
385 implied_from_between(ts_col_name, ts_col_unit, expr, low, high)
386 }
387 _ => None,
389 }
390}
391
392fn match_ts_column_literal<'a>(
393 ts_col_name: &str,
394 left: &'a Expr,
395 right: &'a Expr,
396) -> Option<(Timestamp, bool)> {
397 let (col, scalar, reverse) = match (left, right) {
398 (Expr::Column(c), Expr::Literal(s, _)) => (c, s, false),
399 (Expr::Literal(s, _), Expr::Column(c)) => (c, s, true),
400 _ => return None,
401 };
402 if col.name != ts_col_name {
403 return None;
404 }
405 if is_string_timestamp_literal(scalar) {
408 return None;
409 }
410 scalar_value_to_timestamp(scalar, None).map(|t| (t, reverse))
411}
412
413fn implied_from_cmp(
414 ts_col_name: &str,
415 ts_col_unit: TimeUnit,
416 left: &Expr,
417 op: Operator,
418 right: &Expr,
419) -> Option<TimestampRange> {
420 let (ts, reverse) = match_ts_column_literal(ts_col_name, left, right)?;
421 let op = if reverse {
423 match op {
424 Operator::Lt => Operator::Gt,
425 Operator::LtEq => Operator::GtEq,
426 Operator::Gt => Operator::Lt,
427 Operator::GtEq => Operator::LtEq,
428 Operator::Eq => Operator::Eq,
429 _ => return None,
430 }
431 } else {
432 op
433 };
434
435 match op {
436 Operator::GtEq => {
437 let b = ts.convert_to_ceil(ts_col_unit)?;
439 Some(TimestampRange::from_start(b))
440 }
441 Operator::Gt => {
442 let v = ts.convert_to(ts_col_unit)?.value().checked_add(1)?;
444 Some(TimestampRange::from_start(Timestamp::new(v, ts_col_unit)))
445 }
446 Operator::LtEq => {
447 let b = ts.convert_to(ts_col_unit)?;
449 Some(TimestampRange::until_end(b, true))
450 }
451 Operator::Lt => {
452 let b = ts.convert_to_ceil(ts_col_unit)?;
455 Some(TimestampRange::until_end(b, false))
456 }
457 Operator::Eq => {
458 let f = ts.convert_to(ts_col_unit)?;
460 let c = ts.convert_to_ceil(ts_col_unit)?;
461 if f.value() != c.value() {
462 return None;
463 }
464 Some(TimestampRange::single(f))
465 }
466 _ => None,
467 }
468}
469
470fn implied_from_between(
471 ts_col_name: &str,
472 ts_col_unit: TimeUnit,
473 expr: &Expr,
474 low: &Expr,
475 high: &Expr,
476) -> Option<TimestampRange> {
477 let Expr::Column(c) = expr else {
478 return None;
479 };
480 if c.name != ts_col_name {
481 return None;
482 }
483 let (low_s, high_s) = match (low, high) {
484 (Expr::Literal(l, _), Expr::Literal(h, _)) => (l, h),
485 _ => return None,
486 };
487 if is_string_timestamp_literal(low_s) || is_string_timestamp_literal(high_s) {
488 return None;
489 }
490 let low_ts = scalar_value_to_timestamp(low_s, None)?;
491 let high_ts = scalar_value_to_timestamp(high_s, None)?;
492 let lo = low_ts.convert_to_ceil(ts_col_unit)?;
494 let hi = high_ts.convert_to(ts_col_unit)?;
495 Some(TimestampRange::new_inclusive(Some(lo), Some(hi)))
496}
497
498pub(crate) fn build_range_cache_key(
500 stream_ctx: &StreamContext,
501 part_range: &PartitionRange,
502) -> Option<RangeScanCacheKey> {
503 build_range_cache_key_inner(stream_ctx, part_range, None)
504}
505
506pub(crate) fn build_candidate_range_cache_key(
508 stream_ctx: &StreamContext,
509 part_range: &PartitionRange,
510) -> Option<RangeScanCacheKey> {
511 build_range_cache_key_inner(
512 stream_ctx,
513 part_range,
514 Some(RangeScanStage::CandidateSeries),
515 )
516}
517
518pub(crate) fn build_series_range_cache_key(
520 stream_ctx: &StreamContext,
521 part_range: &PartitionRange,
522 range: SeriesRange,
523) -> Option<RangeScanCacheKey> {
524 build_range_cache_key_inner(
525 stream_ctx,
526 part_range,
527 Some(RangeScanStage::SeriesData(range)),
528 )
529}
530
531fn build_range_cache_key_inner(
532 stream_ctx: &StreamContext,
533 part_range: &PartitionRange,
534 stage: Option<RangeScanStage>,
535) -> Option<RangeScanCacheKey> {
536 if !stream_ctx.input.cache_strategy.has_range_result_cache() {
537 return None;
538 }
539
540 let fingerprint = stream_ctx.input.scan_fingerprint()?;
541
542 let has_dyn_filters = stream_ctx
544 .input
545 .predicate_group()
546 .predicate_without_region()
547 .is_some_and(|p| !p.dyn_filters().is_empty());
548 if has_dyn_filters {
549 return None;
550 }
551
552 let rg = collect_partition_range_row_groups(stream_ctx, part_range);
553 if !rg.only_file_sources || rg.row_groups.is_empty() {
554 return None;
555 }
556
557 let range_meta = &stream_ctx.ranges[part_range.identifier];
564 let (file_min, file_max) = range_meta.time_range;
565 let covers = match stream_ctx.input.implied_time_range() {
566 Some(implied) if !implied.is_empty() => {
569 implied.contains(&file_min) && implied.contains(&file_max)
575 }
576 _ => false,
577 };
578 let scan = if covers {
579 fingerprint.without_time_filters()
580 } else {
581 fingerprint.clone()
582 };
583 let scan = match stage {
584 Some(RangeScanStage::CandidateSeries) => scan.for_candidate_series(),
585 Some(RangeScanStage::SeriesData(assignment)) => scan.for_series_data(assignment),
586 Some(RangeScanStage::Data) | None => scan,
587 };
588
589 Some(RangeScanCacheKey {
590 region_id: stream_ctx.input.region_metadata().region_id,
591 row_groups: rg.row_groups,
592 scan,
593 })
594}
595
596pub(crate) fn cached_flat_range_stream(value: Arc<RangeScanCacheValue>) -> BoxedRecordBatchStream {
598 Box::pin(try_stream! {
599 for cached_batch in &value.cached_batches {
600 let mut offset = 0;
601 for &len in &cached_batch.slice_lengths {
602 yield cached_batch.batch.slice(offset, len);
603 offset += len;
604 }
605 }
606 })
607}
608
609enum CacheConcatCommand {
610 Compact(Vec<RecordBatch>),
611 Finish {
612 pending: Vec<RecordBatch>,
613 key: RangeScanCacheKey,
614 cache_strategy: CacheStrategy,
615 part_metrics: PartitionMetrics,
616 result_tx: Option<oneshot::Sender<Result<Arc<RangeScanCacheValue>>>>,
617 },
618}
619
620#[derive(Default)]
621struct CacheConcatState {
622 cached_batches: Vec<CachedBatchSlice>,
623 estimated_size: usize,
624}
625
626impl CacheConcatState {
627 async fn compact(
628 &mut self,
629 batches: Vec<RecordBatch>,
630 limiter: &crate::cache::RangeResultMemoryLimiter,
631 ) -> Result<()> {
632 if batches.is_empty() {
633 return Ok(());
634 }
635
636 let input_size = batches
637 .iter()
638 .map(RecordBatch::get_array_memory_size)
639 .sum::<usize>();
640 let _permit = limiter.acquire(input_size).await?;
641
642 let compacted = compact_record_batches(batches)?;
643 self.estimated_size += compacted.batch.get_array_memory_size();
644 self.cached_batches.push(compacted);
645 Ok(())
646 }
647
648 fn finish(self) -> RangeScanCacheValue {
649 RangeScanCacheValue::new(self.cached_batches, self.estimated_size)
650 }
651}
652
653fn compact_record_batches(batches: Vec<RecordBatch>) -> Result<CachedBatchSlice> {
654 debug_assert!(!batches.is_empty());
655
656 let slice_lengths = batches.iter().map(RecordBatch::num_rows).collect();
657 build_cached_batch_slice(batches, slice_lengths)
658}
659
660fn build_cached_batch_slice(
661 batches: Vec<RecordBatch>,
662 slice_lengths: Vec<usize>,
663) -> Result<CachedBatchSlice> {
664 let batch = if batches.len() == 1 {
665 batches.into_iter().next().unwrap()
666 } else {
667 let schema = batches[0].schema();
668 concat_batches(&schema, &batches).context(ComputeArrowSnafu)?
669 };
670
671 Ok(CachedBatchSlice {
672 batch,
673 slice_lengths,
674 })
675}
676
677async fn run_cache_concat_task(
678 mut rx: mpsc::UnboundedReceiver<CacheConcatCommand>,
679 limiter: Arc<crate::cache::RangeResultMemoryLimiter>,
680 skip_threshold_bytes: usize,
681) {
682 let mut state = CacheConcatState::default();
683
684 while let Some(cmd) = rx.recv().await {
685 match cmd {
686 CacheConcatCommand::Compact(batches) => {
687 if let Err(err) = state.compact(batches, &limiter).await {
688 warn!(err; "Failed to compact range cache batches");
689 return;
690 }
691 if state.estimated_size > skip_threshold_bytes {
694 return;
695 }
696 }
697 CacheConcatCommand::Finish {
698 pending,
699 key,
700 cache_strategy,
701 part_metrics,
702 result_tx,
703 } => {
704 let compact_result = state
705 .compact(pending, &limiter)
706 .await
707 .map(|()| state.finish());
708 let result = match compact_result {
709 Ok(v) => {
710 let value = Arc::new(v);
711 part_metrics
712 .inc_range_cache_size(key.estimated_size() + value.estimated_size());
713 cache_strategy.put_range_result(key, value.clone());
714
715 Ok(value)
716 }
717 Err(e) => {
718 warn!(e; "Failed to finalize range cache batches");
719
720 Err(e)
721 }
722 };
723
724 if let Some(tx) = result_tx {
725 let _ = tx.send(result);
726 }
727
728 break;
729 }
730 }
731 }
732}
733
734struct CacheBatchBuffer {
735 buffered_batches: Vec<RecordBatch>,
736 buffered_rows: usize,
737 buffered_size: usize,
738 sender: Option<mpsc::UnboundedSender<CacheConcatCommand>>,
739 concat_task: Option<common_runtime::JoinHandle<()>>,
742}
743
744impl CacheBatchBuffer {
745 fn new(cache_strategy: &CacheStrategy) -> Self {
746 let (sender, concat_task) = cache_strategy
747 .range_result_memory_limiter()
748 .map(|limiter| {
749 let skip_threshold_bytes = cache_strategy.range_result_cache_size().unwrap_or(0);
750 let (tx, rx) = mpsc::unbounded_channel();
751 let task = common_runtime::spawn_query(run_cache_concat_task(
752 rx,
753 limiter.clone(),
754 skip_threshold_bytes,
755 ));
756 (tx, task)
757 })
758 .unzip();
759
760 Self {
761 buffered_batches: Vec::new(),
762 buffered_rows: 0,
763 buffered_size: 0,
764 sender,
765 concat_task,
766 }
767 }
768
769 fn push(&mut self, batch: RecordBatch) -> Result<()> {
770 if self.sender.is_none() {
771 return Ok(());
772 }
773
774 self.buffered_rows += batch.num_rows();
775 self.buffered_size += batch.get_array_memory_size();
776 self.buffered_batches.push(batch);
777
778 if self.buffered_batches.len() > 1
779 && (self.buffered_rows > DEFAULT_READ_BATCH_SIZE
780 || self.buffered_size > RANGE_CACHE_COMPACT_THRESHOLD_BYTES)
781 {
782 self.notify_compact();
783 }
784
785 Ok(())
786 }
787
788 fn notify_compact(&mut self) {
789 if self.buffered_batches.is_empty() || self.sender.is_none() {
790 return;
791 }
792
793 let batches = mem::take(&mut self.buffered_batches);
794 self.buffered_rows = 0;
795 self.buffered_size = 0;
796
797 let Some(sender) = &self.sender else {
798 return;
799 };
800 if sender.send(CacheConcatCommand::Compact(batches)).is_err() {
801 self.sender = None;
802 }
803 }
804
805 fn finish(
806 mut self,
807 key: RangeScanCacheKey,
808 cache_strategy: CacheStrategy,
809 part_metrics: PartitionMetrics,
810 result_tx: Option<oneshot::Sender<Result<Arc<RangeScanCacheValue>>>>,
811 ) {
812 let Some(sender) = self.sender.take() else {
813 return;
814 };
815
816 if sender
817 .send(CacheConcatCommand::Finish {
818 pending: mem::take(&mut self.buffered_batches),
819 key,
820 cache_strategy,
821 part_metrics,
822 result_tx,
823 })
824 .is_ok()
825 {
826 self.concat_task = None;
829 }
830 }
831}
832
833impl Drop for CacheBatchBuffer {
834 fn drop(&mut self) {
835 if let Some(task) = &self.concat_task {
839 task.abort();
840 }
841 }
842}
843
844pub(crate) fn cache_flat_range_stream(
846 mut stream: BoxedRecordBatchStream,
847 cache_strategy: CacheStrategy,
848 key: RangeScanCacheKey,
849 part_metrics: PartitionMetrics,
850) -> BoxedRecordBatchStream {
851 Box::pin(try_stream! {
852 let mut buffer = CacheBatchBuffer::new(&cache_strategy);
853 while let Some(batch) = stream.try_next().await? {
854 buffer.push(batch.clone())?;
855 yield batch;
856 }
857
858 buffer.finish(key, cache_strategy, part_metrics, None);
859 })
860}
861
862#[cfg(feature = "test")]
867pub fn bench_cache_flat_range_stream(
868 stream: BoxedRecordBatchStream,
869 cache_size_bytes: u64,
870 region_id: RegionId,
871) -> BoxedRecordBatchStream {
872 use std::time::Instant;
873
874 use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
875
876 use crate::region::options::MergeMode;
877
878 let cache_manager = Arc::new(
879 crate::cache::CacheManager::builder()
880 .range_result_cache_size(cache_size_bytes)
881 .build(),
882 );
883 let cache_strategy = CacheStrategy::EnableAll(cache_manager);
884
885 let fingerprint = ScanRequestFingerprintBuilder {
886 read_columns: ReadColumns::new(std::iter::empty()),
887 read_column_types: vec![],
888 filters: vec![],
889 time_filters: vec![],
890 series_row_selector: None,
891 append_mode: false,
892 filter_deleted: false,
893 merge_mode: MergeMode::LastRow,
894 sequence_range: None,
895 partition_expr_version: 0,
896 }
897 .build();
898
899 let key = RangeScanCacheKey {
900 region_id,
901 row_groups: vec![],
902 scan: fingerprint,
903 };
904
905 let metrics_set = ExecutionPlanMetricsSet::new();
906 let part_metrics =
907 PartitionMetrics::new(region_id, 0, "bench", Instant::now(), false, &metrics_set);
908
909 cache_flat_range_stream(stream, cache_strategy, key, part_metrics)
910}
911
912#[cfg(test)]
913mod tests {
914 use std::sync::Arc;
915 use std::time::Instant;
916
917 use common_time::Timestamp;
918 use common_time::range::TimestampRange;
919 use common_time::timestamp::TimeUnit;
920 use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
921 use datafusion_common::ScalarValue;
922 use datafusion_expr::{Expr, col, lit};
923 use smallvec::smallvec;
924 use store_api::storage::{FileId, RegionId};
925
926 use super::*;
927 use crate::cache::CacheManager;
928 use crate::read::flat_projection::FlatProjectionMapper;
929 use crate::read::range::{RangeMeta, RowGroupIndex, SourceIndex};
930 use crate::read::scan_region::{PredicateGroup, ScanInput};
931 use crate::sst::file::FileTimeRange;
932 use crate::test_util::memtable_util::metadata_with_primary_key;
933 use crate::test_util::scheduler_util::SchedulerEnv;
934 use crate::test_util::sst_util::sst_file_handle_with_file_id;
935
936 fn test_cache_strategy() -> CacheStrategy {
937 CacheStrategy::EnableAll(Arc::new(
938 CacheManager::builder()
939 .range_result_cache_size(1024 * 1024)
940 .build(),
941 ))
942 }
943
944 fn test_scan_fingerprint(
945 filters: Vec<String>,
946 time_filters: Vec<String>,
947 series_row_selector: Option<TimeSeriesRowSelector>,
948 filter_deleted: bool,
949 partition_expr_version: u64,
950 ) -> ScanRequestFingerprint {
951 let read_columns = ReadColumns::new([1, 2]);
952 ScanRequestFingerprintBuilder {
953 read_columns,
954 read_column_types: vec![None, None],
955 filters,
956 time_filters,
957 series_row_selector,
958 append_mode: false,
959 filter_deleted,
960 merge_mode: MergeMode::LastRow,
961 sequence_range: None,
962 partition_expr_version,
963 }
964 .build()
965 }
966
967 fn test_cache_context(strategy: &CacheStrategy) -> (RangeScanCacheKey, PartitionMetrics) {
968 let region_id = RegionId::new(1, 1);
969 let key = RangeScanCacheKey {
970 region_id,
971 row_groups: vec![],
972 scan: test_scan_fingerprint(vec![], vec![], None, false, 0),
973 };
974
975 let metrics_set = ExecutionPlanMetricsSet::new();
976 let part_metrics =
977 PartitionMetrics::new(region_id, 0, "test", Instant::now(), false, &metrics_set);
978
979 assert!(strategy.get_range_result(&key).is_none());
980 (key, part_metrics)
981 }
982
983 async fn finish_cache_batch_buffer(
984 buffer: CacheBatchBuffer,
985 key: RangeScanCacheKey,
986 cache_strategy: CacheStrategy,
987 part_metrics: PartitionMetrics,
988 ) -> Result<Arc<RangeScanCacheValue>> {
989 let (tx, rx) = oneshot::channel();
990 common_telemetry::info!("finish start");
991 buffer.finish(key, cache_strategy, part_metrics, Some(tx));
992 common_telemetry::info!("finish end");
993 rx.await.context(crate::error::RecvSnafu)?
994 }
995
996 async fn new_stream_context(
997 filters: Vec<Expr>,
998 query_time_range: Option<TimestampRange>,
999 partition_time_range: FileTimeRange,
1000 ) -> (StreamContext, PartitionRange) {
1001 let env = SchedulerEnv::new().await;
1002 let metadata = Arc::new(metadata_with_primary_key(vec![0, 1], false));
1003 let mapper = FlatProjectionMapper::new(&metadata, [0, 2, 3]).unwrap();
1004 let predicate = PredicateGroup::new(metadata.as_ref(), &filters).unwrap();
1005 let file_id = FileId::random();
1006 let file = sst_file_handle_with_file_id(
1007 file_id,
1008 partition_time_range.0.value(),
1009 partition_time_range.1.value(),
1010 );
1011 let input = ScanInput::builder(env.access_layer.clone(), mapper)
1012 .with_predicate(predicate)
1013 .with_time_range(query_time_range)
1014 .with_files(vec![file])
1015 .with_cache(test_cache_strategy())
1016 .build();
1017 let range_meta = RangeMeta {
1018 time_range: partition_time_range,
1019 indices: smallvec![SourceIndex {
1020 index: 0,
1021 num_row_groups: 1,
1022 }],
1023 row_group_indices: smallvec![RowGroupIndex {
1024 index: 0,
1025 row_group_index: 0,
1026 }],
1027 num_rows: 10,
1028 };
1029 let partition_range = range_meta.new_partition_range(0);
1030 let stream_ctx = StreamContext {
1031 input,
1032 ranges: vec![range_meta],
1033 query_start: Instant::now(),
1034 };
1035
1036 (stream_ctx, partition_range)
1037 }
1038
1039 fn ts_lit(val: i64) -> Expr {
1041 lit(ScalarValue::TimestampMillisecond(Some(val), None))
1042 }
1043
1044 fn normalized_exprs(exprs: impl IntoIterator<Item = Expr>) -> Vec<String> {
1045 let mut exprs = exprs
1046 .into_iter()
1047 .map(|expr| expr.to_string())
1048 .collect::<Vec<_>>();
1049 exprs.sort_unstable();
1050 exprs
1051 }
1052
1053 async fn assert_range_cache_filters(
1054 filters: Vec<Expr>,
1055 query_time_range: Option<TimestampRange>,
1056 partition_time_range: FileTimeRange,
1057 expected_filters: Vec<Expr>,
1058 expected_time_filters: Vec<Expr>,
1059 ) {
1060 let (stream_ctx, part_range) =
1061 new_stream_context(filters, query_time_range, partition_time_range).await;
1062
1063 let key = build_range_cache_key(&stream_ctx, &part_range).unwrap();
1064
1065 assert_eq!(
1066 key.scan.filters(),
1067 normalized_exprs(expected_filters).as_slice()
1068 );
1069 assert_eq!(
1070 key.scan.time_filters(),
1071 normalized_exprs(expected_time_filters).as_slice()
1072 );
1073 }
1074
1075 #[tokio::test]
1076 async fn range_cache_time_filter_key_cases() {
1077 let partition = (
1078 Timestamp::new_millisecond(1000),
1079 Timestamp::new_millisecond(2000),
1080 );
1081
1082 struct Case {
1083 filters: Vec<Expr>,
1084 query_time_range: Option<TimestampRange>,
1085 expected_filters: Vec<Expr>,
1086 expected_time_filters: Vec<Expr>,
1087 }
1088
1089 for case in [
1093 Case {
1094 filters: vec![
1095 col("ts").gt_eq(ts_lit(1000)),
1096 col("ts").lt(ts_lit(2001)),
1097 col("ts").is_not_null(),
1098 col("k0").eq(lit("foo")),
1099 ],
1100 query_time_range: TimestampRange::with_unit(1000, 2002, TimeUnit::Millisecond),
1101 expected_filters: vec![col("k0").eq(lit("foo")), col("ts").is_not_null()],
1102 expected_time_filters: vec![],
1103 },
1104 Case {
1105 filters: vec![
1106 col("ts").gt_eq(ts_lit(500)),
1107 col("ts").lt(ts_lit(3000)),
1108 col("k0").eq(lit("foo")),
1109 ],
1110 query_time_range: TimestampRange::with_unit(500, 3000, TimeUnit::Millisecond),
1111 expected_filters: vec![col("k0").eq(lit("foo"))],
1112 expected_time_filters: vec![],
1113 },
1114 Case {
1115 filters: vec![
1116 col("ts").gt_eq(ts_lit(1000)),
1117 col("ts").lt_eq(ts_lit(2000)),
1118 col("k0").eq(lit("foo")),
1119 ],
1120 query_time_range: TimestampRange::with_unit(1000, 2001, TimeUnit::Millisecond),
1121 expected_filters: vec![col("k0").eq(lit("foo"))],
1122 expected_time_filters: vec![],
1123 },
1124 Case {
1125 filters: vec![
1126 col("ts").between(ts_lit(1000), ts_lit(2000)),
1127 col("k0").eq(lit("foo")),
1128 ],
1129 query_time_range: TimestampRange::with_unit(1000, 2001, TimeUnit::Millisecond),
1130 expected_filters: vec![col("k0").eq(lit("foo"))],
1131 expected_time_filters: vec![],
1132 },
1133 Case {
1134 filters: vec![col("ts").gt_eq(ts_lit(1200)), col("k0").eq(lit("foo"))],
1135 query_time_range: TimestampRange::with_unit(1200, 2001, TimeUnit::Millisecond),
1136 expected_filters: vec![col("k0").eq(lit("foo"))],
1137 expected_time_filters: vec![col("ts").gt_eq(ts_lit(1200))],
1138 },
1139 Case {
1140 filters: vec![
1141 col("ts").gt_eq(ts_lit(1500)),
1142 col("ts").is_not_null(),
1143 col("k0").eq(lit("foo")),
1144 ],
1145 query_time_range: None,
1146 expected_filters: vec![col("k0").eq(lit("foo")), col("ts").is_not_null()],
1147 expected_time_filters: vec![col("ts").gt_eq(ts_lit(1500))],
1148 },
1149 ] {
1150 assert_range_cache_filters(
1151 case.filters,
1152 case.query_time_range,
1153 partition,
1154 case.expected_filters,
1155 case.expected_time_filters,
1156 )
1157 .await;
1158 }
1159 }
1160
1161 #[tokio::test]
1162 async fn two_distinct_queries_share_cache_key_when_both_cover() {
1163 let partition_range = (
1164 Timestamp::new_millisecond(1000),
1165 Timestamp::new_millisecond(2000),
1166 );
1167
1168 let (ctx_a, part_a) = new_stream_context(
1169 vec![
1170 col("ts").gt_eq(ts_lit(500)),
1171 col("ts").lt(ts_lit(3000)),
1172 col("k0").eq(lit("foo")),
1173 ],
1174 TimestampRange::with_unit(500, 3000, TimeUnit::Millisecond),
1175 partition_range,
1176 )
1177 .await;
1178 let (ctx_b, part_b) = new_stream_context(
1179 vec![
1180 col("ts").gt_eq(ts_lit(100)),
1181 col("ts").lt(ts_lit(5000)),
1182 col("k0").eq(lit("foo")),
1183 ],
1184 TimestampRange::with_unit(100, 5000, TimeUnit::Millisecond),
1185 partition_range,
1186 )
1187 .await;
1188
1189 let key_a = build_range_cache_key(&ctx_a, &part_a).unwrap();
1190 let key_b = build_range_cache_key(&ctx_b, &part_b).unwrap();
1191 assert_eq!(key_a.scan, key_b.scan);
1192 assert!(key_a.scan.time_filters().is_empty());
1193 }
1194
1195 #[tokio::test]
1196 async fn scan_stages_have_separate_cache_keys() {
1197 let partition_range = (
1198 Timestamp::new_millisecond(1000),
1199 Timestamp::new_millisecond(2000),
1200 );
1201 let (ctx, part_range) =
1202 new_stream_context(vec![col("k0").eq(lit("foo"))], None, partition_range).await;
1203
1204 let data_key = build_range_cache_key(&ctx, &part_range).unwrap();
1205 let candidate_key = build_candidate_range_cache_key(&ctx, &part_range).unwrap();
1206 let range_0 = SeriesRange::new(0, 2).unwrap();
1207 let range_1 = SeriesRange::new(1, 2).unwrap();
1208 let series_key_0 = build_series_range_cache_key(&ctx, &part_range, range_0).unwrap();
1209 let series_key_1 = build_series_range_cache_key(&ctx, &part_range, range_1).unwrap();
1210
1211 assert_ne!(data_key.scan, candidate_key.scan);
1212 assert_ne!(data_key.scan, series_key_0.scan);
1213 assert_ne!(candidate_key.scan, series_key_0.scan);
1214 assert_ne!(series_key_0.scan, series_key_1.scan);
1215 assert_eq!(data_key.row_groups, candidate_key.row_groups);
1216 assert_eq!(data_key.row_groups, series_key_0.row_groups);
1217 }
1218
1219 #[tokio::test]
1220 async fn disables_optimization_on_or_clause() {
1221 let partition_range = (
1222 Timestamp::new_millisecond(1000),
1223 Timestamp::new_millisecond(2000),
1224 );
1225
1226 let or_a = col("ts").gt_eq(ts_lit(1000)).or(col("ts").lt(ts_lit(500)));
1227 let or_b = col("ts").gt_eq(ts_lit(900)).or(col("ts").lt(ts_lit(400)));
1228
1229 let (ctx_a, part_a) = new_stream_context(
1230 vec![or_a.clone(), col("k0").eq(lit("foo"))],
1231 None,
1232 partition_range,
1233 )
1234 .await;
1235 let (ctx_b, part_b) = new_stream_context(
1236 vec![or_b.clone(), col("k0").eq(lit("foo"))],
1237 None,
1238 partition_range,
1239 )
1240 .await;
1241
1242 assert!(ctx_a.input.implied_time_range().is_none());
1243 let key_a = build_range_cache_key(&ctx_a, &part_a).unwrap();
1244 let key_b = build_range_cache_key(&ctx_b, &part_b).unwrap();
1245 assert_ne!(key_a.scan, key_b.scan);
1246 assert_eq!(
1247 key_a.scan.time_filters(),
1248 normalized_exprs([or_a]).as_slice()
1249 );
1250 }
1251
1252 #[tokio::test]
1253 async fn empty_implied_range_does_not_panic_on_non_ms_file_range() {
1254 let partition = (
1259 Timestamp::new_millisecond(1000),
1260 Timestamp::new_millisecond(2000),
1261 );
1262
1263 let (mut ctx, part_range) = new_stream_context(
1264 vec![
1265 col("ts").gt_eq(ts_lit(1500)),
1266 col("ts").lt(ts_lit(1500)),
1267 col("k0").eq(lit("foo")),
1268 ],
1269 TimestampRange::with_unit(1500, 3000, TimeUnit::Millisecond),
1270 partition,
1271 )
1272 .await;
1273
1274 assert!(ctx.input.implied_time_range().unwrap().is_empty());
1275 ctx.ranges[0].time_range = (
1276 Timestamp::new(1_000_000_000, TimeUnit::Nanosecond),
1277 Timestamp::new(2_000_000_000, TimeUnit::Nanosecond),
1278 );
1279
1280 let key = build_range_cache_key(&ctx, &part_range).unwrap();
1281 assert!(!key.scan.time_filters().is_empty());
1283 }
1284
1285 #[tokio::test]
1286 async fn mixed_unit_file_range_is_compared_by_instant() {
1287 let partition = (
1292 Timestamp::new_millisecond(1000),
1293 Timestamp::new_millisecond(2000),
1294 );
1295
1296 let (mut ctx, part_range) = new_stream_context(
1300 vec![
1301 col("ts").gt_eq(ts_lit(0)),
1302 col("ts").lt(ts_lit(3000)),
1303 col("k0").eq(lit("foo")),
1304 ],
1305 TimestampRange::with_unit(0, 3000, TimeUnit::Millisecond),
1306 partition,
1307 )
1308 .await;
1309 ctx.ranges[0].time_range = (
1310 Timestamp::new_microsecond(1_000_000),
1311 Timestamp::new_microsecond(2_000_000),
1312 );
1313 let key = build_range_cache_key(&ctx, &part_range).unwrap();
1314 assert!(key.scan.time_filters().is_empty());
1315
1316 let (mut ctx, part_range) = new_stream_context(
1319 vec![
1320 col("ts").gt_eq(ts_lit(1500)),
1321 col("ts").lt(ts_lit(3000)),
1322 col("k0").eq(lit("foo")),
1323 ],
1324 TimestampRange::with_unit(1500, 3000, TimeUnit::Millisecond),
1325 partition,
1326 )
1327 .await;
1328 ctx.ranges[0].time_range = (
1329 Timestamp::new_microsecond(1_000_000),
1330 Timestamp::new_microsecond(2_000_000),
1331 );
1332 let key = build_range_cache_key(&ctx, &part_range).unwrap();
1333 assert!(!key.scan.time_filters().is_empty());
1334 }
1335
1336 fn ms_ts(v: i64) -> Timestamp {
1337 Timestamp::new_millisecond(v)
1338 }
1339
1340 fn implied_ms(expr: Expr) -> Option<TimestampRange> {
1341 implied_time_range_from_exprs("ts", TimeUnit::Millisecond, &[&expr])
1342 }
1343
1344 #[test]
1345 fn implied_time_range_supported_exprs() {
1346 for (expr, expected) in [
1347 (
1348 col("ts").gt_eq(ts_lit(1000)),
1349 Some(TimestampRange::from_start(ms_ts(1000))),
1350 ),
1351 (
1352 col("ts").gt(ts_lit(1000)),
1353 Some(TimestampRange::from_start(ms_ts(1001))),
1354 ),
1355 (
1356 col("ts").lt_eq(ts_lit(2000)),
1357 Some(TimestampRange::until_end(ms_ts(2000), true)),
1358 ),
1359 (
1360 col("ts").lt(ts_lit(2000)),
1361 Some(TimestampRange::until_end(ms_ts(2000), false)),
1362 ),
1363 (
1364 col("ts").eq(ts_lit(1500)),
1365 Some(TimestampRange::single(ms_ts(1500))),
1366 ),
1367 (
1368 ts_lit(1000).lt_eq(col("ts")),
1369 Some(TimestampRange::from_start(ms_ts(1000))),
1370 ),
1371 (
1372 col("ts").between(ts_lit(1000), ts_lit(2000)),
1373 Some(TimestampRange::new_inclusive(
1374 Some(ms_ts(1000)),
1375 Some(ms_ts(2000)),
1376 )),
1377 ),
1378 (
1379 col("ts")
1380 .gt_eq(ts_lit(1000))
1381 .and(col("ts").lt(ts_lit(2000))),
1382 TimestampRange::with_unit(1000, 2000, TimeUnit::Millisecond),
1383 ),
1384 (
1385 col("ts")
1386 .gt_eq(ts_lit(1000))
1387 .and(col("ts").lt(ts_lit(5000)))
1388 .and(col("ts").lt_eq(ts_lit(3000))),
1389 TimestampRange::with_unit(1000, 3001, TimeUnit::Millisecond),
1390 ),
1391 ] {
1392 assert_eq!(implied_ms(expr), expected);
1393 }
1394
1395 assert_eq!(
1396 implied_time_range_from_exprs("ts", TimeUnit::Millisecond, &[]),
1397 None
1398 );
1399 }
1400
1401 #[test]
1402 fn implied_time_range_unsupported_exprs() {
1403 let not_between = Expr::Between(Between {
1404 expr: Box::new(col("ts")),
1405 negated: true,
1406 low: Box::new(ts_lit(1000)),
1407 high: Box::new(ts_lit(2000)),
1408 });
1409
1410 for expr in [
1411 not_between,
1412 col("ts").gt_eq(ts_lit(1000)).or(col("ts").lt(ts_lit(500))),
1413 Expr::Not(Box::new(col("ts").gt_eq(ts_lit(1000)))),
1414 col("ts").in_list(vec![ts_lit(1000), ts_lit(2000)], false),
1415 col("ts").gt_eq(col("other")),
1416 col("other_ts").gt_eq(ts_lit(1000)),
1417 ] {
1418 assert!(implied_ms(expr).is_none());
1419 }
1420 }
1421
1422 #[test]
1423 fn implied_time_range_unit_conversion() {
1424 let second_1 = lit(ScalarValue::TimestampSecond(Some(1), None));
1425 let ns_1500 = lit(ScalarValue::TimestampNanosecond(Some(1_500_000_000), None));
1426 let ns_1500_5 = lit(ScalarValue::TimestampNanosecond(Some(1_500_500_000), None));
1427
1428 for (expr, expected) in [
1429 (
1430 col("ts").gt_eq(second_1.clone()),
1431 Some(TimestampRange::from_start(ms_ts(1000))),
1432 ),
1433 (
1434 col("ts").lt_eq(second_1),
1435 Some(TimestampRange::until_end(ms_ts(1000), true)),
1436 ),
1437 (
1438 col("ts").eq(ns_1500),
1439 Some(TimestampRange::single(ms_ts(1500))),
1440 ),
1441 (col("ts").eq(ns_1500_5.clone()), None),
1442 (
1443 col("ts").gt_eq(ns_1500_5.clone()),
1444 Some(TimestampRange::from_start(ms_ts(1501))),
1445 ),
1446 (
1447 col("ts").lt_eq(ns_1500_5.clone()),
1448 Some(TimestampRange::until_end(ms_ts(1500), true)),
1449 ),
1450 (
1451 col("ts").gt(ns_1500_5.clone()),
1452 Some(TimestampRange::from_start(ms_ts(1501))),
1453 ),
1454 (
1455 col("ts").lt(ns_1500_5),
1456 Some(TimestampRange::until_end(ms_ts(1501), false)),
1457 ),
1458 ] {
1459 assert_eq!(implied_ms(expr), expected);
1460 }
1461 }
1462
1463 #[test]
1464 fn selector_after_merge_changes_fingerprint() {
1465 let ordinary = test_scan_fingerprint(
1466 vec!["k0 = 'foo'".to_string()],
1467 vec![],
1468 Some(TimeSeriesRowSelector::LastRow { after_merge: false }),
1469 true,
1470 0,
1471 );
1472 let after_merge = test_scan_fingerprint(
1473 vec!["k0 = 'foo'".to_string()],
1474 vec![],
1475 Some(TimeSeriesRowSelector::LastRow { after_merge: true }),
1476 true,
1477 0,
1478 );
1479
1480 assert_ne!(ordinary, after_merge);
1481 }
1482
1483 #[test]
1484 fn true_selector_after_merge_is_preserved_by_fingerprint_transforms() {
1485 let normalized =
1486 test_scan_fingerprint(vec!["k0 = 'foo'".to_string()], vec![], None, true, 0);
1487
1488 assert!(normalized.time_filters().is_empty());
1489
1490 let fingerprint = test_scan_fingerprint(
1491 vec!["k0 = 'foo'".to_string()],
1492 vec!["ts >= 1000".to_string()],
1493 Some(TimeSeriesRowSelector::LastRow { after_merge: true }),
1494 true,
1495 7,
1496 );
1497
1498 let reset = fingerprint.without_time_filters();
1499 let candidate = fingerprint.for_candidate_series();
1500 let series_data = fingerprint.for_series_data(SeriesRange::new(0, 1).unwrap());
1501
1502 assert_eq!(reset.read_columns(), fingerprint.read_columns());
1503 assert_eq!(reset.read_column_types(), fingerprint.read_column_types());
1504 assert_eq!(reset.filters(), fingerprint.filters());
1505 assert!(reset.time_filters().is_empty());
1506 assert_eq!(reset.series_row_selector, fingerprint.series_row_selector);
1507 assert_eq!(
1508 fingerprint.series_row_selector,
1509 Some(TimeSeriesRowSelector::LastRow { after_merge: true })
1510 );
1511 assert_eq!(
1512 candidate.series_row_selector,
1513 fingerprint.series_row_selector
1514 );
1515 assert_eq!(
1516 series_data.series_row_selector,
1517 fingerprint.series_row_selector
1518 );
1519 assert_eq!(reset.append_mode, fingerprint.append_mode);
1520 assert_eq!(reset.filter_deleted, fingerprint.filter_deleted);
1521 assert_eq!(reset.merge_mode, fingerprint.merge_mode);
1522 assert_eq!(
1523 reset.partition_expr_version,
1524 fingerprint.partition_expr_version
1525 );
1526 }
1527
1528 fn test_schema() -> Arc<datatypes::arrow::datatypes::Schema> {
1529 use datatypes::arrow::datatypes::{DataType as ArrowDataType, Field, Schema};
1530
1531 Arc::new(Schema::new(vec![Field::new(
1532 "value",
1533 ArrowDataType::Int64,
1534 false,
1535 )]))
1536 }
1537
1538 fn make_batch(values: &[i64]) -> RecordBatch {
1539 use datatypes::arrow::array::Int64Array;
1540
1541 RecordBatch::try_new(
1542 test_schema(),
1543 vec![Arc::new(Int64Array::from(values.to_vec()))],
1544 )
1545 .unwrap()
1546 }
1547
1548 fn make_large_binary_batch(rows: usize, bytes_per_row: usize) -> RecordBatch {
1549 use datatypes::arrow::array::BinaryArray;
1550 use datatypes::arrow::datatypes::{DataType as ArrowDataType, Field, Schema};
1551
1552 let schema = Arc::new(Schema::new(vec![Field::new(
1553 "value",
1554 ArrowDataType::Binary,
1555 false,
1556 )]));
1557 let payload = vec![b'x'; bytes_per_row];
1558 let values = (0..rows).map(|_| payload.as_slice()).collect::<Vec<_>>();
1559
1560 RecordBatch::try_new(schema, vec![Arc::new(BinaryArray::from_vec(values))]).unwrap()
1561 }
1562
1563 #[test]
1564 fn compact_record_batches_keeps_original_boundaries() {
1565 let batches = vec![make_batch(&[1, 2]), make_batch(&[3]), make_batch(&[4, 5])];
1566
1567 let compacted = compact_record_batches(batches).unwrap();
1568
1569 assert_eq!(compacted.batch.num_rows(), 5);
1570 assert_eq!(compacted.slice_lengths, vec![2, 1, 2]);
1571 }
1572
1573 #[tokio::test]
1574 async fn cached_flat_range_stream_replays_original_batches() {
1575 let value = Arc::new(RangeScanCacheValue::new(
1576 vec![CachedBatchSlice {
1577 batch: make_batch(&[1, 2, 3]),
1578 slice_lengths: vec![2, 1],
1579 }],
1580 make_batch(&[1, 2, 3]).get_array_memory_size(),
1581 ));
1582
1583 let replayed = cached_flat_range_stream(value)
1584 .try_collect::<Vec<_>>()
1585 .await
1586 .unwrap();
1587
1588 assert_eq!(replayed.len(), 2);
1589 assert_eq!(replayed[0].num_rows(), 2);
1590 assert_eq!(replayed[1].num_rows(), 1);
1591 }
1592
1593 #[tokio::test]
1594 async fn dropped_cache_buffer_releases_batches_while_waiting_for_memory() {
1595 let strategy = test_cache_strategy();
1596 let limiter = strategy.range_result_memory_limiter().unwrap();
1597 let _permit = limiter
1599 .acquire(limiter.available_permits() * limiter.permit_bytes())
1600 .await
1601 .unwrap();
1602 let batch = make_batch(&vec![1; DEFAULT_READ_BATCH_SIZE / 2 + 1]);
1603 let weak = Arc::downgrade(batch.column(0));
1604 let mut buffer = CacheBatchBuffer::new(&strategy);
1605 buffer.push(batch.clone()).unwrap();
1606 buffer.push(batch).unwrap();
1607 assert!(buffer.buffered_batches.is_empty());
1609
1610 tokio::time::timeout(std::time::Duration::from_secs(5), async {
1613 while limiter.waited_acquires() == 0 {
1614 tokio::task::yield_now().await;
1615 }
1616 })
1617 .await
1618 .expect("concat task must wait for a memory permit");
1619 drop(buffer);
1620
1621 tokio::time::timeout(std::time::Duration::from_secs(5), async {
1622 while weak.upgrade().is_some() {
1623 tokio::task::yield_now().await;
1624 }
1625 })
1626 .await
1627 .expect("cancelled cache work must release input without waiting for a permit");
1628 }
1629
1630 #[tokio::test]
1631 async fn cache_batch_buffer_finishes_pending_batches() {
1632 let strategy = test_cache_strategy();
1633 let batch = make_batch(&[1, 2, 3]);
1634 let expected_size = batch.get_array_memory_size();
1635 let (key, part_metrics) = test_cache_context(&strategy);
1636
1637 let mut buffer = CacheBatchBuffer::new(&strategy);
1638 buffer.push(batch).unwrap();
1639
1640 let value = finish_cache_batch_buffer(buffer, key.clone(), strategy.clone(), part_metrics)
1641 .await
1642 .unwrap();
1643 assert_eq!(value.cached_batches.len(), 1);
1644 assert_eq!(value.cached_batches[0].slice_lengths, vec![3]);
1645 assert_eq!(value.estimated_batches_size, expected_size);
1646 assert!(Arc::ptr_eq(
1647 &value,
1648 &strategy.get_range_result(&key).unwrap()
1649 ));
1650 }
1651
1652 #[tokio::test]
1653 async fn cache_batch_buffer_compacts_when_rows_exceed_default_batch_size() {
1654 let strategy = test_cache_strategy();
1655 let batch = make_batch(&vec![1; DEFAULT_READ_BATCH_SIZE / 2 + 1]);
1656 let (key, part_metrics) = test_cache_context(&strategy);
1657
1658 let mut buffer = CacheBatchBuffer::new(&strategy);
1659 buffer.push(batch.clone()).unwrap();
1660 buffer.push(batch).unwrap();
1661
1662 assert_eq!(buffer.buffered_rows, 0);
1663 assert!(buffer.buffered_batches.is_empty());
1664
1665 let value = finish_cache_batch_buffer(buffer, key, strategy, part_metrics)
1666 .await
1667 .unwrap();
1668 assert_eq!(value.cached_batches.len(), 1);
1669 assert_eq!(
1670 value.cached_batches[0].slice_lengths,
1671 vec![
1672 DEFAULT_READ_BATCH_SIZE / 2 + 1,
1673 DEFAULT_READ_BATCH_SIZE / 2 + 1
1674 ]
1675 );
1676 }
1677
1678 #[tokio::test]
1679 async fn cache_batch_buffer_compacts_when_buffered_size_exceeds_threshold() {
1680 let large_batch = make_large_binary_batch(DEFAULT_READ_BATCH_SIZE, 4096);
1681 let strategy = CacheStrategy::EnableAll(Arc::new(
1682 CacheManager::builder()
1683 .range_result_cache_size((large_batch.get_array_memory_size() * 3) as u64)
1684 .build(),
1685 ));
1686 let (key, part_metrics) = test_cache_context(&strategy);
1687
1688 let mut buffer = CacheBatchBuffer::new(&strategy);
1689 buffer.push(large_batch.clone()).unwrap();
1690
1691 assert_eq!(buffer.buffered_rows, large_batch.num_rows());
1692 assert_eq!(buffer.buffered_batches.len(), 1);
1693
1694 buffer.push(large_batch.clone()).unwrap();
1695
1696 assert_eq!(buffer.buffered_rows, 0);
1697 assert!(buffer.buffered_batches.is_empty());
1698
1699 let value = finish_cache_batch_buffer(buffer, key, strategy, part_metrics)
1700 .await
1701 .unwrap();
1702 assert_eq!(value.cached_batches.len(), 1);
1703 assert_eq!(
1704 value.cached_batches[0].slice_lengths,
1705 vec![large_batch.num_rows(), large_batch.num_rows()]
1706 );
1707 }
1708
1709 #[tokio::test]
1710 async fn cache_batch_buffer_skips_cache_when_compacted_size_exceeds_limit() {
1711 let large_batch = make_large_binary_batch(DEFAULT_READ_BATCH_SIZE / 2 + 1, 4096);
1712 let budget = (large_batch.get_array_memory_size() as u64) * 2 + 1;
1714 let strategy = CacheStrategy::EnableAll(Arc::new(
1715 CacheManager::builder()
1716 .range_result_cache_size(budget)
1717 .build(),
1718 ));
1719 let (key, part_metrics) = test_cache_context(&strategy);
1720
1721 let mut buffer = CacheBatchBuffer::new(&strategy);
1722 for _ in 0..4 {
1723 buffer.push(large_batch.clone()).unwrap();
1724 }
1725 assert!(
1726 finish_cache_batch_buffer(buffer, key.clone(), strategy.clone(), part_metrics)
1727 .await
1728 .is_err()
1729 );
1730 assert!(strategy.get_range_result(&key).is_none());
1731 }
1732}