kcode-k1-persons-store 0.2.0

Current SQLite storage and recovery for the K1 persons projection
Documentation
use std::{path::Path, time::Instant};

use kcode_k1_persons_store::{PersonId, Store, TxId};
use kcode_k1_txn_ordering::{GENESIS_PARENT, K1TxnOrdering};
use rusqlite::{Connection, params};
use tempfile::TempDir;

fn canonical_ordering(root: &Path, count: usize) -> (K1TxnOrdering, Vec<TxId>) {
    let ordering = K1TxnOrdering::open(root).unwrap();
    let mut parent = GENESIS_PARENT;
    let mut ids = Vec::with_capacity(count);
    for nonce in 0..count {
        let mut bytes = Vec::with_capacity(136);
        bytes.extend_from_slice(parent.as_bytes());
        bytes.extend_from_slice(&(nonce as u64).to_le_bytes());
        bytes.extend_from_slice(&[7; 32]);
        bytes.push(b'p');
        bytes.extend_from_slice(&[0; 19]);
        bytes.extend_from_slice(&[0; 64]);
        let id = TxId::for_transaction(&bytes);
        ordering.submit_txn(&bytes).unwrap();
        ids.push(id);
        parent = id;
    }
    (ordering, ids)
}

fn person(id: TxId) -> PersonId {
    PersonId::from_tx_id(id)
}

#[test]
fn ten_thousand_person_snapshot_opens_under_five_seconds() {
    fn assert_send<T: Send>() {}
    assert_send::<Store>();

    let store_root = TempDir::new().unwrap();
    let ordering_root = TempDir::new().unwrap();
    let (ordering, ids) = canonical_ordering(ordering_root.path(), 10_001);
    let (store, snapshot) = Store::open(store_root.path(), &ordering).unwrap();
    assert!(snapshot.persons().is_empty());
    drop(store);

    let mut connection = Connection::open(store_root.path().join("persons.sqlite3")).unwrap();
    let transaction = connection.transaction().unwrap();
    {
        let mut insert = transaction
            .prepare("INSERT INTO persons(id, root, name) VALUES(?1, ?2, ?3)")
            .unwrap();
        for index in 0..10_000 {
            let id = ids[index].as_bytes();
            let root = if index < 5_000 { ids[0].as_bytes() } else { id };
            let name = if index == 0 || index >= 5_000 {
                Some(format!("Person {index}"))
            } else {
                None
            };
            insert.execute(params![&id[..], &root[..], name]).unwrap();
        }
    }
    transaction
        .execute(
            "UPDATE meta SET checkpoint = ?1",
            params![&ids[10_000].as_bytes()[..]],
        )
        .unwrap();
    transaction.commit().unwrap();
    drop(connection);

    let started = Instant::now();
    let (_, snapshot) = Store::open(store_root.path(), &ordering).unwrap();
    assert!(started.elapsed().as_secs_f64() < 5.0);
    assert_eq!(snapshot.persons().len(), 10_000);
    assert_eq!(
        snapshot
            .persons()
            .iter()
            .filter(|stored| stored.root() == person(ids[0]))
            .count(),
        5_000
    );
}