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}