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#[derive(Copy, Clone, Debug, Eq, PartialEq)]
25pub enum VacuumMode {
26 None,
27 Incremental,
28 Full,
29}
30
31#[derive(Clone)]
33pub struct DatabaseConfig {
34 pub columns: u32,
37 pub flags: OpenFlags,
39 pub num_conns: usize,
41 pub vacuum_mode: VacuumMode,
43 pub repair_on_corrupt: bool,
46}
47
48impl DatabaseConfig {
49 pub fn new() -> Self {
51 Default::default()
52 }
53
54 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 pub fn with_repair_on_corrupt(self, repair_on_corrupt: bool) -> Self {
62 Self {
63 repair_on_corrupt,
64 ..self
65 }
66 }
67
68 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 pub fn with_flags(self, flags: OpenFlags) -> Self {
81 Self { flags, ..self }
82 }
83
84 pub fn with_num_conns(self, num_conns: usize) -> Self {
86 Self { num_conns, ..self }
87 }
88
89 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
112fn 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
138pub 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#[derive(Debug, Clone)]
209pub struct RepairReport {
210 pub detected: String,
212 pub rows_recovered: u64,
214 pub partial_tables: u32,
216 pub corrupt_path: PathBuf,
218}
219
220pub 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 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 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 pub fn repair_report(&self) -> Option<&RepairReport> {
358 self.unlocked_inner.repair_report.as_ref()
359 }
360
361 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 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 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 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 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 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 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 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 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 pub fn transaction(&self) -> DBTransaction {
591 DBTransaction::new()
592 }
593
594 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 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 conn.execute("CREATE TABLE IF NOT EXISTS control (id INTEGER PRIMARY KEY AUTOINCREMENT, [key] TEXT UNIQUE, value TEXT)", [])?;
781
782 let on_disk_columns =
784 Self::get_unique_value(conn, this.control_table(), "columns", 0u32)?;
785
786 if columns <= on_disk_columns {
788 return Ok(());
789 }
790
791 for cn in on_disk_columns..columns {
793 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 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 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 that.stats_read(sw, sbw);
1087 break Ok(Some(out));
1088 }
1089 Err(e) => {
1090 that.stats_read(sw, sbw);
1092 break Err(e);
1093 }
1094 }
1095 }
1096 Ok(None) => {
1098 break Ok(None);
1099 }
1100 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 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 that.stats_read(sw, sbw);
1212 break Ok(Some(out));
1213 }
1214 Err(e) => {
1215 that.stats_read(sw, sbw);
1217 break Err(e);
1218 }
1219 }
1220 }
1221 Ok(None) => {
1223 break Ok(None);
1224 }
1225 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 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 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 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 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 {
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 {
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 {
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 {
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}