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