persistent_map/lib.rs
1//! # PersistentMap
2//!
3//! `persistent-map` provides an in-memory key-value store with async, pluggable persistence.
4//!
5//! It combines the speed of [`DashMap`](https://docs.rs/dashmap) for in-memory operations
6//! with the durability of various storage backends for persistence.
7//!
8//! ## Features
9//!
10//! - **Fast in-memory access**: Uses `DashMap` for concurrent read/write operations
11//! - **Async API**: Non-blocking I/O operations for persistence
12//! - **Multiple backends**: SQLite, CSV, in-memory, and extensible for more
13//! - **Type-safe**: Works with any types that implement the required traits
14//!
15//! ## Example
16//!
17//! ```rust,no_run
18//! use persistent_map::{PersistentMap, Result};
19//! # #[cfg(feature = "sqlite")]
20//! use persistent_map::sqlite::SqliteBackend;
21//!
22//! # #[cfg(feature = "sqlite")]
23//! # async fn example() -> Result<()> {
24//! # // Create a SQLite backend
25//! # let backend = SqliteBackend::new("my_database.db").await?;
26//! #
27//! # // Initialize the persistent map
28//! # let map = PersistentMap::new(backend).await?;
29//! #
30//! # // Insert a key-value pair (persists automatically)
31//! # map.insert("hello".to_string(), "world".to_string()).await?;
32//! #
33//! # // Retrieve a value (from memory)
34//! # assert_eq!(map.get(&"hello".to_string()), Some("world".to_string()));
35//! # Ok(())
36//! # }
37//! #
38//! # #[cfg(not(feature = "sqlite"))]
39//! # fn example() {}
40//! ```
41
42use dashmap::DashMap;
43use serde::{de::DeserializeOwned, Serialize};
44use std::{collections::HashMap, hash::Hash};
45use thiserror::Error;
46/// A trait for implementing storage backends for `PersistentMap`.
47///
48/// This trait defines the interface that all storage backends must implement.
49/// It provides methods for loading, saving, and deleting key-value pairs.
50///
51/// # Type Parameters
52///
53/// * `K`: The key type, which must be hashable, serializable, and cloneable
54/// * `V`: The value type, which must be serializable and cloneable
55///
56/// # Examples
57///
58/// Implementing a custom backend:
59///
60/// ```rust
61/// use persistent_map::{StorageBackend, PersistentError, Result};
62/// use std::collections::HashMap;
63/// use serde::{Serialize, de::DeserializeOwned};
64/// use std::hash::Hash;
65///
66/// struct MyCustomBackend {
67/// // Your backend-specific fields
68/// }
69///
70/// #[async_trait::async_trait]
71/// impl<K, V> StorageBackend<K, V> for MyCustomBackend
72/// where
73/// K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
74/// V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
75/// {
76/// async fn load_all(&self) -> Result<HashMap<K, V>, PersistentError> {
77/// // Implementation for loading all key-value pairs
78/// # Ok(HashMap::new())
79/// }
80///
81/// async fn save(&self, key: K, value: V) -> Result<(), PersistentError> {
82/// // Implementation for saving a key-value pair
83/// # Ok(())
84/// }
85///
86/// async fn delete(&self, key: &K) -> Result<(), PersistentError> {
87/// // Implementation for deleting a key-value pair
88/// # Ok(())
89/// }
90/// }
91/// ```
92#[async_trait::async_trait]
93pub trait StorageBackend<K, V>
94where
95 K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
96 V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
97{
98 /// Load all key-value pairs from the storage backend.
99 ///
100 /// This method is called when initializing a `PersistentMap` to populate
101 /// the in-memory map with existing data.
102 async fn load_all(&self) -> Result<HashMap<K, V>, PersistentError>;
103
104 /// Save a key-value pair to the storage backend.
105 ///
106 /// This method is called whenever a key-value pair is inserted into the map.
107 async fn save(&self, key: K, value: V) -> Result<(), PersistentError>;
108
109 /// Delete a key-value pair from the storage backend.
110 ///
111 /// This method is called whenever a key-value pair is removed from the map.
112 async fn delete(&self, key: &K) -> Result<(), PersistentError>;
113
114 /// Flush any buffered writes to the storage backend.
115 ///
116 /// This method is optional and has a default implementation that does nothing.
117 /// Backends that buffer writes should override this method to ensure data is persisted.
118 async fn flush(&self) -> Result<(), PersistentError> {
119 Ok(())
120 }
121}
122
123/// Errors that can occur when using `PersistentMap`.
124///
125/// This enum represents all the possible errors that can occur when using
126/// the various storage backends.
127#[derive(Error, Debug)]
128pub enum PersistentError {
129 /// An error occurred in the SQLite backend.
130 #[cfg(feature = "sqlite")]
131 #[error("sqlite error: {0}")]
132 Sqlite(#[from] tokio_rusqlite::Error),
133
134 /// An error occurred in the CSV backend.
135 #[cfg(feature = "csv_backend")]
136 #[error("csv error: {0}")]
137 Csv(String),
138
139 /// An I/O error occurred.
140 #[error("io error: {0}")]
141 Io(#[from] std::io::Error),
142
143 /// A serialization or deserialization error occurred.
144 #[error("serde error: {0}")]
145 Serde(#[from] serde_json::Error),
146
147 /// An error occurred in the Sled backend.
148 #[cfg(feature = "sled_backend")]
149 #[error("sled error: {0}")]
150 Sled(#[from] sled::Error),
151}
152
153/// Shorthand Result with error defaulting to PersistentError.
154pub type Result<T, E = PersistentError> = std::result::Result<T, E>;
155mod backends;
156pub use backends::*;
157
158/// A persistent key-value map with in-memory caching.
159///
160/// `PersistentMap` combines a fast in-memory `DashMap` with a persistent
161/// storage backend. It provides a simple API for storing and retrieving
162/// key-value pairs, with automatic persistence.
163///
164/// # Type Parameters
165///
166/// * `K`: The key type, which must be hashable, serializable, and cloneable
167/// * `V`: The value type, which must be serializable and cloneable
168/// * `B`: The storage backend type, which must implement `StorageBackend<K, V>`
169///
170/// # Examples
171///
172/// ```rust,no_run
173/// use persistent_map::{PersistentMap, Result};
174/// # #[cfg(feature = "sqlite")]
175/// use persistent_map::sqlite::SqliteBackend;
176///
177/// # #[cfg(feature = "sqlite")]
178/// # async fn example() -> Result<()> {
179/// # // Create a SQLite backend
180/// # let backend = SqliteBackend::new("my_database.db").await?;
181/// #
182/// # // Initialize the persistent map
183/// # let map = PersistentMap::new(backend).await?;
184/// #
185/// # // Insert a key-value pair (persists automatically)
186/// # map.insert("hello".to_string(), "world".to_string()).await?;
187/// #
188/// # // Retrieve a value (from memory)
189/// # assert_eq!(map.get(&"hello".to_string()), Some("world".to_string()));
190/// #
191/// # // Remove a key-value pair (removes from persistence too)
192/// # let old_value = map.remove(&"hello".to_string()).await?;
193/// # assert_eq!(old_value, Some("world".to_string()));
194/// # Ok(())
195/// # }
196/// #
197/// # #[cfg(not(feature = "sqlite"))]
198/// # fn example() {}
199/// ```
200pub struct PersistentMap<K, V, B>
201where
202 K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
203 V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
204 B: StorageBackend<K, V> + Send + Sync + 'static,
205{
206 /// The in-memory map for fast access
207 map: DashMap<K, V>,
208
209 /// The storage backend for persistence
210 backend: B,
211}
212
213impl<K, V, B> PersistentMap<K, V, B>
214where
215 K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
216 V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
217 B: StorageBackend<K, V> + Send + Sync + 'static,
218{
219 /// Creates a new `PersistentMap` with the given storage backend.
220 ///
221 /// This method initializes the map and loads all existing key-value pairs
222 /// from the storage backend into memory.
223 ///
224 /// # Examples
225 ///
226 /// ```rust,no_run
227 /// use persistent_map::{PersistentMap, Result};
228 /// # #[cfg(feature = "sqlite")]
229 /// use persistent_map::sqlite::SqliteBackend;
230 ///
231 /// # #[cfg(feature = "sqlite")]
232 /// # async fn example() -> Result<()> {
233 /// # let backend = SqliteBackend::new("my_database.db").await?;
234 /// # let map: PersistentMap<String, String, _> = PersistentMap::new(backend).await?;
235 /// # Ok(())
236 /// # }
237 /// #
238 /// # #[cfg(not(feature = "sqlite"))]
239 /// # fn example() {}
240 /// ```
241 pub async fn new(backend: B) -> Result<Self> {
242 let map = DashMap::new();
243 let pm = Self { map, backend };
244 pm.load().await?;
245 Ok(pm)
246 }
247
248 /// Loads all key-value pairs from the storage backend into memory.
249 ///
250 /// This method is called automatically when creating a new `PersistentMap`,
251 /// but can also be called manually to refresh the in-memory cache.
252 ///
253 /// # Examples
254 ///
255 /// ```rust,no_run
256 /// # use persistent_map::{PersistentMap, StorageBackend, Result};
257 /// #
258 /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
259 /// // Reload all data from the storage backend
260 /// map.load().await?;
261 /// # Ok(())
262 /// # }
263 /// ```
264 pub async fn load(&self) -> Result<(), PersistentError> {
265 let all = self.backend.load_all().await?;
266 for (k, v) in all {
267 self.map.insert(k, v);
268 }
269 Ok(())
270 }
271
272 /// Inserts a key-value pair into the map and persists it to the storage backend.
273 ///
274 /// If the map already contains the key, the value is updated and the old value
275 /// is returned. Otherwise, `None` is returned.
276 ///
277 /// # Examples
278 ///
279 /// ```rust,no_run
280 /// # use persistent_map::{PersistentMap, StorageBackend, Result};
281 /// #
282 /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
283 /// // Insert a new key-value pair
284 /// let old = map.insert("key".to_string(), "value".to_string()).await?;
285 /// assert_eq!(old, None);
286 ///
287 /// // Update an existing key
288 /// let old = map.insert("key".to_string(), "new value".to_string()).await?;
289 /// assert_eq!(old, Some("value".to_string()));
290 /// # Ok(())
291 /// # }
292 /// ```
293 pub async fn insert(&self, key: K, value: V) -> Result<Option<V>> {
294 let old = self.map.insert(key.clone(), value.clone());
295 self.backend.save(key, value).await?;
296 Ok(old)
297 }
298
299 /// Retrieves a value from the map by its key.
300 ///
301 /// This method only accesses the in-memory map and does not interact with
302 /// the storage backend, making it very fast.
303 ///
304 /// # Examples
305 ///
306 /// ```rust,no_run
307 /// # use persistent_map::{PersistentMap, StorageBackend};
308 /// #
309 /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
310 /// // Get a value
311 /// if let Some(value) = map.get(&"key".to_string()) {
312 /// println!("Value: {}", value);
313 /// }
314 /// # }
315 /// ```
316 pub fn get(&self, key: &K) -> Option<V> {
317 self.map.get(key).map(|r| r.value().clone())
318 }
319
320 /// Removes a key-value pair from the map and the storage backend.
321 ///
322 /// If the map contains the key, the key-value pair is removed and the old value
323 /// is returned. Otherwise, `None` is returned.
324 ///
325 /// # Examples
326 ///
327 /// ```rust,no_run
328 /// # use persistent_map::{PersistentMap, StorageBackend, Result};
329 /// #
330 /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
331 /// // Remove a key-value pair
332 /// let old = map.remove(&"key".to_string()).await?;
333 /// if let Some(value) = old {
334 /// println!("Removed value: {}", value);
335 /// }
336 /// # Ok(())
337 /// # }
338 /// ```
339 pub async fn remove(&self, key: &K) -> Result<Option<V>> {
340 let old = self.map.remove(key).map(|(_, v)| v);
341 if old.is_some() {
342 self.backend.delete(key).await?;
343 }
344 Ok(old)
345 }
346
347 /// Returns the number of key-value pairs in the map.
348 ///
349 /// # Examples
350 ///
351 /// ```rust,no_run
352 /// # use persistent_map::{PersistentMap, StorageBackend};
353 /// #
354 /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
355 /// let count = map.len();
356 /// println!("Map contains {} entries", count);
357 /// # }
358 /// ```
359 pub fn len(&self) -> usize {
360 self.map.len()
361 }
362
363 /// Returns `true` if the map contains no key-value pairs.
364 ///
365 /// # Examples
366 ///
367 /// ```rust,no_run
368 /// # use persistent_map::{PersistentMap, StorageBackend};
369 /// #
370 /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
371 /// if map.is_empty() {
372 /// println!("Map is empty");
373 /// }
374 /// # }
375 /// ```
376 pub fn is_empty(&self) -> bool {
377 self.map.is_empty()
378 }
379
380 /// Returns `true` if the map contains the specified key.
381 ///
382 /// # Examples
383 ///
384 /// ```rust,no_run
385 /// # use persistent_map::{PersistentMap, StorageBackend};
386 /// #
387 /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
388 /// if map.contains_key(&"key".to_string()) {
389 /// println!("Map contains the key");
390 /// }
391 /// # }
392 /// ```
393 pub fn contains_key(&self, key: &K) -> bool {
394 self.map.contains_key(key)
395 }
396
397 /// Clears the in-memory map without affecting the storage backend.
398 ///
399 /// This method only clears the in-memory cache and does not delete any data
400 /// from the storage backend. To completely clear the storage, you should
401 /// delete the underlying storage file or database.
402 ///
403 /// # Examples
404 ///
405 /// ```rust,no_run
406 /// # use persistent_map::{PersistentMap, StorageBackend};
407 /// #
408 /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
409 /// // Clear the in-memory cache
410 /// map.clear();
411 /// assert_eq!(map.len(), 0);
412 /// # }
413 /// ```
414 pub fn clear(&self) {
415 self.map.clear();
416 }
417
418 /// Flushes any buffered writes to the storage backend.
419 ///
420 /// This method is useful for backends that buffer writes for performance.
421 /// It ensures that all data is persisted to the storage medium.
422 ///
423 /// # Examples
424 ///
425 /// ```rust,no_run
426 /// # use persistent_map::{PersistentMap, StorageBackend, Result};
427 /// #
428 /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
429 /// // Ensure all data is persisted
430 /// map.flush().await?;
431 /// # Ok(())
432 /// # }
433 /// ```
434 pub async fn flush(&self) -> Result<(), PersistentError> {
435 self.backend.flush().await
436 }
437
438 /// Returns a reference to the storage backend.
439 ///
440 /// This method is useful for accessing backend-specific functionality.
441 ///
442 /// # Examples
443 ///
444 /// ```rust,no_run
445 /// # use persistent_map::{PersistentMap, StorageBackend};
446 /// #
447 /// # fn example<B>(map: PersistentMap<String, String, B>)
448 /// # where B: StorageBackend<String, String> + Send + Sync
449 /// # {
450 /// let backend = map.backend();
451 /// // Use backend-specific functionality
452 /// # }
453 /// ```
454 pub fn backend(&self) -> &B {
455 &self.backend
456 }
457}