#![cfg_attr(
not(feature = "memory"),
doc = r#"
Without `memory`, neither the root reexport nor the backend module is public:
```compile_fail
use actordb::store::MemoryStore;
```
```compile_fail
use actordb::store::memory::MemoryStore;
```
"#
)]
#![cfg_attr(
not(feature = "rocksdb"),
doc = r#"
Without `rocksdb`, neither the root reexport nor the backend module is public:
```compile_fail
use actordb::store::RocksDbStore;
```
```compile_fail
use actordb::store::rocksdb::RocksDbStore;
```
RocksDB open options and Node's convenience constructor are also opt-in:
```compile_fail
use actordb::store::RocksDbOptions;
```
```compile_fail
let config = actordb::node::NodeConfig::with_rocksdb("./data/node-a");
```
"#
)]
use std::fmt;
use crate::actor::VersionedValue;
#[cfg(feature = "memory")]
pub mod memory;
pub mod prefixed;
#[cfg(feature = "rocksdb")]
pub mod rocksdb;
#[cfg(feature = "memory")]
pub use memory::MemoryStore;
pub use prefixed::PrefixedStore;
#[cfg(feature = "rocksdb")]
pub use rocksdb::RocksDbStore;
#[derive(Debug)]
pub struct StoreError(pub String);
impl fmt::Display for StoreError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "store error: {}", self.0)
}
}
impl std::error::Error for StoreError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StoreIdentity {
node_id: String,
fragment_count: u32,
}
impl StoreIdentity {
pub fn new(node_id: String, fragment_count: u32) -> Self {
Self {
node_id,
fragment_count,
}
}
pub fn node_id(&self) -> &str {
&self.node_id
}
pub fn fragment_count(&self) -> u32 {
self.fragment_count
}
}
pub struct StoreBinding {
release: Option<Box<dyn FnOnce() + Send + Sync>>,
}
impl StoreBinding {
pub fn unmanaged() -> Self {
Self { release: None }
}
pub fn managed(release: impl FnOnce() + Send + Sync + 'static) -> Self {
Self {
release: Some(Box::new(release)),
}
}
pub(crate) fn abandon(&mut self) {
if let Some(release) = self.release.take() {
std::mem::forget(release);
}
}
}
impl Drop for StoreBinding {
fn drop(&mut self) {
if let Some(release) = self.release.take() {
release();
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PrefixDeleteSupport {
#[default]
Retained,
Supported,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum StoreCommitDurability {
#[default]
Buffered,
Stable,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PrefixDeleteOutcome {
Deleted,
Retained,
}
pub trait KvStore: Send + Sync + 'static {
fn bind(&self, _identity: &StoreIdentity) -> Result<StoreBinding, StoreError> {
Ok(StoreBinding::unmanaged())
}
fn load(&self) -> Result<Vec<(Vec<u8>, VersionedValue)>, StoreError>;
fn load_prefix(&self, prefix: &[u8]) -> Result<Vec<(Vec<u8>, VersionedValue)>, StoreError> {
Ok(self
.load()?
.into_iter()
.filter(|(key, _)| key.starts_with(prefix))
.collect())
}
fn commit_durability(&self) -> StoreCommitDurability {
StoreCommitDurability::Buffered
}
fn stable_fence(&self) -> Result<StoreCommitDurability, StoreError> {
Ok(self.commit_durability())
}
fn write_batch(&self, writes: &[(Vec<u8>, VersionedValue)]) -> Result<(), StoreError>;
fn prefix_delete_support(&self) -> PrefixDeleteSupport {
PrefixDeleteSupport::Retained
}
fn delete_prefix(&self, _prefix: &[u8]) -> Result<PrefixDeleteOutcome, StoreError> {
Ok(PrefixDeleteOutcome::Retained)
}
}
#[cfg(feature = "rocksdb")]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum RocksDbDurability {
AsyncWal,
#[default]
SyncWal,
}
#[cfg(feature = "rocksdb")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, bon::Builder)]
pub struct RocksDbOptions {
#[builder(default = RocksDbDurability::SyncWal)]
durability: RocksDbDurability,
#[builder(default = false, name = legacy_manifest_adoption)]
adopt_legacy_manifest: bool,
}
#[cfg(feature = "rocksdb")]
impl Default for RocksDbOptions {
fn default() -> Self {
Self::builder().build()
}
}
#[cfg(feature = "rocksdb")]
impl RocksDbOptions {
pub fn with_durability(mut self, durability: RocksDbDurability) -> Self {
self.durability = durability;
self
}
pub fn with_legacy_manifest_adoption(mut self, allow: bool) -> Self {
self.adopt_legacy_manifest = allow;
self
}
pub fn durability(&self) -> RocksDbDurability {
self.durability
}
pub fn legacy_manifest_adoption(&self) -> bool {
self.adopt_legacy_manifest
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn custom_store_capabilities_default_to_retention_and_buffering() {
struct CustomStore;
impl KvStore for CustomStore {
fn load(&self) -> Result<Vec<(Vec<u8>, VersionedValue)>, StoreError> {
Ok(Vec::new())
}
fn write_batch(&self, _writes: &[(Vec<u8>, VersionedValue)]) -> Result<(), StoreError> {
Ok(())
}
}
let store = CustomStore;
assert_eq!(store.commit_durability(), StoreCommitDurability::Buffered);
assert_eq!(
store.stable_fence().unwrap(),
StoreCommitDurability::Buffered
);
assert_eq!(store.prefix_delete_support(), PrefixDeleteSupport::Retained);
}
}