1use crate::scalar::bloomfilter::sbbf::{Sbbf, SbbfBuilder};
11use crate::scalar::expression::{BloomFilterQueryParser, ScalarQueryParser};
12use crate::scalar::registry::{
13 ScalarIndexPlugin, TrainingCriteria, TrainingOrdering, TrainingRequest,
14};
15use crate::scalar::{
16 BloomFilterQuery, BuiltinIndexType, CreatedIndex, ScalarIndexParams, UpdateCriteria,
17};
18use crate::{Any, pb};
19use arrow_array::{Array, UInt64Array};
20mod as_bytes;
21pub mod sbbf;
22use arrow_schema::{DataType, Field};
23use serde::{Deserialize, Serialize};
24
25use std::sync::LazyLock;
26
27use datafusion::execution::SendableRecordBatchStream;
28use std::{collections::HashMap, sync::Arc};
29
30use crate::scalar::FragReuseIndex;
31use crate::scalar::{AnyQuery, IndexStore, MetricsCollector, ScalarIndex, SearchResult};
32use crate::vector::VectorIndex;
33use crate::{Index, IndexType};
34use arrow_array::{ArrayRef, RecordBatch};
35use async_trait::async_trait;
36use deepsize::DeepSizeOf;
37use lance_core::Error;
38use lance_core::Result;
39use lance_core::cache::LanceCache;
40use roaring::RoaringBitmap;
41
42use super::zoned::{ZoneBound, ZoneProcessor, ZoneTrainer, rebuild_zones, search_zones};
43
44const BLOOMFILTER_FILENAME: &str = "bloomfilter.lance";
45const BLOOMFILTER_ITEM_META_KEY: &str = "bloomfilter_item";
46const BLOOMFILTER_PROBABILITY_META_KEY: &str = "bloomfilter_probability";
47const BLOOMFILTER_INDEX_VERSION: u32 = 0;
48
49#[derive(Debug, Clone)]
50struct BloomFilterStatistics {
51 bound: ZoneBound,
54 has_null: bool,
56 bloom_filter: Sbbf,
58}
59
60impl DeepSizeOf for BloomFilterStatistics {
61 fn deep_size_of_children(&self, _context: &mut deepsize::Context) -> usize {
62 self.bloom_filter.to_bytes().len()
66 }
67}
68
69impl AsRef<ZoneBound> for BloomFilterStatistics {
70 fn as_ref(&self) -> &ZoneBound {
71 &self.bound
72 }
73}
74
75#[derive(Debug, Clone)]
76pub struct BloomFilterIndex {
77 zones: Vec<BloomFilterStatistics>,
78 number_of_items: u64,
80 probability: f64,
82}
83
84impl DeepSizeOf for BloomFilterIndex {
85 fn deep_size_of_children(&self, context: &mut deepsize::Context) -> usize {
86 self.zones.deep_size_of_children(context)
87 }
88}
89
90impl BloomFilterIndex {
91 async fn load(
92 store: Arc<dyn IndexStore>,
93 _fri: Option<Arc<FragReuseIndex>>,
94 _index_cache: &LanceCache,
95 ) -> Result<Arc<Self>> {
96 let index_file = store.open_index_file(BLOOMFILTER_FILENAME).await?;
97 let bloom_data = index_file
98 .read_range(0..index_file.num_rows(), None)
99 .await?;
100 let file_schema = index_file.schema();
101
102 let number_of_items: u64 = file_schema
103 .metadata
104 .get(BLOOMFILTER_ITEM_META_KEY)
105 .and_then(|bs| bs.parse().ok())
106 .unwrap_or(*DEFAULT_NUMBER_OF_ITEMS);
107
108 let probability: f64 = file_schema
109 .metadata
110 .get(BLOOMFILTER_PROBABILITY_META_KEY)
111 .and_then(|bs| bs.parse().ok())
112 .unwrap_or(*DEFAULT_PROBABILITY);
113
114 Ok(Arc::new(Self::try_from_serialized(
115 bloom_data,
116 number_of_items,
117 probability,
118 )?))
119 }
120
121 fn try_from_serialized(
122 data: RecordBatch,
123 number_of_items: u64,
124 probability: f64,
125 ) -> Result<Self> {
126 if data.num_rows() == 0 {
127 return Ok(Self {
129 zones: Vec::new(),
130 number_of_items,
131 probability,
132 });
133 }
134
135 let fragment_id_col = data
136 .column_by_name("fragment_id")
137 .ok_or_else(|| Error::invalid_input("BloomFilterIndex: missing 'fragment_id' column"))?
138 .as_any()
139 .downcast_ref::<arrow_array::UInt64Array>()
140 .ok_or_else(|| {
141 Error::invalid_input("BloomFilterIndex: 'fragment_id' column is not UInt64")
142 })?;
143
144 let zone_start_col = data
145 .column_by_name("zone_start")
146 .ok_or_else(|| Error::invalid_input("BloomFilterIndex: missing 'zone_start' column"))?
147 .as_any()
148 .downcast_ref::<arrow_array::UInt64Array>()
149 .ok_or_else(|| {
150 Error::invalid_input("BloomFilterIndex: 'zone_start' column is not UInt64")
151 })?;
152
153 let zone_length_col = data
154 .column_by_name("zone_length")
155 .ok_or_else(|| Error::invalid_input("BloomFilterIndex: missing 'zone_length' column"))?
156 .as_any()
157 .downcast_ref::<arrow_array::UInt64Array>()
158 .ok_or_else(|| {
159 Error::invalid_input("BloomFilterIndex: 'zone_length' column is not UInt64")
160 })?;
161
162 let bloom_filter_data_col = data
163 .column_by_name("bloom_filter_data")
164 .ok_or_else(|| {
165 Error::invalid_input("BloomFilterIndex: missing 'bloom_filter_data' column")
166 })?
167 .as_any()
168 .downcast_ref::<arrow_array::BinaryArray>()
169 .ok_or_else(|| {
170 Error::invalid_input("BloomFilterIndex: 'bloom_filter_data' column is not Binary")
171 })?;
172
173 let has_null_col = data
174 .column_by_name("has_null")
175 .ok_or_else(|| Error::invalid_input("BloomFilterIndex: missing 'has_null' column"))?
176 .as_any()
177 .downcast_ref::<arrow_array::BooleanArray>()
178 .ok_or_else(|| {
179 Error::invalid_input("BloomFilterIndex: 'has_null' column is not Boolean")
180 })?;
181
182 let num_blocks = data.num_rows();
183 let mut blocks = Vec::with_capacity(num_blocks);
184
185 for i in 0..num_blocks {
186 let bloom_filter_bytes = if bloom_filter_data_col.is_valid(i) {
187 bloom_filter_data_col.value(i).to_vec()
188 } else {
189 Vec::new()
190 };
191
192 let bloom_filter = Sbbf::new(&bloom_filter_bytes).map_err(|e| {
194 Error::invalid_input(format!("Failed to deserialize bloom filter: {:?}", e))
195 })?;
196
197 blocks.push(BloomFilterStatistics {
198 bound: ZoneBound {
199 fragment_id: fragment_id_col.value(i),
200 start: zone_start_col.value(i),
201 length: zone_length_col.value(i) as usize,
202 },
203 has_null: has_null_col.value(i),
204 bloom_filter,
205 });
206 }
207
208 Ok(Self {
209 zones: blocks,
210 number_of_items,
211 probability,
212 })
213 }
214
215 fn evaluate_block_against_query(
216 &self,
217 block: &BloomFilterStatistics,
218 query: &BloomFilterQuery,
219 ) -> Result<bool> {
220 let sbbf = &block.bloom_filter;
221
222 match query {
223 BloomFilterQuery::IsNull() => {
224 Ok(block.has_null)
226 }
227 BloomFilterQuery::Equals(target) => {
228 if target.is_null() {
229 return Ok(block.has_null);
231 }
232
233 match target {
235 datafusion_common::ScalarValue::Int8(Some(val)) => Ok(sbbf.check(val)),
237 datafusion_common::ScalarValue::Int16(Some(val)) => Ok(sbbf.check(val)),
238 datafusion_common::ScalarValue::Int32(Some(val)) => Ok(sbbf.check(val)),
239 datafusion_common::ScalarValue::Int64(Some(val)) => Ok(sbbf.check(val)),
240 datafusion_common::ScalarValue::UInt8(Some(val)) => Ok(sbbf.check(val)),
242 datafusion_common::ScalarValue::UInt16(Some(val)) => Ok(sbbf.check(val)),
243 datafusion_common::ScalarValue::UInt32(Some(val)) => Ok(sbbf.check(val)),
244 datafusion_common::ScalarValue::UInt64(Some(val)) => Ok(sbbf.check(val)),
245 datafusion_common::ScalarValue::Float32(Some(val)) => Ok(sbbf.check(val)),
247 datafusion_common::ScalarValue::Float64(Some(val)) => Ok(sbbf.check(val)),
248 datafusion_common::ScalarValue::Utf8(Some(val)) => Ok(sbbf.check(val.as_str())),
250 datafusion_common::ScalarValue::LargeUtf8(Some(val)) => {
251 Ok(sbbf.check(val.as_str()))
252 }
253 datafusion_common::ScalarValue::Binary(Some(val)) => {
255 Ok(sbbf.check(val.as_slice()))
256 }
257 datafusion_common::ScalarValue::LargeBinary(Some(val)) => {
258 Ok(sbbf.check(val.as_slice()))
259 }
260 datafusion_common::ScalarValue::Date32(Some(val)) => Ok(sbbf.check(val)),
262 datafusion_common::ScalarValue::Date64(Some(val)) => Ok(sbbf.check(val)),
263 datafusion_common::ScalarValue::Time32Second(Some(val)) => Ok(sbbf.check(val)),
264 datafusion_common::ScalarValue::Time32Millisecond(Some(val)) => {
265 Ok(sbbf.check(val))
266 }
267 datafusion_common::ScalarValue::Time64Microsecond(Some(val)) => {
268 Ok(sbbf.check(val))
269 }
270 datafusion_common::ScalarValue::Time64Nanosecond(Some(val)) => {
271 Ok(sbbf.check(val))
272 }
273 datafusion_common::ScalarValue::TimestampSecond(Some(val), _) => {
274 Ok(sbbf.check(val))
275 }
276 datafusion_common::ScalarValue::TimestampMillisecond(Some(val), _) => {
277 Ok(sbbf.check(val))
278 }
279 datafusion_common::ScalarValue::TimestampMicrosecond(Some(val), _) => {
280 Ok(sbbf.check(val))
281 }
282 datafusion_common::ScalarValue::TimestampNanosecond(Some(val), _) => {
283 Ok(sbbf.check(val))
284 }
285 _ => Err(Error::invalid_input_source(
286 format!("Unsupported data type in bloom filter query: {:?}", target).into(),
287 )),
288 }
289 }
290 BloomFilterQuery::IsIn(values) => {
291 for value in values {
293 if value.is_null() {
294 if block.has_null {
296 return Ok(true);
297 }
298 continue;
299 }
300
301 let found = match value {
302 datafusion_common::ScalarValue::Int8(Some(val)) => sbbf.check(val),
304 datafusion_common::ScalarValue::Int16(Some(val)) => sbbf.check(val),
305 datafusion_common::ScalarValue::Int32(Some(val)) => sbbf.check(val),
306 datafusion_common::ScalarValue::Int64(Some(val)) => sbbf.check(val),
307 datafusion_common::ScalarValue::UInt8(Some(val)) => sbbf.check(val),
309 datafusion_common::ScalarValue::UInt16(Some(val)) => sbbf.check(val),
310 datafusion_common::ScalarValue::UInt32(Some(val)) => sbbf.check(val),
311 datafusion_common::ScalarValue::UInt64(Some(val)) => sbbf.check(val),
312 datafusion_common::ScalarValue::Float32(Some(val)) => sbbf.check(val),
314 datafusion_common::ScalarValue::Float64(Some(val)) => sbbf.check(val),
315 datafusion_common::ScalarValue::Utf8(Some(val)) => sbbf.check(val.as_str()),
317 datafusion_common::ScalarValue::LargeUtf8(Some(val)) => {
318 sbbf.check(val.as_str())
319 }
320 datafusion_common::ScalarValue::Binary(Some(val)) => {
322 sbbf.check(val.as_slice())
323 }
324 datafusion_common::ScalarValue::LargeBinary(Some(val)) => {
325 sbbf.check(val.as_slice())
326 }
327 datafusion_common::ScalarValue::Date32(Some(val)) => sbbf.check(val),
329 datafusion_common::ScalarValue::Date64(Some(val)) => sbbf.check(val),
330 datafusion_common::ScalarValue::Time32Second(Some(val)) => sbbf.check(val),
331 datafusion_common::ScalarValue::Time32Millisecond(Some(val)) => {
332 sbbf.check(val)
333 }
334 datafusion_common::ScalarValue::Time64Microsecond(Some(val)) => {
335 sbbf.check(val)
336 }
337 datafusion_common::ScalarValue::Time64Nanosecond(Some(val)) => {
338 sbbf.check(val)
339 }
340 datafusion_common::ScalarValue::TimestampSecond(Some(val), _) => {
341 sbbf.check(val)
342 }
343 datafusion_common::ScalarValue::TimestampMillisecond(Some(val), _) => {
344 sbbf.check(val)
345 }
346 datafusion_common::ScalarValue::TimestampMicrosecond(Some(val), _) => {
347 sbbf.check(val)
348 }
349 datafusion_common::ScalarValue::TimestampNanosecond(Some(val), _) => {
350 sbbf.check(val)
351 }
352 _ => {
353 return Err(Error::invalid_input_source(
354 format!("Unsupported data type in bloom filter query: {:?}", value)
355 .into(),
356 ));
357 }
358 };
359
360 if found {
361 return Ok(true);
362 }
363 }
364 Ok(false) }
366 }
367 }
368}
369
370#[async_trait]
371impl Index for BloomFilterIndex {
372 fn as_any(&self) -> &dyn Any {
373 self
374 }
375
376 fn as_index(self: Arc<Self>) -> Arc<dyn Index> {
377 self
378 }
379
380 fn as_vector_index(self: Arc<Self>) -> Result<Arc<dyn VectorIndex>> {
381 Err(Error::invalid_input_source(
382 "BloomFilter is not a vector index".into(),
383 ))
384 }
385
386 async fn prewarm(&self) -> Result<()> {
387 Ok(())
388 }
389
390 fn statistics(&self) -> Result<serde_json::Value> {
391 Ok(serde_json::json!({
392 "type": "BloomFilter",
393 "num_blocks": self.zones.len(),
394 "number_of_items": self.number_of_items,
395 "probability": self.probability,
396 }))
397 }
398
399 fn index_type(&self) -> IndexType {
400 IndexType::BloomFilter
401 }
402
403 async fn calculate_included_frags(&self) -> Result<RoaringBitmap> {
404 let mut frag_ids = RoaringBitmap::new();
405
406 for block in &self.zones {
408 frag_ids.insert(block.bound.fragment_id as u32);
409 }
410
411 Ok(frag_ids)
412 }
413}
414
415#[async_trait]
416impl ScalarIndex for BloomFilterIndex {
417 async fn search(
418 &self,
419 query: &dyn AnyQuery,
420 metrics: &dyn MetricsCollector,
421 ) -> Result<SearchResult> {
422 let query = query.as_any().downcast_ref::<BloomFilterQuery>().unwrap();
423 search_zones(&self.zones, metrics, |block| {
424 self.evaluate_block_against_query(block, query)
425 })
426 }
427
428 fn can_remap(&self) -> bool {
429 false
430 }
431
432 async fn remap(
433 &self,
434 _mapping: &HashMap<u64, Option<u64>>,
435 _dest_store: &dyn IndexStore,
436 ) -> Result<CreatedIndex> {
437 Err(Error::invalid_input_source(
438 "BloomFilter does not support remap".into(),
439 ))
440 }
441
442 async fn update(
443 &self,
444 new_data: SendableRecordBatchStream,
445 dest_store: &dyn IndexStore,
446 _valid_old_fragments: Option<&RoaringBitmap>,
447 ) -> Result<CreatedIndex> {
448 let params = BloomFilterIndexBuilderParams {
450 number_of_items: self.number_of_items,
451 probability: self.probability,
452 };
453
454 let processor = BloomFilterProcessor::new(params.clone())?;
455 let trainer = ZoneTrainer::new(processor, params.number_of_items)?;
456 let updated_blocks = rebuild_zones(&self.zones, trainer, new_data).await?;
457
458 let mut builder = BloomFilterIndexBuilder::try_new(params)?;
460 builder.blocks = updated_blocks;
461 builder.write_index(dest_store).await?;
462
463 Ok(CreatedIndex {
464 index_details: prost_types::Any::from_msg(&pb::BloomFilterIndexDetails::default())
465 .unwrap(),
466 index_version: BLOOMFILTER_INDEX_VERSION,
467 })
468 }
469
470 fn update_criteria(&self) -> UpdateCriteria {
471 UpdateCriteria::only_new_data(
472 TrainingCriteria::new(TrainingOrdering::Addresses).with_row_addr(),
473 )
474 }
475
476 fn derive_index_params(&self) -> Result<ScalarIndexParams> {
477 let params = serde_json::to_value(BloomFilterIndexBuilderParams {
478 number_of_items: self.number_of_items,
479 probability: self.probability,
480 })?;
481 Ok(ScalarIndexParams::for_builtin(BuiltinIndexType::BloomFilter).with_params(¶ms))
482 }
483}
484
485fn default_number_of_items() -> u64 {
486 *DEFAULT_NUMBER_OF_ITEMS
487}
488
489fn default_probability() -> f64 {
490 *DEFAULT_PROBABILITY
491}
492
493static DEFAULT_NUMBER_OF_ITEMS: LazyLock<u64> = LazyLock::new(|| {
496 std::env::var("LANCE_BLOOMFILTER_DEFAULT_NUMBER_OF_ITEMS")
497 .unwrap_or_else(|_| "8192".to_string())
498 .parse()
499 .expect("failed to parse Lance_BLOOMFILTER_DEFAULT_NUMBER_OF_ITEMS")
500});
501
502#[allow(clippy::manual_inspect)]
503static DEFAULT_PROBABILITY: LazyLock<f64> = LazyLock::new(|| {
504 std::env::var("LANCE_BLOOMFILTER_DEFAULT_PROBABILITY")
505 .unwrap_or_else(|_| "0.00057".to_string())
507 .parse()
508 .map(|prob: f64| {
509 assert!(
510 (0.0..=1.0).contains(&prob),
511 "LANCE_BLOOMFILTER_DEFAULT_PROBABILITY must be between 0 and 1, got {}",
512 prob
513 );
514 prob
515 })
516 .expect("failed to parse LANCE_BLOOMFILTER_DEFAULT_PROBABILITY")
517});
518
519#[derive(Debug, Clone, Serialize, Deserialize)]
520pub struct BloomFilterIndexBuilderParams {
521 #[serde(default = "default_number_of_items")]
522 number_of_items: u64,
523 #[serde(default = "default_probability")]
524 probability: f64,
525}
526
527impl Default for BloomFilterIndexBuilderParams {
528 fn default() -> Self {
529 Self {
530 number_of_items: *DEFAULT_NUMBER_OF_ITEMS,
531 probability: *DEFAULT_PROBABILITY,
532 }
533 }
534}
535
536impl BloomFilterIndexBuilderParams {
537 #[cfg(test)]
538 fn new(number_of_items: u64, probability: f64) -> Self {
539 Self {
540 number_of_items,
541 probability,
542 }
543 }
544}
545
546pub struct BloomFilterIndexBuilder {
547 params: BloomFilterIndexBuilderParams,
548 blocks: Vec<BloomFilterStatistics>,
549}
550
551impl BloomFilterIndexBuilder {
552 pub fn try_new(params: BloomFilterIndexBuilderParams) -> Result<Self> {
553 Ok(Self {
554 params,
555 blocks: Vec::new(),
556 })
557 }
558
559 pub async fn train(&mut self, batches_source: SendableRecordBatchStream) -> Result<()> {
563 let processor = BloomFilterProcessor::new(self.params.clone())?;
564 let trainer = ZoneTrainer::new(processor, self.params.number_of_items)?;
565 self.blocks = trainer.train(batches_source).await?;
566 Ok(())
567 }
568
569 fn bloomfilter_stats_as_batch(&self) -> Result<RecordBatch> {
570 let fragment_ids =
571 UInt64Array::from_iter_values(self.blocks.iter().map(|block| block.bound.fragment_id));
572
573 let zone_starts =
574 UInt64Array::from_iter_values(self.blocks.iter().map(|block| block.bound.start));
575
576 let zone_lengths = UInt64Array::from_iter_values(
577 self.blocks.iter().map(|block| block.bound.length as u64),
578 );
579
580 let has_nulls = arrow_array::BooleanArray::from(
581 self.blocks
582 .iter()
583 .map(|block| block.has_null)
584 .collect::<Vec<bool>>(),
585 );
586
587 let bloom_filter_data = if self.blocks.is_empty() {
589 Arc::new(arrow_array::BinaryArray::new_null(0)) as ArrayRef
590 } else {
591 let binary_data: Vec<Vec<u8>> = self
592 .blocks
593 .iter()
594 .map(|block| block.bloom_filter.to_bytes())
595 .collect();
596 let binary_refs: Vec<Option<&[u8]>> = binary_data
597 .iter()
598 .map(|bytes| Some(bytes.as_slice()))
599 .collect();
600 Arc::new(arrow_array::BinaryArray::from_opt_vec(binary_refs)) as ArrayRef
601 };
602
603 let schema = Arc::new(arrow_schema::Schema::new(vec![
604 Field::new("fragment_id", DataType::UInt64, false),
605 Field::new("zone_start", DataType::UInt64, false),
606 Field::new("zone_length", DataType::UInt64, false),
607 Field::new("has_null", DataType::Boolean, false),
608 Field::new("bloom_filter_data", DataType::Binary, false),
609 ]));
610
611 let columns: Vec<ArrayRef> = vec![
612 Arc::new(fragment_ids) as ArrayRef,
613 Arc::new(zone_starts) as ArrayRef,
614 Arc::new(zone_lengths) as ArrayRef,
615 Arc::new(has_nulls) as ArrayRef,
616 bloom_filter_data,
617 ];
618
619 Ok(RecordBatch::try_new(schema, columns)?)
620 }
621
622 pub async fn write_index(self, index_store: &dyn IndexStore) -> Result<()> {
623 let record_batch = self.bloomfilter_stats_as_batch()?;
624
625 let mut file_schema = record_batch.schema().as_ref().clone();
626 file_schema.metadata.insert(
627 BLOOMFILTER_ITEM_META_KEY.to_string(),
628 self.params.number_of_items.to_string(),
629 );
630
631 file_schema.metadata.insert(
632 BLOOMFILTER_PROBABILITY_META_KEY.to_string(),
633 self.params.probability.to_string(),
634 );
635
636 let mut index_file = index_store
637 .new_index_file(BLOOMFILTER_FILENAME, Arc::new(file_schema))
638 .await?;
639 index_file.write_record_batch(record_batch).await?;
640 index_file.finish().await?;
641 Ok(())
642 }
643}
644
645struct BloomFilterProcessor {
647 params: BloomFilterIndexBuilderParams,
648 sbbf: Option<Sbbf>,
649 cur_zone_has_null: bool,
650}
651
652impl BloomFilterProcessor {
653 fn new(params: BloomFilterIndexBuilderParams) -> Result<Self> {
654 let mut processor = Self {
655 params,
656 sbbf: None,
657 cur_zone_has_null: false,
658 };
659 processor.reset()?;
660 Ok(processor)
661 }
662
663 fn build_filter(params: &BloomFilterIndexBuilderParams) -> Result<Sbbf> {
664 SbbfBuilder::new()
665 .expected_items(params.number_of_items)
666 .false_positive_probability(params.probability)
667 .build()
668 .map_err(|e| {
669 Error::invalid_input_source(format!("Failed to build SBBF: {:?}", e).into())
670 })
671 }
672
673 fn process_primitive_array<T>(sbbf: &mut Sbbf, array: &arrow_array::PrimitiveArray<T>) -> bool
674 where
675 T: arrow_array::ArrowPrimitiveType,
676 T::Native: as_bytes::AsBytes,
677 {
678 let mut has_null = false;
679 for i in 0..array.len() {
680 if array.is_valid(i) {
681 sbbf.insert(&array.value(i));
682 } else {
683 has_null = true;
684 }
685 }
686 has_null
687 }
688
689 fn process_string_array(sbbf: &mut Sbbf, array: &arrow_array::StringArray) -> bool {
690 let mut has_null = false;
691 for i in 0..array.len() {
692 if array.is_valid(i) {
693 sbbf.insert(array.value(i));
694 } else {
695 has_null = true;
696 }
697 }
698 has_null
699 }
700
701 fn process_large_string_array(sbbf: &mut Sbbf, array: &arrow_array::LargeStringArray) -> bool {
702 let mut has_null = false;
703 for i in 0..array.len() {
704 if array.is_valid(i) {
705 sbbf.insert(array.value(i));
706 } else {
707 has_null = true;
708 }
709 }
710 has_null
711 }
712
713 fn process_binary_array(sbbf: &mut Sbbf, array: &arrow_array::BinaryArray) -> bool {
714 let mut has_null = false;
715 for i in 0..array.len() {
716 if array.is_valid(i) {
717 sbbf.insert(array.value(i));
718 } else {
719 has_null = true;
720 }
721 }
722 has_null
723 }
724
725 fn process_large_binary_array(sbbf: &mut Sbbf, array: &arrow_array::LargeBinaryArray) -> bool {
726 let mut has_null = false;
727 for i in 0..array.len() {
728 if array.is_valid(i) {
729 sbbf.insert(array.value(i));
730 } else {
731 has_null = true;
732 }
733 }
734 has_null
735 }
736}
737
738impl ZoneProcessor for BloomFilterProcessor {
739 type ZoneStatistics = BloomFilterStatistics;
740
741 fn process_chunk(&mut self, array: &ArrayRef) -> Result<()> {
742 let sbbf = self.sbbf.as_mut().ok_or_else(|| {
743 Error::invalid_input("BloomFilterProcessor did not initialize bloom filter")
744 })?;
745
746 let has_null = match array.data_type() {
747 DataType::Int8 => {
749 let typed_array = array
750 .as_any()
751 .downcast_ref::<arrow_array::Int8Array>()
752 .unwrap();
753 Self::process_primitive_array(sbbf, typed_array)
754 }
755 DataType::Int16 => {
756 let typed_array = array
757 .as_any()
758 .downcast_ref::<arrow_array::Int16Array>()
759 .unwrap();
760 Self::process_primitive_array(sbbf, typed_array)
761 }
762 DataType::Int32 => {
763 let typed_array = array
764 .as_any()
765 .downcast_ref::<arrow_array::Int32Array>()
766 .unwrap();
767 Self::process_primitive_array(sbbf, typed_array)
768 }
769 DataType::Int64 => {
770 let typed_array = array
771 .as_any()
772 .downcast_ref::<arrow_array::Int64Array>()
773 .unwrap();
774 Self::process_primitive_array(sbbf, typed_array)
775 }
776 DataType::UInt8 => {
778 let typed_array = array
779 .as_any()
780 .downcast_ref::<arrow_array::UInt8Array>()
781 .unwrap();
782 Self::process_primitive_array(sbbf, typed_array)
783 }
784 DataType::UInt16 => {
785 let typed_array = array
786 .as_any()
787 .downcast_ref::<arrow_array::UInt16Array>()
788 .unwrap();
789 Self::process_primitive_array(sbbf, typed_array)
790 }
791 DataType::UInt32 => {
792 let typed_array = array
793 .as_any()
794 .downcast_ref::<arrow_array::UInt32Array>()
795 .unwrap();
796 Self::process_primitive_array(sbbf, typed_array)
797 }
798 DataType::UInt64 => {
799 let typed_array = array
800 .as_any()
801 .downcast_ref::<arrow_array::UInt64Array>()
802 .unwrap();
803 Self::process_primitive_array(sbbf, typed_array)
804 }
805 DataType::Float32 => {
807 let typed_array = array
808 .as_any()
809 .downcast_ref::<arrow_array::Float32Array>()
810 .unwrap();
811 Self::process_primitive_array(sbbf, typed_array)
812 }
813 DataType::Float64 => {
814 let typed_array = array
815 .as_any()
816 .downcast_ref::<arrow_array::Float64Array>()
817 .unwrap();
818 Self::process_primitive_array(sbbf, typed_array)
819 }
820 DataType::Date32 => {
822 let typed_array = array
823 .as_any()
824 .downcast_ref::<arrow_array::Date32Array>()
825 .unwrap();
826 Self::process_primitive_array(sbbf, typed_array)
827 }
828 DataType::Time32(time_unit) => match time_unit {
829 arrow_schema::TimeUnit::Second => {
830 let typed_array = array
831 .as_any()
832 .downcast_ref::<arrow_array::Time32SecondArray>()
833 .unwrap();
834 Self::process_primitive_array(sbbf, typed_array)
835 }
836 arrow_schema::TimeUnit::Millisecond => {
837 let typed_array = array
838 .as_any()
839 .downcast_ref::<arrow_array::Time32MillisecondArray>()
840 .unwrap();
841 Self::process_primitive_array(sbbf, typed_array)
842 }
843 _ => {
844 return Err(Error::invalid_input_source(
845 format!("Unsupported Time32 unit: {:?}", time_unit).into(),
846 ));
847 }
848 },
849 DataType::Date64 => {
851 let typed_array = array
852 .as_any()
853 .downcast_ref::<arrow_array::Date64Array>()
854 .unwrap();
855 Self::process_primitive_array(sbbf, typed_array)
856 }
857 DataType::Time64(time_unit) => match time_unit {
858 arrow_schema::TimeUnit::Microsecond => {
859 let typed_array = array
860 .as_any()
861 .downcast_ref::<arrow_array::Time64MicrosecondArray>()
862 .unwrap();
863 Self::process_primitive_array(sbbf, typed_array)
864 }
865 arrow_schema::TimeUnit::Nanosecond => {
866 let typed_array = array
867 .as_any()
868 .downcast_ref::<arrow_array::Time64NanosecondArray>()
869 .unwrap();
870 Self::process_primitive_array(sbbf, typed_array)
871 }
872 _ => {
873 return Err(Error::invalid_input_source(
874 format!("Unsupported Time64 unit: {:?}", time_unit).into(),
875 ));
876 }
877 },
878 DataType::Timestamp(time_unit, _) => match time_unit {
879 arrow_schema::TimeUnit::Second => {
880 let typed_array = array
881 .as_any()
882 .downcast_ref::<arrow_array::TimestampSecondArray>()
883 .unwrap();
884 Self::process_primitive_array(sbbf, typed_array)
885 }
886 arrow_schema::TimeUnit::Millisecond => {
887 let typed_array = array
888 .as_any()
889 .downcast_ref::<arrow_array::TimestampMillisecondArray>()
890 .unwrap();
891 Self::process_primitive_array(sbbf, typed_array)
892 }
893 arrow_schema::TimeUnit::Microsecond => {
894 let typed_array = array
895 .as_any()
896 .downcast_ref::<arrow_array::TimestampMicrosecondArray>()
897 .unwrap();
898 Self::process_primitive_array(sbbf, typed_array)
899 }
900 arrow_schema::TimeUnit::Nanosecond => {
901 let typed_array = array
902 .as_any()
903 .downcast_ref::<arrow_array::TimestampNanosecondArray>()
904 .unwrap();
905 Self::process_primitive_array(sbbf, typed_array)
906 }
907 },
908 DataType::Utf8 => {
909 let typed_array = array
910 .as_any()
911 .downcast_ref::<arrow_array::StringArray>()
912 .unwrap();
913 Self::process_string_array(sbbf, typed_array)
914 }
915 DataType::LargeUtf8 => {
916 let typed_array = array
917 .as_any()
918 .downcast_ref::<arrow_array::LargeStringArray>()
919 .unwrap();
920 Self::process_large_string_array(sbbf, typed_array)
921 }
922 DataType::Binary => {
923 let typed_array = array
924 .as_any()
925 .downcast_ref::<arrow_array::BinaryArray>()
926 .unwrap();
927 Self::process_binary_array(sbbf, typed_array)
928 }
929 DataType::LargeBinary => {
930 let typed_array = array
931 .as_any()
932 .downcast_ref::<arrow_array::LargeBinaryArray>()
933 .unwrap();
934 Self::process_large_binary_array(sbbf, typed_array)
935 }
936 _ => {
937 return Err(Error::invalid_input_source(
938 format!(
939 "Bloom filter does not support data type: {:?}",
940 array.data_type()
941 )
942 .into(),
943 ));
944 }
945 };
946
947 self.cur_zone_has_null = self.cur_zone_has_null || has_null;
949 Ok(())
950 }
951
952 fn finish_zone(&mut self, bound: ZoneBound) -> Result<Self::ZoneStatistics> {
953 let bloom_filter = self.sbbf.as_ref().ok_or_else(|| {
954 Error::invalid_input("BloomFilterProcessor did not initialize bloom filter")
955 })?;
956 Ok(BloomFilterStatistics {
957 bound,
958 has_null: self.cur_zone_has_null,
959 bloom_filter: bloom_filter.clone(),
960 })
961 }
962
963 fn reset(&mut self) -> Result<()> {
964 self.sbbf = Some(Self::build_filter(&self.params)?);
965 self.cur_zone_has_null = false;
966 Ok(())
967 }
968}
969
970#[derive(Debug, Default)]
971pub struct BloomFilterIndexPlugin;
972
973impl BloomFilterIndexPlugin {
974 async fn train_bloomfilter_index(
975 batches_source: SendableRecordBatchStream,
976 index_store: &dyn IndexStore,
977 options: Option<BloomFilterIndexBuilderParams>,
978 ) -> Result<()> {
979 let mut builder = BloomFilterIndexBuilder::try_new(options.unwrap_or_default())?;
980
981 builder.train(batches_source).await?;
982
983 builder.write_index(index_store).await?;
984 Ok(())
985 }
986}
987
988#[async_trait]
989impl ScalarIndexPlugin for BloomFilterIndexPlugin {
990 fn name(&self) -> &str {
991 "BloomFilter"
992 }
993
994 fn new_training_request(
995 &self,
996 params: &str,
997 field: &Field,
998 ) -> Result<Box<dyn TrainingRequest>> {
999 if field.data_type().is_nested() {
1000 return Err(Error::invalid_input_source(
1001 "A bloom filter index can only be created on a non-nested field.".into(),
1002 ));
1003 }
1004
1005 match field.data_type() {
1007 DataType::Int8
1009 | DataType::Int16
1010 | DataType::Int32
1011 | DataType::Int64
1012 | DataType::UInt8
1014 | DataType::UInt16
1015 | DataType::UInt32
1016 | DataType::UInt64
1017 | DataType::Float32
1019 | DataType::Float64
1020 | DataType::Utf8
1022 | DataType::LargeUtf8
1023 | DataType::Binary
1025 | DataType::LargeBinary
1026 | DataType::Date32
1028 | DataType::Date64
1029 | DataType::Time32(_)
1030 | DataType::Time64(_)
1031 | DataType::Timestamp(_, _) => {
1032 }
1034 _ => {
1035 return Err(Error::invalid_input_source(format!(
1036 "Bloom filter index does not support data type: {:?}. Supported types: Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float32, Float64, Utf8, LargeUtf8, Binary, LargeBinary, Date32, Date64, Time32, Time64, Timestamp",
1037 field.data_type()
1038 ).into()));
1039 }
1040 }
1041
1042 let params = serde_json::from_str::<BloomFilterIndexBuilderParams>(params)?;
1043
1044 Ok(Box::new(BloomFilterIndexTrainingRequest::new(params)))
1045 }
1046
1047 async fn train_index(
1048 &self,
1049 data: SendableRecordBatchStream,
1050 index_store: &dyn IndexStore,
1051 request: Box<dyn TrainingRequest>,
1052 fragment_ids: Option<Vec<u32>>,
1053 _progress: Arc<dyn crate::progress::IndexBuildProgress>,
1054 ) -> Result<CreatedIndex> {
1055 if fragment_ids.is_some() {
1056 return Err(Error::invalid_input_source(
1057 "BloomFilter index does not support fragment training".into(),
1058 ));
1059 }
1060
1061 let request = (request as Box<dyn std::any::Any>)
1062 .downcast::<BloomFilterIndexTrainingRequest>()
1063 .map_err(|_| {
1064 Error::invalid_input_source(
1065 "must provide training request created by new_training_request".into(),
1066 )
1067 })?;
1068 Self::train_bloomfilter_index(data, index_store, Some(request.params)).await?;
1069 Ok(CreatedIndex {
1070 index_details: prost_types::Any::from_msg(&pb::BloomFilterIndexDetails::default())
1071 .unwrap(),
1072 index_version: BLOOMFILTER_INDEX_VERSION,
1073 })
1074 }
1075
1076 fn provides_exact_answer(&self) -> bool {
1077 false
1078 }
1079
1080 fn version(&self) -> u32 {
1081 BLOOMFILTER_INDEX_VERSION
1082 }
1083
1084 fn new_query_parser(
1085 &self,
1086 index_name: String,
1087 _index_details: &prost_types::Any,
1088 ) -> Option<Box<dyn ScalarQueryParser>> {
1089 Some(Box::new(BloomFilterQueryParser::new(index_name, true)))
1090 }
1091
1092 async fn load_index(
1093 &self,
1094 index_store: Arc<dyn IndexStore>,
1095 _index_details: &prost_types::Any,
1096 frag_reuse_index: Option<Arc<FragReuseIndex>>,
1097 cache: &LanceCache,
1098 ) -> Result<Arc<dyn ScalarIndex>> {
1099 Ok(
1100 BloomFilterIndex::load(index_store, frag_reuse_index, cache).await?
1101 as Arc<dyn ScalarIndex>,
1102 )
1103 }
1104
1105 async fn load_statistics(
1106 &self,
1107 _index_store: Arc<dyn IndexStore>,
1108 _index_details: &prost_types::Any,
1109 ) -> Result<Option<serde_json::Value>> {
1110 Ok(None)
1111 }
1112}
1113
1114#[derive(Debug)]
1115pub struct BloomFilterIndexTrainingRequest {
1116 pub params: BloomFilterIndexBuilderParams,
1117 pub criteria: TrainingCriteria,
1118}
1119
1120impl BloomFilterIndexTrainingRequest {
1121 pub fn new(params: BloomFilterIndexBuilderParams) -> Self {
1122 Self {
1123 params,
1124 criteria: TrainingCriteria::new(TrainingOrdering::Addresses).with_row_addr(),
1125 }
1126 }
1127}
1128
1129impl TrainingRequest for BloomFilterIndexTrainingRequest {
1130 fn as_any(&self) -> &dyn std::any::Any {
1131 self
1132 }
1133
1134 fn criteria(&self) -> &TrainingCriteria {
1135 &self.criteria
1136 }
1137}
1138
1139#[cfg(test)]
1140mod tests {
1141 use crate::scalar::registry::VALUE_COLUMN_NAME;
1142 use std::sync::Arc;
1143
1144 use crate::scalar::bloomfilter::BloomFilterIndexPlugin;
1145 use arrow_array::{RecordBatch, UInt64Array, record_batch};
1146 use arrow_schema::{DataType, Field, Schema};
1147 use datafusion::execution::SendableRecordBatchStream;
1148 use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
1149 use datafusion_common::ScalarValue;
1150 use futures::{StreamExt, stream};
1151 use lance_core::{
1152 ROW_ADDR,
1153 cache::LanceCache,
1154 utils::{mask::RowAddrTreeMap, tempfile::TempObjDir},
1155 };
1156 use lance_io::object_store::ObjectStore;
1157
1158 use crate::scalar::{
1159 BloomFilterQuery, ScalarIndex, SearchResult,
1160 bloomfilter::{BloomFilterIndex, BloomFilterIndexBuilderParams},
1161 lance_format::LanceIndexStore,
1162 };
1163
1164 use crate::Index; use crate::metrics::NoOpMetricsCollector;
1166 use roaring::RoaringBitmap; fn add_row_addr(stream: SendableRecordBatchStream) -> SendableRecordBatchStream {
1170 let schema = stream.schema();
1171 let schema_with_row_addr = Arc::new(Schema::new(vec![
1172 schema.field(0).clone(),
1173 Field::new(ROW_ADDR, DataType::UInt64, false),
1174 ]));
1175 let schema = schema_with_row_addr.clone();
1176 let stream = stream.enumerate().map(move |(frag_id, batch)| {
1177 let batch = batch.unwrap();
1178 let row_addr = Arc::new(UInt64Array::from_iter_values(
1179 (0..batch.num_rows() as u64).map(|off| off + ((frag_id as u64) << 32)),
1180 ));
1181 Ok(RecordBatch::try_new(
1182 schema_with_row_addr.clone(),
1183 vec![batch.column(0).clone(), row_addr],
1184 )?)
1185 });
1186 Box::pin(RecordBatchStreamAdapter::new(schema, stream))
1187 }
1188
1189 #[tokio::test]
1190 async fn test_empty_bloomfilter_index() {
1191 let tmpdir = TempObjDir::default();
1192 let test_store = Arc::new(LanceIndexStore::new(
1193 Arc::new(ObjectStore::local()),
1194 tmpdir.clone(),
1195 Arc::new(LanceCache::no_cache()),
1196 ));
1197
1198 let data = arrow_array::Int32Array::from(Vec::<i32>::new());
1199 let schema = Arc::new(Schema::new(vec![Field::new(
1200 VALUE_COLUMN_NAME,
1201 DataType::Int32,
1202 false,
1203 )]));
1204 let data = RecordBatch::try_new(schema.clone(), vec![Arc::new(data)]).unwrap();
1205
1206 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1207 schema,
1208 stream::once(std::future::ready(Ok(data))),
1209 ));
1210 let data_stream = add_row_addr(data_stream);
1211
1212 BloomFilterIndexPlugin::train_bloomfilter_index(data_stream, test_store.as_ref(), None)
1213 .await
1214 .unwrap();
1215
1216 log::debug!("Successfully wrote the index file");
1217
1218 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1220 .await
1221 .expect("Failed to load BloomFilterIndex");
1222 assert_eq!(index.zones.len(), 0);
1223 assert_eq!(index.number_of_items, 8192);
1224 assert_eq!(index.probability, 0.00057); let query = BloomFilterQuery::Equals(ScalarValue::Int32(None));
1228 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1229 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1230 }
1231
1232 #[tokio::test]
1233 async fn test_basic_bloomfilter_index() {
1234 let tmpdir = TempObjDir::default();
1235 let test_store = Arc::new(LanceIndexStore::new(
1236 Arc::new(ObjectStore::local()),
1237 tmpdir.clone(),
1238 Arc::new(LanceCache::no_cache()),
1239 ));
1240
1241 let data = arrow_array::Int32Array::from_iter_values(0..100);
1242 let schema = Arc::new(Schema::new(vec![Field::new(
1243 VALUE_COLUMN_NAME,
1244 DataType::Int32,
1245 false,
1246 )]));
1247 let data = RecordBatch::try_new(schema.clone(), vec![Arc::new(data)]).unwrap();
1248 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1249 schema,
1250 stream::once(std::future::ready(Ok(data))),
1251 ));
1252 let data_stream = add_row_addr(data_stream);
1253
1254 BloomFilterIndexPlugin::train_bloomfilter_index(
1255 data_stream,
1256 test_store.as_ref(),
1257 Some(BloomFilterIndexBuilderParams::new(100, 0.01)), )
1259 .await
1260 .unwrap();
1261
1262 log::debug!("Successfully wrote the index file");
1263
1264 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1266 .await
1267 .expect("Failed to load BloomFilterIndex");
1268
1269 assert_eq!(index.zones.len(), 1);
1270 assert_eq!(index.number_of_items, 100);
1271 assert_eq!(index.probability, 0.01);
1272
1273 assert_eq!(index.zones[0].bound.fragment_id, 0u64);
1275 assert_eq!(index.zones[0].bound.start, 0u64);
1276 assert_eq!(index.zones[0].bound.length, 100);
1277
1278 let query = BloomFilterQuery::Equals(ScalarValue::Int32(Some(50)));
1281 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1282
1283 let mut expected = RowAddrTreeMap::new();
1285 expected.insert_range(0..100);
1286 assert_eq!(result, SearchResult::at_most(expected));
1287
1288 let query = BloomFilterQuery::Equals(ScalarValue::Int32(Some(500))); let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1291
1292 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1294
1295 assert_eq!(
1297 index.calculate_included_frags().await.unwrap(),
1298 RoaringBitmap::from_iter(0..1)
1299 );
1300 }
1301
1302 #[tokio::test]
1303 async fn test_multiple_fragments_bloomfilter() {
1304 let tmpdir = TempObjDir::default();
1305 let test_store = Arc::new(LanceIndexStore::new(
1306 Arc::new(ObjectStore::local()),
1307 tmpdir.clone(),
1308 Arc::new(LanceCache::no_cache()),
1309 ));
1310
1311 let schema = Arc::new(Schema::new(vec![Field::new(
1312 VALUE_COLUMN_NAME,
1313 DataType::Int64,
1314 false,
1315 )]));
1316
1317 let fragment0_data = arrow_array::Int64Array::from_iter_values(0..100);
1320 let fragment0_batch =
1321 RecordBatch::try_new(schema.clone(), vec![Arc::new(fragment0_data)]).unwrap();
1322
1323 let fragment1_data = arrow_array::Int64Array::from_iter_values(100..200);
1325 let fragment1_batch =
1326 RecordBatch::try_new(schema.clone(), vec![Arc::new(fragment1_data)]).unwrap();
1327
1328 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1330 schema.clone(),
1331 stream::iter(vec![
1332 Ok(fragment0_batch.clone()),
1333 Ok(fragment1_batch.clone()),
1334 ]),
1335 ));
1336 let data_stream = add_row_addr(data_stream);
1337
1338 BloomFilterIndexPlugin::train_bloomfilter_index(
1339 data_stream,
1340 test_store.as_ref(),
1341 Some(BloomFilterIndexBuilderParams::new(50, 0.05)), )
1343 .await
1344 .unwrap();
1345
1346 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1348 .await
1349 .expect("Failed to load BloomFilterIndex");
1350
1351 assert_eq!(index.zones.len(), 4);
1353
1354 assert_eq!(index.zones[0].bound.fragment_id, 0u64);
1356 assert_eq!(index.zones[0].bound.start, 0u64);
1357 assert_eq!(index.zones[0].bound.length, 50);
1358
1359 assert_eq!(index.zones[1].bound.fragment_id, 0u64);
1360 assert_eq!(index.zones[1].bound.start, 50u64);
1361 assert_eq!(index.zones[1].bound.length, 50);
1362
1363 assert_eq!(index.zones[2].bound.fragment_id, 1u64);
1365 assert_eq!(index.zones[2].bound.start, 0u64);
1366 assert_eq!(index.zones[2].bound.length, 50);
1367
1368 assert_eq!(index.zones[3].bound.fragment_id, 1u64);
1369 assert_eq!(index.zones[3].bound.start, 50u64);
1370 assert_eq!(index.zones[3].bound.length, 50);
1371
1372 let query = BloomFilterQuery::Equals(ScalarValue::Int64(Some(150)));
1374 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1375
1376 let mut expected = RowAddrTreeMap::new();
1379 expected.insert_range((1u64 << 32) + 50..((1u64 << 32) + 100)); assert_eq!(result, SearchResult::at_most(expected));
1381
1382 assert_eq!(
1384 index.calculate_included_frags().await.unwrap(),
1385 RoaringBitmap::from_iter(0..2)
1386 );
1387 }
1388
1389 #[tokio::test]
1390 async fn test_nan_bloomfilter_index() {
1391 let tmpdir = TempObjDir::default();
1392 let test_store = Arc::new(LanceIndexStore::new(
1393 Arc::new(ObjectStore::local()),
1394 tmpdir.clone(),
1395 Arc::new(LanceCache::no_cache()),
1396 ));
1397
1398 let mut values = Vec::new();
1401 for i in 0..500 {
1402 if i % 5 == 2 {
1403 values.push(f32::NAN);
1404 } else {
1405 values.push(i as f32);
1406 }
1407 }
1408
1409 let float_data = arrow_array::Float32Array::from(values);
1410 let schema = Arc::new(Schema::new(vec![Field::new(
1411 VALUE_COLUMN_NAME,
1412 DataType::Float32,
1413 true,
1414 )]));
1415 let data =
1416 RecordBatch::try_new(schema.clone(), vec![Arc::new(float_data.clone())]).unwrap();
1417 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1418 schema,
1419 stream::once(std::future::ready(Ok(data))),
1420 ));
1421 let data_stream = add_row_addr(data_stream);
1422
1423 BloomFilterIndexPlugin::train_bloomfilter_index(
1424 data_stream,
1425 test_store.as_ref(),
1426 Some(BloomFilterIndexBuilderParams::new(100, 0.01)), )
1428 .await
1429 .unwrap();
1430
1431 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1433 .await
1434 .expect("Failed to load BloomFilterIndex");
1435
1436 assert_eq!(index.zones.len(), 5);
1438
1439 let query = BloomFilterQuery::Equals(ScalarValue::Float32(Some(f32::NAN)));
1441 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1442
1443 let mut expected = RowAddrTreeMap::new();
1445 expected.insert_range(0..500); assert_eq!(result, SearchResult::at_most(expected));
1447
1448 let query = BloomFilterQuery::Equals(ScalarValue::Float32(Some(5.0)));
1450 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1451
1452 let mut expected = RowAddrTreeMap::new();
1454 expected.insert_range(0..100);
1455 assert_eq!(result, SearchResult::at_most(expected));
1456
1457 let query = BloomFilterQuery::Equals(ScalarValue::Float32(Some(250.0)));
1459 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1460
1461 let mut expected = RowAddrTreeMap::new();
1463 expected.insert_range(200..300);
1464 assert_eq!(result, SearchResult::at_most(expected));
1465
1466 let query = BloomFilterQuery::Equals(ScalarValue::Float32(Some(10000.0)));
1468 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1469
1470 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1472
1473 let query = BloomFilterQuery::IsIn(vec![
1475 ScalarValue::Float32(Some(f32::NAN)),
1476 ScalarValue::Float32(Some(5.0)),
1477 ScalarValue::Float32(Some(150.0)), ]);
1479 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1480
1481 let mut expected = RowAddrTreeMap::new();
1483 expected.insert_range(0..500);
1484 assert_eq!(result, SearchResult::at_most(expected));
1485 }
1486
1487 #[tokio::test]
1488 async fn test_complex_bloomfilter_index() {
1489 let tmpdir = TempObjDir::default();
1490 let test_store = Arc::new(LanceIndexStore::new(
1491 Arc::new(ObjectStore::local()),
1492 tmpdir.clone(),
1493 Arc::new(LanceCache::no_cache()),
1494 ));
1495
1496 let data_size = 10000;
1498 let data = arrow_array::Int64Array::from_iter_values(0..data_size as i64);
1499 let schema = Arc::new(Schema::new(vec![Field::new(
1500 VALUE_COLUMN_NAME,
1501 DataType::Int64,
1502 false,
1503 )]));
1504 let data = RecordBatch::try_new(schema.clone(), vec![Arc::new(data)]).unwrap();
1505 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1506 schema,
1507 stream::once(std::future::ready(Ok(data))),
1508 ));
1509 let data_stream = add_row_addr(data_stream);
1510
1511 BloomFilterIndexPlugin::train_bloomfilter_index(
1512 data_stream,
1513 test_store.as_ref(),
1514 Some(BloomFilterIndexBuilderParams::new(1000, 0.001)), )
1516 .await
1517 .unwrap();
1518
1519 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1521 .await
1522 .expect("Failed to load BloomFilterIndex");
1523
1524 assert_eq!(index.zones.len(), 10);
1526 assert_eq!(index.number_of_items, 1000);
1527 assert_eq!(index.probability, 0.001);
1528
1529 for (i, block) in index.zones.iter().enumerate() {
1531 assert_eq!(block.bound.fragment_id, 0u64);
1532 assert_eq!(block.bound.start, (i * 1000) as u64);
1533 assert_eq!(block.bound.length, 1000);
1534 assert!(!block.bloom_filter.to_bytes().is_empty());
1536 }
1537
1538 let query = BloomFilterQuery::Equals(ScalarValue::Int64(Some(2500))); let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1541
1542 let mut expected = RowAddrTreeMap::new();
1544 expected.insert_range(2000..3000);
1545 assert_eq!(result, SearchResult::at_most(expected));
1546
1547 let query = BloomFilterQuery::Equals(ScalarValue::Int64(Some(50000)));
1549 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1550
1551 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1553
1554 let query = BloomFilterQuery::IsIn(vec![
1556 ScalarValue::Int64(Some(500)), ScalarValue::Int64(Some(2500)), ScalarValue::Int64(Some(7500)), ScalarValue::Int64(Some(50000)), ]);
1561 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1562
1563 let mut expected = RowAddrTreeMap::new();
1565 expected.insert_range(0..1000); expected.insert_range(2000..3000); expected.insert_range(7000..8000); assert_eq!(result, SearchResult::at_most(expected));
1569
1570 assert_eq!(
1572 index.calculate_included_frags().await.unwrap(),
1573 RoaringBitmap::from_iter(0..1)
1574 );
1575 }
1576
1577 #[tokio::test]
1578 async fn test_string_bloomfilter_index() {
1579 let tmpdir = TempObjDir::default();
1580 let test_store = Arc::new(LanceIndexStore::new(
1581 Arc::new(ObjectStore::local()),
1582 tmpdir.clone(),
1583 Arc::new(LanceCache::no_cache()),
1584 ));
1585
1586 let string_values: Vec<String> = (0..200).map(|i| format!("value_{:03}", i)).collect();
1588 let string_data = arrow_array::StringArray::from_iter_values(string_values.iter());
1589 let schema = Arc::new(Schema::new(vec![Field::new(
1590 VALUE_COLUMN_NAME,
1591 DataType::Utf8,
1592 false,
1593 )]));
1594 let data = RecordBatch::try_new(schema.clone(), vec![Arc::new(string_data)]).unwrap();
1595 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1596 schema,
1597 stream::once(std::future::ready(Ok(data))),
1598 ));
1599 let data_stream = add_row_addr(data_stream);
1600
1601 BloomFilterIndexPlugin::train_bloomfilter_index(
1602 data_stream,
1603 test_store.as_ref(),
1604 Some(BloomFilterIndexBuilderParams::new(100, 0.01)), )
1606 .await
1607 .unwrap();
1608
1609 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1611 .await
1612 .expect("Failed to load BloomFilterIndex");
1613
1614 assert_eq!(index.zones.len(), 2);
1616
1617 let query = BloomFilterQuery::Equals(ScalarValue::Utf8(Some("value_050".to_string())));
1619 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1620
1621 let mut expected = RowAddrTreeMap::new();
1623 expected.insert_range(0..100);
1624 assert_eq!(result, SearchResult::at_most(expected));
1625
1626 let query = BloomFilterQuery::Equals(ScalarValue::Utf8(Some("value_150".to_string())));
1628 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1629
1630 let mut expected = RowAddrTreeMap::new();
1632 expected.insert_range(100..200);
1633 assert_eq!(result, SearchResult::at_most(expected));
1634
1635 let query =
1637 BloomFilterQuery::Equals(ScalarValue::Utf8(Some("nonexistent_value".to_string())));
1638 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1639
1640 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1642
1643 let query = BloomFilterQuery::IsIn(vec![
1645 ScalarValue::Utf8(Some("value_025".to_string())), ScalarValue::Utf8(Some("value_175".to_string())), ScalarValue::Utf8(Some("nonexistent".to_string())), ]);
1649 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1650
1651 let mut expected = RowAddrTreeMap::new();
1653 expected.insert_range(0..200);
1654 assert_eq!(result, SearchResult::at_most(expected));
1655 }
1656
1657 #[tokio::test]
1658 async fn test_binary_bloomfilter_index() {
1659 let tmpdir = TempObjDir::default();
1660 let test_store = Arc::new(LanceIndexStore::new(
1661 Arc::new(ObjectStore::local()),
1662 tmpdir.clone(),
1663 Arc::new(LanceCache::no_cache()),
1664 ));
1665
1666 let binary_values: Vec<Vec<u8>> = (0..100)
1668 .map(|i| vec![i as u8, (i + 1) as u8, (i + 2) as u8])
1669 .collect();
1670 let binary_data = arrow_array::BinaryArray::from_iter_values(binary_values.iter());
1671 let schema = Arc::new(Schema::new(vec![Field::new(
1672 VALUE_COLUMN_NAME,
1673 DataType::Binary,
1674 false,
1675 )]));
1676 let data = RecordBatch::try_new(schema.clone(), vec![Arc::new(binary_data)]).unwrap();
1677 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1678 schema,
1679 stream::once(std::future::ready(Ok(data))),
1680 ));
1681 let data_stream = add_row_addr(data_stream);
1682
1683 BloomFilterIndexPlugin::train_bloomfilter_index(
1684 data_stream,
1685 test_store.as_ref(),
1686 Some(BloomFilterIndexBuilderParams::new(50, 0.05)),
1687 )
1688 .await
1689 .unwrap();
1690
1691 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1693 .await
1694 .expect("Failed to load BloomFilterIndex");
1695
1696 assert_eq!(index.zones.len(), 2);
1698
1699 let query = BloomFilterQuery::Equals(ScalarValue::Binary(Some(vec![25, 26, 27])));
1701 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1702
1703 let mut expected = RowAddrTreeMap::new();
1705 expected.insert_range(0..50);
1706 assert_eq!(result, SearchResult::at_most(expected));
1707
1708 let query = BloomFilterQuery::Equals(ScalarValue::Binary(Some(vec![75, 76, 77])));
1710 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1711
1712 let mut expected = RowAddrTreeMap::new();
1714 expected.insert_range(50..100);
1715 assert_eq!(result, SearchResult::at_most(expected));
1716
1717 let query = BloomFilterQuery::Equals(ScalarValue::Binary(Some(vec![255, 254, 253])));
1719 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1720
1721 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1723 }
1724
1725 #[tokio::test]
1726 async fn test_large_data_types_bloomfilter_index() {
1727 let tmpdir = TempObjDir::default();
1728 let test_store = Arc::new(LanceIndexStore::new(
1729 Arc::new(ObjectStore::local()),
1730 tmpdir.clone(),
1731 Arc::new(LanceCache::no_cache()),
1732 ));
1733
1734 let large_string_values: Vec<String> =
1736 (0..100).map(|i| format!("large_value_{:05}", i)).collect();
1737 let large_string_data =
1738 arrow_array::LargeStringArray::from_iter_values(large_string_values.iter());
1739 let schema = Arc::new(Schema::new(vec![Field::new(
1740 VALUE_COLUMN_NAME,
1741 DataType::LargeUtf8,
1742 false,
1743 )]));
1744 let data = RecordBatch::try_new(schema.clone(), vec![Arc::new(large_string_data)]).unwrap();
1745 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1746 schema,
1747 stream::once(std::future::ready(Ok(data))),
1748 ));
1749 let data_stream = add_row_addr(data_stream);
1750
1751 BloomFilterIndexPlugin::train_bloomfilter_index(
1752 data_stream,
1753 test_store.as_ref(),
1754 Some(BloomFilterIndexBuilderParams::new(50, 0.05)),
1755 )
1756 .await
1757 .unwrap();
1758
1759 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1761 .await
1762 .expect("Failed to load BloomFilterIndex");
1763
1764 assert_eq!(index.zones.len(), 2);
1765
1766 let query = BloomFilterQuery::Equals(ScalarValue::LargeUtf8(Some(
1768 "large_value_00025".to_string(),
1769 )));
1770 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1771
1772 let mut expected = RowAddrTreeMap::new();
1774 expected.insert_range(0..50);
1775 assert_eq!(result, SearchResult::at_most(expected));
1776
1777 let query = BloomFilterQuery::Equals(ScalarValue::LargeUtf8(Some(
1779 "nonexistent_large_value".to_string(),
1780 )));
1781 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1782
1783 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1785 }
1786
1787 #[tokio::test]
1788 async fn test_timestamp_bloomfilter_index() {
1789 let tmpdir = TempObjDir::default();
1790 let test_store = Arc::new(LanceIndexStore::new(
1791 Arc::new(ObjectStore::local()),
1792 tmpdir.clone(),
1793 Arc::new(LanceCache::no_cache()),
1794 ));
1795
1796 let date32_values: Vec<i32> = (0..100).collect(); let date32_data = arrow_array::Date32Array::from(date32_values.clone());
1799 let schema = Arc::new(Schema::new(vec![Field::new(
1800 VALUE_COLUMN_NAME,
1801 DataType::Date32,
1802 false,
1803 )]));
1804 let data = RecordBatch::try_new(schema.clone(), vec![Arc::new(date32_data)]).unwrap();
1805 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1806 schema,
1807 stream::once(std::future::ready(Ok(data))),
1808 ));
1809 let data_stream = add_row_addr(data_stream);
1810
1811 BloomFilterIndexPlugin::train_bloomfilter_index(
1812 data_stream,
1813 test_store.as_ref(),
1814 Some(BloomFilterIndexBuilderParams::new(50, 0.01)),
1815 )
1816 .await
1817 .unwrap();
1818
1819 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1821 .await
1822 .expect("Failed to load Date32 BloomFilterIndex");
1823
1824 assert_eq!(index.zones.len(), 2); let query = BloomFilterQuery::Equals(ScalarValue::Date32(Some(25)));
1828 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1829 let mut expected = RowAddrTreeMap::new();
1830 expected.insert_range(0..50);
1831 assert_eq!(result, SearchResult::at_most(expected));
1832
1833 let query = BloomFilterQuery::Equals(ScalarValue::Date32(Some(75)));
1835 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1836 let mut expected = RowAddrTreeMap::new();
1837 expected.insert_range(50..100);
1838 assert_eq!(result, SearchResult::at_most(expected));
1839
1840 let query = BloomFilterQuery::Equals(ScalarValue::Date32(Some(500)));
1842 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1843 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1844 }
1845
1846 #[tokio::test]
1847 async fn test_timestamp_types_bloomfilter_index() {
1848 let tmpdir = TempObjDir::default();
1849 let test_store = Arc::new(LanceIndexStore::new(
1850 Arc::new(ObjectStore::local()),
1851 tmpdir.clone(),
1852 Arc::new(LanceCache::no_cache()),
1853 ));
1854
1855 let timestamp_values: Vec<i64> = (0..100).map(|i| 1_000_000_000i64 + (i as i64)).collect();
1857
1858 let timestamp_data = arrow_array::TimestampNanosecondArray::from(timestamp_values.clone());
1859 let schema = Arc::new(Schema::new(vec![Field::new(
1860 VALUE_COLUMN_NAME,
1861 DataType::Timestamp(arrow_schema::TimeUnit::Nanosecond, None),
1862 false,
1863 )]));
1864 let data = RecordBatch::try_new(schema.clone(), vec![Arc::new(timestamp_data)]).unwrap();
1865 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1866 schema,
1867 stream::once(std::future::ready(Ok(data))),
1868 ));
1869 let data_stream = add_row_addr(data_stream);
1870
1871 BloomFilterIndexPlugin::train_bloomfilter_index(
1872 data_stream,
1873 test_store.as_ref(),
1874 Some(BloomFilterIndexBuilderParams::new(50, 0.01)),
1875 )
1876 .await
1877 .unwrap();
1878
1879 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1881 .await
1882 .expect("Failed to load Timestamp BloomFilterIndex");
1883
1884 assert_eq!(index.zones.len(), 2); let first_timestamp = timestamp_values[25];
1888 let query = BloomFilterQuery::Equals(ScalarValue::TimestampNanosecond(
1889 Some(first_timestamp),
1890 None,
1891 ));
1892 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1893 let mut expected = RowAddrTreeMap::new();
1894 expected.insert_range(0..50);
1895 assert_eq!(result, SearchResult::at_most(expected));
1896
1897 let second_timestamp = timestamp_values[75];
1899 let query = BloomFilterQuery::Equals(ScalarValue::TimestampNanosecond(
1900 Some(second_timestamp),
1901 None,
1902 ));
1903 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1904 let mut expected = RowAddrTreeMap::new();
1905 expected.insert_range(50..100);
1906 assert_eq!(result, SearchResult::at_most(expected));
1907
1908 let query =
1910 BloomFilterQuery::Equals(ScalarValue::TimestampNanosecond(Some(999_999_999i64), None));
1911 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1912 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1913
1914 let query = BloomFilterQuery::IsIn(vec![
1916 ScalarValue::TimestampNanosecond(Some(timestamp_values[10]), None), ScalarValue::TimestampNanosecond(Some(timestamp_values[85]), None), ScalarValue::TimestampNanosecond(Some(999_999_999i64), None), ]);
1920 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1921 let mut expected = RowAddrTreeMap::new();
1922 expected.insert_range(0..100); assert_eq!(result, SearchResult::at_most(expected));
1924 }
1925
1926 #[tokio::test]
1927 async fn test_time_types_bloomfilter_index() {
1928 let tmpdir = TempObjDir::default();
1929 let test_store = Arc::new(LanceIndexStore::new(
1930 Arc::new(ObjectStore::local()),
1931 tmpdir.clone(),
1932 Arc::new(LanceCache::no_cache()),
1933 ));
1934
1935 let time_values: Vec<i64> = (0..100)
1937 .map(|i| (i as i64) * 3_600_000_000) .collect();
1939
1940 let time_data = arrow_array::Time64MicrosecondArray::from(time_values.clone());
1941 let schema = Arc::new(Schema::new(vec![Field::new(
1942 VALUE_COLUMN_NAME,
1943 DataType::Time64(arrow_schema::TimeUnit::Microsecond),
1944 false,
1945 )]));
1946 let data = RecordBatch::try_new(schema.clone(), vec![Arc::new(time_data)]).unwrap();
1947 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1948 schema,
1949 stream::once(std::future::ready(Ok(data))),
1950 ));
1951 let data_stream = add_row_addr(data_stream);
1952
1953 BloomFilterIndexPlugin::train_bloomfilter_index(
1954 data_stream,
1955 test_store.as_ref(),
1956 Some(BloomFilterIndexBuilderParams::new(25, 0.05)),
1957 )
1958 .await
1959 .unwrap();
1960
1961 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1963 .await
1964 .expect("Failed to load Time64 BloomFilterIndex");
1965
1966 assert_eq!(index.zones.len(), 4); let first_time = time_values[10];
1970 let query = BloomFilterQuery::Equals(ScalarValue::Time64Microsecond(Some(first_time)));
1971 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1972 let mut expected = RowAddrTreeMap::new();
1973 expected.insert_range(0..25);
1974 assert_eq!(result, SearchResult::at_most(expected));
1975
1976 let query = BloomFilterQuery::Equals(ScalarValue::Time64Microsecond(Some(999_999_999i64)));
1978 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1979 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1980 }
1981
1982 #[tokio::test]
1983 async fn test_bloomfilter_supported_operations() {
1984 let tmpdir = TempObjDir::default();
1985 let test_store = Arc::new(LanceIndexStore::new(
1986 Arc::new(ObjectStore::local()),
1987 tmpdir.clone(),
1988 Arc::new(LanceCache::no_cache()),
1989 ));
1990
1991 let data = arrow_array::Int32Array::from_iter_values(0..1000);
1992 let schema = Arc::new(Schema::new(vec![Field::new(
1993 VALUE_COLUMN_NAME,
1994 DataType::Int32,
1995 false,
1996 )]));
1997 let data = RecordBatch::try_new(schema.clone(), vec![Arc::new(data)]).unwrap();
1998 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1999 schema,
2000 stream::once(std::future::ready(Ok(data))),
2001 ));
2002 let data_stream = add_row_addr(data_stream);
2003
2004 BloomFilterIndexPlugin::train_bloomfilter_index(
2005 data_stream,
2006 test_store.as_ref(),
2007 Some(BloomFilterIndexBuilderParams::new(250, 0.01)), )
2009 .await
2010 .unwrap();
2011
2012 let index = BloomFilterIndex::load(test_store.clone(), None, &LanceCache::no_cache())
2014 .await
2015 .expect("Failed to load BloomFilterIndex");
2016
2017 assert_eq!(index.zones.len(), 4);
2018
2019 let query = BloomFilterQuery::Equals(ScalarValue::Int32(Some(500)));
2022 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2023 let mut expected = RowAddrTreeMap::new();
2024 expected.insert_range(500..750); assert_eq!(result, SearchResult::at_most(expected));
2026
2027 let query = BloomFilterQuery::IsNull();
2029 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2030 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new())); let query = BloomFilterQuery::IsIn(vec![
2034 ScalarValue::Int32(Some(100)),
2035 ScalarValue::Int32(Some(600)),
2036 ]);
2037 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2038 let mut expected = RowAddrTreeMap::new();
2039 expected.insert_range(0..250); expected.insert_range(500..750); assert_eq!(result, SearchResult::at_most(expected));
2042 }
2043
2044 #[tokio::test]
2045 async fn test_bloomfilter_null_handling_in_queries() {
2046 let tmpdir = TempObjDir::default();
2048 let store = Arc::new(LanceIndexStore::new(
2049 Arc::new(ObjectStore::local()),
2050 tmpdir.clone(),
2051 Arc::new(LanceCache::no_cache()),
2052 ));
2053
2054 let batch = record_batch!(
2056 (VALUE_COLUMN_NAME, Int64, [Some(0), Some(5), None]),
2057 (ROW_ADDR, UInt64, [0, 1, 2])
2058 )
2059 .unwrap();
2060 let schema = batch.schema();
2061 let stream = stream::once(async move { Ok(batch) });
2062 let stream = Box::pin(RecordBatchStreamAdapter::new(schema, stream));
2063
2064 BloomFilterIndexPlugin::train_bloomfilter_index(stream, store.as_ref(), None)
2066 .await
2067 .unwrap();
2068
2069 let cache = LanceCache::with_capacity(1024 * 1024);
2070 let index = BloomFilterIndex::load(store.clone(), None, &cache)
2071 .await
2072 .unwrap();
2073
2074 let query = BloomFilterQuery::Equals(ScalarValue::Int64(Some(5)));
2077 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2078
2079 match result {
2080 SearchResult::AtMost(row_addrs) => {
2081 let all_rows: Vec<u64> = row_addrs
2083 .true_rows()
2084 .row_addrs()
2085 .unwrap()
2086 .map(u64::from)
2087 .collect();
2088 assert_eq!(
2089 all_rows,
2090 vec![0, 1, 2],
2091 "Should return all rows (including nulls) since BloomFilter is inexact"
2092 );
2093
2094 }
2096 _ => panic!("Expected AtMost search result from bloomfilter"),
2097 }
2098
2099 let query = BloomFilterQuery::IsIn(vec![
2101 ScalarValue::Int64(Some(0)),
2102 ScalarValue::Int64(Some(10)),
2103 ]);
2104 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2105
2106 match result {
2107 SearchResult::AtMost(row_addrs) => {
2108 let all_rows: Vec<u64> = row_addrs
2109 .true_rows()
2110 .row_addrs()
2111 .unwrap()
2112 .map(u64::from)
2113 .collect();
2114 assert_eq!(
2115 all_rows,
2116 vec![0, 1, 2],
2117 "Should return all rows in zone as possible matches"
2118 );
2119 }
2120 _ => panic!("Expected AtMost search result from bloomfilter"),
2121 }
2122 }
2123}