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>;
155
156// Re-export backends
157#[cfg(feature = "csv_backend")]
158pub use crate::backends::csv;
159
160#[cfg(feature = "in_memory")]
161pub use crate::backends::in_memory;
162
163#[cfg(feature = "sqlite")]
164pub use crate::backends::sqlite;
165
166mod backends;
167
168/// A persistent key-value map with in-memory caching.
169///
170/// `PersistentMap` combines a fast in-memory `DashMap` with a persistent
171/// storage backend. It provides a simple API for storing and retrieving
172/// key-value pairs, with automatic persistence.
173///
174/// # Type Parameters
175///
176/// * `K`: The key type, which must be hashable, serializable, and cloneable
177/// * `V`: The value type, which must be serializable and cloneable
178/// * `B`: The storage backend type, which must implement `StorageBackend<K, V>`
179///
180/// # Examples
181///
182/// ```rust,no_run
183/// use persistent_map::{PersistentMap, Result};
184/// # #[cfg(feature = "sqlite")]
185/// use persistent_map::sqlite::SqliteBackend;
186///
187/// # #[cfg(feature = "sqlite")]
188/// # async fn example() -> Result<()> {
189/// # // Create a SQLite backend
190/// # let backend = SqliteBackend::new("my_database.db").await?;
191/// #
192/// # // Initialize the persistent map
193/// # let map = PersistentMap::new(backend).await?;
194/// #
195/// # // Insert a key-value pair (persists automatically)
196/// # map.insert("hello".to_string(), "world".to_string()).await?;
197/// #
198/// # // Retrieve a value (from memory)
199/// # assert_eq!(map.get(&"hello".to_string()), Some("world".to_string()));
200/// #
201/// # // Remove a key-value pair (removes from persistence too)
202/// # let old_value = map.remove(&"hello".to_string()).await?;
203/// # assert_eq!(old_value, Some("world".to_string()));
204/// # Ok(())
205/// # }
206/// #
207/// # #[cfg(not(feature = "sqlite"))]
208/// # fn example() {}
209/// ```
210pub struct PersistentMap<K, V, B>
211where
212    K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
213    V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
214    B: StorageBackend<K, V> + Send + Sync + 'static,
215{
216    /// The in-memory map for fast access
217    map: DashMap<K, V>,
218
219    /// The storage backend for persistence
220    backend: B,
221}
222
223impl<K, V, B> PersistentMap<K, V, B>
224where
225    K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
226    V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
227    B: StorageBackend<K, V> + Send + Sync + 'static,
228{
229    /// Creates a new `PersistentMap` with the given storage backend.
230    ///
231    /// This method initializes the map and loads all existing key-value pairs
232    /// from the storage backend into memory.
233    ///
234    /// # Examples
235    ///
236    /// ```rust,no_run
237    /// use persistent_map::{PersistentMap, Result};
238    /// # #[cfg(feature = "sqlite")]
239    /// use persistent_map::sqlite::SqliteBackend;
240    ///
241    /// # #[cfg(feature = "sqlite")]
242    /// # async fn example() -> Result<()> {
243    /// # let backend = SqliteBackend::new("my_database.db").await?;
244    /// # let map: PersistentMap<String, String, _> = PersistentMap::new(backend).await?;
245    /// # Ok(())
246    /// # }
247    /// #
248    /// # #[cfg(not(feature = "sqlite"))]
249    /// # fn example() {}
250    /// ```
251    /// # Errors
252    ///
253    /// Returns an error if loading from the backend fails.
254    #[inline]
255    pub async fn new(backend: B) -> Result<Self> {
256        let map = DashMap::new();
257        let pm = Self { map, backend };
258        pm.load().await?;
259        Ok(pm)
260    }
261
262    /// Loads all key-value pairs from the storage backend into memory.
263    ///
264    /// This method is called automatically when creating a new `PersistentMap`,
265    /// but can also be called manually to refresh the in-memory cache.
266    ///
267    /// # Examples
268    ///
269    /// ```rust,no_run
270    /// # use persistent_map::{PersistentMap, StorageBackend, Result};
271    /// #
272    /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
273    /// // Reload all data from the storage backend
274    /// map.load().await?;
275    /// # Ok(())
276    /// # }
277    /// ```
278    /// # Errors
279    ///
280    /// Returns an error if loading from the backend fails.
281    #[inline]
282    pub async fn load(&self) -> Result<(), PersistentError> {
283        let all = self.backend.load_all().await?;
284        for (k, v) in all {
285            self.map.insert(k, v);
286        }
287        Ok(())
288    }
289
290    /// Inserts a key-value pair into the map and persists it to the storage backend.
291    ///
292    /// If the map already contains the key, the value is updated and the old value
293    /// is returned. Otherwise, `None` is returned.
294    ///
295    /// # Examples
296    ///
297    /// ```rust,no_run
298    /// # use persistent_map::{PersistentMap, StorageBackend, Result};
299    /// #
300    /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
301    /// // Insert a new key-value pair
302    /// let old = map.insert("key".to_string(), "value".to_string()).await?;
303    /// assert_eq!(old, None);
304    ///
305    /// // Update an existing key
306    /// let old = map.insert("key".to_string(), "new value".to_string()).await?;
307    /// assert_eq!(old, Some("value".to_string()));
308    /// # Ok(())
309    /// # }
310    /// ```
311    /// # Errors
312    ///
313    /// Returns an error if saving to the backend fails.
314    #[inline]
315    pub async fn insert(&self, key: K, value: V) -> Result<Option<V>> {
316        let old = self.map.insert(key.clone(), value.clone());
317        self.backend.save(key, value).await?;
318        Ok(old)
319    }
320
321    /// Retrieves a value from the map by its key.
322    ///
323    /// This method only accesses the in-memory map and does not interact with
324    /// the storage backend, making it very fast.
325    ///
326    /// # Examples
327    ///
328    /// ```rust,no_run
329    /// # use persistent_map::{PersistentMap, StorageBackend};
330    /// #
331    /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
332    /// // Get a value
333    /// if let Some(value) = map.get(&"key".to_string()) {
334    ///     println!("Value: {}", value);
335    /// }
336    /// # }
337    /// ```
338    #[inline]
339    pub fn get(&self, key: &K) -> Option<V> {
340        self.map.get(key).map(|r| r.value().clone())
341    }
342
343    /// Removes a key-value pair from the map and the storage backend.
344    ///
345    /// If the map contains the key, the key-value pair is removed and the old value
346    /// is returned. Otherwise, `None` is returned.
347    ///
348    /// # Examples
349    ///
350    /// ```rust,no_run
351    /// # use persistent_map::{PersistentMap, StorageBackend, Result};
352    /// #
353    /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
354    /// // Remove a key-value pair
355    /// let old = map.remove(&"key".to_string()).await?;
356    /// if let Some(value) = old {
357    ///     println!("Removed value: {}", value);
358    /// }
359    /// # Ok(())
360    /// # }
361    /// ```
362    /// # Errors
363    ///
364    /// Returns an error if deleting from the backend fails.
365    #[inline]
366    pub async fn remove(&self, key: &K) -> Result<Option<V>> {
367        let old = self.map.remove(key).map(|(_, v)| v);
368        if old.is_some() {
369            match self.backend.delete(key).await {
370                Ok(()) => {}
371                Err(e) => return Err(e),
372            }
373        }
374        Ok(old)
375    }
376
377    /// Returns the number of key-value pairs in the map.
378    ///
379    /// # Examples
380    ///
381    /// ```rust,no_run
382    /// # use persistent_map::{PersistentMap, StorageBackend};
383    /// #
384    /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
385    /// let count = map.len();
386    /// println!("Map contains {} entries", count);
387    /// # }
388    /// ```
389    #[inline]
390    pub fn len(&self) -> usize {
391        self.map.len()
392    }
393
394    /// Returns `true` if the map contains no key-value pairs.
395    ///
396    /// # Examples
397    ///
398    /// ```rust,no_run
399    /// # use persistent_map::{PersistentMap, StorageBackend};
400    /// #
401    /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
402    /// if map.is_empty() {
403    ///     println!("Map is empty");
404    /// }
405    /// # }
406    /// ```
407    #[inline]
408    pub fn is_empty(&self) -> bool {
409        self.map.is_empty()
410    }
411
412    /// Returns `true` if the map contains the specified key.
413    ///
414    /// # Examples
415    ///
416    /// ```rust,no_run
417    /// # use persistent_map::{PersistentMap, StorageBackend};
418    /// #
419    /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
420    /// if map.contains_key(&"key".to_string()) {
421    ///     println!("Map contains the key");
422    /// }
423    /// # }
424    /// ```
425    #[inline]
426    pub fn contains_key(&self, key: &K) -> bool {
427        self.map.contains_key(key)
428    }
429
430    /// Clears the in-memory map without affecting the storage backend.
431    ///
432    /// This method only clears the in-memory cache and does not delete any data
433    /// from the storage backend. To completely clear the storage, you should
434    /// delete the underlying storage file or database.
435    ///
436    /// # Examples
437    ///
438    /// ```rust,no_run
439    /// # use persistent_map::{PersistentMap, StorageBackend};
440    /// #
441    /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
442    /// // Clear the in-memory cache
443    /// map.clear();
444    /// assert_eq!(map.len(), 0);
445    /// # }
446    /// ```
447    #[inline]
448    pub fn clear(&self) {
449        self.map.clear();
450    }
451
452    /// Flushes any buffered writes to the storage backend.
453    ///
454    /// This method is useful for backends that buffer writes for performance.
455    /// It ensures that all data is persisted to the storage medium.
456    ///
457    /// # Examples
458    ///
459    /// ```rust,no_run
460    /// # use persistent_map::{PersistentMap, StorageBackend, Result};
461    /// #
462    /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
463    /// // Ensure all data is persisted
464    /// map.flush().await?;
465    /// # Ok(())
466    /// # }
467    /// ```
468    /// # Errors
469    ///
470    /// Returns an error if flushing the backend fails.
471    #[inline]
472    pub async fn flush(&self) -> Result<(), PersistentError> {
473        self.backend.flush().await
474    }
475
476    /// Returns a reference to the storage backend.
477    ///
478    /// This method is useful for accessing backend-specific functionality.
479    ///
480    /// # Examples
481    ///
482    /// ```rust,no_run
483    /// # use persistent_map::{PersistentMap, StorageBackend};
484    /// #
485    /// # fn example<B>(map: PersistentMap<String, String, B>)
486    /// # where B: StorageBackend<String, String> + Send + Sync
487    /// # {
488    /// let backend = map.backend();
489    /// // Use backend-specific functionality
490    /// # }
491    /// ```
492    #[inline]
493    pub const fn backend(&self) -> &B {
494        &self.backend
495    }
496}