persistent_map/backends/
sqlite.rs

1//! SQLite backend implementation for PersistentMap.
2//!
3//! This module provides a SQLite-based storage backend for PersistentMap.
4//! It uses tokio-rusqlite for asynchronous SQLite operations.
5
6use crate::StorageBackend;
7use crate::{PersistentError, Result};
8use serde::{de::DeserializeOwned, Serialize};
9use std::{collections::HashMap, hash::Hash, str::FromStr};
10use tokio_rusqlite::{params, Connection};
11
12/// A SQLite-based storage backend for PersistentMap.
13///
14/// This backend stores key-value pairs in a SQLite database, providing
15/// durable persistence with good performance characteristics.
16///
17/// # Examples
18///
19/// ```rust,no_run
20/// use persistent_map::{PersistentMap, Result};
21/// use persistent_map::sqlite::SqliteBackend;
22///
23/// # async fn example() -> Result<()> {
24/// // Create a SQLite backend
25/// let backend = SqliteBackend::new("my_database.db").await?;
26///
27/// // Initialize a PersistentMap with the backend
28/// let map: PersistentMap<String, String, _> = PersistentMap::new(backend).await?;
29/// # Ok(())
30/// # }
31/// ```
32#[derive(Debug)]
33pub struct SqliteBackend {
34    /// The SQLite connection
35    conn: Connection,
36}
37
38impl SqliteBackend {
39    /// Creates a new SQLite backend with the given database path.
40    ///
41    /// This method opens a connection to the SQLite database at the specified path
42    /// and creates the necessary table if it doesn't exist.
43    ///
44    /// # Arguments
45    ///
46    /// * `db_path` - The path to the SQLite database file
47    ///
48    /// # Returns
49    ///
50    /// A `Result` containing the new `SqliteBackend` or an error
51    ///
52    /// # Examples
53    ///
54    /// ```rust,no_run
55    /// use persistent_map::sqlite::SqliteBackend;
56    /// use persistent_map::Result;
57    ///
58    /// # async fn example() -> Result<()> {
59    /// let backend = SqliteBackend::new("my_database.db").await?;
60    /// # Ok(())
61    /// # }
62    /// ```
63    pub async fn new(db_path: &str) -> Result<Self> {
64        let conn = Connection::open(db_path).await?;
65        conn.call(|c| {
66            c.execute(
67                "CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)",
68                [],
69            )
70            .map_err(tokio_rusqlite::Error::Rusqlite)
71        })
72        .await?;
73
74        // Create an index for faster lookups if it doesn't exist
75        conn.call(|c| {
76            c.execute("CREATE INDEX IF NOT EXISTS kv_key_idx ON kv (key)", [])
77                .map_err(tokio_rusqlite::Error::Rusqlite)
78        })
79        .await?;
80
81        Ok(Self { conn })
82    }
83
84    /// Returns the path to the SQLite database file.
85    ///
86    /// # Returns
87    ///
88    /// A `Result` containing the path to the database file or an error
89    pub async fn db_path(&self) -> Result<String> {
90        let result = self
91            .conn
92            .call(|c| {
93                c.query_row("PRAGMA database_list", [], |row| {
94                    let path: String = row.get(2)?;
95                    Ok(path)
96                })
97                .map_err(tokio_rusqlite::Error::Rusqlite)
98            })
99            .await?; // Use ? to convert the error type
100
101        Ok(result)
102    }
103}
104
105/// Implementation of the `StorageBackend` trait for `SqliteBackend`.
106///
107/// This implementation provides methods for loading, saving, and deleting
108/// key-value pairs from a SQLite database.
109#[async_trait::async_trait]
110impl<K, V> StorageBackend<K, V> for SqliteBackend
111where
112    K: Eq
113        + Hash
114        + Clone
115        + Serialize
116        + DeserializeOwned
117        + Send
118        + Sync
119        + 'static
120        + ToString
121        + FromStr,
122    <K as FromStr>::Err: std::error::Error + Send + Sync + 'static,
123    V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
124{
125    /// Loads all key-value pairs from the SQLite database.
126    ///
127    /// This method queries the database for all key-value pairs and deserializes
128    /// them into the appropriate types.
129    async fn load_all(&self) -> Result<HashMap<K, V>, PersistentError> {
130        let rows = self
131            .conn
132            .call(|c| {
133                let mut stmt = c.prepare_cached("SELECT key, value FROM kv")?;
134                let mut map = HashMap::with_capacity(100); // Pre-allocate for better performance
135                let mut rows_iter = stmt.query_map([], |r| {
136                    let key_str: String = r.get(0)?;
137                    let val_str: String = r.get(1)?;
138                    Ok((key_str, val_str))
139                })?;
140
141                while let Some(Ok((k_str, v_str))) = rows_iter.next() {
142                    // Deserialize the value from JSON
143                    let value: V = serde_json::from_str(&v_str)
144                        .map_err(|e| tokio_rusqlite::Error::Other(Box::new(e)))?;
145
146                    // Parse the key from string
147                    let key = k_str
148                        .parse()
149                        .map_err(|e| tokio_rusqlite::Error::Other(Box::new(e)))?;
150
151                    map.insert(key, value);
152                }
153                Ok(map)
154            })
155            .await?;
156        Ok(rows)
157    }
158
159    /// Saves a key-value pair to the SQLite database.
160    ///
161    /// This method serializes the key and value to strings and inserts or
162    /// replaces them in the database.
163    async fn save(&self, key: K, value: V) -> Result<(), PersistentError> {
164        let key_str = key.to_string();
165        let val_json = serde_json::to_string(&value)?;
166
167        self.conn
168            .call(move |c| {
169                c.execute(
170                    "INSERT OR REPLACE INTO kv (key, value) VALUES (?1, ?2)",
171                    params![key_str, val_json],
172                )
173                .map_err(tokio_rusqlite::Error::Rusqlite)
174            })
175            .await?;
176
177        Ok(())
178    }
179
180    /// Deletes a key-value pair from the SQLite database.
181    ///
182    /// This method removes the key-value pair with the specified key from the database.
183    async fn delete(&self, key: &K) -> Result<(), PersistentError> {
184        let key_str = key.to_string();
185
186        self.conn
187            .call(move |c| {
188                c.execute("DELETE FROM kv WHERE key = ?1", params![key_str])
189                    .map_err(tokio_rusqlite::Error::Rusqlite)
190            })
191            .await?;
192
193        Ok(())
194    }
195
196    /// Flushes any buffered writes to the SQLite database.
197    ///
198    /// This method ensures that all data is written to disk by executing
199    /// a PRAGMA synchronous command.
200    async fn flush(&self) -> Result<(), PersistentError> {
201        self.conn
202            .call(|c| {
203                c.execute("PRAGMA synchronous = FULL", [])
204                    .map_err(tokio_rusqlite::Error::Rusqlite)
205            })
206            .await?;
207
208        Ok(())
209    }
210}