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}