shodh-redb 0.5.0

Multi-modal embedded database - vectors, blobs, TTL, merge operators, and causal tracking built on ACID B-trees
Documentation
mod bad_block;
mod ftl;
pub mod hardware;
mod journal;
mod wear_leveling;

pub use hardware::{FlashGeometry, FlashHardware};

use crate::StorageBackend;
use crate::error::BackendError;
use core::fmt::{Debug, Formatter};
use ftl::FlashTranslationLayer;

/// Aggregate wear statistics for the flash device.
#[derive(Debug, Clone, Copy)]
pub struct FlashWearStats {
    /// Lowest erase count across all physical blocks.
    pub min_erase_count: u32,
    /// Highest erase count across all physical blocks.
    pub max_erase_count: u32,
    /// Sum of all erase counts (divide by `total_blocks` for mean).
    pub total_erase_count: u64,
    /// Total number of physical blocks tracked.
    pub total_blocks: u32,
    /// Number of blocks marked as bad.
    pub bad_block_count: u32,
}

/// Storage backend for bare-metal flash devices.
///
/// Wraps a user-provided [`FlashHardware`] implementation with a full Flash
/// Translation Layer (FTL) providing:
///
/// - **Wear leveling** -- both dynamic (allocate lowest-wear free blocks) and
///   static (periodic cold/hot data swap).
/// - **Bad block management** -- scan on mount, runtime detection, transparent
///   remapping.
/// - **Power-loss safety** -- double-buffered metadata journal with xxh3-128
///   checksums, matching shodh-redb's own double-buffered commit slot pattern.
/// - **Copy-on-write** -- all writes go to fresh physical blocks; old blocks
///   are erased and returned to the free pool.
///
/// # Usage
///
/// ```rust,ignore
/// use shodh_redb::{Builder, FlashBackend, FlashGeometry, FlashHardware};
///
/// // 1. Implement FlashHardware for your specific flash chip
/// struct MySpiFlash { /* SPI peripheral, CS pin, etc. */ }
/// impl FlashHardware for MySpiFlash { /* ... */ }
///
/// // 2. Create the backend and open the database
/// let hw = MySpiFlash::new(/* ... */);
/// let backend = FlashBackend::mount(hw).expect("flash mount failed");
/// let db = Builder::new().create_with_backend(backend).expect("db open");
/// ```
///
/// # eMMC note
///
/// eMMC devices have their own internal FTL. For eMMC, consider implementing
/// [`StorageBackend`] directly (like [`InMemoryBackend`](crate::InMemoryBackend))
/// to avoid the overhead of a redundant translation layer.
pub struct FlashBackend<H: FlashHardware> {
    ftl: FlashTranslationLayer<H>,
}

impl<H: FlashHardware> Debug for FlashBackend<H> {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("FlashBackend").finish_non_exhaustive()
    }
}

impl<H: FlashHardware> FlashBackend<H> {
    /// Mount an existing database from flash, recovering FTL state from the journal.
    ///
    /// If no valid journal is found (fresh/erased device), the device is formatted
    /// automatically.
    pub fn mount(hw: H) -> core::result::Result<Self, BackendError> {
        let ftl = FlashTranslationLayer::mount(hw)?;
        Ok(Self { ftl })
    }

    /// Aggregate wear statistics for the flash device.
    pub fn wear_stats(&self) -> FlashWearStats {
        self.ftl.wear_stats()
    }

    /// Set the static wear-leveling threshold (default: 100 erase cycles).
    ///
    /// A swap between hot and cold blocks is triggered when the erase count
    /// delta exceeds this value. Lower values spread wear more aggressively
    /// but increase block moves. Higher values reduce churn for low-write
    /// workloads.
    pub fn set_wear_leveling_threshold(&self, threshold: u32) {
        self.ftl.set_wl_threshold(threshold);
    }

    /// Set the static wear-leveling check interval (default: every 256 erases).
    ///
    /// Controls how often the FTL scans for hot/cold block imbalance.
    pub fn set_wear_leveling_interval(&self, interval: u32) {
        self.ftl.set_wl_interval(interval);
    }

    /// Format the flash device and create a fresh FTL.
    ///
    /// **All existing data on the device is lost.**
    pub fn format(hw: H) -> core::result::Result<Self, BackendError> {
        let ftl = FlashTranslationLayer::format(hw)?;
        Ok(Self { ftl })
    }
}

impl<H: FlashHardware> StorageBackend for FlashBackend<H> {
    fn len(&self) -> core::result::Result<u64, BackendError> {
        self.ftl.len()
    }

    fn read(&self, offset: u64, out: &mut [u8]) -> core::result::Result<(), BackendError> {
        self.ftl.read(offset, out)
    }

    fn set_len(&self, len: u64) -> core::result::Result<(), BackendError> {
        self.ftl.set_len(len)
    }

    fn sync_data(&self) -> core::result::Result<(), BackendError> {
        self.ftl.sync()
    }

    fn write(&self, offset: u64, data: &[u8]) -> core::result::Result<(), BackendError> {
        self.ftl.write(offset, data)
    }

    fn close(&self) -> core::result::Result<(), BackendError> {
        self.ftl.close()
    }
}