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::frag_reuse::lineage::TaggedLineage;
16use crate::system_index::frag_reuse::metadata::is_tagged;
17use crate::system_index::is_system_index;
18use crate::transaction::{
19 DataReplacementGroup, Operation, RewriteGroup, RewrittenIndex, Transaction,
20};
21use lance_core::datatypes::Schema;
22use lance_core::{Error, Result};
23use roaring::RoaringBitmap;
24use std::collections::{HashMap, HashSet};
25use uuid::Uuid;
26
27impl Transaction {
28 pub(super) fn register_pure_rewrite_rows_update_frags_in_indices(
29 indices: &mut [IndexMetadata],
30 pure_update_frag_ids: &[u64],
31 original_fragment_ids: &[u64],
32 fields_for_preserving_frag_bitmap: &[u32],
33 original_overlaid_frags: &HashMap<u32, &Fragment>,
34 schema: &Schema,
35 ) -> Result<()> {
36 if pure_update_frag_ids.is_empty() {
37 return Ok(());
38 }
39
40 let value_updated_field_set = fields_for_preserving_frag_bitmap
41 .iter()
42 .collect::<HashSet<_>>();
43
44 for index in indices.iter_mut() {
45 if index.results_are_row_addrs() {
48 continue;
49 }
50 let index_covers_modified_field = index.fields.iter().any(|field_id| {
51 value_updated_field_set.contains(&u32::try_from(*field_id).unwrap())
52 });
53 if index_covers_modified_field {
54 continue;
55 }
56 let Some(fragment_bitmap) = index.fragment_bitmap.as_ref() else {
57 continue;
58 };
59
60 let index_covers_all_original_fragments = original_fragment_ids
63 .iter()
64 .all(|&fragment_id| fragment_bitmap.contains(fragment_id as u32));
65 if !index_covers_all_original_fragments {
66 continue;
67 }
68
69 let mut overlay_stale = RoaringBitmap::new();
75 collect_overlay_stale_frags(
76 index,
77 original_overlaid_frags,
78 &mut overlay_stale,
79 schema,
80 )?;
81 if !overlay_stale.is_empty() {
82 continue;
83 }
84
85 if let Some(fragment_bitmap) = index.fragment_bitmap.as_mut() {
86 for fragment_id in pure_update_frag_ids.iter().map(|f| *f as u32) {
87 fragment_bitmap.insert(fragment_id);
88 }
89 }
90 }
91 Ok(())
92 }
93
94 pub fn rewritten_physical_columns(
107 operation: &Operation,
108 schema: &Schema,
109 previous_fragments: &[Fragment],
110 ) -> Vec<(u64, Vec<u32>)> {
111 match operation {
112 Operation::Update {
113 updated_fragments,
114 fields_modified,
115 ..
116 } if !fields_modified.is_empty() => updated_fragments
117 .iter()
118 .map(|fragment| (fragment.id, fields_modified.clone()))
119 .collect(),
120 Operation::Merge { fragments, .. } => {
121 Self::merge_rewritten_fields(previous_fragments, fragments)
122 }
123 Operation::DataReplacement { replacements } => replacements
124 .iter()
125 .map(|DataReplacementGroup(fragment_id, new_file)| {
126 let mut fields: Vec<u32> = new_file
127 .schema(schema)
128 .field_ids()
129 .into_iter()
130 .chain(new_file.fields.iter().copied())
131 .filter_map(|id| u32::try_from(id).ok())
132 .collect();
133 fields.sort_unstable();
134 fields.dedup();
135 (*fragment_id, fields)
136 })
137 .collect(),
138 _ => Vec::new(),
139 }
140 }
141
142 pub fn withdraw_in_place_rewrites(
156 indices: &mut Vec<IndexMetadata>,
157 rewrites: Vec<(u64, Vec<u32>)>,
158 schema: &Schema,
159 live: &RoaringBitmap,
160 lineage: Option<&TaggedLineage>,
161 ) {
162 if rewrites.is_empty() {
163 return;
164 }
165 match lineage {
166 Some(lineage) => {
167 let rewrites = Self::expand_rewritten_fields(schema, rewrites);
168 Self::withdraw_rewritten_coverage(indices, Some(lineage), live, &rewrites);
169 }
170 None => {
171 for (fragment, fields) in rewrites {
172 Self::prune_updated_fields_from_indices(
173 indices,
174 &[Fragment::new(fragment)],
175 &fields,
176 );
177 }
178 }
179 }
180 }
181
182 fn expand_rewritten_fields(
186 schema: &Schema,
187 rewrites: Vec<(u64, Vec<u32>)>,
188 ) -> Vec<(u64, Vec<u32>)> {
189 rewrites
190 .into_iter()
191 .map(|(fragment, fields)| (fragment, Self::with_descendants(schema, fields)))
192 .collect()
193 }
194
195 pub fn with_descendants(schema: &Schema, fields: Vec<u32>) -> Vec<u32> {
197 let mut expanded = Vec::with_capacity(fields.len());
198 for id in fields {
199 expanded.push(id);
200 let Ok(field_id) = i32::try_from(id) else {
201 continue;
202 };
203 let mut pending: Vec<&lance_core::datatypes::Field> = schema
204 .field_by_id(field_id)
205 .map(|field| field.children.iter().collect())
206 .unwrap_or_default();
207 while let Some(child) = pending.pop() {
208 if let Ok(child_id) = u32::try_from(child.id) {
209 expanded.push(child_id);
210 }
211 pending.extend(child.children.iter());
212 }
213 }
214 expanded.sort_unstable();
215 expanded.dedup();
216 expanded
217 }
218
219 pub fn withdraw_rewritten_coverage(
260 indices: &mut Vec<IndexMetadata>,
261 lineage: Option<&TaggedLineage>,
262 live: &RoaringBitmap,
263 rewrites: &[(u64, Vec<u32>)],
264 ) {
265 let mut emptied = Vec::new();
266 for index in indices.iter_mut().filter(|index| !is_system_index(index)) {
267 let indexed: HashSet<u32> = index
268 .fields
269 .iter()
270 .filter_map(|field| u32::try_from(*field).ok())
271 .collect();
272 let Some(bitmap) = index.fragment_bitmap.as_mut() else {
273 continue;
274 };
275 let retired = &*bitmap - live;
276 let mut withdrawn = RoaringBitmap::new();
277 for (fragment, fields) in rewrites {
278 if !fields.iter().any(|field| indexed.contains(field)) {
279 continue;
280 }
281 let Ok(rewritten) = u32::try_from(*fragment) else {
282 continue;
283 };
284 if bitmap.contains(rewritten) {
285 withdrawn.insert(rewritten);
286 }
287 for source in retired.iter() {
291 match lineage.and_then(|lineage| lineage.destinations_of(source)) {
292 Some(destinations) if destinations.contains(&rewritten) => {
293 withdrawn.insert(source);
294 }
295 Some(_) => {}
296 None => {
297 withdrawn.insert(source);
298 }
299 }
300 }
301 }
302 if withdrawn.is_empty() {
303 continue;
304 }
305 *bitmap -= &withdrawn;
306 let withdrew_retired = !(&withdrawn & &retired).is_empty();
307 if withdrew_retired && !bitmap.is_empty() {
308 let reader_translates = !bitmap.is_subset(live)
318 || bitmap.iter().any(|fragment| {
319 lineage
320 .and_then(|lineage| lineage.mentions(fragment))
321 .unwrap_or(true)
322 });
323 if !reader_translates {
324 log::warn!(
325 "index {} (segment {}): its remaining coverage {:?} would be loaded \
326 as it is while its file still holds rows of the withdrawn retired \
327 fragments; withdrawing it whole so the segment is rebuilt",
328 index.name,
329 index.uuid,
330 bitmap.iter().collect::<Vec<_>>()
331 );
332 withdrawn |= &*bitmap;
333 bitmap.clear();
334 }
335 }
336 if bitmap.is_empty() {
337 emptied.push(index.uuid);
338 }
339 log::warn!(
340 "index {} (segment {}): withdrew {} fragment(s) of coverage after an in-place \
341 rewrite of an indexed column; the rows they covered are scanned until the index \
342 is optimized",
343 index.name,
344 index.uuid,
345 withdrawn.len()
346 );
347 }
348 Self::retire_emptied_segments(indices, &emptied);
349 }
350
351 fn retire_emptied_segments(indices: &mut Vec<IndexMetadata>, emptied: &[Uuid]) {
356 let removable: Vec<Uuid> = emptied
357 .iter()
358 .copied()
359 .filter(|uuid| {
360 let Some(name) = indices
361 .iter()
362 .find(|index| index.uuid == *uuid)
363 .map(|index| index.name.as_str())
364 else {
365 return false;
366 };
367 indices.iter().any(|sibling| {
368 sibling.name == name
369 && sibling.uuid != *uuid
370 && sibling
371 .fragment_bitmap
372 .as_ref()
373 .is_none_or(|bitmap| !bitmap.is_empty())
374 })
375 })
376 .collect();
377 if removable.is_empty() {
378 return;
379 }
380 indices.retain(|index| {
381 let retired = removable.contains(&index.uuid);
382 if retired {
383 log::info!(
384 "index {} (segment {}): every fragment of its coverage was withdrawn and a \
385 sibling segment still serves the index; the segment leaves the manifest and \
386 its files are reclaimed by index cleanup",
387 index.name,
388 index.uuid
389 );
390 }
391 !retired
392 });
393 }
394
395 pub fn prune_updated_fields_from_indices(
398 indices: &mut [IndexMetadata],
399 updated_fragments: &[Fragment],
400 fields_modified: &[u32],
401 ) {
402 if fields_modified.is_empty() {
403 return;
404 }
405
406 let fragment_ids = updated_fragments.iter().map(|f| f.id as u32).collect();
407 Self::prune_updated_fragment_ids_from_indices(indices, &fragment_ids, fields_modified);
408 }
409
410 pub fn prune_updated_fragment_ids_from_indices(
412 indices: &mut [IndexMetadata],
413 fragment_ids: &RoaringBitmap,
414 fields_modified: &[u32],
415 ) {
416 if fields_modified.is_empty() || fragment_ids.is_empty() {
417 return;
418 }
419 let fields_modified_set = fields_modified.iter().collect::<HashSet<_>>();
422 for index in indices.iter_mut() {
423 if index
424 .fields
425 .iter()
426 .any(|field_id| fields_modified_set.contains(&u32::try_from(*field_id).unwrap()))
427 && let Some(fragment_bitmap) = &mut index.fragment_bitmap
428 {
429 *fragment_bitmap -= fragment_ids;
430 }
431 }
432 }
433
434 fn fragment_field_paths(frag: &Fragment) -> HashMap<i32, &str> {
437 let mut map = HashMap::new();
438 for file in &frag.files {
439 for &field_id in file.fields.iter() {
440 if field_id >= 0 {
441 map.insert(field_id, file.path.as_str());
442 }
443 }
444 }
445 map
446 }
447
448 pub fn merge_rewritten_fields(
452 prev_fragments: &[Fragment],
453 new_fragments: &[Fragment],
454 ) -> Vec<(u64, Vec<u32>)> {
455 let prev_by_id: HashMap<u64, &Fragment> =
456 prev_fragments.iter().map(|f| (f.id, f)).collect();
457 let mut seen_prev_ids = HashSet::new();
461 new_fragments
462 .iter()
463 .filter_map(|new_frag| {
464 let prev = prev_by_id.get(&new_frag.id)?;
465 if !seen_prev_ids.insert(new_frag.id) {
466 return None;
467 }
468 let prev_paths = Self::fragment_field_paths(prev);
469 let new_paths = Self::fragment_field_paths(new_frag);
470 let mut changed: Vec<u32> = prev_paths
471 .iter()
472 .filter(|(field_id, prev_path)| {
473 new_paths
474 .get(*field_id)
475 .is_some_and(|new_path| new_path != *prev_path)
476 })
477 .map(|(field_id, _)| *field_id as u32)
478 .collect();
479 changed.sort_unstable();
480 (!changed.is_empty()).then_some((new_frag.id, changed))
481 })
482 .collect()
483 }
484
485 pub(super) fn prune_overlay_stale_fields_from_indices(
497 indices: &mut [IndexMetadata],
498 groups: &[RewriteGroup],
499 ) {
500 for group in groups {
501 let mut overlaid_field_versions: HashMap<i32, u64> = HashMap::new();
503 for old_frag in &group.old_fragments {
504 for overlay in &old_frag.overlays {
505 for &field_id in overlay.data_file.fields.iter() {
506 if field_id < 0 {
507 continue;
509 }
510 let entry = overlaid_field_versions.entry(field_id).or_insert(0);
511 *entry = (*entry).max(overlay.committed_version);
512 }
513 }
514 }
515 if overlaid_field_versions.is_empty() {
516 continue;
517 }
518
519 let fragment_ids = group
520 .old_fragments
521 .iter()
522 .chain(group.new_fragments.iter())
523 .map(|f| f.id as u32)
524 .collect::<Vec<_>>();
525 for index in indices.iter_mut() {
526 let is_stale = index.fields.iter().any(|field_id| {
527 overlaid_field_versions
528 .get(field_id)
529 .is_some_and(|&overlay_version| overlay_version > index.dataset_version)
530 });
531 if is_stale && let Some(fragment_bitmap) = &mut index.fragment_bitmap {
532 for id in &fragment_ids {
533 fragment_bitmap.remove(*id);
534 }
535 }
536 }
537 }
538 }
539
540 pub(crate) fn retain_relevant_indices(
541 indices: &mut Vec<IndexMetadata>,
542 schema: &Schema,
543 fragments: &[Fragment],
544 ) {
545 let field_ids = schema
546 .fields_pre_order()
547 .map(|f| f.id)
548 .collect::<HashSet<_>>();
549
550 indices.retain(|existing_index| {
552 existing_index
553 .fields
554 .iter()
555 .all(|field_id| field_ids.contains(field_id))
556 || is_system_index(existing_index)
557 });
558
559 let mut indices_by_name: std::collections::HashMap<String, Vec<&IndexMetadata>> =
560 std::collections::HashMap::new();
561
562 for index in indices.iter() {
563 if index.name != FRAG_REUSE_INDEX_NAME {
564 indices_by_name
565 .entry(index.name.clone())
566 .or_default()
567 .push(index);
568 }
569 }
570
571 let mut uuids_to_keep = std::collections::HashSet::new();
572
573 let existing_fragments = fragments
574 .iter()
575 .map(|f| f.id as u32)
576 .collect::<RoaringBitmap>();
577
578 let tagged = indices.iter().any(is_tagged);
587
588 for (_, same_name_indices) in indices_by_name {
589 if tagged {
590 for index in same_name_indices {
591 uuids_to_keep.insert(index.uuid);
592 }
593 continue;
594 }
595 let (unknown_coverage, same_name_indices): (Vec<_>, Vec<_>) = same_name_indices
599 .into_iter()
600 .partition(|index| index.fragment_bitmap.is_none());
601 for index in unknown_coverage {
602 uuids_to_keep.insert(index.uuid);
603 }
604
605 if same_name_indices.len() > 1 {
606 let (empty_indices, non_empty_indices): (Vec<_>, Vec<_>) =
607 same_name_indices.iter().partition(|index| {
608 index
609 .effective_fragment_bitmap(&existing_fragments)
610 .as_ref()
611 .is_none_or(|bitmap| bitmap.is_empty())
612 });
613
614 if non_empty_indices.is_empty() {
615 let mut sorted_indices = empty_indices;
631 sorted_indices.sort_by_key(|index: &&IndexMetadata| {
632 (
633 index.created_at.is_none(),
634 index.created_at,
635 index.dataset_version,
636 )
637 });
638
639 if let Some(oldest) = sorted_indices.first() {
640 uuids_to_keep.insert(oldest.uuid);
641 }
642 } else {
643 for index in non_empty_indices {
644 uuids_to_keep.insert(index.uuid);
645 }
646 }
647 } else {
648 if let Some(index) = same_name_indices.first() {
651 uuids_to_keep.insert(index.uuid);
652 }
653 }
654 }
655
656 indices.retain(|index| {
657 index.name == FRAG_REUSE_INDEX_NAME || uuids_to_keep.contains(&index.uuid)
658 });
659 }
660
661 pub(super) fn ordered_rewrite_groups(
668 groups: &[RewriteGroup],
669 reordered_sources: Option<&RoaringBitmap>,
670 ) -> Result<Vec<RewriteGroup>> {
671 let Some(sources) = reordered_sources else {
672 return Ok(groups.to_vec());
673 };
674 let mut ordered = Vec::new();
675 for group in groups {
676 let covered = group
677 .old_fragments
678 .iter()
679 .filter(|frag| sources.contains(frag.id as u32))
680 .count();
681 if covered == 0 {
682 ordered.push(group.clone());
683 } else if covered != group.old_fragments.len() {
684 return Err(Error::invalid_input(
685 "a rewrite group mixes transition-covered and order-preserving source fragments",
686 ));
687 }
688 }
689 Ok(ordered)
690 }
691
692 pub(super) fn recalculate_fragment_bitmap(
693 old: &RoaringBitmap,
694 groups: &[RewriteGroup],
695 ) -> Result<RoaringBitmap> {
696 let mut new_bitmap = old.clone();
697 for group in groups {
698 let any_in_index = group
699 .old_fragments
700 .iter()
701 .any(|frag| old.contains(frag.id as u32));
702 let all_in_index = group
703 .old_fragments
704 .iter()
705 .all(|frag| old.contains(frag.id as u32));
706 if any_in_index {
711 if all_in_index {
712 for frag_id in group.old_fragments.iter().map(|frag| frag.id as u32) {
713 new_bitmap.remove(frag_id);
714 }
715 new_bitmap.extend(group.new_fragments.iter().map(|frag| frag.id as u32));
716 } else {
717 return Err(Error::invalid_input(
718 "The compaction plan included a rewrite group that was a split of indexed and non-indexed data",
719 ));
720 }
721 }
722 }
723 Ok(new_bitmap)
724 }
725
726 pub(super) fn handle_rewrite_indices(
727 indices: &mut [IndexMetadata],
728 rewritten_indices: &[RewrittenIndex],
729 groups: &[RewriteGroup],
730 ) -> Result<()> {
731 let mut modified_indices = HashSet::new();
732
733 for rewritten_index in rewritten_indices {
734 if !modified_indices.insert(rewritten_index.old_id) {
735 return Err(Error::invalid_input(format!(
736 "An invalid compaction plan must have been generated because multiple tasks modified the same index: {}",
737 rewritten_index.old_id
738 )));
739 }
740
741 let Some(index) = indices
743 .iter_mut()
744 .find(|idx| idx.uuid == rewritten_index.old_id)
745 else {
746 continue;
747 };
748
749 index.fragment_bitmap = Some(Self::recalculate_fragment_bitmap(
750 index.fragment_bitmap.as_ref().ok_or_else(|| {
751 Error::invalid_input(format!(
752 "Cannot rewrite index {} which did not store fragment bitmap",
753 index.uuid
754 ))
755 })?,
756 groups,
757 )?);
758 index.uuid = rewritten_index.new_id;
759 index.files = rewritten_index.new_index_files.clone();
763 }
764 Ok(())
765 }
766
767 fn duplicate_rewrite_error(version: u64, fragment_id: u64) -> Error {
768 Error::commit_conflict_source(
769 version,
770 format!(
771 "a rewrite operation attempts to replace fragment id={} more than once",
772 fragment_id
773 )
774 .into(),
775 )
776 }
777
778 pub(super) fn handle_rewrite_fragments(
779 final_fragments: &mut Vec<Fragment>,
780 groups: &[RewriteGroup],
781 fragment_id: &mut u64,
782 version: u64,
783 _next_row_id: Option<&u64>,
784 ) -> Result<()> {
785 let positions: HashMap<u64, usize> = final_fragments
790 .iter()
791 .enumerate()
792 .map(|(position, fragment)| (fragment.id, position))
793 .collect();
794
795 let mut removed = vec![false; final_fragments.len()];
799 let mut replacements: HashMap<usize, Vec<Fragment>> = HashMap::new();
800 let mut appended: Vec<Fragment> = Vec::new();
801
802 for group in groups {
803 let start = *positions.get(&group.old_fragments[0].id).ok_or_else(|| {
804 Error::commit_conflict_source(
805 version,
806 format!(
807 "dataset does not contain a fragment a rewrite operation wants to replace: id={}",
808 group.old_fragments[0].id
809 )
810 .into(),
811 )
812 })?;
813
814 let contiguous = start + group.old_fragments.len() <= final_fragments.len()
815 && group
816 .old_fragments
817 .iter()
818 .enumerate()
819 .all(|(i, old)| final_fragments[start + i].id == old.id);
820
821 let new_fragments = Self::fragments_with_ids(group.new_fragments.clone(), fragment_id)
822 .collect::<Vec<_>>();
823
824 if contiguous {
829 for (i, old_fragment) in group.old_fragments.iter().enumerate() {
830 let position = start + i;
831 if removed[position] {
832 return Err(Self::duplicate_rewrite_error(version, old_fragment.id));
833 }
834 removed[position] = true;
835 }
836 replacements.insert(start, new_fragments);
837 } else {
838 for old_fragment in group.old_fragments.iter() {
839 let position = positions.get(&old_fragment.id).ok_or_else(|| {
840 Error::commit_conflict_source(
841 version,
842 format!(
843 "dataset does not contain a fragment a rewrite operation wants to replace: id={}",
844 old_fragment.id
845 )
846 .into(),
847 )
848 })?;
849 if removed[*position] {
850 return Err(Self::duplicate_rewrite_error(version, old_fragment.id));
851 }
852 removed[*position] = true;
853 }
854 appended.extend(new_fragments);
855 }
856 }
857
858 let mut rewritten = Vec::with_capacity(final_fragments.len() + appended.len());
859 for (position, fragment) in std::mem::take(final_fragments).into_iter().enumerate() {
860 if let Some(new_fragments) = replacements.remove(&position) {
861 rewritten.extend(new_fragments);
862 }
863 if !removed[position] {
864 rewritten.push(fragment);
865 }
866 }
867 rewritten.extend(appended);
868 *final_fragments = rewritten;
869 Ok(())
870 }
871}
872
873#[cfg(test)]
874mod tests {
875 use super::*;
876 use crate::transaction::test_support::overlay_with_field;
877 use chrono::{TimeZone, Utc};
878 use uuid::Uuid;
879
880 #[test]
881 fn test_ordered_rewrite_groups_split_and_mixed() {
882 use crate::format::pb::fragment_reuse_index_details as pb_fri;
883 let group = |old_ids: &[u64], new_ids: &[u64]| RewriteGroup {
884 old_fragments: old_ids.iter().map(|&id| Fragment::new(id)).collect(),
885 new_fragments: new_ids.iter().map(|&id| Fragment::new(id)).collect(),
886 };
887 let groups = vec![group(&[0, 1], &[10]), group(&[2], &[11])];
888
889 assert_eq!(
891 Transaction::ordered_rewrite_groups(&groups, None)
892 .unwrap()
893 .len(),
894 2
895 );
896
897 let digest = |id: u64| pb_fri::FragmentDigest {
899 id,
900 physical_rows: 4,
901 num_deleted_rows: 0,
902 };
903 let reordered = crate::transaction::reordered_sources(&[pb_fri::Transition {
904 sources: vec![digest(0), digest(1)],
905 destinations: vec![digest(10), digest(10)],
906 mapping: None,
907 }])
908 .unwrap();
909 let ordered = Transaction::ordered_rewrite_groups(&groups, Some(&reordered)).unwrap();
910 assert_eq!(ordered.len(), 1);
911 assert_eq!(ordered[0].old_fragments[0].id, 2);
912
913 let mixed = vec![group(&[1, 2], &[10])];
916 assert!(Transaction::ordered_rewrite_groups(&mixed, Some(&reordered)).is_err());
917 }
918
919 #[test]
920 fn test_rewrite_fragments() {
921 let existing_fragments: Vec<Fragment> = (0..10).map(Fragment::new).collect();
922
923 let mut final_fragments = existing_fragments;
924 let rewrite_groups = vec![
925 RewriteGroup {
928 old_fragments: vec![Fragment::new(1), Fragment::new(2)],
929 new_fragments: vec![Fragment::new(15), Fragment::new(16)],
931 },
932 RewriteGroup {
934 old_fragments: vec![Fragment::new(5), Fragment::new(8)],
935 new_fragments: vec![Fragment::new(0)],
938 },
939 ];
940
941 let mut fragment_id = 20;
942 let version = 0;
943
944 Transaction::handle_rewrite_fragments(
945 &mut final_fragments,
946 &rewrite_groups,
947 &mut fragment_id,
948 version,
949 None,
950 )
951 .unwrap();
952
953 assert_eq!(fragment_id, 21);
954
955 let expected_fragments: Vec<Fragment> = vec![
956 Fragment::new(0),
957 Fragment::new(15),
958 Fragment::new(16),
959 Fragment::new(3),
960 Fragment::new(4),
961 Fragment::new(6),
962 Fragment::new(7),
963 Fragment::new(9),
964 Fragment::new(20),
965 ];
966
967 assert_eq!(final_fragments, expected_fragments);
968 }
969
970 #[test]
971 fn test_rewrite_fragments_missing_fragment() {
972 let mut final_fragments: Vec<Fragment> = (0..4).map(Fragment::new).collect();
973 let rewrite_groups = vec![RewriteGroup {
974 old_fragments: vec![Fragment::new(7)],
975 new_fragments: vec![Fragment::new(0)],
976 }];
977
978 let mut fragment_id = 10;
979 let result = Transaction::handle_rewrite_fragments(
980 &mut final_fragments,
981 &rewrite_groups,
982 &mut fragment_id,
983 42,
984 None,
985 );
986
987 let error = result.unwrap_err();
988 assert!(matches!(error, Error::CommitConflict { .. }));
989 assert!(error.to_string().contains("id=7"));
990 }
991
992 #[test]
993 fn test_rewrite_fragments_non_contiguous_missing_fragment() {
994 let mut final_fragments: Vec<Fragment> = (0..4).map(Fragment::new).collect();
999 let rewrite_groups = vec![RewriteGroup {
1000 old_fragments: vec![Fragment::new(1), Fragment::new(7)],
1001 new_fragments: vec![Fragment::new(0)],
1002 }];
1003
1004 let mut fragment_id = 10;
1005 let result = Transaction::handle_rewrite_fragments(
1006 &mut final_fragments,
1007 &rewrite_groups,
1008 &mut fragment_id,
1009 42,
1010 None,
1011 );
1012
1013 let error = result.unwrap_err();
1014 assert!(matches!(error, Error::CommitConflict { .. }));
1015 assert!(error.to_string().contains("id=7"));
1016 }
1017
1018 #[test]
1019 fn test_rewrite_fragments_past_the_end() {
1020 let mut final_fragments: Vec<Fragment> = (0..4).map(Fragment::new).collect();
1023 let rewrite_groups = vec![RewriteGroup {
1024 old_fragments: vec![Fragment::new(3), Fragment::new(0)],
1025 new_fragments: vec![Fragment::new(10)],
1026 }];
1027
1028 let mut fragment_id = 11;
1029 Transaction::handle_rewrite_fragments(
1030 &mut final_fragments,
1031 &rewrite_groups,
1032 &mut fragment_id,
1033 42,
1034 None,
1035 )
1036 .unwrap();
1037
1038 let expected_fragments: Vec<Fragment> =
1041 vec![Fragment::new(1), Fragment::new(2), Fragment::new(10)];
1042 assert_eq!(final_fragments, expected_fragments);
1043 }
1044
1045 #[test]
1046 fn test_rewrite_fragments_duplicate_fragment() {
1047 let mut final_fragments: Vec<Fragment> = (0..4).map(Fragment::new).collect();
1048 let rewrite_groups = vec![
1049 RewriteGroup {
1050 old_fragments: vec![Fragment::new(1), Fragment::new(2)],
1051 new_fragments: vec![Fragment::new(0)],
1052 },
1053 RewriteGroup {
1054 old_fragments: vec![Fragment::new(2), Fragment::new(3)],
1055 new_fragments: vec![Fragment::new(0)],
1056 },
1057 ];
1058
1059 let mut fragment_id = 10;
1060 let result = Transaction::handle_rewrite_fragments(
1061 &mut final_fragments,
1062 &rewrite_groups,
1063 &mut fragment_id,
1064 42,
1065 None,
1066 );
1067
1068 let error = result.unwrap_err();
1069 assert!(matches!(error, Error::CommitConflict { .. }));
1070 assert!(error.to_string().contains("id=2"));
1071 }
1072
1073 #[test]
1074 fn test_retain_indices_removes_missing_fields() {
1075 let schema = create_test_schema(&[1, 2]);
1076 let fragments = vec![Fragment::new(1), Fragment::new(2)];
1077
1078 let mut indices = vec![
1079 create_test_index("idx1", 1, 1, Some(RoaringBitmap::from_iter([1])), false),
1080 create_test_index("idx2", 2, 1, Some(RoaringBitmap::from_iter([1])), false),
1081 create_test_index("idx3", 99, 1, Some(RoaringBitmap::from_iter([1])), false), ];
1083
1084 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1085
1086 assert_eq!(indices.len(), 2);
1087 assert!(indices.iter().all(|idx| idx.fields[0] != 99));
1088 }
1089
1090 #[test]
1091 fn test_retain_indices_keeps_system_indices() {
1092 use crate::system_index::mem_wal::MEM_WAL_INDEX_NAME;
1093
1094 let schema = create_test_schema(&[1, 2]);
1095 let fragments = vec![Fragment::new(1)];
1096
1097 let mut indices = vec![
1098 legacy_frag_reuse_index(99), create_system_index(MEM_WAL_INDEX_NAME, 99), create_test_index("regular_idx", 99, 1, Some(RoaringBitmap::new()), false), ];
1102
1103 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1104
1105 assert_eq!(indices.len(), 2);
1106 assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
1107 assert!(indices.iter().any(|idx| idx.name == MEM_WAL_INDEX_NAME));
1108 }
1109
1110 #[test]
1111 fn test_retain_indices_keeps_fragment_reuse_index() {
1112 let schema = create_test_schema(&[1]);
1113 let fragments = vec![Fragment::new(1)];
1114
1115 let mut indices = vec![
1116 legacy_frag_reuse_index(1),
1117 create_test_index("other_idx", 1, 1, Some(RoaringBitmap::new()), false),
1118 ];
1119
1120 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1121
1122 assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
1124 }
1125
1126 #[test]
1127 fn test_retain_single_empty_scalar_index() {
1128 let schema = create_test_schema(&[1]);
1129 let fragments = vec![Fragment::new(1)];
1130
1131 let mut indices = vec![create_test_index(
1132 "scalar_idx",
1133 1,
1134 1,
1135 Some(RoaringBitmap::new()), false,
1137 )];
1138
1139 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1140
1141 assert_eq!(indices.len(), 1);
1143 }
1144
1145 #[test]
1146 fn test_retain_single_empty_vector_index_is_kept() {
1147 let schema = create_test_schema(&[1]);
1148 let fragments = vec![Fragment::new(1)];
1149
1150 let mut indices = vec![create_test_index(
1151 "vector_idx",
1152 1,
1153 1,
1154 Some(RoaringBitmap::new()), true,
1156 )];
1157
1158 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1159
1160 assert_eq!(indices.len(), 1);
1163 }
1164
1165 #[test]
1166 fn test_retain_single_nonempty_index() {
1167 let schema = create_test_schema(&[1]);
1168 let fragments = vec![Fragment::new(1)];
1169
1170 let mut scalar_indices = vec![create_test_index(
1171 "scalar_idx",
1172 1,
1173 1,
1174 Some(RoaringBitmap::from_iter([1])),
1175 false,
1176 )];
1177
1178 let mut vector_indices = vec![create_test_index(
1179 "vector_idx",
1180 1,
1181 1,
1182 Some(RoaringBitmap::from_iter([1])),
1183 true,
1184 )];
1185
1186 Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
1187 Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
1188
1189 assert_eq!(scalar_indices.len(), 1);
1191 assert_eq!(vector_indices.len(), 1);
1192 }
1193
1194 #[test]
1195 fn test_retain_single_index_with_none_bitmap() {
1196 let schema = create_test_schema(&[1]);
1197 let fragments = vec![Fragment::new(1)];
1198
1199 let mut scalar_indices = vec![create_test_index("scalar_idx", 1, 1, None, false)];
1200 let mut vector_indices = vec![create_test_index("vector_idx", 1, 1, None, true)];
1201
1202 Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
1203 Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
1204
1205 assert_eq!(scalar_indices.len(), 1);
1208 assert_eq!(vector_indices.len(), 1);
1209 }
1210
1211 #[test]
1212 fn test_retain_unknown_coverage_alongside_nonempty_sibling() {
1213 let schema = create_test_schema(&[1]);
1214 let fragments = vec![Fragment::new(1), Fragment::new(2)];
1215
1216 let mut indices = vec![
1217 create_test_index("idx", 1, 1, None, false), create_test_index("idx", 1, 2, Some(RoaringBitmap::from_iter([2])), false),
1219 ];
1220
1221 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1222
1223 assert_eq!(indices.len(), 2);
1227 assert!(indices.iter().any(|idx| idx.fragment_bitmap.is_none()));
1228 }
1229
1230 #[test]
1231 fn test_retain_multiple_empty_scalar_indices_keeps_oldest() {
1232 let schema = create_test_schema(&[1]);
1233 let fragments = vec![Fragment::new(1)];
1234
1235 let mut indices = vec![
1236 create_test_index("idx", 1, 3, Some(RoaringBitmap::new()), false),
1237 create_test_index("idx", 1, 1, Some(RoaringBitmap::new()), false), create_test_index("idx", 1, 2, Some(RoaringBitmap::new()), false),
1239 ];
1240
1241 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1242
1243 assert_eq!(indices.len(), 1);
1245 assert_eq!(indices[0].dataset_version, 1);
1246 }
1247
1248 #[test]
1249 fn test_retain_multiple_empty_vector_indices_keeps_oldest() {
1250 let schema = create_test_schema(&[1]);
1251 let fragments = vec![Fragment::new(1)];
1252
1253 let mut indices = vec![
1254 create_test_index("vec_idx", 1, 1, Some(RoaringBitmap::new()), true),
1255 create_test_index("vec_idx", 1, 2, Some(RoaringBitmap::new()), true),
1256 create_test_index("vec_idx", 1, 3, Some(RoaringBitmap::new()), true),
1257 ];
1258
1259 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1260
1261 assert_eq!(indices.len(), 1);
1264 assert_eq!(indices[0].dataset_version, 1);
1265 }
1266
1267 #[test]
1268 fn test_retain_multiple_empty_indices_keeps_earliest_created() {
1269 let schema = create_test_schema(&[1]);
1270 let fragments = vec![Fragment::new(1)];
1271 let at = |secs| Some(Utc.timestamp_opt(secs, 0).unwrap());
1272
1273 let mut indices = vec![
1276 create_test_index("idx", 1, 1, Some(RoaringBitmap::new()), false),
1277 create_test_index("idx", 1, 2, Some(RoaringBitmap::new()), false),
1278 create_test_index("idx", 1, 3, Some(RoaringBitmap::new()), false),
1279 ];
1280 indices[0].created_at = at(300);
1281 indices[1].created_at = None;
1282 indices[2].created_at = at(100);
1283
1284 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1285
1286 assert_eq!(indices.len(), 1);
1287 assert_eq!(indices[0].dataset_version, 3);
1288 assert_eq!(indices[0].created_at, at(100));
1289
1290 let mut indices = vec![
1293 create_test_index("idx", 1, 1, Some(RoaringBitmap::new()), false),
1294 create_test_index("idx", 1, 5, Some(RoaringBitmap::new()), false),
1295 create_test_index("idx", 1, 4, Some(RoaringBitmap::new()), false),
1296 ];
1297 indices[0].created_at = None;
1298 indices[1].created_at = at(200);
1299 indices[2].created_at = at(200);
1300
1301 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1302
1303 assert_eq!(indices.len(), 1);
1304 assert_eq!(indices[0].dataset_version, 4);
1305 }
1306
1307 #[test]
1308 fn test_retain_mixed_empty_nonempty_keeps_nonempty() {
1309 let schema = create_test_schema(&[1]);
1310 let fragments = vec![Fragment::new(1)];
1311
1312 let mut indices = vec![
1313 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), ];
1318
1319 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1320
1321 assert_eq!(indices.len(), 2);
1323 assert!(
1324 indices
1325 .iter()
1326 .all(|idx| idx.dataset_version == 2 || idx.dataset_version == 4)
1327 );
1328 }
1329
1330 #[test]
1331 fn test_retain_mixed_empty_nonempty_vector_keeps_nonempty() {
1332 let schema = create_test_schema(&[1]);
1333 let fragments = vec![Fragment::new(1)];
1334
1335 let mut indices = vec![
1336 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), ];
1340
1341 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1342
1343 assert_eq!(indices.len(), 1);
1345 assert_eq!(indices[0].dataset_version, 2);
1346 }
1347
1348 #[test]
1349 fn test_retain_fragment_bitmap_with_nonexistent_fragments() {
1350 let schema = create_test_schema(&[1]);
1351 let fragments = vec![Fragment::new(1), Fragment::new(2)]; let mut indices = vec![create_test_index(
1354 "idx",
1355 1,
1356 1,
1357 Some(RoaringBitmap::from_iter([1, 2, 3, 4])), false,
1359 )];
1360
1361 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1362
1363 assert_eq!(indices.len(), 1);
1365 assert_eq!(
1367 indices[0].fragment_bitmap.as_ref().unwrap(),
1368 &RoaringBitmap::from_iter([1, 2, 3, 4])
1369 );
1370 }
1371
1372 #[test]
1373 fn test_retain_effective_empty_bitmap_single_index() {
1374 let schema = create_test_schema(&[1]);
1375 let fragments = vec![Fragment::new(5), Fragment::new(6)];
1376
1377 let mut scalar_indices = vec![create_test_index(
1379 "scalar_idx",
1380 1,
1381 1,
1382 Some(RoaringBitmap::from_iter([1, 2, 3])),
1383 false,
1384 )];
1385
1386 let mut vector_indices = vec![create_test_index(
1387 "vector_idx",
1388 1,
1389 1,
1390 Some(RoaringBitmap::from_iter([1, 2, 3])),
1391 true,
1392 )];
1393
1394 Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
1395 Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
1396
1397 assert_eq!(scalar_indices.len(), 1);
1400 assert_eq!(vector_indices.len(), 1);
1401 }
1402
1403 #[test]
1404 fn test_retain_different_index_names() {
1405 let schema = create_test_schema(&[1]);
1406 let fragments = vec![Fragment::new(1)];
1407
1408 let mut indices = vec![
1409 create_test_index("idx_a", 1, 1, Some(RoaringBitmap::new()), false),
1410 create_test_index("idx_b", 1, 1, Some(RoaringBitmap::new()), true),
1411 create_test_index("idx_c", 1, 1, Some(RoaringBitmap::from_iter([1])), false),
1412 ];
1413
1414 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1415
1416 assert_eq!(indices.len(), 3);
1419 assert!(indices.iter().any(|idx| idx.name == "idx_a"));
1420 assert!(indices.iter().any(|idx| idx.name == "idx_b"));
1421 assert!(indices.iter().any(|idx| idx.name == "idx_c"));
1422 }
1423
1424 #[test]
1425 fn test_retain_empty_indices_vec() {
1426 let schema = create_test_schema(&[1]);
1427 let fragments = vec![Fragment::new(1)];
1428
1429 let mut indices: Vec<IndexMetadata> = vec![];
1430
1431 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1432
1433 assert_eq!(indices.len(), 0);
1434 }
1435
1436 #[test]
1437 fn test_retain_all_indices_removed() {
1438 let schema = create_test_schema(&[1]);
1439 let fragments = vec![Fragment::new(1)];
1440
1441 let mut indices = vec![
1442 create_test_index("vec1", 1, 1, Some(RoaringBitmap::new()), true),
1443 create_test_index("vec2", 1, 1, Some(RoaringBitmap::new()), true),
1444 create_test_index("idx3", 99, 1, Some(RoaringBitmap::from_iter([1])), false), ];
1446
1447 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1448
1449 assert_eq!(indices.len(), 2);
1452 assert!(!indices.iter().any(|idx| idx.name == "idx3"));
1453 }
1454
1455 #[test]
1456 fn test_retain_complex_scenario() {
1457 let schema = create_test_schema(&[1, 2]);
1458 let fragments = vec![Fragment::new(1), Fragment::new(2)];
1459
1460 let mut indices = vec![
1461 legacy_frag_reuse_index(1),
1463 create_test_index("idx_a", 1, 3, Some(RoaringBitmap::new()), false),
1465 create_test_index("idx_a", 1, 1, Some(RoaringBitmap::new()), false), create_test_index("idx_a", 1, 2, Some(RoaringBitmap::new()), false),
1467 create_test_index("vec_b", 1, 1, Some(RoaringBitmap::new()), true),
1469 create_test_index("vec_b", 1, 2, Some(RoaringBitmap::new()), true),
1470 create_test_index("idx_c", 2, 1, Some(RoaringBitmap::new()), false),
1472 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),
1476 create_test_index("idx_e", 99, 1, Some(RoaringBitmap::from_iter([1])), false),
1478 ];
1479
1480 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1481
1482 assert_eq!(indices.len(), 6);
1485
1486 assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
1488
1489 let idx_a_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "idx_a").collect();
1491 assert_eq!(idx_a_indices.len(), 1);
1492 assert_eq!(idx_a_indices[0].dataset_version, 1);
1493
1494 let vec_b_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "vec_b").collect();
1496 assert_eq!(vec_b_indices.len(), 1);
1497 assert_eq!(vec_b_indices[0].dataset_version, 1);
1498
1499 let idx_c_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "idx_c").collect();
1501 assert_eq!(idx_c_indices.len(), 2);
1502 assert!(
1503 idx_c_indices
1504 .iter()
1505 .all(|idx| idx.dataset_version == 2 || idx.dataset_version == 3)
1506 );
1507
1508 assert!(indices.iter().any(|idx| idx.name == "idx_d"));
1510
1511 assert!(!indices.iter().any(|idx| idx.name == "idx_e"));
1513 }
1514
1515 #[test]
1516 fn test_handle_rewrite_indices_skips_missing_index() {
1517 let mut indices = vec![];
1519
1520 let rewritten_indices = vec![RewrittenIndex {
1522 old_id: Uuid::new_v4(),
1523 new_id: Uuid::new_v4(),
1524 new_index_details: prost_types::Any {
1525 type_url: String::new(),
1526 value: vec![],
1527 },
1528 new_index_version: 1,
1529 new_index_files: None,
1530 }];
1531
1532 let result = Transaction::handle_rewrite_indices(&mut indices, &rewritten_indices, &[]);
1534 assert!(result.is_ok());
1535 assert!(indices.is_empty());
1536 }
1537
1538 #[test]
1539 fn test_prune_overlay_stale_fields_from_indices() {
1540 let mut old_frag = Fragment::new(0);
1543 old_frag.overlays = vec![overlay_with_field(1, 5)];
1544 let groups = vec![RewriteGroup {
1545 old_fragments: vec![old_frag],
1546 new_fragments: vec![Fragment::new(7)],
1547 }];
1548
1549 let covering = || Some(RoaringBitmap::from_iter([7u32]));
1551 let mut indices = vec![
1552 create_test_index("stale", 1, 2, covering(), false),
1554 create_test_index("fresh", 1, 5, covering(), false),
1557 create_test_index("unrelated", 2, 2, covering(), false),
1559 create_test_index(
1561 "deferred",
1562 1,
1563 2,
1564 Some(RoaringBitmap::from_iter([0u32])),
1565 false,
1566 ),
1567 ];
1568
1569 Transaction::prune_overlay_stale_fields_from_indices(&mut indices, &groups);
1570
1571 assert!(
1572 !indices[0].fragment_bitmap.as_ref().unwrap().contains(7),
1573 "stale index must drop the rewritten fragment from its coverage"
1574 );
1575 assert!(
1576 indices[1].fragment_bitmap.as_ref().unwrap().contains(7),
1577 "an index built at/after the overlay is not stale"
1578 );
1579 assert!(
1580 indices[2].fragment_bitmap.as_ref().unwrap().contains(7),
1581 "an index on an un-overlaid field is unaffected"
1582 );
1583 assert!(
1584 indices[3].fragment_bitmap.as_ref().unwrap().is_empty(),
1585 "stale index must drop the old fragment when the remap is deferred"
1586 );
1587 }
1588
1589 fn create_test_index(
1591 name: &str,
1592 field_id: i32,
1593 dataset_version: u64,
1594 fragment_bitmap: Option<RoaringBitmap>,
1595 is_vector: bool,
1596 ) -> IndexMetadata {
1597 use prost_types::Any;
1598 use std::sync::Arc;
1599
1600 let index_details = if is_vector {
1601 Some(Arc::new(Any {
1602 type_url: "type.googleapis.com/lance.index.VectorIndexDetails".to_string(),
1603 value: vec![],
1604 }))
1605 } else {
1606 Some(Arc::new(Any {
1607 type_url: "type.googleapis.com/lance.index.ScalarIndexDetails".to_string(),
1608 value: vec![],
1609 }))
1610 };
1611
1612 IndexMetadata {
1613 uuid: Uuid::new_v4(),
1614 fields: vec![field_id],
1615 covering_fields: vec![],
1616 name: name.to_string(),
1617 dataset_version,
1618 fragment_bitmap,
1619 index_details,
1620 index_version: 1,
1621 created_at: None,
1622 base_id: None,
1623 files: None,
1624 }
1625 }
1626
1627 fn legacy_frag_reuse_index(field_id: i32) -> IndexMetadata {
1630 let mut index = create_system_index(FRAG_REUSE_INDEX_NAME, field_id);
1631 index.index_version = 0;
1632 index
1633 }
1634
1635 #[test]
1640 fn test_retain_keeps_every_segment_under_tagged_history() {
1641 let schema = create_test_schema(&[1, 2]);
1642 let fragments = vec![Fragment::new(10), Fragment::new(11)];
1643 let mut indices = vec![
1644 create_system_index(FRAG_REUSE_INDEX_NAME, 1),
1645 create_test_index("idx_a", 1, 1, Some(RoaringBitmap::from_iter([0])), false),
1647 create_test_index("idx_a", 1, 2, Some(RoaringBitmap::from_iter([1])), false),
1648 create_test_index("idx_a", 1, 3, Some(RoaringBitmap::from_iter([10])), false),
1650 create_test_index("vec_b", 1, 1, Some(RoaringBitmap::from_iter([0])), true),
1652 create_test_index("vec_b", 1, 2, Some(RoaringBitmap::from_iter([1])), true),
1653 create_test_index("idx_c", 2, 1, Some(RoaringBitmap::new()), false),
1654 create_test_index("idx_c", 2, 2, Some(RoaringBitmap::new()), false),
1655 create_test_index("idx_e", 99, 1, Some(RoaringBitmap::from_iter([10])), false),
1657 ];
1658 assert!(indices.iter().any(is_tagged));
1659
1660 Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
1661
1662 assert_eq!(indices.len(), 8);
1663 assert!(!indices.iter().any(|idx| idx.name == "idx_e"));
1664 assert_eq!(indices.iter().filter(|idx| idx.name == "idx_a").count(), 3);
1665 assert_eq!(indices.iter().filter(|idx| idx.name == "vec_b").count(), 2);
1666 assert_eq!(indices.iter().filter(|idx| idx.name == "idx_c").count(), 2);
1667 }
1668
1669 fn create_system_index(name: &str, field_id: i32) -> IndexMetadata {
1670 use prost_types::Any;
1671 use std::sync::Arc;
1672
1673 IndexMetadata {
1674 uuid: Uuid::new_v4(),
1675 fields: vec![field_id],
1676 covering_fields: vec![],
1677 name: name.to_string(),
1678 dataset_version: 1,
1679 fragment_bitmap: Some(RoaringBitmap::from_iter([1, 2])),
1680 index_details: Some(Arc::new(Any {
1681 type_url: "type.googleapis.com/lance.index.SystemIndexDetails".to_string(),
1682 value: vec![],
1683 })),
1684 index_version: 1,
1685 created_at: None,
1686 base_id: None,
1687 files: None,
1688 }
1689 }
1690
1691 fn create_test_schema(field_ids: &[i32]) -> Schema {
1692 use arrow_schema::{DataType, Field as ArrowField, Schema as ArrowSchema};
1693 use lance_core::datatypes::Schema as LanceSchema;
1694
1695 let fields: Vec<ArrowField> = field_ids
1696 .iter()
1697 .map(|id| ArrowField::new(format!("field_{}", id), DataType::Int32, false))
1698 .collect();
1699
1700 let arrow_schema = ArrowSchema::new(fields);
1701 let mut lance_schema = LanceSchema::try_from(&arrow_schema).unwrap();
1702
1703 for (i, field_id) in field_ids.iter().enumerate() {
1705 lance_schema.mut_field_by_id(i as i32).unwrap().id = *field_id;
1706 }
1707
1708 lance_schema
1709 }
1710}
1711
1712#[cfg(test)]
1713mod withdrawal_tests {
1714 use super::*;
1715 use crate::transaction::test_support::{sample_index_metadata, tagged_entry};
1716
1717 fn segment(bitmap: Option<&[u32]>, fields: Vec<i32>) -> IndexMetadata {
1718 let mut segment = sample_index_metadata("idx");
1719 segment.fields = fields;
1720 segment.fragment_bitmap = bitmap.map(|ids| ids.iter().copied().collect());
1721 segment
1722 }
1723
1724 fn lineage(entry: &IndexMetadata) -> TaggedLineage {
1725 TaggedLineage::from_indices_with_ledger(std::slice::from_ref(entry), None)
1726 .unwrap()
1727 .unwrap()
1728 }
1729
1730 fn history() -> IndexMetadata {
1732 tagged_entry(&[(&[1, 2], &[3]), (&[3], &[4]), (&[6], &[8])])
1733 }
1734
1735 #[test]
1736 fn withdraws_the_whole_transition_reaching_the_rewritten_fragment() {
1737 let entry = history();
1738 let live: RoaringBitmap = [4u32, 8, 9].into_iter().collect();
1739 let mut indices = vec![segment(Some(&[1, 2, 6]), vec![0]), entry.clone()];
1743 Transaction::withdraw_rewritten_coverage(
1744 &mut indices,
1745 Some(&lineage(&entry)),
1746 &live,
1747 &[(4, vec![0])],
1748 );
1749 assert_eq!(
1750 indices[0].fragment_bitmap.as_ref().unwrap(),
1751 &RoaringBitmap::from_iter([6u32])
1752 );
1753 assert_eq!(indices[1].fragment_bitmap, entry.fragment_bitmap);
1755 }
1756
1757 #[test]
1762 fn live_only_remainder_after_withdrawing_retired_sources_goes_whole() {
1763 let entry = history();
1764 let live: RoaringBitmap = [4u32, 8, 9].into_iter().collect();
1765 let mut indices = vec![segment(Some(&[1, 2, 9]), vec![0]), entry.clone()];
1766 Transaction::withdraw_rewritten_coverage(
1767 &mut indices,
1768 Some(&lineage(&entry)),
1769 &live,
1770 &[(4, vec![0])],
1771 );
1772 assert!(
1773 indices[0].fragment_bitmap.as_ref().unwrap().is_empty(),
1774 "{:?}",
1775 indices[0].fragment_bitmap
1776 );
1777 }
1778
1779 #[test]
1782 fn remainder_the_history_mentions_survives() {
1783 let entry = history();
1784 let live: RoaringBitmap = [4u32, 8, 9].into_iter().collect();
1785 let mut indices = vec![segment(Some(&[1, 2, 8]), vec![0])];
1786 Transaction::withdraw_rewritten_coverage(
1787 &mut indices,
1788 Some(&lineage(&entry)),
1789 &live,
1790 &[(4, vec![0])],
1791 );
1792 assert_eq!(
1793 indices[0].fragment_bitmap.as_ref().unwrap(),
1794 &RoaringBitmap::from_iter([8u32])
1795 );
1796 }
1797
1798 #[test]
1802 fn withdrawing_only_a_live_fragment_keeps_the_remainder() {
1803 let entry = history();
1804 let live: RoaringBitmap = [4u32, 8, 9].into_iter().collect();
1805 let mut indices = vec![segment(Some(&[4, 9]), vec![0])];
1806 Transaction::withdraw_rewritten_coverage(
1807 &mut indices,
1808 Some(&lineage(&entry)),
1809 &live,
1810 &[(4, vec![0])],
1811 );
1812 assert_eq!(
1813 indices[0].fragment_bitmap.as_ref().unwrap(),
1814 &RoaringBitmap::from_iter([9u32])
1815 );
1816 }
1817
1818 #[test]
1819 fn mixed_provenance_loses_the_direct_id_and_the_sources() {
1820 let entry = history();
1821 let live: RoaringBitmap = [4u32, 8].into_iter().collect();
1822 let mut indices = vec![segment(Some(&[3, 4]), vec![0])];
1824 Transaction::withdraw_rewritten_coverage(
1825 &mut indices,
1826 Some(&lineage(&entry)),
1827 &live,
1828 &[(4, vec![0])],
1829 );
1830 assert!(indices[0].fragment_bitmap.as_ref().unwrap().is_empty());
1831 }
1832
1833 #[test]
1834 fn unrelated_lineage_unindexed_field_and_missing_bitmap_are_untouched() {
1835 let entry = history();
1836 let live: RoaringBitmap = [4u32, 8].into_iter().collect();
1837 let mut indices = vec![
1838 segment(Some(&[6]), vec![0]),
1839 segment(Some(&[1, 2]), vec![1]),
1840 segment(None, vec![0]),
1841 ];
1842 Transaction::withdraw_rewritten_coverage(
1843 &mut indices,
1844 Some(&lineage(&entry)),
1845 &live,
1846 &[(4, vec![0])],
1847 );
1848 assert_eq!(
1849 indices[0].fragment_bitmap.as_ref().unwrap(),
1850 &RoaringBitmap::from_iter([6u32])
1851 );
1852 assert_eq!(
1853 indices[1].fragment_bitmap.as_ref().unwrap(),
1854 &RoaringBitmap::from_iter([1u32, 2])
1855 );
1856 assert!(indices[2].fragment_bitmap.is_none());
1857 }
1858
1859 #[test]
1860 fn without_a_walkable_history_every_retired_fragment_goes() {
1861 let entry = history();
1862 let live: RoaringBitmap = [4u32, 8, 9].into_iter().collect();
1863 let mut indices = vec![segment(Some(&[6, 9]), vec![0])];
1864 Transaction::withdraw_rewritten_coverage(
1865 &mut indices,
1866 Some(&TaggedLineage::new(&entry, None)),
1867 &live,
1868 &[(4, vec![0])],
1869 );
1870 assert_eq!(
1871 indices[0].fragment_bitmap.as_ref().unwrap(),
1872 &RoaringBitmap::from_iter([9u32])
1873 );
1874 }
1875}