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}