Merge pull request #201 from mozilla/easy-migration

Make migration easier for consumers
without.crypto
Victor Porof 4 years ago committed by GitHub
commit 5e2d0d6764
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      Cargo.toml
  2. 145
      src/backend/impl_lmdb/environment.rs
  3. 15
      src/backend/impl_lmdb/error.rs
  4. 22
      src/backend/impl_safe/environment.rs
  5. 9
      src/backend/impl_safe/error.rs
  6. 9
      src/backend/traits.rs
  7. 15
      src/env.rs
  8. 26
      src/error.rs
  9. 4
      src/helpers.rs
  10. 1
      src/lib.rs
  11. 45
      src/manager.rs
  12. 57
      src/migrator.rs
  13. 59
      tests/env-lmdb.rs
  14. 161
      tests/env-migration.rs
  15. 4
      tests/env-safe.rs

@ -37,6 +37,7 @@ lazy_static = "1.0"
lmdb-rkv = "0.14"
log = "0.4"
ordered-float = "1.0"
paste = "0.1"
serde = {version = "1.0", features = ["derive", "rc"]}
serde_derive = "1.0"
url = "2.0"

@ -10,7 +10,10 @@
use std::{
fs,
path::Path,
path::{
Path,
PathBuf,
},
};
use lmdb::Error as LmdbError;
@ -38,9 +41,10 @@ use crate::backend::traits::{
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct EnvironmentBuilderImpl {
builder: lmdb::EnvironmentBuilder,
envtype: EnvironmentType,
env_path_type: EnvironmentPathType,
env_lock_type: EnvironmentLockType,
env_db_type: EnvironmentDefaultDbType,
make_dir: bool,
check_env_exists: bool,
}
impl<'b> BackendEnvironmentBuilder<'b> for EnvironmentBuilderImpl {
@ -51,9 +55,10 @@ impl<'b> BackendEnvironmentBuilder<'b> for EnvironmentBuilderImpl {
fn new() -> EnvironmentBuilderImpl {
EnvironmentBuilderImpl {
builder: lmdb::Environment::new(),
envtype: EnvironmentType::SingleDatabase,
env_path_type: EnvironmentPathType::SubDir,
env_lock_type: EnvironmentLockType::Lockfile,
env_db_type: EnvironmentDefaultDbType::SingleDatabase,
make_dir: false,
check_env_exists: false,
}
}
@ -61,7 +66,14 @@ impl<'b> BackendEnvironmentBuilder<'b> for EnvironmentBuilderImpl {
where
T: Into<Self::Flags>,
{
self.builder.set_flags(flags.into().0);
let flags = flags.into();
if flags.0 == lmdb::EnvironmentFlags::NO_SUB_DIR {
self.env_path_type = EnvironmentPathType::NoSubDir;
}
if flags.0 == lmdb::EnvironmentFlags::NO_LOCK {
self.env_lock_type = EnvironmentLockType::NoLockfile;
}
self.builder.set_flags(flags.0);
self
}
@ -71,10 +83,10 @@ impl<'b> BackendEnvironmentBuilder<'b> for EnvironmentBuilderImpl {
}
fn set_max_dbs(&mut self, max_dbs: u32) -> &mut Self {
self.builder.set_max_dbs(max_dbs);
if max_dbs > 0 {
self.envtype = EnvironmentType::MultipleNamedDatabases
self.env_db_type = EnvironmentDefaultDbType::MultipleNamedDatabases
}
self.builder.set_max_dbs(max_dbs);
self
}
@ -88,33 +100,73 @@ impl<'b> BackendEnvironmentBuilder<'b> for EnvironmentBuilderImpl {
self
}
fn set_check_if_env_exists(&mut self, check_env_exists: bool) -> &mut Self {
self.check_env_exists = check_env_exists;
self
}
fn open(&self, path: &Path) -> Result<Self::Environment, Self::Error> {
if self.check_env_exists && !path.join("data.mdb").exists() {
return Err(ErrorImpl::EnvironmentDoesNotExistError(path.into()));
match self.env_path_type {
EnvironmentPathType::NoSubDir => {
if !path.is_file() {
return Err(ErrorImpl::UnsuitableEnvironmentPath(path.into()));
}
},
EnvironmentPathType::SubDir => {
if !path.is_dir() {
if !self.make_dir {
return Err(ErrorImpl::UnsuitableEnvironmentPath(path.into()));
}
fs::create_dir_all(path)?;
}
},
}
if !path.is_dir() {
if !self.make_dir {
return Err(ErrorImpl::DirectoryDoesNotExistError(path.into()));
}
fs::create_dir_all(path).map_err(ErrorImpl::IoError)?;
}
self.builder.open(path).map(|env| EnvironmentImpl(env, self.envtype)).map_err(ErrorImpl::LmdbError)
self.builder.open(path).map_err(ErrorImpl::LmdbError).and_then(|lmdbenv| {
EnvironmentImpl::new(path, self.env_path_type, self.env_lock_type, self.env_db_type, lmdbenv)
})
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum EnvironmentType {
pub enum EnvironmentPathType {
SubDir,
NoSubDir,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum EnvironmentLockType {
Lockfile,
NoLockfile,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum EnvironmentDefaultDbType {
SingleDatabase,
MultipleNamedDatabases,
}
#[derive(Debug)]
pub struct EnvironmentImpl(lmdb::Environment, EnvironmentType);
pub struct EnvironmentImpl {
path: PathBuf,
env_path_type: EnvironmentPathType,
env_lock_type: EnvironmentLockType,
env_db_type: EnvironmentDefaultDbType,
lmdbenv: lmdb::Environment,
}
impl EnvironmentImpl {
pub(crate) fn new(
path: &Path,
env_path_type: EnvironmentPathType,
env_lock_type: EnvironmentLockType,
env_db_type: EnvironmentDefaultDbType,
lmdbenv: lmdb::Environment,
) -> Result<EnvironmentImpl, ErrorImpl> {
Ok(EnvironmentImpl {
path: path.to_path_buf(),
env_path_type,
env_lock_type,
env_db_type,
lmdbenv,
})
}
}
impl<'e> BackendEnvironment<'e> for EnvironmentImpl {
type Database = DatabaseImpl;
@ -126,10 +178,10 @@ impl<'e> BackendEnvironment<'e> for EnvironmentImpl {
type Stat = StatImpl;
fn get_dbs(&self) -> Result<Vec<Option<String>>, Self::Error> {
if self.1 == EnvironmentType::SingleDatabase {
if self.env_db_type == EnvironmentDefaultDbType::SingleDatabase {
return Ok(vec![None]);
}
let db = self.0.open_db(None).map(DatabaseImpl).map_err(ErrorImpl::LmdbError)?;
let db = self.lmdbenv.open_db(None).map(DatabaseImpl).map_err(ErrorImpl::LmdbError)?;
let reader = self.begin_ro_txn()?;
let cursor = reader.open_ro_cursor(&db)?;
let mut iter = cursor.into_iter();
@ -143,35 +195,35 @@ impl<'e> BackendEnvironment<'e> for EnvironmentImpl {
}
fn open_db(&self, name: Option<&str>) -> Result<Self::Database, Self::Error> {
self.0.open_db(name).map(DatabaseImpl).map_err(ErrorImpl::LmdbError)
self.lmdbenv.open_db(name).map(DatabaseImpl).map_err(ErrorImpl::LmdbError)
}
fn create_db(&self, name: Option<&str>, flags: Self::Flags) -> Result<Self::Database, Self::Error> {
self.0.create_db(name, flags.0).map(DatabaseImpl).map_err(ErrorImpl::LmdbError)
self.lmdbenv.create_db(name, flags.0).map(DatabaseImpl).map_err(ErrorImpl::LmdbError)
}
fn begin_ro_txn(&'e self) -> Result<Self::RoTransaction, Self::Error> {
self.0.begin_ro_txn().map(RoTransactionImpl).map_err(ErrorImpl::LmdbError)
self.lmdbenv.begin_ro_txn().map(RoTransactionImpl).map_err(ErrorImpl::LmdbError)
}
fn begin_rw_txn(&'e self) -> Result<Self::RwTransaction, Self::Error> {
self.0.begin_rw_txn().map(RwTransactionImpl).map_err(ErrorImpl::LmdbError)
self.lmdbenv.begin_rw_txn().map(RwTransactionImpl).map_err(ErrorImpl::LmdbError)
}
fn sync(&self, force: bool) -> Result<(), Self::Error> {
self.0.sync(force).map_err(ErrorImpl::LmdbError)
self.lmdbenv.sync(force).map_err(ErrorImpl::LmdbError)
}
fn stat(&self) -> Result<Self::Stat, Self::Error> {
self.0.stat().map(StatImpl).map_err(ErrorImpl::LmdbError)
self.lmdbenv.stat().map(StatImpl).map_err(ErrorImpl::LmdbError)
}
fn info(&self) -> Result<Self::Info, Self::Error> {
self.0.info().map(InfoImpl).map_err(ErrorImpl::LmdbError)
self.lmdbenv.info().map(InfoImpl).map_err(ErrorImpl::LmdbError)
}
fn freelist(&self) -> Result<usize, Self::Error> {
self.0.freelist().map_err(ErrorImpl::LmdbError)
self.lmdbenv.freelist().map_err(ErrorImpl::LmdbError)
}
fn load_ratio(&self) -> Result<Option<f32>, Self::Error> {
@ -189,6 +241,29 @@ impl<'e> BackendEnvironment<'e> for EnvironmentImpl {
}
fn set_map_size(&self, size: usize) -> Result<(), Self::Error> {
self.0.set_map_size(size).map_err(ErrorImpl::LmdbError)
self.lmdbenv.set_map_size(size).map_err(ErrorImpl::LmdbError)
}
fn get_files_on_disk(&self) -> Vec<PathBuf> {
let mut store = vec![];
if self.env_path_type == EnvironmentPathType::NoSubDir {
// The option NO_SUB_DIR could change the default directory layout; therefore this should
// probably return the path used to create environment, along with the custom lockfile
// when available.
unimplemented!();
}
let mut db_filename = self.path.clone();
db_filename.push("data.mdb");
store.push(db_filename);
if self.env_lock_type == EnvironmentLockType::Lockfile {
let mut lock_filename = self.path.clone();
lock_filename.push("lock.mdb");
store.push(lock_filename);
}
store
}
}

@ -22,8 +22,7 @@ use crate::{
#[derive(Debug)]
pub enum ErrorImpl {
LmdbError(lmdb::Error),
DirectoryDoesNotExistError(PathBuf),
EnvironmentDoesNotExistError(PathBuf),
UnsuitableEnvironmentPath(PathBuf),
IoError(io::Error),
}
@ -33,8 +32,7 @@ impl fmt::Display for ErrorImpl {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
ErrorImpl::LmdbError(e) => e.fmt(fmt),
ErrorImpl::DirectoryDoesNotExistError(_) => write!(fmt, "DirectoryDoesNotExistError"),
ErrorImpl::EnvironmentDoesNotExistError(_) => write!(fmt, "EnvironmentDoesNotExistError"),
ErrorImpl::UnsuitableEnvironmentPath(_) => write!(fmt, "UnsuitableEnvironmentPath"),
ErrorImpl::IoError(e) => e.fmt(fmt),
}
}
@ -51,9 +49,14 @@ impl Into<StoreError> for ErrorImpl {
ErrorImpl::LmdbError(lmdb::Error::DbsFull) => StoreError::DbsFull,
ErrorImpl::LmdbError(lmdb::Error::ReadersFull) => StoreError::ReadersFull,
ErrorImpl::LmdbError(error) => StoreError::LmdbError(error),
ErrorImpl::DirectoryDoesNotExistError(path) => StoreError::DirectoryDoesNotExistError(path),
ErrorImpl::EnvironmentDoesNotExistError(path) => StoreError::EnvironmentDoesNotExistError(path),
ErrorImpl::UnsuitableEnvironmentPath(path) => StoreError::UnsuitableEnvironmentPath(path),
ErrorImpl::IoError(error) => StoreError::IoError(error),
}
}
}
impl From<io::Error> for ErrorImpl {
fn from(e: io::Error) -> ErrorImpl {
ErrorImpl::IoError(e)
}
}

@ -55,7 +55,6 @@ pub struct EnvironmentBuilderImpl {
max_dbs: Option<usize>,
map_size: Option<usize>,
make_dir: bool,
check_env_exists: bool,
}
impl<'b> BackendEnvironmentBuilder<'b> for EnvironmentBuilderImpl {
@ -70,7 +69,6 @@ impl<'b> BackendEnvironmentBuilder<'b> for EnvironmentBuilderImpl {
max_dbs: None,
map_size: None,
make_dir: false,
check_env_exists: false,
}
}
@ -102,18 +100,12 @@ impl<'b> BackendEnvironmentBuilder<'b> for EnvironmentBuilderImpl {
self
}
fn set_check_if_env_exists(&mut self, check_env_exists: bool) -> &mut Self {
self.check_env_exists = check_env_exists;
self
}
fn open(&self, path: &Path) -> Result<Self::Environment, Self::Error> {
if self.check_env_exists && !path.join(DEFAULT_DB_FILENAME).exists() {
return Err(ErrorImpl::EnvironmentDoesNotExistError(path.into()));
}
// Technically NO_SUB_DIR should change these checks here, but they're both currently
// unimplemented with this storage backend.
if !path.is_dir() {
if !self.make_dir {
return Err(ErrorImpl::DirectoryDoesNotExistError(path.into()));
return Err(ErrorImpl::UnsuitableEnvironmentPath(path.into()));
}
fs::create_dir_all(path)?;
}
@ -286,4 +278,12 @@ impl<'e> BackendEnvironment<'e> for EnvironmentImpl {
warn!("`set_map_size({})` is ignored by this storage backend.", size);
Ok(())
}
fn get_files_on_disk(&self) -> Vec<PathBuf> {
// Technically NO_SUB_DIR and NO_LOCK should change this output, but
// they're both currently unimplemented with this storage backend.
let mut db_filename = self.path.clone();
db_filename.push(DEFAULT_DB_FILENAME);
return vec![db_filename];
}
}

@ -29,8 +29,7 @@ pub enum ErrorImpl {
DbsIllegalOpen,
DbNotFoundError,
DbIsForeignError,
DirectoryDoesNotExistError(PathBuf),
EnvironmentDoesNotExistError(PathBuf),
UnsuitableEnvironmentPath(PathBuf),
IoError(io::Error),
BincodeError(BincodeError),
}
@ -46,8 +45,7 @@ impl fmt::Display for ErrorImpl {
ErrorImpl::DbsIllegalOpen => write!(fmt, "DbIllegalOpen (safe mode)"),
ErrorImpl::DbNotFoundError => write!(fmt, "DbNotFoundError (safe mode)"),
ErrorImpl::DbIsForeignError => write!(fmt, "DbIsForeignError (safe mode)"),
ErrorImpl::DirectoryDoesNotExistError(_) => write!(fmt, "DirectoryDoesNotExistError (safe mode)"),
ErrorImpl::EnvironmentDoesNotExistError(_) => write!(fmt, "EnvironmentDoesNotExistError (safe mode)"),
ErrorImpl::UnsuitableEnvironmentPath(_) => write!(fmt, "UnsuitableEnvironmentPath (safe mode)"),
ErrorImpl::IoError(e) => e.fmt(fmt),
ErrorImpl::BincodeError(e) => e.fmt(fmt),
}
@ -64,8 +62,7 @@ impl Into<StoreError> for ErrorImpl {
ErrorImpl::KeyValuePairNotFound => StoreError::KeyValuePairNotFound,
ErrorImpl::BincodeError(_) => StoreError::FileInvalid,
ErrorImpl::DbsFull => StoreError::DbsFull,
ErrorImpl::DirectoryDoesNotExistError(path) => StoreError::DirectoryDoesNotExistError(path),
ErrorImpl::EnvironmentDoesNotExistError(path) => StoreError::EnvironmentDoesNotExistError(path),
ErrorImpl::UnsuitableEnvironmentPath(path) => StoreError::UnsuitableEnvironmentPath(path),
ErrorImpl::IoError(error) => StoreError::IoError(error),
_ => StoreError::SafeModeError(self),
}

@ -13,7 +13,10 @@ use std::{
Debug,
Display,
},
path::Path,
path::{
Path,
PathBuf,
},
};
use crate::{
@ -90,8 +93,6 @@ pub trait BackendEnvironmentBuilder<'b>: Debug + Eq + PartialEq + Copy + Clone {
fn set_make_dir_if_needed(&mut self, make_dir: bool) -> &mut Self;
fn set_check_if_env_exists(&mut self, check_env: bool) -> &mut Self;
fn open(&self, path: &Path) -> Result<Self::Environment, Self::Error>;
}
@ -125,6 +126,8 @@ pub trait BackendEnvironment<'e>: Debug {
fn load_ratio(&self) -> Result<Option<f32>, Self::Error>;
fn set_map_size(&self, size: usize) -> Result<(), Self::Error>;
fn get_files_on_disk(&self) -> Vec<PathBuf>;
}
pub trait BackendRoTransaction: Debug {

@ -9,6 +9,7 @@
// specific language governing permissions and limitations under the License.
use std::{
fs,
os::raw::c_uint,
path::{
Path,
@ -308,4 +309,18 @@ where
pub fn set_map_size(&self, size: usize) -> Result<(), StoreError> {
self.env.set_map_size(size).map_err(Into::into)
}
/// Closes this environment and deletes all its files from disk. Doesn't delete the
/// folder used when opening the environment.
pub fn close_and_delete(self) -> Result<(), StoreError> {
let files = self.env.get_files_on_disk();
self.sync(true)?;
drop(self);
for file in files {
fs::remove_file(file)?;
}
Ok(())
}
}

@ -12,6 +12,7 @@ use std::{
io,
path::PathBuf,
str,
sync,
thread,
thread::ThreadId,
};
@ -56,6 +57,9 @@ impl From<Box<bincode::ErrorKind>> for DataError {
#[derive(Debug, Fail)]
pub enum StoreError {
#[fail(display = "manager poisoned")]
ManagerPoisonError,
#[fail(display = "database corrupted")]
DatabaseCorrupted,
@ -80,11 +84,8 @@ pub enum StoreError {
#[fail(display = "I/O error: {:?}", _0)]
IoError(io::Error),
#[fail(display = "directory does not exist or not a directory: {:?}", _0)]
DirectoryDoesNotExistError(PathBuf),
#[fail(display = "environment does not exist in directory: {:?}", _0)]
EnvironmentDoesNotExistError(PathBuf),
#[fail(display = "environment path does not exist or not the right type: {:?}", _0)]
UnsuitableEnvironmentPath(PathBuf),
#[fail(display = "data error: {:?}", _0)]
DataError(DataError),
@ -124,11 +125,20 @@ impl From<io::Error> for StoreError {
}
}
impl<T> From<sync::PoisonError<T>> for StoreError {
fn from(_: sync::PoisonError<T>) -> StoreError {
StoreError::ManagerPoisonError
}
}
#[derive(Debug, Fail)]
pub enum MigrateError {
#[fail(display = "store error: {}", _0)]
StoreError(StoreError),
#[fail(display = "manager poisoned")]
ManagerPoisonError,
#[fail(display = "source is empty")]
SourceEmpty,
@ -141,3 +151,9 @@ impl From<StoreError> for MigrateError {
MigrateError::StoreError(e)
}
}
impl<T> From<sync::PoisonError<T>> for MigrateError {
fn from(_: sync::PoisonError<T>) -> MigrateError {
MigrateError::ManagerPoisonError
}
}

@ -39,8 +39,8 @@ where
let canonical = path.into().canonicalize()?;
Ok(if cfg!(target_os = "windows") {
let url = Url::from_file_path(&canonical).map_err(|_| io::Error::new(io::ErrorKind::Other, "passing error"))?;
url.to_file_path().map_err(|_| io::Error::new(io::ErrorKind::Other, "path canonicalization error"))?
let map_err = |_| io::Error::new(io::ErrorKind::Other, "path canonicalization error");
Url::from_file_path(&canonical).and_then(|url| url.to_file_path()).map_err(map_err)?
} else {
canonical
})

@ -225,6 +225,7 @@ pub use error::{
StoreError,
};
pub use manager::Manager;
pub use migrator::Migrator;
pub use readwrite::{
Readable,
Reader,

@ -29,6 +29,8 @@ use lazy_static::lazy_static;
use crate::{
backend::{
BackendEnvironment,
BackendEnvironmentBuilder,
LmdbEnvironment,
SafeModeEnvironment,
},
@ -51,7 +53,10 @@ pub struct Manager<E> {
environments: BTreeMap<PathBuf, SharedRkv<E>>,
}
impl<E> Manager<E> {
impl<'e, E> Manager<E>
where
E: BackendEnvironment<'e>,
{
fn new() -> Manager<E> {
Manager {
environments: Default::default(),
@ -98,6 +103,44 @@ impl<E> Manager<E> {
},
})
}
/// Return a new Rkv environment from the builder, or create it by calling `f`.
pub fn get_or_create_from_builder<'p, F, P, B>(&mut self, path: P, builder: B, f: F) -> Result<SharedRkv<E>>
where
F: FnOnce(&Path, B) -> Result<Rkv<E>>,
P: Into<&'p Path>,
B: BackendEnvironmentBuilder<'e, Environment = E>,
{
let canonical = canonicalize_path(path)?;
Ok(match self.environments.entry(canonical) {
Entry::Occupied(e) => e.get().clone(),
Entry::Vacant(e) => {
let k = Arc::new(RwLock::new(f(e.key().as_path(), builder)?));
e.insert(k).clone()
},
})
}
/// Tries to close the specified environment and delete all its files from disk.
/// Doesn't delete the folder used when opening the environment.
/// This will only work if there's no other users of this environment.
pub fn try_close_and_delete<'p, P>(&mut self, path: P) -> Result<()>
where
P: Into<&'p Path>,
{
let canonical = canonicalize_path(path)?;
match self.environments.entry(canonical) {
Entry::Vacant(_) => {}, // noop
Entry::Occupied(e) => {
if Arc::strong_count(e.get()) == 1 {
if let Ok(env) = Arc::try_unwrap(e.remove()) {
env.into_inner()?.close_and_delete()?;
}
}
},
}
Ok(())
}
}
impl Manager<LmdbEnvironment> {

@ -47,8 +47,9 @@ pub use crate::backend::{
};
// FIXME: should parametrize this instead.
macro_rules! fn_migrator {
($name:tt, $src:ty, $dst:ty) => {
($name:tt, $src_env:ty, $dst_env:ty) => {
/// Migrate all data in all of databases from the source environment to the destination
/// 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.
@ -57,7 +58,11 @@ macro_rules! fn_migrator {
/// the given environments.
///
/// The destination environment should be empty of data, otherwise an error is returned.
pub fn $name(src_env: &Rkv<$src>, dst_env: &Rkv<$dst>) -> Result<(), MigrateError> {
pub fn $name<S, D>(src_env: S, dst_env: D) -> Result<(), MigrateError>
where
S: std::ops::Deref<Target = Rkv<$src_env>>,
D: std::ops::Deref<Target = Rkv<$dst_env>>,
{
let src_dbs = src_env.get_dbs().unwrap();
if src_dbs.is_empty() {
return Err(MigrateError::SourceEmpty);
@ -80,12 +85,54 @@ macro_rules! fn_migrator {
Ok(())
}
};
($migrate:tt, $name:tt, $builder:tt, $src_env:ty, $dst_env:ty) => {
/// Same as the other migration methods, but automatically attempts to open the source
/// environment, ignores it if it doesn't exist or if it's empty, and finally attempts to
/// delete all of its supporting files.
pub fn $name<F, D>(path: &std::path::Path, build: F, dst_env: D) -> Result<(), MigrateError>
where
F: FnOnce(crate::backend::$builder) -> crate::backend::$builder,
D: std::ops::Deref<Target = Rkv<$dst_env>>,
{
use crate::backend::*;
let mut manager = crate::Manager::<$src_env>::singleton().write()?;
let mut builder = Rkv::<$src_env>::environment_builder::<$builder>();
builder.set_max_dbs(crate::env::DEFAULT_MAX_DBS);
builder = build(builder);
let src_env = manager.get_or_create_from_builder(path, builder, Rkv::from_builder::<$builder>)?;
match Migrator::$migrate(src_env.read()?, dst_env) {
Err(crate::MigrateError::SourceEmpty) => return Ok(()),
result => result,
}?;
drop(src_env);
manager.try_close_and_delete(path)?;
Ok(())
}
};
}
macro_rules! fns_migrator {
($src:tt, $dst:tt) => {
paste::item! {
fns_migrator!([<migrate_ $src _to_ $dst>], $src, $dst);
fns_migrator!([<migrate_ $dst _to_ $src>], $dst, $src);
}
};
($name:tt, $src:tt, $dst:tt) => {
paste::item! {
fn_migrator!($name, [<$src:camel Environment>], [<$dst:camel Environment>]);
fn_migrator!($name, [<auto_ $name>], [<$src:camel>], [<$src:camel Environment>], [<$dst:camel Environment>]);
}
};
}
pub struct Migrator;
impl Migrator {
fn_migrator!(migrate_lmdb_to_safe_mode, LmdbEnvironment, SafeModeEnvironment);
fn_migrator!(migrate_safe_mode_to_lmdb, SafeModeEnvironment, LmdbEnvironment);
fns_migrator!(lmdb, safe_mode);
}

@ -72,7 +72,7 @@ fn test_open_fails() {
let pb = nope.to_path_buf();
match Rkv::new::<Lmdb>(nope.as_path()).err() {
Some(StoreError::DirectoryDoesNotExistError(p)) => {
Some(StoreError::UnsuitableEnvironmentPath(p)) => {
assert_eq!(pb, p);
},
_ => panic!("expected error"),
@ -104,6 +104,61 @@ fn test_open_from_builder() {
check_rkv(&k);
}
#[test]
fn test_open_from_builder_with_no_subdir_1() {
let root = Builder::new().prefix("test_open_from_builder").tempdir().expect("tempdir");
println!("Root path: {:?}", root.path());
fs::create_dir_all(root.path()).expect("dir created");
assert!(root.path().is_dir());
{
let mut builder = Rkv::environment_builder::<Lmdb>();
builder.set_max_dbs(2);
let k = Rkv::from_builder(root.path(), builder).expect("rkv");
check_rkv(&k);
}
{
let mut builder = Rkv::environment_builder::<Lmdb>();
builder.set_flags(EnvironmentFlags::NO_SUB_DIR);
builder.set_max_dbs(2);
let mut datamdb = root.path().to_path_buf();
datamdb.push("data.mdb");
let k = Rkv::from_builder(&datamdb, builder).expect("rkv");
check_rkv(&k);
}
}
#[test]
#[should_panic(expected = "rkv: UnsuitableEnvironmentPath")]
fn test_open_from_builder_with_no_subdir_2() {
let root = Builder::new().prefix("test_open_from_builder").tempdir().expect("tempdir");
println!("Root path: {:?}", root.path());
fs::create_dir_all(root.path()).expect("dir created");
assert!(root.path().is_dir());
{
let mut builder = Rkv::environment_builder::<Lmdb>();
builder.set_max_dbs(2);
let k = Rkv::from_builder(root.path(), builder).expect("rkv");
check_rkv(&k);
}
{
let mut builder = Rkv::environment_builder::<Lmdb>();
builder.set_flags(EnvironmentFlags::NO_SUB_DIR);
builder.set_max_dbs(2);
let mut datamdb = root.path().to_path_buf();
datamdb.push("bogus.mdb");
let k = Rkv::from_builder(&datamdb, builder).expect("rkv");
check_rkv(&k);
}
}
#[test]
fn test_open_from_builder_with_dir_1() {
let root = Builder::new().prefix("test_open_from_builder").tempdir().expect("tempdir");
@ -118,7 +173,7 @@ fn test_open_from_builder_with_dir_1() {
}
#[test]
#[should_panic(expected = "rkv: DirectoryDoesNotExistError(\"bogus\")")]
#[should_panic(expected = "rkv: UnsuitableEnvironmentPath(\"bogus\")")]
fn test_open_from_builder_with_dir_2() {
let root = Path::new("bogus");
println!("Root path: {:?}", root);

@ -14,11 +14,10 @@ use tempfile::Builder;
use rkv::{
backend::{
BackendEnvironmentBuilder,
Lmdb,
SafeMode,
},
migrator::Migrator,
Migrator,
Rkv,
StoreOptions,
Value,
@ -36,13 +35,149 @@ macro_rules! populate_store {
}
#[test]
#[should_panic(expected = "new succeeded: EnvironmentDoesNotExistError")]
fn test_migrator_lmdb_to_safe_0() {
let mut builder = Lmdb::new();
builder.set_check_if_env_exists(true);
fn test_simple_migrator_lmdb_to_safe() {
let root = Builder::new().prefix("test_simple_migrator_lmdb_to_safe").tempdir().expect("tempdir");
fs::create_dir_all(root.path()).expect("dir created");
let root = Builder::new().prefix("test_migrate_lmdb_to_safe").tempdir().expect("tempdir");
let _ = Rkv::from_builder::<Lmdb>(root.path(), builder).expect("new succeeded");
// Populate source environment and persist to disk.
{
let src_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
populate_store!(&src_env);
src_env.sync(true).expect("synced");
}
// Check if the files were written to disk.
{
let mut datamdb = root.path().to_path_buf();
let mut lockmdb = root.path().to_path_buf();
datamdb.push("data.mdb");
lockmdb.push("lock.mdb");
assert!(datamdb.exists());
assert!(lockmdb.exists());
}
// Verify that database was written to disk.
{
let src_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
let store = src_env.open_single("store", StoreOptions::default()).expect("opened");
let reader = src_env.read().expect("reader");
assert_eq!(store.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
assert_eq!(store.get(&reader, "bar").expect("read"), Some(Value::Bool(true)));
assert_eq!(store.get(&reader, "baz").expect("read"), Some(Value::Str("héllo, yöu")));
}
// Easy migrate.
{
let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
Migrator::auto_migrate_lmdb_to_safe_mode(root.path(), |builder| builder, &dst_env).expect("migrated");
}
// Verify that the database was indeed migrated.
{
let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
let store = dst_env.open_single("store", StoreOptions::default()).expect("opened");
let reader = dst_env.read().expect("reader");
assert_eq!(store.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
assert_eq!(store.get(&reader, "bar").expect("read"), Some(Value::Bool(true)));
assert_eq!(store.get(&reader, "baz").expect("read"), Some(Value::Str("héllo, yöu")));
}
// Check if the old files were deleted from disk.
{
let mut datamdb = root.path().to_path_buf();
let mut lockmdb = root.path().to_path_buf();
datamdb.push("data.mdb");
lockmdb.push("lock.mdb");
assert!(!datamdb.exists());
assert!(!lockmdb.exists());
}
}
#[test]
fn test_simple_migrator_safe_to_lmdb() {
let root = Builder::new().prefix("test_simple_migrator_safe_to_lmdb").tempdir().expect("tempdir");
fs::create_dir_all(root.path()).expect("dir created");
// Populate source environment and persist to disk.
{
let src_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
populate_store!(&src_env);
src_env.sync(true).expect("synced");
}
// Check if the files were written to disk.
{
let mut safebin = root.path().to_path_buf();
safebin.push("data.safe.bin");
assert!(safebin.exists());
}
// Verify that database was written to disk.
{
let src_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
let store = src_env.open_single("store", StoreOptions::default()).expect("opened");
let reader = src_env.read().expect("reader");
assert_eq!(store.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
assert_eq!(store.get(&reader, "bar").expect("read"), Some(Value::Bool(true)));
assert_eq!(store.get(&reader, "baz").expect("read"), Some(Value::Str("héllo, yöu")));
}
// Easy migrate.
{
let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
Migrator::auto_migrate_safe_mode_to_lmdb(root.path(), |builder| builder, &dst_env).expect("migrated");
}
// Verify that the database was indeed migrated.
{
let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
let store = dst_env.open_single("store", StoreOptions::default()).expect("opened");
let reader = dst_env.read().expect("reader");
assert_eq!(store.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
assert_eq!(store.get(&reader, "bar").expect("read"), Some(Value::Bool(true)));
assert_eq!(store.get(&reader, "baz").expect("read"), Some(Value::Str("héllo, yöu")));
}
// Check if the old files were deleted from disk.
{
let mut safebin = root.path().to_path_buf();
safebin.push("data.safe.bin");
assert!(!safebin.exists());
}
}
#[test]
fn test_migrator_round_trip() {
let root = Builder::new().prefix("test_simple_migrator_lmdb_to_safe").tempdir().expect("tempdir");
fs::create_dir_all(root.path()).expect("dir created");
// Populate source environment and persist to disk.
{
let src_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
populate_store!(&src_env);
src_env.sync(true).expect("synced");
}
// Easy migrate.
{
let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
Migrator::auto_migrate_lmdb_to_safe_mode(root.path(), |builder| builder, &dst_env).expect("migrated");
}
// Easy migrate back.
{
let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
Migrator::auto_migrate_safe_mode_to_lmdb(root.path(), |builder| builder, &dst_env).expect("migrated");
}
// Verify that the database was indeed migrated twice.
{
let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
let store = dst_env.open_single("store", StoreOptions::default()).expect("opened");
let reader = dst_env.read().expect("reader");
assert_eq!(store.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
assert_eq!(store.get(&reader, "bar").expect("read"), Some(Value::Bool(true)));
assert_eq!(store.get(&reader, "baz").expect("read"), Some(Value::Str("héllo, yöu")));
}
// Check if the right files are finally present on disk.
{
let mut datamdb = root.path().to_path_buf();
let mut lockmdb = root.path().to_path_buf();
let mut safebin = root.path().to_path_buf();
datamdb.push("data.mdb");
lockmdb.push("lock.mdb");
safebin.push("data.safe.bin");
assert!(datamdb.exists());
assert!(lockmdb.exists());
assert!(!safebin.exists());
}
}
#[test]
@ -86,16 +221,6 @@ fn test_migrator_lmdb_to_safe_3() {
assert_eq!(store.get(&reader, "baz").expect("read"), Some(Value::Str("héllo, yöu")));
}
#[test]
#[should_panic(expected = "new succeeded: EnvironmentDoesNotExistError")]
fn test_migrator_safe_to_lmdb_0() {
let mut builder = SafeMode::new();
builder.set_check_if_env_exists(true);
let root = Builder::new().prefix("test_migrate_safe_to_lmdb").tempdir().expect("tempdir");
let _ = Rkv::from_builder::<SafeMode>(root.path(), builder).expect("new succeeded");
}
#[test]
#[should_panic(expected = "migrated: SourceEmpty")]
fn test_migrator_safe_to_lmdb_1() {

@ -66,7 +66,7 @@ fn test_open_fails_safe() {
let pb = nope.to_path_buf();
match Rkv::new::<SafeMode>(nope.as_path()).err() {
Some(StoreError::DirectoryDoesNotExistError(p)) => {
Some(StoreError::UnsuitableEnvironmentPath(p)) => {
assert_eq!(pb, p);
},
_ => panic!("expected error"),
@ -112,7 +112,7 @@ fn test_open_from_builder_with_dir_safe_1() {
}
#[test]
#[should_panic(expected = "rkv: DirectoryDoesNotExistError(\"bogus\")")]
#[should_panic(expected = "rkv: UnsuitableEnvironmentPath(\"bogus\")")]
fn test_open_from_builder_with_dir_safe_2() {
let root = Path::new("bogus");
println!("Root path: {:?}", root);

Loading…
Cancel
Save