1mod builder;
16
17use std::collections::BTreeMap;
18use std::ops::Range;
19use std::sync::Arc;
20use std::time::Instant;
21
22use common_base::range_read::RangeReader;
23use common_telemetry::{tracing, warn};
24use datatypes::data_type::ConcreteDataType;
25use index::bloom_filter::applier::{BloomFilterApplier, InListPredicate};
26use index::bloom_filter::reader::{
27 BloomFilterReadMetrics, BloomFilterReader, BloomFilterReaderImpl,
28};
29use index::target::IndexTarget;
30use object_store::ObjectStore;
31use puffin::puffin_manager::cache::PuffinMetadataCacheRef;
32use puffin::puffin_manager::file_key::PuffinFileKey;
33use puffin::puffin_manager::{PuffinManager, PuffinReader};
34use snafu::ResultExt;
35use store_api::metadata::RegionMetadataRef;
36use store_api::region_request::PathType;
37
38use crate::access_layer::{RegionFilePathFactory, WriteCachePathProvider};
39use crate::cache::file_cache::{FileCacheRef, FileType, IndexKey};
40use crate::cache::index::bloom_filter_index::{
41 BloomFilterIndexCacheRef, CachedBloomFilterIndexBlobReader, Tag,
42};
43use crate::error::{
44 ApplyBloomFilterIndexSnafu, Error, MetadataSnafu, PuffinBuildReaderSnafu, PuffinReadBlobSnafu,
45 Result,
46};
47use crate::metrics::INDEX_APPLY_ELAPSED;
48use crate::sst::file::RegionIndexId;
49use crate::sst::index::bloom_filter::INDEX_BLOB_TYPE;
50pub use crate::sst::index::bloom_filter::applier::builder::BloomFilterIndexApplierBuilder;
51use crate::sst::index::column::skipping_target_data_type;
52use crate::sst::index::puffin_manager::{BlobReader, PuffinManagerFactory, SstPuffinManager};
53use crate::sst::index::{TYPE_BLOOM_FILTER_INDEX, trigger_index_background_download};
54
55#[derive(Default, Clone)]
57pub struct BloomFilterIndexApplyMetrics {
58 pub apply_elapsed: std::time::Duration,
60 pub blob_cache_miss: usize,
62 pub blob_read_bytes: u64,
64 pub read_metrics: BloomFilterReadMetrics,
66}
67
68impl std::fmt::Debug for BloomFilterIndexApplyMetrics {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 let Self {
71 apply_elapsed,
72 blob_cache_miss,
73 blob_read_bytes,
74 read_metrics,
75 } = self;
76
77 if self.is_empty() {
78 return write!(f, "{{}}");
79 }
80 write!(f, "{{")?;
81
82 write!(f, "\"apply_elapsed\":\"{:?}\"", apply_elapsed)?;
83
84 if *blob_cache_miss > 0 {
85 write!(f, ", \"blob_cache_miss\":{}", blob_cache_miss)?;
86 }
87 if *blob_read_bytes > 0 {
88 write!(f, ", \"blob_read_bytes\":{}", blob_read_bytes)?;
89 }
90 write!(f, ", \"read_metrics\":{:?}", read_metrics)?;
91
92 write!(f, "}}")
93 }
94}
95
96impl BloomFilterIndexApplyMetrics {
97 pub fn is_empty(&self) -> bool {
99 self.apply_elapsed.is_zero()
100 }
101
102 pub fn merge_from(&mut self, other: &Self) {
104 self.apply_elapsed += other.apply_elapsed;
105 self.blob_cache_miss += other.blob_cache_miss;
106 self.blob_read_bytes += other.blob_read_bytes;
107 self.read_metrics.merge_from(&other.read_metrics);
108 }
109}
110
111pub(crate) type BloomFilterIndexApplierRef = Arc<BloomFilterIndexApplier>;
112
113pub struct BloomFilterIndexApplier {
115 table_dir: String,
117
118 path_type: PathType,
120
121 object_store: ObjectStore,
123
124 file_cache: Option<FileCacheRef>,
126
127 puffin_manager_factory: PuffinManagerFactory,
129
130 remote_puffin_manager: SstPuffinManager,
132 cached_puffin_manager: Option<SstPuffinManager>,
133
134 puffin_metadata_cache: Option<PuffinMetadataCacheRef>,
136
137 bloom_filter_index_cache: Option<BloomFilterIndexCacheRef>,
139
140 default_predicates: Arc<BTreeMap<IndexTarget, Vec<InListPredicate>>>,
143
144 expected_predicate_target_types: BTreeMap<IndexTarget, ConcreteDataType>,
146}
147
148impl BloomFilterIndexApplier {
149 pub fn new(
153 table_dir: String,
154 path_type: PathType,
155 object_store: ObjectStore,
156 puffin_manager_factory: PuffinManagerFactory,
157 predicates: BTreeMap<IndexTarget, Vec<InListPredicate>>,
158 expected_predicate_target_types: BTreeMap<IndexTarget, ConcreteDataType>,
159 ) -> Self {
160 let remote_puffin_manager = puffin_manager_factory.build(
161 object_store.clone(),
162 RegionFilePathFactory::new(table_dir.clone(), path_type),
163 );
164 let default_predicates = Arc::new(predicates);
165 Self {
166 table_dir,
167 path_type,
168 object_store,
169 file_cache: None,
170 puffin_manager_factory,
171 remote_puffin_manager,
172 cached_puffin_manager: None,
173 puffin_metadata_cache: None,
174 bloom_filter_index_cache: None,
175 default_predicates,
176 expected_predicate_target_types,
177 }
178 }
179
180 pub fn with_file_cache(mut self, file_cache: Option<FileCacheRef>) -> Self {
181 self.cached_puffin_manager = file_cache.as_ref().map(|cache| {
182 self.puffin_manager_factory.build_cached(
183 &self.remote_puffin_manager,
184 cache.local_store(),
185 WriteCachePathProvider::new(cache.clone()),
186 )
187 });
188 self.file_cache = file_cache;
189 self
190 }
191
192 pub fn with_puffin_metadata_cache(
193 mut self,
194 puffin_metadata_cache: Option<PuffinMetadataCacheRef>,
195 ) -> Self {
196 self.puffin_metadata_cache = puffin_metadata_cache;
197 self
198 }
199
200 pub fn with_bloom_filter_cache(
201 mut self,
202 bloom_filter_index_cache: Option<BloomFilterIndexCacheRef>,
203 ) -> Self {
204 self.bloom_filter_index_cache = bloom_filter_index_cache;
205 self
206 }
207
208 #[tracing::instrument(
223 skip_all,
224 fields(file_id = %file_id)
225 )]
226 pub async fn apply(
227 &self,
228 file_id: RegionIndexId,
229 file_size_hint: Option<u64>,
230 predicates: &BTreeMap<IndexTarget, Vec<InListPredicate>>,
231 row_groups: impl Iterator<Item = (usize, bool)>,
232 mut metrics: Option<&mut BloomFilterIndexApplyMetrics>,
233 ) -> Result<Vec<(usize, Vec<Range<usize>>)>> {
234 let apply_start = Instant::now();
235
236 let mut input = Vec::with_capacity(row_groups.size_hint().0);
238 let mut start = 0;
239 for (i, (len, to_search)) in row_groups.enumerate() {
240 let end = start + len;
241 if to_search {
242 input.push((i, start..end));
243 }
244 start = end;
245 }
246
247 let mut output = input
250 .iter()
251 .map(|(i, range)| (*i, vec![range.clone()]))
252 .collect::<Vec<_>>();
253
254 for (target, predicates) in predicates {
255 let (file_key, blob) = match self
256 .blob_reader(file_id, target, file_size_hint, metrics.as_deref_mut())
257 .await?
258 {
259 Some(blob) => blob,
260 None => continue,
261 };
262
263 if let Some(bloom_filter_cache) = &self.bloom_filter_index_cache {
265 let blob_size = blob.metadata().await.context(MetadataSnafu)?.content_length;
266 if let Some(m) = &mut metrics {
267 m.blob_read_bytes += blob_size;
268 }
269 let reader = CachedBloomFilterIndexBlobReader::new(
270 file_key,
271 target.clone(),
272 Tag::Skipping,
273 blob_size,
274 BloomFilterReaderImpl::new(blob),
275 bloom_filter_cache.clone(),
276 );
277 self.apply_predicates(reader, predicates, &mut output, metrics.as_deref_mut())
278 .await
279 .context(ApplyBloomFilterIndexSnafu)?;
280 } else {
281 let reader = BloomFilterReaderImpl::new(blob);
282 self.apply_predicates(reader, predicates, &mut output, metrics.as_deref_mut())
283 .await
284 .context(ApplyBloomFilterIndexSnafu)?;
285 }
286 }
287
288 for ((_, output), (_, input)) in output.iter_mut().zip(input) {
290 let start = input.start;
291 for range in output.iter_mut() {
292 range.start -= start;
293 range.end -= start;
294 }
295 }
296
297 let elapsed = apply_start.elapsed();
299 INDEX_APPLY_ELAPSED
300 .with_label_values(&[TYPE_BLOOM_FILTER_INDEX])
301 .observe(elapsed.as_secs_f64());
302
303 if let Some(m) = metrics {
304 m.apply_elapsed += elapsed;
305 }
306
307 Ok(output)
308 }
309
310 async fn blob_reader(
314 &self,
315 file_id: RegionIndexId,
316 target: &IndexTarget,
317 file_size_hint: Option<u64>,
318 metrics: Option<&mut BloomFilterIndexApplyMetrics>,
319 ) -> Result<Option<(PuffinFileKey<RegionIndexId>, BlobReader)>> {
320 let reader = match self
321 .cached_blob_reader(file_id, target, file_size_hint)
322 .await
323 {
324 Ok(Some(puffin_reader)) => puffin_reader,
325 other => {
326 if let Some(m) = metrics {
327 m.blob_cache_miss += 1;
328 }
329 if let Err(err) = other {
330 if is_blob_not_found(&err) {
332 return Ok(None);
333 }
334 warn!(err; "An unexpected error occurred while reading the cached index file. Fallback to remote index file.")
335 }
336 let res = self
337 .remote_blob_reader(file_id, target, file_size_hint)
338 .await;
339 if let Err(err) = res {
340 if is_blob_not_found(&err) {
342 return Ok(None);
343 }
344 return Err(err);
345 }
346
347 res?
348 }
349 };
350
351 Ok(Some(reader))
352 }
353
354 async fn cached_blob_reader(
356 &self,
357 file_id: RegionIndexId,
358 target: &IndexTarget,
359 file_size_hint: Option<u64>,
360 ) -> Result<Option<(PuffinFileKey<RegionIndexId>, BlobReader)>> {
361 let (Some(file_cache), Some(puffin_manager)) =
362 (&self.file_cache, &self.cached_puffin_manager)
363 else {
364 return Ok(None);
365 };
366
367 let index_key = IndexKey::new(
368 file_id.region_id(),
369 file_id.file_id(),
370 FileType::Puffin(file_id.version),
371 );
372 if file_cache.get(index_key).await.is_none() {
373 return Ok(None);
374 };
375
376 let blob_name = Self::target_blob_name(target);
377
378 let reader = puffin_manager
379 .reader(&file_id)
380 .await
381 .context(PuffinBuildReaderSnafu)?
382 .with_file_size_hint(file_size_hint)
383 .blob(&blob_name)
384 .await
385 .context(PuffinReadBlobSnafu)?
386 .reader()
387 .await
388 .context(PuffinBuildReaderSnafu)?;
389 Ok(Some((puffin_manager.file_key(&file_id), reader)))
390 }
391
392 fn target_blob_name(target: &IndexTarget) -> String {
394 let target_key = target.encode();
395 format!("{INDEX_BLOB_TYPE}-{target_key}")
396 }
397
398 async fn remote_blob_reader(
400 &self,
401 file_id: RegionIndexId,
402 target: &IndexTarget,
403 file_size_hint: Option<u64>,
404 ) -> Result<(PuffinFileKey<RegionIndexId>, BlobReader)> {
405 let path_factory = RegionFilePathFactory::new(self.table_dir.clone(), self.path_type);
406
407 trigger_index_background_download(
409 self.file_cache.as_ref(),
410 &file_id,
411 file_size_hint,
412 &path_factory,
413 &self.object_store,
414 );
415
416 let puffin_manager = self
417 .remote_puffin_manager
418 .clone()
419 .with_puffin_metadata_cache(self.puffin_metadata_cache.clone());
420
421 let blob_name = Self::target_blob_name(target);
422
423 let reader = puffin_manager
424 .reader(&file_id)
425 .await
426 .context(PuffinBuildReaderSnafu)?
427 .with_file_size_hint(file_size_hint)
428 .blob(&blob_name)
429 .await
430 .context(PuffinReadBlobSnafu)?
431 .reader()
432 .await
433 .context(PuffinBuildReaderSnafu)?;
434 Ok((puffin_manager.file_key(&file_id), reader))
435 }
436
437 async fn apply_predicates<R: BloomFilterReader + Send + 'static>(
438 &self,
439 reader: R,
440 predicates: &[InListPredicate],
441 output: &mut [(usize, Vec<Range<usize>>)],
442 metrics: Option<&mut BloomFilterIndexApplyMetrics>,
443 ) -> std::result::Result<(), index::bloom_filter::error::Error> {
444 let mut applier = BloomFilterApplier::new(Box::new(reader)).await?;
445 let mut row_groups = output.iter_mut().map(|(_, r)| r).collect::<Vec<_>>();
446 let read_metrics = metrics.map(|m| &mut m.read_metrics);
447 applier
448 .search_groups(predicates, &mut row_groups, read_metrics)
449 .await
450 }
451
452 pub fn compatible_predicate_for_sst(
456 &self,
457 sst_metadata: &RegionMetadataRef,
458 ) -> Option<Arc<BTreeMap<IndexTarget, Vec<InListPredicate>>>> {
459 let mut has_type_mismatch = false;
460 let mut compatible_targets = Vec::new();
461
462 for (target, expected) in &self.expected_predicate_target_types {
463 if skipping_target_data_type(sst_metadata, target).as_ref() != Some(expected) {
464 has_type_mismatch = true;
465 continue;
466 }
467 compatible_targets.push(target.clone());
468 }
469
470 if compatible_targets.is_empty() {
471 return None;
472 }
473
474 if !has_type_mismatch {
475 return Some(self.default_predicates.clone());
476 }
477
478 let mut compatible_predicates = BTreeMap::new();
479 for target in compatible_targets {
480 if let Some(predicates) = self.default_predicates.get(&target) {
481 compatible_predicates.insert(target, predicates.clone());
482 }
483 }
484
485 Some(Arc::new(compatible_predicates))
486 }
487}
488
489fn is_blob_not_found(err: &Error) -> bool {
490 matches!(
491 err,
492 Error::PuffinReadBlob {
493 source: puffin::error::Error::BlobNotFound { .. },
494 ..
495 }
496 )
497}
498
499#[cfg(test)]
500mod tests {
501 use std::collections::BTreeSet;
502
503 use datafusion_expr::{Expr, col, lit};
504 use futures::future::BoxFuture;
505 use index::Bytes;
506 use object_store::services::Memory;
507 use puffin::puffin_manager::PuffinWriter;
508 use store_api::metadata::RegionMetadata;
509 use store_api::storage::FileId;
510
511 use super::*;
512 use crate::sst::file::RegionFileId;
513 use crate::sst::index::IndexBuildType;
514 use crate::sst::index::bloom_filter::creator::BloomFilterIndexer;
515 use crate::sst::index::bloom_filter::creator::tests::{
516 mock_object_store, mock_region_metadata, new_batch, new_intm_mgr,
517 };
518
519 #[tokio::test]
520 async fn test_json_skipping_build_and_prune_both_formats() {
521 use crate::read::{Batch, BatchColumn};
522 use common_function::scalars::json::json_get::JsonGetWithType;
523 use common_function::scalars::udf::create_udf;
524 use datafusion_common::ScalarValue;
525 use datafusion_expr::expr::ScalarFunction;
526 use datatypes::arrow::array::{ArrayRef, Int64Array, StructArray};
527 use datatypes::arrow::buffer::NullBuffer;
528 use datatypes::arrow::datatypes::{Field, Schema};
529 use datatypes::arrow::record_batch::RecordBatch;
530 use datatypes::schema::SkippingIndexOptions;
531 use datatypes::vectors::{Helper, UInt8Vector, UInt64Vector};
532
533 for flat in [false, true] {
534 let (dir, factory) =
535 PuffinManagerFactory::new_for_test_async("json_skipping_index").await;
536 let mut metadata = (*mock_region_metadata()).clone();
537 let column = &mut metadata.column_metadatas[2].column_schema;
538 column.data_type =
539 ConcreteDataType::json2(datatypes::types::json_type::JsonNativeType::Object(
540 datatypes::types::json_type::JsonObjectType::new(),
541 ));
542 column.with_extension_type(&datatypes::extension::json::Json2ExtensionType::new(
543 Arc::new(datatypes::extension::json::JsonMetadata::new(
544 datatypes::json::JsonSettings::try_new(
545 vec![datatypes::json::JsonTypeHint {
546 path: vec!["value".into()],
547 data_type: ConcreteDataType::int64_datatype(),
548 inverted_index: false,
549 fulltext_index: None,
550 skipping_index: Some(
551 SkippingIndexOptions::new(2, 0.01, Default::default()).unwrap(),
552 ),
553 }],
554 Some(0),
555 )
556 .unwrap(),
557 )),
558 ));
559 for col in &mut metadata.column_metadatas {
561 col.column_schema.unset_skipping_options().unwrap();
562 }
563 let metadata = Arc::new(metadata);
564 let file_id = FileId::random();
565 let target = IndexTarget::new_json_path(3, vec!["value".into()]).unwrap();
566 let mut creator = BloomFilterIndexer::new(
567 file_id,
568 &metadata,
569 new_intm_mgr(dir.path().to_string_lossy()).await,
570 None,
571 IndexBuildType::Flush,
572 )
573 .unwrap()
574 .unwrap();
575 let values: ArrayRef = Arc::new(Int64Array::from(vec![Some(1), None, Some(1), None]));
576 let root: ArrayRef = Arc::new(StructArray::new(
577 vec![Field::new("value", values.data_type().clone(), true)].into(),
578 vec![values],
579 Some(NullBuffer::from(vec![true, false, true, true])),
580 ));
581 for start in [0, 2] {
582 let array = root.slice(start, 2);
583 if flat {
584 let batch = RecordBatch::try_new(
585 Arc::new(Schema::new(vec![Field::new(
586 "field_u64",
587 array.data_type().clone(),
588 true,
589 )])),
590 vec![array],
591 )
592 .unwrap();
593 creator.update_flat(&batch).await.unwrap();
594 } else {
595 let mut batch = Batch::new(
596 vec![],
597 Arc::new(UInt64Vector::from_vec(vec![0, 1])),
598 Arc::new(UInt64Vector::from_vec(vec![0, 0])),
599 Arc::new(UInt8Vector::from_vec(vec![1, 1])),
600 vec![BatchColumn {
601 column_id: 3,
602 data: Helper::try_into_vector(array).unwrap(),
603 }],
604 )
605 .unwrap();
606 creator.update(&mut batch).await.unwrap();
607 }
608 }
609
610 assert_eq!(creator.indexed_targets().collect::<Vec<_>>(), vec![&target]);
611 let object_store = mock_object_store();
612 let manager = factory.build(
613 object_store.clone(),
614 RegionFilePathFactory::new("json_test".into(), PathType::Bare),
615 );
616 let index_id = RegionIndexId::new(RegionFileId::new(metadata.region_id, file_id), 0);
617 let mut writer = manager.writer(&index_id).await.unwrap();
618 let (rows, bytes) = creator.finish(&mut writer).await.unwrap();
619 assert_eq!(rows, 4);
620 assert!(bytes > 0);
621 writer.finish().await.unwrap();
622 let json_expr = |path: &str, ty| {
623 Expr::ScalarFunction(ScalarFunction::new_udf(
624 Arc::new(create_udf(Arc::new(JsonGetWithType::default()))),
625 vec![col("field_u64"), lit(path), Expr::Literal(ty, None)],
626 ))
627 };
628 let cache = Arc::new(
629 crate::cache::index::bloom_filter_index::BloomFilterIndexCache::new(
630 1024 * 1024,
631 1024 * 1024,
632 1024,
633 ),
634 );
635 for expr in [
636 json_expr("$.value", ScalarValue::Int64(None)).eq(lit(9_i64)),
637 json_expr("$.value", ScalarValue::Int64(None))
638 .in_list(vec![lit(8_i64), lit(9_i64)], false),
639 ] {
640 let applier = BloomFilterIndexApplierBuilder::new(
641 "json_test".into(),
642 PathType::Bare,
643 object_store.clone(),
644 &metadata,
645 factory.clone(),
646 )
647 .with_bloom_filter_index_cache(Some(cache.clone()))
648 .build(&[expr])
649 .unwrap()
650 .unwrap();
651 let predicates = applier.compatible_predicate_for_sst(&metadata).unwrap();
652 assert!(predicates.contains_key(&target));
653 for _ in 0..2 {
655 let result = applier
656 .apply(index_id, None, &predicates, [(4, true)].into_iter(), None)
657 .await
658 .unwrap();
659 assert!(result[0].1.is_empty());
660 }
661 assert!(
662 applier
663 .compatible_predicate_for_sst(&mock_region_metadata())
664 .is_none()
665 );
666 }
667 let applier = BloomFilterIndexApplierBuilder::new(
668 "json_test".into(),
669 PathType::Bare,
670 object_store.clone(),
671 &metadata,
672 factory.clone(),
673 )
674 .build(&[json_expr("$.value", ScalarValue::Int64(None)).eq(lit(1_i64))])
675 .unwrap()
676 .unwrap();
677 let predicates = applier.compatible_predicate_for_sst(&metadata).unwrap();
678 let result = applier
679 .apply(index_id, None, &predicates, [(4, true)].into_iter(), None)
680 .await
681 .unwrap();
682 assert_eq!(result[0].1.len(), 1);
683 assert_eq!(result[0].1[0], 0..4);
684 for expr in [
685 json_expr("$.value", ScalarValue::Float64(None)),
686 json_expr("$.missing", ScalarValue::Int64(None)),
687 ] {
688 assert!(
689 BloomFilterIndexApplierBuilder::new(
690 "json_test".into(),
691 PathType::Bare,
692 object_store.clone(),
693 &metadata,
694 factory.clone()
695 )
696 .build(&[expr.eq(lit(1_i64))])
697 .unwrap()
698 .is_none()
699 );
700 }
701 }
702 }
703
704 #[tokio::test]
705 async fn test_compatible_predicate_for_sst() {
706 let (_d, puffin_manager_factory) =
707 PuffinManagerFactory::new_for_test_async("test_plan_for_sst_basic_").await;
708 let object_store = ObjectStore::new(Memory::default()).unwrap();
709 let table_dir = "table_dir".to_string();
710
711 let predicates = BTreeMap::from_iter([(
712 IndexTarget::ColumnId(1),
713 vec![InListPredicate {
714 list: BTreeSet::from_iter([Bytes::from("foo")]),
715 }],
716 )]);
717 let expected_predicate_target_types = BTreeMap::from_iter([(
718 IndexTarget::ColumnId(1),
719 ConcreteDataType::string_datatype(),
720 )]);
721
722 let applier = BloomFilterIndexApplier::new(
723 table_dir,
724 PathType::Bare,
725 object_store,
726 puffin_manager_factory,
727 predicates,
728 expected_predicate_target_types,
729 );
730 let predicates = applier.compatible_predicate_for_sst(&mock_region_metadata());
731 assert!(predicates.is_some());
732 }
733
734 #[tokio::test]
735 async fn test_compatible_predicate_for_sst_type_mismatch() {
736 let (_d, puffin_manager_factory) =
737 PuffinManagerFactory::new_for_test_async("test_plan_for_sst_type_mismatch_").await;
738 let object_store = ObjectStore::new(Memory::default()).unwrap();
739 let table_dir = "table_dir".to_string();
740
741 let predicates = BTreeMap::from_iter([(
742 IndexTarget::ColumnId(1),
743 vec![InListPredicate {
744 list: BTreeSet::from_iter([Bytes::from("foo")]),
745 }],
746 )]);
747 let expected_predicate_target_types =
748 BTreeMap::from_iter([(IndexTarget::ColumnId(1), ConcreteDataType::int64_datatype())]);
749
750 let applier = BloomFilterIndexApplier::new(
751 table_dir,
752 PathType::Bare,
753 object_store,
754 puffin_manager_factory,
755 predicates,
756 expected_predicate_target_types,
757 );
758 let predicates = applier.compatible_predicate_for_sst(&mock_region_metadata());
759 assert!(predicates.is_none());
760 }
761
762 #[tokio::test]
763 async fn test_compatible_predicate_for_sst_partial_type_mismatch() {
764 let (_d, puffin_manager_factory) =
765 PuffinManagerFactory::new_for_test_async("test_plan_for_sst_partial_mismatch_").await;
766 let object_store = ObjectStore::new(Memory::default()).unwrap();
767 let table_dir = "table_dir".to_string();
768
769 let predicates = BTreeMap::from_iter([
772 (
773 IndexTarget::ColumnId(1),
774 vec![InListPredicate {
775 list: BTreeSet::from_iter([Bytes::from("foo")]),
776 }],
777 ),
778 (
779 IndexTarget::ColumnId(3),
780 vec![InListPredicate {
781 list: BTreeSet::from_iter([Bytes::from("bar")]),
782 }],
783 ),
784 ]);
785 let expected_predicate_target_types = BTreeMap::from_iter([
786 (
787 IndexTarget::ColumnId(1),
788 ConcreteDataType::string_datatype(),
789 ),
790 (IndexTarget::ColumnId(3), ConcreteDataType::int64_datatype()), ]);
792
793 let applier = BloomFilterIndexApplier::new(
794 table_dir,
795 PathType::Bare,
796 object_store,
797 puffin_manager_factory,
798 predicates,
799 expected_predicate_target_types,
800 );
801 let result = applier.compatible_predicate_for_sst(&mock_region_metadata());
802
803 let result = result.expect("expected Some with compatible subset");
805 assert!(
806 result.contains_key(&IndexTarget::ColumnId(1)),
807 "compatible column 1 must be present"
808 );
809 assert!(
810 !result.contains_key(&IndexTarget::ColumnId(3)),
811 "mismatched column 3 must be absent"
812 );
813 assert_eq!(result.len(), 1, "only the compatible predicate must remain");
814 }
815
816 #[allow(clippy::type_complexity)]
817 fn tester(
818 table_dir: String,
819 object_store: ObjectStore,
820 metadata: &RegionMetadata,
821 puffin_manager_factory: PuffinManagerFactory,
822 file_id: RegionIndexId,
823 ) -> impl Fn(&[Expr], Vec<(usize, bool)>) -> BoxFuture<'static, Vec<(usize, Vec<Range<usize>>)>>
824 + use<'_> {
825 move |exprs, row_groups| {
826 let table_dir = table_dir.clone();
827 let object_store: ObjectStore = object_store.clone();
828 let metadata = metadata.clone();
829 let puffin_manager_factory = puffin_manager_factory.clone();
830 let exprs = exprs.to_vec();
831
832 Box::pin(async move {
833 let builder = BloomFilterIndexApplierBuilder::new(
834 table_dir,
835 PathType::Bare,
836 object_store,
837 &metadata,
838 puffin_manager_factory,
839 );
840
841 let applier = builder.build(&exprs).unwrap().unwrap();
842 let predicates = applier
843 .compatible_predicate_for_sst(&Arc::new(metadata.clone()))
844 .unwrap();
845 applier
846 .apply(file_id, None, &predicates, row_groups.into_iter(), None)
847 .await
848 .unwrap()
849 .into_iter()
850 .filter(|(_, ranges)| !ranges.is_empty())
851 .collect()
852 })
853 }
854 }
855
856 #[tokio::test]
857 #[allow(clippy::single_range_in_vec_init)]
858 async fn test_bloom_filter_applier() {
859 let region_metadata = mock_region_metadata();
876 let prefix = "test_bloom_filter_applier_";
877 let (d, factory) = PuffinManagerFactory::new_for_test_async(prefix).await;
878 let object_store = mock_object_store();
879 let intm_mgr = new_intm_mgr(d.path().to_string_lossy()).await;
880 let memory_usage_threshold = Some(1024);
881 let file_id = RegionFileId::new(region_metadata.region_id, FileId::random());
882 let file_id = RegionIndexId::new(file_id, 0);
883 let table_dir = "table_dir".to_string();
884
885 let mut indexer = BloomFilterIndexer::new(
886 file_id.file_id(),
887 ®ion_metadata,
888 intm_mgr,
889 memory_usage_threshold,
890 IndexBuildType::Flush,
891 )
892 .unwrap()
893 .unwrap();
894
895 let mut batch = new_batch("tag1", 0..10);
897 indexer.update(&mut batch).await.unwrap();
898 let mut batch = new_batch("tag2", 10..20);
899 indexer.update(&mut batch).await.unwrap();
900
901 let puffin_manager = factory.build(
902 object_store.clone(),
903 RegionFilePathFactory::new(table_dir.clone(), PathType::Bare),
904 );
905
906 let mut puffin_writer = puffin_manager.writer(&file_id).await.unwrap();
907 indexer.finish(&mut puffin_writer).await.unwrap();
908 puffin_writer.finish().await.unwrap();
909
910 let tester = tester(
911 table_dir.clone(),
912 object_store.clone(),
913 ®ion_metadata,
914 factory.clone(),
915 file_id,
916 );
917
918 let res = tester(
922 &[col("tag_str").eq(lit("tag1"))],
923 vec![(5, true), (5, true), (5, true), (5, true)],
924 )
925 .await;
926 assert_eq!(res, vec![(0, vec![0..5]), (1, vec![0..5])]);
927
928 let res = tester(
932 &[col("tag_str").eq(lit("tag1"))],
933 vec![(5, true), (5, false), (5, true), (5, true)],
934 )
935 .await;
936 assert_eq!(res, vec![(0, vec![0..5])]);
937
938 let res = tester(
943 &[
944 col("tag_str").eq(lit("tag1")),
945 col("field_u64").eq(lit(1u64)),
946 ],
947 vec![(5, true), (5, true), (5, true), (5, true)],
948 )
949 .await;
950 assert_eq!(res, vec![(0, vec![0..4])]);
951
952 let res = tester(
956 &[col("field_u64").in_list(vec![lit(1u64), lit(11u64)], false)],
957 vec![(5, true), (5, true), (5, false), (5, true)],
958 )
959 .await;
960 assert_eq!(res, vec![(0, vec![0..4]), (1, vec![3..5])]);
961 }
962}