use keyvaluedb::KeyValueDB;
use keyvaluedb_sqlite::{Database, DatabaseConfig};
fn cfg(repair: bool) -> DatabaseConfig {
DatabaseConfig::new()
.with_columns(2)
.with_repair_on_corrupt(repair)
}
async fn fill(db: &Database, col: u32, n: u32) {
let mut tx = db.transaction();
for i in 0..n {
tx.put(
col,
format!("key_{i:04}").as_bytes(),
format!("value_{i}").as_bytes(),
);
}
db.write(tx).await.unwrap();
}
#[tokio::test]
async fn clean_open_has_no_repair_report() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("db");
let db = Database::open(&path, cfg(false)).unwrap();
fill(&db, 0, 10).await;
drop(db);
let db = Database::open(&path, cfg(true)).unwrap();
assert!(db.repair_report().is_none());
assert_eq!(
db.get(0, b"key_0003").await.unwrap().unwrap(),
b"value_3".to_vec()
);
}
#[tokio::test]
async fn repair_salvages_readable_rows() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("db");
let db = Database::open(&path, cfg(false)).unwrap();
fill(&db, 0, 500).await;
fill(&db, 1, 100).await;
drop(db);
let mut bytes = std::fs::read(&path).unwrap();
let page_size = u16::from_be_bytes([bytes[16], bytes[17]]) as usize;
let target = (bytes.len() / page_size) / 2;
let start = target * page_size;
bytes[start..start + page_size].fill(0xFF);
std::fs::write(&path, bytes).unwrap();
let db = Database::open(&path, cfg(true)).unwrap();
let report = db.repair_report().expect("repair should have run").clone();
assert!(report.rows_recovered > 0, "report: {report:?}");
assert!(report.corrupt_path.exists());
let mut survivors = 0;
for i in 0..500u32 {
if db
.get(0, format!("key_{i:04}").as_bytes())
.await
.unwrap()
.is_some()
{
survivors += 1;
}
}
assert!(survivors > 300, "survivors: {survivors}");
fill(&db, 0, 10).await;
drop(db);
let db = Database::open(&path, cfg(true)).unwrap();
assert!(db.repair_report().is_none());
}
#[tokio::test]
#[ignore = "needs KVDB_REPAIR_CORPSE pointing at a damaged database copy"]
async fn repair_real_corpse() {
let src = std::env::var("KVDB_REPAIR_CORPSE").expect("set KVDB_REPAIR_CORPSE");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("db");
std::fs::copy(&src, &path).unwrap();
let db = Database::open(
&path,
DatabaseConfig::new()
.with_columns(1)
.with_repair_on_corrupt(true),
)
.unwrap();
let report = db.repair_report().expect("corpse should trigger repair");
eprintln!("repair report: {report:?}");
assert!(report.rows_recovered > 0);
}