Update crate documentation, more consistent formatting

Signed-off-by: Victor Porof <victor.porof@gmail.com>
without.crypto
Victor Porof 4 years ago
parent d33e79fcd1
commit 9fe69b11ce
  1. 7
      .rustfmt.toml
  2. 31
      Cargo.toml
  3. 9
      README.md
  4. 16
      examples/iterator.rs
  5. 14
      examples/simple-store.rs
  6. 44
      src/backend.rs
  7. 6
      src/backend/impl_lmdb/environment.rs
  8. 6
      src/backend/impl_lmdb/error.rs
  9. 22
      src/backend/impl_lmdb/flags.rs
  10. 4
      src/backend/impl_lmdb/transaction.rs
  11. 6
      src/backend/impl_safe/database.rs
  12. 32
      src/backend/impl_safe/environment.rs
  13. 12
      src/backend/impl_safe/error.rs
  14. 22
      src/backend/impl_safe/flags.rs
  15. 10
      src/backend/impl_safe/snapshot.rs
  16. 10
      src/backend/impl_safe/transaction.rs
  17. 22
      src/backend/traits.rs
  18. 14
      src/bin/dump.rs
  19. 20
      src/bin/rand.rs
  20. 146
      src/env.rs
  21. 14
      src/error.rs
  22. 16
      src/helpers.rs
  23. 99
      src/lib.rs
  24. 69
      src/manager.rs
  25. 251
      src/migrate.rs
  26. 22
      src/readwrite.rs
  27. 46
      src/store/integer.rs
  28. 56
      src/store/integermulti.rs
  29. 39
      src/store/multi.rs
  30. 36
      src/store/single.rs
  31. 23
      src/value.rs
  32. 12
      tests/env-all.rs
  33. 32
      tests/env-lmdb.rs
  34. 28
      tests/env-safe.rs
  35. 2
      tests/integer-store.rs
  36. 20
      tests/manager.rs
  37. 2
      tests/multi-integer-store.rs
  38. 28
      tests/test_txn.rs

@ -1,4 +1,9 @@
comment_width = 90
force_multiline_blocks = true
imports_layout = "Vertical"
max_width = 120
match_block_trailing_comma = true
max_width = 120
merge_imports = true
reorder_impl_items = true
use_small_heuristics = "Off"
wrap_comments = true

@ -1,23 +1,28 @@
[package]
name = "rkv"
version = "0.11.1"
authors = ["Richard Newman <rnewman@twinql.com>", "Nan Jiang <najiang@mozilla.com>", "Myk Melez <myk@mykzilla.org>", "Victor Porof <vporof@mozilla.com>"]
edition = "2018"
license = "Apache-2.0"
description = "a simple, humane, typed Rust interface to LMDB"
authors = [
"Richard Newman <rnewman@twinql.com>",
"Nan Jiang <najiang@mozilla.com>",
"Myk Melez <myk@mykzilla.org>",
"Victor Porof <vporof@mozilla.com>",
]
categories = ["database"]
description = "A simple, humane, typed key-value storage solution"
documentation = "https://docs.rs/rkv"
edition = "2018"
exclude = ["/tests/envs/*"]
homepage = "https://github.com/mozilla/rkv"
repository = "https://github.com/mozilla/rkv"
readme = "README.md"
keywords = ["lmdb", "database", "storage"]
categories = ["database"]
exclude = ["/tests/envs/*"]
license = "Apache-2.0"
name = "rkv"
readme = "README.md"
repository = "https://github.com/mozilla/rkv"
version = "0.11.1"
[features]
default = ["db-dup-sort", "db-int-key"]
backtrace = ["failure/backtrace", "failure/std"]
db-dup-sort = []
db-int-key = []
default = ["db-dup-sort", "db-int-key"]
with-asan = ["lmdb-rkv/with-asan"]
with-fuzzer = ["lmdb-rkv/with-fuzzer"]
with-fuzzer-no-link = ["lmdb-rkv/with-fuzzer-no-link"]
@ -32,7 +37,7 @@ lazy_static = "1.0"
lmdb-rkv = "0.14"
log = "0.4"
ordered-float = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }
serde = {version = "1.0", features = ["derive", "rc"]}
serde_derive = "1.0"
url = "2.0"
uuid = "0.8"
@ -40,9 +45,9 @@ uuid = "0.8"
# Get rid of failure's dependency on backtrace. Eventually
# backtrace will move into Rust core, but we don't need it here.
[dependencies.failure]
version = "0.1"
default_features = false
features = ["derive"]
version = "0.1"
[dev-dependencies]
byteorder = "1"

@ -9,8 +9,6 @@ The [rkv Rust crate](https://crates.io/crates/rkv) is a simple, humane, typed ke
## ⚠ Warning ⚠
The LMDB backend is currently unstable and crash-prone. We're attempting to fix these crashes in bugs [1538539](https://bugzilla.mozilla.org/show_bug.cgi?id=1538539), [1538541](https://bugzilla.mozilla.org/show_bug.cgi?id=1538541) and [1550174](https://bugzilla.mozilla.org/show_bug.cgi?id=1550174).
To use rkv in production/release environments at Mozilla, you may do so with the "SafeMode" backend, for example:
```rust
@ -23,9 +21,9 @@ let shared_rkv = manager.get_or_create(path, Rkv::new::<SafeMode>).unwrap();
...
```
The "SafeMode` backend performs well, with two caveats: the entire database is stored in memory, and write transactions are synchronously written to disk on commit.
The "SafeMode" backend performs well, with two caveats: the entire database is stored in memory, and write transactions are synchronously written to disk (only on commit).
In the future, it will be advisable to switch to a different backend with better performance guarantees. We're working on either fixing the LMDB crashes, or offering more choices of backend engines (e.g. SQLite).
In the future, it will be advisable to switch to a different backend with better performance guarantees. We're working on either fixing some LMDB crashes, or offering more choices of backend engines (e.g. SQLite).
## Use
@ -49,8 +47,7 @@ There are several features that you can opt-in and out of when using rkv:
By default, `db-dup-sort` and `db-int-key` features offer high level database APIs which allow multiple values per key, and optimizations around integer-based keys respectively. Opt out of these default features when specifying the rkv dependency in your Cargo.toml file to disable them; doing so avoids a certain amount of overhead required to support them.
If you specify the `backtrace` feature, backtraces will be enabled in "failure"
errors. This feature is disabled by default.
If you specify the `backtrace` feature, backtraces will be enabled in "failure" errors. This feature is disabled by default.
To aid fuzzing efforts, `with-asan`, `with-fuzzer`, and `with-fuzzer-no-link` configure the build scripts responsible with compiling the underlying backing engines (e.g. LMDB) to build with these LLMV features enabled. Please refer to the official LLVM/Clang documentation on them for more informatiuon. These features are also disabled by default.

@ -7,17 +7,19 @@
//!
//! cargo run --example iterator
use std::fs;
use std::str;
use std::{
fs,
str,
};
use tempfile::Builder;
use rkv::backend::{
Lmdb,
LmdbDatabase,
LmdbEnvironment,
};
use rkv::{
backend::{
Lmdb,
LmdbDatabase,
LmdbEnvironment,
},
Manager,
Rkv,
SingleStore,

@ -11,14 +11,14 @@ use std::fs;
use tempfile::Builder;
use rkv::backend::{
BackendStat,
Lmdb,
LmdbDatabase,
LmdbEnvironment,
LmdbRwTransaction,
};
use rkv::{
backend::{
BackendStat,
Lmdb,
LmdbDatabase,
LmdbEnvironment,
LmdbRwTransaction,
},
Manager,
Rkv,
StoreOptions,

@ -16,48 +16,36 @@ mod traits;
pub use common::*;
pub use traits::*;
pub use impl_lmdb::DatabaseImpl as LmdbDatabase;
pub use impl_lmdb::EnvironmentBuilderImpl as Lmdb;
pub use impl_lmdb::EnvironmentImpl as LmdbEnvironment;
pub use impl_lmdb::ErrorImpl as LmdbError;
pub use impl_lmdb::IterImpl as LmdbIter;
pub use impl_lmdb::{
DatabaseFlagsImpl as LmdbDatabaseFlags,
DatabaseImpl as LmdbDatabase,
EnvironmentBuilderImpl as Lmdb,
EnvironmentFlagsImpl as LmdbEnvironmentFlags,
WriteFlagsImpl as LmdbWriteFlags,
};
pub use impl_lmdb::{
EnvironmentImpl as LmdbEnvironment,
ErrorImpl as LmdbError,
InfoImpl as LmdbInfo,
StatImpl as LmdbStat,
};
pub use impl_lmdb::{
IterImpl as LmdbIter,
RoCursorImpl as LmdbRoCursor,
RwCursorImpl as LmdbRwCursor,
};
pub use impl_lmdb::{
RoTransactionImpl as LmdbRoTransaction,
RwCursorImpl as LmdbRwCursor,
RwTransactionImpl as LmdbRwTransaction,
StatImpl as LmdbStat,
WriteFlagsImpl as LmdbWriteFlags,
};
pub use impl_safe::DatabaseImpl as SafeModeDatabase;
pub use impl_safe::EnvironmentBuilderImpl as SafeMode;
pub use impl_safe::EnvironmentImpl as SafeModeEnvironment;
pub use impl_safe::ErrorImpl as SafeModeError;
pub use impl_safe::IterImpl as SafeModeIter;
pub use impl_safe::{
DatabaseFlagsImpl as SafeModeDatabaseFlags,
DatabaseImpl as SafeModeDatabase,
EnvironmentBuilderImpl as SafeMode,
EnvironmentFlagsImpl as SafeModeEnvironmentFlags,
WriteFlagsImpl as SafeModeWriteFlags,
};
pub use impl_safe::{
EnvironmentImpl as SafeModeEnvironment,
ErrorImpl as SafeModeError,
InfoImpl as SafeModeInfo,
StatImpl as SafeModeStat,
};
pub use impl_safe::{
IterImpl as SafeModeIter,
RoCursorImpl as SafeModeRoCursor,
RwCursorImpl as SafeModeRwCursor,
};
pub use impl_safe::{
RoTransactionImpl as SafeModeRoTransaction,
RwCursorImpl as SafeModeRwCursor,
RwTransactionImpl as SafeModeRwTransaction,
StatImpl as SafeModeStat,
WriteFlagsImpl as SafeModeWriteFlags,
};

@ -29,8 +29,8 @@ use crate::backend::traits::{
pub struct EnvironmentBuilderImpl(lmdb::EnvironmentBuilder);
impl<'b> BackendEnvironmentBuilder<'b> for EnvironmentBuilderImpl {
type Error = ErrorImpl;
type Environment = EnvironmentImpl;
type Error = ErrorImpl;
type Flags = EnvironmentFlagsImpl;
fn new() -> EnvironmentBuilderImpl {
@ -69,13 +69,13 @@ impl<'b> BackendEnvironmentBuilder<'b> for EnvironmentBuilderImpl {
pub struct EnvironmentImpl(lmdb::Environment);
impl<'e> BackendEnvironment<'e> for EnvironmentImpl {
type Error = ErrorImpl;
type Database = DatabaseImpl;
type Error = ErrorImpl;
type Flags = DatabaseFlagsImpl;
type Stat = StatImpl;
type Info = InfoImpl;
type RoTransaction = RoTransactionImpl<'e>;
type RwTransaction = RwTransactionImpl<'e>;
type Stat = StatImpl;
fn open_db(&self, name: Option<&str>) -> Result<Self::Database, Self::Error> {
self.0.open_db(name).map(DatabaseImpl).map_err(ErrorImpl)

@ -10,8 +10,10 @@
use std::fmt;
use crate::backend::traits::BackendError;
use crate::error::StoreError;
use crate::{
backend::traits::BackendError,
error::StoreError,
};
#[derive(Debug)]
pub struct ErrorImpl(pub(crate) lmdb::Error);

@ -8,16 +8,18 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use crate::backend::common::{
DatabaseFlags,
EnvironmentFlags,
WriteFlags,
};
use crate::backend::traits::{
BackendDatabaseFlags,
BackendEnvironmentFlags,
BackendFlags,
BackendWriteFlags,
use crate::backend::{
common::{
DatabaseFlags,
EnvironmentFlags,
WriteFlags,
},
traits::{
BackendDatabaseFlags,
BackendEnvironmentFlags,
BackendFlags,
BackendWriteFlags,
},
};
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]

@ -27,8 +27,8 @@ use crate::backend::traits::{
pub struct RoTransactionImpl<'t>(pub(crate) lmdb::RoTransaction<'t>);
impl<'t> BackendRoTransaction for RoTransactionImpl<'t> {
type Error = ErrorImpl;
type Database = DatabaseImpl;
type Error = ErrorImpl;
fn get(&self, db: &Self::Database, key: &[u8]) -> Result<&[u8], Self::Error> {
self.0.get(db.0, &key).map_err(ErrorImpl)
@ -51,8 +51,8 @@ impl<'t> BackendRoCursorTransaction<'t> for RoTransactionImpl<'t> {
pub struct RwTransactionImpl<'t>(pub(crate) lmdb::RwTransaction<'t>);
impl<'t> BackendRwTransaction for RwTransactionImpl<'t> {
type Error = ErrorImpl;
type Database = DatabaseImpl;
type Error = ErrorImpl;
type Flags = WriteFlagsImpl;
fn get(&self, db: &Self::Database, key: &[u8]) -> Result<&[u8], Self::Error> {

@ -14,8 +14,10 @@ use serde_derive::{
Serialize,
};
use super::snapshot::Snapshot;
use super::DatabaseFlagsImpl;
use super::{
snapshot::Snapshot,
DatabaseFlagsImpl,
};
use crate::backend::traits::BackendDatabase;
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]

@ -8,18 +8,20 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs;
use std::path::{
Path,
PathBuf,
};
use std::sync::Arc;
use std::sync::{
RwLock,
RwLockReadGuard,
RwLockWriteGuard,
use std::{
borrow::Cow,
collections::HashMap,
fs,
path::{
Path,
PathBuf,
},
sync::{
Arc,
RwLock,
RwLockReadGuard,
RwLockWriteGuard,
},
};
use id_arena::Arena;
@ -55,8 +57,8 @@ pub struct EnvironmentBuilderImpl {
}
impl<'b> BackendEnvironmentBuilder<'b> for EnvironmentBuilderImpl {
type Error = ErrorImpl;
type Environment = EnvironmentImpl;
type Error = ErrorImpl;
type Flags = EnvironmentFlagsImpl;
fn new() -> EnvironmentBuilderImpl {
@ -188,13 +190,13 @@ impl EnvironmentImpl {
}
impl<'e> BackendEnvironment<'e> for EnvironmentImpl {
type Error = ErrorImpl;
type Database = DatabaseImpl;
type Error = ErrorImpl;
type Flags = DatabaseFlagsImpl;
type Stat = StatImpl;
type Info = InfoImpl;
type RoTransaction = RoTransactionImpl<'e>;
type RwTransaction = RwTransactionImpl<'e>;
type Stat = StatImpl;
fn open_db(&self, name: Option<&str>) -> Result<Self::Database, Self::Error> {
if Arc::strong_count(&self.ro_txns) > 1 {

@ -8,13 +8,17 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::fmt;
use std::io;
use std::{
fmt,
io,
};
use bincode::Error as BincodeError;
use crate::backend::traits::BackendError;
use crate::error::StoreError;
use crate::{
backend::traits::BackendError,
error::StoreError,
};
#[derive(Debug)]
pub enum ErrorImpl {

@ -14,16 +14,18 @@ use serde_derive::{
Serialize,
};
use crate::backend::common::{
DatabaseFlags,
EnvironmentFlags,
WriteFlags,
};
use crate::backend::traits::{
BackendDatabaseFlags,
BackendEnvironmentFlags,
BackendFlags,
BackendWriteFlags,
use crate::backend::{
common::{
DatabaseFlags,
EnvironmentFlags,
WriteFlags,
},
traits::{
BackendDatabaseFlags,
BackendEnvironmentFlags,
BackendFlags,
BackendWriteFlags,
},
};
bitflags! {

@ -8,11 +8,13 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::collections::{
BTreeMap,
BTreeSet,
use std::{
collections::{
BTreeMap,
BTreeSet,
},
sync::Arc,
};
use std::sync::Arc;
use serde_derive::{
Deserialize,

@ -8,8 +8,10 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::collections::HashMap;
use std::sync::Arc;
use std::{
collections::HashMap,
sync::Arc,
};
use super::{
snapshot::Snapshot,
@ -45,8 +47,8 @@ impl<'t> RoTransactionImpl<'t> {
}
impl<'t> BackendRoTransaction for RoTransactionImpl<'t> {
type Error = ErrorImpl;
type Database = DatabaseImpl;
type Error = ErrorImpl;
fn get(&self, db: &Self::Database, key: &[u8]) -> Result<&[u8], Self::Error> {
let snapshot = self.snapshots.get(db).ok_or_else(|| ErrorImpl::DbIsForeignError)?;
@ -86,8 +88,8 @@ impl<'t> RwTransactionImpl<'t> {
}
impl<'t> BackendRwTransaction for RwTransactionImpl<'t> {
type Error = ErrorImpl;
type Database = DatabaseImpl;
type Error = ErrorImpl;
type Flags = WriteFlagsImpl;
fn get(&self, db: &Self::Database, key: &[u8]) -> Result<&[u8], Self::Error> {

@ -8,18 +8,22 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::fmt::{
Debug,
Display,
use std::{
fmt::{
Debug,
Display,
},
path::Path,
};
use std::path::Path;
use crate::backend::common::{
DatabaseFlags,
EnvironmentFlags,
WriteFlags,
use crate::{
backend::common::{
DatabaseFlags,
EnvironmentFlags,
WriteFlags,
},
error::StoreError,
};
use crate::error::StoreError;
pub trait BackendError: Debug + Display + Into<StoreError> {}

@ -8,12 +8,16 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::env::args;
use std::io;
use std::path::Path;
use std::{
env::args,
io,
path::Path,
};
use rkv::migrate::Migrator;
use rkv::MigrateError;
use rkv::{
migrate::Migrator,
MigrateError,
};
fn main() -> Result<(), MigrateError> {
let mut cli_args = args();

@ -14,17 +14,19 @@
//! the number of key/value pairs to create via the `-n <number>` flag
//! (for which the default value is 50).
use std::env::args;
use std::fs;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use rkv::backend::{
BackendEnvironmentBuilder,
Lmdb,
use std::{
env::args,
fs,
fs::File,
io::Read,
path::Path,
};
use rkv::{
backend::{
BackendEnvironmentBuilder,
Lmdb,
},
Rkv,
StoreOptions,
Value,

@ -8,10 +8,12 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::os::raw::c_uint;
use std::path::{
Path,
PathBuf,
use std::{
os::raw::c_uint,
path::{
Path,
PathBuf,
},
};
#[cfg(any(feature = "db-dup-sort", feature = "db-int-key"))]
@ -19,22 +21,26 @@ use crate::backend::{
BackendDatabaseFlags,
DatabaseFlags,
};
use crate::backend::{
BackendEnvironment,
BackendEnvironmentBuilder,
BackendInfo,
BackendRoCursorTransaction,
BackendRwCursorTransaction,
BackendStat,
SafeModeError,
};
use crate::error::StoreError;
use crate::readwrite::{
Reader,
Writer,
use crate::{
backend::{
BackendEnvironment,
BackendEnvironmentBuilder,
BackendInfo,
BackendRoCursorTransaction,
BackendRwCursorTransaction,
BackendStat,
SafeModeError,
},
error::StoreError,
readwrite::{
Reader,
Writer,
},
store::{
single::SingleStore,
Options as StoreOptions,
},
};
use crate::store::single::SingleStore;
use crate::store::Options as StoreOptions;
#[cfg(feature = "db-dup-sort")]
use crate::store::multi::MultiStore;
@ -49,7 +55,7 @@ use crate::store::integermulti::MultiIntegerStore;
pub static DEFAULT_MAX_DBS: c_uint = 5;
/// Wrapper around an `Environment` (e.g. an LMDB environment).
/// Wrapper around an `Environment` (e.g. such as an `LMDB` or `SafeMode` environment).
#[derive(Debug)]
pub struct Rkv<E> {
path: PathBuf,
@ -104,9 +110,11 @@ where
Ok(Rkv {
path: path.into(),
env: builder.open(path).map_err(|e| match e.into() {
StoreError::OtherError(2) => StoreError::DirectoryDoesNotExistError(path.into()),
e => e,
env: builder.open(path).map_err(|e| {
match e.into() {
StoreError::OtherError(2) => StoreError::DirectoryDoesNotExistError(path.into()),
e => e,
}
})?,
})
}
@ -118,8 +126,8 @@ where
E: BackendEnvironment<'e>,
{
/// Create or Open an existing database in (&[u8] -> Single Value) mode.
/// Note: that create=true cannot be called concurrently with other operations
/// so if you are sure that the database exists, call this with create=false.
/// Note: that create=true cannot be called concurrently with other operations so if
/// you are sure that the database exists, call this with create=false.
pub fn open_single<'s, T>(
&self,
name: T,
@ -132,8 +140,8 @@ where
}
/// Create or Open an existing database in (Integer -> Single Value) mode.
/// Note: that create=true cannot be called concurrently with other operations
/// so if you are sure that the database exists, call this with create=false.
/// Note: that create=true cannot be called concurrently with other operations so if
/// you are sure that the database exists, call this with create=false.
#[cfg(feature = "db-int-key")]
pub fn open_integer<'s, T, K>(
&self,
@ -149,8 +157,8 @@ where
}
/// Create or Open an existing database in (&[u8] -> Multiple Values) mode.
/// Note: that create=true cannot be called concurrently with other operations
/// so if you are sure that the database exists, call this with create=false.
/// Note: that create=true cannot be called concurrently with other operations so if
/// you are sure that the database exists, call this with create=false.
#[cfg(feature = "db-dup-sort")]
pub fn open_multi<'s, T>(
&self,
@ -165,8 +173,8 @@ where
}
/// Create or Open an existing database in (Integer -> Multiple Values) mode.
/// Note: that create=true cannot be called concurrently with other operations
/// so if you are sure that the database exists, call this with create=false.
/// Note: that create=true cannot be called concurrently with other operations so if
/// you are sure that the database exists, call this with create=false.
#[cfg(all(feature = "db-dup-sort", feature = "db-int-key"))]
pub fn open_multi_integer<'s, T, K>(
&self,
@ -187,16 +195,20 @@ where
T: Into<Option<&'s str>>,
{
if opts.create {
self.env.create_db(name.into(), opts.flags).map_err(|e| match e.into() {
StoreError::LmdbError(lmdb::Error::BadRslot) => StoreError::open_during_transaction(),
StoreError::SafeModeError(SafeModeError::DbsIllegalOpen) => StoreError::open_during_transaction(),
e => e,
self.env.create_db(name.into(), opts.flags).map_err(|e| {
match e.into() {
StoreError::LmdbError(lmdb::Error::BadRslot) => StoreError::open_during_transaction(),
StoreError::SafeModeError(SafeModeError::DbsIllegalOpen) => StoreError::open_during_transaction(),
e => e,
}
})
} else {
self.env.open_db(name.into()).map_err(|e| match e.into() {
StoreError::LmdbError(lmdb::Error::BadRslot) => StoreError::open_during_transaction(),
StoreError::SafeModeError(SafeModeError::DbsIllegalOpen) => StoreError::open_during_transaction(),
e => e,
self.env.open_db(name.into()).map_err(|e| {
match e.into() {
StoreError::LmdbError(lmdb::Error::BadRslot) => StoreError::open_during_transaction(),
StoreError::SafeModeError(SafeModeError::DbsIllegalOpen) => StoreError::open_during_transaction(),
e => e,
}
})
}
}
@ -207,9 +219,9 @@ impl<'e, E> Rkv<E>
where
E: BackendEnvironment<'e>,
{
/// Create a read transaction. There can be multiple concurrent readers
/// for an environment, up to the maximum specified by LMDB (default 126),
/// and you can open readers while a write transaction is active.
/// Create a read transaction. There can be multiple concurrent readers for an
/// environment, up to the maximum specified by LMDB (default 126), and you can open
/// readers while a write transaction is active.
pub fn read<T>(&'e self) -> Result<Reader<T>, StoreError>
where
E: BackendEnvironment<'e, RoTransaction = T>,
@ -218,9 +230,9 @@ where
Ok(Reader::new(self.env.begin_ro_txn().map_err(|e| e.into())?))
}
/// Create a write transaction. There can be only one write transaction
/// active at any given time, so trying to create a second one will block
/// until the first is committed or aborted.
/// Create a write transaction. There can be only one write transaction active at any
/// given time, so trying to create a second one will block until the first is
/// committed or aborted.
pub fn write<T>(&'e self) -> Result<Writer<T>, StoreError>
where
E: BackendEnvironment<'e, RwTransaction = T>,
@ -235,18 +247,18 @@ impl<'e, E> Rkv<E>
where
E: BackendEnvironment<'e>,
{
/// Flush the data buffers to disk. This call is only useful, when the environment
/// was open with either `NO_SYNC`, `NO_META_SYNC` or `MAP_ASYNC` (see below).
/// The call is not valid if the environment was opened with `READ_ONLY`.
/// Flush the data buffers to disk. This call is only useful, when the environment was
/// open with either `NO_SYNC`, `NO_META_SYNC` or `MAP_ASYNC` (see below). The call is
/// not valid if the environment was opened with `READ_ONLY`.
///
/// Data is always written to disk when `transaction.commit()` is called,
/// but the operating system may keep it buffered.
/// LMDB always flushes the OS buffers upon commit as well,
/// unless the environment was opened with `NO_SYNC` or in part `NO_META_SYNC`.
/// Data is always written to disk when `transaction.commit()` is called, but the
/// operating system may keep it buffered. LMDB always flushes the OS buffers upon
/// commit as well, unless the environment was opened with `NO_SYNC` or in part
/// `NO_META_SYNC`.
///
/// `force`: if true, force a synchronous flush.
/// Otherwise if the environment has the `NO_SYNC` flag set the flushes will be omitted,
/// and with `MAP_ASYNC` they will be asynchronous.
/// `force`: if true, force a synchronous flush. Otherwise if the environment has the
/// `NO_SYNC` flag set the flushes will be omitted, and with `MAP_ASYNC` they will
/// be asynchronous.
pub fn sync(&self, force: bool) -> Result<(), StoreError> {
self.env.sync(force).map_err(|e| e.into())
}
@ -295,23 +307,23 @@ where
/// Sets the size of the memory map to use for the environment.
///
/// This can be used to resize the map when the environment is already open.
/// You can also use `Rkv::environment_builder()` to set the map size during
/// the `Rkv` initialization.
/// This can be used to resize the map when the environment is already open. You can
/// also use `Rkv::environment_builder()` to set the map size during the `Rkv`
/// initialization.
///
/// Note:
///
/// * No active transactions allowed when performing resizing in this process.
/// It's up to the consumer to enforce that.
/// * No active transactions allowed when performing resizing in this process. It's up
/// to the consumer to enforce that.
///
/// * The size should be a multiple of the OS page size. Any attempt to set
/// a size smaller than the space already consumed by the environment will
/// be silently changed to the current size of the used space.
/// * The size should be a multiple of the OS page size. Any attempt to set a size
/// smaller than the space already consumed by the environment will be silently
/// changed to the current size of the used space.
///
/// * In the multi-process case, once a process resizes the map, other
/// processes need to either re-open the environment, or call set_map_size
/// with size 0 to update the environment. Otherwise, new transaction creation
/// will fail with `LmdbError::MapResized`.
/// * In the multi-process case, once a process resizes the map, other processes need
/// to either re-open the environment, or call set_map_size with size 0 to update
/// the environment. Otherwise, new transaction creation will fail with
/// `LmdbError::MapResized`.
pub fn set_map_size(&self, size: usize) -> Result<(), StoreError> {
self.env.set_map_size(size).map_err(Into::into)
}

@ -8,12 +8,14 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::io;
use std::num;
use std::path::PathBuf;
use std::str;
use std::thread;
use std::thread::ThreadId;
use std::{
io,
num,
path::PathBuf,
str,
thread,
thread::ThreadId,
};
use failure::Fail;

@ -8,16 +8,20 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::io;
use std::path::{
Path,
PathBuf,
use std::{
io,
path::{
Path,
PathBuf,
},
};
use url::Url;
use crate::error::StoreError;
use crate::value::Value;
use crate::{
error::StoreError,
value::Value,
};
pub(crate) fn read_transform(value: Result<&[u8], StoreError>) -> Result<Option<Value>, StoreError> {
match value {

@ -8,27 +8,32 @@
// 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/)
//! A simple, humane, typed key-value storage solution. It supports multiple backend
//! engines with varying guarantees, such as [LMDB](http://www.lmdb.tech/doc/) for
//! performance, or "SafeMode" for reliability.
//!
//! It aims to achieve the following:
//!
//! - Avoid LMDB's sharp edges (e.g., obscure error codes for common situations).
//! - Avoid sharp edges (e.g., obscure error codes for common situations).
//! - Report errors via [failure](https://docs.rs/failure/).
//! - 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.
//! - 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 safe and ergonomic.
//! - Encode and decode values via [bincode](https://docs.rs/bincode/)/[serde](https://docs.rs/serde/)
//! 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 that contains a set of key/value databases
//! - [SingleStore](store/single/struct.SingleStore.html): an LMDB database that contains a set of key/value pairs
//! - [Manager](struct.Manager.html): a singleton that controls access to environments
//! - [Rkv](struct.Rkv.html): an environment contains a set of key/value databases
//! - [SingleStore](store/single/struct.SingleStore.html): a database contains a set of
//! key/value pairs
//!
//! Keys can be anything that implements `AsRef<[u8]>` or integers
//! (when accessing an [IntegerStore](store/integer/struct.IntegerStore.html)).
//! Values can be any of the types defined by the [Value](value/enum.Value.html) enum, including:
//!
//! 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`)
@ -45,8 +50,8 @@
//! use std::fs;
//! use tempfile::Builder;
//!
//! // First determine the path to the environment, which is represented
//! // on disk as a directory containing two files:
//! // 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
@ -57,10 +62,9 @@
//! 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:
//! // 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();
@ -69,15 +73,15 @@
//! 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.
//! // 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.
//! // 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();
@ -87,15 +91,15 @@
//! 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.
//! // 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.
//! // 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>`.
@ -111,9 +115,9 @@
//! // 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()`.
//! // 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()`.
//! }
//!
//! {
@ -126,9 +130,9 @@
//! }
//!
//! {
//! // 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.
//! // 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();
@ -144,18 +148,17 @@
//! 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.
//! // 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.
//! // 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.
//! // 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());
@ -168,9 +171,9 @@
//! 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`.
//! // 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();
//! }
//!
@ -227,9 +230,11 @@ pub use readwrite::{
Reader,
Writer,
};
pub use store::keys::EncodableKey;
pub use store::single::SingleStore;
pub use store::Options as StoreOptions;
pub use store::{
keys::EncodableKey,
single::SingleStore,
Options as StoreOptions,
};
pub use value::{
OwnedValue,
Value,

@ -8,41 +8,45 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use std::os::raw::c_uint;
use std::path::{
Path,
PathBuf,
};
use std::result;
use std::sync::{
Arc,
RwLock,
use std::{
collections::{
btree_map::Entry,
BTreeMap,
},
os::raw::c_uint,
path::{
Path,
PathBuf,
},
result,
sync::{
Arc,
RwLock,
},
};
use lazy_static::lazy_static;
use crate::backend::{
LmdbEnvironment,
SafeModeEnvironment,
use crate::{
backend::{
LmdbEnvironment,
SafeModeEnvironment,
},
error::StoreError,
helpers::canonicalize_path,
Rkv,
};
use crate::error::StoreError;
use crate::helpers::canonicalize_path;
use crate::Rkv;
type Result<T> = result::Result<T, StoreError>;
type SharedRkv<E> = Arc<RwLock<Rkv<E>>>;
lazy_static! {
/// 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.
static ref MANAGER_LMDB: RwLock<Manager<LmdbEnvironment>> = RwLock::new(Manager::new());
static ref MANAGER_SAFE_MODE: RwLock<Manager<SafeModeEnvironment>> = RwLock::new(Manager::new());
}
/// 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.
/// 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.
pub struct Manager<E> {
environments: BTreeMap<PathBuf, SharedRkv<E>>,
}
@ -79,8 +83,7 @@ impl<E> Manager<E> {
})
}
/// Return the open env at `path` with capacity `capacity`,
/// or create it by calling `f`.
/// Return the open env at `path` with `capacity`, or create it by calling `f`.
pub fn get_or_create_with_capacity<'p, F, P>(&mut self, path: P, capacity: c_uint, f: F) -> Result<SharedRkv<E>>
where
F: FnOnce(&Path, c_uint) -> Result<Rkv<E>>,
@ -111,12 +114,13 @@ impl Manager<SafeModeEnvironment> {
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::Builder;
use super::*;
use crate::*;
use std::fs;
use tempfile::Builder;
use backend::Lmdb;
/// Test that one can mutate managed Rkv instances in surprising ways.
@ -129,8 +133,8 @@ mod tests {
let path1 = root1.path();
let arc = manager.get_or_create(path1, Rkv::new::<Lmdb>).expect("created");
// Arc<RwLock<>> has interior mutability, so we can replace arc's Rkv
// instance with a new instance that has a different path.
// Arc<RwLock<>> has interior mutability, so we can replace arc's Rkv instance with a new
// instance that has a different path.
let root2 = Builder::new().prefix("test_mutate_managed_rkv_2").tempdir().expect("tempdir");
fs::create_dir_all(root2.path()).expect("dir created");
let path2 = root2.path();
@ -140,14 +144,13 @@ mod tests {
*rkv = rkv2;
}
// arc now has a different internal Rkv with path2, but it's still
// mapped to path1 in manager, so its pointer is equal to a new Arc
// for path1.
// Arc now has a different internal Rkv with path2, but it's still mapped to path1 in
// manager, so its pointer is equal to a new Arc for path1.
let path1_arc = manager.get(path1).expect("success").expect("existed");
assert!(Arc::ptr_eq(&path1_arc, &arc));
// Meanwhile, a new Arc for path2 has a different pointer, even though
// its Rkv's path is the same as arc's current path.
// Meanwhile, a new Arc for path2 has a different pointer, even though its Rkv's path is
// the same as arc's current path.
let path2_arc = manager.get_or_create(path2, Rkv::new::<Lmdb>).expect("success");
assert!(!Arc::ptr_eq(&path2_arc, &arc));
}

@ -8,43 +8,40 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//! A utility for migrating data from one LMDB environment to another.
//! Notably, this tool can migrate data from an enviroment created with
//! a different bit-depth than the current rkv consumer, which enables
//! the consumer to retrieve data from an environment that can't be read
//! directly using the rkv APIs.
//! A utility for migrating data from one LMDB environment to another. Notably, this tool
//! can migrate data from an enviroment created with a different bit-depth than the
//! current rkv consumer, which enables the consumer to retrieve data from an environment
//! that can't be read directly using the rkv APIs.
//!
//! The utility supports both 32-bit and 64-bit LMDB source environments,
//! and it automatically migrates data in both the default database
//! and any named (sub) databases. It also migrates the source environment's
//! "map size" and "max DBs" configuration options to the destination
//! environment.
//! The utility supports both 32-bit and 64-bit LMDB source environments, and it
//! automatically migrates data in both the default database and any named (sub)
//! databases. It also migrates the source environment's "map size" and "max DBs"
//! configuration options to the destination environment.
//!
//! The destination environment must be at the rkv consumer's bit depth
//! and should be empty of data. It can be an empty directory, in which case
//! the utility will create a new LMDB environment within the directory.
//! The destination environment must be at the rkv consumer's bit depth and should be
//! empty of data. It can be an empty directory, in which case the utility will create a
//! new LMDB environment within the directory.
//!
//! The tool currently has these limitations:
//!
//! 1. It doesn't support migration from environments created with
//! `EnvironmentFlags::NO_SUB_DIR`. To migrate such an environment,
//! create a temporary directory, copy the environment's data file
//! to a file called data.mdb in the temporary directory, then migrate
//! the temporary directory as the source environment.
//! 2. It doesn't support migration from databases created with
//! `DatabaseFlags::DUP_SORT` (with or without `DatabaseFlags::DUP_FIXED`).
//! 3. It doesn't account for existing data in the destination environment,
//! which means that it can overwrite data (causing data loss) or fail
//! to migrate data if the destination environment contains existing data.
//! `EnvironmentFlags::NO_SUB_DIR`. To migrate such an environment, create a
//! temporary directory, copy the environment's data file to a file called data.mdb in
//! the temporary directory, then migrate the temporary directory as the source
//! environment.
//! 2. It doesn't support migration from databases created with DatabaseFlags::DUP_SORT`
//! (with or without `DatabaseFlags::DUP_FIXED`).
//! 3. It doesn't account for existing data in the destination environment, which means
//! that it can overwrite data (causing data loss) or fail to migrate data if the
//! destination environment contains existing data.
//!
//! ## Basic Usage
//!
//! Call `Migrator::new()` with the path to the source environment to create
//! a `Migrator` instance; then call the instance's `migrate()` method
//! with the path to the destination environment to migrate data from the source
//! to the destination environment. For example, this snippet migrates data
//! from the tests/envs/ref_env_32 environment to a new environment
//! in a temporary directory:
//! Call `Migrator::new()` with the path to the source environment to create a `Migrator`
//! instance; then call the instance's `migrate()` method with the path to the destination
//! environment to migrate data from the source to the destination environment. For
//! example, this snippet migrates data from the tests/envs/ref_env_32 environment to a
//! new environment in a temporary directory:
//!
//! ```
//! use rkv::migrate::Migrator;
@ -54,22 +51,10 @@
//! migrator.migrate(&tempdir().unwrap().path()).unwrap();
//! ```
//!
//! Both `Migrator::new()` and `migrate()` return a `MigrateResult` that is
//! either an `Ok()` result or an `Err<MigrateError>`, where `MigrateError`
//! is an enum whose variants identify specific kinds of migration failures.
//! Both `Migrator::new()` and `migrate()` return a `MigrateResult` that is either an
//! `Ok()` result or an `Err<MigrateError>`, where `MigrateError` is an enum whose
//! variants identify specific kinds of migration failures.
pub use crate::error::MigrateError;
use bitflags::bitflags;
use byteorder::{
LittleEndian,
ReadBytesExt,
};
use lmdb::{
DatabaseFlags,
Environment,
Transaction,
WriteFlags,
};
use std::{
collections::{
BTreeMap,
@ -92,12 +77,25 @@ use std::{
str,
};
use bitflags::bitflags;
use byteorder::{
LittleEndian,
ReadBytesExt,
};
use lmdb::{
DatabaseFlags,
Environment,
Transaction,
WriteFlags,
};
pub use crate::error::MigrateError;
const PAGESIZE: u16 = 4096;
// The magic number is 0xBEEFC0DE, which is 0xDEC0EFBE in little-endian.
// It appears at offset 12 on 32-bit systems and 16 on 64-bit systems.
// We don't support big-endian migration, but presumably we could do so
// by detecting the order of the bytes.
// The magic number is 0xBEEFC0DE, which is 0xDEC0EFBE in little-endian. It appears at
// offset 12 on 32-bit systems and 16 on 64-bit systems. We don't support big-endian
// migration, but presumably we could do so by detecting the order of the bytes.
const MAGIC: [u8; 4] = [0xDE, 0xC0, 0xEF, 0xBE];
pub type MigrateResult<T> = Result<T, MigrateError>;
@ -126,9 +124,8 @@ bitflags! {
}
}
// The bit depth of the executable that created an LMDB environment.
// The Migrator determines this automatically based on the location of
// the magic number in the data.mdb file.
// The bit depth of the executable that created an LMDB environment. The Migrator
// determines this automatically based on the location of the magic number in data.mdb.
#[derive(Clone, Copy, PartialEq)]
enum Bits {
U32,
@ -369,8 +366,8 @@ impl Page {
}
fn parse_leaf_node(cursor: &mut Cursor<&[u8]>, bits: Bits) -> MigrateResult<LeafNode> {
// The order of the mn_lo and mn_hi fields is endian-dependent and would
// be reversed in an LMDB environment created on a big-endian system.
// The order of the mn_lo and mn_hi fields is endian-dependent and would be
// reversed in an LMDB environment created on a big-endian system.
let mn_lo = cursor.read_u16::<LittleEndian>()?;
let mn_hi = cursor.read_u16::<LittleEndian>()?;
@ -385,7 +382,6 @@ impl Page {
let mv_size = Self::leaf_node_size(mn_lo, mn_hi);
if mn_flags.contains(NodeFlags::BIGDATA) {
let overflow_pgno = cursor.read_uint::<LittleEndian>(bits.size())?;
Ok(LeafNode::BigData {
mn_lo,
mn_hi,
@ -402,7 +398,6 @@ impl Page {
let mut cursor = std::io::Cursor::new(&value[..]);
let db = Database::new(&mut cursor, bits)?;
validate_page_num(db.md_root, bits)?;
Ok(LeafNode::SubData {
mn_lo,
mn_hi,
@ -417,7 +412,6 @@ impl Page {
let start = usize::try_from(cursor.position())?;
let end = usize::try_from(cursor.position() + u64::from(mv_size))?;
let value = cursor.get_ref()[start..end].to_vec();
Ok(LeafNode::Regular {
mn_lo,
mn_hi,
@ -449,15 +443,15 @@ impl Page {
}
fn parse_branch_node(cursor: &mut Cursor<&[u8]>, bits: Bits) -> MigrateResult<BranchNode> {
// The order of the mn_lo and mn_hi fields is endian-dependent and would
// be reversed in an LMDB environment created on a big-endian system.
// The order of the mn_lo and mn_hi fields is endian-dependent and would be
// reversed in an LMDB environment created on a big-endian system.
let mn_lo = cursor.read_u16::<LittleEndian>()?;
let mn_hi = cursor.read_u16::<LittleEndian>()?;
let mn_flags = cursor.read_u16::<LittleEndian>()?;
// Branch nodes overload the mn_lo, mn_hi, and mn_flags fields
// to store the page number, so we derive the number from those fields.
// Branch nodes overload the mn_lo, mn_hi, and mn_flags fields to store the page
// number, so we derive the number from those fields.
let mp_pgno = Self::branch_node_page_num(mn_lo, mn_hi, mn_flags, bits);
let mn_ksize = cursor.read_u16::<LittleEndian>()?;
@ -502,10 +496,10 @@ pub struct Migrator {
}
impl Migrator {
/// Create a new Migrator for the LMDB environment at the given path.
/// This tries to open the data.mdb file in the environment and determine
/// the bit depth of the executable that created it, so it can fail
/// and return an Err if the file can't be opened or the depth determined.
/// Create a new Migrator for the LMDB environment at the given path. This tries to
/// open the data.mdb file in the environment and determine the bit depth of the
/// executable that created it, so it can fail and return an Err if the file can't be
/// opened or the depth determined.
pub fn new(path: &Path) -> MigrateResult<Migrator> {
let mut path = PathBuf::from(path);
path.push("data.mdb");
@ -533,20 +527,18 @@ impl Migrator {
})
}
/// Dump the data in one of the databases in the LMDB environment.
/// If the `database` paremeter is None, then we dump the data in the main
/// database. If it's the name of a subdatabase, then we dump the data
/// in that subdatabase.
///
/// Note that the output isn't identical to that of the mdb_dump utility,
/// since mdb_dump includes subdatabase key/value pairs when dumping
/// the main database, and those values are architecture-dependent, since
/// they contain pointer-sized data.
/// Dump the data in one of the databases in the LMDB environment. If the `database`
/// paremeter is None, then we dump the data in the main database. If it's the name
/// of a subdatabase, then we dump the data in that subdatabase.
///
/// If we wanted to support identical output, we could parameterize
/// inclusion of subdatabase pairs in get_pairs() and include them
/// when dumping data, while continuing to exclude them when migrating
/// Note that the output isn't identical to that of the `mdb_dump` utility, since
/// `mdb_dump` includes subdatabase key/value pairs when dumping the main database,
/// and those values are architecture-dependent, since they contain pointer-sized
/// data.
///
/// If we wanted to support identical output, we could parameterize inclusion of
/// subdatabase pairs in get_pairs() and include them when dumping data, while
/// continuing to exclude them when migrating data.
pub fn dump<T: Write>(&mut self, database: Option<&str>, mut out: T) -> MigrateResult<()> {
let meta_data = self.get_meta_data()?;
let root_page_num = meta_data.mm_dbs.main.md_root;
@ -593,20 +585,18 @@ impl Migrator {
Ok(())
}
/// Migrate all data in all of databases in the existing LMDB environment
/// to a new environment. This includes all key/value pairs in the main
/// database that aren't metadata about subdatabases and all key/value pairs
/// in all subdatabases.
/// Migrate all data in all of databases in the existing LMDB environment to a new
/// environment. This includes all key/value pairs in the main database that aren't
/// metadata about subdatabases and all key/value pairs in all subdatabases.
///
/// We also set the map size and maximum databases of the new environment
/// to their values for the existing environment. But we don't set
/// other metadata, and we don't check that the new environment is empty
/// before migrating data.
/// We also set the map size and maximum databases of the new environment to their
/// values for the existing environment. But we don't set other metadata, and we
/// don't check that the new environment is empty before migrating data.
///
/// Thus it's possible for this to overwrite existing data or fail
/// to migrate data if the new environment isn't empty. It's the consumer's
/// responsibility to ensure that data can be safely migrated to the new
/// environment. In general, this means that environment should be empty.
/// Thus it's possible for this to overwrite existing data or fail to migrate data if
/// the new environment isn't empty. It's the consumer's responsibility to ensure
/// that data can be safely migrated to the new environment. In general, this means
/// that environment should be empty.
pub fn migrate(&mut self, dest: &Path) -> MigrateResult<()> {
let meta_data = self.get_meta_data()?;
let root_page_num = meta_data.mm_dbs.main.md_root;
@ -619,24 +609,23 @@ impl Migrator {
.set_max_dbs(subdbs.len() as u32)
.open(dest)?;
// Create the databases before we open a read-write transaction,
// since database creation requires its own read-write transaction,
// which would hang while awaiting completion of an existing one.
// Create the databases before we open a read-write transaction, since database
// creation requires its own read-write transaction, which would hang while
// awaiting completion of an existing one.
env.create_db(None, meta_data.mm_dbs.main.md_flags)?;
for (subdb_name, subdb_info) in &subdbs {
env.create_db(Some(str::from_utf8(&subdb_name)?), subdb_info.md_flags)?;
}
// Now open the read-write transaction that we'll use to migrate
// all the data.
// Now open the read-write transaction that we'll use to migrate all the data.
let mut txn = env.begin_rw_txn()?;
// Migrate the main database.
let pairs = self.get_pairs(root_page)?;
let db = env.open_db(None)?;
for (key, value) in pairs {
// If we knew that the target database was empty, we could
// specify WriteFlags::APPEND to speed up the migration.
// If we knew that the target database was empty, we could specify
// WriteFlags::APPEND to speed up the migration.
txn.put(db, &key, &value, WriteFlags::empty())?;
}
@ -646,8 +635,8 @@ impl Migrator {
let pairs = self.get_pairs(root_page)?;
let db = env.open_db(Some(str::from_utf8(&subdb_name)?))?;
for (key, value) in pairs {
// If we knew that the target database was empty, we could
// specify WriteFlags::APPEND to speed up the migration.
// If we knew that the target database was empty, we could specify
// WriteFlags::APPEND to speed up the migration.
txn.put(db, &key, &value, WriteFlags::empty())?;
}
}
@ -716,9 +705,9 @@ impl Migrator {
overflow_pgno,
..
} => {
// XXX perhaps we could reduce memory consumption
// during a migration by waiting to read big data
// until it's time to write it to the new database.
// Perhaps we could reduce memory consumption during a
// migration by waiting to read big data until it's time
// to write it to the new database.
let value = self.read_data(
*overflow_pgno * u64::from(PAGESIZE) + page_header_size(self.bits),
*mv_size as usize,
@ -728,16 +717,15 @@ impl Migrator {
LeafNode::SubData {
..
} => {
// We don't include subdatabase leaves in pairs,
// since there's no architecture-neutral
// representation of them, and in any case they're
// meta-data that should get recreated when we
// migrate the subdatabases themselves.
// We don't include subdatabase leaves in pairs, since
// there's no architecture-neutral representation of them,
// and in any case they're meta-data that should get
// recreated when we migrate the subdatabases themselves.
//
// If we wanted to create identical dumps to those
// produced by mdb_dump, however, we could allow
// consumers to specify that they'd like to include
// these records.
// produced by `mdb_dump`, however, we could allow
// consumers to specify that they'd like to include these
// records.
},
};
}
@ -787,26 +775,17 @@ impl Migrator {
#[cfg(test)]
mod tests {
use super::MigrateResult;
use super::Migrator;
use crate::error::MigrateError;
use lmdb::{
Environment,
Error as LmdbError,
};
use super::*;
use std::{
env,
fs::{
self,
File,
},
io::{
Read,
Seek,
SeekFrom,
},
fs,
mem::size_of,
path::PathBuf,
};
use lmdb::{
Environment,
Error as LmdbError,
};
use tempfile::{
tempdir,
@ -823,15 +802,17 @@ mod tests {
loop {
match ref_file.read(ref_buf) {
Err(err) => panic!(err),
Ok(ref_len) => match new_file.read(new_buf) {
Err(err) => panic!(err),
Ok(new_len) => {
assert_eq!(ref_len, new_len);
if ref_len == 0 {
break;
};
assert_eq!(ref_buf[0..ref_len], new_buf[0..new_len]);
},
Ok(ref_len) => {
match new_file.read(new_buf) {
Err(err) => panic!(err),
Ok(new_len) => {
assert_eq!(ref_len, new_len);
if ref_len == 0 {
break;
};
assert_eq!(ref_buf[0..ref_len], new_buf[0..new_len]);
},
}
},
}
}
@ -1017,8 +998,8 @@ mod tests {
// Compare the new dump file to the reference dump file.
compare_files(&mut ref_dump_file, &mut new_dump_file)?;
// Overwrite the old env's files with the new env's files and confirm
// that it's now possible to open the old env with LMDB.
// Overwrite the old env's files with the new env's files and confirm that it's now
// possible to open the old env with LMDB.
fs::copy(new_env.path().join("data.mdb"), old_env.path().join("data.mdb"))?;
fs::copy(new_env.path().join("lock.mdb"), old_env.path().join("lock.mdb"))?;
assert!(Environment::new().open(&old_env.path()).is_ok());

@ -8,17 +8,19 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use crate::backend::{
BackendDatabase,
BackendRoCursor,
BackendRoCursorTransaction,
BackendRoTransaction,
BackendRwCursorTransaction,
BackendRwTransaction,
use crate::{
backend::{
BackendDatabase,
BackendRoCursor,
BackendRoCursorTransaction,
BackendRoTransaction,
BackendRwCursorTransaction,
BackendRwTransaction,
},
error::StoreError,
helpers::read_transform,
value::Value,
};
use crate::error::StoreError;
use crate::helpers::read_transform;
use crate::value::Value;
pub struct Reader<T>(T);
pub struct Writer<T>(T);

@ -10,21 +10,25 @@
use std::marker::PhantomData;
use crate::backend::{
BackendDatabase,
BackendRwTransaction,
use crate::{
backend::{
BackendDatabase,
BackendRwTransaction,
},
error::StoreError,
readwrite::{
Readable,
Writer,
},
store::{
keys::{
Key,
PrimitiveInt,
},
single::SingleStore,
},
value::Value,
};
use crate::error::StoreError;
use crate::readwrite::{
Readable,
Writer,
};
use crate::store::keys::{
Key,
PrimitiveInt,
};
use crate::store::single::SingleStore;
use crate::value::Value;
type EmptyResult = Result<(), StoreError>;
@ -77,12 +81,13 @@ where
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::Builder;
use super::*;
use crate::*;
use std::fs;
use tempfile::Builder;
#[test]
fn test_integer_keys() {
let root = Builder::new().prefix("test_integer_keys").tempdir().expect("tempdir");
@ -310,12 +315,13 @@ mod tests {
#[cfg(test)]
mod tests_safe {
use std::fs;
use tempfile::Builder;
use super::*;
use crate::*;
use std::fs;
use tempfile::Builder;
#[test]
fn test_integer_keys() {
let root = Builder::new().prefix("test_integer_keys").tempdir().expect("tempdir");

@ -10,26 +10,30 @@
use std::marker::PhantomData;
use crate::backend::{
BackendDatabase,
BackendIter,
BackendRoCursor,
BackendRwTransaction,
use crate::{
backend::{
BackendDatabase,
BackendIter,
BackendRoCursor,
BackendRwTransaction,
},
error::StoreError,
readwrite::{
Readable,
Writer,
},
store::{
keys::{
Key,
PrimitiveInt,
},
multi::{
Iter,
MultiStore,
},
},
value::Value,
};
use crate::error::StoreError;
use crate::readwrite::{
Readable,
Writer,
};
use crate::store::keys::{
Key,
PrimitiveInt,
};
use crate::store::multi::{
Iter,
MultiStore,
};
use crate::value::Value;
type EmptyResult = Result<(), StoreError>;
@ -106,12 +110,13 @@ where
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::Builder;
use super::*;
use crate::*;
use std::fs;
use tempfile::Builder;
#[test]
fn test_integer_keys() {
let root = Builder::new().prefix("test_integer_keys").tempdir().expect("tempdir");
@ -322,12 +327,13 @@ mod tests {
#[cfg(test)]
mod tests_safe {
use std::fs;
use tempfile::Builder;
use super::*;
use crate::*;
use std::fs;
use tempfile::Builder;
#[test]
fn test_integer_keys() {
let root = Builder::new().prefix("test_integer_keys").tempdir().expect("tempdir");

@ -10,20 +10,22 @@
use std::marker::PhantomData;
use crate::backend::{
BackendDatabase,
BackendFlags,
BackendIter,
BackendRoCursor,
BackendRwTransaction,
use crate::{
backend::{
BackendDatabase,
BackendFlags,
BackendIter,
BackendRoCursor,
BackendRwTransaction,
},
error::StoreError,
helpers::read_transform,
readwrite::{
Readable,
Writer,
},
value::Value,
};
use crate::error::StoreError;
use crate::helpers::read_transform;
use crate::readwrite::{
Readable,
Writer,
};
use crate::value::Value;
type EmptyResult = Result<(), StoreError>;
@ -47,7 +49,8 @@ where
}
}
/// Provides a cursor to all of the values for the duplicate entries that match this key
/// Provides a cursor to all of the values for the duplicate entries that match this
/// key
pub fn get<'r, R, I, C, K>(&self, reader: &'r R, k: K) -> Result<Iter<'r, I>, StoreError>
where
R: Readable<'r, Database = D, RoCursor = C>,
@ -125,9 +128,11 @@ where
fn next(&mut self) -> Option<Self::Item> {
match self.iter.next() {
None => None,
Some(Ok((key, bytes))) => match read_transform(Ok(bytes)) {
Ok(val) => Some(Ok((key, val))),
Err(err) => Some(Err(err)),
Some(Ok((key, bytes))) => {
match read_transform(Ok(bytes)) {
Ok(val) => Some(Ok((key, val))),
Err(err) => Some(Err(err)),
}
},
Some(Err(err)) => Some(Err(err.into())),
}

@ -10,20 +10,22 @@
use std::marker::PhantomData;
use crate::backend::{
BackendDatabase,
BackendFlags,
BackendIter,
BackendRoCursor,
BackendRwTransaction,
use crate::{
backend::{
BackendDatabase,
BackendFlags,
BackendIter,
BackendRoCursor,
BackendRwTransaction,
},
error::StoreError,
helpers::read_transform,
readwrite::{
Readable,
Writer,
},
value::Value,
};
use crate::error::StoreError;
use crate::helpers::read_transform;
use crate::readwrite::{
Readable,
Writer,
};
use crate::value::Value;
type EmptyResult = Result<(), StoreError>;
@ -131,9 +133,11 @@ where
fn next(&mut self) -> Option<Self::Item> {
match self.iter.next() {
None => None,
Some(Ok((key, bytes))) => match read_transform(Ok(bytes)) {
Ok(val) => Some(Ok((key, val))),
Err(err) => Some(Err(err)),
Some(Ok((key, bytes))) => {
match read_transform(Ok(bytes)) {
Ok(val) => Some(Ok((key, val))),
Err(err) => Some(Err(err)),
}
},
Some(Err(err)) => Some(Err(err.into())),
}

@ -24,10 +24,9 @@ use uuid::{
use crate::error::DataError;
/// We define a set of types, associated with simple integers, to annotate values
/// stored in LMDB. This is to avoid an accidental 'cast' from a value of one type
/// to another. For this reason we don't simply use `deserialize` from the `bincode`
/// crate.
/// We define a set of types, associated with simple integers, to annotate values stored
/// in LMDB. This is to avoid an accidental 'cast' from a value of one type to another.
/// For this reason we don't simply use `deserialize` from the `bincode` crate.
#[repr(u8)]
#[derive(Debug, PartialEq, Eq)]
pub enum Type {
@ -129,9 +128,11 @@ impl<'v> Value<'v> {
fn from_type_and_data(t: Type, data: &'v [u8]) -> Result<Value<'v>, DataError> {
if t == Type::Uuid {
return deserialize(data)
.map_err(|e| DataError::DecodingError {
value_type: t,
err: e,
.map_err(|e| {
DataError::DecodingError {
value_type: t,
err: e,
}
})
.map(uuid)?;
}
@ -150,9 +151,11 @@ impl<'v> Value<'v> {
unreachable!()
},
}
.map_err(|e| DataError::DecodingError {
value_type: t,
err: e,
.map_err(|e| {
DataError::DecodingError {
value_type: t,
err: e,
}
})
}

@ -12,11 +12,11 @@ use std::fs;
use tempfile::Builder;
use rkv::backend::{
Lmdb,
SafeMode,
};
use rkv::{
backend::{
Lmdb,
SafeMode,
},
Rkv,
StoreOptions,
Value,
@ -67,7 +67,7 @@ fn test_open_safe_same_dir_as_lmdb() {
assert_eq!(sk.get(&reader, "bar").expect("read"), Some(Value::Bool(true)));
assert_eq!(sk.get(&reader, "baz").expect("read"), Some(Value::Str("héllo, yöu")));
}
// Create database of type B and save to disk (database of type A exists at the same path).
// Create database of type B and save to disk (type A exists at the same path).
{
let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
@ -149,7 +149,7 @@ fn test_open_lmdb_same_dir_as_safe() {
assert_eq!(sk.get(&reader, "bar").expect("read"), Some(Value::Bool(true)));
assert_eq!(sk.get(&reader, "baz").expect("read"), Some(Value::Str("héllo, yöu")));
}
// Create database of type B and save to disk (database of type A exists at the same path).
// Create database of type B and save to disk (type A exists at the same path).
{
let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
let sk = k.open_single("sk", StoreOptions::create()).expect("opened");

@ -12,13 +12,15 @@
// deprecates `clippy::cyclomatic_complexity`.
#![allow(clippy::complexity)]
use std::fs;
use std::str;
use std::sync::{
Arc,
RwLock,
use std::{
fs,
str,
sync::{
Arc,
RwLock,
},
thread,
};
use std::thread;
use byteorder::{
ByteOrder,
@ -26,16 +28,16 @@ use byteorder::{
};
use tempfile::Builder;
use rkv::backend::{
BackendEnvironmentBuilder,
BackendInfo,
BackendStat,
Lmdb,
LmdbDatabase,
LmdbEnvironment,
LmdbRwTransaction,
};
use rkv::{
backend::{
BackendEnvironmentBuilder,
BackendInfo,
BackendStat,
Lmdb,
LmdbDatabase,
LmdbEnvironment,
LmdbRwTransaction,
},
EnvironmentFlags,
Rkv,
SingleStore,

@ -12,13 +12,15 @@
// deprecates `clippy::cyclomatic_complexity`.
#![allow(clippy::complexity)]
use std::fs;
use std::str;
use std::sync::{
Arc,
RwLock,
use std::{
fs,
str,
sync::{
Arc,
RwLock,
},
thread,
};
use std::thread;
use byteorder::{
ByteOrder,
@ -26,14 +28,14 @@ use byteorder::{
};
use tempfile::Builder;
use rkv::backend::{
BackendEnvironmentBuilder,
SafeMode,
SafeModeDatabase,
SafeModeEnvironment,
SafeModeRwTransaction,
};
use rkv::{
backend::{
BackendEnvironmentBuilder,
SafeMode,
SafeModeDatabase,
SafeModeEnvironment,
SafeModeRwTransaction,
},
Rkv,
SingleStore,
StoreError,

@ -15,8 +15,8 @@ use std::fs;
use serde_derive::Serialize;
use tempfile::Builder;
use rkv::backend::Lmdb;
use rkv::{
backend::Lmdb,
PrimitiveInt,
Rkv,
StoreOptions,

@ -8,18 +8,22 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::fs;
use std::sync::Arc;
use std::{
fs,
sync::Arc,
};
use tempfile::Builder;
use rkv::backend::{
Lmdb,
LmdbEnvironment,
SafeMode,
SafeModeEnvironment,
use rkv::{
backend::{
Lmdb,
LmdbEnvironment,
SafeMode,
SafeModeEnvironment,
},
Rkv,
};
use rkv::Rkv;
/// Test that a manager can be created with simple type inference.
#[test]

@ -15,8 +15,8 @@ use std::fs;
use serde_derive::Serialize;
use tempfile::Builder;
use rkv::backend::Lmdb;
use rkv::{
backend::Lmdb,
PrimitiveInt,
Rkv,
StoreOptions,

@ -13,13 +13,13 @@ use std::fs;
use tempfile::Builder;
use rkv::backend::{
Lmdb,
LmdbDatabase,
LmdbRoCursor,
LmdbRwTransaction,
};
use rkv::{
backend::{
Lmdb,
LmdbDatabase,
LmdbRoCursor,
LmdbRwTransaction,
},
Readable,
Rkv,
StoreOptions,
@ -33,10 +33,10 @@ use rkv::{
/// value: String,
/// date: String,
/// }
/// We would like to index all of the fields so that we can search for the struct not only by ID
/// but also by value and date. When we index the fields individually in their own tables, it
/// is important that we run all operations within a single transaction to ensure coherence of
/// the indices.
/// We would like to index all of the fields so that we can search for the struct not only
/// by ID but also by value and date. When we index the fields individually in their own
/// tables, it is important that we run all operations within a single transaction to
/// ensure coherence of the indices.
/// This test features helper functions for reading and writing the parts of the struct.
/// Note that the reader functions take `Readable` because they might run within a Read
/// Transaction or a Write Transaction. The test demonstrates fetching values via both.
@ -97,9 +97,11 @@ where
store
.get(txn, field)
.expect("get iterator")
.map(|id| match id.expect("field") {
(_, Some(Value::U64(id))) => id,
_ => panic!("getting value in iter"),
.map(|id| {
match id.expect("field") {
(_, Some(Value::U64(id))) => id,
_ => panic!("getting value in iter"),
}
})
.collect::<Vec<u64>>()
}

Loading…
Cancel
Save