[][src]Crate rkv

A simple, humane, typed key-value storage solution. It supports multiple backend engines with varying guarantees, such as LMDB for performance, or "SafeMode" for reliability.

It aims to achieve the following:

  • Avoid 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 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 environments
  • Rkv: an environment contains a set of key/value databases
  • SingleStore: a database 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

use rkv::{Manager, Rkv, SingleStore, Value, StoreOptions};
use rkv::backend::{Lmdb, LmdbEnvironment};
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. Use it to retrieve the handle
// to an opened environment—or create one if it hasn't already been opened:
let mut manager = Manager::<LmdbEnvironment>::singleton().write().unwrap();
let created_arc = manager.get_or_create(path, Rkv::new::<Lmdb>).unwrap();
let env = created_arc.read().unwrap();

// Then you can use the environment handle to get a handle to a datastore:
let store = env.open_single("mydb", StoreOptions::create()).unwrap();

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

    // Keys are `AsRef<[u8]>`, while values are `Value` enum instances. Use the `Blob`
    // variant to store arbitrary collections of bytes. Putting data returns a
    // `Result<(), StoreError>`, where StoreError is an enum identifying the reason
    // for a failure.
    store.put(&mut writer, "int", &Value::I64(1234)).unwrap();
    store.put(&mut writer, "uint", &Value::U64(1234_u64)).unwrap();
    store.put(&mut writer, "float", &Value::F64(1234.0.into())).unwrap();
    store.put(&mut writer, "instant", &Value::Instant(1528318073700)).unwrap();
    store.put(&mut writer, "boolean", &Value::Bool(true)).unwrap();
    store.put(&mut writer, "string", &Value::Str("Héllo, wörld!")).unwrap();
    store.put(&mut writer, "json", &Value::Json(r#"{"foo":"bar", "number": 1}"#)).unwrap();
    store.put(&mut writer, "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 via a `Reader`. There can be multiple
    // concurrent readers for a store, and readers never block on a writer nor other
    // readers.
    let reader = env.read().expect("reader");

    // Keys are `AsRef<u8>`, and the return value is `Result<Option<Value>, StoreError>`.
    println!("Get int {:?}", store.get(&reader, "int").unwrap());
    println!("Get uint {:?}", store.get(&reader, "uint").unwrap());
    println!("Get float {:?}", store.get(&reader, "float").unwrap());
    println!("Get instant {:?}", store.get(&reader, "instant").unwrap());
    println!("Get boolean {:?}", store.get(&reader, "boolean").unwrap());
    println!("Get string {:?}", store.get(&reader, "string").unwrap());
    println!("Get json {:?}", store.get(&reader, "json").unwrap());
    println!("Get blob {:?}", store.get(&reader, "blob").unwrap());

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

    // 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 = env.write().unwrap();
    store.put(&mut writer, "foo", &Value::Str("bar")).unwrap();
    writer.abort();
    let reader = env.read().expect("reader");
    println!("It should be None! ({:?})", store.get(&reader, "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 = env.write().unwrap();
        store.put(&mut writer, "foo", &Value::Str("bar")).unwrap();
    }
    let reader = env.read().expect("reader");
    println!("It should be None! ({:?})", store.get(&reader, "foo").unwrap());
}

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

    // A write transaction also supports reading, and the version of the store that it
    // reads includes the changes it has made regardless of the commit state of that
    // transaction.
    // In the code above, "foo" and "bar" were put into the store, then "foo" was
    // deleted so only "bar" will return a result when the database is queried via the
    // writer.
    println!("It should be None! ({:?})", store.get(&writer, "foo").unwrap());
    println!("Get bar ({:?})", store.get(&writer, "bar").unwrap());

    // But a reader won't see that change until the write transaction is committed.
    {
        let reader = env.read().expect("reader");
        println!("Get foo {:?}", store.get(&reader, "foo").unwrap());
        println!("Get bar {:?}", store.get(&reader, "bar").unwrap());
    }
    writer.commit().unwrap();
    {
        let reader = env.read().expect("reader");
        println!("It should be None! ({:?})", store.get(&reader, "foo").unwrap());
        println!("Get bar {:?}", store.get(&reader, "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]:
    // borrow of moved value: `writer`".
    // store.put(&mut writer, "baz", &Value::Str("buz")).unwrap();
}

{
    // Clearing all the entries in the store with a write transaction.
    {
        let mut writer = env.write().unwrap();
        store.put(&mut writer, "foo", &Value::Str("bar")).unwrap();
        store.put(&mut writer, "bar", &Value::Str("baz")).unwrap();
        writer.commit().unwrap();
    }

    {
        let mut writer = env.write().unwrap();
        store.clear(&mut writer).unwrap();
        writer.commit().unwrap();
    }

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

}

Re-exports

pub use migrator::Migrator;
pub use store::single::SingleStore;
pub use store::Options as StoreOptions;
pub use value::OwnedValue;
pub use value::Value;
pub use store::multi::MultiStore;
pub use store::integer::IntegerStore;
pub use store::integermulti::MultiIntegerStore;

Modules

backend
migrator

A simple utility for migrating data from one RVK environment to another. Notably, this tool can migrate data from an enviroment created with a different backend than the current RKV consumer (e.g from Lmdb to SafeMode).

store
value

Structs

Manager

A process is only permitted to have one open handle to each Rkv environment. This manager exists to enforce that constraint: don't open environments directly.

Reader
Rkv

Wrapper around an Environment (e.g. such as an LMDB or SafeMode environment).

Writer

Enums

DataError
DatabaseFlags
EnvironmentFlags
MigrateError
StoreError
WriteFlags

Traits

EncodableKey
PrimitiveInt
Readable