Skip to main content

sqlite_containers/
sqlite_map.rs

1// SQLiteMap
2// This file is part of the 'SQLite-based containers for Rust' project (sqlite-containers)
3// SPDX-License-Identifier: Unlicense
4
5use rusqlite::OptionalExtension;
6
7use crate::common::{Error, SizeT, check_constraint_violation};
8use crate::rusqlite::{Connection, Transaction};
9
10// ---------------------------------------------------------------------------
11// Statements
12// ---------------------------------------------------------------------------
13
14const SQL_CREATE_TBL: &str = "CREATE TABLE data (key TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL) WITHOUT ROWID;";
15const SQL_CREATE_NOC: &str = "CREATE TABLE data (key TEXT PRIMARY KEY NOT NULL COLLATE NOCASE, value TEXT NOT NULL) WITHOUT ROWID;";
16const SQL_COUNT_KEYS: &str = "SELECT COUNT(*) FROM data;";
17const SQL_INSERT_KEY: &str = "INSERT INTO data (key, value) VALUES (?1, ?2);";
18const SQL_UPSERT_KEY: &str = "INSERT INTO data (key, value) VALUES (?1, ?2) ON CONFLICT(key) DO UPDATE SET value = excluded.value;";
19const SQL_EXISTS_KEY: &str = "SELECT 1 FROM data WHERE key = ? LIMIT 1;";
20const SQL_EXISTS_NOC: &str = "SELECT 1 FROM data WHERE key COLLATE NOCASE = ? LIMIT 1;";
21const SQL_LOOKUP_KEY: &str = "SELECT value FROM data WHERE key = ? LIMIT 1;";
22const SQL_LOOKUP_NOC: &str = "SELECT value FROM data WHERE key COLLATE NOCASE = ? LIMIT 1;";
23const SQL_QUERY_KEYS: &str = "SELECT key, value FROM data;";
24const SQL_DELETE_KEY: &str = "DELETE FROM data WHERE key = ?;";
25const SQL_DELETE_NOC: &str = "DELETE FROM data WHERE key COLLATE NOCASE = ?;";
26const SQL_DELETE_ALL: &str = "DELETE FROM data;";
27
28// ---------------------------------------------------------------------------
29// SQLiteMap
30// ---------------------------------------------------------------------------
31
32/// A [hash map](https://doc.rust-lang.org/std/collections/struct.HashMap.html) with [string](https://doc.rust-lang.org/beta/std/string/struct.String.html) keys and values, backed by an SQLite in-memory database.
33///
34/// By default, `SQLiteMap` treats its keys as case-sensitive, but a case-insensitive variant is available. Even when using the case-sensitive map variant, for some operations a dedicated "case-insensitive" version is provided.
35///
36/// <div class="warning">
37///
38/// **Important:** For bulk inserts or updates, it is **strongly recommended** to use an explicit [transaction](Self::transaction). Without one, SQLite executes each insert or update in its own transaction, which can significantly degrade performance.
39///
40/// </div>
41pub struct SQLiteMap {
42    connection: Connection,
43}
44
45impl SQLiteMap {
46    /// Creates a new, empty SQLite-backed hash map with case-sensitive keys.
47    #[inline]
48    pub fn new() -> Result<Self, Error> {
49        Ok(Self { connection: Self::initialize_connection(false)? })
50    }
51
52    /// Creates a new, empty SQLite-backed hash map with case-insensitive keys.
53    #[inline]
54    pub fn with_nocase() -> Result<Self, Error> {
55        Ok(Self { connection: Self::initialize_connection(true)? })
56    }
57
58    #[inline]
59    fn initialize_connection(no_case: bool) -> Result<Connection, Error> {
60        let connection = Connection::open_in_memory()?;
61        connection.pragma_update(None, "journal_mode", "OFF")?;
62        connection.pragma_update(None, "synchronous", "OFF")?;
63        connection.pragma_update(None, "temp_store", "MEMORY")?;
64        if !no_case {
65            connection.execute(SQL_CREATE_TBL, [])?;
66        } else {
67            connection.execute(SQL_CREATE_NOC, [])?;
68        }
69        Ok(connection)
70    }
71
72    /// Starts a new SQLite transaction for this map.
73    ///
74    /// Please note that using an explicit SQLite transaction allows for much more efficient bulk inserts &#x1F680;
75    ///
76    /// Returns the new [`SQLiteMapTransaction`] instance.
77    #[inline]
78    pub fn transaction(&mut self) -> Result<SQLiteMapTransaction<'_>, Error> {
79        SQLiteMapTransaction::from(&mut self.connection)
80    }
81
82    /// Tries to insert the given key-value pair into the map.
83    ///
84    /// If the map already contains the specified key, then its associated value is **not** updated to the new value!
85    ///
86    /// Returns `true`, if the key-value pair was inserted; otherwise returns `false`.
87    ///
88    /// Please use the [`update()`](Self::update) function to update the value associated with a key that may already exist.
89    #[inline]
90    pub fn insert(&mut self, key: &str, value: &str) -> Result<bool, Error> {
91        let mut insert = self.connection.prepare_cached(SQL_INSERT_KEY)?;
92        match insert.execute([key, value]) {
93            Ok(_) => Ok(true),
94            Err(error) => check_constraint_violation(error),
95        }
96    }
97
98    /// Associates the specified value with the specified key.
99    ///
100    /// If the map does *not* already contain the specified key, then the key is inserted automatically.
101    ///
102    /// Due to limitations in SQLite, it is *not* possible to determine whether the key-value pair was inserted or updated.
103    #[inline]
104    pub fn update(&mut self, key: &str, value: &str) -> Result<(), Error> {
105        let mut update = self.connection.prepare_cached(SQL_UPSERT_KEY)?;
106        update.execute([key, value])?;
107        Ok(())
108    }
109
110    /// Checks whether the map contains the specified key.
111    ///
112    /// For case-sensitive maps, the check is case-sensitive; for case-insensitive maps, the check is case-insensitive.
113    ///
114    /// Returns `true`, if the map contains the key; otherwise returns `false`.
115    #[inline]
116    pub fn contains(&self, key: &str) -> Result<bool, Error> {
117        let mut contains = self.connection.prepare_cached(SQL_EXISTS_KEY)?;
118        Ok(contains.exists([key])?)
119    }
120
121    /// This is the "case-insensitive" version of the [`contains()`](Self::contains) function.
122    ///
123    /// The check is *always* performed case-insensitive.
124    ///
125    /// Returns `true`, if the map contains the key; otherwise returns `false`.
126    #[inline]
127    pub fn contains_nocase(&self, key: &str) -> Result<bool, Error> {
128        let mut contains = self.connection.prepare_cached(SQL_EXISTS_NOC)?;
129        Ok(contains.exists([key])?)
130    }
131
132    /// Tries to retrieve the value for the specified key.
133    ///
134    /// For case-sensitive maps, the key is treated as case-sensitive; for case-insensitive maps, it is treated as case-insensitive.
135    ///
136    /// Returns the `Some(value)`, if the map contains the key; otherwise returns `None`.
137    #[inline]
138    pub fn get(&self, key: &str) -> Result<Option<String>, Error> {
139        let mut get = self.connection.prepare_cached(SQL_LOOKUP_KEY)?;
140        Ok(get.query_one([key], |row| row.get(0)).optional()?)
141    }
142
143    /// This is the "case-insensitive" version of the [`get()`](Self::get) function.
144    ///
145    /// The key is *always* treated as case-insensitive.
146    ///
147    /// Returns the `Some(value)`, if the map contains the key; otherwise returns `None`.
148    #[inline]
149    pub fn get_nocase(&self, key: &str) -> Result<Option<String>, Error> {
150        let mut get = self.connection.prepare_cached(SQL_LOOKUP_NOC)?;
151        Ok(get.query_one([key], |row| row.get(0)).optional()?)
152    }
153
154    /// Removes the specified key from the map, if present.
155    ///
156    /// For case-sensitive maps, the key is treated as case-sensitive; for case-insensitive maps, it is treated as case-insensitive.
157    ///
158    /// Returns `true`, if the map contained the key; otherwise returns `false`.
159    #[inline]
160    pub fn remove(&mut self, key: &str) -> Result<bool, Error> {
161        let mut contains = self.connection.prepare_cached(SQL_DELETE_KEY)?;
162        Ok(contains.execute([key])? != 0)
163    }
164
165    /// This is the "case-insensitive" version of the [`remove()`](Self::remove) function.
166    ///
167    /// The key is *always* treated as case-insensitive.
168    ///
169    /// Returns `true`, if the map contained the key; otherwise returns `false`.
170    #[inline]
171    pub fn remove_nocase(&mut self, key: &str) -> Result<bool, Error> {
172        let mut contains = self.connection.prepare_cached(SQL_DELETE_NOC)?;
173        Ok(contains.execute([key])? != 0)
174    }
175
176    /// Invokes the given `callback` function for each key-value pair that is currently contained in the map.
177    ///
178    /// This function does **not** guarantee a specific iteration order.
179    #[inline]
180    pub fn for_each<F>(&self, mut callback: F) -> Result<(), Error>
181    where
182        F: FnMut(&str, &str),
183    {
184        let mut iter = self.connection.prepare_cached(SQL_QUERY_KEYS)?;
185        let mut result = iter.query([])?;
186        while let Some(current_item) = result.next()? {
187            let key: String = current_item.get(0)?;
188            let value: String = current_item.get(1)?;
189            callback(&key, &value);
190        }
191        Ok(())
192    }
193
194    /// Searches the map for the first key-value pair that satisfies the given `predicate`.
195    ///
196    /// Returns the first key-value pair that satisfies the given predicate, or `None` if none satisfies the predicate or the map is empty.
197    ///
198    /// This function does **not** guarantee a specific iteration order.
199    ///
200    /// Also, the predicate is **not** always tested on *all* key-value pairs, because the function returns at the first match.
201    #[inline]
202    pub fn find<P>(&self, predicate: P) -> Result<Option<(String, String)>, Error>
203    where
204        P: Fn(&str, &str) -> bool,
205    {
206        let mut iter = self.connection.prepare_cached(SQL_QUERY_KEYS)?;
207        let mut result = iter.query([])?;
208        while let Some(current_item) = result.next()? {
209            let key: String = current_item.get(0)?;
210            let value: String = current_item.get(1)?;
211            if predicate(&key, &value) {
212                return Ok(Some((key, value)));
213            }
214        }
215        Ok(None)
216    }
217
218    /// Returns the number of unique keys in the map.
219    #[inline]
220    pub fn len(&self) -> Result<SizeT, Error> {
221        let mut query_count = self.connection.prepare_cached(SQL_COUNT_KEYS)?;
222        let count: i64 = query_count.query_one([], |row| row.get(0))?;
223        Ok(count.try_into().unwrap_or_default())
224    }
225
226    /// Returns `true` if the map contains **no** keys; otherwise returns `false`.
227    #[inline]
228    pub fn is_empty(&self) -> Result<bool, Error> {
229        Ok(self.len()? == 0)
230    }
231
232    /// Removes *all* keys from the map.
233    #[inline]
234    pub fn clear(&mut self) -> Result<(), Error> {
235        let mut clear = self.connection.prepare_cached(SQL_DELETE_ALL)?;
236        clear.execute([])?;
237        Ok(())
238    }
239}
240
241impl Default for SQLiteMap {
242    /// Returns a new, empty map, as created by the [`SQLiteMap::new()`] function.
243    ///
244    /// # Panics
245    ///
246    /// Panics if a new `SQLiteMap` instance could **not** be created, e.g., because of an SQLite error.
247    #[inline]
248    fn default() -> Self {
249        Self::new().expect("Failed to create SQLiteMap instance!")
250    }
251}
252
253// ---------------------------------------------------------------------------
254// SQLiteMap Transaction
255// ---------------------------------------------------------------------------
256
257/// Represents an active SQLite transaction for a [`SQLiteMap`].
258///
259/// Most functions provided by this struct mirror the corresponding functions of the `SQLiteMap` struct.
260///
261/// The transaction is committed when the `SQLiteMapTransaction` is dropped.
262pub struct SQLiteMapTransaction<'a> {
263    transaction: Transaction<'a>,
264}
265
266impl<'a> SQLiteMapTransaction<'a> {
267    #[inline]
268    fn from(connection: &'a mut Connection) -> Result<Self, Error> {
269        let mut transaction = connection.transaction()?;
270        transaction.set_drop_behavior(rusqlite::DropBehavior::Commit);
271        Ok(Self { transaction })
272    }
273
274    /// Drops the `SQLiteMapTransaction`, thereby committing the SQLite transaction.
275    #[inline]
276    pub fn commit(self) {}
277
278    /// This function is equivalent to [`SQLiteMap::insert()`].
279    #[inline]
280    pub fn insert(&mut self, key: &str, value: &str) -> Result<bool, Error> {
281        let mut insert = self.transaction.prepare_cached(SQL_INSERT_KEY)?;
282        match insert.execute([key, value]) {
283            Ok(_) => Ok(true),
284            Err(error) => check_constraint_violation(error),
285        }
286    }
287
288    /// This function is equivalent to [`SQLiteMap::update()`].
289    #[inline]
290    pub fn update(&mut self, key: &str, value: &str) -> Result<(), Error> {
291        let mut update = self.transaction.prepare_cached(SQL_UPSERT_KEY)?;
292        update.execute([key, value])?;
293        Ok(())
294    }
295
296    /// This function is equivalent to [`SQLiteMap::contains()`].
297    #[inline]
298    pub fn contains(&self, key: &str) -> Result<bool, Error> {
299        let mut contains = self.transaction.prepare_cached(SQL_EXISTS_KEY)?;
300        Ok(contains.exists([key])?)
301    }
302
303    /// This function is equivalent to [`SQLiteMap::contains_nocase()`].
304    #[inline]
305    pub fn contains_nocase(&self, key: &str) -> Result<bool, Error> {
306        let mut contains = self.transaction.prepare_cached(SQL_EXISTS_NOC)?;
307        Ok(contains.exists([key])?)
308    }
309
310    /// This function is equivalent to [`SQLiteMap::get()`].
311    #[inline]
312    pub fn get(&self, key: &str) -> Result<Option<String>, Error> {
313        let mut get = self.transaction.prepare_cached(SQL_LOOKUP_KEY)?;
314        Ok(get.query_one([key], |row| row.get(0)).optional()?)
315    }
316
317    /// This function is equivalent to [`SQLiteMap::get_nocase()`].
318    #[inline]
319    pub fn get_nocase(&self, key: &str) -> Result<Option<String>, Error> {
320        let mut get = self.transaction.prepare_cached(SQL_LOOKUP_NOC)?;
321        Ok(get.query_one([key], |row| row.get(0)).optional()?)
322    }
323
324    /// This function is equivalent to [`SQLiteMap::remove()`].
325    #[inline]
326    pub fn remove(&mut self, key: &str) -> Result<bool, Error> {
327        let mut contains = self.transaction.prepare_cached(SQL_DELETE_KEY)?;
328        Ok(contains.execute([key])? != 0)
329    }
330
331    /// This function is equivalent to [`SQLiteMap::remove_nocase()`].
332    #[inline]
333    pub fn remove_nocase(&mut self, key: &str) -> Result<bool, Error> {
334        let mut contains = self.transaction.prepare_cached(SQL_DELETE_NOC)?;
335        Ok(contains.execute([key])? != 0)
336    }
337
338    /// This function is equivalent to [`SQLiteMap::for_each()`].
339    #[inline]
340    pub fn for_each<F>(&self, mut callback: F) -> Result<(), Error>
341    where
342        F: FnMut(&str, &str),
343    {
344        let mut iter = self.transaction.prepare_cached(SQL_QUERY_KEYS)?;
345        let mut result = iter.query([])?;
346        while let Some(current_item) = result.next()? {
347            let key: String = current_item.get(0)?;
348            let value: String = current_item.get(1)?;
349            callback(&key, &value);
350        }
351        Ok(())
352    }
353
354    /// This function is equivalent to [`SQLiteMap::find()`].
355    #[inline]
356    pub fn find<P>(&self, predicate: P) -> Result<Option<(String, String)>, Error>
357    where
358        P: Fn(&str, &str) -> bool,
359    {
360        let mut iter = self.transaction.prepare_cached(SQL_QUERY_KEYS)?;
361        let mut result = iter.query([])?;
362        while let Some(current_item) = result.next()? {
363            let key: String = current_item.get(0)?;
364            let value: String = current_item.get(1)?;
365            if predicate(&key, &value) {
366                return Ok(Some((key, value)));
367            }
368        }
369        Ok(None)
370    }
371
372    /// This function is equivalent to [`SQLiteMap::len()`].
373    #[inline]
374    pub fn len(&self) -> Result<SizeT, Error> {
375        let mut query_count = self.transaction.prepare_cached(SQL_COUNT_KEYS)?;
376        let count: i64 = query_count.query_one([], |row| row.get(0))?;
377        Ok(count.try_into().unwrap_or_default())
378    }
379
380    /// This function is equivalent to [`SQLiteMap::is_empty()`].
381    #[inline]
382    pub fn is_empty(&self) -> Result<bool, Error> {
383        Ok(self.len()? == 0)
384    }
385
386    /// This function is equivalent to [`SQLiteMap::clear()`].
387    #[inline]
388    pub fn clear(&mut self) -> Result<(), Error> {
389        let mut clear = self.transaction.prepare_cached(SQL_DELETE_ALL)?;
390        clear.execute([])?;
391        Ok(())
392    }
393}