use nodedb_client::NodeDb;
use nodedb_lite::storage::engine::StorageEngine;
use nodedb_lite::{Encryption, NodeDbLite, PagedbStorageDefault};
use nodedb_types::document::Document;
use nodedb_types::text_search::TextSearchParams;
use nodedb_types::value::Value;
const COLLECTION: &str = "articles";
const DOC_COUNT: usize = 10;
fn make_doc(i: usize) -> (String, Document) {
let id = format!("doc{i}");
let mut doc = Document::new(&id);
doc.set(
"body",
Value::String(format!(
"rustsearch document number {i} about embedded databases"
)),
);
doc.set("idx", Value::Integer(i as i64));
(id, doc)
}
#[tokio::test]
async fn fts_index_persists_across_restart() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("fts_test.db");
let pre_restart_results: Vec<(String, f32)>;
{
let storage = PagedbStorageDefault::open(&path, Encryption::Plaintext)
.await
.expect("open storage");
let db = NodeDbLite::open(storage).await.expect("open NodeDbLite");
for i in 0..DOC_COUNT {
let (_id, doc) = make_doc(i);
db.document_put(COLLECTION, doc)
.await
.expect("document_put");
}
let results = db
.text_search(
COLLECTION,
"body",
"rustsearch",
DOC_COUNT,
TextSearchParams::default(),
None,
)
.await
.expect("text_search before flush");
assert!(
!results.is_empty(),
"expected at least one result before flush"
);
pre_restart_results = results.iter().map(|r| (r.id.clone(), r.distance)).collect();
db.flush().await.expect("flush");
db.shutdown().await;
}
{
{
use nodedb_types::Namespace;
let storage = PagedbStorageDefault::open(&path, Encryption::Plaintext)
.await
.expect("storage for fts count check");
let fts_count = storage.count(Namespace::Fts).await.expect("fts count");
assert!(
fts_count > 0,
"Namespace::Fts should have entries after flush, got 0"
);
}
let storage = PagedbStorageDefault::open(&path, Encryption::Plaintext)
.await
.expect("reopen storage");
let db = NodeDbLite::open(storage).await.expect("reopen NodeDbLite");
let results = db
.text_search(
COLLECTION,
"body",
"rustsearch",
DOC_COUNT,
TextSearchParams::default(),
None,
)
.await
.expect("text_search after restart");
assert!(
!results.is_empty(),
"expected at least one result after restart"
);
let post_restart_results: Vec<(String, f32)> =
results.iter().map(|r| (r.id.clone(), r.distance)).collect();
assert_eq!(
pre_restart_results.len(),
post_restart_results.len(),
"result count mismatch after restart"
);
let mut pre_sorted = pre_restart_results.clone();
let mut post_sorted = post_restart_results.clone();
pre_sorted.sort_by(|a, b| a.0.cmp(&b.0));
post_sorted.sort_by(|a, b| a.0.cmp(&b.0));
for (pre, post) in pre_sorted.iter().zip(post_sorted.iter()) {
assert_eq!(
pre.0, post.0,
"doc_id mismatch after restart: pre={}, post={}",
pre.0, post.0
);
assert!(
(pre.1 - post.1).abs() < f32::EPSILON,
"score mismatch for {}: pre={}, post={}",
pre.0,
pre.1,
post.1
);
}
}
}