use std::fs::create_dir_all;
use std::path::{Path, PathBuf};
use bytesize::ByteSize;
use heed::{CompactionOption, Env, EnvOpenOptions, WithoutTls};
use super::lmdb_error::LmdbLayerError;
pub type LmdbEnv = Env<WithoutTls>;
const MAX_READERS: u32 = 1024;
const MAX_DBS: u32 = 1;
const LMDB_DATA_FILE: &str = "data.mdb";
fn build_options(map_size: ByteSize) -> Result<EnvOpenOptions<WithoutTls>, LmdbLayerError> {
let size = usize::try_from(map_size.as_u64())
.map_err(|_| LmdbLayerError::MapSizeUnrepresentable(map_size))?;
let aligned = size
.checked_next_multiple_of(page_size::get())
.ok_or(LmdbLayerError::MapSizeUnrepresentable(map_size))?;
let mut options = EnvOpenOptions::new().read_txn_without_tls();
options.map_size(aligned);
options.max_dbs(MAX_DBS);
options.max_readers(MAX_READERS);
Ok(options)
}
pub(in crate::lmdb) fn open_lmdb_env(
dir: &Path,
map_size: ByteSize,
) -> Result<LmdbEnv, LmdbLayerError> {
create_dir_all(dir).map_err(|source| LmdbLayerError::CreateDir {
path: dir.to_path_buf(),
source,
})?;
let options = build_options(map_size)?;
let lmdb_env = unsafe { options.open(dir) }.map_err(|source| LmdbLayerError::Open {
path: dir.to_path_buf(),
source,
})?;
Ok(lmdb_env)
}
pub(crate) fn lmdb_env_exists(dir: &Path) -> bool {
dir.join(LMDB_DATA_FILE).exists()
}
pub(in crate::lmdb) fn copy_lmdb_env_to_dir(
lmdb_env: &LmdbEnv,
dst_dir: &Path,
) -> Result<PathBuf, LmdbLayerError> {
create_dir_all(dst_dir).map_err(|source| LmdbLayerError::CreateDir {
path: dst_dir.to_path_buf(),
source,
})?;
let dst = dst_dir.join(LMDB_DATA_FILE);
lmdb_env
.copy_to_path(&dst, CompactionOption::Disabled)
.map_err(|source| LmdbLayerError::Copy {
dst: dst.clone(),
source,
})?;
Ok(dst)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lmdb::lmdb_db::LmdbDb;
use crate::lmdb::txn::with_write_txn;
#[test]
fn open_lmdb_env_rounds_unaligned_map_size_up_to_page_size() {
let dir = tempfile::tempdir().expect("create temp dir");
open_lmdb_env(dir.path(), ByteSize::kb(100)).expect("open env with unaligned map size");
}
#[test]
fn exhausting_the_fixed_map_is_detectable_as_map_full() {
let dir = tempfile::tempdir().expect("create temp dir");
let lmdb_env = open_lmdb_env(dir.path(), ByteSize::kib(256)).expect("open env");
let db =
with_write_txn(&lmdb_env, |txn| LmdbDb::open(&lmdb_env, txn, "data")).expect("open db");
let result: Result<(), LmdbLayerError> = with_write_txn(&lmdb_env, |txn| {
let value = vec![0_u8; 32 * 1024];
for i in 0..10_000_u32 {
db.put(txn, &i.to_be_bytes(), &value)?;
}
Ok(())
});
let err = result.expect_err("a 256 KiB map should fill within a few 32 KiB writes");
assert!(err.is_map_full(), "expected map-full, got: {err}");
}
}