use super::*;
use crate::features::storage::sstable;
fn open(base: &std::path::Path) -> StorageHandle {
open_store(StorageConfig {
buffer_pool_pages: 8,
wal_dir: base.join("wal"),
wal_segment_max_bytes: 1 << 20,
manifest_path: base.join("ir.manifest"),
sstable_dir: base.join("sst"),
})
.unwrap()
}
#[test]
fn retire_vector_space_marks_space_retired() {
let base = temp_dir("retire_marks_retired");
let mut handle = open(&base);
let space_id = 1_u32;
put_full_node(&mut handle, 1, 1, &[]).unwrap();
let payload = encode_vector_payload_f32(space_id, VectorMetric::Cosine, &[1.0, 0.0], false);
put_vector_delta(&mut handle, &encode_delta(1, 2, &payload)).unwrap();
retire_vector_space(&mut handle, space_id).unwrap();
assert!(
handle.manifest.is_space_retired(space_id),
"space must be retired after retire_vector_space"
);
}
#[test]
fn put_vector_delta_rejects_retired_space() {
let base = temp_dir("retire_rejects_delta");
let mut handle = open(&base);
let space_id = 2_u32;
put_full_node(&mut handle, 1, 1, &[]).unwrap();
let payload = encode_vector_payload_f32(space_id, VectorMetric::Cosine, &[1.0, 0.0], false);
put_vector_delta(&mut handle, &encode_delta(1, 2, &payload)).unwrap();
retire_vector_space(&mut handle, space_id).unwrap();
let payload2 = encode_vector_payload_f32(space_id, VectorMetric::Cosine, &[0.9, 0.1], false);
let err = put_vector_delta(&mut handle, &encode_delta(1, 3, &payload2)).unwrap_err();
assert!(
matches!(err, StorageError::SpaceRetired(id) if id == space_id),
"expected SpaceRetired({space_id}), got {err:?}"
);
}
#[test]
fn put_vector_delta_batch_rejects_retired_space() {
let base = temp_dir("retire_rejects_batch");
let mut handle = open(&base);
let space_id = 3_u32;
put_full_node(&mut handle, 1, 1, &[]).unwrap();
retire_vector_space(&mut handle, space_id).unwrap();
let payload = encode_vector_payload_f32(space_id, VectorMetric::Cosine, &[1.0, 0.0], false);
let deltas = vec![encode_delta(1, 2, &payload)];
let err = put_vector_delta_batch(&mut handle, &deltas).unwrap_err();
assert!(
matches!(err, StorageError::SpaceRetired(id) if id == space_id),
"expected SpaceRetired({space_id}), got {err:?}"
);
}
#[test]
fn retired_state_survives_manifest_reload() {
let base = temp_dir("retire_manifest_reload");
let space_id = 4_u32;
{
let mut handle = open(&base);
put_full_node(&mut handle, 1, 1, &[]).unwrap();
let payload = encode_vector_payload_f32(space_id, VectorMetric::Cosine, &[1.0, 0.0], false);
put_vector_delta(&mut handle, &encode_delta(1, 2, &payload)).unwrap();
retire_vector_space(&mut handle, space_id).unwrap();
}
let handle2 = open(&base);
assert!(
handle2.manifest.is_space_retired(space_id),
"retired state must survive manifest reload"
);
}
#[test]
fn compaction_removes_retired_space_vector_deltas() {
let base = temp_dir("retire_compaction_cleanup");
let mut handle = open(&base);
let space_id = 5_u32;
put_full_node(&mut handle, 10, 1, &[]).unwrap();
let payload = encode_vector_payload_f32(space_id, VectorMetric::Cosine, &[1.0, 0.0], false);
put_vector_delta(&mut handle, &encode_delta(10, 2, &payload)).unwrap();
flush(&mut handle).unwrap();
put_full_node(&mut handle, 11, 1, &[]).unwrap();
flush(&mut handle).unwrap();
assert_eq!(
handle.l0_runs.len(),
2,
"need 2 SSTables to trigger compaction"
);
retire_vector_space(&mut handle, space_id).unwrap();
compact(&mut handle, Some(0)).unwrap();
assert_eq!(handle.l0_runs.len(), 1, "compaction should reduce to 1 run");
let compacted_path = handle.l0_runs[0].clone();
let table = sstable::read_sstable(&compacted_path).unwrap();
let has_retired_vector_delta = table.entries.iter().any(|e| {
if e.kind != sstable::EntryKind::VectorDelta {
return false;
}
matches!(
decode_vector_payload(&e.value),
Ok(DecodedVectorPayload::Structured(v)) if v.descriptor.space_id == space_id
)
});
assert!(
!has_retired_vector_delta,
"compaction must remove VectorDelta entries for retired space {space_id}"
);
}
#[test]
fn non_retired_space_unaffected_by_other_retirement() {
let base = temp_dir("retire_other_unaffected");
let mut handle = open(&base);
put_full_node(&mut handle, 1, 1, &[]).unwrap();
put_full_node(&mut handle, 2, 1, &[]).unwrap();
let payload1 = encode_vector_payload_f32(1, VectorMetric::Cosine, &[1.0, 0.0], false);
let payload2 = encode_vector_payload_f32(2, VectorMetric::Cosine, &[0.0, 1.0], false);
put_vector_delta(&mut handle, &encode_delta(1, 2, &payload1)).unwrap();
put_vector_delta(&mut handle, &encode_delta(2, 2, &payload2)).unwrap();
retire_vector_space(&mut handle, 1).unwrap();
let payload2b = encode_vector_payload_f32(2, VectorMetric::Cosine, &[0.1, 0.9], false);
put_vector_delta(&mut handle, &encode_delta(2, 3, &payload2b)).unwrap();
}