use crate::format::overlay::staleness::collect_overlay_stale_frags;
use crate::format::{Fragment, IndexMetadata};
use crate::system_index::frag_reuse::FRAG_REUSE_INDEX_NAME;
use crate::system_index::is_system_index;
use crate::transaction::{RewriteGroup, RewrittenIndex, Transaction};
use lance_core::datatypes::Schema;
use lance_core::{Error, Result};
use roaring::RoaringBitmap;
use std::collections::{HashMap, HashSet};
impl Transaction {
pub(super) fn register_pure_rewrite_rows_update_frags_in_indices(
indices: &mut [IndexMetadata],
pure_update_frag_ids: &[u64],
original_fragment_ids: &[u64],
fields_for_preserving_frag_bitmap: &[u32],
original_overlaid_frags: &HashMap<u32, &Fragment>,
schema: &Schema,
) -> Result<()> {
if pure_update_frag_ids.is_empty() {
return Ok(());
}
let value_updated_field_set = fields_for_preserving_frag_bitmap
.iter()
.collect::<HashSet<_>>();
for index in indices.iter_mut() {
if index.results_are_row_addrs() {
continue;
}
let index_covers_modified_field = index.fields.iter().any(|field_id| {
value_updated_field_set.contains(&u32::try_from(*field_id).unwrap())
});
if index_covers_modified_field {
continue;
}
let Some(fragment_bitmap) = index.fragment_bitmap.as_ref() else {
continue;
};
let index_covers_all_original_fragments = original_fragment_ids
.iter()
.all(|&fragment_id| fragment_bitmap.contains(fragment_id as u32));
if !index_covers_all_original_fragments {
continue;
}
let mut overlay_stale = RoaringBitmap::new();
collect_overlay_stale_frags(
index,
original_overlaid_frags,
&mut overlay_stale,
schema,
)?;
if !overlay_stale.is_empty() {
continue;
}
if let Some(fragment_bitmap) = index.fragment_bitmap.as_mut() {
for fragment_id in pure_update_frag_ids.iter().map(|f| *f as u32) {
fragment_bitmap.insert(fragment_id);
}
}
}
Ok(())
}
pub fn prune_updated_fields_from_indices(
indices: &mut [IndexMetadata],
updated_fragments: &[Fragment],
fields_modified: &[u32],
) {
if fields_modified.is_empty() {
return;
}
let fields_modified_set = fields_modified.iter().collect::<HashSet<_>>();
for index in indices.iter_mut() {
if index
.fields
.iter()
.any(|field_id| fields_modified_set.contains(&u32::try_from(*field_id).unwrap()))
&& let Some(fragment_bitmap) = &mut index.fragment_bitmap
{
for fragment_id in updated_fragments.iter().map(|f| f.id as u32) {
fragment_bitmap.remove(fragment_id);
}
}
}
}
fn fragment_field_paths(frag: &Fragment) -> HashMap<i32, &str> {
let mut map = HashMap::new();
for file in &frag.files {
for &field_id in file.fields.iter() {
if field_id >= 0 {
map.insert(field_id, file.path.as_str());
}
}
}
map
}
pub(super) fn prune_merge_rewritten_fields_from_indices(
indices: &mut [IndexMetadata],
prev_fragments: &[Fragment],
new_fragments: &[Fragment],
) {
let prev_by_id: HashMap<u64, &Fragment> =
prev_fragments.iter().map(|f| (f.id, f)).collect();
for new_frag in new_fragments {
let Some(prev) = prev_by_id.get(&new_frag.id) else {
continue; };
let prev_paths = Self::fragment_field_paths(prev);
let new_paths = Self::fragment_field_paths(new_frag);
let changed: Vec<u32> = prev_paths
.iter()
.filter(|(field_id, prev_path)| {
new_paths
.get(*field_id)
.is_some_and(|new_path| new_path != *prev_path)
})
.map(|(field_id, _)| *field_id as u32)
.collect();
if changed.is_empty() {
continue;
}
Self::prune_updated_fields_from_indices(
indices,
std::slice::from_ref(new_frag),
&changed,
);
}
}
pub(super) fn prune_overlay_stale_fields_from_indices(
indices: &mut [IndexMetadata],
groups: &[RewriteGroup],
) {
for group in groups {
let mut overlaid_field_versions: HashMap<i32, u64> = HashMap::new();
for old_frag in &group.old_fragments {
for overlay in &old_frag.overlays {
for &field_id in overlay.data_file.fields.iter() {
if field_id < 0 {
continue;
}
let entry = overlaid_field_versions.entry(field_id).or_insert(0);
*entry = (*entry).max(overlay.committed_version);
}
}
}
if overlaid_field_versions.is_empty() {
continue;
}
let new_fragment_ids = group
.new_fragments
.iter()
.map(|f| f.id as u32)
.collect::<Vec<_>>();
for index in indices.iter_mut() {
let is_stale = index.fields.iter().any(|field_id| {
overlaid_field_versions
.get(field_id)
.is_some_and(|&overlay_version| overlay_version > index.dataset_version)
});
if is_stale && let Some(fragment_bitmap) = &mut index.fragment_bitmap {
for new_id in &new_fragment_ids {
fragment_bitmap.remove(*new_id);
}
}
}
}
}
pub(crate) fn retain_relevant_indices(
indices: &mut Vec<IndexMetadata>,
schema: &Schema,
fragments: &[Fragment],
) {
let field_ids = schema
.fields_pre_order()
.map(|f| f.id)
.collect::<HashSet<_>>();
indices.retain(|existing_index| {
existing_index
.fields
.iter()
.all(|field_id| field_ids.contains(field_id))
|| is_system_index(existing_index)
});
let mut indices_by_name: std::collections::HashMap<String, Vec<&IndexMetadata>> =
std::collections::HashMap::new();
for index in indices.iter() {
if index.name != FRAG_REUSE_INDEX_NAME {
indices_by_name
.entry(index.name.clone())
.or_default()
.push(index);
}
}
let mut uuids_to_keep = std::collections::HashSet::new();
let existing_fragments = fragments
.iter()
.map(|f| f.id as u32)
.collect::<RoaringBitmap>();
for (_, same_name_indices) in indices_by_name {
if same_name_indices.len() > 1 {
let (empty_indices, non_empty_indices): (Vec<_>, Vec<_>) =
same_name_indices.iter().partition(|index| {
index
.effective_fragment_bitmap(&existing_fragments)
.as_ref()
.is_none_or(|bitmap| bitmap.is_empty())
});
if non_empty_indices.is_empty() {
let mut sorted_indices = empty_indices;
sorted_indices.sort_by_key(|index: &&IndexMetadata| index.dataset_version);
if let Some(oldest) = sorted_indices.first() {
uuids_to_keep.insert(oldest.uuid);
}
} else {
for index in non_empty_indices {
uuids_to_keep.insert(index.uuid);
}
}
} else {
if let Some(index) = same_name_indices.first() {
uuids_to_keep.insert(index.uuid);
}
}
}
indices.retain(|index| {
index.name == FRAG_REUSE_INDEX_NAME || uuids_to_keep.contains(&index.uuid)
});
}
pub(super) fn recalculate_fragment_bitmap(
old: &RoaringBitmap,
groups: &[RewriteGroup],
) -> Result<RoaringBitmap> {
let mut new_bitmap = old.clone();
for group in groups {
let any_in_index = group
.old_fragments
.iter()
.any(|frag| old.contains(frag.id as u32));
let all_in_index = group
.old_fragments
.iter()
.all(|frag| old.contains(frag.id as u32));
if any_in_index {
if all_in_index {
for frag_id in group.old_fragments.iter().map(|frag| frag.id as u32) {
new_bitmap.remove(frag_id);
}
new_bitmap.extend(group.new_fragments.iter().map(|frag| frag.id as u32));
} else {
return Err(Error::invalid_input(
"The compaction plan included a rewrite group that was a split of indexed and non-indexed data",
));
}
}
}
Ok(new_bitmap)
}
pub(super) fn handle_rewrite_indices(
indices: &mut [IndexMetadata],
rewritten_indices: &[RewrittenIndex],
groups: &[RewriteGroup],
) -> Result<()> {
let mut modified_indices = HashSet::new();
for rewritten_index in rewritten_indices {
if !modified_indices.insert(rewritten_index.old_id) {
return Err(Error::invalid_input(format!(
"An invalid compaction plan must have been generated because multiple tasks modified the same index: {}",
rewritten_index.old_id
)));
}
let Some(index) = indices
.iter_mut()
.find(|idx| idx.uuid == rewritten_index.old_id)
else {
continue;
};
index.fragment_bitmap = Some(Self::recalculate_fragment_bitmap(
index.fragment_bitmap.as_ref().ok_or_else(|| {
Error::invalid_input(format!(
"Cannot rewrite index {} which did not store fragment bitmap",
index.uuid
))
})?,
groups,
)?);
index.uuid = rewritten_index.new_id;
index.files = rewritten_index.new_index_files.clone();
}
Ok(())
}
pub(super) fn handle_rewrite_fragments(
final_fragments: &mut Vec<Fragment>,
groups: &[RewriteGroup],
fragment_id: &mut u64,
version: u64,
_next_row_id: Option<&u64>,
) -> Result<()> {
for group in groups {
let replace_range = {
let start = final_fragments
.iter()
.enumerate()
.find(|(_, f)| f.id == group.old_fragments[0].id)
.ok_or_else(|| {
Error::commit_conflict_source(
version,
format!(
"dataset does not contain a fragment a rewrite operation wants to replace: id={}",
group.old_fragments[0].id
)
.into(),
)
})?
.0;
let mut i = 1;
loop {
if i == group.old_fragments.len() {
break Some(start..start + i);
}
if final_fragments[start + i].id != group.old_fragments[i].id {
break None;
}
i += 1;
}
};
let new_fragments = Self::fragments_with_ids(group.new_fragments.clone(), fragment_id)
.collect::<Vec<_>>();
if let Some(replace_range) = replace_range {
final_fragments.splice(replace_range, new_fragments);
} else {
for fragment in group.old_fragments.iter() {
final_fragments.retain(|f| f.id != fragment.id);
}
final_fragments.extend(new_fragments);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transaction::test_support::overlay_with_field;
use uuid::Uuid;
#[test]
fn test_rewrite_fragments() {
let existing_fragments: Vec<Fragment> = (0..10).map(Fragment::new).collect();
let mut final_fragments = existing_fragments;
let rewrite_groups = vec![
RewriteGroup {
old_fragments: vec![Fragment::new(1), Fragment::new(2)],
new_fragments: vec![Fragment::new(15), Fragment::new(16)],
},
RewriteGroup {
old_fragments: vec![Fragment::new(5), Fragment::new(8)],
new_fragments: vec![Fragment::new(0)],
},
];
let mut fragment_id = 20;
let version = 0;
Transaction::handle_rewrite_fragments(
&mut final_fragments,
&rewrite_groups,
&mut fragment_id,
version,
None,
)
.unwrap();
assert_eq!(fragment_id, 21);
let expected_fragments: Vec<Fragment> = vec![
Fragment::new(0),
Fragment::new(15),
Fragment::new(16),
Fragment::new(3),
Fragment::new(4),
Fragment::new(6),
Fragment::new(7),
Fragment::new(9),
Fragment::new(20),
];
assert_eq!(final_fragments, expected_fragments);
}
#[test]
fn test_retain_indices_removes_missing_fields() {
let schema = create_test_schema(&[1, 2]);
let fragments = vec![Fragment::new(1), Fragment::new(2)];
let mut indices = vec![
create_test_index("idx1", 1, 1, Some(RoaringBitmap::from_iter([1])), false),
create_test_index("idx2", 2, 1, Some(RoaringBitmap::from_iter([1])), false),
create_test_index("idx3", 99, 1, Some(RoaringBitmap::from_iter([1])), false), ];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 2);
assert!(indices.iter().all(|idx| idx.fields[0] != 99));
}
#[test]
fn test_retain_indices_keeps_system_indices() {
use crate::system_index::mem_wal::MEM_WAL_INDEX_NAME;
let schema = create_test_schema(&[1, 2]);
let fragments = vec![Fragment::new(1)];
let mut indices = vec![
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), ];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 2);
assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
assert!(indices.iter().any(|idx| idx.name == MEM_WAL_INDEX_NAME));
}
#[test]
fn test_retain_indices_keeps_fragment_reuse_index() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut indices = vec![
create_system_index(FRAG_REUSE_INDEX_NAME, 1),
create_test_index("other_idx", 1, 1, Some(RoaringBitmap::new()), false),
];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
}
#[test]
fn test_retain_single_empty_scalar_index() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut indices = vec![create_test_index(
"scalar_idx",
1,
1,
Some(RoaringBitmap::new()), false,
)];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 1);
}
#[test]
fn test_retain_single_empty_vector_index_is_kept() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut indices = vec![create_test_index(
"vector_idx",
1,
1,
Some(RoaringBitmap::new()), true,
)];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 1);
}
#[test]
fn test_retain_single_nonempty_index() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut scalar_indices = vec![create_test_index(
"scalar_idx",
1,
1,
Some(RoaringBitmap::from_iter([1])),
false,
)];
let mut vector_indices = vec![create_test_index(
"vector_idx",
1,
1,
Some(RoaringBitmap::from_iter([1])),
true,
)];
Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
assert_eq!(scalar_indices.len(), 1);
assert_eq!(vector_indices.len(), 1);
}
#[test]
fn test_retain_single_index_with_none_bitmap() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut scalar_indices = vec![create_test_index("scalar_idx", 1, 1, None, false)];
let mut vector_indices = vec![create_test_index("vector_idx", 1, 1, None, true)];
Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
assert_eq!(scalar_indices.len(), 1);
assert_eq!(vector_indices.len(), 1);
}
#[test]
fn test_retain_multiple_empty_scalar_indices_keeps_oldest() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut indices = vec![
create_test_index("idx", 1, 3, Some(RoaringBitmap::new()), false),
create_test_index("idx", 1, 1, Some(RoaringBitmap::new()), false), create_test_index("idx", 1, 2, Some(RoaringBitmap::new()), false),
];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 1);
assert_eq!(indices[0].dataset_version, 1);
}
#[test]
fn test_retain_multiple_empty_vector_indices_keeps_oldest() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut indices = vec![
create_test_index("vec_idx", 1, 1, Some(RoaringBitmap::new()), true),
create_test_index("vec_idx", 1, 2, Some(RoaringBitmap::new()), true),
create_test_index("vec_idx", 1, 3, Some(RoaringBitmap::new()), true),
];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 1);
assert_eq!(indices[0].dataset_version, 1);
}
#[test]
fn test_retain_mixed_empty_nonempty_keeps_nonempty() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut indices = vec![
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), ];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 2);
assert!(
indices
.iter()
.all(|idx| idx.dataset_version == 2 || idx.dataset_version == 4)
);
}
#[test]
fn test_retain_mixed_empty_nonempty_vector_keeps_nonempty() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut indices = vec![
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), ];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 1);
assert_eq!(indices[0].dataset_version, 2);
}
#[test]
fn test_retain_fragment_bitmap_with_nonexistent_fragments() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1), Fragment::new(2)];
let mut indices = vec![create_test_index(
"idx",
1,
1,
Some(RoaringBitmap::from_iter([1, 2, 3, 4])), false,
)];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 1);
assert_eq!(
indices[0].fragment_bitmap.as_ref().unwrap(),
&RoaringBitmap::from_iter([1, 2, 3, 4])
);
}
#[test]
fn test_retain_effective_empty_bitmap_single_index() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(5), Fragment::new(6)];
let mut scalar_indices = vec![create_test_index(
"scalar_idx",
1,
1,
Some(RoaringBitmap::from_iter([1, 2, 3])),
false,
)];
let mut vector_indices = vec![create_test_index(
"vector_idx",
1,
1,
Some(RoaringBitmap::from_iter([1, 2, 3])),
true,
)];
Transaction::retain_relevant_indices(&mut scalar_indices, &schema, &fragments);
Transaction::retain_relevant_indices(&mut vector_indices, &schema, &fragments);
assert_eq!(scalar_indices.len(), 1);
assert_eq!(vector_indices.len(), 1);
}
#[test]
fn test_retain_different_index_names() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut indices = vec![
create_test_index("idx_a", 1, 1, Some(RoaringBitmap::new()), false),
create_test_index("idx_b", 1, 1, Some(RoaringBitmap::new()), true),
create_test_index("idx_c", 1, 1, Some(RoaringBitmap::from_iter([1])), false),
];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 3);
assert!(indices.iter().any(|idx| idx.name == "idx_a"));
assert!(indices.iter().any(|idx| idx.name == "idx_b"));
assert!(indices.iter().any(|idx| idx.name == "idx_c"));
}
#[test]
fn test_retain_empty_indices_vec() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut indices: Vec<IndexMetadata> = vec![];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 0);
}
#[test]
fn test_retain_all_indices_removed() {
let schema = create_test_schema(&[1]);
let fragments = vec![Fragment::new(1)];
let mut indices = vec![
create_test_index("vec1", 1, 1, Some(RoaringBitmap::new()), true),
create_test_index("vec2", 1, 1, Some(RoaringBitmap::new()), true),
create_test_index("idx3", 99, 1, Some(RoaringBitmap::from_iter([1])), false), ];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 2);
assert!(!indices.iter().any(|idx| idx.name == "idx3"));
}
#[test]
fn test_retain_complex_scenario() {
let schema = create_test_schema(&[1, 2]);
let fragments = vec![Fragment::new(1), Fragment::new(2)];
let mut indices = vec![
create_system_index(FRAG_REUSE_INDEX_NAME, 1),
create_test_index("idx_a", 1, 3, Some(RoaringBitmap::new()), false),
create_test_index("idx_a", 1, 1, Some(RoaringBitmap::new()), false), create_test_index("idx_a", 1, 2, Some(RoaringBitmap::new()), false),
create_test_index("vec_b", 1, 1, Some(RoaringBitmap::new()), true),
create_test_index("vec_b", 1, 2, Some(RoaringBitmap::new()), true),
create_test_index("idx_c", 2, 1, Some(RoaringBitmap::new()), false),
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),
create_test_index("idx_e", 99, 1, Some(RoaringBitmap::from_iter([1])), false),
];
Transaction::retain_relevant_indices(&mut indices, &schema, &fragments);
assert_eq!(indices.len(), 6);
assert!(indices.iter().any(|idx| idx.name == FRAG_REUSE_INDEX_NAME));
let idx_a_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "idx_a").collect();
assert_eq!(idx_a_indices.len(), 1);
assert_eq!(idx_a_indices[0].dataset_version, 1);
let vec_b_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "vec_b").collect();
assert_eq!(vec_b_indices.len(), 1);
assert_eq!(vec_b_indices[0].dataset_version, 1);
let idx_c_indices: Vec<_> = indices.iter().filter(|idx| idx.name == "idx_c").collect();
assert_eq!(idx_c_indices.len(), 2);
assert!(
idx_c_indices
.iter()
.all(|idx| idx.dataset_version == 2 || idx.dataset_version == 3)
);
assert!(indices.iter().any(|idx| idx.name == "idx_d"));
assert!(!indices.iter().any(|idx| idx.name == "idx_e"));
}
#[test]
fn test_handle_rewrite_indices_skips_missing_index() {
let mut indices = vec![];
let rewritten_indices = vec![RewrittenIndex {
old_id: Uuid::new_v4(),
new_id: Uuid::new_v4(),
new_index_details: prost_types::Any {
type_url: String::new(),
value: vec![],
},
new_index_version: 1,
new_index_files: None,
}];
let result = Transaction::handle_rewrite_indices(&mut indices, &rewritten_indices, &[]);
assert!(result.is_ok());
assert!(indices.is_empty());
}
#[test]
fn test_prune_overlay_stale_fields_from_indices() {
let mut old_frag = Fragment::new(0);
old_frag.overlays = vec![overlay_with_field(1, 5)];
let groups = vec![RewriteGroup {
old_fragments: vec![old_frag],
new_fragments: vec![Fragment::new(7)],
}];
let covering = || Some(RoaringBitmap::from_iter([7u32]));
let mut indices = vec![
create_test_index("stale", 1, 2, covering(), false),
create_test_index("fresh", 1, 5, covering(), false),
create_test_index("unrelated", 2, 2, covering(), false),
];
Transaction::prune_overlay_stale_fields_from_indices(&mut indices, &groups);
assert!(
!indices[0].fragment_bitmap.as_ref().unwrap().contains(7),
"stale index must drop the rewritten fragment from its coverage"
);
assert!(
indices[1].fragment_bitmap.as_ref().unwrap().contains(7),
"an index built at/after the overlay is not stale"
);
assert!(
indices[2].fragment_bitmap.as_ref().unwrap().contains(7),
"an index on an un-overlaid field is unaffected"
);
}
fn create_test_index(
name: &str,
field_id: i32,
dataset_version: u64,
fragment_bitmap: Option<RoaringBitmap>,
is_vector: bool,
) -> IndexMetadata {
use prost_types::Any;
use std::sync::Arc;
let index_details = if is_vector {
Some(Arc::new(Any {
type_url: "type.googleapis.com/lance.index.VectorIndexDetails".to_string(),
value: vec![],
}))
} else {
Some(Arc::new(Any {
type_url: "type.googleapis.com/lance.index.ScalarIndexDetails".to_string(),
value: vec![],
}))
};
IndexMetadata {
uuid: Uuid::new_v4(),
fields: vec![field_id],
covering_fields: vec![],
name: name.to_string(),
dataset_version,
fragment_bitmap,
index_details,
index_version: 1,
created_at: None,
base_id: None,
files: None,
}
}
fn create_system_index(name: &str, field_id: i32) -> IndexMetadata {
use prost_types::Any;
use std::sync::Arc;
IndexMetadata {
uuid: Uuid::new_v4(),
fields: vec![field_id],
covering_fields: vec![],
name: name.to_string(),
dataset_version: 1,
fragment_bitmap: Some(RoaringBitmap::from_iter([1, 2])),
index_details: Some(Arc::new(Any {
type_url: "type.googleapis.com/lance.index.SystemIndexDetails".to_string(),
value: vec![],
})),
index_version: 1,
created_at: None,
base_id: None,
files: None,
}
}
fn create_test_schema(field_ids: &[i32]) -> Schema {
use arrow_schema::{DataType, Field as ArrowField, Schema as ArrowSchema};
use lance_core::datatypes::Schema as LanceSchema;
let fields: Vec<ArrowField> = field_ids
.iter()
.map(|id| ArrowField::new(format!("field_{}", id), DataType::Int32, false))
.collect();
let arrow_schema = ArrowSchema::new(fields);
let mut lance_schema = LanceSchema::try_from(&arrow_schema).unwrap();
for (i, field_id) in field_ids.iter().enumerate() {
lance_schema.mut_field_by_id(i as i32).unwrap().id = *field_id;
}
lance_schema
}
}