1use lance_core::utils::row_addr_remap::RowAddrRemap;
5use std::{
6 any::Any,
7 cmp::Reverse,
8 collections::{BTreeMap, BinaryHeap, HashMap},
9 fmt::Debug,
10 ops::Bound,
11 sync::Arc,
12};
13
14use arrow::array::BinaryBuilder;
15use arrow_array::{Array, BinaryArray, RecordBatch, UInt64Array, new_null_array};
16use arrow_schema::{DataType, Field, Schema};
17use async_trait::async_trait;
18use bytes::Bytes;
19use datafusion::physical_plan::SendableRecordBatchStream;
20use datafusion_common::ScalarValue;
21use futures::{StreamExt, TryStreamExt, stream};
22use lance_core::deepsize::DeepSizeOf;
23use lance_core::{
24 Error, ROW_ID, Result,
25 cache::{
26 CacheCodec, CacheCodecImpl, CacheEntryReader, CacheEntryWriter, CacheKey, LanceCache,
27 WeakLanceCache,
28 },
29 error::LanceOptionExt,
30 utils::tokio::get_num_compute_intensive_cpus,
31};
32use lance_io::object_store::ObjectStore;
33use lance_select::{NullableRowAddrSet, RowAddrTreeMap, RowSetOps};
34use object_store::path::Path;
35use roaring::RoaringBitmap;
36use serde::{Deserialize, Serialize};
37use tracing::{instrument, warn};
38
39use super::{AnyQuery, IndexFile, IndexStore, ScalarIndex};
40use super::{
41 BuiltinIndexType, SargableQuery, ScalarIndexParams, SearchResult, btree::OrderableScalarValue,
42};
43use crate::pbold;
44use crate::{Index, IndexType, metrics::MetricsCollector};
45use crate::{
46 progress::IndexBuildProgress,
47 scalar::{
48 CreatedIndex, RowIdRemapper, UpdateCriteria,
49 expression::SargableQueryParser,
50 registry::{
51 BasicTrainer, ScalarIndexLoad, ScalarIndexPlugin, TrainingCriteria, TrainingOrdering,
52 TrainingRequest, VALUE_COLUMN_NAME, single_flight_open,
53 },
54 },
55};
56use crate::{scalar::IndexReader, scalar::expression::ScalarQueryParser};
57
58pub const BITMAP_LOOKUP_NAME: &str = "bitmap_page_lookup.lance";
59pub const INDEX_STATS_METADATA_KEY: &str = "lance:index_stats";
60const BITMAP_PART_LOOKUP_PREFIX: &str = "part_";
61const BITMAP_PART_LOOKUP_SUFFIX: &str = "_bitmap_page_lookup.lance";
62const EXPLICIT_SHARD_ID_TAG: u64 = 0;
63const IMPLICIT_FRAGMENT_ID_TAG: u64 = 1;
64
65const MAX_BITMAP_ARRAY_LENGTH: usize = i32::MAX as usize - 1024 * 1024; const MAX_ROWS_PER_CHUNK: usize = 2 * 1024;
68const MERGE_ROWS_PER_CHUNK: usize = 512;
72
73const BITMAP_INDEX_VERSION: u32 = 0;
74
75#[derive(Clone)]
78struct LazyIndexReader {
79 index_reader: Arc<tokio::sync::Mutex<Option<Arc<dyn IndexReader>>>>,
80 store: Arc<dyn IndexStore>,
81}
82
83impl std::fmt::Debug for LazyIndexReader {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 f.debug_struct("LazyIndexReader")
86 .field("store", &self.store)
87 .finish()
88 }
89}
90
91impl LazyIndexReader {
92 fn new(store: Arc<dyn IndexStore>) -> Self {
93 Self {
94 index_reader: Arc::new(tokio::sync::Mutex::new(None)),
95 store,
96 }
97 }
98
99 async fn get(&self) -> Result<Arc<dyn IndexReader>> {
100 let mut reader = self.index_reader.lock().await;
101 if reader.is_none() {
102 let index_reader = self.store.open_index_file(BITMAP_LOOKUP_NAME).await?;
103 *reader = Some(index_reader);
104 }
105 Ok(reader.as_ref().unwrap().clone())
106 }
107}
108
109#[derive(Clone, Debug)]
114pub struct BitmapIndex {
115 index_map: Arc<BTreeMap<OrderableScalarValue, usize>>,
119
120 null_map: Arc<RowAddrTreeMap>,
121
122 value_type: DataType,
123
124 store: Arc<dyn IndexStore>,
125
126 index_cache: WeakLanceCache,
127
128 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
129
130 lazy_reader: LazyIndexReader,
131}
132
133#[derive(Debug, Clone)]
134pub struct BitmapKey {
135 value: OrderableScalarValue,
136}
137
138impl CacheKey for BitmapKey {
139 type ValueType = RowAddrTreeMap;
140
141 fn key(&self) -> std::borrow::Cow<'_, str> {
142 format!("{}", self.value.0).into()
143 }
144
145 fn type_name() -> &'static str {
146 "Bitmap"
147 }
148
149 fn codec() -> Option<CacheCodec> {
150 Some(CacheCodec::from_impl::<RowAddrTreeMap>())
151 }
152}
153
154#[derive(Debug, Clone)]
161pub struct BitmapIndexState {
162 lookup_batch: RecordBatch,
169 null_map: Arc<RowAddrTreeMap>,
172 value_type: DataType,
175 index_map: Arc<BTreeMap<OrderableScalarValue, usize>>,
179}
180
181impl DeepSizeOf for BitmapIndexState {
182 fn deep_size_of_children(&self, context: &mut lance_core::deepsize::Context) -> usize {
183 self.lookup_batch.get_array_memory_size()
184 + self.null_map.deep_size_of_children(context)
185 + self.index_map.deep_size_of_children(context)
186 }
187}
188
189impl BitmapIndexState {
190 pub(crate) fn from_index(index: &BitmapIndex) -> Result<Self> {
191 Ok(Self {
192 lookup_batch: build_lookup_batch(&index.index_map, &index.value_type)?,
193 null_map: index.null_map.clone(),
194 value_type: index.value_type.clone(),
195 index_map: index.index_map.clone(),
196 })
197 }
198
199 fn from_scalar_index(index: &dyn ScalarIndex) -> Result<Self> {
200 let bitmap = index
201 .as_any()
202 .downcast_ref::<BitmapIndex>()
203 .ok_or_else(|| {
204 Error::internal(
205 "BitmapIndexState::from_scalar_index called with a non-bitmap index",
206 )
207 })?;
208 Self::from_index(bitmap)
209 }
210
211 pub(crate) fn to_bitmap_index(
212 &self,
213 store: Arc<dyn IndexStore>,
214 index_cache: &LanceCache,
215 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
216 ) -> Result<Arc<BitmapIndex>> {
217 Ok(Arc::new(BitmapIndex::new(
218 self.index_map.clone(),
219 self.null_map.clone(),
220 self.value_type.clone(),
221 store,
222 WeakLanceCache::from(index_cache),
223 frag_reuse_index,
224 )))
225 }
226
227 #[cfg(test)]
230 pub(crate) fn new_for_test(
231 index_map: BTreeMap<OrderableScalarValue, usize>,
232 null_map: RowAddrTreeMap,
233 value_type: DataType,
234 ) -> Result<Self> {
235 Ok(Self {
236 lookup_batch: build_lookup_batch(&index_map, &value_type)?,
237 null_map: Arc::new(null_map),
238 value_type,
239 index_map: Arc::new(index_map),
240 })
241 }
242
243 #[cfg(test)]
244 pub(crate) fn lookup_batch(&self) -> &RecordBatch {
245 &self.lookup_batch
246 }
247
248 #[cfg(test)]
249 pub(crate) fn null_map(&self) -> &RowAddrTreeMap {
250 &self.null_map
251 }
252}
253
254fn build_lookup_batch(
255 index_map: &BTreeMap<OrderableScalarValue, usize>,
256 value_type: &DataType,
257) -> Result<RecordBatch> {
258 let keys = if index_map.is_empty() {
259 arrow_array::new_empty_array(value_type)
260 } else {
261 ScalarValue::iter_to_array(index_map.keys().map(|k| k.0.clone()))?
262 };
263 let offsets = Arc::new(UInt64Array::from_iter_values(
264 index_map.values().map(|v| *v as u64),
265 ));
266 let schema = Arc::new(Schema::new(vec![
267 Field::new("keys", value_type.clone(), true),
268 Field::new("offsets", DataType::UInt64, false),
269 ]));
270 Ok(RecordBatch::try_new(schema, vec![keys, offsets])?)
271}
272
273fn parse_lookup_batch(batch: &RecordBatch) -> Result<BTreeMap<OrderableScalarValue, usize>> {
274 let keys = batch.column(0);
275 let offsets = batch
276 .column(1)
277 .as_any()
278 .downcast_ref::<UInt64Array>()
279 .ok_or_else(|| {
280 Error::internal("BitmapIndexState: expected UInt64 offsets column".to_string())
281 })?;
282 let mut index_map = BTreeMap::new();
283 for idx in 0..batch.num_rows() {
284 let value = OrderableScalarValue(ScalarValue::try_from_array(keys, idx)?);
285 index_map.insert(value, offsets.value(idx) as usize);
286 }
287 Ok(index_map)
288}
289
290impl CacheCodecImpl for BitmapIndexState {
291 const TYPE_ID: &'static str = "lance.scalar.BitmapIndexState";
292 const CURRENT_VERSION: u32 = 1;
293
294 fn serialize(&self, w: &mut CacheEntryWriter<'_>) -> Result<()> {
301 let mut null_bytes = Vec::with_capacity(self.null_map.serialized_size());
302 self.null_map.serialize_into(&mut null_bytes)?;
303 w.write_raw(&null_bytes)?;
304 w.write_ipc(&self.lookup_batch)?;
305 Ok(())
306 }
307
308 fn deserialize(r: &mut CacheEntryReader<'_>) -> Result<Self> {
309 let null_bytes = r.read_raw()?;
310 let null_map = Arc::new(RowAddrTreeMap::deserialize_from(null_bytes.as_ref())?);
311 let lookup_batch = r.read_ipc()?;
312 let value_type = lookup_batch.schema().field(0).data_type().clone();
313 let index_map = Arc::new(parse_lookup_batch(&lookup_batch)?);
314 Ok(Self {
315 lookup_batch,
316 null_map,
317 value_type,
318 index_map,
319 })
320 }
321}
322
323struct BitmapIndexStateKey;
326
327impl CacheKey for BitmapIndexStateKey {
328 type ValueType = BitmapIndexState;
329
330 fn key(&self) -> std::borrow::Cow<'_, str> {
331 "state".into()
332 }
333
334 fn type_name() -> &'static str {
335 "BitmapIndexState"
336 }
337
338 fn codec() -> Option<CacheCodec> {
339 Some(CacheCodec::from_impl::<BitmapIndexState>())
340 }
341}
342
343impl BitmapIndex {
344 fn new(
345 index_map: Arc<BTreeMap<OrderableScalarValue, usize>>,
346 null_map: Arc<RowAddrTreeMap>,
347 value_type: DataType,
348 store: Arc<dyn IndexStore>,
349 index_cache: WeakLanceCache,
350 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
351 ) -> Self {
352 let lazy_reader = LazyIndexReader::new(store.clone());
353 Self {
354 index_map,
355 null_map,
356 value_type,
357 store,
358 index_cache,
359 frag_reuse_index,
360 lazy_reader,
361 }
362 }
363
364 pub(crate) async fn load(
365 store: Arc<dyn IndexStore>,
366 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
367 index_cache: &LanceCache,
368 ) -> Result<Arc<Self>> {
369 let page_lookup_file = store.open_index_file(BITMAP_LOOKUP_NAME).await?;
370 let total_rows = page_lookup_file.num_rows();
371
372 if total_rows == 0 {
373 let schema = page_lookup_file.schema();
374 let data_type = schema.fields[0].data_type();
375 return Ok(Arc::new(Self::new(
376 Arc::new(BTreeMap::new()),
377 Arc::new(RowAddrTreeMap::default()),
378 data_type,
379 store,
380 WeakLanceCache::from(index_cache),
381 frag_reuse_index,
382 )));
383 }
384
385 let mut index_map: BTreeMap<OrderableScalarValue, usize> = BTreeMap::new();
386 let mut null_map = Arc::new(RowAddrTreeMap::default());
387 let mut null_location: Option<usize> = None;
388 let value_type = page_lookup_file.schema().fields[0].data_type();
389
390 let mut keys_stream = page_lookup_file
393 .read_range_stream(0..total_rows, Some(&["keys"]))
394 .await?;
395 let mut row_offset: usize = 0;
396 while let Some(keys_batch) = keys_stream.try_next().await? {
397 let dict_keys = keys_batch.column(0);
398 for idx in 0..keys_batch.num_rows() {
399 let key = OrderableScalarValue(ScalarValue::try_from_array(dict_keys, idx)?);
400 if key.0.is_null() {
401 null_location = Some(row_offset);
402 } else {
403 index_map.insert(key, row_offset);
404 }
405 row_offset += 1;
406 }
407 }
408
409 if let Some(null_loc) = null_location {
410 let batch = page_lookup_file
411 .read_range(null_loc..null_loc + 1, Some(&["bitmaps"]))
412 .await?;
413
414 let binary_bitmaps = batch
415 .column(0)
416 .as_any()
417 .downcast_ref::<BinaryArray>()
418 .ok_or_else(|| Error::internal("Invalid bitmap column type".to_string()))?;
419 let bitmap_bytes = binary_bitmaps.value(0);
420 let mut bitmap = RowAddrTreeMap::deserialize_from(bitmap_bytes).unwrap();
421
422 if let Some(fri) = &frag_reuse_index {
424 bitmap = fri.remap_row_addrs_tree_map(&bitmap);
425 }
426
427 null_map = Arc::new(bitmap);
428 }
429
430 Ok(Arc::new(Self::new(
431 Arc::new(index_map),
432 null_map,
433 value_type,
434 store,
435 WeakLanceCache::from(index_cache),
436 frag_reuse_index,
437 )))
438 }
439
440 async fn load_bitmap(
441 &self,
442 key: &OrderableScalarValue,
443 metrics: Option<&dyn MetricsCollector>,
444 ) -> Result<Arc<RowAddrTreeMap>> {
445 if key.0.is_null() {
446 return Ok(self.null_map.clone());
447 }
448
449 let cache_key = BitmapKey { value: key.clone() };
450
451 if let Some(cached) = self.index_cache.get_with_key(&cache_key).await {
452 return Ok(cached);
453 }
454
455 if let Some(metrics) = metrics {
457 metrics.record_part_load();
458 }
459
460 let row_offset = match self.index_map.get(key) {
461 Some(loc) => *loc,
462 None => return Ok(Arc::new(RowAddrTreeMap::default())),
463 };
464
465 let page_lookup_file = self.lazy_reader.get().await?;
466 let batch = page_lookup_file
467 .read_range(row_offset..row_offset + 1, Some(&["bitmaps"]))
468 .await?;
469
470 let binary_bitmaps = batch
471 .column(0)
472 .as_any()
473 .downcast_ref::<BinaryArray>()
474 .ok_or_else(|| Error::internal("Invalid bitmap column type".to_string()))?;
475 let bitmap_bytes = binary_bitmaps.value(0); let mut bitmap = RowAddrTreeMap::deserialize_from(bitmap_bytes).unwrap();
477
478 if let Some(fri) = &self.frag_reuse_index {
479 bitmap = fri.remap_row_addrs_tree_map(&bitmap);
480 }
481
482 self.index_cache
483 .insert_with_key(&cache_key, Arc::new(bitmap.clone()))
484 .await;
485
486 Ok(Arc::new(bitmap))
487 }
488
489 pub(crate) fn value_type(&self) -> &DataType {
490 &self.value_type
491 }
492
493 pub(crate) async fn load_bitmap_index_state(
495 &self,
496 ) -> Result<HashMap<ScalarValue, RowAddrTreeMap>> {
497 let mut state = HashMap::new();
498
499 for key in self.index_map.keys() {
500 let bitmap = self.load_bitmap(key, None).await?;
501 state.insert(key.0.clone(), (*bitmap).clone());
502 }
503
504 if !self.null_map.is_empty() {
505 let existing_null = new_null_array(&self.value_type, 1);
506 let existing_null = ScalarValue::try_from_array(existing_null.as_ref(), 0)?;
507 state.insert(existing_null, (*self.null_map).clone());
508 }
509
510 Ok(state)
511 }
512}
513
514impl DeepSizeOf for BitmapIndex {
515 fn deep_size_of_children(&self, context: &mut lance_core::deepsize::Context) -> usize {
516 self.index_map.deep_size_of_children(context) + self.store.deep_size_of_children(context)
517 }
518}
519
520#[derive(Serialize)]
521struct BitmapStatistics {
522 num_bitmaps: usize,
523}
524
525#[derive(Debug, Clone, Default, Serialize, Deserialize)]
526pub struct BitmapParameters {
527 pub shard_id: Option<u32>,
530}
531
532struct BitmapTrainingRequest {
533 parameters: BitmapParameters,
534 criteria: TrainingCriteria,
535}
536
537impl BitmapTrainingRequest {
538 fn new(parameters: BitmapParameters) -> Self {
539 Self {
540 parameters,
541 criteria: TrainingCriteria::new(TrainingOrdering::Values).with_row_id(),
542 }
543 }
544}
545
546impl TrainingRequest for BitmapTrainingRequest {
547 fn as_any(&self) -> &dyn std::any::Any {
548 self
549 }
550
551 fn criteria(&self) -> &TrainingCriteria {
552 &self.criteria
553 }
554}
555
556#[async_trait]
557impl Index for BitmapIndex {
558 fn as_any(&self) -> &dyn Any {
559 self
560 }
561
562 fn as_index(self: Arc<Self>) -> Arc<dyn Index> {
563 self
564 }
565
566 async fn prewarm(&self) -> Result<()> {
567 let page_lookup_file = self.lazy_reader.get().await?;
568 let total_rows = page_lookup_file.num_rows();
569
570 if total_rows == 0 {
571 return Ok(());
572 }
573
574 for start_row in (0..total_rows).step_by(MAX_ROWS_PER_CHUNK) {
575 let end_row = (start_row + MAX_ROWS_PER_CHUNK).min(total_rows);
576 let chunk = page_lookup_file
577 .read_range(start_row..end_row, None)
578 .await?;
579
580 if chunk.num_rows() == 0 {
581 continue;
582 }
583
584 let dict_keys = chunk.column(0);
585 let binary_bitmaps = chunk.column(1);
586 let bitmap_binary_array = binary_bitmaps
587 .as_any()
588 .downcast_ref::<BinaryArray>()
589 .unwrap();
590
591 for idx in 0..chunk.num_rows() {
592 let key = OrderableScalarValue(ScalarValue::try_from_array(dict_keys, idx)?);
593
594 if key.0.is_null() {
595 continue;
596 }
597
598 let bitmap_bytes = bitmap_binary_array.value(idx);
599 let mut bitmap = RowAddrTreeMap::deserialize_from(bitmap_bytes).unwrap();
600
601 if let Some(frag_reuse_index_ref) = self.frag_reuse_index.as_ref() {
602 bitmap = frag_reuse_index_ref.remap_row_addrs_tree_map(&bitmap);
603 }
604
605 let cache_key = BitmapKey { value: key };
606 self.index_cache
607 .insert_with_key(&cache_key, Arc::new(bitmap))
608 .await;
609 }
610 }
611
612 Ok(())
613 }
614
615 fn index_type(&self) -> IndexType {
616 IndexType::Bitmap
617 }
618
619 fn statistics(&self) -> Result<serde_json::Value> {
620 let stats = BitmapStatistics {
621 num_bitmaps: self.index_map.len() + if !self.null_map.is_empty() { 1 } else { 0 },
622 };
623 serde_json::to_value(stats).map_err(|e| {
624 Error::internal(format!(
625 "failed to serialize bitmap index statistics: {}",
626 e
627 ))
628 })
629 }
630
631 async fn calculate_included_frags(&self) -> Result<RoaringBitmap> {
632 unimplemented!()
633 }
634}
635
636#[async_trait]
637impl ScalarIndex for BitmapIndex {
638 #[instrument(name = "bitmap_search", level = "debug", skip_all)]
639 async fn search(
640 &self,
641 query: &dyn AnyQuery,
642 metrics: &dyn MetricsCollector,
643 ) -> Result<SearchResult> {
644 let query = query.as_any().downcast_ref::<SargableQuery>().unwrap();
645
646 let (row_ids, null_row_ids) = match query {
647 SargableQuery::Equals(val) => {
648 metrics.record_comparisons(1);
649 if val.is_null() {
650 ((*self.null_map).clone(), None)
652 } else {
653 let key = OrderableScalarValue(val.clone());
654 let bitmap = self.load_bitmap(&key, Some(metrics)).await?;
655 let null_rows = if !self.null_map.is_empty() {
656 Some((*self.null_map).clone())
657 } else {
658 None
659 };
660 ((*bitmap).clone(), null_rows)
661 }
662 }
663 SargableQuery::Range(start, end) => {
664 let range_start = match start {
665 Bound::Included(val) => Bound::Included(OrderableScalarValue(val.clone())),
666 Bound::Excluded(val) => Bound::Excluded(OrderableScalarValue(val.clone())),
667 Bound::Unbounded => Bound::Unbounded,
668 };
669
670 let range_end = match end {
671 Bound::Included(val) => Bound::Included(OrderableScalarValue(val.clone())),
672 Bound::Excluded(val) => Bound::Excluded(OrderableScalarValue(val.clone())),
673 Bound::Unbounded => Bound::Unbounded,
674 };
675
676 let empty_range = match (&range_start, &range_end) {
678 (Bound::Included(lower), Bound::Included(upper)) => lower > upper,
679 (Bound::Included(lower), Bound::Excluded(upper))
680 | (Bound::Excluded(lower), Bound::Included(upper))
681 | (Bound::Excluded(lower), Bound::Excluded(upper)) => lower >= upper,
682 _ => false,
683 };
684
685 let keys: Vec<_> = if empty_range {
686 Vec::new()
687 } else {
688 self.index_map
689 .range((range_start, range_end))
690 .map(|(k, _v)| k.clone())
691 .collect()
692 };
693
694 metrics.record_comparisons(keys.len());
695
696 let result = if keys.is_empty() {
697 RowAddrTreeMap::default()
698 } else {
699 let bitmaps: Vec<_> = stream::iter(
700 keys.into_iter()
701 .map(|key| async move { self.load_bitmap(&key, None).await }),
702 )
703 .buffer_unordered(get_num_compute_intensive_cpus())
704 .try_collect()
705 .await?;
706
707 let bitmap_refs: Vec<_> = bitmaps.iter().map(|b| b.as_ref()).collect();
708 RowAddrTreeMap::union_all(&bitmap_refs)
709 };
710
711 let null_rows = if !self.null_map.is_empty() {
712 Some((*self.null_map).clone())
713 } else {
714 None
715 };
716 (result, null_rows)
717 }
718 SargableQuery::IsIn(values) => {
719 metrics.record_comparisons(values.len());
720
721 let mut has_null = false;
723 let keys: Vec<_> = values
724 .iter()
725 .filter_map(|val| {
726 if val.is_null() {
727 has_null = true;
728 None
729 } else {
730 let key = OrderableScalarValue(val.clone());
731 if self.index_map.contains_key(&key) {
732 Some(key)
733 } else {
734 None
735 }
736 }
737 })
738 .collect();
739
740 let mut bitmaps: Vec<_> = stream::iter(
742 keys.into_iter()
743 .map(|key| async move { self.load_bitmap(&key, None).await }),
744 )
745 .buffer_unordered(get_num_compute_intensive_cpus())
746 .try_collect()
747 .await?;
748
749 if has_null && !self.null_map.is_empty() {
751 bitmaps.push(self.null_map.clone());
752 }
753
754 let result = if bitmaps.is_empty() {
755 RowAddrTreeMap::default()
756 } else {
757 let bitmap_refs: Vec<_> = bitmaps.iter().map(|b| b.as_ref()).collect();
759 RowAddrTreeMap::union_all(&bitmap_refs)
760 };
761
762 let null_rows = if !has_null && !self.null_map.is_empty() {
765 Some((*self.null_map).clone())
766 } else {
767 None
768 };
769 (result, null_rows)
770 }
771 SargableQuery::IsNull() => {
772 metrics.record_comparisons(1);
773 ((*self.null_map).clone(), None)
775 }
776 SargableQuery::FullTextSearch(_) => {
777 return Err(Error::not_supported_source(
778 "full text search is not supported for bitmap indexes".into(),
779 ));
780 }
781 SargableQuery::LikePrefix(_) => {
782 return Err(Error::not_supported_source(
783 "LIKE prefix queries are not supported for bitmap indexes".into(),
784 ));
785 }
786 };
787
788 let selection = NullableRowAddrSet::new(row_ids, null_row_ids.unwrap_or_default());
789 Ok(SearchResult::Exact(selection))
790 }
791
792 fn can_remap(&self) -> bool {
793 true
794 }
795
796 async fn remap(
798 &self,
799 mapping: &RowAddrRemap,
800 dest_store: &dyn IndexStore,
801 ) -> Result<CreatedIndex> {
802 let state = self.load_bitmap_index_state().await?;
803 let remapped_state = BitmapIndexPlugin::remap_bitmap_state(state, mapping);
804 let file =
805 BitmapIndexPlugin::write_bitmap_index(remapped_state, dest_store, &self.value_type)
806 .await?;
807
808 Ok(CreatedIndex {
809 index_details: prost_types::Any::from_msg(&pbold::BitmapIndexDetails::default())
810 .unwrap(),
811 index_version: BITMAP_INDEX_VERSION,
812 files: vec![file],
813 })
814 }
815
816 async fn update(
818 &self,
819 new_data: SendableRecordBatchStream,
820 dest_store: &dyn IndexStore,
821 old_data_filter: Option<super::OldIndexDataFilter>,
822 ) -> Result<CreatedIndex> {
823 let file = BitmapIndexPlugin::streaming_build_and_write(
824 new_data,
825 Some(self),
826 dest_store,
827 BITMAP_LOOKUP_NAME,
828 old_data_filter.as_ref(),
829 )
830 .await?;
831
832 Ok(CreatedIndex {
833 index_details: prost_types::Any::from_msg(&pbold::BitmapIndexDetails::default())
834 .unwrap(),
835 index_version: BITMAP_INDEX_VERSION,
836 files: vec![file],
837 })
838 }
839
840 fn update_criteria(&self) -> UpdateCriteria {
841 UpdateCriteria::only_new_data(TrainingCriteria::new(TrainingOrdering::Values).with_row_id())
842 }
843
844 fn derive_index_params(&self) -> Result<ScalarIndexParams> {
845 Ok(ScalarIndexParams::for_builtin(BuiltinIndexType::Bitmap))
846 }
847}
848
849struct BitmapBatchWriter {
852 file: Box<dyn super::IndexWriter>,
853 keys: Vec<ScalarValue>,
854 serialized: Vec<Vec<u8>>,
855 bytes: usize,
856 num_bitmaps: usize,
857}
858
859impl BitmapBatchWriter {
860 fn new(file: Box<dyn super::IndexWriter>) -> Self {
861 Self {
862 file,
863 keys: Vec::new(),
864 serialized: Vec::new(),
865 bytes: 0,
866 num_bitmaps: 0,
867 }
868 }
869
870 async fn emit(&mut self, key: ScalarValue, bitmap: &RowAddrTreeMap) -> Result<()> {
873 let mut buf = Vec::new();
874 bitmap.serialize_into(&mut buf).unwrap();
875 let size = buf.len();
876
877 if self.bytes + size > MAX_BITMAP_ARRAY_LENGTH {
878 self.flush().await?;
879 }
880
881 self.keys.push(key);
882 self.serialized.push(buf);
883 self.bytes += size;
884 self.num_bitmaps += 1;
885 Ok(())
886 }
887
888 async fn flush(&mut self) -> Result<()> {
890 if self.keys.is_empty() {
891 return Ok(());
892 }
893 let keys_array =
894 ScalarValue::iter_to_array(self.keys.drain(..).collect::<Vec<_>>()).unwrap();
895 let total_size: usize = self.serialized.iter().map(|b| b.len()).sum();
896 let mut binary_builder = BinaryBuilder::with_capacity(self.serialized.len(), total_size);
897 for b in self.serialized.drain(..) {
898 binary_builder.append_value(&b);
899 }
900 let bitmaps_array = Arc::new(binary_builder.finish()) as Arc<dyn Array>;
901 let batch = BitmapIndexPlugin::get_batch_from_arrays(keys_array, bitmaps_array)?;
902 self.file.write_record_batch(batch).await?;
903 self.bytes = 0;
904 Ok(())
905 }
906
907 async fn finish(mut self) -> Result<IndexFile> {
909 self.flush().await?;
910 let stats_json = serde_json::to_string(&BitmapStatistics {
911 num_bitmaps: self.num_bitmaps,
912 })
913 .map_err(|e| Error::internal(format!("failed to serialize bitmap statistics: {e}")))?;
914 let mut metadata = HashMap::new();
915 metadata.insert(INDEX_STATS_METADATA_KEY.to_string(), stats_json);
916 self.file.finish_with_metadata(metadata).await
917 }
918}
919
920fn bitmap_shard_file_name(partition_id: u64) -> String {
921 format!("{BITMAP_PART_LOOKUP_PREFIX}{partition_id}{BITMAP_PART_LOOKUP_SUFFIX}")
922}
923
924fn tagged_bitmap_partition_id(id: u32, tag: u64) -> u64 {
925 ((id as u64) << 32) | tag
926}
927
928fn bitmap_shard_partition_id(fragment_ids: &[u32], shard_id: Option<u32>) -> Result<u64> {
929 if fragment_ids.is_empty() {
930 return Err(Error::invalid_input(
931 "Bitmap shard build requires at least one fragment id".to_string(),
932 ));
933 }
934
935 if let Some(shard_id) = shard_id {
936 return Ok(tagged_bitmap_partition_id(shard_id, EXPLICIT_SHARD_ID_TAG));
937 }
938
939 let [fragment_id] = fragment_ids else {
940 return Err(Error::invalid_input(format!(
941 "Bitmap distributed build over multiple fragments requires an explicit shard_id. \
942 Received {} fragment ids: {:?}. Please assign mutually exclusive shard_id values \
943 to disjoint fragment groups.",
944 fragment_ids.len(),
945 fragment_ids
946 )));
947 };
948
949 Ok(tagged_bitmap_partition_id(
950 *fragment_id,
951 IMPLICIT_FRAGMENT_ID_TAG,
952 ))
953}
954
955fn extract_bitmap_shard_id(filename: &str) -> Result<u64> {
956 let partition_id = filename
957 .strip_prefix(BITMAP_PART_LOOKUP_PREFIX)
958 .and_then(|name| name.strip_suffix(BITMAP_PART_LOOKUP_SUFFIX))
959 .ok_or_else(|| {
960 Error::internal(format!("Invalid bitmap shard file name format: {filename}"))
961 })?;
962 partition_id.parse::<u64>().map_err(|_| {
963 Error::internal(format!(
964 "Failed to parse bitmap partition id from file name: {filename}"
965 ))
966 })
967}
968
969fn deserialize_bitmap(bitmap_bytes: &[u8], file_name: &str) -> Result<RowAddrTreeMap> {
970 RowAddrTreeMap::deserialize_from(bitmap_bytes).map_err(|error| {
971 Error::corrupt_file(
972 Path::from(file_name),
973 format!("Failed to deserialize bitmap bytes: {error}"),
974 )
975 })
976}
977
978async fn new_bitmap_batch_writer(
979 index_store: &dyn IndexStore,
980 file_name: &str,
981 value_type: &DataType,
982) -> Result<BitmapBatchWriter> {
983 let schema = Arc::new(Schema::new(vec![
984 Field::new("keys", value_type.clone(), true),
985 Field::new("bitmaps", DataType::Binary, true),
986 ]));
987 let index_file = index_store.new_index_file(file_name, schema).await?;
988 Ok(BitmapBatchWriter::new(index_file))
989}
990
991#[derive(Clone, Debug, Eq, PartialEq)]
992struct BitmapHeapItem {
993 key: OrderableScalarValue,
994 shard_idx: usize,
995}
996
997impl Ord for BitmapHeapItem {
998 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
999 self.key
1000 .cmp(&other.key)
1001 .then_with(|| self.shard_idx.cmp(&other.shard_idx))
1002 }
1003}
1004
1005impl PartialOrd for BitmapHeapItem {
1006 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1007 Some(self.cmp(other))
1008 }
1009}
1010
1011struct BitmapShardCursor {
1012 file_name: String,
1013 reader: Arc<dyn IndexReader>,
1014 total_rows: usize,
1015 next_row_offset: usize,
1016 batch: Option<RecordBatch>,
1017 batch_row_idx: usize,
1018}
1019
1020impl BitmapShardCursor {
1021 async fn try_new(file_name: String, reader: Arc<dyn IndexReader>) -> Result<Option<Self>> {
1022 let total_rows = reader.num_rows();
1023 if total_rows == 0 {
1024 return Ok(None);
1025 }
1026
1027 let mut cursor = Self {
1028 file_name,
1029 reader,
1030 total_rows,
1031 next_row_offset: 0,
1032 batch: None,
1033 batch_row_idx: 0,
1034 };
1035 if cursor.advance().await? {
1036 Ok(Some(cursor))
1037 } else {
1038 Ok(None)
1039 }
1040 }
1041
1042 fn peek_key(&self) -> Result<OrderableScalarValue> {
1043 let batch = self.batch.as_ref().ok_or_else(|| {
1044 Error::internal(format!(
1045 "Bitmap shard {} has no active batch",
1046 self.file_name
1047 ))
1048 })?;
1049 let key = ScalarValue::try_from_array(batch.column(0), self.batch_row_idx)?;
1050 Ok(OrderableScalarValue(key))
1051 }
1052
1053 fn take_current(&mut self) -> Result<(ScalarValue, RowAddrTreeMap)> {
1054 let batch = self.batch.as_ref().ok_or_else(|| {
1055 Error::internal(format!(
1056 "Bitmap shard {} has no active batch",
1057 self.file_name
1058 ))
1059 })?;
1060 let keys = batch.column(0);
1061 let binary_bitmaps = batch
1062 .column(1)
1063 .as_any()
1064 .downcast_ref::<BinaryArray>()
1065 .ok_or_else(|| {
1066 Error::corrupt_file(
1067 Path::from(self.file_name.as_str()),
1068 "Bitmap shard batch has non-binary bitmap column".to_string(),
1069 )
1070 })?;
1071 let key = ScalarValue::try_from_array(keys, self.batch_row_idx)?;
1072 let bitmap = deserialize_bitmap(binary_bitmaps.value(self.batch_row_idx), &self.file_name)?;
1073 self.batch_row_idx += 1;
1074 Ok((key, bitmap))
1075 }
1076
1077 async fn advance(&mut self) -> Result<bool> {
1078 loop {
1079 if let Some(batch) = &self.batch
1080 && self.batch_row_idx < batch.num_rows()
1081 {
1082 return Ok(true);
1083 }
1084
1085 if self.next_row_offset >= self.total_rows {
1086 self.batch = None;
1087 return Ok(false);
1088 }
1089
1090 let end_row = (self.next_row_offset + MERGE_ROWS_PER_CHUNK).min(self.total_rows);
1091 let batch = self
1092 .reader
1093 .read_range(self.next_row_offset..end_row, None)
1094 .await?;
1095 self.next_row_offset = end_row;
1096 self.batch = Some(batch);
1097 self.batch_row_idx = 0;
1098 }
1099 }
1100}
1101
1102async fn advance_cursor_and_push(
1103 cursors: &mut [BitmapShardCursor],
1104 heap: &mut BinaryHeap<Reverse<BitmapHeapItem>>,
1105 shard_idx: usize,
1106) -> Result<()> {
1107 if cursors[shard_idx].advance().await? {
1108 heap.push(Reverse(BitmapHeapItem {
1109 key: cursors[shard_idx].peek_key()?,
1110 shard_idx,
1111 }));
1112 }
1113 Ok(())
1114}
1115
1116async fn drain_same_key_bitmaps(
1117 cursors: &mut [BitmapShardCursor],
1118 heap: &mut BinaryHeap<Reverse<BitmapHeapItem>>,
1119 item: BitmapHeapItem,
1120) -> Result<(ScalarValue, RowAddrTreeMap)> {
1121 let (key, mut merged_bitmap) = cursors[item.shard_idx].take_current()?;
1122 let merged_key = OrderableScalarValue(key);
1123 advance_cursor_and_push(cursors, heap, item.shard_idx).await?;
1124
1125 while let Some(Reverse(next_item)) = heap.peek() {
1126 if next_item.key != merged_key {
1127 break;
1128 }
1129
1130 let shard_idx = next_item.shard_idx;
1131 let _ = heap.pop();
1132 let (_, bitmap) = cursors[shard_idx].take_current()?;
1133 merged_bitmap |= &bitmap;
1134 advance_cursor_and_push(cursors, heap, shard_idx).await?;
1135 }
1136
1137 Ok((merged_key.0, merged_bitmap))
1138}
1139
1140async fn list_bitmap_shard_files(
1141 object_store: &ObjectStore,
1142 index_dir: &Path,
1143 progress: &dyn IndexBuildProgress,
1144) -> Result<Vec<String>> {
1145 let mut shard_files = Vec::new();
1146 let mut list_stream = object_store.list(Some(index_dir.clone()));
1147 while let Some(item) = list_stream.next().await {
1148 match item {
1149 Ok(meta) => {
1150 let file_name = meta.location.filename().unwrap_or_default();
1151 if file_name.starts_with(BITMAP_PART_LOOKUP_PREFIX)
1152 && file_name.ends_with(BITMAP_PART_LOOKUP_SUFFIX)
1153 {
1154 shard_files.push(file_name.to_string());
1155 progress
1156 .stage_progress("scan_bitmap_shards", shard_files.len() as u64)
1157 .await?;
1158 }
1159 }
1160 Err(err) => {
1161 return Err(Error::io(format!(
1162 "Failed to list bitmap shard files in {}: {err}",
1163 index_dir
1164 )));
1165 }
1166 }
1167 }
1168 let mut shard_files = shard_files
1169 .into_iter()
1170 .map(|file_name| extract_bitmap_shard_id(&file_name).map(|shard_id| (shard_id, file_name)))
1171 .collect::<Result<Vec<_>>>()?;
1172 shard_files.sort_unstable_by_key(|(shard_id, _)| *shard_id);
1173 let shard_files = shard_files
1174 .into_iter()
1175 .map(|(_, file_name)| file_name)
1176 .collect::<Vec<_>>();
1177 if shard_files.is_empty() {
1178 return Err(Error::invalid_input(format!(
1179 "No bitmap shard files found in index directory: {}; \
1180 call build_index for each fragment before calling merge_index_metadata",
1181 index_dir
1182 )));
1183 }
1184 Ok(shard_files)
1185}
1186
1187async fn cleanup_bitmap_shard_files(store: &dyn IndexStore, shard_files: &[String]) {
1188 for file_name in shard_files {
1189 if let Err(error) = store.delete_index_file(file_name).await {
1190 warn!(
1191 "Failed to delete bitmap shard file '{}': {}. \
1192 This does not affect the merged bitmap index, but the shard file \
1193 may need manual cleanup.",
1194 file_name, error
1195 );
1196 }
1197 }
1198}
1199
1200#[derive(Debug, Default)]
1201pub struct BitmapIndexPlugin;
1202
1203fn retain_valid(
1207 mut bitmap: RowAddrTreeMap,
1208 filter: Option<&super::OldIndexDataFilter>,
1209) -> RowAddrTreeMap {
1210 if let Some(filter) = filter {
1211 filter.retain_old_rows(&mut bitmap);
1212 }
1213 bitmap
1214}
1215
1216impl BitmapIndexPlugin {
1217 fn get_batch_from_arrays(
1218 keys: Arc<dyn Array>,
1219 binary_bitmaps: Arc<dyn Array>,
1220 ) -> Result<RecordBatch> {
1221 let schema = Arc::new(Schema::new(vec![
1222 Field::new("keys", keys.data_type().clone(), true),
1223 Field::new("bitmaps", binary_bitmaps.data_type().clone(), true),
1224 ]));
1225
1226 let columns = vec![keys, binary_bitmaps];
1227
1228 Ok(RecordBatch::try_new(schema, columns)?)
1229 }
1230
1231 async fn write_bitmap_index(
1232 state: HashMap<ScalarValue, RowAddrTreeMap>,
1233 index_store: &dyn IndexStore,
1234 value_type: &DataType,
1235 ) -> Result<IndexFile> {
1236 Self::write_bitmap_index_with_extras(
1237 state,
1238 index_store,
1239 value_type,
1240 HashMap::new(),
1241 Vec::new(),
1242 )
1243 .await
1244 }
1245
1246 pub(crate) async fn write_bitmap_index_with_extras(
1248 state: HashMap<ScalarValue, RowAddrTreeMap>,
1249 index_store: &dyn IndexStore,
1250 value_type: &DataType,
1251 mut metadata: HashMap<String, String>,
1252 global_buffers: Vec<(String, Bytes)>,
1253 ) -> Result<IndexFile> {
1254 let num_bitmaps = state.len();
1255 let schema = Arc::new(Schema::new(vec![
1256 Field::new("keys", value_type.clone(), true),
1257 Field::new("bitmaps", DataType::Binary, true),
1258 ]));
1259
1260 let mut bitmap_index_file = index_store
1261 .new_index_file(BITMAP_LOOKUP_NAME, schema)
1262 .await?;
1263
1264 for (metadata_key, data) in global_buffers {
1265 let buffer_idx = bitmap_index_file.add_global_buffer(data).await?;
1266 metadata.insert(metadata_key, buffer_idx.to_string());
1267 }
1268
1269 let mut cur_keys = Vec::new();
1270 let mut cur_bitmaps = Vec::new();
1271 let mut cur_bytes = 0;
1272
1273 for (key, bitmap) in state.into_iter() {
1274 let mut bytes = Vec::new();
1275 bitmap.serialize_into(&mut bytes).unwrap();
1276 let bitmap_size = bytes.len();
1277
1278 if cur_bytes + bitmap_size > MAX_BITMAP_ARRAY_LENGTH {
1279 let keys_array = ScalarValue::iter_to_array(cur_keys.clone()).unwrap();
1280 let mut binary_builder = BinaryBuilder::new();
1281 for b in &cur_bitmaps {
1282 binary_builder.append_value(b);
1283 }
1284 let bitmaps_array = Arc::new(binary_builder.finish()) as Arc<dyn Array>;
1285
1286 let record_batch = Self::get_batch_from_arrays(keys_array, bitmaps_array)?;
1287 bitmap_index_file.write_record_batch(record_batch).await?;
1288
1289 cur_keys.clear();
1290 cur_bitmaps.clear();
1291 cur_bytes = 0;
1292 }
1293
1294 cur_keys.push(key);
1295 cur_bitmaps.push(bytes);
1296 cur_bytes += bitmap_size;
1297 }
1298
1299 if !cur_keys.is_empty() {
1301 let keys_array = ScalarValue::iter_to_array(cur_keys).unwrap();
1302 let mut binary_builder = BinaryBuilder::new();
1303 for b in &cur_bitmaps {
1304 binary_builder.append_value(b);
1305 }
1306 let bitmaps_array = Arc::new(binary_builder.finish()) as Arc<dyn Array>;
1307
1308 let record_batch = Self::get_batch_from_arrays(keys_array, bitmaps_array)?;
1309 bitmap_index_file.write_record_batch(record_batch).await?;
1310 }
1311
1312 let stats_json = serde_json::to_string(&BitmapStatistics { num_bitmaps })
1314 .map_err(|e| Error::internal(format!("failed to serialize bitmap statistics: {e}")))?;
1315 metadata.insert(INDEX_STATS_METADATA_KEY.to_string(), stats_json);
1316
1317 bitmap_index_file.finish_with_metadata(metadata).await
1318 }
1319
1320 pub(crate) async fn build_bitmap_index_state(
1322 mut data_source: SendableRecordBatchStream,
1323 mut state: HashMap<ScalarValue, RowAddrTreeMap>,
1324 ) -> Result<(HashMap<ScalarValue, RowAddrTreeMap>, DataType)> {
1325 let value_type = data_source.schema().field(0).data_type().clone();
1326 while let Some(batch) = data_source.try_next().await? {
1327 let values = batch.column_by_name(VALUE_COLUMN_NAME).expect_ok()?;
1328 let row_ids = batch.column_by_name(ROW_ID).expect_ok()?;
1329 debug_assert_eq!(row_ids.data_type(), &DataType::UInt64);
1330
1331 let row_id_column = row_ids.as_any().downcast_ref::<UInt64Array>().unwrap();
1332
1333 for i in 0..values.len() {
1334 let row_id = row_id_column.value(i);
1335 let key = ScalarValue::try_from_array(values.as_ref(), i)?;
1336 state.entry(key.clone()).or_default().insert(row_id);
1337 }
1338 }
1339
1340 Ok((state, value_type))
1341 }
1342
1343 pub async fn train_bitmap_index(
1344 data: SendableRecordBatchStream,
1345 index_store: &dyn IndexStore,
1346 ) -> Result<IndexFile> {
1347 Self::streaming_build_and_write(data, None, index_store, BITMAP_LOOKUP_NAME, None).await
1348 }
1349
1350 async fn train_bitmap_shard(
1351 data: SendableRecordBatchStream,
1352 index_store: &dyn IndexStore,
1353 fragment_ids: &[u32],
1354 shard_id: Option<u32>,
1355 progress: Arc<dyn crate::progress::IndexBuildProgress>,
1356 ) -> Result<IndexFile> {
1357 let partition_id = bitmap_shard_partition_id(fragment_ids, shard_id)?;
1358 let file_name = bitmap_shard_file_name(partition_id);
1359 progress
1360 .stage_start("build_bitmap_shard", None, "rows")
1361 .await?;
1362 let file =
1363 Self::streaming_build_and_write(data, None, index_store, &file_name, None).await?;
1364 progress.stage_complete("build_bitmap_shard").await?;
1365 Ok(file)
1366 }
1367
1368 async fn streaming_build_and_write(
1376 mut data_source: SendableRecordBatchStream,
1377 old_index: Option<&BitmapIndex>,
1378 index_store: &dyn IndexStore,
1379 output_file_name: &str,
1380 old_data_filter: Option<&super::OldIndexDataFilter>,
1381 ) -> Result<IndexFile> {
1382 let value_type = data_source.schema().field(0).data_type().clone();
1383
1384 let mut writer =
1385 new_bitmap_batch_writer(index_store, output_file_name, &value_type).await?;
1386
1387 let old_keys: Vec<OrderableScalarValue> = old_index
1390 .map(|idx| idx.index_map.keys().cloned().collect())
1391 .unwrap_or_default();
1392 let mut old_pos: usize = 0;
1393
1394 let mut current_key: Option<ScalarValue> = None;
1396 let mut current_bitmap = RowAddrTreeMap::default();
1397 let mut emitted_null = false;
1400
1401 while let Some(batch) = data_source.try_next().await? {
1402 let values = batch.column_by_name(VALUE_COLUMN_NAME).expect_ok()?;
1403 let row_ids = batch.column_by_name(ROW_ID).expect_ok()?;
1404 debug_assert_eq!(row_ids.data_type(), &DataType::UInt64);
1405 let row_id_column = row_ids.as_any().downcast_ref::<UInt64Array>().unwrap();
1406
1407 for i in 0..values.len() {
1408 let row_id = row_id_column.value(i);
1409 let key = ScalarValue::try_from_array(values.as_ref(), i)?;
1410
1411 match ¤t_key {
1412 Some(cur) if *cur == key => {
1413 current_bitmap.insert(row_id);
1414 }
1415 _ => {
1416 if let Some(prev_key) = current_key.take() {
1418 let mut prev_bitmap = std::mem::take(&mut current_bitmap);
1419 Self::finish_run(
1420 prev_key,
1421 &mut prev_bitmap,
1422 old_index,
1423 &old_keys,
1424 &mut old_pos,
1425 &mut emitted_null,
1426 &mut writer,
1427 old_data_filter,
1428 )
1429 .await?;
1430 }
1431 current_key = Some(key);
1432 current_bitmap = RowAddrTreeMap::default();
1433 current_bitmap.insert(row_id);
1434 }
1435 }
1436 }
1437 }
1438
1439 if let Some(last_key) = current_key.take() {
1441 let mut last_bitmap = std::mem::take(&mut current_bitmap);
1442 Self::finish_run(
1443 last_key,
1444 &mut last_bitmap,
1445 old_index,
1446 &old_keys,
1447 &mut old_pos,
1448 &mut emitted_null,
1449 &mut writer,
1450 old_data_filter,
1451 )
1452 .await?;
1453 }
1454
1455 if let Some(idx) = old_index {
1457 while old_pos < old_keys.len() {
1458 let old_bitmap = retain_valid(
1459 idx.load_bitmap(&old_keys[old_pos], None)
1460 .await?
1461 .as_ref()
1462 .clone(),
1463 old_data_filter,
1464 );
1465 writer
1466 .emit(old_keys[old_pos].0.clone(), &old_bitmap)
1467 .await?;
1468 old_pos += 1;
1469 }
1470 }
1471
1472 if !emitted_null
1474 && let Some(idx) = old_index
1475 && !idx.null_map.is_empty()
1476 {
1477 let null_key = new_null_array(&value_type, 1);
1478 let null_key = ScalarValue::try_from_array(null_key.as_ref(), 0)?;
1479 let null_bitmap = retain_valid((*idx.null_map).clone(), old_data_filter);
1480 writer.emit(null_key, &null_bitmap).await?;
1481 }
1482
1483 writer.finish().await
1484 }
1485
1486 #[allow(clippy::too_many_arguments)]
1490 async fn finish_run(
1491 key: ScalarValue,
1492 bitmap: &mut RowAddrTreeMap,
1493 old_index: Option<&BitmapIndex>,
1494 old_keys: &[OrderableScalarValue],
1495 old_pos: &mut usize,
1496 emitted_null: &mut bool,
1497 writer: &mut BitmapBatchWriter,
1498 old_data_filter: Option<&super::OldIndexDataFilter>,
1499 ) -> Result<()> {
1500 if key.is_null() {
1501 if let Some(idx) = old_index
1503 && !idx.null_map.is_empty()
1504 {
1505 *bitmap |= &retain_valid((*idx.null_map).clone(), old_data_filter);
1506 }
1507 *emitted_null = true;
1508 writer.emit(key, bitmap).await?;
1509 } else if let Some(idx) = old_index {
1510 let orderable = OrderableScalarValue(key.clone());
1511
1512 while *old_pos < old_keys.len() && old_keys[*old_pos] < orderable {
1514 let old_bitmap = retain_valid(
1515 idx.load_bitmap(&old_keys[*old_pos], None)
1516 .await?
1517 .as_ref()
1518 .clone(),
1519 old_data_filter,
1520 );
1521 writer
1522 .emit(old_keys[*old_pos].0.clone(), &old_bitmap)
1523 .await?;
1524 *old_pos += 1;
1525 }
1526
1527 if *old_pos < old_keys.len() && old_keys[*old_pos] == orderable {
1529 *bitmap |= &retain_valid(
1530 idx.load_bitmap(&old_keys[*old_pos], None)
1531 .await?
1532 .as_ref()
1533 .clone(),
1534 old_data_filter,
1535 );
1536 *old_pos += 1;
1537 }
1538
1539 writer.emit(key, bitmap).await?;
1540 } else {
1541 writer.emit(key, bitmap).await?;
1542 }
1543 Ok(())
1544 }
1545
1546 pub(crate) fn remap_bitmap_state(
1548 state: HashMap<ScalarValue, RowAddrTreeMap>,
1549 mapping: &RowAddrRemap,
1550 ) -> HashMap<ScalarValue, RowAddrTreeMap> {
1551 state
1552 .into_iter()
1553 .map(|(key, bitmap)| {
1554 let remapped_bitmap =
1555 RowAddrTreeMap::from_iter(bitmap.row_addrs().unwrap().filter_map(|addr| {
1556 let addr_as_u64 = u64::from(addr);
1557 mapping.get(addr_as_u64).unwrap_or(Some(addr_as_u64))
1558 }));
1559 (key, remapped_bitmap)
1560 })
1561 .collect()
1562 }
1563
1564 async fn merge_shards(
1581 store: &dyn IndexStore,
1582 shard_files: &[String],
1583 progress: Arc<dyn IndexBuildProgress>,
1584 ) -> Result<IndexFile> {
1585 progress
1586 .stage_start("merge_bitmap_shards", None, "bitmaps")
1587 .await?;
1588
1589 let mut cursors = Vec::with_capacity(shard_files.len());
1590 let mut heap = BinaryHeap::with_capacity(shard_files.len());
1591 let mut value_type: Option<DataType> = None;
1592
1593 for file_name in shard_files {
1594 let reader = store.open_index_file(file_name).await?;
1595 let shard_value_type = reader.schema().fields[0].data_type().clone();
1596 if let Some(existing_type) = &value_type {
1597 if existing_type != &shard_value_type {
1598 return Err(Error::invalid_input(format!(
1599 "Bitmap shard {} has value type {:?}, expected {:?}",
1600 file_name, shard_value_type, existing_type
1601 )));
1602 }
1603 } else {
1604 value_type = Some(shard_value_type);
1605 }
1606 if let Some(cursor) = BitmapShardCursor::try_new(file_name.clone(), reader).await? {
1607 let key = cursor.peek_key()?;
1608 let shard_idx = cursors.len();
1609 cursors.push(cursor);
1610 heap.push(Reverse(BitmapHeapItem { key, shard_idx }));
1611 }
1612 }
1613
1614 let value_type = value_type.ok_or_else(|| {
1615 Error::invalid_input("Bitmap shard merge requires at least one shard file".to_string())
1616 })?;
1617 let mut writer = new_bitmap_batch_writer(store, BITMAP_LOOKUP_NAME, &value_type).await?;
1618 let mut merged_keys = 0u64;
1619
1620 while let Some(Reverse(item)) = heap.pop() {
1621 let (key, merged_bitmap) =
1622 drain_same_key_bitmaps(&mut cursors, &mut heap, item).await?;
1623 writer.emit(key, &merged_bitmap).await?;
1624 merged_keys += 1;
1625 progress
1626 .stage_progress("merge_bitmap_shards", merged_keys)
1627 .await?;
1628 }
1629
1630 progress.stage_complete("merge_bitmap_shards").await?;
1631 progress
1632 .stage_start("write_bitmap_index", Some(1), "files")
1633 .await?;
1634 let file = writer.finish().await?;
1635 progress.stage_progress("write_bitmap_index", 1).await?;
1636 progress.stage_complete("write_bitmap_index").await?;
1637 Ok(file)
1638 }
1639}
1640
1641pub async fn merge_index_files(
1642 object_store: &ObjectStore,
1643 index_dir: &Path,
1644 store: Arc<dyn IndexStore>,
1645 progress: Arc<dyn IndexBuildProgress>,
1646) -> Result<()> {
1647 progress
1648 .stage_start("scan_bitmap_shards", None, "files")
1649 .await?;
1650 let shard_files = list_bitmap_shard_files(object_store, index_dir, progress.as_ref()).await?;
1651 progress.stage_complete("scan_bitmap_shards").await?;
1652
1653 BitmapIndexPlugin::merge_shards(store.as_ref(), &shard_files, progress).await?;
1654 cleanup_bitmap_shard_files(store.as_ref(), &shard_files).await;
1655 Ok(())
1656}
1657
1658pub async fn merge_bitmap_indices(
1659 source_indices: &[Arc<BitmapIndex>],
1660 dest_store: &dyn IndexStore,
1661 progress: Arc<dyn IndexBuildProgress>,
1662) -> Result<CreatedIndex> {
1663 if source_indices.is_empty() {
1664 return Err(Error::invalid_input(
1665 "Bitmap segment merge requires at least one source segment".to_string(),
1666 ));
1667 }
1668
1669 let value_type = source_indices[0].value_type().clone();
1670 let mut merged_state = HashMap::<ScalarValue, RowAddrTreeMap>::new();
1671
1672 progress
1673 .stage_start(
1674 "merge_bitmap_segments",
1675 Some(source_indices.len() as u64),
1676 "segments",
1677 )
1678 .await?;
1679 for (idx, source_index) in source_indices.iter().enumerate() {
1680 if source_index.value_type() != &value_type {
1681 return Err(Error::invalid_input(format!(
1682 "Bitmap segment has value type {:?}, expected {:?}",
1683 source_index.value_type(),
1684 value_type
1685 )));
1686 }
1687
1688 let state = source_index.load_bitmap_index_state().await?;
1689 for (key, bitmap) in state {
1690 merged_state
1691 .entry(key)
1692 .and_modify(|existing| *existing |= &bitmap)
1693 .or_insert(bitmap);
1694 }
1695 progress
1696 .stage_progress("merge_bitmap_segments", (idx + 1) as u64)
1697 .await?;
1698 }
1699 progress.stage_complete("merge_bitmap_segments").await?;
1700
1701 progress
1702 .stage_start("write_bitmap_index", Some(1), "files")
1703 .await?;
1704 let file = BitmapIndexPlugin::write_bitmap_index(merged_state, dest_store, &value_type).await?;
1705 progress.stage_progress("write_bitmap_index", 1).await?;
1706 progress.stage_complete("write_bitmap_index").await?;
1707
1708 Ok(CreatedIndex {
1709 index_details: prost_types::Any::from_msg(&pbold::BitmapIndexDetails::default()).unwrap(),
1710 index_version: BITMAP_INDEX_VERSION,
1711 files: vec![file],
1712 })
1713}
1714
1715#[async_trait]
1716impl BasicTrainer for BitmapIndexPlugin {
1717 fn new_training_request(
1718 &self,
1719 params: &str,
1720 field: &Field,
1721 ) -> Result<Box<dyn TrainingRequest>> {
1722 if field.data_type().is_nested() {
1723 return Err(Error::invalid_input_source(
1724 "A bitmap index can only be created on a non-nested field.".into(),
1725 ));
1726 }
1727 let params = if params.is_empty() {
1728 BitmapParameters::default()
1729 } else {
1730 serde_json::from_str::<BitmapParameters>(params)?
1731 };
1732 Ok(Box::new(BitmapTrainingRequest::new(params)))
1733 }
1734
1735 async fn train_index(
1736 &self,
1737 data: SendableRecordBatchStream,
1738 index_store: &dyn IndexStore,
1739 request: Box<dyn TrainingRequest>,
1740 fragment_ids: Option<Vec<u32>>,
1741 progress: Arc<dyn crate::progress::IndexBuildProgress>,
1742 ) -> Result<CreatedIndex> {
1743 let request = request
1744 .as_any()
1745 .downcast_ref::<BitmapTrainingRequest>()
1746 .ok_or_else(|| {
1747 Error::internal(
1748 "BitmapIndexPlugin::train_index received a non-bitmap training request"
1749 .to_string(),
1750 )
1751 })?;
1752 let file = if let Some(fragment_ids) = fragment_ids.as_ref() {
1753 Self::train_bitmap_shard(
1754 data,
1755 index_store,
1756 fragment_ids,
1757 request.parameters.shard_id,
1758 progress,
1759 )
1760 .await?
1761 } else if request.parameters.shard_id.is_some() {
1762 return Err(Error::invalid_input(
1763 "Bitmap shard_id requires fragment_ids and is only supported for distributed shard builds"
1764 .to_string(),
1765 ));
1766 } else {
1767 Self::train_bitmap_index(data, index_store).await?
1768 };
1769 Ok(CreatedIndex {
1770 index_details: prost_types::Any::from_msg(&pbold::BitmapIndexDetails::default())
1771 .unwrap(),
1772 index_version: BITMAP_INDEX_VERSION,
1773 files: vec![file],
1774 })
1775 }
1776}
1777
1778#[async_trait]
1779impl ScalarIndexPlugin for BitmapIndexPlugin {
1780 fn basic_trainer(&self) -> Option<&dyn BasicTrainer> {
1781 Some(self)
1782 }
1783
1784 fn name(&self) -> &str {
1785 "Bitmap"
1786 }
1787
1788 fn provides_exact_answer(&self) -> bool {
1789 true
1790 }
1791
1792 fn version(&self) -> u32 {
1793 BITMAP_INDEX_VERSION
1794 }
1795
1796 fn new_query_parser(
1797 &self,
1798 index_name: String,
1799 _index_details: &prost_types::Any,
1800 ) -> Option<Box<dyn ScalarQueryParser>> {
1801 Some(Box::new(
1804 SargableQueryParser::new(index_name, self.name().to_string(), false)
1805 .without_like_prefix(),
1806 ))
1807 }
1808
1809 async fn load_index(
1811 &self,
1812 index_store: Arc<dyn IndexStore>,
1813 _index_details: &prost_types::Any,
1814 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
1815 cache: &LanceCache,
1816 ) -> Result<Arc<dyn ScalarIndex>> {
1817 Ok(BitmapIndex::load(index_store, frag_reuse_index, cache).await? as Arc<dyn ScalarIndex>)
1818 }
1819
1820 async fn get_from_cache(
1821 &self,
1822 index_store: Arc<dyn IndexStore>,
1823 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
1824 cache: &LanceCache,
1825 ) -> Result<Option<Arc<dyn ScalarIndex>>> {
1826 let Some(state) = cache.get_with_key(&BitmapIndexStateKey).await else {
1827 return Ok(None);
1828 };
1829 let index = state.to_bitmap_index(index_store, cache, frag_reuse_index)?;
1830 Ok(Some(index as Arc<dyn ScalarIndex>))
1831 }
1832
1833 async fn put_in_cache(&self, cache: &LanceCache, index: Arc<dyn ScalarIndex>) -> Result<()> {
1834 let state = BitmapIndexState::from_scalar_index(index.as_ref())?;
1835 cache
1836 .insert_with_key(&BitmapIndexStateKey, Arc::new(state))
1837 .await;
1838 Ok(())
1839 }
1840
1841 async fn get_or_insert_in_cache(
1842 &self,
1843 index_store: Arc<dyn IndexStore>,
1844 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
1845 cache: &LanceCache,
1846 load: ScalarIndexLoad<'_>,
1847 ) -> Result<Arc<dyn ScalarIndex>> {
1848 single_flight_open(
1849 cache,
1850 BitmapIndexStateKey,
1851 load,
1852 BitmapIndexState::from_scalar_index,
1853 move |state| {
1854 Ok(state.to_bitmap_index(index_store, cache, frag_reuse_index)?
1855 as Arc<dyn ScalarIndex>)
1856 },
1857 )
1858 .await
1859 }
1860
1861 async fn load_statistics(
1862 &self,
1863 index_store: Arc<dyn IndexStore>,
1864 _index_details: &prost_types::Any,
1865 ) -> Result<Option<serde_json::Value>> {
1866 let reader = index_store.open_index_file(BITMAP_LOOKUP_NAME).await?;
1867 if let Some(value) = reader.schema().metadata.get(INDEX_STATS_METADATA_KEY) {
1868 let stats = serde_json::from_str(value).map_err(|e| {
1869 Error::internal(format!("failed to parse bitmap statistics metadata: {e}"))
1870 })?;
1871 Ok(Some(stats))
1872 } else {
1873 Ok(None)
1874 }
1875 }
1876}
1877
1878#[cfg(test)]
1879mod tests {
1880 use super::*;
1881 use crate::metrics::NoOpMetricsCollector;
1882 use crate::scalar::lance_format::LanceIndexStore;
1883 use arrow_array::{RecordBatch, StringArray, UInt64Array, record_batch};
1884 use arrow_schema::{DataType, Field, Schema};
1885
1886 fn sort_batch_by_value(batch: &RecordBatch) -> RecordBatch {
1889 use arrow::compute::SortOptions;
1890 let values = batch.column(0);
1891 let row_ids = batch.column(1);
1892 let options = SortOptions {
1893 descending: false,
1894 nulls_first: true,
1895 };
1896 let indices = arrow::compute::sort_to_indices(values, Some(options), None).unwrap();
1897 let sorted_values = arrow::compute::take(values.as_ref(), &indices, None).unwrap();
1898 let sorted_row_ids = arrow::compute::take(row_ids.as_ref(), &indices, None).unwrap();
1899 RecordBatch::try_new(batch.schema(), vec![sorted_values, sorted_row_ids]).unwrap()
1900 }
1901 use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
1902 use futures::stream;
1903 use lance_core::utils::{address::RowAddress, tempfile::TempObjDir};
1904 use lance_io::object_store::ObjectStore;
1905 use lance_select::RowSetOps;
1906 use rstest::rstest;
1907
1908 fn assert_state_roundtrips(state: &BitmapIndexState) {
1909 let mut buf = Vec::new();
1910 state
1911 .serialize(&mut CacheEntryWriter::new(&mut buf))
1912 .unwrap();
1913 let data = bytes::Bytes::from(buf);
1914 let mut reader = CacheEntryReader::new(&data, 0, BitmapIndexState::CURRENT_VERSION);
1915 let restored = BitmapIndexState::deserialize(&mut reader).unwrap();
1916 assert_eq!(restored.lookup_batch, state.lookup_batch);
1917 assert_eq!(&*restored.null_map, &*state.null_map);
1918 assert_eq!(restored.value_type, state.value_type);
1919 }
1920
1921 #[test]
1922 fn test_bitmap_index_state_codec_roundtrip() {
1923 let mut index_map = BTreeMap::new();
1925 index_map.insert(OrderableScalarValue(ScalarValue::Int32(Some(1))), 0);
1926 index_map.insert(OrderableScalarValue(ScalarValue::Int32(Some(7))), 1);
1927 index_map.insert(OrderableScalarValue(ScalarValue::Int32(Some(42))), 2);
1928 let mut null_map = RowAddrTreeMap::new();
1929 null_map.insert(RowAddress::new_from_parts(0, 3).into());
1930 null_map.insert(RowAddress::new_from_parts(0, 5).into());
1931 let state = BitmapIndexState {
1932 lookup_batch: build_lookup_batch(&index_map, &DataType::Int32).unwrap(),
1933 null_map: Arc::new(null_map),
1934 value_type: DataType::Int32,
1935 index_map: Arc::new(index_map),
1936 };
1937 assert_state_roundtrips(&state);
1938
1939 let empty_state = BitmapIndexState {
1941 lookup_batch: build_lookup_batch(&BTreeMap::new(), &DataType::Utf8).unwrap(),
1942 null_map: Arc::new(RowAddrTreeMap::new()),
1943 value_type: DataType::Utf8,
1944 index_map: Arc::new(BTreeMap::new()),
1945 };
1946 assert_state_roundtrips(&empty_state);
1947 }
1948
1949 #[test]
1953 fn test_bitmap_index_state_lookup_is_zero_copy() {
1954 const ALIGN: usize = 64;
1955 let mut index_map = BTreeMap::new();
1956 for k in 0..32i32 {
1957 index_map.insert(
1958 OrderableScalarValue(ScalarValue::Int32(Some(k))),
1959 k as usize,
1960 );
1961 }
1962 let state = BitmapIndexState {
1963 lookup_batch: build_lookup_batch(&index_map, &DataType::Int32).unwrap(),
1964 null_map: Arc::new(RowAddrTreeMap::new()),
1965 value_type: DataType::Int32,
1966 index_map: Arc::new(index_map),
1967 };
1968
1969 let codec = CacheCodec::from_impl::<BitmapIndexState>();
1970 let any: Arc<dyn std::any::Any + Send + Sync> = Arc::new(state);
1971 let mut buf = Vec::new();
1972 codec.serialize(&any, &mut buf).unwrap();
1973
1974 let mut v = vec![0u8; buf.len() + ALIGN];
1976 let pad = (ALIGN - (v.as_ptr() as usize % ALIGN)) % ALIGN;
1977 v[pad..pad + buf.len()].copy_from_slice(&buf);
1978 let data = bytes::Bytes::from(v).slice(pad..pad + buf.len());
1979
1980 let restored = codec.deserialize(&data).hit().unwrap();
1981 let restored = restored.downcast::<BitmapIndexState>().unwrap();
1982
1983 let base = data.as_ptr() as usize;
1984 let end = base + data.len();
1985 for col in restored.lookup_batch.columns() {
1986 for buffer in col.to_data().buffers() {
1987 let ptr = buffer.as_ptr() as usize;
1988 assert!(
1989 ptr >= base && ptr < end,
1990 "lookup batch buffer was realigned out of the input — misaligned IPC section",
1991 );
1992 }
1993 }
1994 }
1995
1996 #[tokio::test]
1997 async fn test_bitmap_lazy_loading_and_cache() {
1998 let tmpdir = TempObjDir::default();
2000 let store = Arc::new(LanceIndexStore::new(
2001 Arc::new(ObjectStore::local()),
2002 tmpdir.clone(),
2003 Arc::new(LanceCache::no_cache()),
2004 ));
2005
2006 let colors = vec![
2008 "red", "blue", "green", "red", "yellow", "blue", "red", "green", "blue", "yellow",
2009 "red", "red", "blue", "green", "yellow",
2010 ];
2011
2012 let row_ids = (0u64..15u64).collect::<Vec<_>>();
2013
2014 let schema = Arc::new(Schema::new(vec![
2015 Field::new("value", DataType::Utf8, false),
2016 Field::new("_rowid", DataType::UInt64, false),
2017 ]));
2018
2019 let batch = RecordBatch::try_new(
2020 schema.clone(),
2021 vec![
2022 Arc::new(StringArray::from(colors.clone())),
2023 Arc::new(UInt64Array::from(row_ids.clone())),
2024 ],
2025 )
2026 .unwrap();
2027
2028 let batch = sort_batch_by_value(&batch);
2029 let stream = stream::once(async move { Ok(batch) });
2030 let stream = Box::pin(RecordBatchStreamAdapter::new(schema, stream));
2031
2032 BitmapIndexPlugin::train_bitmap_index(stream, store.as_ref())
2034 .await
2035 .unwrap();
2036
2037 let cache = LanceCache::with_capacity(1024 * 1024); let index = BitmapIndex::load(store.clone(), None, &cache)
2042 .await
2043 .unwrap();
2044
2045 assert_eq!(index.index_map.len(), 4); assert!(index.null_map.is_empty()); let query = SargableQuery::Equals(ScalarValue::Utf8(Some("red".to_string())));
2050 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2051
2052 let expected_red_rows = vec![0u64, 3, 6, 10, 11];
2054 if let SearchResult::Exact(row_ids) = result {
2055 let mut actual: Vec<u64> = row_ids
2056 .true_rows()
2057 .row_addrs()
2058 .unwrap()
2059 .map(|id| id.into())
2060 .collect();
2061 actual.sort();
2062 assert_eq!(actual, expected_red_rows);
2063 } else {
2064 panic!("Expected exact search result");
2065 }
2066
2067 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2069 if let SearchResult::Exact(row_ids) = result {
2070 let mut actual: Vec<u64> = row_ids
2071 .true_rows()
2072 .row_addrs()
2073 .unwrap()
2074 .map(|id| id.into())
2075 .collect();
2076 actual.sort();
2077 assert_eq!(actual, expected_red_rows);
2078 }
2079
2080 let query = SargableQuery::Range(
2082 std::ops::Bound::Included(ScalarValue::Utf8(Some("blue".to_string()))),
2083 std::ops::Bound::Included(ScalarValue::Utf8(Some("green".to_string()))),
2084 );
2085 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2086
2087 let expected_range_rows = vec![1u64, 2, 5, 7, 8, 12, 13];
2088 if let SearchResult::Exact(row_ids) = result {
2089 let mut actual: Vec<u64> = row_ids
2090 .true_rows()
2091 .row_addrs()
2092 .unwrap()
2093 .map(|id| id.into())
2094 .collect();
2095 actual.sort();
2096 assert_eq!(actual, expected_range_rows);
2097 }
2098
2099 let query = SargableQuery::Range(
2101 std::ops::Bound::Included(ScalarValue::Utf8(Some("green".to_string()))),
2102 std::ops::Bound::Included(ScalarValue::Utf8(Some("blue".to_string()))),
2103 );
2104 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2105 if let SearchResult::Exact(row_ids) = result {
2106 assert!(row_ids.true_rows().is_empty());
2107 } else {
2108 panic!("Expected exact search result");
2109 }
2110
2111 let query = SargableQuery::IsIn(vec![
2113 ScalarValue::Utf8(Some("red".to_string())),
2114 ScalarValue::Utf8(Some("yellow".to_string())),
2115 ]);
2116 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2117
2118 let expected_in_rows = vec![0u64, 3, 4, 6, 9, 10, 11, 14];
2119 if let SearchResult::Exact(row_ids) = result {
2120 let mut actual: Vec<u64> = row_ids
2121 .true_rows()
2122 .row_addrs()
2123 .unwrap()
2124 .map(|id| id.into())
2125 .collect();
2126 actual.sort();
2127 assert_eq!(actual, expected_in_rows);
2128 }
2129 }
2130
2131 #[tokio::test]
2137 async fn test_bitmap_cache_fast_path() {
2138 use arrow_array::Int32Array;
2139
2140 let tmpdir = TempObjDir::default();
2141 let store = Arc::new(LanceIndexStore::new(
2142 Arc::new(ObjectStore::local()),
2143 tmpdir.clone(),
2144 Arc::new(LanceCache::no_cache()),
2145 ));
2146
2147 const N: u64 = 1_000;
2149 const NULL_COUNT: u64 = 5;
2150 let null_values: Vec<Option<i32>> =
2152 std::iter::repeat_n(None, NULL_COUNT as usize).collect();
2153 let non_null_values: Vec<Option<i32>> = (0..N as i32).map(Some).collect();
2154 let all_values: Vec<Option<i32>> = null_values.into_iter().chain(non_null_values).collect();
2155 let all_row_ids: Vec<u64> = (0..N + NULL_COUNT).collect();
2156
2157 let schema = Arc::new(Schema::new(vec![
2158 Field::new("value", DataType::Int32, true),
2159 Field::new("_rowid", DataType::UInt64, false),
2160 ]));
2161 let batch = RecordBatch::try_new(
2162 schema.clone(),
2163 vec![
2164 Arc::new(Int32Array::from(all_values)),
2165 Arc::new(UInt64Array::from(all_row_ids)),
2166 ],
2167 )
2168 .unwrap();
2169 let stream = stream::once(async move { Ok(batch) });
2170 let stream = Box::pin(RecordBatchStreamAdapter::new(schema, stream));
2171 BitmapIndexPlugin::train_bitmap_index(stream, store.as_ref())
2172 .await
2173 .unwrap();
2174
2175 let cache = LanceCache::with_capacity(16 * 1024 * 1024);
2176 let index = BitmapIndex::load(store.clone(), None, &cache)
2177 .await
2178 .unwrap();
2179
2180 let plugin = BitmapIndexPlugin;
2181 let index_arc: Arc<dyn ScalarIndex> = index.clone() as Arc<dyn ScalarIndex>;
2182 plugin.put_in_cache(&cache, index_arc).await.unwrap();
2183
2184 let cached = plugin
2187 .get_from_cache(store.clone(), None, &cache)
2188 .await
2189 .unwrap()
2190 .expect("get_from_cache must return Some after put_in_cache");
2191
2192 let query = SargableQuery::IsNull();
2194 match cached.search(&query, &NoOpMetricsCollector).await.unwrap() {
2195 SearchResult::Exact(row_set) => {
2196 let mut null_rows: Vec<u64> = row_set
2197 .true_rows()
2198 .row_addrs()
2199 .unwrap()
2200 .map(u64::from)
2201 .collect();
2202 null_rows.sort();
2203 let expected: Vec<u64> = (0..NULL_COUNT).collect();
2204 assert_eq!(null_rows, expected);
2205 }
2206 _ => panic!("Expected Exact result for IS NULL"),
2207 }
2208 }
2209
2210 #[tokio::test]
2211 #[ignore]
2212 async fn test_big_bitmap_index() {
2213 use super::{BITMAP_LOOKUP_NAME, BitmapIndex};
2216 use crate::scalar::IndexStore;
2217 use crate::scalar::lance_format::LanceIndexStore;
2218 use arrow_schema::DataType;
2219 use datafusion_common::ScalarValue;
2220 use lance_core::cache::LanceCache;
2221 use lance_io::object_store::ObjectStore;
2222 use lance_select::RowAddrTreeMap;
2223 use std::collections::HashMap;
2224 use std::sync::Arc;
2225
2226 let m: u32 = 2_500_000;
2232 let per_bitmap_size = 1000; let mut state = HashMap::new();
2235 for i in 0..m {
2236 let bitmap = RowAddrTreeMap::from_iter(0..per_bitmap_size);
2238
2239 let key = ScalarValue::UInt32(Some(i));
2240 state.insert(key, bitmap);
2241 }
2242
2243 let tmpdir = TempObjDir::default();
2245 let test_store = LanceIndexStore::new(
2246 Arc::new(ObjectStore::local()),
2247 tmpdir.clone(),
2248 Arc::new(LanceCache::no_cache()),
2249 );
2250
2251 let result =
2254 BitmapIndexPlugin::write_bitmap_index(state, &test_store, &DataType::UInt32).await;
2255
2256 assert!(
2257 result.is_ok(),
2258 "Failed to write bitmap index: {:?}",
2259 result.err()
2260 );
2261
2262 let index_file = test_store.open_index_file(BITMAP_LOOKUP_NAME).await;
2264 assert!(
2265 index_file.is_ok(),
2266 "Failed to open index file: {:?}",
2267 index_file.err()
2268 );
2269 let index_file = index_file.unwrap();
2270
2271 tracing::info!(
2273 "Index file contains {} rows in total",
2274 index_file.num_rows()
2275 );
2276
2277 tracing::info!("Loading index from disk...");
2279 let loaded_index = BitmapIndex::load(Arc::new(test_store), None, &LanceCache::no_cache())
2280 .await
2281 .expect("Failed to load bitmap index");
2282
2283 assert_eq!(
2285 loaded_index.index_map.len(),
2286 m as usize,
2287 "Loaded index has incorrect number of keys (expected {}, got {})",
2288 m,
2289 loaded_index.index_map.len()
2290 );
2291
2292 let test_keys = [0, m / 2, m - 1]; for &key_val in &test_keys {
2295 let key = OrderableScalarValue(ScalarValue::UInt32(Some(key_val)));
2296 let bitmap = loaded_index
2298 .load_bitmap(&key, None)
2299 .await
2300 .unwrap_or_else(|_| panic!("Key {} should exist", key_val));
2301
2302 let row_addrs: Vec<u64> = bitmap.row_addrs().unwrap().map(u64::from).collect();
2304
2305 assert_eq!(
2307 row_addrs.len(),
2308 per_bitmap_size as usize,
2309 "Bitmap for key {} has wrong size",
2310 key_val
2311 );
2312
2313 for i in 0..5.min(per_bitmap_size) {
2315 assert!(
2316 row_addrs.contains(&i),
2317 "Bitmap for key {} should contain row_id {}",
2318 key_val,
2319 i
2320 );
2321 }
2322
2323 for i in (per_bitmap_size - 5)..per_bitmap_size {
2324 assert!(
2325 row_addrs.contains(&i),
2326 "Bitmap for key {} should contain row_id {}",
2327 key_val,
2328 i
2329 );
2330 }
2331
2332 let expected_range: Vec<u64> = (0..per_bitmap_size).collect();
2334 assert_eq!(
2335 row_addrs, expected_range,
2336 "Bitmap for key {} doesn't contain expected values",
2337 key_val
2338 );
2339
2340 tracing::info!(
2341 "✓ Verified bitmap for key {}: {} rows as expected",
2342 key_val,
2343 row_addrs.len()
2344 );
2345 }
2346
2347 tracing::info!("Test successful! Index properly contains {} keys", m);
2348 }
2349
2350 #[tokio::test]
2351 async fn test_bitmap_prewarm() {
2352 let tmpdir = TempObjDir::default();
2354 let store = Arc::new(LanceIndexStore::new(
2355 Arc::new(ObjectStore::local()),
2356 tmpdir.clone(),
2357 Arc::new(LanceCache::no_cache()),
2358 ));
2359
2360 let colors = vec![
2362 "red", "blue", "green", "red", "yellow", "blue", "red", "green", "blue", "yellow",
2363 "red", "red", "blue", "green", "yellow",
2364 ];
2365
2366 let row_ids = (0u64..15u64).collect::<Vec<_>>();
2367
2368 let schema = Arc::new(Schema::new(vec![
2369 Field::new("value", DataType::Utf8, false),
2370 Field::new("_rowid", DataType::UInt64, false),
2371 ]));
2372
2373 let batch = RecordBatch::try_new(
2374 schema.clone(),
2375 vec![
2376 Arc::new(StringArray::from(colors.clone())),
2377 Arc::new(UInt64Array::from(row_ids.clone())),
2378 ],
2379 )
2380 .unwrap();
2381
2382 let batch = sort_batch_by_value(&batch);
2383 let stream = stream::once(async move { Ok(batch) });
2384 let stream = Box::pin(RecordBatchStreamAdapter::new(schema, stream));
2385
2386 BitmapIndexPlugin::train_bitmap_index(stream, store.as_ref())
2388 .await
2389 .unwrap();
2390
2391 let cache = LanceCache::with_capacity(1024 * 1024); let index = BitmapIndex::load(store.clone(), None, &cache)
2396 .await
2397 .unwrap();
2398
2399 let cache_key_red = BitmapKey {
2401 value: OrderableScalarValue(ScalarValue::Utf8(Some("red".to_string()))),
2402 };
2403 let cache_key_blue = BitmapKey {
2404 value: OrderableScalarValue(ScalarValue::Utf8(Some("blue".to_string()))),
2405 };
2406
2407 assert!(
2408 cache
2409 .get_with_key::<BitmapKey>(&cache_key_red)
2410 .await
2411 .is_none()
2412 );
2413 assert!(
2414 cache
2415 .get_with_key::<BitmapKey>(&cache_key_blue)
2416 .await
2417 .is_none()
2418 );
2419
2420 index.prewarm().await.unwrap();
2422
2423 assert!(
2425 cache
2426 .get_with_key::<BitmapKey>(&cache_key_red)
2427 .await
2428 .is_some()
2429 );
2430 assert!(
2431 cache
2432 .get_with_key::<BitmapKey>(&cache_key_blue)
2433 .await
2434 .is_some()
2435 );
2436
2437 let cached_red = cache
2439 .get_with_key::<BitmapKey>(&cache_key_red)
2440 .await
2441 .unwrap();
2442 let red_rows: Vec<u64> = cached_red.row_addrs().unwrap().map(u64::from).collect();
2443 assert_eq!(red_rows, vec![0, 3, 6, 10, 11]);
2444
2445 index.prewarm().await.unwrap();
2447
2448 let cached_red_2 = cache
2450 .get_with_key::<BitmapKey>(&cache_key_red)
2451 .await
2452 .unwrap();
2453 let red_rows_2: Vec<u64> = cached_red_2.row_addrs().unwrap().map(u64::from).collect();
2454 assert_eq!(red_rows_2, vec![0, 3, 6, 10, 11]);
2455 }
2456
2457 fn bitmap_remap_compact() -> RowAddrRemap {
2460 use lance_core::utils::row_addr_remap::GroupInput;
2461 use roaring::RoaringTreemap;
2462 RowAddrRemap::compact([GroupInput {
2463 rewritten_old_row_addrs: RoaringTreemap::from_iter(
2464 (0..3)
2465 .map(|o| u64::from(RowAddress::new_from_parts(1, o)))
2466 .chain((0..3).map(|o| u64::from(RowAddress::new_from_parts(2, o)))),
2467 ),
2468 old_frag_ids: vec![1, 2],
2469 new_frags: vec![(3, 6)],
2470 }])
2471 .unwrap()
2472 }
2473
2474 fn bitmap_remap_explicit() -> RowAddrRemap {
2475 RowAddrRemap::direct(
2477 (0..6u32)
2478 .map(|i| {
2479 let (f, o) = if i < 3 { (1, i) } else { (2, i - 3) };
2480 (
2481 u64::from(RowAddress::new_from_parts(f, o)),
2482 Some(u64::from(RowAddress::new_from_parts(3, i))),
2483 )
2484 })
2485 .collect(),
2486 )
2487 }
2488
2489 #[rstest]
2491 #[case(bitmap_remap_compact())]
2492 #[case(bitmap_remap_explicit())]
2493 #[tokio::test]
2494 async fn test_remap_bitmap_with_null(#[case] remap: RowAddrRemap) {
2495 use arrow_array::UInt32Array;
2496
2497 let tmpdir = TempObjDir::default();
2499 let test_store = Arc::new(LanceIndexStore::new(
2500 Arc::new(ObjectStore::local()),
2501 tmpdir.clone(),
2502 Arc::new(LanceCache::no_cache()),
2503 ));
2504
2505 let values = vec![
2510 None, None, Some(1u32), Some(1u32), Some(2u32), Some(2u32), ];
2517
2518 let row_ids: Vec<u64> = vec![
2520 RowAddress::new_from_parts(1, 0).into(),
2521 RowAddress::new_from_parts(1, 1).into(),
2522 RowAddress::new_from_parts(1, 2).into(),
2523 RowAddress::new_from_parts(2, 0).into(),
2524 RowAddress::new_from_parts(2, 1).into(),
2525 RowAddress::new_from_parts(2, 2).into(),
2526 ];
2527
2528 let schema = Arc::new(Schema::new(vec![
2529 Field::new("value", DataType::UInt32, true),
2530 Field::new("_rowid", DataType::UInt64, false),
2531 ]));
2532
2533 let batch = RecordBatch::try_new(
2534 schema.clone(),
2535 vec![
2536 Arc::new(UInt32Array::from(values)),
2537 Arc::new(UInt64Array::from(row_ids)),
2538 ],
2539 )
2540 .unwrap();
2541
2542 let stream = stream::once(async move { Ok(batch) });
2543 let stream = Box::pin(RecordBatchStreamAdapter::new(schema, stream));
2544
2545 BitmapIndexPlugin::train_bitmap_index(stream, test_store.as_ref())
2547 .await
2548 .unwrap();
2549
2550 let index = BitmapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
2552 .await
2553 .expect("Failed to load bitmap index");
2554
2555 assert_eq!(index.index_map.len(), 2); assert!(!index.null_map.is_empty()); index.remap(&remap, test_store.as_ref()).await.unwrap();
2561
2562 let reloaded_idx = BitmapIndex::load(test_store, None, &LanceCache::no_cache())
2564 .await
2565 .expect("Failed to load remapped bitmap index");
2566
2567 let expected_null_addrs: Vec<u64> = vec![
2569 RowAddress::new_from_parts(3, 0).into(),
2570 RowAddress::new_from_parts(3, 1).into(),
2571 ];
2572 let actual_null_addrs: Vec<u64> = reloaded_idx
2573 .null_map
2574 .row_addrs()
2575 .unwrap()
2576 .map(u64::from)
2577 .collect();
2578 assert_eq!(
2579 actual_null_addrs, expected_null_addrs,
2580 "Null bitmap not remapped correctly"
2581 );
2582
2583 let query = SargableQuery::Equals(ScalarValue::UInt32(Some(1)));
2585 let result = reloaded_idx
2586 .search(&query, &NoOpMetricsCollector)
2587 .await
2588 .unwrap();
2589 if let crate::scalar::SearchResult::Exact(row_ids) = result {
2590 let mut actual: Vec<u64> = row_ids
2591 .true_rows()
2592 .row_addrs()
2593 .unwrap()
2594 .map(u64::from)
2595 .collect();
2596 actual.sort();
2597 let expected: Vec<u64> = vec![
2598 RowAddress::new_from_parts(3, 2).into(),
2599 RowAddress::new_from_parts(3, 3).into(),
2600 ];
2601 assert_eq!(actual, expected, "Value 1 bitmap not remapped correctly");
2602 }
2603
2604 let query = SargableQuery::Equals(ScalarValue::UInt32(Some(2)));
2606 let result = reloaded_idx
2607 .search(&query, &NoOpMetricsCollector)
2608 .await
2609 .unwrap();
2610 if let crate::scalar::SearchResult::Exact(row_ids) = result {
2611 let mut actual: Vec<u64> = row_ids
2612 .true_rows()
2613 .row_addrs()
2614 .unwrap()
2615 .map(u64::from)
2616 .collect();
2617 actual.sort();
2618 let expected: Vec<u64> = vec![
2619 RowAddress::new_from_parts(3, 4).into(),
2620 RowAddress::new_from_parts(3, 5).into(),
2621 ];
2622 assert_eq!(actual, expected, "Value 2 bitmap not remapped correctly");
2623 }
2624
2625 let query = SargableQuery::IsNull();
2627 let result = reloaded_idx
2628 .search(&query, &NoOpMetricsCollector)
2629 .await
2630 .unwrap();
2631 if let crate::scalar::SearchResult::Exact(row_ids) = result {
2632 let mut actual: Vec<u64> = row_ids
2633 .true_rows()
2634 .row_addrs()
2635 .unwrap()
2636 .map(u64::from)
2637 .collect();
2638 actual.sort();
2639 assert_eq!(
2640 actual, expected_null_addrs,
2641 "Null search results not correct"
2642 );
2643 }
2644 }
2645
2646 #[tokio::test]
2647 async fn test_bitmap_null_handling_in_queries() {
2648 let tmpdir = TempObjDir::default();
2650 let store = Arc::new(LanceIndexStore::new(
2651 Arc::new(ObjectStore::local()),
2652 tmpdir.clone(),
2653 Arc::new(LanceCache::no_cache()),
2654 ));
2655
2656 let batch = record_batch!(
2658 ("value", Int64, [Some(0), Some(5), None]),
2659 ("_rowid", UInt64, [0, 1, 2])
2660 )
2661 .unwrap();
2662 let schema = batch.schema();
2663 let stream = stream::once(async move { Ok(batch) });
2664 let stream = Box::pin(RecordBatchStreamAdapter::new(schema, stream));
2665
2666 BitmapIndexPlugin::train_bitmap_index(stream, store.as_ref())
2668 .await
2669 .unwrap();
2670
2671 let cache = LanceCache::with_capacity(1024 * 1024);
2672 let index = BitmapIndex::load(store.clone(), None, &cache)
2673 .await
2674 .unwrap();
2675
2676 let query = SargableQuery::Equals(ScalarValue::Int64(Some(5)));
2678 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2679
2680 match result {
2681 SearchResult::Exact(row_ids) => {
2682 let actual_rows: Vec<u64> = row_ids
2683 .true_rows()
2684 .row_addrs()
2685 .unwrap()
2686 .map(u64::from)
2687 .collect();
2688 assert_eq!(actual_rows, vec![1], "Should find row 1 where value == 5");
2689
2690 let null_row_ids = row_ids.null_rows();
2691 assert!(!null_row_ids.is_empty(), "null_row_ids should be Some");
2693 let null_rows: Vec<u64> =
2694 null_row_ids.row_addrs().unwrap().map(u64::from).collect();
2695 assert_eq!(null_rows, vec![2], "Should report row 2 as null");
2696 }
2697 _ => panic!("Expected Exact search result"),
2698 }
2699
2700 let query = SargableQuery::IsNull();
2702 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2703
2704 match result {
2705 SearchResult::Exact(row_addrs) => {
2706 let actual_rows: Vec<u64> = row_addrs
2707 .true_rows()
2708 .row_addrs()
2709 .unwrap()
2710 .map(u64::from)
2711 .collect();
2712 assert_eq!(
2713 actual_rows,
2714 vec![2],
2715 "IsNull should find row 2 where value is null"
2716 );
2717
2718 let null_row_ids = row_addrs.null_rows();
2719 assert!(
2721 null_row_ids.is_empty(),
2722 "null_row_ids should be None for IsNull query"
2723 );
2724 }
2725 _ => panic!("Expected Exact search result"),
2726 }
2727
2728 let query = SargableQuery::Range(
2730 std::ops::Bound::Included(ScalarValue::Int64(Some(0))),
2731 std::ops::Bound::Included(ScalarValue::Int64(Some(3))),
2732 );
2733 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2734
2735 match result {
2736 SearchResult::Exact(row_addrs) => {
2737 let actual_rows: Vec<u64> = row_addrs
2738 .true_rows()
2739 .row_addrs()
2740 .unwrap()
2741 .map(u64::from)
2742 .collect();
2743 assert_eq!(actual_rows, vec![0], "Should find row 0 where value == 0");
2744
2745 let null_row_ids = row_addrs.null_rows();
2747 assert!(!null_row_ids.is_empty(), "null_row_ids should be Some");
2748 let null_rows: Vec<u64> =
2749 null_row_ids.row_addrs().unwrap().map(u64::from).collect();
2750 assert_eq!(null_rows, vec![2], "Should report row 2 as null");
2751 }
2752 _ => panic!("Expected Exact search result"),
2753 }
2754 }
2755}