#![forbid(unsafe_code)]
#![warn(missing_docs)]
use std::io::Read as _;
use std::path::Path;
pub use oxistore_core::{
expiry_epoch_millis, is_expired, prefix_upper_bound, BlobStore, BoxKvStore, ColumnarStore,
KeysIter, KvSnapshot, KvStore, KvTxn, RangeItem, RangeIter, StoreConfig, StoreError,
StoreMetrics,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StoreKind {
Redb,
Sled,
Fjall,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Backend {
KvRedb,
KvSled,
KvFjall,
Columnar,
BlobLocal,
BlobMemory,
Cache,
}
impl From<StoreKind> for Backend {
fn from(kind: StoreKind) -> Self {
match kind {
StoreKind::Redb => Backend::KvRedb,
StoreKind::Sled => Backend::KvSled,
StoreKind::Fjall => Backend::KvFjall,
}
}
}
#[must_use = "the store must be used; dropping it immediately is likely a bug"]
pub fn open(path: impl AsRef<Path>) -> Result<BoxKvStore, StoreError> {
open_with(StoreKind::Redb, path)
}
#[must_use = "the store must be used; dropping it immediately is likely a bug"]
pub fn open_with(kind: StoreKind, path: impl AsRef<Path>) -> Result<BoxKvStore, StoreError> {
open_with_inner(kind, path.as_ref())
}
#[must_use = "the in-memory store must be used; dropping it immediately is likely a bug"]
pub fn open_in_memory(kind: StoreKind) -> Result<BoxKvStore, StoreError> {
match kind {
StoreKind::Redb => open_redb_in_memory(),
StoreKind::Sled => open_sled_in_memory(),
StoreKind::Fjall => {
let dir = std::env::temp_dir().join(format!(
"oxistore_fjall_mem_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0)
));
open_fjall(&dir)
}
}
}
fn open_with_inner(kind: StoreKind, path: &Path) -> Result<BoxKvStore, StoreError> {
match kind {
StoreKind::Redb => open_redb(path),
StoreKind::Sled => open_sled(path),
StoreKind::Fjall => open_fjall(path),
}
}
#[cfg(feature = "kv-redb")]
fn open_redb(path: &Path) -> Result<BoxKvStore, StoreError> {
oxistore_kv_redb::RedbStore::open(path).map(|s| Box::new(s) as BoxKvStore)
}
#[cfg(not(feature = "kv-redb"))]
fn open_redb(_path: &Path) -> Result<BoxKvStore, StoreError> {
Err(StoreError::Other("kv-redb feature not enabled".to_string()))
}
#[cfg(feature = "kv-redb")]
fn open_redb_in_memory() -> Result<BoxKvStore, StoreError> {
oxistore_kv_redb::RedbStore::open_in_memory().map(|s| Box::new(s) as BoxKvStore)
}
#[cfg(not(feature = "kv-redb"))]
fn open_redb_in_memory() -> Result<BoxKvStore, StoreError> {
Err(StoreError::Other("kv-redb feature not enabled".to_string()))
}
#[cfg(feature = "kv-sled")]
fn open_sled(path: &Path) -> Result<BoxKvStore, StoreError> {
oxistore_kv_sled::SledStore::open(path).map(|s| Box::new(s) as BoxKvStore)
}
#[cfg(not(feature = "kv-sled"))]
fn open_sled(_path: &Path) -> Result<BoxKvStore, StoreError> {
Err(StoreError::Other("kv-sled feature not enabled".to_string()))
}
#[cfg(feature = "kv-sled")]
fn open_sled_in_memory() -> Result<BoxKvStore, StoreError> {
oxistore_kv_sled::SledStore::open_temporary().map(|s| Box::new(s) as BoxKvStore)
}
#[cfg(not(feature = "kv-sled"))]
fn open_sled_in_memory() -> Result<BoxKvStore, StoreError> {
Err(StoreError::Other("kv-sled feature not enabled".to_string()))
}
#[cfg(feature = "kv-fjall")]
fn open_fjall(path: &Path) -> Result<BoxKvStore, StoreError> {
oxistore_kv_fjall::FjallStore::open(path)
.map(|s| Box::new(s) as BoxKvStore)
.map_err(|e| StoreError::Other(e.to_string()))
}
#[cfg(not(feature = "kv-fjall"))]
fn open_fjall(_path: &Path) -> Result<BoxKvStore, StoreError> {
Err(StoreError::Other(
"kv-fjall feature not enabled".to_string(),
))
}
#[cfg(feature = "kv-redb")]
pub mod kv_redb {
pub use oxistore_kv_redb::RedbStore;
}
#[cfg(feature = "kv-sled")]
pub mod kv_sled {
pub use oxistore_kv_sled::SledStore;
}
#[cfg(feature = "kv-fjall")]
pub mod kv_fjall {
pub use oxistore_kv_fjall::FjallStore;
}
#[cfg(feature = "columnar")]
pub mod columnar {
pub use oxistore_columnar::*;
}
#[cfg(feature = "cache")]
pub mod cache {
pub use oxistore_cache::*;
}
#[cfg(feature = "blob")]
pub mod blob {
pub use oxistore_blob::{
sha256, sha256_streaming, BlobError, BlobMeta, BlobStore, BlobStoreBuilder, ChunkedUpload,
Digest, LocalBlobStore, MemoryBlobStore,
};
}
#[cfg(feature = "encrypt")]
pub mod encrypt {
pub use oxistore_encrypt::*;
}
#[cfg(feature = "compress")]
pub mod compress {
pub use oxistore_compress::{CompressError, OxiArcCodec};
}
const REDB_MAGIC: [u8; 9] = [b'r', b'e', b'd', b'b', 0x1A, 0x0A, 0xA9, 0x0D, 0x0A];
#[must_use = "the detected backend kind should be used; ignoring it is likely a mistake"]
pub fn detect_backend(path: impl AsRef<Path>) -> Result<StoreKind, StoreError> {
let path = path.as_ref();
if path.is_file() {
let mut buf = [0u8; 9];
let mut file =
std::fs::File::open(path).map_err(|e| StoreError::Io(std::sync::Arc::new(e)))?;
let n = file
.read(&mut buf)
.map_err(|e| StoreError::Io(std::sync::Arc::new(e)))?;
if n >= REDB_MAGIC.len() && buf == REDB_MAGIC {
return Ok(StoreKind::Redb);
}
return Err(StoreError::Other(format!(
"unrecognized file format at {}",
path.display()
)));
}
if path.is_dir() {
if path.join("conf").exists() {
return Ok(StoreKind::Sled);
}
return Ok(StoreKind::Fjall);
}
Err(StoreError::Other(format!(
"path does not exist: {}",
path.display()
)))
}
pub fn destroy(kind: StoreKind, path: impl AsRef<Path>) -> Result<(), StoreError> {
let path = path.as_ref();
match kind {
StoreKind::Redb => {
if path.exists() && path.is_file() {
std::fs::remove_file(path).map_err(|e| StoreError::Io(std::sync::Arc::new(e)))?;
}
}
StoreKind::Sled | StoreKind::Fjall => {
if path.exists() && path.is_dir() {
std::fs::remove_dir_all(path)
.map_err(|e| StoreError::Io(std::sync::Arc::new(e)))?;
}
}
}
Ok(())
}
pub fn backup_store(
kind: StoreKind,
src: impl AsRef<Path>,
dst: impl AsRef<Path>,
) -> Result<(), StoreError> {
let store = open_with(kind, src)?;
store.backup(dst.as_ref())
}
pub fn restore_store(
kind: StoreKind,
backup_path: impl AsRef<Path>,
dst: impl AsRef<Path>,
) -> Result<(), StoreError> {
let store = open_with(kind, dst)?;
store.restore(backup_path.as_ref())
}
#[must_use = "the blob store must be used; dropping it immediately is likely a bug"]
#[cfg(feature = "blob")]
pub fn open_blob(path: impl AsRef<Path>) -> Result<oxistore_blob::LocalBlobStore, StoreError> {
Ok(oxistore_blob::LocalBlobStore::new(path.as_ref()))
}
#[must_use = "the columnar table must be used; dropping it immediately is likely a bug"]
#[cfg(feature = "columnar")]
pub fn open_columnar(
path: impl AsRef<Path>,
) -> Result<oxistore_columnar::ColumnarTable, StoreError> {
oxistore_columnar::ColumnarTable::read_from(path.as_ref())
.map_err(|e| StoreError::Other(e.to_string()))
}
#[cfg(all(
feature = "cache",
any(feature = "kv-redb", feature = "kv-sled", feature = "kv-fjall")
))]
pub type CachedKvStore =
oxistore_cache::CacheableKvStore<BoxStoreAdapter, oxistore_cache::LruCache<Vec<u8>, Vec<u8>>>;
#[must_use = "the cached store must be used; dropping it immediately is likely a bug"]
#[cfg(all(
feature = "cache",
any(feature = "kv-redb", feature = "kv-sled", feature = "kv-fjall")
))]
pub fn open_cached(
kind: StoreKind,
path: impl AsRef<Path>,
cache_cap: usize,
) -> Result<CachedKvStore, StoreError> {
let inner = open_with_inner(kind, path.as_ref())?;
let adapter = BoxStoreAdapter { inner };
let cache = oxistore_cache::LruCache::new(cache_cap);
Ok(oxistore_cache::CacheableKvStore::new(adapter, cache))
}
#[must_use = "the store must be used; dropping it immediately is likely a bug"]
pub fn open_config(path: impl AsRef<Path>, config: StoreConfig) -> Result<BoxKvStore, StoreError> {
open_config_inner(path.as_ref(), config)
}
fn open_config_inner(path: &Path, config: StoreConfig) -> Result<BoxKvStore, StoreError> {
if config.read_only {
open_read_only_inner(path)
} else {
open_redb(path)
}
}
#[must_use = "the store must be used; dropping it immediately is likely a bug"]
pub fn open_read_only(path: impl AsRef<Path>) -> Result<BoxKvStore, StoreError> {
open_read_only_inner(path.as_ref())
}
#[cfg(feature = "kv-redb")]
fn open_read_only_inner(path: &Path) -> Result<BoxKvStore, StoreError> {
let inner = open_redb(path)?;
Ok(Box::new(ReadOnlyStore { inner }) as BoxKvStore)
}
#[cfg(not(feature = "kv-redb"))]
fn open_read_only_inner(_path: &Path) -> Result<BoxKvStore, StoreError> {
Err(StoreError::Other("kv-redb feature not enabled".to_string()))
}
struct ReadOnlyStore {
inner: BoxKvStore,
}
impl KvStore for ReadOnlyStore {
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError> {
self.inner.get(key)
}
fn put(&self, _key: &[u8], _value: &[u8]) -> Result<(), StoreError> {
Err(StoreError::ReadOnly)
}
fn delete(&self, _key: &[u8]) -> Result<(), StoreError> {
Err(StoreError::ReadOnly)
}
fn contains(&self, key: &[u8]) -> Result<bool, StoreError> {
self.inner.contains(key)
}
fn range<'a>(
&'a self,
lo: &[u8],
hi: &[u8],
) -> Result<oxistore_core::RangeIter<'a>, StoreError> {
self.inner.range(lo, hi)
}
fn iter<'a>(&'a self) -> Result<oxistore_core::RangeIter<'a>, StoreError> {
self.inner.iter()
}
fn transaction(&self) -> Result<Box<dyn oxistore_core::KvTxn + '_>, StoreError> {
Err(StoreError::ReadOnly)
}
fn snapshot(&self) -> Result<Box<dyn oxistore_core::KvSnapshot + '_>, StoreError> {
self.inner.snapshot()
}
fn flush(&self) -> Result<(), StoreError> {
self.inner.flush()
}
}
#[cfg(all(
feature = "cache",
any(feature = "kv-redb", feature = "kv-sled", feature = "kv-fjall")
))]
pub struct BoxStoreAdapter {
inner: BoxKvStore,
}
#[cfg(all(
feature = "cache",
any(feature = "kv-redb", feature = "kv-sled", feature = "kv-fjall")
))]
impl KvStore for BoxStoreAdapter {
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError> {
self.inner.get(key)
}
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), StoreError> {
self.inner.put(key, value)
}
fn delete(&self, key: &[u8]) -> Result<(), StoreError> {
self.inner.delete(key)
}
fn contains(&self, key: &[u8]) -> Result<bool, StoreError> {
self.inner.contains(key)
}
fn range<'a>(
&'a self,
lo: &[u8],
hi: &[u8],
) -> Result<oxistore_core::RangeIter<'a>, StoreError> {
self.inner.range(lo, hi)
}
fn iter<'a>(&'a self) -> Result<oxistore_core::RangeIter<'a>, StoreError> {
self.inner.iter()
}
fn transaction(&self) -> Result<Box<dyn oxistore_core::KvTxn + '_>, StoreError> {
self.inner.transaction()
}
fn snapshot(&self) -> Result<Box<dyn oxistore_core::KvSnapshot + '_>, StoreError> {
self.inner.snapshot()
}
fn flush(&self) -> Result<(), StoreError> {
self.inner.flush()
}
}
#[cfg(feature = "serde-typed")]
pub use oxistore_core::{JsonCodec, TypedCodec, TypedKvError, TypedKvStore};
pub mod prelude {
pub use crate::{open, open_in_memory, open_with, Backend, StoreError, StoreKind};
pub use oxistore_core::{KvSnapshot, KvStore, KvTxn};
#[cfg(feature = "cache")]
pub use oxistore_cache::{ArcCache, LruCache};
#[cfg(feature = "blob")]
pub use oxistore_blob::{BlobMeta, LocalBlobStore, MemoryBlobStore};
#[cfg(feature = "columnar")]
pub use oxistore_columnar::ColumnarTable;
#[cfg(feature = "encrypt")]
pub use oxistore_encrypt::EncryptedKv;
#[cfg(feature = "serde-typed")]
pub use oxistore_core::{JsonCodec, TypedCodec, TypedKvError, TypedKvStore};
}