pub(crate) use lance_table::system_index::mem_wal::{
load_mem_wal_index_details, new_mem_wal_index_meta, open_mem_wal_index,
};
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use lance_index::mem_wal::{CompactedSsTable, MEM_WAL_INDEX_NAME, MemWalIndexDetails};
use lance_table::format::IndexMetadata;
use lance_table::system_index::mem_wal::update_mem_wal_index_compacted_sstables;
use std::sync::Arc;
use uuid::Uuid;
use crate::index::DatasetIndexExt;
use arrow_array::{Int32Array, RecordBatch};
use arrow_schema::{DataType, Field, Schema};
use crate::dataset::transaction::{Operation, Transaction};
use crate::dataset::{CommitBuilder, InsertBuilder, WriteParams};
async fn test_dataset() -> crate::Dataset {
let write_params = WriteParams {
max_rows_per_file: 10,
..Default::default()
};
let data = RecordBatch::try_new(
Arc::new(Schema::new(vec![
Field::new("a", DataType::Int32, false),
Field::new("b", DataType::Int32, true),
])),
vec![
Arc::new(Int32Array::from_iter_values(0..10_i32)),
Arc::new(Int32Array::from_iter_values(std::iter::repeat_n(0, 10))),
],
)
.unwrap();
InsertBuilder::new("memory://test_mem_wal")
.with_params(&write_params)
.execute(vec![data])
.await
.unwrap()
}
async fn test_dataset_with_mem_wal() -> crate::Dataset {
let dataset = test_dataset().await;
let mem_wal_index =
new_mem_wal_index_meta(dataset.manifest.version, MemWalIndexDetails::default())
.unwrap();
let txn = Transaction::new(
dataset.manifest.version,
Operation::CreateIndex {
new_indices: vec![mem_wal_index],
removed_indices: vec![],
},
None,
);
CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap()
}
#[tokio::test]
async fn test_update_mem_wal_state_preserves_fragments() {
let dataset = test_dataset_with_mem_wal().await;
let rows_before = dataset.count_rows(None).await.unwrap();
let fragments_before: Vec<u64> = dataset.fragments().iter().map(|f| f.id).collect();
assert!(rows_before > 0, "precondition: the table holds rows");
let txn = Transaction::new(
dataset.manifest.version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(Uuid::new_v4(), 1)],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap();
assert_eq!(
dataset.fragments().iter().map(|f| f.id).collect::<Vec<_>>(),
fragments_before,
"UpdateMemWalState dropped fragments"
);
assert_eq!(
dataset.count_rows(None).await.unwrap(),
rows_before,
"UpdateMemWalState dropped rows"
);
}
#[tokio::test]
async fn test_update_mem_wal_state_conflict_lower_generation_no_retry() {
let dataset = test_dataset_with_mem_wal().await;
let shard = Uuid::new_v4();
let txn1 = Transaction::new(
dataset.manifest.version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 10)],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn1)
.await
.unwrap();
let txn2 = Transaction::new(
dataset.manifest.version - 1, Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 5)],
},
None,
);
let result = CommitBuilder::new(Arc::new(dataset)).execute(txn2).await;
assert!(
matches!(result, Err(crate::Error::IncompatibleTransaction { .. })),
"Expected non-retryable IncompatibleTransaction for lower generation, got {:?}",
result
);
}
#[tokio::test]
async fn test_update_mem_wal_state_conflict_equal_generation_no_retry() {
let dataset = test_dataset_with_mem_wal().await;
let shard = Uuid::new_v4();
let txn1 = Transaction::new(
dataset.manifest.version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 10)],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn1)
.await
.unwrap();
let txn2 = Transaction::new(
dataset.manifest.version - 1, Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 10)],
},
None,
);
let result = CommitBuilder::new(Arc::new(dataset)).execute(txn2).await;
assert!(
matches!(result, Err(crate::Error::IncompatibleTransaction { .. })),
"Expected non-retryable IncompatibleTransaction for equal generation, got {:?}",
result
);
}
#[tokio::test]
async fn test_update_mem_wal_state_conflict_higher_generation_retryable() {
let dataset = test_dataset_with_mem_wal().await;
let shard = Uuid::new_v4();
let txn1 = Transaction::new(
dataset.manifest.version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 5)],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn1)
.await
.unwrap();
let txn2 = Transaction::new(
dataset.manifest.version - 1, Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 10)],
},
None,
);
let result = CommitBuilder::new(Arc::new(dataset)).execute(txn2).await;
assert!(
matches!(result, Err(crate::Error::RetryableCommitConflict { .. })),
"Expected retryable conflict for higher generation, got {:?}",
result
);
}
#[tokio::test]
async fn test_update_mem_wal_state_different_shards_no_conflict() {
let dataset = test_dataset_with_mem_wal().await;
let shard1 = Uuid::new_v4();
let shard2 = Uuid::new_v4();
let txn1 = Transaction::new(
dataset.manifest.version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard1, 10)],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn1)
.await
.unwrap();
let txn2 = Transaction::new(
dataset.manifest.version - 1, Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard2, 5)],
},
None,
);
let result = CommitBuilder::new(Arc::new(dataset)).execute(txn2).await;
assert!(
result.is_ok(),
"Expected success for different shards, got {:?}",
result
);
let dataset = result.unwrap();
let mem_wal_idx = dataset
.load_indices()
.await
.unwrap()
.iter()
.find(|idx| idx.name == MEM_WAL_INDEX_NAME)
.unwrap()
.clone();
let details = load_mem_wal_index_details(mem_wal_idx).unwrap();
assert_eq!(details.compacted_sstables.len(), 2);
}
#[tokio::test]
async fn test_create_index_rebase_against_update_mem_wal_state() {
let dataset = test_dataset_with_mem_wal().await;
let shard = Uuid::new_v4();
let txn1 = Transaction::new(
dataset.manifest.version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 10)],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn1)
.await
.unwrap();
let details = MemWalIndexDetails {
num_shards: 1,
..Default::default()
};
let mem_wal_index = new_mem_wal_index_meta(dataset.manifest.version - 1, details).unwrap();
let txn2 = Transaction::new(
dataset.manifest.version - 1, Operation::CreateIndex {
new_indices: vec![mem_wal_index],
removed_indices: vec![],
},
None,
);
let result = CommitBuilder::new(Arc::new(dataset)).execute(txn2).await;
assert!(
result.is_ok(),
"Expected CreateIndex to succeed with rebase, got {:?}",
result
);
let dataset = result.unwrap();
let mem_wal_idx = dataset
.load_indices()
.await
.unwrap()
.iter()
.find(|idx| idx.name == MEM_WAL_INDEX_NAME)
.unwrap()
.clone();
let details = load_mem_wal_index_details(mem_wal_idx).unwrap();
assert_eq!(details.compacted_sstables.len(), 1);
assert_eq!(details.compacted_sstables[0].shard_id, shard);
assert_eq!(details.compacted_sstables[0].generation, 10);
assert_eq!(details.num_shards, 1); }
#[tokio::test]
async fn test_update_mem_wal_state_against_create_index_lower_generation() {
let dataset = test_dataset().await;
let shard = Uuid::new_v4();
let details = MemWalIndexDetails {
compacted_sstables: vec![CompactedSsTable::new(shard, 10)],
..Default::default()
};
let mem_wal_index = new_mem_wal_index_meta(dataset.manifest.version, details).unwrap();
let txn1 = Transaction::new(
dataset.manifest.version,
Operation::CreateIndex {
new_indices: vec![mem_wal_index],
removed_indices: vec![],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn1)
.await
.unwrap();
let txn2 = Transaction::new(
dataset.manifest.version - 1, Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 5)],
},
None,
);
let result = CommitBuilder::new(Arc::new(dataset)).execute(txn2).await;
assert!(
matches!(result, Err(crate::Error::IncompatibleTransaction { .. })),
"Expected non-retryable IncompatibleTransaction when UpdateMemWalState generation is lower than CreateIndex, got {:?}",
result
);
}
async fn compacted_dataset(shard: Uuid, generation: u64) -> crate::Dataset {
let dataset = test_dataset_with_mem_wal().await;
let txn = Transaction::new(
dataset.manifest.version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, generation)],
},
None,
);
CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap()
}
fn index_over(name: &str, fragments: &[u32]) -> IndexMetadata {
IndexMetadata {
uuid: Uuid::new_v4(),
name: name.to_string(),
fields: vec![0],
covering_fields: vec![],
dataset_version: 1,
fragment_bitmap: Some(roaring::RoaringBitmap::from_iter(fragments.iter().copied())),
index_details: None,
index_version: 0,
created_at: None,
base_id: None,
files: None,
}
}
async fn catch_up_generation(dataset: &crate::Dataset, index: &str) -> Option<u64> {
let meta = dataset
.load_indices()
.await
.unwrap()
.iter()
.find(|idx| idx.name == MEM_WAL_INDEX_NAME)
.unwrap()
.clone();
load_mem_wal_index_details(meta)
.unwrap()
.index_catchup
.into_iter()
.find(|entry| entry.index_name == index)
.and_then(|entry| entry.caught_up_generations.first().map(|g| g.generation))
}
#[tokio::test]
async fn an_index_covering_the_table_earns_catch_up_on_commit() {
let shard = Uuid::new_v4();
let dataset = compacted_dataset(shard, 5).await;
let txn = Transaction::new(
dataset.manifest.version,
Operation::CreateIndex {
new_indices: vec![index_over("idx", &[0])],
removed_indices: vec![],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap();
assert_eq!(catch_up_generation(&dataset, "idx").await, Some(5));
}
#[tokio::test]
async fn a_no_work_repair_still_records_derived_catch_up() {
use lance_index::optimize::OptimizeOptions;
let shard = Uuid::new_v4();
let dataset = compacted_dataset(shard, 7).await;
let txn = Transaction::new(
dataset.manifest.version,
Operation::CreateIndex {
new_indices: vec![index_over("idx", &[0])],
removed_indices: vec![],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap();
let txn = Transaction::new(
dataset.manifest.version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 9)],
},
None,
);
let mut dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap();
dataset
.optimize_indices(&OptimizeOptions::append().index_names(vec!["idx".to_string()]))
.await
.unwrap();
assert_eq!(catch_up_generation(&dataset, "idx").await, Some(9));
let after_repair = dataset.manifest.version;
dataset
.optimize_indices(&OptimizeOptions::append().index_names(vec!["idx".to_string()]))
.await
.unwrap();
assert_eq!(dataset.manifest.version, after_repair);
}
#[tokio::test]
async fn a_no_work_optimize_with_nothing_compacted_commits_nothing() {
use lance_index::optimize::OptimizeOptions;
let dataset = test_dataset_with_mem_wal().await;
let txn = Transaction::new(
dataset.manifest.version,
Operation::CreateIndex {
new_indices: vec![index_over("idx", &[0])],
removed_indices: vec![],
},
None,
);
let mut dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap();
let before = dataset.manifest.version;
dataset
.optimize_indices(&OptimizeOptions::append().index_names(vec!["idx".to_string()]))
.await
.unwrap();
assert_eq!(dataset.manifest.version, before);
}
#[tokio::test]
async fn a_table_with_no_catchup_entry_earns_one() {
let dataset = test_dataset_with_mem_wal().await;
let shard = Uuid::new_v4();
let txn = Transaction::new(
dataset.manifest.version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 5)],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap();
let txn = Transaction::new(
dataset.manifest.version,
Operation::CreateIndex {
new_indices: vec![index_over("idx", &[0])],
removed_indices: vec![],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap();
assert_eq!(catch_up_generation(&dataset, "idx").await, Some(5));
}
#[tokio::test]
async fn credit_is_anchored_to_the_read_version_across_a_rebase() {
let shard = Uuid::new_v4();
let dataset = compacted_dataset(shard, 5).await;
let read_version = dataset.manifest.version;
let data = RecordBatch::try_new(
Arc::new(Schema::from(dataset.schema())),
vec![
Arc::new(Int32Array::from_iter_values(10..20_i32)),
Arc::new(Int32Array::from_iter_values(std::iter::repeat_n(0, 10))),
],
)
.unwrap();
let dataset = InsertBuilder::new(Arc::new(dataset))
.with_params(&WriteParams {
mode: crate::dataset::WriteMode::Append,
max_rows_per_file: 10,
..Default::default()
})
.execute(vec![data])
.await
.unwrap();
assert!(
dataset.get_fragments().len() > 1,
"the append must add a fragment the index has not seen"
);
let txn = Transaction::new(
read_version,
Operation::CreateIndex {
new_indices: vec![index_over("idx", &[0])],
removed_indices: vec![],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap();
assert_eq!(catch_up_generation(&dataset, "idx").await, Some(5));
}
#[tokio::test]
async fn a_user_index_build_cannot_rebase_past_a_compaction_commit() {
let shard = Uuid::new_v4();
let dataset = compacted_dataset(shard, 5).await;
let read_version = dataset.manifest.version;
let txn = Transaction::new(
dataset.manifest.version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 9)],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap();
let txn = Transaction::new(
read_version,
Operation::CreateIndex {
new_indices: vec![index_over("idx", &[0])],
removed_indices: vec![],
},
None,
);
let err = CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap_err();
assert!(
err.to_string().contains("incompatible"),
"expected an incompatible-transaction error, got {err}"
);
}
fn indices_with(details: MemWalIndexDetails) -> Vec<IndexMetadata> {
vec![new_mem_wal_index_meta(1, details).unwrap()]
}
fn compacted_generation(indices: &[IndexMetadata], shard: Uuid) -> Option<u64> {
load_mem_wal_index_details(indices[0].clone())
.unwrap()
.compacted_sstables
.iter()
.find(|sstable| sstable.shard_id == shard)
.map(|sstable| sstable.generation)
}
#[test]
fn test_update_compacted_sstables() {
let shard1 = Uuid::new_v4();
let shard2 = Uuid::new_v4();
let mut indices = indices_with(MemWalIndexDetails::default());
update_mem_wal_index_compacted_sstables(
&mut indices,
1,
vec![CompactedSsTable::new(shard1, 5)],
)
.unwrap();
assert_eq!(indices.len(), 1);
assert_eq!(compacted_generation(&indices, shard1), Some(5));
update_mem_wal_index_compacted_sstables(
&mut indices,
2,
vec![CompactedSsTable::new(shard1, 10)],
)
.unwrap();
assert_eq!(compacted_generation(&indices, shard1), Some(10));
update_mem_wal_index_compacted_sstables(
&mut indices,
3,
vec![CompactedSsTable::new(shard2, 3)],
)
.unwrap();
assert_eq!(compacted_generation(&indices, shard1), Some(10));
assert_eq!(compacted_generation(&indices, shard2), Some(3));
}
#[test]
fn a_lower_generation_rejects_the_whole_update() {
let shard = Uuid::new_v4();
let mut indices = indices_with(MemWalIndexDetails {
compacted_sstables: vec![CompactedSsTable::new(shard, 10)],
..Default::default()
});
let before = indices.clone();
let err = update_mem_wal_index_compacted_sstables(
&mut indices,
2,
vec![CompactedSsTable::new(shard, 8)],
)
.unwrap_err();
assert!(
err.to_string().contains("Stale SSTable compaction"),
"{err}"
);
assert_eq!(
indices[0].uuid, before[0].uuid,
"a rejected update must leave the index list untouched"
);
assert_eq!(compacted_generation(&indices, shard), Some(10));
}
#[test]
fn an_equal_generation_rejects() {
let shard = Uuid::new_v4();
let mut indices = indices_with(MemWalIndexDetails {
compacted_sstables: vec![CompactedSsTable::new(shard, 10)],
..Default::default()
});
let err = update_mem_wal_index_compacted_sstables(
&mut indices,
2,
vec![CompactedSsTable::new(shard, 10)],
)
.unwrap_err();
assert!(
err.to_string().contains("Stale SSTable compaction"),
"{err}"
);
}
#[test]
fn duplicate_shards_in_one_update_reject() {
let shard = Uuid::new_v4();
let mut indices = indices_with(MemWalIndexDetails::default());
let err = update_mem_wal_index_compacted_sstables(
&mut indices,
1,
vec![
CompactedSsTable::new(shard, 5),
CompactedSsTable::new(shard, 6),
],
)
.unwrap_err();
assert!(err.to_string().contains("Duplicate shard"), "{err}");
}
#[test]
fn a_missing_mem_wal_index_rejects() {
let mut indices: Vec<IndexMetadata> = Vec::new();
let err = update_mem_wal_index_compacted_sstables(
&mut indices,
1,
vec![CompactedSsTable::new(Uuid::new_v4(), 5)],
)
.unwrap_err();
assert!(err.to_string().contains("does not exist"), "{err}");
assert!(indices.is_empty(), "nothing should have been created");
}
#[test]
fn recording_progress_keeps_the_system_index_position() {
let shard = Uuid::new_v4();
let mut indices = indices_with(MemWalIndexDetails::default());
indices.push(IndexMetadata {
name: "other_index".to_string(),
..indices[0].clone()
});
update_mem_wal_index_compacted_sstables(
&mut indices,
2,
vec![CompactedSsTable::new(shard, 5)],
)
.unwrap();
assert_eq!(indices[0].name, MEM_WAL_INDEX_NAME);
assert_eq!(indices[1].name, "other_index");
}
#[test]
fn recording_progress_preserves_unrelated_mem_wal_state() {
let shard = Uuid::new_v4();
let other_shard = Uuid::new_v4();
let mut writer_config_defaults = HashMap::new();
writer_config_defaults.insert("target_size".to_string(), "64MB".to_string());
let details = MemWalIndexDetails {
snapshot_ts_millis: 12_345,
num_shards: 4,
maintained_indexes: vec!["vector_idx".to_string()],
compacted_sstables: vec![CompactedSsTable::new(other_shard, 7)],
writer_config_defaults: writer_config_defaults.clone(),
..Default::default()
};
let mut indices = indices_with(details);
update_mem_wal_index_compacted_sstables(
&mut indices,
2,
vec![CompactedSsTable::new(shard, 5)],
)
.unwrap();
let after = load_mem_wal_index_details(indices[0].clone()).unwrap();
assert_eq!(after.snapshot_ts_millis, 12_345);
assert_eq!(after.num_shards, 4);
assert_eq!(after.maintained_indexes, vec!["vector_idx".to_string()]);
assert_eq!(after.writer_config_defaults, writer_config_defaults);
assert_eq!(compacted_generation(&indices, other_shard), Some(7));
}
#[tokio::test]
async fn a_stale_generation_against_latest_head_publishes_nothing() {
use lance_table::format::Fragment;
let shard = Uuid::new_v4();
let dataset = test_dataset_with_mem_wal().await;
let version = dataset.manifest.version;
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(Transaction::new(
version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 10)],
},
None,
))
.await
.unwrap();
let version_before = dataset.manifest.version;
let fragments_before = dataset.get_fragments().len();
let stale = Transaction::new(
version_before,
Operation::Update {
removed_fragment_ids: vec![],
updated_fragments: vec![],
new_fragments: vec![Fragment::new(999)],
fields_modified: vec![],
compacted_sstables: vec![CompactedSsTable::new(shard, 5)],
fields_for_preserving_frag_bitmap: vec![],
update_mode: None,
inserted_rows_filter: None,
updated_fragment_offsets: None,
},
None,
);
let mut dataset = dataset;
let result = CommitBuilder::new(Arc::new(dataset.clone()))
.execute(stale)
.await;
let err = result.expect_err("a stale generation must fail the whole commit");
assert!(
err.to_string().contains("Stale SSTable compaction"),
"expected stale-generation rejection, got {err}"
);
dataset.checkout_latest().await.unwrap();
let latest = dataset;
assert_eq!(
latest.manifest.version, version_before,
"no new table version may be published"
);
assert_eq!(
latest.get_fragments().len(),
fragments_before,
"the row mutation must not be published"
);
let details = load_mem_wal_index_details(
latest
.load_indices()
.await
.unwrap()
.iter()
.find(|idx| idx.name == MEM_WAL_INDEX_NAME)
.unwrap()
.clone(),
)
.unwrap();
assert_eq!(
details.compacted_sstables[0].generation, 10,
"the recorded generation must be unchanged"
);
}
#[test]
fn test_empty_compacted_sstables_noop() {
let mut indices = Vec::new();
update_mem_wal_index_compacted_sstables(&mut indices, 1, vec![]).unwrap();
assert!(indices.is_empty());
}
#[tokio::test]
async fn test_describe_indices_includes_mem_wal_system_index() {
use crate::index::DatasetIndexExt;
use lance_index::IndexType;
use lance_index::scalar::ScalarIndexParams;
let mut dataset = test_dataset_with_mem_wal().await;
dataset
.create_index(
&["a"],
IndexType::Scalar,
None,
&ScalarIndexParams::default(),
true,
)
.await
.unwrap();
let shard = Uuid::new_v4();
let txn = Transaction::new(
dataset.manifest.version,
Operation::UpdateMemWalState {
compacted_sstables: vec![CompactedSsTable::new(shard, 1)],
},
None,
);
let dataset = CommitBuilder::new(Arc::new(dataset))
.execute(txn)
.await
.unwrap();
let mem_wal = dataset
.load_indices()
.await
.unwrap()
.iter()
.find(|i| i.name == MEM_WAL_INDEX_NAME)
.unwrap()
.clone();
assert!(mem_wal.fragment_bitmap.is_none());
let descriptions = dataset.describe_indices(None).await.unwrap();
let mem_wal_desc = descriptions
.iter()
.find(|d| d.name() == MEM_WAL_INDEX_NAME)
.expect("__mem_wal must be described, not skipped");
assert_eq!(
mem_wal_desc.index_type(),
"MemWal",
"system index type must resolve via infer_system_index_type"
);
assert_eq!(
mem_wal_desc.rows_indexed(),
0,
"a bitmap-less system index indexes zero rows"
);
assert_eq!(
descriptions.len(),
2,
"both the real scalar index and __mem_wal must be listed"
);
}
}