use crate::{
storage::Storage, store::Store, ConfigurableStorage, StorageConfig, SPHERE_DB_STORE_NAMES,
};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use noosphere_common::ConditionalSend;
use rocksdb::{ColumnFamilyDescriptor, DBWithThreadMode, Options};
use std::{
path::{Path, PathBuf},
sync::Arc,
};
#[cfg(not(feature = "rocksdb-multi-thread"))]
type DbInner = DBWithThreadMode<rocksdb::SingleThreaded>;
#[cfg(not(feature = "rocksdb-multi-thread"))]
type ColumnType<'a> = &'a rocksdb::ColumnFamily;
#[cfg(feature = "rocksdb-multi-thread")]
type DbInner = DBWithThreadMode<rocksdb::MultiThreaded>;
#[cfg(feature = "rocksdb-multi-thread")]
type ColumnType<'a> = Arc<rocksdb::BoundColumnFamily<'a>>;
struct Db(DbInner);
impl Drop for Db {
fn drop(&mut self) {
self.0.cancel_all_background_work(true);
}
}
impl std::ops::Deref for Db {
type Target = DbInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone)]
pub struct RocksDbStorage {
db: Arc<Db>,
debug_data: Arc<(PathBuf, StorageConfig)>,
}
impl RocksDbStorage {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
Self::with_config(path, StorageConfig::default())
}
pub fn with_config<P: AsRef<Path>>(path: P, storage_config: StorageConfig) -> Result<Self> {
std::fs::create_dir_all(path.as_ref())?;
let db_path = path.as_ref().canonicalize()?;
let db = {
let mut cfs: Vec<ColumnFamilyDescriptor> =
Vec::with_capacity(SPHERE_DB_STORE_NAMES.len());
for store_name in SPHERE_DB_STORE_NAMES {
let cf_opts = Options::default();
cfs.push(ColumnFamilyDescriptor::new(*store_name, cf_opts));
}
let mut db_opts = Options::default();
db_opts.create_if_missing(true);
db_opts.create_missing_column_families(true);
if let Some(memory_cache_limit) = storage_config.memory_cache_limit {
db_opts.set_db_write_buffer_size(memory_cache_limit);
}
Arc::new(Db(DbInner::open_cf_descriptors(&db_opts, path, cfs)?))
};
Ok(RocksDbStorage {
db,
debug_data: Arc::new((db_path, storage_config)),
})
}
async fn get_store(&self, name: &str) -> Result<RocksDbStore> {
if SPHERE_DB_STORE_NAMES
.iter()
.find(|val| **val == name)
.is_none()
{
return Err(anyhow!("No such store named {}", name));
}
RocksDbStore::new(self.db.clone(), name.to_owned())
}
}
#[async_trait]
impl Storage for RocksDbStorage {
type BlockStore = RocksDbStore;
type KeyValueStore = RocksDbStore;
async fn get_block_store(&self, name: &str) -> Result<Self::BlockStore> {
self.get_store(name).await
}
async fn get_key_value_store(&self, name: &str) -> Result<Self::KeyValueStore> {
self.get_store(name).await
}
}
#[async_trait]
impl ConfigurableStorage for RocksDbStorage {
async fn open_with_config<P: AsRef<Path> + ConditionalSend>(
path: P,
storage_config: StorageConfig,
) -> Result<Self> {
Self::with_config(path, storage_config)
}
}
impl std::fmt::Debug for RocksDbStorage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RocksDbStorage")
.field("path", &self.debug_data.0)
.field("config", &self.debug_data.1)
.finish()
}
}
#[derive(Clone)]
pub struct RocksDbStore {
name: String,
db: Arc<Db>,
}
impl RocksDbStore {
fn new(db: Arc<Db>, name: String) -> Result<Self> {
Ok(RocksDbStore { db, name })
}
fn cf_handle<'a>(&'a self) -> Result<ColumnType> {
self.db
.cf_handle(&self.name)
.ok_or_else(move || anyhow!("Could not open handle for {}", self.name))
}
}
#[async_trait]
impl Store for RocksDbStore {
async fn read(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
let cf = self.cf_handle()?;
#[cfg(feature = "rocksdb-multi-thread")]
let cf = &cf;
Ok(self.db.get_cf(cf, key)?)
}
async fn write(&mut self, key: &[u8], bytes: &[u8]) -> Result<Option<Vec<u8>>> {
let cf = self.cf_handle()?;
#[cfg(feature = "rocksdb-multi-thread")]
let cf = &cf;
let old_bytes = self.db.get_cf(cf, key)?;
self.db.put_cf(cf, key, bytes)?;
Ok(old_bytes)
}
async fn remove(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>> {
let cf = self.cf_handle()?;
#[cfg(feature = "rocksdb-multi-thread")]
let cf = &cf;
let old_bytes = self.db.get_cf(cf, key)?;
self.db.delete_cf(cf, key)?;
Ok(old_bytes)
}
async fn flush(&self) -> Result<()> {
Ok(())
}
}
#[async_trait]
impl crate::Space for RocksDbStorage {
async fn get_space_usage(&self) -> Result<u64> {
crate::get_dir_size(&self.debug_data.0).await
}
}