reifydb-store-multi 0.9.0

Multi-version storage for OLTP operations with MVCC support
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 ReifyDB

//! Cold tier of the multi-version store: one durable row per key, carrying the latest version the sweep
//! flushed, not a version chain. The default backend is SQLite; the trait surface is generic so another
//! backend can be plugged in without touching the buffer or transaction layer.

use std::ops::Bound;

use reifydb_codec::key::encoded::EncodedKey;
use reifydb_core::{common::CommitVersion, interface::store::EntryKind};
use reifydb_runtime::shutdown::Shutdown;
#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
use reifydb_sqlite::{SqliteConfig, SqliteTempPathGuard};
use reifydb_value::{Result, value::datetime::DateTime};

use crate::{
	MultiVersionScope,
	tier::{
		DisplacedValues, RangeBatch, RangeCursor, RawEntry, TierBackend, TierBatch, TierStorage,
		VersionedGetResult,
	},
};

#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
pub mod sqlite;

#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
use sqlite::storage::{SqlitePageCacheMetrics, SqlitePersistentStorage};

#[derive(Clone)]
#[cfg_attr(all(feature = "sqlite", not(target_arch = "wasm32")), repr(u8))]
pub enum MultiPersistentTier {
	#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
	Sqlite(SqlitePersistentStorage) = 0,
}

impl Shutdown for MultiPersistentTier {
	fn shutdown(&self) {
		match self {
			#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
			Self::Sqlite(s) => s.shutdown(),
			#[cfg(not(all(feature = "sqlite", not(target_arch = "wasm32"))))]
			_ => {}
		}
	}
}

#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
impl MultiPersistentTier {
	pub fn sqlite(config: SqliteConfig) -> Self {
		Self::Sqlite(SqlitePersistentStorage::new(config))
	}

	pub fn page_cache_metrics(&self) -> SqlitePageCacheMetrics {
		match self {
			Self::Sqlite(storage) => storage.page_cache_metrics(),
		}
	}

	pub fn sqlite_in_memory() -> (Self, SqliteTempPathGuard) {
		let (storage, guard) = SqlitePersistentStorage::in_memory();
		(Self::Sqlite(storage), guard)
	}

	pub fn set_checkpoint_threshold(&self, frames: u32) {
		match self {
			Self::Sqlite(s) => s.set_checkpoint_threshold(frames),
		}
	}

	pub fn delete_below_version(
		&self,
		table: EntryKind,
		cutoff_version: CommitVersion,
		prefix: Option<&[u8]>,
		cursor: Option<&[u8]>,
		limit: usize,
	) -> Result<(Vec<EncodedKey>, Option<EncodedKey>)> {
		match self {
			Self::Sqlite(s) => s.delete_below_version(table, cutoff_version, prefix, cursor, limit),
		}
	}

	pub fn delete_keys(&self, table: EntryKind, keys: &[EncodedKey]) -> Result<u64> {
		match self {
			Self::Sqlite(s) => s.delete_keys(table, keys),
		}
	}

	pub fn expired_keys(
		&self,
		table: EntryKind,
		cutoff: DateTime,
		cursor: Option<(DateTime, &[u8])>,
		limit: usize,
	) -> Result<Vec<(EncodedKey, DateTime)>> {
		match self {
			Self::Sqlite(s) => s.expired_keys(table, cutoff, cursor, limit),
		}
	}

	pub fn list_current_entries(&self) -> Result<Vec<EntryKind>> {
		match self {
			Self::Sqlite(s) => s.list_current_entries(),
		}
	}

	pub fn reap_tombstones(
		&self,
		kind: EntryKind,
		cutoff_version: CommitVersion,
		limit: usize,
	) -> Result<(u64, bool)> {
		match self {
			Self::Sqlite(s) => s.reap_tombstones(kind, cutoff_version, limit),
		}
	}

	pub fn set_collecting_accepted(&self, version: CommitVersion, batches: TierBatch) -> Result<Vec<EncodedKey>> {
		match self {
			Self::Sqlite(s) => s.set_collecting_accepted(version, batches),
		}
	}

	pub fn persist_sweep(&self, batches: Vec<(CommitVersion, TierBatch)>) -> Result<Vec<EncodedKey>> {
		match self {
			Self::Sqlite(s) => s.persist_sweep(batches),
		}
	}

	pub fn load_range_consistent(
		&self,
		table: EntryKind,
		start: Bound<&[u8]>,
		end: Bound<&[u8]>,
		read: CommitVersion,
		limit: Option<usize>,
	) -> Result<Vec<RawEntry>> {
		match self {
			Self::Sqlite(s) => s.load_range_consistent(table, start, end, read, limit),
		}
	}
}

#[cfg(not(all(feature = "sqlite", not(target_arch = "wasm32"))))]
impl MultiPersistentTier {
	pub fn set_checkpoint_threshold(&self, _frames: u32) {
		match *self {}
	}

	pub fn delete_below_version(
		&self,
		_table: EntryKind,
		_cutoff_version: CommitVersion,
		_prefix: Option<&[u8]>,
		_cursor: Option<&[u8]>,
		_limit: usize,
	) -> Result<(Vec<EncodedKey>, Option<EncodedKey>)> {
		match *self {}
	}

	pub fn delete_keys(&self, _table: EntryKind, _keys: &[EncodedKey]) -> Result<u64> {
		match *self {}
	}

	pub fn expired_keys(
		&self,
		_table: EntryKind,
		_cutoff: DateTime,
		_cursor: Option<(DateTime, &[u8])>,
		_limit: usize,
	) -> Result<Vec<(EncodedKey, DateTime)>> {
		match *self {}
	}

	pub fn list_current_entries(&self) -> Result<Vec<EntryKind>> {
		match *self {}
	}

	pub fn reap_tombstones(
		&self,
		_kind: EntryKind,
		_cutoff_version: CommitVersion,
		_limit: usize,
	) -> Result<(u64, bool)> {
		match *self {}
	}

	pub fn persist_sweep(&self, _batches: Vec<(CommitVersion, TierBatch)>) -> Result<Vec<EncodedKey>> {
		match *self {}
	}

	pub fn load_range_consistent(
		&self,
		_table: EntryKind,
		_start: Bound<&[u8]>,
		_end: Bound<&[u8]>,
		_read: CommitVersion,
		_limit: Option<usize>,
	) -> Result<Vec<RawEntry>> {
		match *self {}
	}
}

#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
impl TierStorage for MultiPersistentTier {
	fn get(&self, table: EntryKind, key: &[u8], version: CommitVersion) -> Result<VersionedGetResult> {
		match self {
			Self::Sqlite(s) => s.get(table, key, version),
		}
	}

	fn get_many(
		&self,
		table: EntryKind,
		keys: &[&[u8]],
		version: CommitVersion,
	) -> Result<Vec<VersionedGetResult>> {
		match self {
			Self::Sqlite(s) => s.get_many(table, keys, version),
		}
	}

	fn set(&self, version: CommitVersion, batches: TierBatch) -> Result<DisplacedValues> {
		match self {
			Self::Sqlite(s) => s.set(version, batches),
		}
	}

	fn range_next(
		&self,
		table: EntryKind,
		cursor: &mut RangeCursor,
		start: Bound<&[u8]>,
		end: Bound<&[u8]>,
		scope: MultiVersionScope,
		batch_size: usize,
	) -> Result<RangeBatch> {
		match self {
			Self::Sqlite(s) => s.range_next(table, cursor, start, end, scope, batch_size),
		}
	}

	fn range_rev_next(
		&self,
		table: EntryKind,
		cursor: &mut RangeCursor,
		start: Bound<&[u8]>,
		end: Bound<&[u8]>,
		scope: MultiVersionScope,
		batch_size: usize,
	) -> Result<RangeBatch> {
		match self {
			Self::Sqlite(s) => s.range_rev_next(table, cursor, start, end, scope, batch_size),
		}
	}

	fn ensure_table(&self, table: EntryKind) -> Result<()> {
		match self {
			Self::Sqlite(s) => s.ensure_table(table),
		}
	}

	fn clear_table(&self, table: EntryKind) -> Result<()> {
		match self {
			Self::Sqlite(s) => s.clear_table(table),
		}
	}
}

#[cfg(not(all(feature = "sqlite", not(target_arch = "wasm32"))))]
impl TierStorage for MultiPersistentTier {
	fn get(&self, _table: EntryKind, _key: &[u8], _version: CommitVersion) -> Result<VersionedGetResult> {
		match *self {}
	}

	fn set(&self, _version: CommitVersion, _batches: TierBatch) -> Result<DisplacedValues> {
		match *self {}
	}

	fn range_next(
		&self,
		_table: EntryKind,
		_cursor: &mut RangeCursor,
		_start: Bound<&[u8]>,
		_end: Bound<&[u8]>,
		_scope: MultiVersionScope,
		_batch_size: usize,
	) -> Result<RangeBatch> {
		match *self {}
	}

	fn range_rev_next(
		&self,
		_table: EntryKind,
		_cursor: &mut RangeCursor,
		_start: Bound<&[u8]>,
		_end: Bound<&[u8]>,
		_scope: MultiVersionScope,
		_batch_size: usize,
	) -> Result<RangeBatch> {
		match *self {}
	}

	fn ensure_table(&self, _table: EntryKind) -> Result<()> {
		match *self {}
	}

	fn clear_table(&self, _table: EntryKind) -> Result<()> {
		match *self {}
	}
}

impl TierBackend for MultiPersistentTier {}