use super::*;
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 quantized_i8_encode_put_decode_round_trip() {
let base = temp_dir("qi8_storage_round_trip");
let mut handle = open(&base);
let original = vec![0.8_f32, -0.4, 0.2, 0.1];
let space_id = 5_u32;
let node_id = 42_u64;
put_full_node(&mut handle, node_id, 1, &[]).unwrap();
let payload =
encode_vector_payload_quantized_i8(space_id, VectorMetric::Cosine, &original, false)
.unwrap();
let delta = encode_delta(node_id, 2, &payload);
put_vector_delta(&mut handle, &delta).unwrap();
let logical = get_logical_node(&mut handle, node_id).unwrap();
let vector_delta = logical
.deltas
.iter()
.find(|e| e.kind == crate::features::storage::sstable::EntryKind::VectorDelta)
.expect("vector delta must be present");
let decoded = decode_vector_payload(&vector_delta.value).unwrap();
let DecodedVectorPayload::Structured(vec) = decoded else {
panic!("expected structured vector payload");
};
assert_eq!(vec.descriptor.space_id, space_id);
assert_eq!(vec.descriptor.encoding, VectorEncoding::QuantizedI8);
assert_eq!(vec.descriptor.metric, VectorMetric::Cosine);
assert_eq!(vec.values.len(), original.len());
for (got, expected) in vec.values.iter().zip(original.iter()) {
assert!(
(got - expected).abs() < 0.02,
"quantized round-trip error too large: got {got}, expected {expected}"
);
}
}
#[test]
fn quantized_i8_cosine_vector_inserted_into_hnsw() {
let base = temp_dir("qi8_hnsw_insert");
let mut handle = open(&base);
let space_id = 7_u32;
put_full_node(&mut handle, 1, 1, &[]).unwrap();
put_full_node(&mut handle, 2, 1, &[]).unwrap();
let payload1 =
encode_vector_payload_quantized_i8(space_id, VectorMetric::Cosine, &[1.0, 0.0], false)
.unwrap();
let payload2 =
encode_vector_payload_quantized_i8(space_id, VectorMetric::Cosine, &[0.0, 1.0], false)
.unwrap();
put_vector_delta(&mut handle, &encode_delta(1, 2, &payload1)).unwrap();
put_vector_delta(&mut handle, &encode_delta(2, 2, &payload2)).unwrap();
let results = hnsw_search_in_space(&handle, space_id, &[1.0, 0.0], 5);
assert!(
!results.is_empty(),
"HNSW search should return results for QuantizedI8 cosine space"
);
let top_id = results[0].0;
assert_eq!(top_id, 1, "node 1 ([1,0]) should be nearest to query [1,0]");
}
#[test]
fn quantized_i8_wal_replay_round_trip() {
let base = temp_dir("qi8_wal_replay");
let original = vec![0.6_f32, -0.8];
let space_id = 9_u32;
let node_id = 55_u64;
{
let mut handle = open(&base);
put_full_node(&mut handle, node_id, 1, &[]).unwrap();
let payload =
encode_vector_payload_quantized_i8(space_id, VectorMetric::Cosine, &original, false)
.unwrap();
put_vector_delta(&mut handle, &encode_delta(node_id, 2, &payload)).unwrap();
}
let mut handle = open(&base);
recover_from_wal(&mut handle).unwrap();
let logical = get_logical_node(&mut handle, node_id).unwrap();
let vector_delta = logical
.deltas
.iter()
.find(|e| e.kind == crate::features::storage::sstable::EntryKind::VectorDelta)
.expect("vector delta must survive WAL replay");
let decoded = decode_vector_payload(&vector_delta.value).unwrap();
let DecodedVectorPayload::Structured(vec) = decoded else {
panic!("expected structured vector payload after WAL replay");
};
assert_eq!(vec.descriptor.encoding, VectorEncoding::QuantizedI8);
assert_eq!(vec.values.len(), original.len());
for (got, expected) in vec.values.iter().zip(original.iter()) {
assert!(
(got - expected).abs() < 0.02,
"round-trip error after WAL replay too large: got {got}, expected {expected}"
);
}
}