zp_kvdb_web/
lib.rs

1// Copyright 2020 Parity Technologies
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! A key-value database for use in browsers
10//!
11//! Writes data both into memory and IndexedDB, reads the whole database in memory
12//! from the IndexedDB on `open`.
13
14#![deny(missing_docs)]
15
16mod error;
17mod indexed_db;
18
19use std::io;
20
21pub use error::Error;
22use futures::prelude::*;
23pub use kvdb::KeyValueDB;
24use kvdb::{DBKeyValue, DBTransaction, DBValue};
25use kvdb_memorydb::{self as in_memory, InMemory};
26use send_wrapper::SendWrapper;
27use web_sys::IdbDatabase;
28
29/// Database backed by both IndexedDB and in memory implementation.
30pub struct Database {
31    name: String,
32    version: u32,
33    columns: u32,
34    in_memory: InMemory,
35    indexed_db: SendWrapper<IdbDatabase>,
36}
37
38// TODO: implement when web-based implementation need memory stats
39parity_util_mem::malloc_size_of_is_0!(Database);
40
41impl Database {
42    /// Opens the database with the given name,
43    /// and the specified number of columns (not including the default one).
44    pub async fn open(name: String, columns: u32) -> Result<Database, error::Error> {
45        let name_clone = name.clone();
46        // let's try to open the latest version of the db first
47        let db = indexed_db::open(name.as_str(), None, columns).await?;
48
49        // If we need more column than the latest version has,
50        // then bump the version (+ 1 for the default column).
51        // In order to bump the version, we close the database
52        // and reopen it with a higher version than it was opened with previously.
53        // cf. https://github.com/paritytech/parity-common/pull/202#discussion_r321221751
54        let db = if columns + 1 > db.columns {
55            let next_version = db.version + 1;
56            drop(db);
57            indexed_db::open(name.as_str(), Some(next_version), columns).await?
58        } else {
59            db
60        };
61        // populate the in_memory db from the IndexedDB
62        let indexed_db::IndexedDB { version, inner, .. } = db;
63        let in_memory = in_memory::create(columns);
64        // read the columns from the IndexedDB
65        for column in 0..columns {
66            let mut txn = DBTransaction::new();
67            let mut stream = indexed_db::idb_cursor(&*inner, column);
68            while let Some((key, value)) = stream.next().await {
69                txn.put_vec(column, key.as_ref(), value);
70            }
71            // write each column into memory
72            in_memory
73                .write(txn)
74                .expect("writing in memory always succeeds; qed");
75        }
76        Ok(Database {
77            name: name_clone,
78            version,
79            columns,
80            in_memory,
81            indexed_db: inner,
82        })
83    }
84
85    /// Get the database name.
86    pub fn name(&self) -> &str {
87        self.name.as_str()
88    }
89
90    /// Get the database version.
91    pub fn version(&self) -> u32 {
92        self.version
93    }
94}
95
96impl Drop for Database {
97    fn drop(&mut self) {
98        self.indexed_db.close();
99    }
100}
101
102impl KeyValueDB for Database {
103    fn get(&self, col: u32, key: &[u8]) -> io::Result<Option<DBValue>> {
104        self.in_memory.get(col, key)
105    }
106
107    fn get_by_prefix(&self, col: u32, prefix: &[u8]) -> io::Result<Option<Vec<u8>>> {
108        self.in_memory.get_by_prefix(col, prefix)
109    }
110
111    fn write(&self, transaction: DBTransaction) -> io::Result<()> {
112        let _ = indexed_db::idb_commit_transaction(&*self.indexed_db, &transaction, self.columns);
113        self.in_memory.write(transaction)
114    }
115
116    fn iter<'a>(&'a self, col: u32) -> Box<dyn Iterator<Item = io::Result<DBKeyValue>> + 'a> {
117        self.in_memory.iter(col)
118    }
119
120    fn iter_with_prefix<'a>(
121        &'a self,
122        col: u32,
123        prefix: &'a [u8],
124    ) -> Box<dyn Iterator<Item = io::Result<DBKeyValue>> + 'a> {
125        self.in_memory.iter_with_prefix(col, prefix)
126    }
127}