Skip to content

Commit

Permalink
Merge pull request mozilla#42 from mykmelez/document-rkv
Browse files Browse the repository at this point in the history
document project and API
  • Loading branch information
mykmelez authored Jun 22, 2018
2 parents 6b3afc1 + f74b3aa commit 12e5a10
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 13 deletions.
51 changes: 38 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,49 @@
[![Travis CI Build Status](https://travis-ci.org/mozilla/rkv.svg?branch=master)](https://travis-ci.org/mozilla/rkv)
[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/lk936u5y5bi6qafb/branch/master?svg=true)](https://ci.appveyor.com/project/mykmelez/rkv/branch/master)
[![Documentation](https://docs.rs/rkv/badge.svg)](https://docs.rs/rkv/)
[![Crate](https://img.shields.io/crates/v/rkv.svg)](https://crates.io/crates/rkv)

# rkv

<a href="https://crates.io/crates/rkv">
<img src="https://img.shields.io/crates/v/rkv.svg">
</a>
The [rkv Rust crate](https://crates.io/crates/rkv) is a simple, humane, typed Rust interface to [LMDB](http://www.lmdb.tech/doc/).

## Use

Comprehensive information about using rkv is available in its [online documentation](https://docs.rs/rkv/), which you can also generate for local consumption:

rkv is a usable Rust wrapper around LMDB.
```sh
cargo doc --open
```

It aims to achieve the following:
## Build

- Avoid LMDB's sharp edges (e.g., obscure error codes for common situations).
- Report errors via `failure`.
- Correctly restrict 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.
Build this project as you would build other Rust crates:

## Feature choices
```sh
cargo build
```

If you specify the `backtrace` feature, backtraces will be enabled in `failure`
errors. This feature is disabled by default.

## Contributing
## Test

Test this project as you would test other Rust crates:

```sh
cargo test --tests
```

The project includes unit tests embedded in the `src/` files, integration tests in the `tests/` subdirectory, and usage examples in the `examples/` subdirectory. To ensure your changes don't break examples, specify `--tests` when running tests to run both unit/integration tests and example binaries.

## Contribute

Of the various open source archetypes described in [A Framework for Purposeful Open Source](https://medium.com/mozilla-open-innovation/whats-your-open-source-strategy-here-are-10-answers-383221b3f9d3), the rkv project most closely resembles the Specialty Library, and we welcome contributions. Please report problems or ask questions using this repo's GitHub [issue tracker](https://github.com/mozilla/rkv/issues) and submit [pull requests](https://github.com/mozilla/rkv/pulls) for code and documentation changes.

rkv relies on the latest [rustfmt](https://github.com/rust-lang-nursery/rustfmt) for code formatting, so please make sure your pull request passes the rustfmt before submitting it for review. See rustfmt's [quick start](https://github.com/rust-lang-nursery/rustfmt#quick-start) for installation details.

We follow Mozilla's [Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/) while contributing to this project.

## License

rkv relies on the latest [rustfmt](https://github.com/rust-lang-nursery/rustfmt) for code formatting, please make sure your pull request passes the rustfmt before submitting it for review. See rustfmt's [quick start](https://github.com/rust-lang-nursery/rustfmt#quick-start) for installation details.
The rkv source code is licensed under the Apache License, Version 2.0, as described in the [LICENSE](https://github.com/mozilla/rkv/blob/master/LICENSE) file.
21 changes: 21 additions & 0 deletions examples/simple-store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ fn main() {
println!("Get string {:?}", store.get(r, "string").unwrap());
println!("Get json {:?}", store.get(r, "json").unwrap());
println!("Get blob {:?}", store.get(r, "blob").unwrap());
println!("Get non-existent {:?}", store.get(r, "non-existent").unwrap());
}

println!("Looking up keys via Reader.get()...");
{
// An alternate way to query the store.
let r = store.read(&k).expect("reader");
println!("Get int {:?}", r.get("int").unwrap());
println!("Get uint {:?}", r.get("uint").unwrap());
println!("Get float {:?}", r.get("float").unwrap());
println!("Get instant {:?}", r.get("instant").unwrap());
println!("Get boolean {:?}", r.get("boolean").unwrap());
println!("Get string {:?}", r.get("string").unwrap());
println!("Get json {:?}", r.get("json").unwrap());
println!("Get blob {:?}", r.get("blob").unwrap());
println!("Get non-existent {:?}", r.get("non-existent").unwrap());
}

println!("Aborting transaction...");
Expand All @@ -84,5 +100,10 @@ fn main() {
// Write transaction also supports read
println!("It should be None! ({:?})", writer.get("foo").unwrap());
writer.commit().unwrap();

// Committing a transaction consumes the writer, preventing you
// from reusing it by failing and reporting a compile-time error.
// This line would report error[E0382]: use of moved value: `writer`.
// writer.put("baz", &Value::Str("buz")).unwrap();
}
}
157 changes: 157 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,163 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

//! a simple, humane, typed Rust interface to [LMDB](http://www.lmdb.tech/doc/)
//!
//! It aims to achieve the following:
//!
//! - Avoid LMDB's sharp edges (e.g., obscure error codes for common situations).
//! - Report errors via [failure](../failure/index.html).
//! - Correctly restrict access to one handle per process via a [Manager](struct.Manager.html).
//! - 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](../bincode/index.html)/[serde](../serde/index.html)
//! and type tags, achieving platform-independent storage and input/output flexibility.
//!
//! It exposes these primary abstractions:
//!
//! - [Manager](struct.Manager.html): a singleton that controls access to LMDB environments
//! - [Rkv](struct.Rkv.html): an LMDB environment, which contains a set of key/value databases
//! - [Store](struct.Store.html): 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](struct.IntegerStore.html)).
//! Values can be any of the types defined by the [Value](value/enum.Value.html) enum, including:
//!
//! - booleans (`Value::Bool`)
//! - integers (`Value::I64`, `Value::U64`)
//! - floats (`Value::F64`)
//! - strings (`Value::Str`)
//! - blobs (`Value::Blob`)
//!
//! See [Value](value/enum.Value.html) 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();
//! }
//! ```
#![allow(dead_code)]

#[macro_use]
Expand Down

0 comments on commit 12e5a10

Please sign in to comment.