//! v13 recounts the per-pds account counts from the repo rows the way v6 does, which repairs //! a database whose counts drifted from the rows they count use fjall::OwnedWriteBatch; use miette::{Context, IntoDiagnostic, Result}; use crate::db::Db; use crate::types::v7; use super::v6::{counted_host, recount_pds_accounts}; pub(super) fn recount_pds_account_counts(db: &Db, batch: &mut OwnedWriteBatch) -> Result { recount_pds_accounts(db, batch, |value| { let state: v7::RepoState = rmp_serde::from_slice(value) .into_diagnostic() .wrap_err("invalid v12 repo state")?; Ok(counted_host(state.active, state.pds.as_deref())) }) } #[cfg(test)] mod tests { use super::*; use crate::config::Config; use crate::db::keys; use crate::db::migration::rewind_version_for_test; use jacquard_common::CowStr; use jacquard_common::types::did::Did; use tempfile::tempdir; fn counts_on_disk(db: &Db) -> Result, Vec)>> { db.counts .prefix(keys::count_keyspace_key("p|")) .map(|guard| { let (key, value) = guard.into_inner().into_diagnostic()?; Ok((key.to_vec(), value.to_vec())) }) .collect() } #[test] fn repairs_drifted_per_pds_counts_from_the_rows() -> Result<()> { let tmp = tempdir().into_diagnostic()?; let cfg = Config { database_path: tmp.path().to_path_buf(), ..Default::default() }; let accounts = |db: &Db, host| db.get_count_sync(&keys::pds_account_count_key(host)); { let db = Db::open(&cfg)?; let mut batch = db.inner.batch(); let rows = [ ("did:web:one.test", "https://pds.example/", true), ("did:web:two.test", "https://pds.example/", true), ("did:web:gone.test", "https://gone.example/", false), ("did:web:also-gone.test", "https://gone.example/", false), ("did:web:fresh.test", "https://fresh.example/", true), ]; for (did, pds, active) in rows { let row = v7::RepoState { active, status: v7::RepoStatus::Desynchronized, root: None, last_message_time: None, last_identity_time: None, last_account_time: None, last_updated_at: 0, signing_key: None, pds: Some(CowStr::Borrowed(pds)), handle: None, }; batch.insert( &db.repos, keys::repo_key(&Did::new_static(did).into_diagnostic()?), rmp_serde::to_vec(&row).into_diagnostic()?, ); } // what the writers that never counted left behind: one short, one host whose // rows all went inactive still counted, one never counted at all for (host, count) in [("pds.example", 1), ("gone.example", 2)] { crate::db::set_ks_count(&mut batch, &db, &keys::pds_account_count_key(host), count); } batch.insert( &db.counts, keys::count_delta_key(1_000_000, &keys::pds_account_count_key("gone.example")), 3_i64.to_be_bytes(), ); batch.commit().into_diagnostic()?; rewind_version_for_test(&db, 9)?; db.persist()?; } let repaired = { let db = Db::open(&cfg)?; assert_eq!(accounts(&db, "pds.example"), 2); assert_eq!(accounts(&db, "gone.example"), 0); assert_eq!(accounts(&db, "fresh.example"), 1); let on_disk = counts_on_disk(&db)?; rewind_version_for_test(&db, 9)?; db.persist()?; on_disk }; let db = Db::open(&cfg)?; assert_eq!(counts_on_disk(&db)?, repaired); assert_eq!(accounts(&db, "pds.example"), 2); assert_eq!(accounts(&db, "gone.example"), 0); assert_eq!(accounts(&db, "fresh.example"), 1); Ok(()) } }