Skip to main content

keyvaluedb_sqlite/
lib.rs

1#![deny(clippy::all)]
2
3mod tools;
4
5pub use async_sqlite::rusqlite::OpenFlags;
6use async_sqlite::rusqlite::{params, OptionalExtension as _};
7use async_sqlite::*;
8use keyvaluedb::{
9    DBKeyRef, DBKeyValue, DBKeyValueRef, DBOp, DBTransaction, DBTransactionError, DBValue, IoStats,
10    IoStatsKind, KeyValueDB, KeyValueDBPinBoxFuture,
11};
12use parking_lot::Mutex;
13use std::sync::Arc;
14use std::time::{Duration, Instant};
15use std::{
16    io,
17    path::{Path, PathBuf},
18    str::FromStr,
19};
20use tools::*;
21
22///////////////////////////////////////////////////////////////////////////////
23
24#[derive(Copy, Clone, Debug, Eq, PartialEq)]
25pub enum VacuumMode {
26    None,
27    Incremental,
28    Full,
29}
30
31/// Database configuration
32#[derive(Clone)]
33pub struct DatabaseConfig {
34    /// Set number of columns.
35    /// The number of columns must not be zero.
36    pub columns: u32,
37    /// Set flags used to open the database
38    pub flags: OpenFlags,
39    /// Number of connections to open
40    pub num_conns: usize,
41    /// Vacuum mode
42    pub vacuum_mode: VacuumMode,
43    /// Run an integrity check at open and, on corruption, salvage the readable
44    /// rows into a fresh database, keeping the damaged files beside it
45    pub repair_on_corrupt: bool,
46}
47
48impl DatabaseConfig {
49    /// Create new `DatabaseConfig` with default parameters
50    pub fn new() -> Self {
51        Default::default()
52    }
53
54    /// Set the number of columns. `columns` must not be zero.
55    pub fn with_columns(self, columns: u32) -> Self {
56        assert!(columns > 0, "the number of columns must not be zero");
57        Self { columns, ..self }
58    }
59
60    /// Enable integrity check and salvage at open
61    pub fn with_repair_on_corrupt(self, repair_on_corrupt: bool) -> Self {
62        Self {
63            repair_on_corrupt,
64            ..self
65        }
66    }
67
68    /// Sets the flags to 'in-memory database'
69    pub fn with_in_memory(self) -> Self {
70        Self {
71            flags: OpenFlags::SQLITE_OPEN_READ_WRITE
72                | OpenFlags::SQLITE_OPEN_CREATE
73                | OpenFlags::SQLITE_OPEN_NO_MUTEX
74                | OpenFlags::SQLITE_OPEN_MEMORY,
75            ..self
76        }
77    }
78
79    /// Replaces all the flags
80    pub fn with_flags(self, flags: OpenFlags) -> Self {
81        Self { flags, ..self }
82    }
83
84    /// Sets the number of connections for this database
85    pub fn with_num_conns(self, num_conns: usize) -> Self {
86        Self { num_conns, ..self }
87    }
88
89    /// Set the vacuum mode used by 'cleanup'
90    pub fn with_vacuum_mode(self, vacuum_mode: VacuumMode) -> Self {
91        Self {
92            vacuum_mode,
93            ..self
94        }
95    }
96}
97
98impl Default for DatabaseConfig {
99    fn default() -> DatabaseConfig {
100        DatabaseConfig {
101            columns: 1,
102            flags: OpenFlags::SQLITE_OPEN_READ_WRITE
103                | OpenFlags::SQLITE_OPEN_CREATE
104                | OpenFlags::SQLITE_OPEN_NO_MUTEX,
105            num_conns: 1,
106            vacuum_mode: VacuumMode::None,
107            repair_on_corrupt: false,
108        }
109    }
110}
111
112///////////////////////////////////////////////////////////////////////////////
113
114/// Whether an error chain bottoms out in sqlite saying the file is damaged
115fn io_error_is_corruption(e: &io::Error) -> bool {
116    fn code_is_corruption(e: &rusqlite::Error) -> bool {
117        matches!(
118            e,
119            rusqlite::Error::SqliteFailure(f, _) if matches!(
120                f.code,
121                rusqlite::ErrorCode::DatabaseCorrupt | rusqlite::ErrorCode::NotADatabase
122            )
123        )
124    }
125    let mut src: Option<&(dyn std::error::Error + 'static)> = e.get_ref().map(|b| b as _);
126    while let Some(cur) = src {
127        if let Some(e) = cur.downcast_ref::<rusqlite::Error>() {
128            return code_is_corruption(e);
129        }
130        if let Some(Error::Rusqlite(e)) = cur.downcast_ref::<Error>() {
131            return code_is_corruption(e);
132        }
133        src = cur.source();
134    }
135    false
136}
137
138///////////////////////////////////////////////////////////////////////////////
139
140/// An sqlite table with its statement strings
141pub struct DatabaseTable {
142    _table: String,
143    str_has_value: String,
144    str_has_value_like: String,
145    str_get_unique_value: String,
146    str_get_first_value_like: String,
147    str_set_unique_value: String,
148    str_remove_unique_value: String,
149    str_remove_and_return_unique_value: String,
150    str_remove_unique_value_like: String,
151    str_iter_with_prefix: String,
152    str_iter_no_prefix: String,
153    str_iter_keys_with_prefix: String,
154    str_iter_keys_no_prefix: String,
155}
156
157impl DatabaseTable {
158    pub fn new(table: String) -> Self {
159        let str_has_value = format!("SELECT 1 FROM {} WHERE [key] = ? LIMIT 1", table);
160        let str_has_value_like = format!(
161            "SELECT 1 FROM {} WHERE [key] LIKE ? ESCAPE '\\' LIMIT 1",
162            table
163        );
164        let str_get_unique_value = format!("SELECT value FROM {} WHERE [key] = ? LIMIT 1", table);
165        let str_get_first_value_like = format!(
166            "SELECT key, value FROM {} WHERE [key] LIKE ? ESCAPE '\\' LIMIT 1",
167            table
168        );
169        let str_set_unique_value = format!(
170            "INSERT OR REPLACE INTO {} ([key], value) VALUES(?, ?)",
171            table
172        );
173        let str_remove_unique_value = format!("DELETE FROM {} WHERE [key] = ?", table);
174        let str_remove_and_return_unique_value =
175            format!("DELETE FROM {} WHERE [key] = ? RETURNING value", table);
176        let str_remove_unique_value_like =
177            format!("DELETE FROM {} WHERE [key] LIKE ? ESCAPE '\\'", table);
178        let str_iter_with_prefix = format!(
179            "SELECT key, value FROM {} WHERE [key] LIKE ? ESCAPE '\\'",
180            table
181        );
182        let str_iter_no_prefix = format!("SELECT key, value FROM {}", table);
183        let str_iter_keys_with_prefix =
184            format!("SELECT key FROM {} WHERE [key] LIKE ? ESCAPE '\\'", table);
185        let str_iter_keys_no_prefix = format!("SELECT key FROM {}", table);
186
187        Self {
188            _table: table,
189            str_has_value,
190            str_has_value_like,
191            str_get_unique_value,
192            str_get_first_value_like,
193            str_set_unique_value,
194            str_remove_unique_value,
195            str_remove_and_return_unique_value,
196            str_remove_unique_value_like,
197            str_iter_with_prefix,
198            str_iter_no_prefix,
199            str_iter_keys_with_prefix,
200            str_iter_keys_no_prefix,
201        }
202    }
203}
204
205///////////////////////////////////////////////////////////////////////////////
206
207/// What the automatic corruption repair did, kept on the database it produced
208#[derive(Debug, Clone)]
209pub struct RepairReport {
210    /// The integrity failure that triggered the repair
211    pub detected: String,
212    /// Rows salvaged across the control table and all column tables
213    pub rows_recovered: u64,
214    /// Tables whose salvage scan ended early on a damaged page
215    pub partial_tables: u32,
216    /// Where the damaged database files were moved
217    pub corrupt_path: PathBuf,
218}
219
220/// An sqlite key-value database fulfilling the `KeyValueDB` trait
221pub struct DatabaseUnlockedInner {
222    path: PathBuf,
223    config: DatabaseConfig,
224    pool: Pool,
225    control_table: Arc<DatabaseTable>,
226    column_tables: Vec<Arc<DatabaseTable>>,
227    repair_report: Option<RepairReport>,
228}
229
230impl Drop for DatabaseUnlockedInner {
231    fn drop(&mut self) {
232        let _ = self.pool.close_blocking();
233    }
234}
235
236pub struct DatabaseInner {
237    overall_stats: IoStats,
238    current_stats: IoStats,
239}
240
241#[derive(Clone)]
242pub struct Database {
243    unlocked_inner: Arc<DatabaseUnlockedInner>,
244    inner: Arc<Mutex<DatabaseInner>>,
245}
246
247impl Database {
248    ////////////////////////////////////////////////////////////////
249    // Initialization
250
251    pub fn open<P: AsRef<Path>>(path: P, config: DatabaseConfig) -> io::Result<Self> {
252        let path = PathBuf::from(path.as_ref());
253        let db = match Self::open_raw(&path, &config, None) {
254            Ok(db) => db,
255            Err(e) if config.repair_on_corrupt && io_error_is_corruption(&e) => {
256                return Self::repair(None, &path, &config, e.to_string());
257            }
258            Err(e) => return Err(e),
259        };
260        if !config.repair_on_corrupt {
261            return Ok(db);
262        }
263        match db.quick_check_blocking() {
264            Ok(None) => Ok(db),
265            Ok(Some(detected)) => Self::repair(Some(db), &path, &config, detected),
266            Err(e) if io_error_is_corruption(&e) => {
267                Self::repair(Some(db), &path, &config, e.to_string())
268            }
269            Err(e) => Err(e),
270        }
271    }
272
273    fn open_raw(
274        path: &Path,
275        config: &DatabaseConfig,
276        repair_report: Option<RepairReport>,
277    ) -> io::Result<Self> {
278        let config = config.clone();
279        assert_ne!(config.columns, 0, "number of columns must be >= 1");
280
281        let path = PathBuf::from(path);
282        let flags = config.flags;
283
284        let mut column_tables = vec![];
285        for n in 0..config.columns {
286            column_tables.push(Arc::new(DatabaseTable::new(get_column_table_name(n))))
287        }
288        let control_table = Arc::new(DatabaseTable::new("control".to_string()));
289
290        let pool_builder = PoolBuilder::new()
291            .path(&path)
292            .flags(flags)
293            .num_conns(config.num_conns);
294
295        let pool = pool_builder.open_blocking().map_err(io::Error::other)?;
296
297        let out = Self {
298            unlocked_inner: Arc::new(DatabaseUnlockedInner {
299                path,
300                config,
301                pool,
302                control_table,
303                column_tables,
304                repair_report,
305            }),
306            inner: Arc::new(Mutex::new(DatabaseInner {
307                overall_stats: IoStats::empty(),
308                current_stats: IoStats::empty(),
309            })),
310        };
311
312        let vacuum_mode = out.config().vacuum_mode;
313
314        out.conn_blocking(move |conn| {
315            // Don't rely on STATEMENT_CACHE_DEFAULT_CAPACITY in rusqlite, set it explicitly
316            conn.set_prepared_statement_cache_capacity(256);
317
318            conn.pragma_update(None, "case_sensitive_like", "ON")?;
319            conn.pragma_update(None, "journal_mode", "WAL")?;
320            conn.pragma_update(None, "synchronous", "normal")?;
321            conn.pragma_update(None, "journal_size_limit", 6144000)?;
322            conn.pragma_update(None, "wal_checkpoint", "TRUNCATE")?;
323
324            match vacuum_mode {
325                VacuumMode::None | VacuumMode::Full => {
326                    let current: u32 =
327                        conn.pragma_query_value(None, "auto_vacuum", |x| x.get(0))?;
328                    if current != 0 {
329                        conn.execute("VACUUM", [])?;
330                        conn.pragma_update(None, "auto_vacuum", 0)?;
331                    }
332                }
333                VacuumMode::Incremental => {
334                    let current: u32 =
335                        conn.pragma_query_value(None, "auto_vacuum", |x| x.get(0))?;
336                    if current != 2 {
337                        conn.execute("VACUUM", [])?;
338                        conn.pragma_update(None, "auto_vacuum", "2")?;
339                    }
340                }
341            }
342
343            Ok(())
344        })
345        .map_err(io::Error::other)?;
346
347        out.open_resize_columns()?;
348
349        Ok(out)
350    }
351
352    pub fn path(&self) -> PathBuf {
353        self.unlocked_inner.path.clone()
354    }
355
356    /// What the open-time repair did, if this database came from one
357    pub fn repair_report(&self) -> Option<&RepairReport> {
358        self.unlocked_inner.repair_report.as_ref()
359    }
360
361    /// Integrity check on one connection. None = clean; Some = what is wrong.
362    /// A check that cannot even run to completion errs with the corruption it hit.
363    fn quick_check_blocking(&self) -> io::Result<Option<String>> {
364        let problems = self
365            .conn_blocking(|conn| {
366                let mut stmt = conn.prepare("PRAGMA quick_check(8)")?;
367                let mut rows = stmt.query([])?;
368                let mut problems: Vec<String> = Vec::new();
369                while let Some(row) = rows.next()? {
370                    problems.push(row.get(0)?);
371                }
372                Ok(problems)
373            })
374            .map_err(io::Error::other)?;
375        if problems.len() == 1 && problems[0] == "ok" {
376            return Ok(None);
377        }
378        Ok(Some(problems.join("; ")))
379    }
380
381    /// Rebuild a damaged database from whatever rows are still readable.
382    ///
383    /// The damaged files move beside the store as `<name>.corrupt` rather than
384    /// being destroyed: corruption that sqlite's own crash-safety should have
385    /// made impossible is evidence worth keeping. `old` is the still-open
386    /// handle when the damage was found by the integrity check; None when the
387    /// file would not even open, in which case nothing is salvageable and the
388    /// result is a fresh empty database.
389    fn repair(
390        old: Option<Self>,
391        path: &Path,
392        config: &DatabaseConfig,
393        detected: String,
394    ) -> io::Result<Self> {
395        let suffixes = ["", "-wal", "-shm"];
396        let sibling = |base: &Path, ext: &str, suffix: &str| {
397            let mut os = base.as_os_str().to_owned();
398            os.push(ext);
399            os.push(suffix);
400            PathBuf::from(os)
401        };
402
403        // Build the replacement in a sibling file, never in place
404        let tmp = sibling(path, ".repairing", "");
405        for suffix in &suffixes {
406            let _ = std::fs::remove_file(sibling(path, ".repairing", suffix));
407        }
408        let fresh = Self::open_raw(&tmp, config, None)?;
409
410        let mut rows_recovered = 0u64;
411        let mut partial_tables = 0u32;
412        if let Some(old) = &old {
413            // Salvage every table the old database says it has, or at least
414            // the configured set if its control table cannot say
415            let old_columns = old
416                .conn_blocking({
417                    let this = old.clone();
418                    move |conn| Self::get_unique_value(conn, this.control_table(), "columns", 0u32)
419                })
420                .unwrap_or(0)
421                .max(config.columns);
422            let mut tables = vec!["control".to_string()];
423            for cn in 0..old_columns {
424                tables.push(get_column_table_name(cn));
425            }
426            for table in tables {
427                let (rows, partial) = old
428                    .conn_blocking({
429                        let table = table.clone();
430                        move |conn| Ok(Self::salvage_table_rows(conn, &table))
431                    })
432                    .map_err(io::Error::other)?;
433                if partial {
434                    partial_tables += 1;
435                }
436                if rows.is_empty() {
437                    continue;
438                }
439                rows_recovered += rows.len() as u64;
440                // INSERT OR IGNORE: values the fresh open already wrote
441                // (control's own column count) win over salvaged ones
442                fresh
443                    .conn_blocking({
444                        let table = table.clone();
445                        move |conn| {
446                            let tx = conn.unchecked_transaction()?;
447                            {
448                                let mut stmt = tx.prepare(&format!(
449                                    "INSERT OR IGNORE INTO {} ([key], value) VALUES (?, ?)",
450                                    table
451                                ))?;
452                                for (k, v) in &rows {
453                                    stmt.execute(params![k, v])?;
454                                }
455                            }
456                            tx.commit()
457                        }
458                    })
459                    .map_err(io::Error::other)?;
460            }
461        }
462
463        // Close both so every file is settled before the swap
464        drop(fresh);
465        drop(old);
466
467        let corrupt_path = sibling(path, ".corrupt", "");
468        for suffix in &suffixes {
469            let _ = std::fs::remove_file(sibling(path, ".corrupt", suffix));
470            let _ = std::fs::rename(sibling(path, "", suffix), sibling(path, ".corrupt", suffix));
471            let _ = std::fs::rename(
472                sibling(path, ".repairing", suffix),
473                sibling(path, "", suffix),
474            );
475        }
476
477        Self::open_raw(
478            path,
479            config,
480            Some(RepairReport {
481                detected,
482                rows_recovered,
483                partial_tables,
484                corrupt_path,
485            }),
486        )
487    }
488
489    /// Read every row a damaged table will still yield, stopping at the first
490    /// page it cannot, keeping what came before it
491    fn salvage_table_rows(
492        conn: &rusqlite::Connection,
493        table: &str,
494    ) -> (Vec<(String, rusqlite::types::Value)>, bool) {
495        let mut out = Vec::new();
496        let mut partial = true;
497        if let Ok(mut stmt) = conn.prepare(&format!("SELECT [key], value FROM {}", table)) {
498            if let Ok(mut rows) = stmt.query([]) {
499                loop {
500                    match rows.next() {
501                        Ok(Some(row)) => {
502                            if let (Ok(k), Ok(v)) = (row.get(0), row.get(1)) {
503                                out.push((k, v));
504                            }
505                        }
506                        Ok(None) => {
507                            partial = false;
508                            break;
509                        }
510                        Err(_) => break,
511                    }
512                }
513            }
514        }
515        (out, partial)
516    }
517
518    pub fn config(&self) -> DatabaseConfig {
519        self.unlocked_inner.config.clone()
520    }
521
522    pub fn columns(&self) -> u32 {
523        self.unlocked_inner.config.columns
524    }
525
526    pub fn control_table(&self) -> Arc<DatabaseTable> {
527        self.unlocked_inner.control_table.clone()
528    }
529
530    pub fn column_table(&self, col: u32) -> Arc<DatabaseTable> {
531        self.unlocked_inner.column_tables[col as usize].clone()
532    }
533
534    pub fn conn_blocking<T, F>(&self, func: F) -> Result<T, Error>
535    where
536        F: FnOnce(&rusqlite::Connection) -> Result<T, rusqlite::Error> + Send + 'static,
537        T: Send + 'static,
538    {
539        self.unlocked_inner.pool.conn_blocking(func)
540    }
541
542    pub async fn conn<T, F>(&self, func: F) -> Result<T, Error>
543    where
544        F: FnOnce(&rusqlite::Connection) -> Result<T, rusqlite::Error> + Send + 'static,
545        T: Send + 'static,
546    {
547        self.unlocked_inner.pool.conn(func).await
548    }
549
550    pub async fn conn_mut<T, F>(&self, func: F) -> Result<T, Error>
551    where
552        F: FnOnce(&mut rusqlite::Connection) -> Result<T, rusqlite::Error> + Send + 'static,
553        T: Send + 'static,
554    {
555        self.unlocked_inner.pool.conn_mut(func).await
556    }
557
558    ////////////////////////////////////////////////////////////////
559    // Low level operations
560
561    /// Remove the last column family in the database. The deletion is definitive.
562    pub fn remove_last_column(&self) -> Result<(), Error> {
563        let this = self.clone();
564        self.conn_blocking(move |conn| {
565            let columns = Self::get_unique_value(conn, this.control_table(), "columns", 0u32)?;
566            if columns == 0 {
567                return Err(rusqlite::Error::QueryReturnedNoRows);
568            }
569            Self::set_unique_value(conn, this.control_table(), "columns", columns - 1)?;
570
571            conn.execute(
572                &format!("DROP TABLE {}", get_column_table_name(columns - 1)),
573                [],
574            )?;
575            Ok(())
576        })
577    }
578
579    /// Add a new column family to the DB.
580    pub fn add_column(&self) -> Result<(), Error> {
581        let this = self.clone();
582
583        self.conn_blocking(move |conn| {
584            let columns = Self::get_unique_value(conn, this.control_table(), "columns", 0u32)?;
585            Self::set_unique_value(conn, this.control_table(), "columns", columns + 1)?;
586            Self::create_column_table(conn, columns)
587        })
588    }
589    /// Helper to create new transaction for this database.
590    pub fn transaction(&self) -> DBTransaction {
591        DBTransaction::new()
592    }
593
594    /// Vacuum database
595    pub async fn vacuum(&self) -> Result<(), Error> {
596        match self.config().vacuum_mode {
597            VacuumMode::None => {
598                self.conn(move |conn| {
599                    conn.pragma_update(None, "wal_checkpoint", "TRUNCATE")?;
600                    Ok(())
601                })
602                .await
603            }
604            VacuumMode::Incremental => {
605                self.conn(move |conn| {
606                    conn.execute("PRAGMA incremental_vacuum", [])?;
607                    conn.pragma_update(None, "wal_checkpoint", "TRUNCATE")?;
608                    Ok(())
609                })
610                .await
611            }
612            VacuumMode::Full => {
613                self.conn(move |conn| {
614                    conn.execute("VACUUM", [])?;
615                    conn.pragma_update(None, "wal_checkpoint", "TRUNCATE")?;
616                    Ok(())
617                })
618                .await
619            }
620        }
621    }
622
623    ////////////////////////////////////////////////////////////////
624    // Internal helpers
625
626    fn validate_column(&self, col: u32) -> rusqlite::Result<()> {
627        if col >= self.columns() {
628            return Err(rusqlite::Error::InvalidColumnIndex(col as usize));
629        }
630        Ok(())
631    }
632
633    fn create_column_table(conn: &rusqlite::Connection, column: u32) -> rusqlite::Result<()> {
634        conn.execute(&format!("CREATE TABLE IF NOT EXISTS {} (id INTEGER PRIMARY KEY AUTOINCREMENT, [key] TEXT UNIQUE, value BLOB)", get_column_table_name(column)), []).map(drop)
635    }
636
637    fn get_unique_value<V>(
638        conn: &rusqlite::Connection,
639        table: Arc<DatabaseTable>,
640        key: &str,
641        default: V,
642    ) -> rusqlite::Result<V>
643    where
644        V: FromStr,
645    {
646        let mut stmt = conn.prepare_cached(&table.str_get_unique_value)?;
647
648        if let Ok(found) = stmt.query_row([key], |row| -> rusqlite::Result<String> { row.get(0) }) {
649            if let Ok(v) = V::from_str(&found) {
650                return Ok(v);
651            }
652        }
653        Ok(default)
654    }
655
656    fn set_unique_value<V>(
657        conn: &rusqlite::Connection,
658        table: Arc<DatabaseTable>,
659        key: &str,
660        value: V,
661    ) -> rusqlite::Result<()>
662    where
663        V: ToString,
664    {
665        let mut stmt = conn.prepare_cached(&table.str_set_unique_value)?;
666
667        let changed = stmt.execute([key, value.to_string().as_str()])?;
668
669        assert!(
670            changed <= 1,
671            "multiple changes to unique key should not occur"
672        );
673        if changed == 0 {
674            return Err(rusqlite::Error::QueryReturnedNoRows);
675        }
676
677        Ok(())
678    }
679
680    fn has_value(
681        conn: &rusqlite::Connection,
682        table: Arc<DatabaseTable>,
683        key: &str,
684    ) -> rusqlite::Result<bool> {
685        let mut stmt = conn.prepare_cached(&table.str_has_value)?;
686        stmt.exists([key])
687    }
688
689    fn has_value_like(
690        conn: &rusqlite::Connection,
691        table: Arc<DatabaseTable>,
692        key: &str,
693    ) -> rusqlite::Result<bool> {
694        let mut stmt = conn.prepare_cached(&table.str_has_value_like)?;
695        stmt.exists([key])
696    }
697
698    fn load_unique_value_blob(
699        conn: &rusqlite::Connection,
700        table: Arc<DatabaseTable>,
701        key: &str,
702    ) -> rusqlite::Result<Option<Vec<u8>>> {
703        let mut stmt = conn.prepare_cached(&table.str_get_unique_value)?;
704
705        stmt.query_row([key], |row| -> rusqlite::Result<Vec<u8>> { row.get(0) })
706            .optional()
707    }
708
709    fn load_first_value_blob_like(
710        conn: &rusqlite::Connection,
711        table: Arc<DatabaseTable>,
712        like: &str,
713    ) -> rusqlite::Result<Option<(String, Vec<u8>)>> {
714        let mut stmt = conn.prepare_cached(&table.str_get_first_value_like)?;
715
716        stmt.query_row([like], |row| -> rusqlite::Result<(String, Vec<u8>)> {
717            Ok((row.get(0)?, row.get(1)?))
718        })
719        .optional()
720    }
721
722    fn store_unique_value_blob(
723        conn: &rusqlite::Connection,
724        table: Arc<DatabaseTable>,
725        key: &str,
726        value: &[u8],
727    ) -> rusqlite::Result<()> {
728        let mut stmt = conn.prepare_cached(&table.str_set_unique_value)?;
729
730        let changed = stmt.execute(params![key, value])?;
731        assert!(
732            changed <= 1,
733            "multiple changes to unique key should not occur"
734        );
735        if changed == 0 {
736            return Err(rusqlite::Error::QueryReturnedNoRows);
737        }
738        Ok(())
739    }
740
741    fn remove_unique_value_blob(
742        conn: &rusqlite::Connection,
743        table: Arc<DatabaseTable>,
744        key: &str,
745    ) -> rusqlite::Result<()> {
746        let mut stmt = conn.prepare_cached(&table.str_remove_unique_value)?;
747
748        let _ = stmt.execute([key])?;
749
750        Ok(())
751    }
752
753    fn remove_and_return_unique_value_blob(
754        conn: &rusqlite::Connection,
755        table: Arc<DatabaseTable>,
756        key: &str,
757    ) -> rusqlite::Result<Option<Vec<u8>>> {
758        let mut stmt = conn.prepare_cached(&table.str_remove_and_return_unique_value)?;
759
760        stmt.query_row([key], |row| -> rusqlite::Result<Vec<u8>> { row.get(0) })
761            .optional()
762    }
763
764    fn remove_unique_value_blob_like(
765        conn: &rusqlite::Connection,
766        table: Arc<DatabaseTable>,
767        like: &str,
768    ) -> rusqlite::Result<usize> {
769        let mut stmt = conn.prepare_cached(&table.str_remove_unique_value_like)?;
770
771        let changed = stmt.execute([like])?;
772        Ok(changed)
773    }
774
775    fn open_resize_columns(&self) -> io::Result<()> {
776        let columns = self.columns();
777        let this = self.clone();
778        self.conn_blocking(move |conn| {
779			// First see if we have a control table with the number of columns
780			conn.execute("CREATE TABLE IF NOT EXISTS control (id INTEGER PRIMARY KEY AUTOINCREMENT, [key] TEXT UNIQUE, value TEXT)", [])?;
781
782            // Get column count
783            let on_disk_columns =
784                Self::get_unique_value(conn, this.control_table(), "columns", 0u32)?;
785
786            // If desired column count is less than or equal to current column count, then allow it, but restrict access to columns
787            if columns <= on_disk_columns {
788                return Ok(());
789            }
790
791            // Otherwise resize and add other columns
792            for cn in on_disk_columns..columns {
793                // Create the column table if we don't have it
794                Self::create_column_table(conn, cn)?;
795            }
796            Self::set_unique_value(
797                conn,
798                this.control_table(),
799                "columns",
800                columns,
801            )?;
802            Ok(())
803        }).map_err(io::Error::other)
804    }
805
806    fn stats_read(&self, count: usize, bytes: usize) {
807        let mut inner = self.inner.lock();
808        inner.current_stats.reads += count as u64;
809        inner.overall_stats.reads += count as u64;
810        inner.current_stats.bytes_read += bytes as u64;
811        inner.overall_stats.bytes_read += bytes as u64;
812    }
813
814    fn stats_write(&self, sizes: &[usize]) {
815        if sizes.is_empty() {
816            return;
817        }
818
819        let mut inner = self.inner.lock();
820        for &size in sizes {
821            inner.current_stats.record_write(size);
822            inner.overall_stats.record_write(size);
823        }
824    }
825
826    fn stats_tx_write(&self, size: usize, duration: Duration) {
827        let mut inner = self.inner.lock();
828        inner
829            .current_stats
830            .record_tx_write(size, duration.as_micros() as f64);
831        inner
832            .overall_stats
833            .record_tx_write(size, duration.as_micros() as f64);
834    }
835
836    fn stats_delete(&self, count: usize) {
837        if count == 0 {
838            return;
839        }
840
841        let mut inner = self.inner.lock();
842        inner.current_stats.deletes += count as u64;
843        inner.overall_stats.deletes += count as u64;
844    }
845
846    fn stats_delete_prefix(&self, count: usize) {
847        if count == 0 {
848            return;
849        }
850
851        let mut inner = self.inner.lock();
852        inner.current_stats.prefix_deletes += count as u64;
853        inner.overall_stats.prefix_deletes += count as u64;
854    }
855
856    fn stats_transaction(&self, count: usize) {
857        let mut inner = self.inner.lock();
858        inner.current_stats.transactions += count as u64;
859        inner.overall_stats.transactions += count as u64;
860    }
861}
862
863impl KeyValueDB for Database {
864    fn get(&self, col: u32, key: &[u8]) -> KeyValueDBPinBoxFuture<'_, io::Result<Option<DBValue>>> {
865        let key_text = key_to_text(key);
866        let key_len = key.len();
867
868        Box::pin(async move {
869            let that = self.clone();
870            that.validate_column(col).map_err(io::Error::other)?;
871            let someval = self
872                .conn_blocking(move |conn| {
873                    Self::load_unique_value_blob(conn, that.column_table(col), &key_text)
874                })
875                .map_err(io::Error::other)?;
876            {
877                match &someval {
878                    Some(val) => self.stats_read(1, key_len + val.len()),
879                    None => self.stats_read(1, key_len),
880                }
881            }
882
883            Ok(someval)
884        })
885    }
886
887    /// Remove a value by key, returning the old value
888    fn delete(
889        &self,
890        col: u32,
891        key: &[u8],
892    ) -> KeyValueDBPinBoxFuture<'_, io::Result<Option<DBValue>>> {
893        let key_text = key_to_text(key);
894        let key_len = key.len();
895
896        Box::pin(async move {
897            let that = self.clone();
898            that.validate_column(col).map_err(io::Error::other)?;
899            self.conn_blocking(move |conn| {
900                let someval = Self::remove_and_return_unique_value_blob(
901                    conn,
902                    that.column_table(col),
903                    &key_text,
904                )?;
905
906                match &someval {
907                    Some(val) => {
908                        that.stats_read(1, key_len + val.len());
909                    }
910                    None => that.stats_read(1, key_len),
911                }
912
913                Ok(someval)
914            })
915            .map_err(io::Error::other)
916        })
917    }
918
919    fn write(
920        &self,
921        transaction: DBTransaction,
922    ) -> KeyValueDBPinBoxFuture<'_, Result<(), DBTransactionError>> {
923        let transaction = Arc::new(transaction);
924        Box::pin(async move {
925            self.stats_transaction(1);
926
927            let that = self.clone();
928            let transaction_clone = transaction.clone();
929            self.conn_mut(move |conn| {
930                let mut sizes = Vec::with_capacity(transaction_clone.ops.len());
931                let mut total_tx_size = 0;
932                let mut deletes = 0usize;
933                let mut prefix_deletes = 0usize;
934                let start = Instant::now();
935
936                let tx = conn.transaction()?;
937
938                for op in &transaction_clone.ops {
939                    match op {
940                        DBOp::Insert { col, key, value } => {
941                            that.validate_column(*col)?;
942                            Self::store_unique_value_blob(
943                                &tx,
944                                that.column_table(*col),
945                                &key_to_text(key),
946                                value,
947                            )?;
948                            sizes.push(key.len() + value.len());
949                            total_tx_size += key.len() + value.len();
950                        }
951                        DBOp::Delete { col, key } => {
952                            that.validate_column(*col)?;
953                            Self::remove_unique_value_blob(
954                                &tx,
955                                that.column_table(*col),
956                                &key_to_text(key),
957                            )?;
958                            deletes += 1;
959                        }
960                        DBOp::DeletePrefix { col, prefix } => {
961                            that.validate_column(*col)?;
962                            Self::remove_unique_value_blob_like(
963                                &tx,
964                                that.column_table(*col),
965                                &(like_key_to_text(prefix) + "%"),
966                            )?;
967                            prefix_deletes += 1;
968                        }
969                    }
970                }
971                tx.commit()?;
972
973                let duration = Instant::now() - start;
974                that.stats_write(&sizes);
975                that.stats_tx_write(total_tx_size, duration);
976                that.stats_delete(deletes);
977                that.stats_delete_prefix(prefix_deletes);
978
979                Ok(())
980            })
981            .await
982            .map_err(io::Error::other)
983            .map_err(|error| {
984                let transaction = transaction.as_ref().clone();
985                DBTransactionError { error, transaction }
986            })
987        })
988    }
989
990    fn iter<
991        'a,
992        T: Send + 'static,
993        C: Send + 'static,
994        F: FnMut(&mut C, DBKeyValueRef) -> io::Result<Option<T>> + Send + Sync + 'static,
995    >(
996        &'a self,
997        col: u32,
998        prefix: Option<&'a [u8]>,
999        context: C,
1000        mut f: F,
1001    ) -> KeyValueDBPinBoxFuture<'a, io::Result<(C, Option<T>)>> {
1002        let opt_prefix_query = prefix.map(|p| like_key_to_text(p) + "%");
1003        Box::pin(async move {
1004            if col >= self.columns() {
1005                return Err(io::Error::from(io::ErrorKind::NotFound));
1006            }
1007
1008            let that = self.clone();
1009            let context = Arc::new(Mutex::new(Some(context)));
1010            let context_ref = context.clone();
1011
1012            let res = self
1013                .conn(move |conn| {
1014                    let mut context = context_ref.lock();
1015                    let context = context.as_mut().unwrap();
1016
1017                    let mut stmt;
1018                    let mut rows;
1019                    if let Some(prefix_query) = opt_prefix_query {
1020                        stmt = match conn
1021                            .prepare_cached(&that.column_table(col).str_iter_with_prefix)
1022                        {
1023                            Ok(v) => v,
1024                            Err(e) => {
1025                                return Ok(Err(io::Error::other(e)));
1026                            }
1027                        };
1028                        rows = match stmt.query([prefix_query]) {
1029                            Ok(v) => v,
1030                            Err(e) => {
1031                                return Ok(Err(io::Error::other(e)));
1032                            }
1033                        };
1034                    } else {
1035                        stmt = match conn.prepare_cached(&that.column_table(col).str_iter_no_prefix)
1036                        {
1037                            Ok(v) => v,
1038                            Err(e) => {
1039                                return Ok(Err(io::Error::other(e)));
1040                            }
1041                        };
1042                        rows = match stmt.query([]) {
1043                            Ok(v) => v,
1044                            Err(e) => {
1045                                return Ok(Err(io::Error::other(e)));
1046                            }
1047                        };
1048                    }
1049
1050                    let mut sw = 0usize;
1051                    let mut sbw = 0usize;
1052
1053                    let out = loop {
1054                        match rows.next() {
1055                            // Iterated value
1056                            Ok(Some(row)) => {
1057                                let kt: String = match row.get(0) {
1058                                    Err(e) => {
1059                                        break Err(io::Error::other(e));
1060                                    }
1061                                    Ok(v) => v,
1062                                };
1063                                let v: Vec<u8> = match row.get(1) {
1064                                    Err(e) => {
1065                                        break Err(io::Error::other(e));
1066                                    }
1067                                    Ok(v) => v,
1068                                };
1069                                let k: Vec<u8> = match text_to_key(&kt) {
1070                                    Err(e) => {
1071                                        break Err(io::Error::other(format!(
1072                                            "SQLite row get column 0 text convert error: {:?}",
1073                                            e
1074                                        )));
1075                                    }
1076                                    Ok(v) => v,
1077                                };
1078
1079                                sw += 1;
1080                                sbw += k.len() + v.len();
1081
1082                                match f(context, (&k, &v)) {
1083                                    Ok(None) => (),
1084                                    Ok(Some(out)) => {
1085                                        // Callback early termination
1086                                        that.stats_read(sw, sbw);
1087                                        break Ok(Some(out));
1088                                    }
1089                                    Err(e) => {
1090                                        // Callback error termination
1091                                        that.stats_read(sw, sbw);
1092                                        break Err(e);
1093                                    }
1094                                }
1095                            }
1096                            // Natural iterator termination
1097                            Ok(None) => {
1098                                break Ok(None);
1099                            }
1100                            // Error iterator termination
1101                            Err(_) => {
1102                                break Ok(None);
1103                            }
1104                        }
1105                    };
1106
1107                    that.stats_read(sw, sbw);
1108
1109                    Ok(out)
1110                })
1111                .await
1112                .map_err(io::Error::other)?;
1113
1114            let context = context.lock().take().unwrap();
1115
1116            res.map(|x| (context, x))
1117        })
1118    }
1119
1120    fn iter_keys<
1121        'a,
1122        T: Send + 'static,
1123        C: Send + 'static,
1124        F: FnMut(&mut C, DBKeyRef) -> io::Result<Option<T>> + Send + Sync + 'static,
1125    >(
1126        &'a self,
1127        col: u32,
1128        prefix: Option<&'a [u8]>,
1129        context: C,
1130        mut f: F,
1131    ) -> KeyValueDBPinBoxFuture<'a, io::Result<(C, Option<T>)>> {
1132        let opt_prefix_query = prefix.map(|p| like_key_to_text(p) + "%");
1133        Box::pin(async move {
1134            if col >= self.columns() {
1135                return Err(io::Error::from(io::ErrorKind::NotFound));
1136            }
1137
1138            let that = self.clone();
1139            let context = Arc::new(Mutex::new(Some(context)));
1140            let context_ref = context.clone();
1141
1142            let res = self
1143                .conn(move |conn| {
1144                    let mut context = context_ref.lock();
1145                    let context = context.as_mut().unwrap();
1146
1147                    let mut stmt;
1148                    let mut rows;
1149                    if let Some(prefix_query) = opt_prefix_query {
1150                        stmt = match conn
1151                            .prepare_cached(&that.column_table(col).str_iter_keys_with_prefix)
1152                        {
1153                            Ok(v) => v,
1154                            Err(e) => {
1155                                return Ok(Err(io::Error::other(e)));
1156                            }
1157                        };
1158                        rows = match stmt.query([prefix_query]) {
1159                            Ok(v) => v,
1160                            Err(e) => {
1161                                return Ok(Err(io::Error::other(e)));
1162                            }
1163                        };
1164                    } else {
1165                        stmt = match conn
1166                            .prepare_cached(&that.column_table(col).str_iter_keys_no_prefix)
1167                        {
1168                            Ok(v) => v,
1169                            Err(e) => {
1170                                return Ok(Err(io::Error::other(e)));
1171                            }
1172                        };
1173                        rows = match stmt.query([]) {
1174                            Ok(v) => v,
1175                            Err(e) => {
1176                                return Ok(Err(io::Error::other(e)));
1177                            }
1178                        };
1179                    }
1180
1181                    let mut sw = 0usize;
1182                    let mut sbw = 0usize;
1183
1184                    let out = loop {
1185                        match rows.next() {
1186                            // Iterated value
1187                            Ok(Some(row)) => {
1188                                let kt: String = match row.get(0) {
1189                                    Err(e) => {
1190                                        break Err(io::Error::other(e));
1191                                    }
1192                                    Ok(v) => v,
1193                                };
1194                                let k: Vec<u8> = match text_to_key(&kt) {
1195                                    Err(e) => {
1196                                        break Err(io::Error::other(format!(
1197                                            "SQLite row get column 0 text convert error: {:?}",
1198                                            e
1199                                        )));
1200                                    }
1201                                    Ok(v) => v,
1202                                };
1203
1204                                sw += 1;
1205                                sbw += k.len();
1206
1207                                match f(context, &k) {
1208                                    Ok(None) => (),
1209                                    Ok(Some(out)) => {
1210                                        // Callback early termination
1211                                        that.stats_read(sw, sbw);
1212                                        break Ok(Some(out));
1213                                    }
1214                                    Err(e) => {
1215                                        // Callback error termination
1216                                        that.stats_read(sw, sbw);
1217                                        break Err(e);
1218                                    }
1219                                }
1220                            }
1221                            // Natural iterator termination
1222                            Ok(None) => {
1223                                break Ok(None);
1224                            }
1225                            // Error iterator termination
1226                            Err(_) => {
1227                                break Ok(None);
1228                            }
1229                        }
1230                    };
1231
1232                    that.stats_read(sw, sbw);
1233
1234                    Ok(out)
1235                })
1236                .await
1237                .map_err(io::Error::other)?;
1238
1239            let context = context.lock().take().unwrap();
1240
1241            res.map(|x| (context, x))
1242        })
1243    }
1244
1245    fn io_stats(&self, kind: IoStatsKind) -> IoStats {
1246        fn duration_since(timestamp_microseconds: u64) -> Duration {
1247            std::time::SystemTime::now()
1248                .duration_since(std::time::UNIX_EPOCH)
1249                .map_or(Duration::from_micros(0), |time| {
1250                    let now = time.as_micros() as u64;
1251                    if now >= timestamp_microseconds {
1252                        Duration::from_micros(now - timestamp_microseconds)
1253                    } else {
1254                        Duration::from_micros(0)
1255                    }
1256                })
1257        }
1258
1259        let mut inner = self.inner.lock();
1260        match kind {
1261            IoStatsKind::Overall => {
1262                let mut stats = inner.overall_stats.clone();
1263                stats.span = duration_since(stats.started);
1264                stats
1265            }
1266            IoStatsKind::SincePrevious => {
1267                let mut stats = inner.current_stats.clone();
1268                stats.span = duration_since(stats.started);
1269                inner.current_stats = IoStats::empty();
1270                stats
1271            }
1272        }
1273    }
1274
1275    fn num_columns(&self) -> io::Result<u32> {
1276        let this = self.clone();
1277        self.conn_blocking(move |conn| {
1278            Self::get_unique_value(conn, this.control_table(), "columns", 0u32)
1279        })
1280        .map_err(io::Error::other)
1281    }
1282
1283    fn num_keys(&self, col: u32) -> KeyValueDBPinBoxFuture<'_, io::Result<u64>> {
1284        let this = self.clone();
1285        Box::pin(async move {
1286            this.conn(move |conn| {
1287                conn.query_row(
1288                    &format!("SELECT Count(*) FROM {}", get_column_table_name(col)),
1289                    [],
1290                    |row| -> rusqlite::Result<u64> { row.get(0) },
1291                )
1292            })
1293            .await
1294            .map_err(|_| io::Error::from(io::ErrorKind::NotFound))
1295        })
1296    }
1297
1298    /// Check for the existence of a value by key.
1299    fn has_key<'a>(
1300        &'a self,
1301        col: u32,
1302        key: &'a [u8],
1303    ) -> KeyValueDBPinBoxFuture<'a, io::Result<bool>> {
1304        let key_text = key_to_text(key);
1305        let key_len = key.len();
1306
1307        Box::pin(async move {
1308            let that = self.clone();
1309            that.validate_column(col).map_err(io::Error::other)?;
1310            let someval = self
1311                .conn_blocking(move |conn| Self::has_value(conn, that.column_table(col), &key_text))
1312                .map_err(io::Error::other)?;
1313
1314            self.stats_read(1, key_len);
1315
1316            Ok(someval)
1317        })
1318    }
1319
1320    /// Check for the existence of a value by prefix.
1321    fn has_prefix<'a>(
1322        &'a self,
1323        col: u32,
1324        prefix: &'a [u8],
1325    ) -> KeyValueDBPinBoxFuture<'a, io::Result<bool>> {
1326        let prefix_len = prefix.len();
1327        let prefix_text = like_key_to_text(prefix) + "%";
1328
1329        Box::pin(async move {
1330            let that = self.clone();
1331            that.validate_column(col).map_err(io::Error::other)?;
1332            let someval = self
1333                .conn_blocking(move |conn| {
1334                    Self::has_value_like(conn, that.column_table(col), &prefix_text)
1335                })
1336                .map_err(io::Error::other)?;
1337
1338            self.stats_read(1, prefix_len);
1339
1340            Ok(someval)
1341        })
1342    }
1343
1344    /// Get the first value matching the given prefix.
1345    fn first_with_prefix<'a>(
1346        &'a self,
1347        col: u32,
1348        prefix: &'a [u8],
1349    ) -> KeyValueDBPinBoxFuture<'a, io::Result<Option<DBKeyValue>>> {
1350        let prefix_len = prefix.len();
1351        let like = like_key_to_text(prefix) + "%";
1352
1353        Box::pin(async move {
1354            let that = self.clone();
1355            that.validate_column(col).map_err(io::Error::other)?;
1356            let someval = self
1357                .conn_blocking(move |conn| {
1358                    Self::load_first_value_blob_like(conn, that.column_table(col), &like)
1359                })
1360                .map_err(io::Error::other)?;
1361
1362            self.stats_read(1, prefix_len);
1363
1364            match someval {
1365                Some((kt, val)) => match text_to_key(&kt) {
1366                    Err(e) => Err(io::Error::other(format!(
1367                        "SQLite row get column 0 text convert error: {:?}",
1368                        e
1369                    ))),
1370                    Ok(k) => Ok(Some((k, val))),
1371                },
1372                None => Ok(None),
1373            }
1374        })
1375    }
1376
1377    /// Vacuum database
1378    fn cleanup(&self) -> KeyValueDBPinBoxFuture<'_, io::Result<()>> {
1379        Box::pin(async { self.vacuum().await.map_err(io::Error::other) })
1380    }
1381}
1382
1383#[cfg(test)]
1384mod tests {
1385
1386    use super::*;
1387    use keyvaluedb_shared_tests as st;
1388    use tempfile::Builder as TempfileBuilder;
1389
1390    fn create(columns: u32) -> io::Result<Database> {
1391        let tempfile = TempfileBuilder::new()
1392            .prefix("")
1393            .tempfile()?
1394            .path()
1395            .to_path_buf();
1396        let config = DatabaseConfig::new().with_columns(columns);
1397        Database::open(tempfile, config)
1398    }
1399
1400    fn create_vacuum_mode(columns: u32, vacuum_mode: VacuumMode) -> io::Result<Database> {
1401        let tempfile = TempfileBuilder::new()
1402            .prefix("")
1403            .tempfile()?
1404            .path()
1405            .to_path_buf();
1406        let config = DatabaseConfig::new()
1407            .with_columns(columns)
1408            .with_vacuum_mode(vacuum_mode);
1409        Database::open(tempfile, config)
1410    }
1411
1412    #[tokio::test]
1413    async fn get_fails_with_non_existing_column() -> io::Result<()> {
1414        let db = create(1)?;
1415        st::test_get_fails_with_non_existing_column(db).await
1416    }
1417
1418    #[tokio::test]
1419    async fn num_keys() -> io::Result<()> {
1420        let db = create(1)?;
1421        st::test_num_keys(db).await
1422    }
1423
1424    #[tokio::test]
1425    async fn put_and_get() -> io::Result<()> {
1426        let db = create(1)?;
1427        st::test_put_and_get(db).await
1428    }
1429
1430    #[tokio::test]
1431    async fn delete_and_get() -> io::Result<()> {
1432        let db = create(1)?;
1433        st::test_delete_and_get(db).await
1434    }
1435
1436    #[tokio::test]
1437    async fn delete_and_get_single() -> io::Result<()> {
1438        let db = create(1)?;
1439        st::test_delete_and_get_single(db).await
1440    }
1441
1442    #[tokio::test]
1443    async fn delete_prefix() -> io::Result<()> {
1444        let db = create(st::DELETE_PREFIX_NUM_COLUMNS)?;
1445        st::test_delete_prefix(db).await
1446    }
1447
1448    #[tokio::test]
1449    async fn iter() -> io::Result<()> {
1450        let db = create(1)?;
1451        st::test_iter(db).await
1452    }
1453
1454    #[tokio::test]
1455    async fn iter_keys() -> io::Result<()> {
1456        let db = create(1)?;
1457        st::test_iter_keys(db).await
1458    }
1459
1460    #[tokio::test]
1461    async fn iter_with_prefix() -> io::Result<()> {
1462        let db = create(1)?;
1463        st::test_iter_with_prefix(db).await
1464    }
1465
1466    #[tokio::test]
1467    async fn complex() -> io::Result<()> {
1468        let db = create(1)?;
1469        st::test_complex(db).await
1470    }
1471
1472    #[tokio::test]
1473    async fn cleanup() -> io::Result<()> {
1474        let db = create(1)?;
1475        st::test_cleanup(db).await?;
1476
1477        let db = create_vacuum_mode(1, VacuumMode::None)?;
1478        st::test_cleanup(db).await?;
1479
1480        let db = create_vacuum_mode(1, VacuumMode::Incremental)?;
1481        st::test_cleanup(db).await?;
1482
1483        let db = create_vacuum_mode(1, VacuumMode::Full)?;
1484        st::test_cleanup(db).await?;
1485
1486        let tempfile = TempfileBuilder::new()
1487            .prefix("")
1488            .tempfile()?
1489            .path()
1490            .to_path_buf();
1491        let config = DatabaseConfig::new().with_vacuum_mode(VacuumMode::None);
1492        let db = Database::open(tempfile.clone(), config)?;
1493        st::test_cleanup(db).await?;
1494
1495        let config = DatabaseConfig::new().with_vacuum_mode(VacuumMode::Incremental);
1496        let db = Database::open(tempfile.clone(), config)?;
1497        st::test_cleanup(db).await?;
1498
1499        let config = DatabaseConfig::new().with_vacuum_mode(VacuumMode::Full);
1500        let db = Database::open(tempfile.clone(), config)?;
1501        st::test_cleanup(db).await?;
1502
1503        let config = DatabaseConfig::new().with_vacuum_mode(VacuumMode::None);
1504        let db = Database::open(tempfile, config)?;
1505        st::test_cleanup(db).await?;
1506
1507        Ok(())
1508    }
1509
1510    #[tokio::test]
1511    async fn stats() -> io::Result<()> {
1512        let db = create(st::IO_STATS_NUM_COLUMNS)?;
1513        st::test_io_stats(db).await
1514    }
1515
1516    #[tokio::test]
1517    #[should_panic]
1518    async fn db_config_with_zero_columns() {
1519        let _cfg = DatabaseConfig::new().with_columns(0);
1520    }
1521
1522    #[tokio::test]
1523    #[should_panic]
1524    async fn open_db_with_zero_columns() {
1525        let cfg = DatabaseConfig::new().with_columns(0);
1526        let _db = Database::open("", cfg);
1527    }
1528
1529    #[tokio::test]
1530    async fn add_columns() {
1531        let config_1 = DatabaseConfig::default();
1532        let config_5 = DatabaseConfig::new().with_columns(5);
1533
1534        let tempfile = TempfileBuilder::new()
1535            .prefix("")
1536            .tempfile()
1537            .unwrap()
1538            .path()
1539            .to_path_buf();
1540
1541        // open 1, add 4.
1542        {
1543            let db = Database::open(&tempfile, config_1).unwrap();
1544            assert_eq!(db.num_columns().unwrap(), 1);
1545
1546            for i in 2..=5 {
1547                db.add_column().unwrap();
1548                assert_eq!(db.num_columns().unwrap(), i);
1549            }
1550        }
1551
1552        // reopen as 5.
1553        {
1554            let db = Database::open(&tempfile, config_5).unwrap();
1555            assert_eq!(db.num_columns().unwrap(), 5);
1556        }
1557    }
1558
1559    #[tokio::test]
1560    async fn remove_columns() {
1561        let config_1 = DatabaseConfig::default();
1562        let config_5 = DatabaseConfig::new().with_columns(5);
1563
1564        let tempfile = TempfileBuilder::new()
1565            .prefix("drop_columns")
1566            .tempfile()
1567            .unwrap()
1568            .path()
1569            .to_path_buf();
1570
1571        // open 5, remove 4.
1572        {
1573            let db = Database::open(&tempfile, config_5).expect("open with 5 columns");
1574            assert_eq!(db.num_columns().unwrap(), 5);
1575
1576            for i in (1..5).rev() {
1577                db.remove_last_column().unwrap();
1578                assert_eq!(db.num_columns().unwrap(), i);
1579            }
1580        }
1581
1582        // reopen as 1.
1583        {
1584            let db = Database::open(&tempfile, config_1).unwrap();
1585            assert_eq!(db.num_columns().unwrap(), 1);
1586        }
1587    }
1588
1589    #[tokio::test]
1590    async fn test_num_keys() {
1591        let tempfile = TempfileBuilder::new()
1592            .prefix("")
1593            .tempfile()
1594            .unwrap()
1595            .path()
1596            .to_path_buf();
1597        let config = DatabaseConfig::new().with_columns(1);
1598        let db = Database::open(tempfile, config).unwrap();
1599
1600        assert_eq!(
1601            db.num_keys(0).await.unwrap(),
1602            0,
1603            "database is empty after creation"
1604        );
1605        let key1 = b"beef";
1606        let mut batch = db.transaction();
1607        batch.put(0, key1, key1);
1608        db.write(batch).await.unwrap();
1609        assert_eq!(
1610            db.num_keys(0).await.unwrap(),
1611            1,
1612            "adding a key increases the count"
1613        );
1614    }
1615}