1use crate::format::overlay::staleness::collect_overlay_stale_frags;
13use crate::format::{Fragment, IndexMetadata};
14use crate::system_index::frag_reuse::FRAG_REUSE_INDEX_NAME;
15use crate::system_index::is_system_index;
16use crate::transaction::{RewriteGroup, RewrittenIndex, Transaction};
17use lance_core::datatypes::Schema;
18use lance_core::{Error, Result};
19use roaring::RoaringBitmap;
20use std::collections::{HashMap, HashSet};
21
22impl Transaction {
23 pub(super) fn register_pure_rewrite_rows_update_frags_in_indices(
24 indices: &mut [IndexMetadata],
25 pure_update_frag_ids: &[u64],
26 original_fragment_ids: &[u64],
27 fields_for_preserving_frag_bitmap: &[u32],
28 original_overlaid_frags: &HashMap<u32, &Fragment>,
29 schema: &Schema,
30 ) -> Result<()> {
31 if pure_update_frag_ids.is_empty() {
32 return Ok(());
33 }
34
35 let value_updated_field_set = fields_for_preserving_frag_bitmap
36 .iter()
37 .collect::<HashSet<_>>();
38
39 for index in indices.iter_mut() {
40 if index.results_are_row_addrs() {
43 continue;
44 }
45 let index_covers_modified_field = index.fields.iter().any(|field_id| {
46 value_updated_field_set.contains(&u32::try_from(*field_id).unwrap())
47 });
48 if index_covers_modified_field {
49 continue;
50 }
51 let Some(fragment_bitmap) = index.fragment_bitmap.as_ref() else {
52 continue;
53 };
54
55 let index_covers_all_original_fragments = original_fragment_ids
58 .iter()
59 .all(|&fragment_id| fragment_bitmap.contains(fragment_id as u32));
60 if !index_covers_all_original_fragments {
61 continue;
62 }
63
64 let mut overlay_stale = RoaringBitmap::new();
70 collect_overlay_stale_frags(
71 index,
72 original_overlaid_frags,
73 &mut overlay_stale,
74 schema,
75 )?;
76 if !overlay_stale.is_empty() {
77 continue;
78 }
79
80 if let Some(fragment_bitmap) = index.fragment_bitmap.as_mut() {
81 for fragment_id in pure_update_frag_ids.iter().map(|f| *f as u32) {
82 fragment_bitmap.insert(fragment_id);
83 }
84 }
85 }
86 Ok(())
87 }
88
89 pub fn prune_updated_fields_from_indices(
92 indices: &mut [IndexMetadata],
93 updated_fragments: &[Fragment],
94 fields_modified: &[u32],
95 ) {
96 if fields_modified.is_empty() {
97 return;
98 }
99
100 let fields_modified_set = fields_modified.iter().collect::<HashSet<_>>();
103 for index in indices.iter_mut() {
104 if index
105 .fields
106 .iter()
107 .any(|field_id| fields_modified_set.contains(&u32::try_from(*field_id).unwrap()))
108 && let Some(fragment_bitmap) = &mut index.fragment_bitmap
109 {
110 for fragment_id in updated_fragments.iter().map(|f| f.id as u32) {
111 fragment_bitmap.remove(fragment_id);
112 }
113 }
114 }
115 }
116
117 fn fragment_field_paths(frag: &Fragment) -> HashMap<i32, &str> {
120 let mut map = HashMap::new();
121 for file in &frag.files {
122 for &field_id in file.fields.iter() {
123 if field_id >= 0 {
124 map.insert(field_id, file.path.as_str());
125 }
126 }
127 }
128 map
129 }
130
131 pub(super) fn prune_merge_rewritten_fields_from_indices(
138 indices: &mut [IndexMetadata],
139 prev_fragments: &[Fragment],
140 new_fragments: &[Fragment],
141 ) {
142 let prev_by_id: HashMap<u64, &Fragment> =
143 prev_fragments.iter().map(|f| (f.id, f)).collect();
144 for new_frag in new_fragments {
145 let Some(prev) = prev_by_id.get(&new_frag.id) else {
146 continue; };
148 let prev_paths = Self::fragment_field_paths(prev);
149 let new_paths = Self::fragment_field_paths(new_frag);
150 let changed: Vec<u32> = prev_paths
152 .iter()
153 .filter(|(field_id, prev_path)| {
154 new_paths
155 .get(*field_id)
156 .is_some_and(|new_path| new_path != *prev_path)
157 })
158 .map(|(field_id, _)| *field_id as u32)
159 .collect();
160 if changed.is_empty() {
161 continue;
162 }
163 Self::prune_updated_fields_from_indices(
164 indices,
165 std::slice::from_ref(new_frag),
166 &changed,
167 );
168 }
169 }
170
171 pub(super) fn prune_overlay_stale_fields_from_indices(
179 indices: &mut [IndexMetadata],
180 groups: &[RewriteGroup],
181 ) {
182 for group in groups {
183 let mut overlaid_field_versions: HashMap<i32, u64> = HashMap::new();
185 for old_frag in &group.old_fragments {
186 for overlay in &old_frag.overlays {
187 for &field_id in overlay.data_file.fields.iter() {
188 if field_id < 0 {
189 continue;
191 }
192 let entry = overlaid_field_versions.entry(field_id).or_insert(0);
193 *entry = (*entry).max(overlay.committed_version);
194 }
195 }
196 }
197 if overlaid_field_versions.is_empty() {
198 continue;
199 }
200
201 let new_fragment_ids = group
202 .new_fragments
203 .iter()
204 .map(|f| f.id as u32)
205 .collect::<Vec<_>>();
206 for index in indices.iter_mut() {
207 let is_stale = index.fields.iter().any(|field_id| {
208 overlaid_field_versions
209 .get(field_id)
210 .is_some_and(|&overlay_version| overlay_version > index.dataset_version)
211 });
212 if is_stale && let Some(fragment_bitmap) = &mut index.fragment_bitmap {
213 for new_id in &new_fragment_ids {
214 fragment_bitmap.remove(*new_id);
215 }
216 }
217 }
218 }
219 }
220
221 pub(crate) fn retain_relevant_indices(
222 indices: &mut Vec<IndexMetadata>,
223 schema: &Schema,
224 fragments: &[Fragment],
225 ) {
226 let field_ids = schema
227 .fields_pre_order()
228 .map(|f| f.id)
229 .collect::<HashSet<_>>();
230
231 indices.retain(|existing_index| {
233 existing_index
234 .fields
235 .iter()
236 .all(|field_id| field_ids.contains(field_id))
237 || is_system_index(existing_index)
238 });
239
240 let mut indices_by_name: std::collections::HashMap<String, Vec<&IndexMetadata>> =
241 std::collections::HashMap::new();
242
243 for index in indices.iter() {
244 if index.name != FRAG_REUSE_INDEX_NAME {
245 indices_by_name
246 .entry(index.name.clone())
247 .or_default()
248 .push(index);
249 }
250 }
251
252 let mut uuids_to_keep = std::collections::HashSet::new();
253
254 let existing_fragments = fragments
255 .iter()
256 .map(|f| f.id as u32)
257 .collect::<RoaringBitmap>();
258
259 for (_, same_name_indices) in indices_by_name {
260 let (unknown_coverage, same_name_indices): (Vec<_>, Vec<_>) = same_name_indices
264 .into_iter()
265 .partition(|index| index.fragment_bitmap.is_none());
266 for index in unknown_coverage {
267 uuids_to_keep.insert(index.uuid);
268 }
269
270 if same_name_indices.len() > 1 {
271 let (empty_indices, non_empty_indices): (Vec<_>, Vec<_>) =
272 same_name_indices.iter().partition(|index| {
273 index
274 .effective_fragment_bitmap(&existing_fragments)
275 .as_ref()
276 .is_none_or(|bitmap| bitmap.is_empty())
277 });
278
279 if non_empty_indices.is_empty() {
280 let mut sorted_indices = empty_indices;
290 sorted_indices.sort_by_key(|index: &&IndexMetadata| index.dataset_version);
291
292 if let Some(oldest) = sorted_indices.first() {
293 uuids_to_keep.insert(oldest.uuid);
294 }
295 } else {
296 for index in non_empty_indices {
297 uuids_to_keep.insert(index.uuid);
298 }
299 }
300 } else {
301 if let Some(index) = same_name_indices.first() {
304 uuids_to_keep.insert(index.uuid);
305 }
306 }
307 }
308
309 indices.retain(|index| {
310 index.name == FRAG_REUSE_INDEX_NAME || uuids_to_keep.contains(&index.uuid)
311 });
312 }
313
314 pub(super) fn recalculate_fragment_bitmap(
315 old: &RoaringBitmap,
316 groups: &[RewriteGroup],
317 ) -> Result<RoaringBitmap> {
318 let mut new_bitmap = old.clone();
319 for group in groups {
320 let any_in_index = group
321 .old_fragments
322 .iter()
323 .any(|frag| old.contains(frag.id as u32));
324 let all_in_index = group
325 .old_fragments
326 .iter()
327 .all(|frag| old.contains(frag.id as u32));
328 if any_in_index {
333 if all_in_index {
334 for frag_id in group.old_fragments.iter().map(|frag| frag.id as u32) {
335 new_bitmap.remove(frag_id);
336 }
337 new_bitmap.extend(group.new_fragments.iter().map(|frag| frag.id as u32));
338 } else {
339 return Err(Error::invalid_input(
340 "The compaction plan included a rewrite group that was a split of indexed and non-indexed data",
341 ));
342 }
343 }
344 }
345 Ok(new_bitmap)
346 }
347
348 pub(super) fn handle_rewrite_indices(
349 indices: &mut [IndexMetadata],
350 rewritten_indices: &[RewrittenIndex],
351 groups: &[RewriteGroup],
352 ) -> Result<()> {
353 let mut modified_indices = HashSet::new();
354
355 for rewritten_index in rewritten_indices {
356 if !modified_indices.insert(rewritten_index.old_id) {
357 return Err(Error::invalid_input(format!(
358 "An invalid compaction plan must have been generated because multiple tasks modified the same index: {}",
359 rewritten_index.old_id
360 )));
361 }
362
363 let Some(index) = indices
365 .iter_mut()
366 .find(|idx| idx.uuid == rewritten_index.old_id)
367 else {
368 continue;
369 };
370
371 index.fragment_bitmap = Some(Self::recalculate_fragment_bitmap(
372 index.fragment_bitmap.as_ref().ok_or_else(|| {
373 Error::invalid_input(format!(
374 "Cannot rewrite index {} which did not store fragment bitmap",
375 index.uuid
376 ))
377 })?,
378 groups,
379 )?);
380 index.uuid = rewritten_index.new_id;
381 index.files = rewritten_index.new_index_files.clone();
385 }
386 Ok(())
387 }
388
389 pub(super) fn handle_rewrite_fragments(
390 final_fragments: &mut Vec<Fragment>,
391 groups: &[RewriteGroup],
392 fragment_id: &mut u64,
393 version: u64,
394 _next_row_id: Option<&u64>,
395 ) -> Result<()> {
396 for group in groups {
397 let replace_range = {
399 let start = final_fragments
400 .iter()
401 .enumerate()
402 .find(|(_, f)| f.id == group.old_fragments[0].id)
403 .ok_or_else(|| {
404 Error::commit_conflict_source(
405 version,
406 format!(
407 "dataset does not contain a fragment a rewrite operation wants to replace: id={}",
408 group.old_fragments[0].id
409 )
410 .into(),
411 )
412 })?
413 .0;
414
415 let mut i = 1;
417 loop {
418 if i == group.old_fragments.len() {
419 break Some(start..start + i);
420 }
421 if final_fragments[start + i].id != group.old_fragments[i].id {
422 break None;
423 }
424 i += 1;
425 }
426 };
427
428 let new_fragments = Self::fragments_with_ids(group.new_fragments.clone(), fragment_id)
429 .collect::<Vec<_>>();
430
431 if let Some(replace_range) = replace_range {
436 final_fragments.splice(replace_range, new_fragments);
438 } else {
439 for fragment in group.old_fragments.iter() {
441 final_fragments.retain(|f| f.id != fragment.id);
442 }
443 final_fragments.extend(new_fragments);
444 }
445 }
446 Ok(())
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453 use crate::transaction::test_support::overlay_with_field;
454 use uuid::Uuid;
455
456 #[test]
457 fn test_rewrite_fragments() {
458 let existing_fragments: Vec<Fragment> = (0..10).map(Fragment::new).collect();
459
460 let mut final_fragments = existing_fragments;
461 let rewrite_groups = vec![
462 RewriteGroup {
465 old_fragments: vec![Fragment::new(1), Fragment::new(2)],
466 new_fragments: vec![Fragment::new(15), Fragment::new(16)],
468 },
469 RewriteGroup {
471 old_fragments: vec![Fragment::new(5), Fragment::new(8)],
472 new_fragments: vec![Fragment::new(0)],
475 },
476 ];
477
478 let mut fragment_id = 20;
479 let version = 0;
480
481 Transaction::handle_rewrite_fragments(
482 &mut final_fragments,
483 &rewrite_groups,
484 &mut fragment_id,
485 version,
486 None,
487 )
488 .unwrap();
489
490 assert_eq!(fragment_id, 21);
491
492 let expected_fragments: Vec<Fragment> = vec![
493 Fragment::new(0),
494 Fragment::new(15),
495 Fragment::new(16),
496 Fragment::new(3),
497 Fragment::new(4),
498 Fragment::new(6),
499 Fragment::new(7),
500 Fragment::new(9),
501 Fragment::new(20),
502 ];
503
504 assert_eq!(final_fragments, expected_fragments);
505 }
506
507 #[test]
508 fn test_retain_indices_removes_missing_fields() {
509 let schema = create_test_schema(&[1, 2]);
510 let fragments = vec![Fragment::new(1), Fragment::new(2)];
511
512 let mut indices = vec![
513 create_test_index("idx1", 1, 1, Some(RoaringBitmap::from_iter([1])), false),
514 create_test_index("idx2", 2, 1, Some(RoaringBitmap::from_iter([1])), false),
515 create_test_index("idx3", 99, 1, Some(RoaringBitmap::from_iter([1])), false), ];
517
518 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
519
520 assert_eq!(indices.len(), 2);
521 assert!(indices.iter().all(|idx| idx.fields[0] != 99));
522 }
523
524 #[test]
525 fn test_retain_indices_keeps_system_indices() {
526 use crate::system_index::mem_wal::MEM_WAL_INDEX_NAME;
527
528 let schema = create_test_schema(&[1, 2]);
529 let fragments = vec![Fragment::new(1)];
530
531 let mut indices = vec![
532 create_system_index(FRAG_REUSE_INDEX_NAME, 99), create_system_index(MEM_WAL_INDEX_NAME, 99), create_test_index("regular_idx", 99, 1, Some(RoaringBitmap::new()), false), ];
536
537 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
538
539 assert_eq!(indices.len(), 2);
540 assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
541 assert!(indices.iter().any(|idx| idx.name == MEM_WAL_INDEX_NAME));
542 }
543
544 #[test]
545 fn test_retain_indices_keeps_fragment_reuse_index() {
546 let schema = create_test_schema(&[1]);
547 let fragments = vec![Fragment::new(1)];
548
549 let mut indices = vec![
550 create_system_index(FRAG_REUSE_INDEX_NAME, 1),
551 create_test_index("other_idx", 1, 1, Some(RoaringBitmap::new()), false),
552 ];
553
554 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
555
556 assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
558 }
559
560 #[test]
561 fn test_retain_single_empty_scalar_index() {
562 let schema = create_test_schema(&[1]);
563 let fragments = vec![Fragment::new(1)];
564
565 let mut indices = vec![create_test_index(
566 "scalar_idx",
567 1,
568 1,
569 Some(RoaringBitmap::new()), false,
571 )];
572
573 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
574
575 assert_eq!(indices.len(), 1);
577 }
578
579 #[test]
580 fn test_retain_single_empty_vector_index_is_kept() {
581 let schema = create_test_schema(&[1]);
582 let fragments = vec![Fragment::new(1)];
583
584 let mut indices = vec![create_test_index(
585 "vector_idx",
586 1,
587 1,
588 Some(RoaringBitmap::new()), true,
590 )];
591
592 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
593
594 assert_eq!(indices.len(), 1);
597 }
598
599 #[test]
600 fn test_retain_single_nonempty_index() {
601 let schema = create_test_schema(&[1]);
602 let fragments = vec![Fragment::new(1)];
603
604 let mut scalar_indices = vec![create_test_index(
605 "scalar_idx",
606 1,
607 1,
608 Some(RoaringBitmap::from_iter([1])),
609 false,
610 )];
611
612 let mut vector_indices = vec![create_test_index(
613 "vector_idx",
614 1,
615 1,
616 Some(RoaringBitmap::from_iter([1])),
617 true,
618 )];
619
620 Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
621 Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
622
623 assert_eq!(scalar_indices.len(), 1);
625 assert_eq!(vector_indices.len(), 1);
626 }
627
628 #[test]
629 fn test_retain_single_index_with_none_bitmap() {
630 let schema = create_test_schema(&[1]);
631 let fragments = vec![Fragment::new(1)];
632
633 let mut scalar_indices = vec![create_test_index("scalar_idx", 1, 1, None, false)];
634 let mut vector_indices = vec![create_test_index("vector_idx", 1, 1, None, true)];
635
636 Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
637 Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
638
639 assert_eq!(scalar_indices.len(), 1);
642 assert_eq!(vector_indices.len(), 1);
643 }
644
645 #[test]
646 fn test_retain_unknown_coverage_alongside_nonempty_sibling() {
647 let schema = create_test_schema(&[1]);
648 let fragments = vec![Fragment::new(1), Fragment::new(2)];
649
650 let mut indices = vec![
651 create_test_index("idx", 1, 1, None, false), create_test_index("idx", 1, 2, Some(RoaringBitmap::from_iter([2])), false),
653 ];
654
655 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
656
657 assert_eq!(indices.len(), 2);
661 assert!(indices.iter().any(|idx| idx.fragment_bitmap.is_none()));
662 }
663
664 #[test]
665 fn test_retain_multiple_empty_scalar_indices_keeps_oldest() {
666 let schema = create_test_schema(&[1]);
667 let fragments = vec![Fragment::new(1)];
668
669 let mut indices = vec![
670 create_test_index("idx", 1, 3, Some(RoaringBitmap::new()), false),
671 create_test_index("idx", 1, 1, Some(RoaringBitmap::new()), false), create_test_index("idx", 1, 2, Some(RoaringBitmap::new()), false),
673 ];
674
675 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
676
677 assert_eq!(indices.len(), 1);
679 assert_eq!(indices[0].dataset_version, 1);
680 }
681
682 #[test]
683 fn test_retain_multiple_empty_vector_indices_keeps_oldest() {
684 let schema = create_test_schema(&[1]);
685 let fragments = vec![Fragment::new(1)];
686
687 let mut indices = vec![
688 create_test_index("vec_idx", 1, 1, Some(RoaringBitmap::new()), true),
689 create_test_index("vec_idx", 1, 2, Some(RoaringBitmap::new()), true),
690 create_test_index("vec_idx", 1, 3, Some(RoaringBitmap::new()), true),
691 ];
692
693 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
694
695 assert_eq!(indices.len(), 1);
698 assert_eq!(indices[0].dataset_version, 1);
699 }
700
701 #[test]
702 fn test_retain_mixed_empty_nonempty_keeps_nonempty() {
703 let schema = create_test_schema(&[1]);
704 let fragments = vec![Fragment::new(1)];
705
706 let mut indices = vec![
707 create_test_index("idx", 1, 1, Some(RoaringBitmap::new()), false), create_test_index("idx", 1, 2, Some(RoaringBitmap::from_iter([1])), false), create_test_index("idx", 1, 3, Some(RoaringBitmap::new()), false), create_test_index("idx", 1, 4, Some(RoaringBitmap::from_iter([1])), false), ];
712
713 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
714
715 assert_eq!(indices.len(), 2);
717 assert!(
718 indices
719 .iter()
720 .all(|idx| idx.dataset_version == 2 || idx.dataset_version == 4)
721 );
722 }
723
724 #[test]
725 fn test_retain_mixed_empty_nonempty_vector_keeps_nonempty() {
726 let schema = create_test_schema(&[1]);
727 let fragments = vec![Fragment::new(1)];
728
729 let mut indices = vec![
730 create_test_index("vec_idx", 1, 1, Some(RoaringBitmap::new()), true), create_test_index("vec_idx", 1, 2, Some(RoaringBitmap::from_iter([1])), true), create_test_index("vec_idx", 1, 3, Some(RoaringBitmap::new()), true), ];
734
735 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
736
737 assert_eq!(indices.len(), 1);
739 assert_eq!(indices[0].dataset_version, 2);
740 }
741
742 #[test]
743 fn test_retain_fragment_bitmap_with_nonexistent_fragments() {
744 let schema = create_test_schema(&[1]);
745 let fragments = vec![Fragment::new(1), Fragment::new(2)]; let mut indices = vec![create_test_index(
748 "idx",
749 1,
750 1,
751 Some(RoaringBitmap::from_iter([1, 2, 3, 4])), false,
753 )];
754
755 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
756
757 assert_eq!(indices.len(), 1);
759 assert_eq!(
761 indices[0].fragment_bitmap.as_ref().unwrap(),
762 &RoaringBitmap::from_iter([1, 2, 3, 4])
763 );
764 }
765
766 #[test]
767 fn test_retain_effective_empty_bitmap_single_index() {
768 let schema = create_test_schema(&[1]);
769 let fragments = vec![Fragment::new(5), Fragment::new(6)];
770
771 let mut scalar_indices = vec![create_test_index(
773 "scalar_idx",
774 1,
775 1,
776 Some(RoaringBitmap::from_iter([1, 2, 3])),
777 false,
778 )];
779
780 let mut vector_indices = vec![create_test_index(
781 "vector_idx",
782 1,
783 1,
784 Some(RoaringBitmap::from_iter([1, 2, 3])),
785 true,
786 )];
787
788 Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
789 Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
790
791 assert_eq!(scalar_indices.len(), 1);
794 assert_eq!(vector_indices.len(), 1);
795 }
796
797 #[test]
798 fn test_retain_different_index_names() {
799 let schema = create_test_schema(&[1]);
800 let fragments = vec![Fragment::new(1)];
801
802 let mut indices = vec![
803 create_test_index("idx_a", 1, 1, Some(RoaringBitmap::new()), false),
804 create_test_index("idx_b", 1, 1, Some(RoaringBitmap::new()), true),
805 create_test_index("idx_c", 1, 1, Some(RoaringBitmap::from_iter([1])), false),
806 ];
807
808 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
809
810 assert_eq!(indices.len(), 3);
813 assert!(indices.iter().any(|idx| idx.name == "idx_a"));
814 assert!(indices.iter().any(|idx| idx.name == "idx_b"));
815 assert!(indices.iter().any(|idx| idx.name == "idx_c"));
816 }
817
818 #[test]
819 fn test_retain_empty_indices_vec() {
820 let schema = create_test_schema(&[1]);
821 let fragments = vec![Fragment::new(1)];
822
823 let mut indices: Vec<IndexMetadata> = vec![];
824
825 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
826
827 assert_eq!(indices.len(), 0);
828 }
829
830 #[test]
831 fn test_retain_all_indices_removed() {
832 let schema = create_test_schema(&[1]);
833 let fragments = vec![Fragment::new(1)];
834
835 let mut indices = vec![
836 create_test_index("vec1", 1, 1, Some(RoaringBitmap::new()), true),
837 create_test_index("vec2", 1, 1, Some(RoaringBitmap::new()), true),
838 create_test_index("idx3", 99, 1, Some(RoaringBitmap::from_iter([1])), false), ];
840
841 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
842
843 assert_eq!(indices.len(), 2);
846 assert!(!indices.iter().any(|idx| idx.name == "idx3"));
847 }
848
849 #[test]
850 fn test_retain_complex_scenario() {
851 let schema = create_test_schema(&[1, 2]);
852 let fragments = vec![Fragment::new(1), Fragment::new(2)];
853
854 let mut indices = vec![
855 create_system_index(FRAG_REUSE_INDEX_NAME, 1),
857 create_test_index("idx_a", 1, 3, Some(RoaringBitmap::new()), false),
859 create_test_index("idx_a", 1, 1, Some(RoaringBitmap::new()), false), create_test_index("idx_a", 1, 2, Some(RoaringBitmap::new()), false),
861 create_test_index("vec_b", 1, 1, Some(RoaringBitmap::new()), true),
863 create_test_index("vec_b", 1, 2, Some(RoaringBitmap::new()), true),
864 create_test_index("idx_c", 2, 1, Some(RoaringBitmap::new()), false),
866 create_test_index("idx_c", 2, 2, Some(RoaringBitmap::from_iter([1])), false), create_test_index("idx_c", 2, 3, Some(RoaringBitmap::from_iter([2])), false), create_test_index("idx_d", 1, 1, Some(RoaringBitmap::from_iter([1, 2])), false),
870 create_test_index("idx_e", 99, 1, Some(RoaringBitmap::from_iter([1])), false),
872 ];
873
874 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
875
876 assert_eq!(indices.len(), 6);
879
880 assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
882
883 let idx_a_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "idx_a").collect();
885 assert_eq!(idx_a_indices.len(), 1);
886 assert_eq!(idx_a_indices[0].dataset_version, 1);
887
888 let vec_b_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "vec_b").collect();
890 assert_eq!(vec_b_indices.len(), 1);
891 assert_eq!(vec_b_indices[0].dataset_version, 1);
892
893 let idx_c_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "idx_c").collect();
895 assert_eq!(idx_c_indices.len(), 2);
896 assert!(
897 idx_c_indices
898 .iter()
899 .all(|idx| idx.dataset_version == 2 || idx.dataset_version == 3)
900 );
901
902 assert!(indices.iter().any(|idx| idx.name == "idx_d"));
904
905 assert!(!indices.iter().any(|idx| idx.name == "idx_e"));
907 }
908
909 #[test]
910 fn test_handle_rewrite_indices_skips_missing_index() {
911 let mut indices = vec![];
913
914 let rewritten_indices = vec![RewrittenIndex {
916 old_id: Uuid::new_v4(),
917 new_id: Uuid::new_v4(),
918 new_index_details: prost_types::Any {
919 type_url: String::new(),
920 value: vec![],
921 },
922 new_index_version: 1,
923 new_index_files: None,
924 }];
925
926 let result = Transaction::handle_rewrite_indices(&mut indices, &rewritten_indices, &[]);
928 assert!(result.is_ok());
929 assert!(indices.is_empty());
930 }
931
932 #[test]
933 fn test_prune_overlay_stale_fields_from_indices() {
934 let mut old_frag = Fragment::new(0);
937 old_frag.overlays = vec![overlay_with_field(1, 5)];
938 let groups = vec![RewriteGroup {
939 old_fragments: vec![old_frag],
940 new_fragments: vec![Fragment::new(7)],
941 }];
942
943 let covering = || Some(RoaringBitmap::from_iter([7u32]));
945 let mut indices = vec![
946 create_test_index("stale", 1, 2, covering(), false),
948 create_test_index("fresh", 1, 5, covering(), false),
951 create_test_index("unrelated", 2, 2, covering(), false),
953 ];
954
955 Transaction::prune_overlay_stale_fields_from_indices(&mut indices, &groups);
956
957 assert!(
958 !indices[0].fragment_bitmap.as_ref().unwrap().contains(7),
959 "stale index must drop the rewritten fragment from its coverage"
960 );
961 assert!(
962 indices[1].fragment_bitmap.as_ref().unwrap().contains(7),
963 "an index built at/after the overlay is not stale"
964 );
965 assert!(
966 indices[2].fragment_bitmap.as_ref().unwrap().contains(7),
967 "an index on an un-overlaid field is unaffected"
968 );
969 }
970
971 fn create_test_index(
973 name: &str,
974 field_id: i32,
975 dataset_version: u64,
976 fragment_bitmap: Option<RoaringBitmap>,
977 is_vector: bool,
978 ) -> IndexMetadata {
979 use prost_types::Any;
980 use std::sync::Arc;
981
982 let index_details = if is_vector {
983 Some(Arc::new(Any {
984 type_url: "type.googleapis.com/lance.index.VectorIndexDetails".to_string(),
985 value: vec![],
986 }))
987 } else {
988 Some(Arc::new(Any {
989 type_url: "type.googleapis.com/lance.index.ScalarIndexDetails".to_string(),
990 value: vec![],
991 }))
992 };
993
994 IndexMetadata {
995 uuid: Uuid::new_v4(),
996 fields: vec![field_id],
997 covering_fields: vec![],
998 name: name.to_string(),
999 dataset_version,
1000 fragment_bitmap,
1001 index_details,
1002 index_version: 1,
1003 created_at: None,
1004 base_id: None,
1005 files: None,
1006 }
1007 }
1008
1009 fn create_system_index(name: &str, field_id: i32) -> IndexMetadata {
1010 use prost_types::Any;
1011 use std::sync::Arc;
1012
1013 IndexMetadata {
1014 uuid: Uuid::new_v4(),
1015 fields: vec![field_id],
1016 covering_fields: vec![],
1017 name: name.to_string(),
1018 dataset_version: 1,
1019 fragment_bitmap: Some(RoaringBitmap::from_iter([1, 2])),
1020 index_details: Some(Arc::new(Any {
1021 type_url: "type.googleapis.com/lance.index.SystemIndexDetails".to_string(),
1022 value: vec![],
1023 })),
1024 index_version: 1,
1025 created_at: None,
1026 base_id: None,
1027 files: None,
1028 }
1029 }
1030
1031 fn create_test_schema(field_ids: &[i32]) -> Schema {
1032 use arrow_schema::{DataType, Field as ArrowField, Schema as ArrowSchema};
1033 use lance_core::datatypes::Schema as LanceSchema;
1034
1035 let fields: Vec<ArrowField> = field_ids
1036 .iter()
1037 .map(|id| ArrowField::new(format!("field_{}", id), DataType::Int32, false))
1038 .collect();
1039
1040 let arrow_schema = ArrowSchema::new(fields);
1041 let mut lance_schema = LanceSchema::try_from(&arrow_schema).unwrap();
1042
1043 for (i, field_id) in field_ids.iter().enumerate() {
1045 lance_schema.mut_field_by_id(i as i32).unwrap().id = *field_id;
1046 }
1047
1048 lance_schema
1049 }
1050}