Skip to main content

Db

Struct Db 

Source
pub struct Db { /* private fields */ }
Available on crate feature std only.
Expand description

An embedded document store backed by a single append-only file.

Open one with Db::open, then insert, get, update, and delete documents by id. Call flush to make recent writes durable, and compact to reclaim space left by overwrites and deletes.

§Concurrency

Db follows a single-writer, multi-reader model, like an embedded SQL engine: reads (get, find, range) take &self, while writes take &mut self. The compiler therefore enforces that writes are exclusive. Db is Send and Sync, so the idiomatic way to share one across threads is an Arc<RwLock<Db>>: many threads can read concurrently, and a writer takes the lock exclusively. The single-writer design is inherent to a single append-only file — there is one tail — and is the right fit for an embedded store.

§Examples

use bison_db::{Db, Document};

let dir = std::env::temp_dir().join("bison_db_doc_example");
let _ = std::fs::remove_file(&dir);
let mut db = Db::open(&dir)?;

let mut user = Document::new();
user.set("name", "grace").set("born", 1906_i64);
let id = db.insert(user)?;

let fetched = db.get(id)?.expect("just inserted");
assert_eq!(fetched.get("name").and_then(|v| v.as_str()), Some("grace"));

db.flush()?;

Implementations§

Source§

impl Db

Source

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

Opens the store at path, creating an empty one if the file does not exist, and replaying any existing records to rebuild the index.

On open the whole log is scanned: each record’s checksum is verified and the in-memory index is reconstructed from the surviving inserts and deletes. A record left half-written by a crash — detectable because it runs past the end of the file or fails its checksum at the tail — is truncated away, restoring the file to its last consistent state. A checksum failure on a record that is not at the tail is reported as Error::Corrupt, because that indicates in-place damage rather than a torn write.

Uses SyncPolicy::Manual; for a different policy, open through DbOptions.

§Errors

Returns Error::Io if the file cannot be opened or read, Error::BadMagic if an existing file is not a bison-db store, Error::UnsupportedVersion if it was written by a newer format, and Error::Corrupt if a non-tail record fails verification.

§Examples
let path = std::env::temp_dir().join("bison_db_open_example.bison");
let _ = std::fs::remove_file(&path);
let db = bison_db::Db::open(&path)?;
assert!(db.is_empty());
Source

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

Opens (or creates) the store at path with the given DbOptions.

A shorthand for DbOptions::open; see Db::open for the open and recovery contract.

§Errors

Same as Db::open.

§Examples
use bison_db::{Db, DbOptions, SyncPolicy};
let db = Db::open_with(&path, DbOptions::new().sync(SyncPolicy::Always))?;
assert_eq!(db.sync_policy(), SyncPolicy::Always);
Source

pub fn sync_policy(&self) -> SyncPolicy

Returns the store’s SyncPolicy.

§Examples
use bison_db::{Db, SyncPolicy};
let db = Db::open(&path)?;
assert_eq!(db.sync_policy(), SyncPolicy::Manual);
Source

pub fn insert(&mut self, doc: Document) -> Result<DocId>

Inserts doc, assigning and returning a fresh DocId.

The document is appended to the log and indexed; it is readable immediately and durable after the next flush.

§Errors

Returns Error::ValueTooLarge if the encoded document exceeds MAX_RECORD_BYTES, or Error::Io if the append fails.

§Examples
use bison_db::{Db, Document};
let mut db = Db::open(&path)?;
let mut doc = Document::new();
doc.set("k", "v");
let id = db.insert(doc)?;
assert!(db.contains(id));
Source

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

Reads the document stored under id, or None if no live document has that id.

§Errors

Returns Error::Io if the body cannot be read, or Error::Corrupt if the stored bytes fail to decode (which a passing checksum makes unexpected in practice).

§Examples
use bison_db::{Db, Document, DocId};
let mut db = Db::open(&path)?;
let id = db.insert({ let mut d = Document::new(); d.set("n", 1_i64); d })?;
assert!(db.get(id)?.is_some());
assert!(db.get(DocId::from(9999))?.is_none());
Source

pub fn update(&mut self, id: DocId, doc: Document) -> Result<bool>

Overwrites the document stored under id with doc, returning true if a document was present to overwrite and false otherwise.

A successful update appends a new record and repoints the index; the previous body remains in the file as dead space until compaction.

§Errors

Returns Error::ValueTooLarge or Error::Io under the same conditions as insert.

§Examples
use bison_db::{Db, Document, DocId};
let mut db = Db::open(&path)?;
let id = db.insert({ let mut d = Document::new(); d.set("v", 1_i64); d })?;

let mut next = Document::new();
next.set("v", 2_i64);
assert!(db.update(id, next)?);
assert!(!db.update(DocId::from(404), Document::new())?);
Source

pub fn delete(&mut self, id: DocId) -> Result<bool>

Deletes the document stored under id, returning true if one was present and false otherwise.

A tombstone is appended so the deletion survives reopening; the document is unreadable as soon as this returns.

§Errors

Returns Error::Io if the tombstone cannot be appended.

§Examples
use bison_db::{Db, Document};
let mut db = Db::open(&path)?;
let id = db.insert({ let mut d = Document::new(); d.set("x", 1_i64); d })?;
assert!(db.delete(id)?);
assert!(db.get(id)?.is_none());
assert!(!db.delete(id)?);
Source

pub fn contains(&self, id: DocId) -> bool

Returns true if a live document has this id.

This is an in-memory index lookup with no file access.

§Examples
use bison_db::{Db, Document};
let mut db = Db::open(&path)?;
let id = db.insert(Document::new())?;
assert!(db.contains(id));
Source

pub fn len(&self) -> usize

Returns the number of live documents.

§Examples
use bison_db::{Db, Document};
let mut db = Db::open(&path)?;
db.insert(Document::new())?;
assert_eq!(db.len(), 1);
Source

pub fn is_empty(&self) -> bool

Returns true if the store holds no live documents.

§Examples
let db = bison_db::Db::open(&path)?;
assert!(db.is_empty());
Source

pub fn ids(&self) -> impl Iterator<Item = DocId> + '_

Returns an iterator over the ids of all live documents.

The order is unspecified and may change between runs; collect and sort if you need a stable order.

§Examples
use bison_db::{Db, Document};
let mut db = Db::open(&path)?;
db.insert(Document::new())?;
db.insert(Document::new())?;
assert_eq!(db.ids().count(), 2);
Source

pub fn flush(&mut self) -> Result<()>

Flushes buffered writes and fsyncs the file, making every preceding write durable against power loss.

§Errors

Returns Error::Io if the sync fails.

§Examples
use bison_db::{Db, Document};
let mut db = Db::open(&path)?;
db.insert(Document::new())?;
db.flush()?;
Source

pub fn path(&self) -> &Path

Returns the path the store was opened from.

§Examples
let db = bison_db::Db::open(&path)?;
assert_eq!(db.path(), path.as_path());
Source

pub fn stats(&self) -> Stats

Returns a Stats snapshot of the store’s size and live contents.

§Examples
use bison_db::{Db, Document};
let mut db = Db::open(&path)?;
db.insert(Document::new())?;
assert_eq!(db.stats().live_documents, 1);
Source

pub fn compact(&mut self) -> Result<()>

Rewrites the file to contain only live documents, reclaiming the space held by superseded and deleted records.

Every overwrite and delete leaves dead bytes behind in the append-only log; over time the file grows past the size of its live data. Compaction writes a fresh copy containing one current record per live document, then atomically swaps it in. Document ids are preserved, so existing DocIds and secondary indexes remain valid; only the on-disk layout changes.

The compacted copy is built in a sibling temporary file and put in place with an atomic rename, so a crash at any point leaves either the original file or the fully compacted one — never a partial result. A leftover temporary from an interrupted compaction is cleaned up on the next open.

Compaction is durable on return regardless of SyncPolicy: the new file is fsynced before the swap.

§Errors

Returns Error::Io if the temporary file cannot be written or swapped in, or Error::Corrupt if a live record cannot be read back.

§Examples
use bison_db::{Db, Document};
let mut db = Db::open(&path)?;
let id = db.insert({ let mut d = Document::new(); d.set("v", 1_i64); d })?;
for n in 2..1000 {
    db.update(id, { let mut d = Document::new(); d.set("v", n); d })?;
}
let before = db.stats().file_bytes;

db.compact()?; // collapse 999 versions down to one live record

assert!(db.stats().file_bytes < before);
assert_eq!(db.get(id)?.unwrap().get("v").and_then(|v| v.as_int()), Some(999));
Source

pub fn create_index(&mut self, field: &str) -> Result<()>

Builds a secondary index over field, making find and range on that field fast point and range lookups instead of full scans.

The index is built by reading every live document once and recording its value for field; documents without the field are skipped. From then on, it is maintained automatically on every insert, update, and delete. Any number of fields may be indexed — call this once per field.

Indexes live in memory only and are not persisted: after reopening a store, call this again for each field you want indexed. Calling it for a field that is already indexed is a no-op.

§Errors

Returns Error::Io or Error::Corrupt if a document cannot be read while building the index.

§Examples
use bison_db::{Db, Document, Value};
let mut db = Db::open(&path)?;
db.insert({ let mut d = Document::new(); d.set("city", "Oslo"); d })?;

db.create_index("city")?;
let hits = db.find("city", &Value::from("Oslo"))?;
assert_eq!(hits.len(), 1);
Source

pub fn drop_index(&mut self, field: &str) -> bool

Drops the secondary index over field, returning true if one existed.

§Examples
let mut db = bison_db::Db::open(&path)?;
db.create_index("name")?;
assert!(db.drop_index("name"));
assert!(!db.drop_index("name"));
Source

pub fn indexes(&self) -> impl Iterator<Item = &str>

Returns an iterator over the names of the currently indexed fields.

The order is unspecified.

§Examples
let mut db = bison_db::Db::open(&path)?;
db.create_index("a")?;
db.create_index("b")?;
assert_eq!(db.indexes().count(), 2);
Source

pub fn find(&self, field: &str, value: &Value) -> Result<Vec<DocId>>

Returns the ids of all live documents whose field equals value.

If field is indexed (see create_index) this is a point lookup; otherwise it falls back to scanning every live document, so the result is correct either way — the index only changes the speed. Equality follows the same total order the indexes use, so a Float field distinguishes 0.0 from -0.0.

§Errors

Returns Error::Io or Error::Corrupt if a document must be read (the unindexed path) and cannot be.

§Examples
use bison_db::{Db, Document, Value};
let mut db = Db::open(&path)?;
db.insert({ let mut d = Document::new(); d.set("role", "admin"); d })?;
db.insert({ let mut d = Document::new(); d.set("role", "user"); d })?;
db.create_index("role")?;

assert_eq!(db.find("role", &Value::from("admin"))?.len(), 1);
assert!(db.find("role", &Value::from("ghost"))?.is_empty());
Source

pub fn range<R: RangeBounds<Value>>( &self, field: &str, range: R, ) -> Result<Vec<DocId>>

Returns the ids of all live documents whose field falls within range.

Bounds are Values compared with the same total order the indexes use; any RangeBounds form works (a..b, a..=b, ..b, a.., ..). If field is indexed the matches come back ordered by field value (then id); otherwise the store scans every live document. As with find, the index changes only the speed, not the result.

§Errors

Returns Error::Io or Error::Corrupt if a document must be read (the unindexed path) and cannot be.

§Examples
use bison_db::{Db, Document, Value};
let mut db = Db::open(&path)?;
for age in [17_i64, 25, 40, 70] {
    db.insert({ let mut d = Document::new(); d.set("age", age); d })?;
}
db.create_index("age")?;

// Working-age adults: 18..=65.
let hits = db.range("age", Value::from(18_i64)..=Value::from(65_i64))?;
assert_eq!(hits.len(), 2); // 25 and 40

Trait Implementations§

Source§

impl Debug for Db

Source§

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

Formats the value using the given formatter. Read more
Source§

impl Drop for Db

Source§

fn drop(&mut self)

Makes a best-effort fsync on a clean shutdown under SyncPolicy::Manual, so a normal program exit does not lose writes that were never explicitly flushed. Under SyncPolicy::Always every write is already durable, so nothing is done. Any error here is ignored because a destructor cannot return one; call Db::flush before dropping when you need to observe a sync failure.

Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. 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, 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, 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.