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 if same_name_indices.len() > 1 {
261 let (empty_indices, non_empty_indices): (Vec<_>, Vec<_>) =
262 same_name_indices.iter().partition(|index| {
263 index
264 .effective_fragment_bitmap(&existing_fragments)
265 .as_ref()
266 .is_none_or(|bitmap| bitmap.is_empty())
267 });
268
269 if non_empty_indices.is_empty() {
270 let mut sorted_indices = empty_indices;
280 sorted_indices.sort_by_key(|index: &&IndexMetadata| index.dataset_version);
281
282 if let Some(oldest) = sorted_indices.first() {
283 uuids_to_keep.insert(oldest.uuid);
284 }
285 } else {
286 for index in non_empty_indices {
287 uuids_to_keep.insert(index.uuid);
288 }
289 }
290 } else {
291 if let Some(index) = same_name_indices.first() {
294 uuids_to_keep.insert(index.uuid);
295 }
296 }
297 }
298
299 indices.retain(|index| {
300 index.name == FRAG_REUSE_INDEX_NAME || uuids_to_keep.contains(&index.uuid)
301 });
302 }
303
304 pub(super) fn recalculate_fragment_bitmap(
305 old: &RoaringBitmap,
306 groups: &[RewriteGroup],
307 ) -> Result<RoaringBitmap> {
308 let mut new_bitmap = old.clone();
309 for group in groups {
310 let any_in_index = group
311 .old_fragments
312 .iter()
313 .any(|frag| old.contains(frag.id as u32));
314 let all_in_index = group
315 .old_fragments
316 .iter()
317 .all(|frag| old.contains(frag.id as u32));
318 if any_in_index {
323 if all_in_index {
324 for frag_id in group.old_fragments.iter().map(|frag| frag.id as u32) {
325 new_bitmap.remove(frag_id);
326 }
327 new_bitmap.extend(group.new_fragments.iter().map(|frag| frag.id as u32));
328 } else {
329 return Err(Error::invalid_input(
330 "The compaction plan included a rewrite group that was a split of indexed and non-indexed data",
331 ));
332 }
333 }
334 }
335 Ok(new_bitmap)
336 }
337
338 pub(super) fn handle_rewrite_indices(
339 indices: &mut [IndexMetadata],
340 rewritten_indices: &[RewrittenIndex],
341 groups: &[RewriteGroup],
342 ) -> Result<()> {
343 let mut modified_indices = HashSet::new();
344
345 for rewritten_index in rewritten_indices {
346 if !modified_indices.insert(rewritten_index.old_id) {
347 return Err(Error::invalid_input(format!(
348 "An invalid compaction plan must have been generated because multiple tasks modified the same index: {}",
349 rewritten_index.old_id
350 )));
351 }
352
353 let Some(index) = indices
355 .iter_mut()
356 .find(|idx| idx.uuid == rewritten_index.old_id)
357 else {
358 continue;
359 };
360
361 index.fragment_bitmap = Some(Self::recalculate_fragment_bitmap(
362 index.fragment_bitmap.as_ref().ok_or_else(|| {
363 Error::invalid_input(format!(
364 "Cannot rewrite index {} which did not store fragment bitmap",
365 index.uuid
366 ))
367 })?,
368 groups,
369 )?);
370 index.uuid = rewritten_index.new_id;
371 index.files = rewritten_index.new_index_files.clone();
375 }
376 Ok(())
377 }
378
379 pub(super) fn handle_rewrite_fragments(
380 final_fragments: &mut Vec<Fragment>,
381 groups: &[RewriteGroup],
382 fragment_id: &mut u64,
383 version: u64,
384 _next_row_id: Option<&u64>,
385 ) -> Result<()> {
386 for group in groups {
387 let replace_range = {
389 let start = final_fragments
390 .iter()
391 .enumerate()
392 .find(|(_, f)| f.id == group.old_fragments[0].id)
393 .ok_or_else(|| {
394 Error::commit_conflict_source(
395 version,
396 format!(
397 "dataset does not contain a fragment a rewrite operation wants to replace: id={}",
398 group.old_fragments[0].id
399 )
400 .into(),
401 )
402 })?
403 .0;
404
405 let mut i = 1;
407 loop {
408 if i == group.old_fragments.len() {
409 break Some(start..start + i);
410 }
411 if final_fragments[start + i].id != group.old_fragments[i].id {
412 break None;
413 }
414 i += 1;
415 }
416 };
417
418 let new_fragments = Self::fragments_with_ids(group.new_fragments.clone(), fragment_id)
419 .collect::<Vec<_>>();
420
421 if let Some(replace_range) = replace_range {
426 final_fragments.splice(replace_range, new_fragments);
428 } else {
429 for fragment in group.old_fragments.iter() {
431 final_fragments.retain(|f| f.id != fragment.id);
432 }
433 final_fragments.extend(new_fragments);
434 }
435 }
436 Ok(())
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443 use crate::transaction::test_support::overlay_with_field;
444 use uuid::Uuid;
445
446 #[test]
447 fn test_rewrite_fragments() {
448 let existing_fragments: Vec<Fragment> = (0..10).map(Fragment::new).collect();
449
450 let mut final_fragments = existing_fragments;
451 let rewrite_groups = vec![
452 RewriteGroup {
455 old_fragments: vec![Fragment::new(1), Fragment::new(2)],
456 new_fragments: vec![Fragment::new(15), Fragment::new(16)],
458 },
459 RewriteGroup {
461 old_fragments: vec![Fragment::new(5), Fragment::new(8)],
462 new_fragments: vec![Fragment::new(0)],
465 },
466 ];
467
468 let mut fragment_id = 20;
469 let version = 0;
470
471 Transaction::handle_rewrite_fragments(
472 &mut final_fragments,
473 &rewrite_groups,
474 &mut fragment_id,
475 version,
476 None,
477 )
478 .unwrap();
479
480 assert_eq!(fragment_id, 21);
481
482 let expected_fragments: Vec<Fragment> = vec![
483 Fragment::new(0),
484 Fragment::new(15),
485 Fragment::new(16),
486 Fragment::new(3),
487 Fragment::new(4),
488 Fragment::new(6),
489 Fragment::new(7),
490 Fragment::new(9),
491 Fragment::new(20),
492 ];
493
494 assert_eq!(final_fragments, expected_fragments);
495 }
496
497 #[test]
498 fn test_retain_indices_removes_missing_fields() {
499 let schema = create_test_schema(&[1, 2]);
500 let fragments = vec![Fragment::new(1), Fragment::new(2)];
501
502 let mut indices = vec![
503 create_test_index("idx1", 1, 1, Some(RoaringBitmap::from_iter([1])), false),
504 create_test_index("idx2", 2, 1, Some(RoaringBitmap::from_iter([1])), false),
505 create_test_index("idx3", 99, 1, Some(RoaringBitmap::from_iter([1])), false), ];
507
508 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
509
510 assert_eq!(indices.len(), 2);
511 assert!(indices.iter().all(|idx| idx.fields[0] != 99));
512 }
513
514 #[test]
515 fn test_retain_indices_keeps_system_indices() {
516 use crate::system_index::mem_wal::MEM_WAL_INDEX_NAME;
517
518 let schema = create_test_schema(&[1, 2]);
519 let fragments = vec![Fragment::new(1)];
520
521 let mut indices = vec![
522 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), ];
526
527 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
528
529 assert_eq!(indices.len(), 2);
530 assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
531 assert!(indices.iter().any(|idx| idx.name == MEM_WAL_INDEX_NAME));
532 }
533
534 #[test]
535 fn test_retain_indices_keeps_fragment_reuse_index() {
536 let schema = create_test_schema(&[1]);
537 let fragments = vec![Fragment::new(1)];
538
539 let mut indices = vec![
540 create_system_index(FRAG_REUSE_INDEX_NAME, 1),
541 create_test_index("other_idx", 1, 1, Some(RoaringBitmap::new()), false),
542 ];
543
544 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
545
546 assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
548 }
549
550 #[test]
551 fn test_retain_single_empty_scalar_index() {
552 let schema = create_test_schema(&[1]);
553 let fragments = vec![Fragment::new(1)];
554
555 let mut indices = vec![create_test_index(
556 "scalar_idx",
557 1,
558 1,
559 Some(RoaringBitmap::new()), false,
561 )];
562
563 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
564
565 assert_eq!(indices.len(), 1);
567 }
568
569 #[test]
570 fn test_retain_single_empty_vector_index_is_kept() {
571 let schema = create_test_schema(&[1]);
572 let fragments = vec![Fragment::new(1)];
573
574 let mut indices = vec![create_test_index(
575 "vector_idx",
576 1,
577 1,
578 Some(RoaringBitmap::new()), true,
580 )];
581
582 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
583
584 assert_eq!(indices.len(), 1);
587 }
588
589 #[test]
590 fn test_retain_single_nonempty_index() {
591 let schema = create_test_schema(&[1]);
592 let fragments = vec![Fragment::new(1)];
593
594 let mut scalar_indices = vec![create_test_index(
595 "scalar_idx",
596 1,
597 1,
598 Some(RoaringBitmap::from_iter([1])),
599 false,
600 )];
601
602 let mut vector_indices = vec![create_test_index(
603 "vector_idx",
604 1,
605 1,
606 Some(RoaringBitmap::from_iter([1])),
607 true,
608 )];
609
610 Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
611 Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
612
613 assert_eq!(scalar_indices.len(), 1);
615 assert_eq!(vector_indices.len(), 1);
616 }
617
618 #[test]
619 fn test_retain_single_index_with_none_bitmap() {
620 let schema = create_test_schema(&[1]);
621 let fragments = vec![Fragment::new(1)];
622
623 let mut scalar_indices = vec![create_test_index("scalar_idx", 1, 1, None, false)];
624 let mut vector_indices = vec![create_test_index("vector_idx", 1, 1, None, true)];
625
626 Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
627 Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
628
629 assert_eq!(scalar_indices.len(), 1);
632 assert_eq!(vector_indices.len(), 1);
633 }
634
635 #[test]
636 fn test_retain_multiple_empty_scalar_indices_keeps_oldest() {
637 let schema = create_test_schema(&[1]);
638 let fragments = vec![Fragment::new(1)];
639
640 let mut indices = vec![
641 create_test_index("idx", 1, 3, Some(RoaringBitmap::new()), false),
642 create_test_index("idx", 1, 1, Some(RoaringBitmap::new()), false), create_test_index("idx", 1, 2, Some(RoaringBitmap::new()), false),
644 ];
645
646 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
647
648 assert_eq!(indices.len(), 1);
650 assert_eq!(indices[0].dataset_version, 1);
651 }
652
653 #[test]
654 fn test_retain_multiple_empty_vector_indices_keeps_oldest() {
655 let schema = create_test_schema(&[1]);
656 let fragments = vec![Fragment::new(1)];
657
658 let mut indices = vec![
659 create_test_index("vec_idx", 1, 1, Some(RoaringBitmap::new()), true),
660 create_test_index("vec_idx", 1, 2, Some(RoaringBitmap::new()), true),
661 create_test_index("vec_idx", 1, 3, Some(RoaringBitmap::new()), true),
662 ];
663
664 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
665
666 assert_eq!(indices.len(), 1);
669 assert_eq!(indices[0].dataset_version, 1);
670 }
671
672 #[test]
673 fn test_retain_mixed_empty_nonempty_keeps_nonempty() {
674 let schema = create_test_schema(&[1]);
675 let fragments = vec![Fragment::new(1)];
676
677 let mut indices = vec![
678 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), ];
683
684 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
685
686 assert_eq!(indices.len(), 2);
688 assert!(
689 indices
690 .iter()
691 .all(|idx| idx.dataset_version == 2 || idx.dataset_version == 4)
692 );
693 }
694
695 #[test]
696 fn test_retain_mixed_empty_nonempty_vector_keeps_nonempty() {
697 let schema = create_test_schema(&[1]);
698 let fragments = vec![Fragment::new(1)];
699
700 let mut indices = vec![
701 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), ];
705
706 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
707
708 assert_eq!(indices.len(), 1);
710 assert_eq!(indices[0].dataset_version, 2);
711 }
712
713 #[test]
714 fn test_retain_fragment_bitmap_with_nonexistent_fragments() {
715 let schema = create_test_schema(&[1]);
716 let fragments = vec![Fragment::new(1), Fragment::new(2)]; let mut indices = vec![create_test_index(
719 "idx",
720 1,
721 1,
722 Some(RoaringBitmap::from_iter([1, 2, 3, 4])), false,
724 )];
725
726 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
727
728 assert_eq!(indices.len(), 1);
730 assert_eq!(
732 indices[0].fragment_bitmap.as_ref().unwrap(),
733 &RoaringBitmap::from_iter([1, 2, 3, 4])
734 );
735 }
736
737 #[test]
738 fn test_retain_effective_empty_bitmap_single_index() {
739 let schema = create_test_schema(&[1]);
740 let fragments = vec![Fragment::new(5), Fragment::new(6)];
741
742 let mut scalar_indices = vec![create_test_index(
744 "scalar_idx",
745 1,
746 1,
747 Some(RoaringBitmap::from_iter([1, 2, 3])),
748 false,
749 )];
750
751 let mut vector_indices = vec![create_test_index(
752 "vector_idx",
753 1,
754 1,
755 Some(RoaringBitmap::from_iter([1, 2, 3])),
756 true,
757 )];
758
759 Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
760 Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
761
762 assert_eq!(scalar_indices.len(), 1);
765 assert_eq!(vector_indices.len(), 1);
766 }
767
768 #[test]
769 fn test_retain_different_index_names() {
770 let schema = create_test_schema(&[1]);
771 let fragments = vec![Fragment::new(1)];
772
773 let mut indices = vec![
774 create_test_index("idx_a", 1, 1, Some(RoaringBitmap::new()), false),
775 create_test_index("idx_b", 1, 1, Some(RoaringBitmap::new()), true),
776 create_test_index("idx_c", 1, 1, Some(RoaringBitmap::from_iter([1])), false),
777 ];
778
779 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
780
781 assert_eq!(indices.len(), 3);
784 assert!(indices.iter().any(|idx| idx.name == "idx_a"));
785 assert!(indices.iter().any(|idx| idx.name == "idx_b"));
786 assert!(indices.iter().any(|idx| idx.name == "idx_c"));
787 }
788
789 #[test]
790 fn test_retain_empty_indices_vec() {
791 let schema = create_test_schema(&[1]);
792 let fragments = vec![Fragment::new(1)];
793
794 let mut indices: Vec<IndexMetadata> = vec![];
795
796 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
797
798 assert_eq!(indices.len(), 0);
799 }
800
801 #[test]
802 fn test_retain_all_indices_removed() {
803 let schema = create_test_schema(&[1]);
804 let fragments = vec![Fragment::new(1)];
805
806 let mut indices = vec![
807 create_test_index("vec1", 1, 1, Some(RoaringBitmap::new()), true),
808 create_test_index("vec2", 1, 1, Some(RoaringBitmap::new()), true),
809 create_test_index("idx3", 99, 1, Some(RoaringBitmap::from_iter([1])), false), ];
811
812 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
813
814 assert_eq!(indices.len(), 2);
817 assert!(!indices.iter().any(|idx| idx.name == "idx3"));
818 }
819
820 #[test]
821 fn test_retain_complex_scenario() {
822 let schema = create_test_schema(&[1, 2]);
823 let fragments = vec![Fragment::new(1), Fragment::new(2)];
824
825 let mut indices = vec![
826 create_system_index(FRAG_REUSE_INDEX_NAME, 1),
828 create_test_index("idx_a", 1, 3, Some(RoaringBitmap::new()), false),
830 create_test_index("idx_a", 1, 1, Some(RoaringBitmap::new()), false), create_test_index("idx_a", 1, 2, Some(RoaringBitmap::new()), false),
832 create_test_index("vec_b", 1, 1, Some(RoaringBitmap::new()), true),
834 create_test_index("vec_b", 1, 2, Some(RoaringBitmap::new()), true),
835 create_test_index("idx_c", 2, 1, Some(RoaringBitmap::new()), false),
837 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),
841 create_test_index("idx_e", 99, 1, Some(RoaringBitmap::from_iter([1])), false),
843 ];
844
845 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
846
847 assert_eq!(indices.len(), 6);
850
851 assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
853
854 let idx_a_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "idx_a").collect();
856 assert_eq!(idx_a_indices.len(), 1);
857 assert_eq!(idx_a_indices[0].dataset_version, 1);
858
859 let vec_b_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "vec_b").collect();
861 assert_eq!(vec_b_indices.len(), 1);
862 assert_eq!(vec_b_indices[0].dataset_version, 1);
863
864 let idx_c_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "idx_c").collect();
866 assert_eq!(idx_c_indices.len(), 2);
867 assert!(
868 idx_c_indices
869 .iter()
870 .all(|idx| idx.dataset_version == 2 || idx.dataset_version == 3)
871 );
872
873 assert!(indices.iter().any(|idx| idx.name == "idx_d"));
875
876 assert!(!indices.iter().any(|idx| idx.name == "idx_e"));
878 }
879
880 #[test]
881 fn test_handle_rewrite_indices_skips_missing_index() {
882 let mut indices = vec![];
884
885 let rewritten_indices = vec![RewrittenIndex {
887 old_id: Uuid::new_v4(),
888 new_id: Uuid::new_v4(),
889 new_index_details: prost_types::Any {
890 type_url: String::new(),
891 value: vec![],
892 },
893 new_index_version: 1,
894 new_index_files: None,
895 }];
896
897 let result = Transaction::handle_rewrite_indices(&mut indices, &rewritten_indices, &[]);
899 assert!(result.is_ok());
900 assert!(indices.is_empty());
901 }
902
903 #[test]
904 fn test_prune_overlay_stale_fields_from_indices() {
905 let mut old_frag = Fragment::new(0);
908 old_frag.overlays = vec![overlay_with_field(1, 5)];
909 let groups = vec![RewriteGroup {
910 old_fragments: vec![old_frag],
911 new_fragments: vec![Fragment::new(7)],
912 }];
913
914 let covering = || Some(RoaringBitmap::from_iter([7u32]));
916 let mut indices = vec![
917 create_test_index("stale", 1, 2, covering(), false),
919 create_test_index("fresh", 1, 5, covering(), false),
922 create_test_index("unrelated", 2, 2, covering(), false),
924 ];
925
926 Transaction::prune_overlay_stale_fields_from_indices(&mut indices, &groups);
927
928 assert!(
929 !indices[0].fragment_bitmap.as_ref().unwrap().contains(7),
930 "stale index must drop the rewritten fragment from its coverage"
931 );
932 assert!(
933 indices[1].fragment_bitmap.as_ref().unwrap().contains(7),
934 "an index built at/after the overlay is not stale"
935 );
936 assert!(
937 indices[2].fragment_bitmap.as_ref().unwrap().contains(7),
938 "an index on an un-overlaid field is unaffected"
939 );
940 }
941
942 fn create_test_index(
944 name: &str,
945 field_id: i32,
946 dataset_version: u64,
947 fragment_bitmap: Option<RoaringBitmap>,
948 is_vector: bool,
949 ) -> IndexMetadata {
950 use prost_types::Any;
951 use std::sync::Arc;
952
953 let index_details = if is_vector {
954 Some(Arc::new(Any {
955 type_url: "type.googleapis.com/lance.index.VectorIndexDetails".to_string(),
956 value: vec![],
957 }))
958 } else {
959 Some(Arc::new(Any {
960 type_url: "type.googleapis.com/lance.index.ScalarIndexDetails".to_string(),
961 value: vec![],
962 }))
963 };
964
965 IndexMetadata {
966 uuid: Uuid::new_v4(),
967 fields: vec![field_id],
968 covering_fields: vec![],
969 name: name.to_string(),
970 dataset_version,
971 fragment_bitmap,
972 index_details,
973 index_version: 1,
974 created_at: None,
975 base_id: None,
976 files: None,
977 }
978 }
979
980 fn create_system_index(name: &str, field_id: i32) -> IndexMetadata {
981 use prost_types::Any;
982 use std::sync::Arc;
983
984 IndexMetadata {
985 uuid: Uuid::new_v4(),
986 fields: vec![field_id],
987 covering_fields: vec![],
988 name: name.to_string(),
989 dataset_version: 1,
990 fragment_bitmap: Some(RoaringBitmap::from_iter([1, 2])),
991 index_details: Some(Arc::new(Any {
992 type_url: "type.googleapis.com/lance.index.SystemIndexDetails".to_string(),
993 value: vec![],
994 })),
995 index_version: 1,
996 created_at: None,
997 base_id: None,
998 files: None,
999 }
1000 }
1001
1002 fn create_test_schema(field_ids: &[i32]) -> Schema {
1003 use arrow_schema::{DataType, Field as ArrowField, Schema as ArrowSchema};
1004 use lance_core::datatypes::Schema as LanceSchema;
1005
1006 let fields: Vec<ArrowField> = field_ids
1007 .iter()
1008 .map(|id| ArrowField::new(format!("field_{}", id), DataType::Int32, false))
1009 .collect();
1010
1011 let arrow_schema = ArrowSchema::new(fields);
1012 let mut lance_schema = LanceSchema::try_from(&arrow_schema).unwrap();
1013
1014 for (i, field_id) in field_ids.iter().enumerate() {
1016 lance_schema.mut_field_by_id(i as i32).unwrap().id = *field_id;
1017 }
1018
1019 lance_schema
1020 }
1021}