bkndb 0.2.4

Embedded hybrid graph-relational database engine with ACID transactions
Documentation
pub use bkndb_core::*;

#[cfg(feature = "redb-backend")]
pub use bkndb_storage_redb::{IntegrityReport, RedbStats, RedbStorageBackend};

#[cfg(feature = "lsm-backend")]
pub use bkndb_storage_lsm::{
    IntegrityReport as LsmIntegrityReport, LsmOptions, LsmStats, LsmStorageBackend,
};

#[cfg(all(not(feature = "redb-backend"), feature = "lsm-backend"))]
pub use bkndb_storage_lsm::IntegrityReport;

#[cfg(feature = "mem-backend")]
pub use bkndb_storage_mem::MemoryStorageBackend;

use std::path::Path;

/// The primary user-facing database handle for BknDb, backed by
/// [`RedbStorageBackend`] for single-file ACID on-disk persistence (`.bkndb`).
#[cfg(feature = "redb-backend")]
#[derive(Clone)]
pub struct BknDb {
    inner: Db<RedbStorageBackend>,
}

#[cfg(feature = "redb-backend")]
impl BknDb {
    /// Opens or creates a single-file BknDb database (`.bkndb`) at `path`
    /// backed by [`RedbStorageBackend`].
    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, BknError> {
        let backend = RedbStorageBackend::open(path)?;
        Ok(Self {
            inner: Db::new(backend),
        })
    }

    /// Compatibility alias for [`BknDb::open`] backed by [`RedbStorageBackend`].
    pub fn open_redb<P: AsRef<Path>>(path: P) -> Result<Self, BknError> {
        Self::open(path)
    }

    /// Opens or creates an on-disk database backed by [`LsmStorageBackend`].
    #[cfg(feature = "lsm-backend")]
    pub fn open_lsm<P: AsRef<Path>>(path: P) -> Result<Db<LsmStorageBackend>, BknError> {
        let backend = LsmStorageBackend::open(path)?;
        Ok(Db::new(backend))
    }

    /// Opens or creates an on-disk database backed by [`LsmStorageBackend`]
    /// with custom [`LsmOptions`].
    #[cfg(feature = "lsm-backend")]
    pub fn open_lsm_with_options<P: AsRef<Path>>(
        path: P,
        options: LsmOptions,
    ) -> Result<Db<LsmStorageBackend>, BknError> {
        let backend = LsmStorageBackend::open_with_options(path, options)?;
        Ok(Db::new(backend))
    }

    /// Creates an ephemeral in-memory database instance for testing or caching.
    #[cfg(feature = "mem-backend")]
    pub fn in_memory() -> Db<MemoryStorageBackend> {
        Db::new(MemoryStorageBackend::new())
    }

    /// Access the underlying [`Db<RedbStorageBackend>`].
    pub fn inner(&self) -> &Db<RedbStorageBackend> {
        &self.inner
    }

    /// Compacts the on-disk database and reclaims free space.
    pub fn compact(&self) -> Result<(), BknError> {
        self.inner.backend().force_compact()
    }

    /// Writes a consistent snapshot copy of everything committed so far to
    /// a new `.bkndb` file at `dest` (which must not exist yet), while the
    /// database stays open for reads and writes.
    pub fn backup_to<P: AsRef<Path>>(&self, dest: P) -> Result<(), BknError> {
        self.inner.backend().backup_to(dest)
    }

    /// File-level and table storage statistics. For logical counts (nodes,
    /// edges, rows) see [`Db::stats`].
    pub fn storage_stats(&self) -> Result<RedbStats, BknError> {
        self.inner.backend().stats()
    }

    /// Verifies database integrity and scans all tables/entries; returns
    /// [`BknError::Corruption`] if anything is damaged.
    pub fn verify_integrity(&self) -> Result<IntegrityReport, BknError> {
        self.inner.backend().verify_integrity()
    }

    /// Convenience helper for joining a list of graph node IDs with a relational table
    /// whose primary key is the node ID.
    #[cfg(all(feature = "graph", feature = "relational-layer"))]
    pub fn join_nodes_with_table(
        &self,
        nodes: &[bkndb_core::graph::NodeId],
        schema: impl Into<bkndb_core::relational::TableSchema>,
    ) -> Result<Vec<bkndb_core::hybrid::JoinedNode>, BknError> {
        self.read_tx(|tx| tx.join_nodes_with_table(nodes, schema))
    }

    /// Convenience helper for joining a list of graph node IDs with a relational table
    /// by a foreign-key integer column.
    #[cfg(all(feature = "graph", feature = "relational-layer"))]
    pub fn join_nodes_by_column(
        &self,
        nodes: &[bkndb_core::graph::NodeId],
        schema: impl Into<bkndb_core::relational::TableSchema>,
        foreign_key_col: &str,
    ) -> Result<Vec<bkndb_core::hybrid::JoinedNodeRows>, BknError> {
        self.read_tx(|tx| tx.join_nodes_by_column(nodes, schema, foreign_key_col))
    }

    /// Ingests a structured batch of graph nodes, edges, and relational rows
    /// in a single atomic transaction using optimized bulk primitives.
    #[cfg(all(feature = "graph", feature = "relational-layer"))]
    pub fn sync_batch(&self, batch: SyncBatch) -> Result<SyncBatchResult, BknError> {
        self.inner.sync_batch(batch)
    }
}

#[cfg(feature = "redb-backend")]
impl std::ops::Deref for BknDb {
    type Target = Db<RedbStorageBackend>;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

#[cfg(feature = "redb-backend")]
impl std::ops::DerefMut for BknDb {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

/// Fallback BknDb when redb-backend is disabled but lsm-backend is enabled.
#[cfg(all(not(feature = "redb-backend"), feature = "lsm-backend"))]
#[derive(Clone)]
pub struct BknDb {
    inner: Db<LsmStorageBackend>,
}

#[cfg(all(not(feature = "redb-backend"), feature = "lsm-backend"))]
impl BknDb {
    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, BknError> {
        let backend = LsmStorageBackend::open(path)?;
        Ok(Self {
            inner: Db::new(backend),
        })
    }

    pub fn open_with_options<P: AsRef<Path>>(path: P, options: LsmOptions) -> Result<Self, BknError> {
        let backend = LsmStorageBackend::open_with_options(path, options)?;
        Ok(Self {
            inner: Db::new(backend),
        })
    }

    pub fn open_lsm<P: AsRef<Path>>(path: P) -> Result<Db<LsmStorageBackend>, BknError> {
        let backend = LsmStorageBackend::open(path)?;
        Ok(Db::new(backend))
    }

    pub fn open_lsm_with_options<P: AsRef<Path>>(
        path: P,
        options: LsmOptions,
    ) -> Result<Db<LsmStorageBackend>, BknError> {
        let backend = LsmStorageBackend::open_with_options(path, options)?;
        Ok(Db::new(backend))
    }

    #[cfg(feature = "mem-backend")]
    pub fn in_memory() -> Db<MemoryStorageBackend> {
        Db::new(MemoryStorageBackend::new())
    }

    pub fn inner(&self) -> &Db<LsmStorageBackend> {
        &self.inner
    }

    pub fn compact(&self) -> Result<(), BknError> {
        self.inner.backend().force_compact()
    }

    pub fn backup_to<P: AsRef<Path>>(&self, dest: P) -> Result<(), BknError> {
        self.inner.backend().backup_to(dest)
    }

    pub fn storage_stats(&self) -> Result<LsmStats, BknError> {
        self.inner.backend().stats()
    }

    pub fn verify_integrity(&self) -> Result<IntegrityReport, BknError> {
        self.inner.backend().verify_integrity()
    }

    #[cfg(all(feature = "graph", feature = "relational-layer"))]
    pub fn join_nodes_with_table(
        &self,
        nodes: &[bkndb_core::graph::NodeId],
        schema: impl Into<bkndb_core::relational::TableSchema>,
    ) -> Result<Vec<bkndb_core::hybrid::JoinedNode>, BknError> {
        self.read_tx(|tx| tx.join_nodes_with_table(nodes, schema))
    }

    #[cfg(all(feature = "graph", feature = "relational-layer"))]
    pub fn join_nodes_by_column(
        &self,
        nodes: &[bkndb_core::graph::NodeId],
        schema: impl Into<bkndb_core::relational::TableSchema>,
        foreign_key_col: &str,
    ) -> Result<Vec<bkndb_core::hybrid::JoinedNodeRows>, BknError> {
        self.read_tx(|tx| tx.join_nodes_by_column(nodes, schema, foreign_key_col))
    }

    #[cfg(all(feature = "graph", feature = "relational-layer"))]
    pub fn sync_batch(&self, batch: SyncBatch) -> Result<SyncBatchResult, BknError> {
        self.inner.sync_batch(batch)
    }
}

#[cfg(all(not(feature = "redb-backend"), feature = "lsm-backend"))]
impl std::ops::Deref for BknDb {
    type Target = Db<LsmStorageBackend>;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

#[cfg(all(not(feature = "redb-backend"), feature = "lsm-backend"))]
impl std::ops::DerefMut for BknDb {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

#[cfg(all(not(feature = "lsm-backend"), not(feature = "redb-backend"), feature = "mem-backend"))]
pub struct BknDb;

#[cfg(all(not(feature = "lsm-backend"), not(feature = "redb-backend"), feature = "mem-backend"))]
impl BknDb {
    /// Creates an ephemeral in-memory database instance for testing or caching.
    pub fn in_memory() -> Db<MemoryStorageBackend> {
        Db::new(MemoryStorageBackend::new())
    }
}