Skip to main content

reifydb_store_multi/hot/
storage.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4//! Hot storage tier enum.
5//!
6//! This module provides the hot storage tier that dispatches to either
7//! Memory or SQLite primitive storage implementations.
8
9use std::{collections::HashMap, ops::Bound};
10
11use reifydb_core::common::CommitVersion;
12use reifydb_type::{Result, util::cowvec::CowVec};
13
14use super::memory::storage::MemoryPrimitiveStorage;
15#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
16use super::sqlite::config::SqliteConfig;
17#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
18use super::sqlite::storage::SqlitePrimitiveStorage;
19use crate::tier::{EntryKind, RangeBatch, RangeCursor, TierBackend, TierStorage};
20
21/// Hot storage tier.
22///
23/// Provides a single interface for hot tier storage operations, dispatching
24/// to either Memory or SQLite implementations.
25#[derive(Clone)]
26#[repr(u8)]
27pub enum HotStorage {
28	/// In-memory storage (non-persistent)
29	Memory(MemoryPrimitiveStorage) = 0,
30	/// SQLite-based persistent storage
31	#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
32	Sqlite(SqlitePrimitiveStorage) = 1,
33}
34
35impl HotStorage {
36	/// Create a new in-memory backend
37	pub fn memory() -> Self {
38		Self::Memory(MemoryPrimitiveStorage::new())
39	}
40
41	/// Create a new SQLite backend with in-memory database
42	#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
43	pub fn sqlite_in_memory() -> Self {
44		Self::Sqlite(SqlitePrimitiveStorage::in_memory())
45	}
46
47	/// Create a new SQLite backend with the given configuration
48	#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
49	pub fn sqlite(config: SqliteConfig) -> Self {
50		Self::Sqlite(SqlitePrimitiveStorage::new(config))
51	}
52}
53
54impl HotStorage {
55	/// Run periodic maintenance (vacuum + shrink) to reclaim memory.
56	pub fn maintenance(&self) {
57		match self {
58			Self::Memory(_) => {}
59			#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
60			Self::Sqlite(s) => {
61				s.incremental_vacuum();
62				s.shrink_memory();
63			}
64		}
65	}
66}
67
68impl TierStorage for HotStorage {
69	#[inline]
70	fn get(&self, table: EntryKind, key: &[u8], version: CommitVersion) -> Result<Option<CowVec<u8>>> {
71		match self {
72			Self::Memory(s) => s.get(table, key, version),
73			#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
74			Self::Sqlite(s) => s.get(table, key, version),
75		}
76	}
77
78	#[inline]
79	fn contains(&self, table: EntryKind, key: &[u8], version: CommitVersion) -> Result<bool> {
80		match self {
81			Self::Memory(s) => s.contains(table, key, version),
82			#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
83			Self::Sqlite(s) => s.contains(table, key, version),
84		}
85	}
86
87	#[inline]
88	fn set(
89		&self,
90		version: CommitVersion,
91		batches: HashMap<EntryKind, Vec<(CowVec<u8>, Option<CowVec<u8>>)>>,
92	) -> Result<()> {
93		match self {
94			Self::Memory(s) => s.set(version, batches),
95			#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
96			Self::Sqlite(s) => s.set(version, batches),
97		}
98	}
99
100	#[inline]
101	fn range_next(
102		&self,
103		table: EntryKind,
104		cursor: &mut RangeCursor,
105		start: Bound<&[u8]>,
106		end: Bound<&[u8]>,
107		version: CommitVersion,
108		batch_size: usize,
109	) -> Result<RangeBatch> {
110		match self {
111			Self::Memory(s) => s.range_next(table, cursor, start, end, version, batch_size),
112			#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
113			Self::Sqlite(s) => s.range_next(table, cursor, start, end, version, batch_size),
114		}
115	}
116
117	#[inline]
118	fn range_rev_next(
119		&self,
120		table: EntryKind,
121		cursor: &mut RangeCursor,
122		start: Bound<&[u8]>,
123		end: Bound<&[u8]>,
124		version: CommitVersion,
125		batch_size: usize,
126	) -> Result<RangeBatch> {
127		match self {
128			Self::Memory(s) => s.range_rev_next(table, cursor, start, end, version, batch_size),
129			#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
130			Self::Sqlite(s) => s.range_rev_next(table, cursor, start, end, version, batch_size),
131		}
132	}
133
134	#[inline]
135	fn ensure_table(&self, table: EntryKind) -> Result<()> {
136		match self {
137			Self::Memory(s) => s.ensure_table(table),
138			#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
139			Self::Sqlite(s) => s.ensure_table(table),
140		}
141	}
142
143	#[inline]
144	fn clear_table(&self, table: EntryKind) -> Result<()> {
145		match self {
146			Self::Memory(s) => s.clear_table(table),
147			#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
148			Self::Sqlite(s) => s.clear_table(table),
149		}
150	}
151
152	#[inline]
153	fn drop(&self, batches: HashMap<EntryKind, Vec<(CowVec<u8>, CommitVersion)>>) -> Result<()> {
154		match self {
155			Self::Memory(s) => s.drop(batches),
156			#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
157			Self::Sqlite(s) => s.drop(batches),
158		}
159	}
160
161	#[inline]
162	fn get_all_versions(&self, table: EntryKind, key: &[u8]) -> Result<Vec<(CommitVersion, Option<CowVec<u8>>)>> {
163		match self {
164			Self::Memory(s) => s.get_all_versions(table, key),
165			#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
166			Self::Sqlite(s) => s.get_all_versions(table, key),
167		}
168	}
169}
170
171impl TierBackend for HotStorage {}
172
173#[cfg(test)]
174pub mod tests {
175	use super::*;
176
177	#[test]
178	fn test_memory_backend() {
179		let storage = HotStorage::memory();
180
181		let key = CowVec::new(b"key".to_vec());
182		let version = CommitVersion(1);
183
184		storage.set(
185			version,
186			HashMap::from([(EntryKind::Multi, vec![(key.clone(), Some(CowVec::new(b"value".to_vec())))])]),
187		)
188		.unwrap();
189		assert_eq!(storage.get(EntryKind::Multi, &key, version).unwrap().as_deref(), Some(b"value".as_slice()));
190	}
191
192	#[test]
193	fn test_sqlite_backend() {
194		let storage = HotStorage::sqlite_in_memory();
195
196		let key = CowVec::new(b"key".to_vec());
197		let version = CommitVersion(1);
198
199		storage.set(
200			version,
201			HashMap::from([(EntryKind::Multi, vec![(key.clone(), Some(CowVec::new(b"value".to_vec())))])]),
202		)
203		.unwrap();
204		assert_eq!(storage.get(EntryKind::Multi, &key, version).unwrap().as_deref(), Some(b"value".as_slice()));
205	}
206
207	#[test]
208	fn test_range_next_memory() {
209		let storage = HotStorage::memory();
210
211		let version = CommitVersion(1);
212		storage.set(
213			version,
214			HashMap::from([(
215				EntryKind::Multi,
216				vec![
217					(CowVec::new(b"a".to_vec()), Some(CowVec::new(b"1".to_vec()))),
218					(CowVec::new(b"b".to_vec()), Some(CowVec::new(b"2".to_vec()))),
219					(CowVec::new(b"c".to_vec()), Some(CowVec::new(b"3".to_vec()))),
220				],
221			)]),
222		)
223		.unwrap();
224
225		let mut cursor = RangeCursor::new();
226		let batch = storage
227			.range_next(EntryKind::Multi, &mut cursor, Bound::Unbounded, Bound::Unbounded, version, 100)
228			.unwrap();
229
230		assert_eq!(batch.entries.len(), 3);
231		assert!(!batch.has_more);
232		assert!(cursor.exhausted);
233	}
234
235	#[test]
236	fn test_range_next_sqlite() {
237		let storage = HotStorage::sqlite_in_memory();
238
239		let version = CommitVersion(1);
240		storage.set(
241			version,
242			HashMap::from([(
243				EntryKind::Multi,
244				vec![
245					(CowVec::new(b"a".to_vec()), Some(CowVec::new(b"1".to_vec()))),
246					(CowVec::new(b"b".to_vec()), Some(CowVec::new(b"2".to_vec()))),
247					(CowVec::new(b"c".to_vec()), Some(CowVec::new(b"3".to_vec()))),
248				],
249			)]),
250		)
251		.unwrap();
252
253		let mut cursor = RangeCursor::new();
254		let batch = storage
255			.range_next(EntryKind::Multi, &mut cursor, Bound::Unbounded, Bound::Unbounded, version, 100)
256			.unwrap();
257
258		assert_eq!(batch.entries.len(), 3);
259		assert!(!batch.has_more);
260		assert!(cursor.exhausted);
261	}
262}