Skip to main content

Db

Struct Db 

Source
pub struct Db { /* private fields */ }
Expand description

The embedded document database.

Db is Send + Sync; share across threads via Arc<Db> for concurrent reader / single-writer access. See design.md § “Concurrent readers, one writer”.

At M6 the public Db is hard-typed against obj_core::FileHandle. A future refactor may make it generic over F: FileBackend so fault-injection harnesses can build on the same API; today the test helpers reach for the lower-level obj-core building blocks instead.

Implementations§

Source§

impl Db

Source

pub fn stat(&self) -> Result<DbStat>

One-shot snapshot of header + catalog summary.

CLI-facing — used by obj stat. May move pre-1.0; user code should prefer the typed Db::iter_all / Db::read_transaction(|tx| tx.collection::<T>()) APIs.

§Errors
  • Error::Busy if the pager mutex is poisoned or contested.
  • Pager / B-tree / postcard errors propagated from the catalog walk + per-collection primary-tree walks.
Source

pub fn dump_raw(&self, collection: &str, limit: usize) -> Result<DumpIter<'_>>

Streaming type-erased walk of a named collection’s primary B-tree. Each step yields a DumpRecord: the primary id, the per-doc header (decoded), and the raw payload bytes.

limit == 0 is treated as unbounded; the caller’s iteration loop is the implicit bound. Power-of-ten Rule 2: callers who want a hard cap must impose it on their take(...).

CLI-facing — used by obj dump. The Document trait is NOT consulted; schema-aware decode requires a registered type and is the caller’s responsibility above this layer.

§Namespace dispatch (M11 #132) + snapshot isolation (#135)

A bare collection resolves against the calling Db’s own env, but BOTH the catalog lookup and the primary-tree walk go through the read txn’s pinned obj_core::ReaderSnapshot (#135) — the scan is snapshot-isolated against concurrent writers exactly as the point reads (get_via_snapshot) and the attached path are; a writer’s post-snapshot node splits / page reuse cannot surface as a spurious Corruption. A "<namespace>.<tail>" name resolves against the read-only database attached under <namespace>: the iterator pins one obj_core::ReaderSnapshot on the attached env for its whole lifetime and walks that env’s primary tree as-of the pinned LSN — mirroring the namespace dispatch the point-read shims (get_with_version etc.) gained in #123, so all() / query.fetch() over an attachment see the same documents the namespaced point reads do.

§Errors
Source§

impl Db

Source

pub fn open<P: AsRef<Path>>(path: P) -> Result<Self>

Open or create a file-backed database at path with default configuration.

Creates the file if absent; reopens otherwise. A Db is Send + Sync — share across threads via Arc<Db> for the concurrent-reader / single-writer workload documented in docs/concurrency.md.

For an ephemeral database use Db::memory; for a read-only handle that coexists with another process’s writer use Db::open_readonly; for custom durability / cache / lock knobs use Db::open_with + Config.

§Examples
use obj::Db;

let dir = tempfile::tempdir()?;

// File-backed. Creates the file if absent; reopens otherwise.
let _db = Db::open(dir.path().join("app.obj"))?;

// In-memory. No persistence, no file locks. Useful for tests.
let _mem = Db::memory()?;

// Read-only. Coexists safely with a writer in another process.
// Every mutating call returns `Err(Error::ReadOnly { ... })`.
let _ro = Db::open_readonly(dir.path().join("app.obj"))?;
§Errors

Returns the underlying Error from obj_core::pager::Pager::open on syscall or format failure.

Source

pub fn open_with<P: AsRef<Path>>(path: P, config: Config) -> Result<Self>

Open or create a file-backed database with config.

§Errors

As Db::open.

Source

pub fn memory() -> Result<Self>

Open a fresh in-memory database. No persistence, no file locks. Useful for unit tests and ephemeral workloads.

§Errors

Returns Error::InvalidArgument only if config has zero cache frames.

Source

pub fn memory_with(config: Config) -> Result<Self>

As Db::memory with a caller-supplied Config.

§Errors

As Db::memory.

Source

pub fn open_readonly<P: AsRef<Path>>(path: P) -> Result<Self>

Open the database at path in read-only mode. The resulting Db rejects every mutating operation with Err(Error::ReadOnly { ... }). Coexists safely with a writer in another process via the cross-process reader lock.

§Errors

As Db::open.

Source

pub fn transaction<R, F>(&self, body: F) -> Result<R>
where F: FnOnce(&mut WriteTxn<'_>) -> Result<R>,

Run a closure inside a write transaction.

Begins a WriteTxn, runs the closure with &mut tx. If the closure returns Ok(r), the transaction is committed and Ok(r) is returned. If the closure returns Err(e), the transaction is rolled back and Err(e) is returned. A panic inside the closure unwinds with an implicit rollback via the WriteTxn Drop impl. See docs/concurrency.md for the lock-acquisition contract.

§Examples

Atomic batch — both inserts commit together or not at all:

use obj::Db;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, obj::Document)]
struct Order {
    customer_id: u64,
    total_cents: u64,
}

let dir = tempfile::tempdir()?;
let db = Db::open(dir.path().join("txn.obj"))?;

let (a, b) = db.transaction(|tx| {
    let coll = tx.collection::<Order>()?;
    let a = coll.insert(Order { customer_id: 1, total_cents: 50 })?;
    let b = coll.insert(Order { customer_id: 2, total_cents: 200 })?;
    Ok((a, b))
})?;
assert_ne!(a, b, "freshly-allocated ids are distinct");

Returning Err(_) rolls every staged write back; the Err the closure returns is the Err the caller sees:

use obj::{Db, Error};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, obj::Document)]
struct Order { total_cents: u64 }

let dir = tempfile::tempdir()?;
let db = Db::open(dir.path().join("rollback.obj"))?;
let id = db.insert(Order { total_cents: 10 })?;

let outcome: obj::Result<()> = db.transaction(|tx| {
    let coll = tx.collection::<Order>()?;
    coll.update(id, |o| { o.total_cents = 99_999; })?;
    Err(Error::InvalidArgument("synthetic abort"))
});
assert!(matches!(outcome, Err(Error::InvalidArgument(_))));

let after: Order = db
    .get::<Order>(id)?
    .ok_or(Error::InvalidArgument("just inserted"))?;
assert_eq!(after.total_cents, 10, "rolled-back update is invisible");
§Errors
  • Error::ReadOnly if the database was opened read-only.
  • Error::Busy if a sibling transaction holds the lock(s).
  • Any error the closure returns.
Source

pub fn read_transaction<R, F>(&self, body: F) -> Result<R>
where F: FnOnce(&ReadTxn<'_>) -> Result<R>,

Run a closure inside a read transaction. See Self::transaction for the closure shape and atomicity contract; reads inside body observe a consistent snapshot of the database.

Every read inside the closure observes a single consistent snapshot — the snapshot is pinned at the moment the closure begins. Concurrent writers do not affect what the closure sees.

§Examples

Two reads inside one read_transaction see the same value even if a writer commits in between:

use obj::Db;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, obj::Document)]
struct Order { total_cents: u64 }

let dir = tempfile::tempdir()?;
let db = Db::open(dir.path().join("read.obj"))?;
let id = db.insert(Order { total_cents: 10 })?;

let (a, b) = db.read_transaction(|tx| {
    let coll = tx.collection::<Order>()?;
    let a = coll.get(id)?;
    let b = coll.get(id)?;
    Ok((a, b))
})?;
assert_eq!(a, b);
§Errors
  • Error::Busy if the reader lock could not be acquired.
  • Any error the closure returns.
Source

pub fn attach<P: AsRef<Path>>( &mut self, path: P, namespace: impl Into<String>, ) -> Result<()>

Attach the database at path under namespace. The attached file is opened read-only; collections in the attached file become visible to subsequent Db::collection::<T>() calls (and the one-shot per-op API — Db::get::<T>(), etc.) when T::COLLECTION is of the form <namespace>.<collection_name>.

Writes against namespaced collections return Error::AttachedDatabaseIsReadOnly.

Each attached database gets its own snapshot pinned at read-transaction begin; Db::detach removes the registry entry but in-flight reads complete against their pinned snapshot.

§Examples
use obj::Db;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, obj::Document)]
#[obj(collection = "orders_attach_doc")]
struct Order { total_cents: u64 }

// Same struct shape, namespaced collection name. Reads
// against this type route to the attached "archive" db.
#[derive(Debug, Clone, Serialize, Deserialize, obj::Document)]
#[obj(collection = "archive.orders_attach_doc")]
struct ArchivedOrder { total_cents: u64 }

let dir = tempfile::tempdir()?;
let live = dir.path().join("live.obj");
let archive = dir.path().join("archive.obj");

// Seed the archive (writes go through its own un-namespaced type).
{
    let archive_db = Db::open(&archive)?;
    let _ = archive_db.insert(Order { total_cents: 999 })?;
}

// Open the live db, attach the archive under "archive".
let mut db = Db::open(&live)?;
let _ = db.insert(Order { total_cents: 100 })?;
db.attach(&archive, "archive")?;

// One read transaction, two collections.
db.read_transaction(|tx| {
    let live = tx.collection::<Order>()?;
    let arch = tx.collection::<ArchivedOrder>()?;
    assert_eq!(live.all()?.len(), 1);
    assert_eq!(arch.all()?.len(), 1);
    Ok(())
})?;

db.detach("archive")?;
§Errors
Source

pub fn attach_shared<P: AsRef<Path>>( &self, path: P, namespace: impl Into<String>, ) -> Result<()>

Shared-reference (&self) form of Self::attach. Attaches the database at path under namespace through &self, for callers that hold a shared handle (e.g. an Arc<Db>) and cannot obtain &mut self.

Behaviour is identical to Self::attach: the attachment registry is interior-mutable, guarded by the same per-Db mutex, so concurrent attach_shared / detach_shared calls from multiple threads serialise on that mutex. The duplicate- namespace re-check after the read-only open closes the race between two concurrent attaches of the same namespace.

§Examples
use std::sync::Arc;
use obj::Db;

let dir = tempfile::tempdir()?;
let live = dir.path().join("live.obj");
let archive = dir.path().join("archive.obj");
let _ = Db::open(&archive)?;

// A shared handle cannot call `&mut self` `attach`, but can
// call `attach_shared`.
let db = Arc::new(Db::open(&live)?);
db.attach_shared(&archive, "archive")?;
db.detach_shared("archive")?;
§Errors
Source

pub fn detach(&mut self, namespace: &str) -> Result<()>

Remove the attachment registered under namespace. Returns Error::CollectionNamespaceUnknown if the namespace is not attached.

In-flight read transactions hold their own snapshot pins on the attached env; detach removes the registry entry, but the in-flight read may still complete against its pinned snapshot.

§Errors
Source

pub fn detach_shared(&self, namespace: &str) -> Result<()>

Shared-reference (&self) form of Self::detach. Removes the attachment registered under namespace through &self, for callers that hold a shared handle (e.g. an Arc<Db>).

Behaviour is identical to Self::detach. In-flight read transactions hold their own snapshot pins on the attached env; detach_shared removes the registry entry, but the in-flight read may still complete against its pinned snapshot. Concurrent attach_shared / detach_shared calls serialise on the same per-Db registry mutex.

§Errors
Source

pub fn insert<T: Document>(&self, doc: T) -> Result<Id>

Insert doc into its collection. One-shot transaction; returns the assigned Id.

The one-shot API opens, commits, and closes a private transaction per call. Reach for Db::transaction when several mutations must commit or roll back as a single atomic unit.

§Examples

One-shot CRUD against a Document-derived type:

use obj::Db;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, obj::Document)]
struct Order {
    customer_id: u64,
    total_cents: u64,
    status: String,
}

let dir = tempfile::tempdir()?;
let db = Db::open(dir.path().join("oneshot.obj"))?;

// insert returns the freshly-allocated Id.
let id = db.insert(Order {
    customer_id: 1,
    total_cents: 100,
    status: "pending".to_owned(),
})?;

// get returns Option<T>.
let _maybe: Option<Order> = db.get::<Order>(id)?;

// update applies a closure in place.
db.update::<Order, _>(id, |o| {
    o.status = "shipped".to_owned();
})?;

// upsert at a caller-supplied id (insert or replace).
let id2 = obj::Id::try_new(42)
    .ok_or(obj::Error::InvalidArgument("non-zero"))?;
db.upsert::<Order>(id2, Order {
    customer_id: 2,
    total_cents: 999,
    status: "new".to_owned(),
})?;

// delete returns true if the row existed.
let existed = db.delete::<Order>(id)?;
assert!(existed);
§Errors

As Self::transaction plus any error from crate::Collection::insert.

Source

pub fn get<T: Document>(&self, id: Id) -> Result<Option<T>>

Fetch the document at id. Returns Ok(None) if absent.

§Errors

As Self::read_transaction plus any error from crate::Collection::get.

Source

pub fn update<T, F>(&self, id: Id, f: F) -> Result<()>
where T: Document, F: FnOnce(&mut T),

Update the document at id via the closure.

§Errors
Source

pub fn delete<T: Document>(&self, id: Id) -> Result<bool>

Delete the document at id. Returns true if it existed.

§Errors

As Self::transaction.

Source

pub fn upsert<T: Document>(&self, id: Id, doc: T) -> Result<()>

Insert or replace the document at id.

§Errors

As Self::transaction.

Source

pub fn find_unique<T: Document>( &self, index_name: &str, key: impl Into<Dynamic>, ) -> Result<Option<T>>

Convenience wrapper around crate::Collection::find_uniquedb.find_unique::<Customer>("by_email", "ada@example.com"). Runs inside a one-shot read transaction.

O(log n), no collection scan; the lookup walks the named index’s B-tree directly. Defined only on Unique indexes — for the other kinds use Collection::lookup or Collection::index_range.

§Examples
use obj::Db;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, obj::Document)]
#[obj(collection = "customers_find_unique_doc")]
struct Customer {
    #[obj(index = unique)]
    email: String,
}

let dir = tempfile::tempdir()?;
let db = Db::open(dir.path().join("find-unique.obj"))?;
let _ = db.insert(Customer { email: "ada@example.com".to_owned() })?;
let by_email: Option<Customer> = db
    .find_unique::<Customer>("email", "ada@example.com")?;
assert!(by_email.is_some());
§Errors

As crate::Collection::find_unique.

Source

pub fn query<T: Document + Send + 'static>(&self) -> Query<'_, T>

Construct a fresh M8 crate::Query builder rooted at this database. The builder borrows &self for the build phase; the borrow ends when crate::Query::fetch returns.

Compose with Query::filter, Query::limit, Query::sort_by, Query::index_range. Terminate with Query::fetch (for the documents) or Query::count (for the count alone).

Mirrors design.md § Querying — see the M8 examples in crates/obj/tests/design_md_queries.rs for the full surface.

§Examples

Top-N matching documents by an indexed field, ascending:

use obj::Db;
use obj_core::codec::Dynamic;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
enum OrderStatus { Pending, Shipped }

#[derive(Debug, Clone, Serialize, Deserialize, obj::Document)]
#[obj(collection = "orders_query_doc")]
struct Order {
    #[obj(index)]
    customer_id: u64,
    status: OrderStatus,
    #[obj(index)]
    placed_at: u64,
}

let dir = tempfile::tempdir()?;
let db = Db::open(dir.path().join("queries.obj"))?;
for i in 0..20u64 {
    let _ = db.insert(Order {
        customer_id: i % 3,
        status: if i % 2 == 0 { OrderStatus::Pending } else { OrderStatus::Shipped },
        placed_at: i * 1_000,
    })?;
}

let pending: Vec<Order> = db
    .query::<Order>()
    .filter(|o| o.status == OrderStatus::Pending)
    .sort_by(|o| Dynamic::U64(o.placed_at))
    .limit(5)
    .fetch()?;
assert!(pending.len() <= 5);
assert!(pending.iter().all(|o| o.status == OrderStatus::Pending));
Source

pub fn collection<T: Document + Send + 'static>( &self, name: impl Into<String>, ) -> Collection<'_, T>

Open a read-only typed handle to the collection registered under the runtime name, instead of the type’s compile-time T::COLLECTION.

This unlocks the design.md § Portability example for attached databases: by passing a namespaced name like "archive.orders", the returned crate::Collection reads from the database attached under the "archive" namespace — see Db::attach.

Construction is infallible: errors (missing collection, unknown namespace, busy lock) surface at the first method call on the handle, not at the call to collection(name). Each read-only method on the returned handle opens a private Db::read_transaction and dispatches against the runtime-named collection’s catalog row.

§Read-only

The returned handle rejects every mutating call — crate::Collection::insert, update, delete, upsert all return Error::ReadOnly. To write into a non-default collection, override obj_core::Document::COLLECTION on the type itself (compile-time-bound) and use the regular Db::transaction / crate::WriteTxn::collection path. Phase 1B (M11 #94) intentionally limits the runtime accessor to reads — the write-through-runtime-name path requires engine plumbing that is deferred to a later milestone.

§Example
use obj::{Db, Document};
use serde::{Deserialize, Serialize};
use tempfile::tempdir;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Order { customer_id: u64, total_cents: u64 }

impl Document for Order {
    const COLLECTION: &'static str = "orders";
    const VERSION: u32 = 1;
}

fn run() -> obj::Result<()> {
    let dir = tempdir()?;
    let archive_path = dir.path().join("archive.obj");
    // Populate the archive database first.
    {
        let archive_db = Db::open(&archive_path)?;
        archive_db.insert(Order { customer_id: 1, total_cents: 999 })?;
    }
    // Attach it under a namespace and read via the runtime
    // accessor — no need to re-declare `Order` with a
    // namespaced `COLLECTION`.
    let main_path = dir.path().join("main.obj");
    let mut db = Db::open(&main_path)?;
    db.attach(&archive_path, "archive")?;
    let archived: Vec<Order> = db
        .collection::<Order>("archive.orders")
        .all()?
        .into_iter()
        .map(|(_id, doc)| doc)
        .collect();
    assert_eq!(archived.len(), 1);
    Ok(())
}
Source

pub fn all<T: Document + Send + 'static>(&self) -> Result<Vec<T>>

Convenience shim mirroring design.mdfor order in db.all::<Order>()? { ... }. Returns an owned Vec<T> (materialised). One-line shim over Db::iter_all that drives the streaming iterator to exhaustion and collects; if the collection is large enough that peak memory matters, prefer Db::iter_all directly.

Sorting requires materialisation — the comparator needs every key up front. Use Db::query + Query::sort_by for the top-N-sorted workload; the iterator side has no streaming sorted shape.

§Examples
use obj::Db;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, obj::Document)]
struct Order { total_cents: u64 }

let dir = tempfile::tempdir()?;
let db = Db::open(dir.path().join("all.obj"))?;
for i in 0..5u64 {
    let _ = db.insert(Order { total_cents: i * 10 })?;
}
let listed: Vec<Order> = db.all::<Order>()?;
assert_eq!(listed.len(), 5);
§Errors

As Db::iter_all.

Source

pub fn backup_to<P: AsRef<Path>>(&self, dest: P) -> Result<()>

Write a self-contained .obj file at dest carrying this database’s state at the LSN of an internally-taken reader snapshot.

Hot backup — writers continue uninterrupted against the source. Post-snapshot writes are NOT in the destination.

Algorithm (per docs/format.md § Hot backup):

  1. Take a ReaderSnapshot against the source pager (pins pinned_lsn).
  2. OpenOptions::create_new(true) on dest.
  3. Copy main-file pages 0..page_count to dest.
  4. Overlay every frame in the snapshot’s frozen WAL view onto dest at the frame’s page-id offset.
  5. If the snapshot carries a WAL-staged page-0 header, overlay it (so dest’s page-0 reflects the catalog root / freelist head / page count the snapshot would have observed).
  6. Patch dest’s page-0 header: zero wal_salt, recompute the header CRC32C.
  7. sync_data(SyncMode::Full) on dest.
  8. Drop the snapshot (releases the WAL pin).

On any mid-backup error the destination file is removed best-effort so a half-written backup does not linger.

§Examples
use obj::Db;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, obj::Document)]
#[obj(collection = "notes_backup_doc")]
struct Note { body: String }

let dir = tempfile::tempdir()?;
let src = dir.path().join("src.obj");
let dst = dir.path().join("backup.obj");

let db = Db::open(&src)?;
let _ = db.insert(Note { body: "before backup".to_owned() })?;
db.backup_to(&dst)?;

// The backup is itself a fully-formed obj file. Open it and read.
let backup = Db::open(&dst)?;
let listed: Vec<Note> = backup.all::<Note>()?;
assert_eq!(listed.len(), 1);
§Errors
Source

pub fn checkpoint(&self) -> Result<()>

Fold committed WAL pages into the main .obj file and reset the WAL back to its 64-byte header.

Wraps obj_core::pager::Pager::checkpoint. Acquires the pager lock the same way Self::backup_to does, then calls the pager checkpoint. After it returns, the committed records live in the main file rather than the -wal sidecar, and the WAL is truncated to header-only.

§Deferred / no-op behavior
  • If a live MVCC reader has pinned an LSN below the end of the WAL, the checkpoint is deferred (a safe no-op) so the reader’s frames are not reclaimed out from under it.
  • If there is nothing to fold (no committed WAL frames), the call is a harmless no-op.

This is the reusable engine entry point behind the Python Db.checkpoint() binding and a future checkpoint-on-close path (issue #5).

§Errors
Source

pub fn iter_all<T: Document + Send + 'static>(&self) -> Result<IterAll<'_, T>>

Streaming iterator over every (Id, T) pair in the collection. The returned IterAll holds a read transaction — and therefore a pinned reader snapshot — for its entire lifetime; the borrow on self ends when the iterator is dropped.

Each next call yields Result<(Id, T)>. Per-doc decode errors surface as Some(Err(_)) rather than ending the iteration; the caller decides whether to propagate or continue.

Peak memory does NOT scale with collection size — the iterator’s internal buffer is fixed at ITER_ALL_BATCH = 256 entries (~128 KiB at the design.md ~512 byte/doc estimate). Power-of-ten Rule 3.

Query::fetch is sort-compatible (sort requires materialisation); iter_all is NOT — there is no streaming shape for sort because the comparator needs every key up front. Use Query::sort_by + fetch for the sorted workload; use iter_all for the unsorted large-scan workload.

§Examples

Streaming a small collection and folding into a running sum. The iterator’s peak memory stays bounded regardless of how many documents the collection holds:

use obj::Db;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, obj::Document)]
struct Order { total_cents: u64 }

let dir = tempfile::tempdir()?;
let db = Db::open(dir.path().join("iter.obj"))?;
for i in 0..5u64 {
    let _ = db.insert(Order { total_cents: i * 10 })?;
}

let mut total: u64 = 0;
for step in db.iter_all::<Order>()? {
    let (_id, doc) = step?;
    total = total
        .checked_add(doc.total_cents)
        .ok_or(obj::Error::InvalidArgument("overflow"))?;
}
assert_eq!(total, 0 + 10 + 20 + 30 + 40);
§Errors
Source§

impl Db

Source

pub fn integrity_check(&self) -> Result<IntegrityReport>

Run the on-demand full integrity walk and return a structured IntegrityReport.

The walk:

  1. Opens a read snapshot (does NOT block writers).
  2. Walks the catalog B-tree and every Active collection’s primary + index B-trees, validating per-page CRCs, sort invariants, depth and sibling-chain invariants.
  3. Cross-references each Active index against its primary: every index entry must point at an extant primary id, and every primary id must be referenced by at least one entry in each non-Each Active index.
  4. Sweeps the freelist chain.
  5. Compares the set of reachable pages to 0..page_count, emitting IntegrityFailure::OrphanPage for each unreferenced page id.

I/O failures during the walk surface as Err(_); content- level violations are accumulated into report.failures and the walk continues.

A lighter-weight catalog-only walk runs automatically at Db::open time; opt out via Config::skip_open_check.

§Examples
use obj::Db;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, obj::Document)]
#[obj(collection = "users_integrity_doc")]
struct User { email: String }

let dir = tempfile::tempdir()?;
let db = Db::open(dir.path().join("check.obj"))?;
for i in 0..16u32 {
    let _ = db.insert(User { email: format!("u{i}@example.com") })?;
}
let report = db.integrity_check()?;
assert!(report.is_ok(), "clean db must pass: {:?}", report.failures);
assert!(report.pages_checked > 0);
§Errors
  • Error::Io on cache-miss read failure during the walk.
  • Error::Busy if the pager mutex is poisoned.
  • Pager / B-tree errors propagated from the catalog walk.

Trait Implementations§

Source§

impl Debug for Db

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Db

§

impl RefUnwindSafe for Db

§

impl Send for Db

§

impl Sync for Db

§

impl Unpin for Db

§

impl UnsafeUnpin for Db

§

impl UnwindSafe for Db

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more