#![cfg(all(test, feature = "persistence"))]
#![allow(deprecated)] #![allow(
clippy::cast_precision_loss,
clippy::float_cmp,
clippy::cast_possible_truncation
)]
use crate::distance::DistanceMetric;
use crate::point::Point;
use crate::Collection;
use std::path::PathBuf;
fn hnsw_saved_on_disk(path: &std::path::Path) -> bool {
path.join("native_meta.bin").exists()
}
fn make_points(start: u64, n: u64) -> Vec<Point> {
(start..start + n)
.map(|i| {
let f = i as f32;
Point::without_payload(i, vec![f, f + 1.0, f + 2.0, f + 3.0])
})
.collect()
}
#[test]
fn test_flush_skips_hnsw_save_but_data_recoverable_on_reopen() {
let temp = tempfile::tempdir().expect("temp dir");
{
let coll = Collection::create(PathBuf::from(temp.path()), 4, DistanceMetric::Cosine)
.expect("create");
coll.upsert(make_points(0, 5)).expect("upsert");
coll.flush().expect("flush");
assert!(
!hnsw_saved_on_disk(temp.path()),
"flush() should NOT create HNSW files (deferred save)"
);
}
let reopened = Collection::open(PathBuf::from(temp.path())).expect("reopen");
assert_eq!(reopened.len(), 5, "all 5 vectors should be recoverable");
let results = reopened.search(&[0.0, 1.0, 2.0, 3.0], 3).expect("search");
assert!(
!results.is_empty(),
"search should return results after gap recovery"
);
}
#[test]
fn test_flush_full_saves_hnsw_to_disk() {
let temp = tempfile::tempdir().expect("temp dir");
let coll =
Collection::create(PathBuf::from(temp.path()), 4, DistanceMetric::Cosine).expect("create");
coll.upsert(make_points(0, 5)).expect("upsert");
coll.flush_full().expect("flush_full");
assert!(
hnsw_saved_on_disk(temp.path()),
"flush_full() must create HNSW graph files"
);
drop(coll);
let reopened = Collection::open(PathBuf::from(temp.path())).expect("reopen");
assert_eq!(
reopened.index.len(),
5,
"HNSW should have 5 vectors after flush_full + reopen"
);
}
#[test]
fn test_flush_full_then_reopen_has_no_gap() {
let temp = tempfile::tempdir().expect("temp dir");
{
let coll = Collection::create(PathBuf::from(temp.path()), 4, DistanceMetric::Cosine)
.expect("create");
coll.upsert(make_points(0, 10)).expect("upsert");
coll.flush_full().expect("flush_full");
}
let reopened = Collection::open(PathBuf::from(temp.path())).expect("reopen");
assert_eq!(
reopened.index.len(),
10,
"HNSW should have all vectors (saved by flush_full)"
);
}
#[test]
fn test_flush_saves_hnsw_when_insert_threshold_exceeded() {
let temp = tempfile::tempdir().expect("temp dir");
let coll =
Collection::create(PathBuf::from(temp.path()), 4, DistanceMetric::Cosine).expect("create");
coll.inserts_since_last_hnsw_save
.store(10_001, std::sync::atomic::Ordering::Relaxed);
coll.upsert(make_points(0, 5)).expect("upsert");
coll.flush().expect("flush");
assert!(
hnsw_saved_on_disk(temp.path()),
"flush() should save HNSW when insert threshold exceeded"
);
assert_eq!(
coll.inserts_since_last_hnsw_save
.load(std::sync::atomic::Ordering::Relaxed),
0,
"counter should be reset after HNSW save"
);
}
#[test]
fn test_upsert_increments_hnsw_save_counter() {
let temp = tempfile::tempdir().expect("temp dir");
let coll =
Collection::create(PathBuf::from(temp.path()), 4, DistanceMetric::Cosine).expect("create");
coll.upsert(make_points(0, 5)).expect("upsert");
let count = coll
.inserts_since_last_hnsw_save
.load(std::sync::atomic::Ordering::Relaxed);
assert_eq!(count, 5, "counter should be 5 after inserting 5 vectors");
coll.upsert(make_points(10, 3)).expect("upsert2");
let count = coll
.inserts_since_last_hnsw_save
.load(std::sync::atomic::Ordering::Relaxed);
assert_eq!(count, 8, "counter should be 8 after inserting 3 more");
}