Skip to main content

sqlite_containers/
sqlite_set.rs

1// SQLiteSet
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, SQLiteMap, Size, SizeExt, TryClone, Type,
7    common::{OPEN_FLAGS, OPEN_FLAGS_NOCREATE, OPEN_FLAGS_RDONLY, SQL_TYPE_TEXT, SQL_TYPES, SqlType, check_constraint_violation, verify_schema},
8    rusqlite::{Connection, ToSql, Transaction},
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_set_v2";
19
20static SQL_CREATE_TBL: LazyLock<BTreeMap<SqlType, String>> = LazyLock::new(|| {
21    let mut map = BTreeMap::<SqlType, String>::new();
22    for sql_type in SQL_TYPES {
23        cfg_if! {
24            if #[cfg(feature = "with_rowid")] {
25                map.insert(sql_type, format!("CREATE TABLE IF NOT EXISTS {TABLE_NAME} (key {sql_type} PRIMARY KEY NOT NULL);"));
26            } else {
27                map.insert(sql_type, format!("CREATE TABLE IF NOT EXISTS {TABLE_NAME} (key {sql_type} PRIMARY KEY NOT NULL) WITHOUT ROWID;"));
28            }
29        }
30    }
31    map
32});
33
34cfg_if! {
35    if #[cfg(feature = "with_rowid")] {
36        const SQL_CREATE_NOC: &str = concatcp!("CREATE TABLE IF NOT EXISTS ", TABLE_NAME, " (key TEXT PRIMARY KEY NOT NULL COLLATE NOCASE);");
37    } else {
38        const SQL_CREATE_NOC: &str = concatcp!("CREATE TABLE IF NOT EXISTS ", TABLE_NAME, " (key TEXT PRIMARY KEY NOT NULL COLLATE NOCASE) WITHOUT ROWID;");
39    }
40}
41
42const SQL_GET_SCHEMA: &str = concatcp!("SELECT sql FROM sqlite_schema WHERE type = 'table' AND name = '", TABLE_NAME, "';");
43const SQL_COUNT_KEYS: &str = concatcp!("SELECT COUNT(*) FROM ", TABLE_NAME, ";");
44const SQL_EXIST_KEYS: &str = concatcp!("SELECT EXISTS (SELECT 1 FROM ", TABLE_NAME, ");");
45const SQL_INSERT_KEY: &str = concatcp!("INSERT INTO ", TABLE_NAME, " (key) VALUES (?1);");
46const SQL_EXISTS_KEY: &str = concatcp!("SELECT 1 FROM ", TABLE_NAME, " WHERE key = ?1 LIMIT 1;");
47const SQL_EXISTS_NOC: &str = concatcp!("SELECT 1 FROM ", TABLE_NAME, " WHERE key COLLATE NOCASE = ? LIMIT 1;");
48const SQL_QUERY_KEYS: &str = concatcp!("SELECT key FROM ", TABLE_NAME, ";");
49const SQL_GLOBA_KEYS: &str = concatcp!("SELECT key FROM ", TABLE_NAME, " WHERE key GLOB ?1;");
50const SQL_GLOBN_KEYS: &str = concatcp!("SELECT key FROM ", TABLE_NAME, " WHERE key NOT GLOB ?1;");
51const SQL_DELETE_KEY: &str = concatcp!("DELETE FROM ", TABLE_NAME, " WHERE key = ?1;");
52const SQL_DELETE_NOC: &str = concatcp!("DELETE FROM ", TABLE_NAME, " WHERE key COLLATE NOCASE = ?1;");
53const SQL_DELETE_ALL: &str = concatcp!("DELETE FROM ", TABLE_NAME, ";");
54
55// ---------------------------------------------------------------------------
56// SQLiteSet
57// ---------------------------------------------------------------------------
58
59/// A [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type)) that stores keys of type [`T`](crate::Type), backed by an SQLite in-memory database.
60///
61/// It serves as a drop-in replacement for [`HashSet`](std::collections::HashSet).
62///
63/// By default, `SQLiteSet<String>` treats its keys as case-sensitive, but a case-insensitive variant is available. Even when using the case-sensitive set variant, for some operations a dedicated "case-insensitive" version is provided.
64///
65/// &nbsp;
66///
67/// <div class="warning">
68///
69/// 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.
70///
71/// </div>
72pub struct SQLiteSet<T>
73where
74    T: Type,
75{
76    _type: PhantomData<T>,
77    connection: Connection,
78}
79
80impl<T> SQLiteSet<T>
81where
82    T: Type,
83{
84    /// Creates a new, empty hash set with case-sensitive keys, backed by an *in-memory* SQLite database.
85    #[inline]
86    pub fn new() -> Result<Self, Error> {
87        Self::from_connection(Self::create_connection::<false, &Path>(None, false, false)?)
88    }
89
90    /// Creates a new, empty hash set with case-insensitive keys, backed by an *in-memory* SQLite database.
91    ///
92    /// Only ASCII characters are case folded. SQLite does **not** attempt to do full UTF case folding!
93    #[inline]
94    pub fn with_nocase() -> Result<Self, Error>
95    where
96        T: CharType,
97    {
98        Self::from_connection(Self::create_connection::<true, &Path>(None, false, false)?)
99    }
100
101    /// Creates a new or re-opens an existing hash set with case-sensitive keys, backed by a *file-based* SQLite database.
102    ///
103    /// If `readonly` is `true`, the database file is opened in “read-only” mode.
104    ///
105    /// If `can_create` is `true` (and if `readonly` is `false`), a new empty database file is created, if the file does *not* exist yet.
106    ///
107    /// Returns [`Error::InvalidSchema`], if the database file already exists but its schema does **not** match the schema expected by this type of set. This means that you **must** re-open a database file with the *same* type of set that was used to create it.
108    #[inline]
109    pub fn from_file<P>(path: P, readonly: bool, can_create: bool) -> Result<Self, Error>
110    where
111        P: AsRef<Path>,
112    {
113        Self::from_connection(Self::create_connection::<false, _>(Some(path), readonly, can_create)?)
114    }
115
116    /// Creates a new or re-opens an existing hash set with case-insensitive keys, backed by a *file-based* SQLite database.
117    ///
118    /// If `readonly` is `true`, the database file is opened in “read-only” mode.
119    ///
120    /// If `can_create` is `true` (and if `readonly` is `false`), a new empty database file is created, if the file does *not* exist yet.
121    ///
122    /// Returns [`Error::InvalidSchema`], if the database file already exists but its schema does **not** match the schema expected by this type of set. This means that you **must** re-open a database file with the *same* type of set that was used to create it.
123    ///
124    /// Only ASCII characters are case folded. SQLite does **not** attempt to do full UTF case folding!
125    #[inline]
126    pub fn from_file_nocase<P>(path: P, readonly: bool, can_create: bool) -> Result<Self, Error>
127    where
128        T: CharType,
129        P: AsRef<Path>,
130    {
131        Self::from_connection(Self::create_connection::<true, _>(Some(path), readonly, can_create)?)
132    }
133
134    #[inline(always)]
135    fn create_connection<const NO_CASE: bool, P>(path_option: Option<P>, readonly: bool, can_create: bool) -> Result<Connection, Error>
136    where
137        P: AsRef<Path>,
138    {
139        let connection = if let Some(path) = path_option.as_ref() {
140            Connection::open_with_flags(path, if !readonly { if can_create { *OPEN_FLAGS } else { *OPEN_FLAGS_NOCREATE } } else { *OPEN_FLAGS_RDONLY })?
141        } else {
142            Connection::open_in_memory()?
143        };
144
145        let connection = Self::initialize_connection(connection)?;
146
147        #[cfg(feature = "checked")]
148        if path_option.is_some() {
149            crate::common::integrity_check(&connection)?;
150        }
151
152        let sql_create_table = if !NO_CASE {
153            SQL_CREATE_TBL.get(&T::SQL_TYPE).unwrap()
154        } else {
155            debug_assert_eq!(T::SQL_TYPE, SQL_TYPE_TEXT);
156            SQL_CREATE_NOC
157        };
158
159        connection.execute(sql_create_table, [])?;
160
161        if path_option.is_some() {
162            let sql_schema_table: String = connection.query_one(SQL_GET_SCHEMA, [], |row| row.get(0))?;
163            if !verify_schema(sql_create_table, &sql_schema_table) {
164                return Err(Error::InvalidSchema);
165            }
166        }
167
168        Ok(connection)
169    }
170
171    #[inline(always)]
172    fn initialize_connection(connection: Connection) -> Result<Connection, Error> {
173        connection.pragma_update(None, "synchronous", "OFF")?;
174        connection.pragma_update(None, "journal_mode", "OFF")?;
175        Ok(connection)
176    }
177
178    #[inline(always)]
179    fn from_connection(connection: Connection) -> Result<Self, Error> {
180        Ok(Self { _type: PhantomData, connection })
181    }
182
183    /// Starts a new SQLite transaction for this set.
184    ///
185    /// Please note that using an explicit SQLite transaction allows for much more efficient bulk inserts &#x1F680;
186    ///
187    /// Returns the new [`SQLiteSetTransaction`] instance.
188    #[inline]
189    pub fn transaction(&mut self) -> Result<SQLiteSetTransaction<'_, T>, Error> {
190        SQLiteSetTransaction::from(&mut self.connection)
191    }
192
193    /// Inserts the given key into the set.
194    ///
195    /// Returns `true`, if the set did not already contain the key; otherwise returns `false`.
196    #[inline]
197    pub fn insert<Q>(&mut self, key: &Q) -> Result<bool, Error>
198    where
199        T: Borrow<Q>,
200        Q: ?Sized + ToSql,
201    {
202        let mut statement = self.connection.prepare_cached(SQL_INSERT_KEY)?;
203        match statement.execute([key]) {
204            Ok(_) => Ok(true),
205            Err(error) => check_constraint_violation(error),
206        }
207    }
208
209    /// Inserts all keys from another set into this set, ignoring existing keys.
210    ///
211    /// Returns the number of new keys that have been inserted.
212    #[inline]
213    pub fn union(&mut self, other: &Self) -> Result<Size, Error> {
214        let trx = self.transaction()?;
215        let mut statement_insert = trx.transaction.prepare_cached(SQL_INSERT_KEY)?;
216        let mut statement_query = other.connection.prepare_cached(SQL_QUERY_KEYS)?;
217        let mut counter = Size::default();
218        for key in statement_query.query_map([], |row| row.get::<_, T>(0))? {
219            if match statement_insert.execute([key?]) {
220                Ok(_) => true,
221                Err(error) => check_constraint_violation(error)?,
222            } {
223                counter += 1;
224            }
225        }
226        Ok(counter)
227    }
228
229    /// Checks whether the set contains the specified key.
230    ///
231    /// For case-sensitive sets, the check is case-sensitive; for case-insensitive sets, the check is case-insensitive.
232    ///
233    /// Returns `true`, if the set contains the key; otherwise returns `false`.
234    #[inline]
235    pub fn contains<Q>(&self, key: &Q) -> Result<bool, Error>
236    where
237        T: Borrow<Q>,
238        Q: ?Sized + ToSql,
239    {
240        let mut statement = self.connection.prepare_cached(SQL_EXISTS_KEY)?;
241        Ok(statement.exists([key])?)
242    }
243
244    /// This is the "case-insensitive" version of the [`contains()`](Self::contains) function.
245    ///
246    /// The check is *always* performed case-insensitive.
247    ///
248    /// Returns `true`, if the set contains the key; otherwise returns `false`.
249    #[inline]
250    pub fn contains_nocase<Q>(&self, key: &Q) -> Result<bool, Error>
251    where
252        T: Borrow<Q> + CharType,
253        Q: ?Sized + ToSql,
254    {
255        let mut statement = self.connection.prepare_cached(SQL_EXISTS_NOC)?;
256        Ok(statement.exists([key])?)
257    }
258
259    /// Removes the specified key from the set, if present.
260    ///
261    /// For case-sensitive sets, the key is treated as case-sensitive; for case-insensitive sets, it is treated as case-insensitive.
262    ///
263    /// Returns `true`, if the set contained the key; otherwise returns `false`.
264    #[inline]
265    pub fn remove<Q>(&mut self, key: &Q) -> Result<bool, Error>
266    where
267        T: Borrow<Q>,
268        Q: ?Sized + ToSql,
269    {
270        let mut statement = self.connection.prepare_cached(SQL_DELETE_KEY)?;
271        Ok(statement.execute([key])? != 0)
272    }
273
274    /// This is the "case-insensitive" version of the [`remove()`](Self::remove) function.
275    ///
276    /// The key is *always* treated as case-insensitive.
277    ///
278    /// Returns `true`, if the set contained the key; otherwise returns `false`.
279    #[inline]
280    pub fn remove_nocase<Q>(&mut self, key: &Q) -> Result<bool, Error>
281    where
282        T: Borrow<Q> + CharType,
283        Q: ?Sized + ToSql,
284    {
285        let mut statement = self.connection.prepare_cached(SQL_DELETE_NOC)?;
286        Ok(statement.execute([key])? != 0)
287    }
288
289    /// Invokes the given `callback` function for each key that is currently contained in the set.
290    ///
291    /// This function does **not** guarantee a specific iteration order.
292    #[inline]
293    pub fn for_each<F>(&self, mut callback: F) -> Result<(), Error>
294    where
295        F: FnMut(T),
296    {
297        let mut statement = self.connection.prepare_cached(SQL_QUERY_KEYS)?;
298        for key in statement.query_map([], |row| row.get(0))? {
299            callback(key?);
300        }
301        Ok(())
302    }
303
304    /// Searches the set for the first key that satisfies the given `predicate`.
305    ///
306    /// Returns the first key that satisfies the given predicate, or `None` if **no** key satisfies the predicate or the set is empty.
307    ///
308    /// This function does **not** guarantee a specific iteration order.
309    ///
310    /// Also, the predicate is **not** always tested on *all* keys, because the function returns at the first match.
311    #[inline]
312    pub fn find<P, Q>(&self, predicate: P) -> Result<Option<T>, Error>
313    where
314        P: Fn(&Q) -> bool,
315        T: Borrow<Q>,
316        Q: ?Sized,
317    {
318        let mut statement = self.connection.prepare_cached(SQL_QUERY_KEYS)?;
319        for key in statement.query_map([], |row| row.get(0))? {
320            let key: T = key?;
321            if predicate(key.borrow()) {
322                return Ok(Some(key));
323            }
324        }
325        Ok(None)
326    }
327
328    /// Invokes the given `callback` function for each key that matches the given pattern.
329    ///
330    /// 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.
331    ///
332    /// This function is case-sensitive and does **not** guarantee a specific iteration order.
333    ///
334    /// If `invert` is `true`, returns all keys that do **not** match the pattern; otherwise the matching keys are returned.
335    #[inline]
336    pub fn glob<F, Q>(&self, invert: bool, pattern: &Q, mut callback: F) -> Result<(), Error>
337    where
338        F: FnMut(T),
339        T: Borrow<Q> + CharType,
340        Q: ?Sized + ToSql,
341    {
342        let mut statement = self.connection.prepare_cached(if invert { SQL_GLOBN_KEYS } else { SQL_GLOBA_KEYS })?;
343        for key in statement.query_map([pattern], |row| row.get(0))? {
344            callback(key?);
345        }
346        Ok(())
347    }
348
349    /// Returns the number of unique keys in the set.
350    #[inline]
351    pub fn len(&self) -> Result<Size, Error> {
352        let mut statement = self.connection.prepare_cached(SQL_COUNT_KEYS)?;
353        let count: i64 = statement.query_one([], |row| row.get(0))?;
354        Ok(Size::from_i64(count))
355    }
356
357    /// Returns `true` if the set contains **no** keys; otherwise returns `false`.
358    #[inline]
359    pub fn is_empty(&self) -> Result<bool, Error> {
360        let mut statement = self.connection.prepare_cached(SQL_EXIST_KEYS)?;
361        let any_elements: bool = statement.query_one([], |row| row.get(0))?;
362        Ok(!any_elements)
363    }
364
365    /// Removes *all* keys from the set.
366    #[inline]
367    pub fn clear(&mut self) -> Result<(), Error> {
368        let mut statement = self.connection.prepare_cached(SQL_DELETE_ALL)?;
369        statement.execute([])?;
370        Ok(())
371    }
372
373    #[cfg(feature = "backup")]
374    #[inline]
375    /// Stores all contents of this set in the specified database file.
376    ///
377    /// The file will be replaced, if it already exists!
378    pub fn to_file<P>(&self, path: P) -> Result<(), Error>
379    where
380        P: AsRef<Path>,
381    {
382        let mut connection_out = Self::initialize_connection(Connection::open_with_flags(path, *OPEN_FLAGS)?)?;
383        crate::common::backup_db(&self.connection, &mut connection_out)?;
384        Ok(())
385    }
386}
387
388cfg_if! {
389    if #[cfg(feature = "backup")] {
390        impl<T> TryClone for SQLiteSet<T>
391        where
392            T: Type,
393        {
394            type Error = Error;
395
396            /// Creates an exact, independent *copy* of the given set instance.
397            ///
398            /// This function fails with [`Error::Unsupported`](crate::Error::Unsupported), if the optional [`backup`](crate#optional-features) feature is **not** enabled &#128549;
399            #[inline]
400            fn try_clone(&self) -> Result<Self, Self::Error> {
401                let mut connection_cloned = Self::initialize_connection(Connection::open_in_memory()?)?;
402                crate::common::backup_db(&self.connection, &mut connection_cloned)?;
403                Self::from_connection(connection_cloned)
404            }
405        }
406    } else {
407        impl<T> TryClone for SQLiteSet<T>
408        where
409            T: Type,
410        {
411            type Error = Error;
412
413            /// Creates an exact, independent *copy* of the given set instance.
414            ///
415            /// This function fails with [`Error::Unsupported`](crate::Error::Unsupported), if the optional [`backup`](crate#optional-features) feature is **not** enabled &#128549;
416            #[inline]
417            fn try_clone(&self) -> Result<Self, Self::Error> {
418                Err(Error::Unsupported)
419            }
420        }
421    }
422}
423
424// ---------------------------------------------------------------------------
425// SQLiteSet Transaction
426// ---------------------------------------------------------------------------
427
428/// Represents an active SQLite transaction for a [`SQLiteSet`].
429///
430/// Most functions provided by this struct mirror the corresponding functions of the `SQLiteSet` struct.
431///
432/// The transaction is committed when the `SQLiteSetTransaction` is dropped.
433pub struct SQLiteSetTransaction<'a, T>
434where
435    T: Type,
436{
437    _type: PhantomData<T>,
438    transaction: Transaction<'a>,
439}
440
441impl<'a, T> SQLiteSetTransaction<'a, T>
442where
443    T: Type,
444{
445    #[inline]
446    fn from(connection: &'a mut Connection) -> Result<Self, Error> {
447        let mut transaction = connection.transaction()?;
448        transaction.set_drop_behavior(rusqlite::DropBehavior::Commit);
449        Ok(Self { _type: PhantomData, transaction })
450    }
451
452    /// Drops the `SQLiteSetTransaction`, thereby committing the SQLite transaction.
453    #[inline]
454    pub fn commit(self) {}
455
456    /// This function is equivalent to [`SQLiteSet::insert()`].
457    #[inline]
458    pub fn insert<Q>(&mut self, key: &Q) -> Result<bool, Error>
459    where
460        T: Borrow<Q>,
461        Q: ?Sized + ToSql,
462    {
463        let mut statement = self.transaction.prepare_cached(SQL_INSERT_KEY)?;
464        match statement.execute([key]) {
465            Ok(_) => Ok(true),
466            Err(error) => check_constraint_violation(error),
467        }
468    }
469
470    /// Insert all keys from the given [`SQLiteMap`] into this set.
471    #[inline]
472    pub(crate) fn insert_mapped_keys<Q>(&mut self, map: &mut SQLiteMap<T, Q>) -> Result<(), Error>
473    where
474        Q: Type,
475    {
476        let mut statement = self.transaction.prepare_cached(SQL_INSERT_KEY)?;
477        let trx = map.transaction()?;
478        trx.try_for_each_key(|key| match statement.execute([key]) {
479            Ok(_) => Ok(()),
480            Err(error) => check_constraint_violation(error).map(|_| ()),
481        })
482    }
483
484    /// This function is equivalent to [`SQLiteSet::contains()`].
485    #[inline]
486    pub fn contains<Q>(&self, key: &Q) -> Result<bool, Error>
487    where
488        T: Borrow<Q>,
489        Q: ?Sized + ToSql,
490    {
491        let mut statement = self.transaction.prepare_cached(SQL_EXISTS_KEY)?;
492        Ok(statement.exists([key])?)
493    }
494
495    /// This function is equivalent to [`SQLiteSet::contains_nocase()`].
496    #[inline]
497    pub fn contains_nocase<Q>(&self, key: &Q) -> Result<bool, Error>
498    where
499        T: Borrow<Q> + CharType,
500        Q: ?Sized + ToSql,
501    {
502        let mut statement = self.transaction.prepare_cached(SQL_EXISTS_NOC)?;
503        Ok(statement.exists([key])?)
504    }
505
506    /// This function is equivalent to [`SQLiteSet::remove()`].
507    #[inline]
508    pub fn remove<Q>(&mut self, key: &Q) -> Result<bool, Error>
509    where
510        T: Borrow<Q>,
511        Q: ?Sized + ToSql,
512    {
513        let mut statement = self.transaction.prepare_cached(SQL_DELETE_KEY)?;
514        Ok(statement.execute([key])? != 0)
515    }
516
517    /// This function is equivalent to [`SQLiteSet::remove_nocase()`].
518    #[inline]
519    pub fn remove_nocase<Q>(&mut self, key: &Q) -> Result<bool, Error>
520    where
521        T: Borrow<Q> + CharType,
522        Q: ?Sized + ToSql,
523    {
524        let mut statement = self.transaction.prepare_cached(SQL_DELETE_NOC)?;
525        Ok(statement.execute([key])? != 0)
526    }
527
528    /// This function is equivalent to [`SQLiteSet::for_each()`].
529    #[inline]
530    pub fn for_each<F>(&self, mut callback: F) -> Result<(), Error>
531    where
532        F: FnMut(T),
533    {
534        let mut statement = self.transaction.prepare_cached(SQL_QUERY_KEYS)?;
535        for key in statement.query_map([], |row| row.get(0))? {
536            callback(key?);
537        }
538        Ok(())
539    }
540
541    /// This function is equivalent to [`SQLiteSet::find()`].
542    #[inline]
543    pub fn find<P, Q>(&self, predicate: P) -> Result<Option<T>, Error>
544    where
545        P: Fn(&Q) -> bool,
546        T: Borrow<Q>,
547        Q: ?Sized,
548    {
549        let mut statement = self.transaction.prepare_cached(SQL_QUERY_KEYS)?;
550        for key in statement.query_map([], |row| row.get(0))? {
551            let key: T = key?;
552            if predicate(key.borrow()) {
553                return Ok(Some(key));
554            }
555        }
556        Ok(None)
557    }
558
559    /// This function is equivalent to [`SQLiteSet::glob()`].
560    #[inline]
561    pub fn glob<F, Q>(&self, invert: bool, pattern: &Q, mut callback: F) -> Result<(), Error>
562    where
563        F: FnMut(T),
564        T: Borrow<Q> + CharType,
565        Q: ?Sized + ToSql,
566    {
567        let mut statement = self.transaction.prepare_cached(if invert { SQL_GLOBN_KEYS } else { SQL_GLOBA_KEYS })?;
568        for key in statement.query_map([pattern], |row| row.get(0))? {
569            callback(key?);
570        }
571        Ok(())
572    }
573
574    /// This function is equivalent to [`SQLiteSet::len()`].
575    #[inline]
576    pub fn len(&self) -> Result<Size, Error> {
577        let mut statement = self.transaction.prepare_cached(SQL_COUNT_KEYS)?;
578        let count: i64 = statement.query_one([], |row| row.get(0))?;
579        Ok(Size::from_i64(count))
580    }
581
582    /// This function is equivalent to [`SQLiteSet::is_empty()`].
583    #[inline]
584    pub fn is_empty(&self) -> Result<bool, Error> {
585        let mut statement = self.transaction.prepare_cached(SQL_EXIST_KEYS)?;
586        let any_elements: bool = statement.query_one([], |row| row.get(0))?;
587        Ok(!any_elements)
588    }
589
590    /// This function is equivalent to [`SQLiteSet::clear()`].
591    #[inline]
592    pub fn clear(&mut self) -> Result<(), Error> {
593        let mut statement = self.transaction.prepare_cached(SQL_DELETE_ALL)?;
594        statement.execute([])?;
595        Ok(())
596    }
597}