Crate rkv[][src]

a simple, humane, typed Rust interface to LMDB

It aims to achieve the following:

  • Avoid LMDB's sharp edges (e.g., obscure error codes for common situations).
  • Report errors via failure.
  • Correctly restrict access to one handle per process via a Manager.
  • Use Rust's type system to make single-typed key stores (including LMDB's own integer-keyed stores) safe and ergonomic.
  • Encode and decode values via bincode/serde and type tags, achieving platform-independent storage and input/output flexibility.

It exposes these primary abstractions:

  • Manager: a singleton that controls access to LMDB environments
  • Rkv: an LMDB environment, which contains a set of key/value databases
  • Store: an LMDB database, which contains a set of key/value pairs

Keys can be anything that implements AsRef<[u8]> or integers (when accessing an IntegerStore). Values can be any of the types defined by the Value enum, including:

  • booleans (Value::Bool)
  • integers (Value::I64, Value::U64)
  • floats (Value::F64)
  • strings (Value::Str)
  • blobs (Value::Blob)

See Value for the complete list of supported types.

Basic Usage

extern crate rkv;
extern crate tempfile;

use rkv::{Manager, Rkv, Store, Value};
use std::fs;
use tempfile::Builder;

// First determine the path to the environment, which is represented
// on disk as a directory containing two files:
//
//   * a data file containing the key/value stores
//   * a lock file containing metadata about current transactions
//
// In this example, we use the `tempfile` crate to create the directory.
//
let root = Builder::new().prefix("simple-db").tempdir().unwrap();
fs::create_dir_all(root.path()).unwrap();
let path = root.path();

// The Manager enforces that each process opens the same environment
// at most once by caching a handle to each environment that it opens.
// Retrieve the handle to an opened environment—or create one if it hasn't
// already been opened—by calling `Manager.get_or_create()`, passing it
// an `Rkv` method that opens an environment (`Rkv::new` in this case):
let created_arc = Manager::singleton().write().unwrap().get_or_create(path, Rkv::new).unwrap();
let env = created_arc.read().unwrap();

// Call `Rkv.create_or_open_default()` to get a handle to the default
// (unnamed) store for the environment.
let store: Store<&str> = env.create_or_open_default().unwrap();

{
    // Use a write transaction to mutate the store by calling
    // `Store.write()` to create a `Writer`.  There can be only one
    // writer for a given store; opening a second one will block
    // until the first completes.
    let mut writer = store.write(&env).unwrap();

    // Keys are `AsRef<[u8]>`, while values are `Value` enum instances.
    // Use the `Blob` variant to store arbitrary collections of bytes.
    writer.put("int", &Value::I64(1234)).unwrap();
    writer.put("uint", &Value::U64(1234_u64)).unwrap();
    writer.put("float", &Value::F64(1234.0.into())).unwrap();
    writer.put("instant", &Value::Instant(1528318073700)).unwrap();
    writer.put("boolean", &Value::Bool(true)).unwrap();
    writer.put("string", &Value::Str("héllo, yöu")).unwrap();
    writer.put("json", &Value::Json(r#"{"foo":"bar", "number": 1}"#)).unwrap();
    writer.put("blob", &Value::Blob(b"blob")).unwrap();

    // You must commit a write transaction before the writer goes out
    // of scope, or the transaction will abort and the data won't persist.
    writer.commit().unwrap();
}

{
    // Use a read transaction to query the store by calling `Store.read()`
    // to create a `Reader`.  There can be unlimited concurrent readers
    // for a store, and readers never block on a writer nor other readers.
    let reader = store.read(&env).expect("reader");

    // To retrieve data, call `Reader.get()`, passing it the key
    // for the value to retrieve.
    println!("Get int {:?}", reader.get("int").unwrap());
    println!("Get uint {:?}", reader.get("uint").unwrap());
    println!("Get float {:?}", reader.get("float").unwrap());
    println!("Get instant {:?}", reader.get("instant").unwrap());
    println!("Get boolean {:?}", reader.get("boolean").unwrap());
    println!("Get string {:?}", reader.get("string").unwrap());
    println!("Get json {:?}", reader.get("json").unwrap());
    println!("Get blob {:?}", reader.get("blob").unwrap());

    // Retrieving a non-existent value returns `Ok(None)`.
    println!("Get non-existent value {:?}", reader.get("non-existent"));

    // A read transaction will automatically close once the reader
    // goes out of scope, so isn't necessary to close it explicitly,
    // although you can do so by calling `Reader.abort()`.
}

{
    // Aborting a write transaction rolls back the change(s).
    let mut writer = store.write(&env).unwrap();
    writer.put("foo", &Value::Str("bar")).unwrap();
    writer.abort();

    let reader = store.read(&env).expect("reader");
    println!("It should be None! ({:?})", reader.get("foo").unwrap());
}

{
    // Explicitly aborting a transaction is not required unless an early
    // abort is desired, since both read and write transactions will
    // implicitly be aborted once they go out of scope.
    {
        let mut writer = store.write(&env).unwrap();
        writer.put("foo", &Value::Str("bar")).unwrap();
    }
    let reader = store.read(&env).expect("reader");
    println!("It should be None! ({:?})", reader.get("foo").unwrap());
}

{
    // Deleting a key/value pair also requires a write transaction.
    let mut writer = store.write(&env).unwrap();
    writer.put("foo", &Value::Str("bar")).unwrap();
    writer.put("bar", &Value::Str("baz")).unwrap();
    writer.delete("foo").unwrap();

    // A write transaction also supports reading, but the version
    // of the store that it reads doesn't include changes it has made.
    // In the code above, "foo" and "bar" were put into the store,
    // then "foo" was deleted; but neither key is visible to readers,
    // not even to the writer itself, until the transaction is committed.
    println!("It should be None! ({:?})", writer.get("foo").unwrap());
    println!("It should be None! ({:?})", writer.get("bar").unwrap());
    writer.commit().unwrap();
    let reader = store.read(&env).expect("reader");
    println!("It should be None! ({:?})", reader.get("foo").unwrap());
    println!("Get bar {:?}", reader.get("bar").unwrap());

    // Committing a transaction consumes the writer, preventing you
    // from reusing it by failing at compile time with an error.
    // This line would report error[E0382]: use of moved value: `writer`.
    // writer.put("baz", &Value::Str("buz")).unwrap();
}

Re-exports

pub use error::DataError;
pub use error::StoreError;
pub use value::Value;

Modules

error
value

Structs

DatabaseFlags

Database Options

EnvironmentBuilder

Options for opening or creating an environment.

EnvironmentFlags

Environment Options

IntegerStore
Manager
Reader
Rkv

Wrapper around an lmdb::Environment.

Store

Wrapper around an lmdb::Database.

WriteFlags

Write Options

Writer

Traits

PrimitiveInt