armour 0.30.27

DDL and serialization for key-value storage
Documentation
use std::{collections::BTreeMap, path::Path};

use derive_more::Debug;
use fjall::{
    DatabaseBuilder, KeyspaceCreateOptions, OptimisticTxDatabase, OptimisticTxKeyspace, PersistMode,
};

use super::write_tx::WriteTx;
use crate::{
    DbError, DbResult, migration,
    snapshot::Snapshot,
    utils::{DbInfo, Persist},
};

#[derive(Debug, Clone)]
pub struct TxDb {
    #[debug(skip)]
    pub db: OptimisticTxDatabase,
    pub db_info: Persist<DbInfo>,
    pub path: String,
    #[debug(skip)]
    pub seq_tree: OptimisticTxKeyspace,
}

impl TxDb {
    pub fn new<P>(path: P) -> Self
    where
        P: AsRef<Path>,
    {
        let builder = Self::builder(path.as_ref());
        Self::with_builder(path, builder)
    }

    pub fn builder<P: AsRef<Path>>(path: P) -> DatabaseBuilder<OptimisticTxDatabase> {
        let db_path = path.as_ref().join("db");
        OptimisticTxDatabase::builder(db_path)
    }

    pub fn with_builder<P: AsRef<Path>>(
        path: P,
        config: DatabaseBuilder<OptimisticTxDatabase>,
    ) -> Self {
        let db = config.open().expect("Failed to open keyspace");
        let seq_tree = db
            .keyspace("__sequences", KeyspaceCreateOptions::default)
            .expect("Failed to open sequence tree");

        let meta_path = path.as_ref().join("db.info");
        let db_info = Persist::<DbInfo>::open(meta_path);

        Self {
            db,
            db_info,
            path: path.as_ref().to_string_lossy().into_owned(),
            seq_tree,
        }
    }

    pub fn open_tree(
        &self,
        name: &str,
        options: Option<KeyspaceCreateOptions>,
    ) -> fjall::OptimisticTxKeyspace {
        self.db
            .keyspace(name, || options.unwrap_or_default())
            .expect("Failed to open partition")
    }

    pub fn get_keyspace(&self, name: &str) -> Option<OptimisticTxKeyspace> {
        if !self.db.keyspace_exists(name) {
            return None;
        }
        self.db
            .keyspace(name, KeyspaceCreateOptions::default)
            .inspect_err(|err| {
                error!("{err}");
            })
            .ok()
    }

    pub fn get_all_keyspaces(&self) -> BTreeMap<String, OptimisticTxKeyspace> {
        self.db_info
            .cloned()
            .collections
            .into_iter()
            .filter_map(|(name, info)| {
                let versioned = migration::collection_name(&name, info.version);
                self.get_keyspace(&versioned).map(|ks| (name, ks))
            })
            .collect()
    }

    pub fn snapshot(&self) -> Snapshot {
        let inner = self.db.read_tx();
        Snapshot { inner }
    }

    pub fn write_tx(&self) -> DbResult<WriteTx> {
        let inner = self.db.write_tx()?;
        Ok(WriteTx { inner })
    }

    pub fn persist(&self, mode: PersistMode) -> DbResult<()> {
        self.db.persist(mode).map_err(DbError::from)
    }

    #[instrument(skip(self), name = "Database::close", fields(path = self.path))]
    pub fn close(&self) {
        self.db_info.save();
        self.db
            .persist(PersistMode::SyncAll)
            .expect("Failed to persist");
    }
}