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 crate::{
6    CharType, Error, Size, SizeExt, Type,
7    common::{OPEN_FLAGS, SQL_TYPE_TEXT, SQL_TYPES, SqlType, check_constraint_violation},
8    rusqlite::{Connection, OptionalExtension, ToSql, Transaction, params},
9};
10use cfg_if::cfg_if;
11use const_format::concatcp;
12use std::{borrow::Borrow, collections::BTreeMap, marker::PhantomData, path::Path, sync::LazyLock};
13
14// ---------------------------------------------------------------------------
15// Statements
16// ---------------------------------------------------------------------------
17
18const TABLE_NAME: &str = "rusqlite_map_v2";
19
20static SQL_CREATE_TBL: LazyLock<BTreeMap<(SqlType, SqlType), String>> = LazyLock::new(|| {
21    let mut map = BTreeMap::<(SqlType, SqlType), String>::new();
22    for key_type in SQL_TYPES {
23        for value_type in SQL_TYPES {
24            cfg_if! {
25                if #[cfg(feature = "with_rowid")] {
26                    map.insert((key_type, value_type), format!("CREATE TABLE IF NOT EXISTS {TABLE_NAME} (key {key_type} PRIMARY KEY NOT NULL, value {value_type} NOT NULL);"));
27                } else {
28                    map.insert((key_type, value_type), format!("CREATE TABLE IF NOT EXISTS {TABLE_NAME} (key {key_type} PRIMARY KEY NOT NULL, value {value_type} NOT NULL) WITHOUT ROWID;"));
29                }
30            }
31        }
32    }
33    map
34});
35
36static SQL_CREATE_NOC: LazyLock<BTreeMap<SqlType, String>> = LazyLock::new(|| {
37    let mut map = BTreeMap::<SqlType, String>::new();
38    for value_type in SQL_TYPES {
39        cfg_if! {
40            if #[cfg(feature = "with_rowid")] {
41                map.insert(value_type, format!("CREATE TABLE IF NOT EXISTS {TABLE_NAME} (key TEXT PRIMARY KEY NOT NULL COLLATE NOCASE, value {value_type} NOT NULL);"));
42            } else {
43                map.insert(value_type, format!("CREATE TABLE IF NOT EXISTS {TABLE_NAME} (key TEXT PRIMARY KEY NOT NULL COLLATE NOCASE, value {value_type} NOT NULL) WITHOUT ROWID;"));
44            }
45        }
46    }
47    map
48});
49
50const SQL_COUNT_KEYS: &str = concatcp!("SELECT COUNT(*) FROM ", TABLE_NAME, ";");
51const SQL_EXIST_KEYS: &str = concatcp!("SELECT EXISTS (SELECT 1 FROM ", TABLE_NAME, ");");
52const SQL_INSERT_KEY: &str = concatcp!("INSERT INTO ", TABLE_NAME, " (key, value) VALUES (?1, ?2);");
53const SQL_UPSERT_KEY: &str = concatcp!("INSERT INTO ", TABLE_NAME, " (key, value) VALUES (?1, ?2) ON CONFLICT(key) DO UPDATE SET value = excluded.value;");
54const SQL_EXISTS_KEY: &str = concatcp!("SELECT 1 FROM ", TABLE_NAME, " WHERE key = ? LIMIT 1;");
55const SQL_EXISTS_NOC: &str = concatcp!("SELECT 1 FROM ", TABLE_NAME, " WHERE key COLLATE NOCASE = ? LIMIT 1;");
56const SQL_LOOKUP_KEY: &str = concatcp!("SELECT value FROM ", TABLE_NAME, " WHERE key = ? LIMIT 1;");
57const SQL_LOOKUP_NOC: &str = concatcp!("SELECT value FROM ", TABLE_NAME, " WHERE key COLLATE NOCASE = ? LIMIT 1;");
58const SQL_QUERY_KEYS: &str = concatcp!("SELECT key FROM ", TABLE_NAME, ";");
59const SQL_QUERY_PAIR: &str = concatcp!("SELECT key, value FROM ", TABLE_NAME, ";");
60const SQL_GLOBA_KEYS: &str = concatcp!("SELECT key FROM ", TABLE_NAME, " WHERE key GLOB ?1;");
61const SQL_GLOBN_KEYS: &str = concatcp!("SELECT key FROM ", TABLE_NAME, " WHERE key NOT GLOB ?1;");
62const SQL_GLOBA_PAIR: &str = concatcp!("SELECT key, value FROM ", TABLE_NAME, " WHERE key GLOB ?1;");
63const SQL_GLOBN_PAIR: &str = concatcp!("SELECT key, value FROM ", TABLE_NAME, " WHERE key NOT GLOB ?1;");
64const SQL_DELETE_KEY: &str = concatcp!("DELETE FROM ", TABLE_NAME, " WHERE key = ?;");
65const SQL_DELETE_NOC: &str = concatcp!("DELETE FROM ", TABLE_NAME, " WHERE key COLLATE NOCASE = ?;");
66const SQL_DELETE_ALL: &str = concatcp!("DELETE FROM ", TABLE_NAME, ";");
67
68// ---------------------------------------------------------------------------
69// SQLiteMap
70// ---------------------------------------------------------------------------
71
72/// A [map](https://en.wikipedia.org/wiki/Associative_array) that stores key-value pairs with keys of type [`K`](crate::Type) and values of type [`V`](crate::Type), backed by an SQLite in-memory database.
73///
74/// It serves as a drop-in replacement for [`HashMap`](std::collections::HashMap).
75///
76/// By default, `SQLiteMap<String>` 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.
77///
78/// &nbsp;
79///
80/// <div class="warning">
81///
82/// 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.
83///
84/// </div>
85pub struct SQLiteMap<K, V>
86where
87    K: Type,
88    V: Type,
89{
90    _types: (PhantomData<K>, PhantomData<V>),
91    connection: Connection,
92}
93
94impl<K, V> SQLiteMap<K, V>
95where
96    K: Type,
97    V: Type,
98{
99    /// Creates a new, empty hash map with case-sensitive keys, backed by an *in-memory* SQLite database.
100    #[inline]
101    pub fn new() -> Result<Self, Error> {
102        Ok(Self { _types: (PhantomData, PhantomData), connection: Self::initialize_connection::<false, &Path>(None)? })
103    }
104
105    /// Creates a new, empty hash map with case-insensitive keys, backed by an *in-memory* SQLite database.
106    ///
107    /// Only ASCII characters are case folded. SQLite does **not** attempt to do full UTF case folding!
108    #[inline]
109    pub fn with_nocase() -> Result<Self, Error>
110    where
111        K: CharType,
112    {
113        Ok(Self { _types: (PhantomData, PhantomData), connection: Self::initialize_connection::<true, &Path>(None)? })
114    }
115
116    /// Creates a new or re-opens an existing hash map, backed by a *file-based* SQLite database.
117    ///
118    /// A new map is created with case-sensitive keys. However, if the database file already exists, its case-sensitivity is preserved.
119    #[inline]
120    pub fn with_file<P>(path: P) -> Result<Self, Error>
121    where
122        P: AsRef<Path>,
123    {
124        Ok(Self { _types: (PhantomData, PhantomData), connection: Self::initialize_connection::<false, _>(Some(path))? })
125    }
126
127    /// Creates a new or re-opens an existing hash map, backed by a *file-based* SQLite database.
128    ///
129    /// A new map is created with case-insensitive keys. However, if the database file already exists, its case-sensitivity is preserved.
130    ///
131    /// Only ASCII characters are case folded. SQLite does **not** attempt to do full UTF case folding!
132    #[inline]
133    pub fn with_file_nocase<P>(path: P) -> Result<Self, Error>
134    where
135        K: CharType,
136        P: AsRef<Path>,
137    {
138        Ok(Self { _types: (PhantomData, PhantomData), connection: Self::initialize_connection::<true, _>(Some(path))? })
139    }
140
141    #[inline(always)]
142    fn initialize_connection<const NO_CASE: bool, P>(path_option: Option<P>) -> Result<Connection, Error>
143    where
144        P: AsRef<Path>,
145    {
146        let connection = if let Some(path) = path_option.as_ref() { Connection::open_with_flags(path, *OPEN_FLAGS)? } else { Connection::open_in_memory()? };
147
148        connection.pragma_update(None, "journal_mode", "OFF")?;
149        connection.pragma_update(None, "synchronous", "OFF")?;
150
151        if path_option.is_some() {
152            connection.pragma_update(None, "temp_store", "MEMORY")?;
153        }
154
155        if !NO_CASE {
156            connection.execute(SQL_CREATE_TBL.get(&(K::SQL_TYPE, V::SQL_TYPE)).unwrap(), [])?;
157        } else {
158            assert_eq!(K::SQL_TYPE, SQL_TYPE_TEXT);
159            connection.execute(SQL_CREATE_NOC.get(&V::SQL_TYPE).unwrap(), [])?;
160        }
161
162        Ok(connection)
163    }
164
165    /// Starts a new SQLite transaction for this map.
166    ///
167    /// Please note that using an explicit SQLite transaction allows for much more efficient bulk inserts &#x1F680;
168    ///
169    /// Returns the new [`SQLiteMapTransaction`] instance.
170    #[inline]
171    pub fn transaction(&mut self) -> Result<SQLiteMapTransaction<'_, K, V>, Error> {
172        SQLiteMapTransaction::from(&mut self.connection)
173    }
174
175    /// Inserts the given key-value pair into the map, if the specified key does *not* exist yet.
176    ///
177    /// If the map already contains the specified key, then its associated value is **not** changed by this operation!
178    ///
179    /// Returns `true`, if the key-value pair was inserted; otherwise returns `false`.
180    ///
181    /// Please use the [`update()`](Self::update) function to update the value associated with a key that may already exist.
182    #[inline]
183    pub fn insert<Q, R>(&mut self, key: &Q, value: &R) -> Result<bool, Error>
184    where
185        K: Borrow<Q>,
186        Q: ?Sized + ToSql,
187        V: Borrow<R>,
188        R: ?Sized + ToSql,
189    {
190        let mut statement = self.connection.prepare_cached(SQL_INSERT_KEY)?;
191        match statement.execute(params![key, value]) {
192            Ok(_) => Ok(true),
193            Err(error) => check_constraint_violation(error),
194        }
195    }
196
197    /// Associates the specified value with the specified key, regardless of whether the key already exists in the map.
198    ///
199    /// If the map does *not* already contain the specified key, then the key is inserted automatically.
200    ///
201    /// **Note:** Due to limitations in SQLite, it is *not* possible to determine whether the key was inserted or updated.
202    #[inline]
203    pub fn update<Q, R>(&mut self, key: &Q, value: &R) -> Result<(), Error>
204    where
205        K: Borrow<Q>,
206        Q: ?Sized + ToSql,
207        V: Borrow<R>,
208        R: ?Sized + ToSql,
209    {
210        let mut statement = self.connection.prepare_cached(SQL_UPSERT_KEY)?;
211        statement.execute(params![key, value])?;
212        Ok(())
213    }
214
215    /// Checks whether the map contains the specified key.
216    ///
217    /// For case-sensitive maps, the check is case-sensitive; for case-insensitive maps, the check is case-insensitive.
218    ///
219    /// Returns `true`, if the map contains the key; otherwise returns `false`.
220    #[inline]
221    pub fn contains_key<Q>(&self, key: &Q) -> Result<bool, Error>
222    where
223        K: Borrow<Q>,
224        Q: ?Sized + ToSql,
225    {
226        let mut statement = self.connection.prepare_cached(SQL_EXISTS_KEY)?;
227        Ok(statement.exists([key])?)
228    }
229
230    /// This is the "case-insensitive" version of the [`contains()`](Self::contains_key) function.
231    ///
232    /// The check is *always* performed case-insensitive.
233    ///
234    /// Returns `true`, if the map contains the key; otherwise returns `false`.
235    #[inline]
236    pub fn contains_key_nocase<Q>(&self, key: &Q) -> Result<bool, Error>
237    where
238        K: Borrow<Q> + CharType,
239        Q: ?Sized + ToSql,
240    {
241        let mut statement = self.connection.prepare_cached(SQL_EXISTS_NOC)?;
242        Ok(statement.exists([key])?)
243    }
244
245    /// Tries to retrieve the value for the specified key.
246    ///
247    /// For case-sensitive maps, the key is treated as case-sensitive; for case-insensitive maps, it is treated as case-insensitive.
248    ///
249    /// Returns the `Some(value)`, if the map contains the key; otherwise returns `None`.
250    #[inline]
251    pub fn get<Q>(&self, key: &Q) -> Result<Option<V>, Error>
252    where
253        K: Borrow<Q>,
254        Q: ?Sized + ToSql,
255    {
256        let mut statement = self.connection.prepare_cached(SQL_LOOKUP_KEY)?;
257        Ok(statement.query_one([key], |row| row.get(0)).optional()?)
258    }
259
260    /// This is the "case-insensitive" version of the [`get()`](Self::get) function.
261    ///
262    /// The key is *always* treated as case-insensitive.
263    ///
264    /// Returns the `Some(value)`, if the map contains the key; otherwise returns `None`.
265    #[inline]
266    pub fn get_nocase<Q>(&self, key: &Q) -> Result<Option<V>, Error>
267    where
268        K: Borrow<Q> + CharType,
269        Q: ?Sized + ToSql,
270    {
271        let mut statement = self.connection.prepare_cached(SQL_LOOKUP_NOC)?;
272        Ok(statement.query_one([key], |row| row.get(0)).optional()?)
273    }
274
275    /// Removes the specified key from the map, if present.
276    ///
277    /// For case-sensitive maps, the key is treated as case-sensitive; for case-insensitive maps, it is treated as case-insensitive.
278    ///
279    /// Returns `true`, if the map contained the key; otherwise returns `false`.
280    #[inline]
281    pub fn remove<Q>(&mut self, key: &Q) -> Result<bool, Error>
282    where
283        K: Borrow<Q>,
284        Q: ?Sized + ToSql,
285    {
286        let mut statement = self.connection.prepare_cached(SQL_DELETE_KEY)?;
287        Ok(statement.execute([key])? != 0)
288    }
289
290    /// This is the "case-insensitive" version of the [`remove()`](Self::remove) function.
291    ///
292    /// The key is *always* treated as case-insensitive.
293    ///
294    /// Returns `true`, if the map contained the key; otherwise returns `false`.
295    #[inline]
296    pub fn remove_nocase<Q>(&mut self, key: &Q) -> Result<bool, Error>
297    where
298        K: Borrow<Q> + CharType,
299        Q: ?Sized + ToSql,
300    {
301        let mut statement = self.connection.prepare_cached(SQL_DELETE_NOC)?;
302        Ok(statement.execute([key])? != 0)
303    }
304
305    /// Invokes the given `callback` function for each key that is currently contained in the map.
306    ///
307    /// This function does **not** guarantee a specific iteration order.
308    #[inline]
309    pub fn for_each_key<F>(&self, mut callback: F) -> Result<(), Error>
310    where
311        F: FnMut(K),
312    {
313        let mut statement = self.connection.prepare_cached(SQL_QUERY_KEYS)?;
314        for key in statement.query_map([], |row| row.get(0))? {
315            callback(key?);
316        }
317        Ok(())
318    }
319
320    /// Invokes the given `callback` function for each key-value pair that is currently contained in the map.
321    ///
322    /// This function does **not** guarantee a specific iteration order.
323    #[inline]
324    pub fn for_each_entry<F>(&self, mut callback: F) -> Result<(), Error>
325    where
326        F: FnMut(K, V),
327    {
328        let mut statement = self.connection.prepare_cached(SQL_QUERY_PAIR)?;
329        let mut result = statement.query([])?;
330        while let Some(key_value) = result.next()? {
331            let (key, value): (K, V) = (key_value.get(0)?, key_value.get(1)?);
332            callback(key, value);
333        }
334        Ok(())
335    }
336
337    /// Searches the map for the first key that satisfies the given `predicate`.
338    ///
339    /// Returns the first key that satisfies the given predicate, or `None` if none satisfies the predicate or the map is empty.
340    ///
341    /// This function does **not** guarantee a specific iteration order.
342    ///
343    /// Also, the predicate is **not** always tested on *all* keys, because the function returns at the first match.
344    #[inline]
345    pub fn find_key<P>(&self, predicate: P) -> Result<Option<K>, Error>
346    where
347        P: Fn(&K) -> bool,
348    {
349        let mut statement = self.connection.prepare_cached(SQL_QUERY_KEYS)?;
350        for key in statement.query_map([], |row| row.get(0))? {
351            let key: K = key?;
352            if predicate(key.borrow()) {
353                return Ok(Some(key));
354            }
355        }
356        Ok(None)
357    }
358
359    /// Searches the map for the first key-value pair that satisfies the given `predicate`.
360    ///
361    /// Returns the first key-value pair that satisfies the given predicate, or `None` if none satisfies the predicate or the map is empty.
362    ///
363    /// This function does **not** guarantee a specific iteration order.
364    ///
365    /// Also, the predicate is **not** always tested on *all* key-value pairs, because the function returns at the first match.
366    #[inline]
367    pub fn find_entry<P>(&self, predicate: P) -> Result<Option<(K, V)>, Error>
368    where
369        P: Fn(&K, &V) -> bool,
370    {
371        let mut statement = self.connection.prepare_cached(SQL_QUERY_PAIR)?;
372        let mut result = statement.query([])?;
373        while let Some(key_value) = result.next()? {
374            let (key, value): (K, V) = (key_value.get(0)?, key_value.get(1)?);
375            if predicate(&key, &value) {
376                return Ok(Some((key, value)));
377            }
378        }
379        Ok(None)
380    }
381
382    /// Invokes the given `callback` function for each key that matches the given pattern.
383    ///
384    /// The pattern string is interpreted according to the [UNIX *globbing*](https://man7.org/linux/man-pages/man7/glob.7.html) rules. Specifically, a `?` character (not between brackets) matches any single character, and a `*` character (not between brackets) matches any string, including the empty string. Additionally, an expression `[…]` where the first character after the leading `[` is *not* an `!` matches a single character that can be any of the characters enclosed by the brackets; and an expression `[!…]` matches a single character that is *not* matched by the expression.
385    ///
386    /// This function is case-sensitive and does **not** guarantee a specific iteration order.
387    ///
388    /// If `invert` is `true`, returns all keys that do **not** match the pattern; otherwise the matching keys are returned.
389    #[inline]
390    pub fn glob_keys<F, Q>(&self, invert: bool, pattern: &Q, mut callback: F) -> Result<(), Error>
391    where
392        F: FnMut(K),
393        K: Borrow<Q> + CharType,
394        Q: ?Sized + ToSql,
395    {
396        let mut statement = self.connection.prepare_cached(if invert { SQL_GLOBN_KEYS } else { SQL_GLOBA_KEYS })?;
397        for key in statement.query_map([pattern], |row| row.get(0))? {
398            callback(key?);
399        }
400        Ok(())
401    }
402
403    /// Invokes the given `callback` function for each key-value pair whose key matches the given pattern.
404    ///
405    /// The pattern string is interpreted according to the [UNIX *globbing*](https://man7.org/linux/man-pages/man7/glob.7.html) rules. Specifically, a `?` character (not between brackets) matches any single character, and a `*` character (not between brackets) matches any string, including the empty string. Additionally, an expression `[…]` where the first character after the leading `[` is *not* an `!` matches a single character that can be any of the characters enclosed by the brackets; and an expression `[!…]` matches a single character that is *not* matched by the expression.
406    ///
407    /// This function is case-sensitive and does **not** guarantee a specific iteration order.
408    ///
409    /// If `invert` is `true`, returns all key-value pairs that do **not** match the pattern; otherwise the matching pairs are returned.
410    #[inline]
411    pub fn glob_entries<F, Q>(&self, invert: bool, pattern: &Q, mut callback: F) -> Result<(), Error>
412    where
413        F: FnMut(K, V),
414        K: Borrow<Q> + CharType,
415        Q: ?Sized + ToSql,
416    {
417        let mut statement = self.connection.prepare_cached(if invert { SQL_GLOBN_PAIR } else { SQL_GLOBA_PAIR })?;
418        let mut result = statement.query([pattern])?;
419        while let Some(key_value) = result.next()? {
420            let (key, value): (K, V) = (key_value.get(0)?, key_value.get(1)?);
421            callback(key, value);
422        }
423        Ok(())
424    }
425
426    /// Returns the number of unique keys in the map.
427    #[inline]
428    pub fn len(&self) -> Result<Size, Error> {
429        let mut statement = self.connection.prepare_cached(SQL_COUNT_KEYS)?;
430        let count: i64 = statement.query_one([], |row| row.get(0))?;
431        Ok(Size::from_i64(count))
432    }
433
434    /// Returns `true` if the map contains **no** keys; otherwise returns `false`.
435    #[inline]
436    pub fn is_empty(&self) -> Result<bool, Error> {
437        let mut statement = self.connection.prepare_cached(SQL_EXIST_KEYS)?;
438        let any_elements: bool = statement.query_one([], |row| row.get(0))?;
439        Ok(!any_elements)
440    }
441
442    /// Removes *all* keys from the map.
443    #[inline]
444    pub fn clear(&mut self) -> Result<(), Error> {
445        let mut statement = self.connection.prepare_cached(SQL_DELETE_ALL)?;
446        statement.execute([])?;
447        Ok(())
448    }
449}
450
451// ---------------------------------------------------------------------------
452// SQLiteMap Transaction
453// ---------------------------------------------------------------------------
454
455/// Represents an active SQLite transaction for a [`SQLiteMap`].
456///
457/// Most functions provided by this struct mirror the corresponding functions of the `SQLiteMap` struct.
458///
459/// The transaction is committed when the `SQLiteMapTransaction` is dropped.
460pub struct SQLiteMapTransaction<'a, K, V>
461where
462    K: Type,
463    V: Type,
464{
465    _types: (PhantomData<K>, PhantomData<V>),
466    transaction: Transaction<'a>,
467}
468
469impl<'a, K, V> SQLiteMapTransaction<'a, K, V>
470where
471    K: Type,
472    V: Type,
473{
474    #[inline]
475    fn from(connection: &'a mut Connection) -> Result<Self, Error> {
476        let mut transaction = connection.transaction()?;
477        transaction.set_drop_behavior(rusqlite::DropBehavior::Commit);
478        Ok(Self { _types: (PhantomData, PhantomData), transaction })
479    }
480
481    /// Drops the `SQLiteMapTransaction`, thereby committing the SQLite transaction.
482    #[inline]
483    pub fn commit(self) {}
484
485    /// This function is equivalent to [`SQLiteMap::insert()`].
486    #[inline]
487    pub fn insert<Q, R>(&mut self, key: &Q, value: &R) -> Result<bool, Error>
488    where
489        K: Borrow<Q>,
490        Q: ?Sized + ToSql,
491        V: Borrow<R>,
492        R: ?Sized + ToSql,
493    {
494        let mut statement = self.transaction.prepare_cached(SQL_INSERT_KEY)?;
495        match statement.execute(params![key, value]) {
496            Ok(_) => Ok(true),
497            Err(error) => check_constraint_violation(error),
498        }
499    }
500
501    /// This function is equivalent to [`SQLiteMap::update()`].
502    #[inline]
503    pub fn update<Q, R>(&mut self, key: &Q, value: &R) -> Result<(), Error>
504    where
505        K: Borrow<Q>,
506        Q: ?Sized + ToSql,
507        V: Borrow<R>,
508        R: ?Sized + ToSql,
509    {
510        let mut statement = self.transaction.prepare_cached(SQL_UPSERT_KEY)?;
511        statement.execute(params![key, value])?;
512        Ok(())
513    }
514
515    /// This function is equivalent to [`SQLiteMap::contains_key()`].
516    #[inline]
517    pub fn contains_key<Q>(&self, key: &Q) -> Result<bool, Error>
518    where
519        K: Borrow<Q>,
520        Q: ?Sized + ToSql,
521    {
522        let mut statement = self.transaction.prepare_cached(SQL_EXISTS_KEY)?;
523        Ok(statement.exists([key])?)
524    }
525
526    /// This function is equivalent to [`SQLiteMap::contains_key_nocase()`].
527    #[inline]
528    pub fn contains_key_nocase<Q>(&self, key: &Q) -> Result<bool, Error>
529    where
530        K: Borrow<Q> + CharType,
531        Q: ?Sized + ToSql,
532    {
533        let mut statement = self.transaction.prepare_cached(SQL_EXISTS_NOC)?;
534        Ok(statement.exists([key])?)
535    }
536
537    /// This function is equivalent to [`SQLiteMap::get()`].
538    #[inline]
539    pub fn get<Q>(&self, key: &Q) -> Result<Option<V>, Error>
540    where
541        K: Borrow<Q>,
542        Q: ?Sized + ToSql,
543    {
544        let mut statement = self.transaction.prepare_cached(SQL_LOOKUP_KEY)?;
545        Ok(statement.query_one([key], |row| row.get(0)).optional()?)
546    }
547
548    /// This function is equivalent to [`SQLiteMap::get_nocase()`].
549    #[inline]
550    pub fn get_nocase<Q>(&self, key: &Q) -> Result<Option<V>, Error>
551    where
552        K: Borrow<Q> + CharType,
553        Q: ?Sized + ToSql,
554    {
555        let mut statement = self.transaction.prepare_cached(SQL_LOOKUP_NOC)?;
556        Ok(statement.query_one([key], |row| row.get(0)).optional()?)
557    }
558
559    /// This function is equivalent to [`SQLiteMap::remove()`].
560    #[inline]
561    pub fn remove<Q>(&mut self, key: &Q) -> Result<bool, Error>
562    where
563        K: Borrow<Q>,
564        Q: ?Sized + ToSql,
565    {
566        let mut statement = self.transaction.prepare_cached(SQL_DELETE_KEY)?;
567        Ok(statement.execute([key])? != 0)
568    }
569
570    /// This function is equivalent to [`SQLiteMap::remove_nocase()`].
571    #[inline]
572    pub fn remove_nocase<Q>(&mut self, key: &Q) -> Result<bool, Error>
573    where
574        K: Borrow<Q> + CharType,
575        Q: ?Sized + ToSql,
576    {
577        let mut statement = self.transaction.prepare_cached(SQL_DELETE_NOC)?;
578        Ok(statement.execute([key])? != 0)
579    }
580
581    /// This function is equivalent to [`SQLiteMap::for_each_key()`].
582    #[inline]
583    pub fn for_each_key<F>(&self, mut callback: F) -> Result<(), Error>
584    where
585        F: FnMut(K),
586    {
587        let mut statement = self.transaction.prepare_cached(SQL_QUERY_KEYS)?;
588        for key in statement.query_map([], |row| row.get(0))? {
589            callback(key?);
590        }
591        Ok(())
592    }
593
594    /// This function is equivalent to [`SQLiteMap::for_each_entry()`].
595    #[inline]
596    pub fn for_each_entry<F>(&self, mut callback: F) -> Result<(), Error>
597    where
598        F: FnMut(K, V),
599    {
600        let mut statement = self.transaction.prepare_cached(SQL_QUERY_PAIR)?;
601        let mut result = statement.query([])?;
602        while let Some(key_value) = result.next()? {
603            let (key, value): (K, V) = (key_value.get(0)?, key_value.get(1)?);
604            callback(key, value);
605        }
606        Ok(())
607    }
608
609    /// This function is equivalent to [`SQLiteMap::find_key()`].
610    #[inline]
611    pub fn find_key<P>(&self, predicate: P) -> Result<Option<K>, Error>
612    where
613        P: Fn(&K) -> bool,
614    {
615        let mut statement = self.transaction.prepare_cached(SQL_QUERY_KEYS)?;
616        for key in statement.query_map([], |row| row.get(0))? {
617            let key: K = key?;
618            if predicate(key.borrow()) {
619                return Ok(Some(key));
620            }
621        }
622        Ok(None)
623    }
624
625    /// This function is equivalent to [`SQLiteMap::find_entry()`].
626    #[inline]
627    pub fn find_entry<P>(&self, predicate: P) -> Result<Option<(K, V)>, Error>
628    where
629        P: Fn(&K, &V) -> bool,
630    {
631        let mut statement = self.transaction.prepare_cached(SQL_QUERY_PAIR)?;
632        let mut result = statement.query([])?;
633        while let Some(key_value) = result.next()? {
634            let (key, value): (K, V) = (key_value.get(0)?, key_value.get(1)?);
635            if predicate(&key, &value) {
636                return Ok(Some((key, value)));
637            }
638        }
639        Ok(None)
640    }
641
642    /// This function is equivalent to [`SQLiteMap::glob_keys()`].
643    #[inline]
644    pub fn glob_keys<F, Q>(&self, invert: bool, pattern: &Q, mut callback: F) -> Result<(), Error>
645    where
646        F: FnMut(K),
647        K: Borrow<Q> + CharType,
648        Q: ?Sized + ToSql,
649    {
650        let mut statement = self.transaction.prepare_cached(if invert { SQL_GLOBN_KEYS } else { SQL_GLOBA_KEYS })?;
651        for key in statement.query_map([pattern], |row| row.get(0))? {
652            callback(key?);
653        }
654        Ok(())
655    }
656
657    /// This function is equivalent to [`SQLiteMap::glob_entries()`].
658    #[inline]
659    pub fn glob_entries<F, Q>(&self, invert: bool, pattern: &Q, mut callback: F) -> Result<(), Error>
660    where
661        F: FnMut(K, V),
662        K: Borrow<Q> + CharType,
663        Q: ?Sized + ToSql,
664    {
665        let mut statement = self.transaction.prepare_cached(if invert { SQL_GLOBN_PAIR } else { SQL_GLOBA_PAIR })?;
666        let mut result = statement.query([pattern])?;
667        while let Some(key_value) = result.next()? {
668            let (key, value): (K, V) = (key_value.get(0)?, key_value.get(1)?);
669            callback(key, value);
670        }
671        Ok(())
672    }
673
674    /// This function is equivalent to [`SQLiteMap::len()`].
675    #[inline]
676    pub fn len(&self) -> Result<Size, Error> {
677        let mut statement = self.transaction.prepare_cached(SQL_COUNT_KEYS)?;
678        let count: i64 = statement.query_one([], |row| row.get(0))?;
679        Ok(Size::from_i64(count))
680    }
681
682    /// This function is equivalent to [`SQLiteMap::is_empty()`].
683    #[inline]
684    pub fn is_empty(&self) -> Result<bool, Error> {
685        let mut statement = self.transaction.prepare_cached(SQL_EXIST_KEYS)?;
686        let any_elements: bool = statement.query_one([], |row| row.get(0))?;
687        Ok(!any_elements)
688    }
689
690    /// This function is equivalent to [`SQLiteMap::clear()`].
691    #[inline]
692    pub fn clear(&mut self) -> Result<(), Error> {
693        let mut statement = self.transaction.prepare_cached(SQL_DELETE_ALL)?;
694        statement.execute([])?;
695        Ok(())
696    }
697}