obj-db 1.1.2

Embedded document database. Stable file format, full ACID, single-file portability.
Documentation
//! Shared bench helpers (M14 #119).
//!
//! The five existing benches under `crates/obj/benches/` each inline
//! the same tempdir + open-Db boilerplate plus a near-identical
//! `OBJ_BENCH_ENFORCE=1` env-var probe. This module consolidates the
//! duplicated parts so the new `perf_table` bench (and the existing
//! ones) call into one definition.
//!
//! Each `benches/*.rs` is its own independent binary crate per
//! Cargo's bench layout, so this module is compiled separately per
//! consumer. Different consumers exercise different helpers; the
//! `#![allow(dead_code)]` below keeps unused-fn warnings from firing
//! on the consumers that only need a subset.
//!
//! Power-of-ten posture:
//! - Rule 2: every helper that takes a count argument is bounded by
//!   that argument; nothing here loops without a caller-supplied cap.
//! - Rule 4: each helper is ≤ 60 lines.
//! - Rule 7: every `Result` is propagated or `expect`-ed with a
//!   diagnostic message — bench code is allowed to panic on setup
//!   failure (it is not a production path).

// Each bench binary picks a different subset of these helpers; the
// unused ones would otherwise warn under workspace `-D warnings`.
#![allow(dead_code)]

use std::path::PathBuf;

use obj::Db;
use tempfile::TempDir;

/// RAII wrapper around a `(TempDir, Db)` pair so the caller can hold
/// onto the database without juggling two bindings; dropping the
/// wrapper drops the `Db` first (releasing file locks) and then the
/// `TempDir` (which deletes the on-disk file).
///
/// Field order matters: `Db` must drop before `TempDir` so the file
/// handles release their advisory locks before the directory is
/// torn down. Rust drops struct fields in declaration order.
pub struct BenchDb {
    pub db: Db,
    pub path: PathBuf,
    #[allow(dead_code)] // held for RAII; the directory drops last.
    pub tempdir: TempDir,
}

/// Open a fresh on-disk `Db` inside a tempdir. The bench owns the
/// returned `BenchDb`; dropping it deletes the underlying file.
///
/// `name` is used for the database file basename so multi-bench
/// runs (e.g. `cargo bench` over the whole crate) don't collide on
/// criterion's output directory listings.
///
/// Panics on tempdir / open failure: bench setup is not a production
/// path. Power-of-ten Rule 7: documented `.expect` with diagnostic.
pub fn fresh_db(name: &str) -> BenchDb {
    let tempdir = TempDir::new().expect("tempdir");
    let path = tempdir.path().join(format!("{name}.obj"));
    let db = Db::open(&path).expect("open fresh db");
    BenchDb { db, path, tempdir }
}

/// Drop the supplied `Db` and re-open the same path. Forces the WAL
/// to flush via the open-time recovery checkpoint — the resulting
/// `Db` sees a quiescent file with no in-flight WAL frames.
///
/// Caller passes the `BenchDb` by value; the returned `BenchDb`
/// shares the same `TempDir` so the file path survives the
/// drop/reopen cycle. Power-of-ten Rule 4: single responsibility.
pub fn reopen(bench_db: BenchDb) -> BenchDb {
    let BenchDb { db, path, tempdir } = bench_db;
    drop(db);
    let db = Db::open(&path).expect("re-open db");
    BenchDb { db, path, tempdir }
}

/// `OBJ_BENCH_ENFORCE=1` (or any non-empty, non-"0" value) upgrades
/// each bench's informational target check to a hard failure. CI's
/// `bench.yml` workflow sets this on the Linux x86-64 runner; local
/// `cargo bench` keeps the informational mode by default.
///
/// Power-of-ten Rule 7: the `Result` from `env::var` is filtered
/// explicitly — neither absence nor an empty value should enable
/// the gate.
#[must_use]
pub fn bench_enforce_enabled() -> bool {
    std::env::var("OBJ_BENCH_ENFORCE")
        .ok()
        .is_some_and(|v| !v.is_empty() && v != "0")
}