Skip to main content

keyvaluedb_web/
lib.rs

1//! A key-value database for use in browsers
2//!
3//! Writes data both into memory and IndexedDB, optionally reads the whole database in memory
4//! from the IndexedDB on `open`.
5
6#![deny(clippy::all)]
7#![deny(missing_docs)]
8
9mod error;
10mod indexed_db;
11
12use keyvaluedb::{
13    DBKeyRef, DBKeyValueRef, DBTransaction, DBTransactionError, DBValue, KeyValueDBPinBoxFuture,
14};
15use keyvaluedb_memorydb::{self as in_memory, InMemory};
16use send_wrapper::SendWrapper;
17use std::io;
18use std::sync::Arc;
19
20pub use crate::error::*;
21pub use keyvaluedb::KeyValueDB;
22
23use futures::prelude::*;
24
25use web_sys::IdbDatabase;
26
27struct DatabaseUnlockedInner {
28    table_name: String,
29    version: u32,
30    columns: u32,
31    in_memory: Option<InMemory>,
32    indexed_db: SendWrapper<IdbDatabase>,
33}
34
35impl Drop for DatabaseUnlockedInner {
36    fn drop(&mut self) {
37        self.indexed_db.close();
38    }
39}
40
41/// Database backed by both IndexedDB and in memory implementation.
42#[derive(Clone)]
43pub struct Database {
44    unlocked_inner: Arc<DatabaseUnlockedInner>,
45}
46
47impl Database {
48    /// Opens the database with the given name,
49    /// and the specified number of columns (not including the default one).
50    pub async fn open(
51        table_name: &str,
52        columns: u32,
53        memory_cached: bool,
54    ) -> Result<Database, error::Error> {
55        // let's try to open the latest version of the db first
56        let db = indexed_db::open(table_name, None, columns)
57            .await
58            .map_err(io_err_string)?;
59
60        // If we need more column than the latest version has,
61        // then bump the version.
62        // In order to bump the version, we close the database
63        // and reopen it with a higher version than it was opened with previously.
64        // cf. https://github.com/paritytech/parity-common/pull/202#discussion_r321221751
65        let db = if columns > db.columns {
66            let next_version = db.version + 1;
67            drop(db);
68            indexed_db::open(table_name, Some(next_version), columns)
69                .await
70                .map_err(io_err_string)?
71        } else {
72            db
73        };
74        // populate the in_memory db from the IndexedDB
75        let indexed_db::IndexedDB { version, inner, .. } = db;
76        let in_memory = if memory_cached {
77            let in_memory = in_memory::create(columns);
78            // read the columns from the IndexedDB
79            for column in 0..columns {
80                let mut tx = DBTransaction::new();
81                let mut stream = indexed_db::idb_cursor(&inner, column, None, None)
82                    .map_err(error::Error::from)?;
83                while let Some(kv) = stream.next().await {
84                    match kv {
85                        Ok((key, value)) => {
86                            tx.put(column, &key, &value);
87                        }
88                        Err(e) => {
89                            return Err(e.into());
90                        }
91                    }
92                }
93                // write each column into memory
94                in_memory
95                    .write(tx)
96                    .await
97                    .expect("writing in memory always succeeds; qed");
98            }
99            Some(in_memory)
100        } else {
101            None
102        };
103
104        Ok(Database {
105            unlocked_inner: Arc::new(DatabaseUnlockedInner {
106                table_name: table_name.to_owned(),
107                version,
108                columns,
109                in_memory,
110                indexed_db: inner,
111            }),
112        })
113    }
114
115    /// Deletes the database with the given name,
116    /// Delete the database. Returns whether one was actually removed; deleting a database that
117    /// does not exist is not an error.
118    pub async fn delete(table_name: &str) -> io::Result<bool> {
119        indexed_db::delete(table_name).await.map_err(io_err_string)
120    }
121
122    /// Enumerate every IndexedDB database visible to the current origin,
123    /// optionally filtered to names starting with `opt_prefix`. Returns
124    /// `(name, version)` tuples. Backed by the `indexedDB.databases()` API.
125    pub fn list(
126        opt_prefix: Option<&str>,
127    ) -> KeyValueDBPinBoxFuture<'_, io::Result<Vec<(String, u32)>>> {
128        let opt_prefix = opt_prefix.map(|p| p.to_owned());
129        Box::pin(SendWrapper::new(async move {
130            let names = indexed_db::names_with_versions()
131                .await
132                .map_err(io_err_string)?;
133            let Some(prefix) = opt_prefix else {
134                return Ok(names);
135            };
136            Ok(names
137                .into_iter()
138                .filter(|(name, _ver)| name.starts_with(&prefix))
139                .collect())
140        }))
141    }
142
143    /// Get the database name.
144    pub fn name(&self) -> String {
145        self.unlocked_inner.table_name.clone()
146    }
147
148    /// Get the database version.
149    pub fn version(&self) -> u32 {
150        self.unlocked_inner.version
151    }
152}
153
154impl KeyValueDB for Database {
155    fn get<'a>(
156        &'a self,
157        col: u32,
158        key: &'a [u8],
159    ) -> KeyValueDBPinBoxFuture<'a, io::Result<Option<DBValue>>> {
160        let this = self.clone();
161        Box::pin(SendWrapper::new(async move {
162            if col >= this.unlocked_inner.columns {
163                return Err(io::Error::from(io::ErrorKind::NotFound));
164            }
165
166            if let Some(in_memory) = &this.unlocked_inner.in_memory {
167                in_memory.get(col, key).await
168            } else {
169                indexed_db::idb_get(&this.unlocked_inner.indexed_db, col, key).await
170            }
171        }))
172    }
173
174    fn delete<'a>(
175        &'a self,
176        col: u32,
177        key: &'a [u8],
178    ) -> KeyValueDBPinBoxFuture<'a, io::Result<Option<DBValue>>> {
179        let this = self.clone();
180        Box::pin(SendWrapper::new(async move {
181            if col >= this.unlocked_inner.columns {
182                return Err(io::Error::from(io::ErrorKind::NotFound));
183            }
184
185            let someval = indexed_db::idb_get(&this.unlocked_inner.indexed_db, col, key).await?;
186
187            let mut transaction = DBTransaction::new();
188            transaction.delete(col, key);
189
190            match indexed_db::idb_commit_transaction(
191                &this.unlocked_inner.indexed_db,
192                &transaction,
193                this.unlocked_inner.columns,
194            )
195            .await
196            {
197                Ok(()) => {}
198                Err(error) => {
199                    return Err(io_err_string(format!("delete failed: {:?}", error)));
200                }
201            };
202
203            if let Some(in_memory) = &this.unlocked_inner.in_memory {
204                in_memory.delete(col, key).await?;
205            }
206
207            Ok(someval)
208        }))
209    }
210
211    fn write(
212        &self,
213        transaction: DBTransaction,
214    ) -> KeyValueDBPinBoxFuture<'_, Result<(), DBTransactionError>> {
215        let this = self.clone();
216        Box::pin(SendWrapper::new(async move {
217            {
218                match indexed_db::idb_commit_transaction(
219                    &this.unlocked_inner.indexed_db,
220                    &transaction,
221                    this.unlocked_inner.columns,
222                )
223                .await
224                {
225                    Ok(()) => {}
226                    Err(error) => {
227                        return Err(DBTransactionError { error, transaction });
228                    }
229                };
230            }
231            if let Some(in_memory) = &this.unlocked_inner.in_memory {
232                in_memory.write(transaction).await
233            } else {
234                Ok(())
235            }
236        }))
237    }
238
239    fn iter<
240        'a,
241        T: Send + 'static,
242        C: Send + 'static,
243        F: FnMut(&mut C, DBKeyValueRef) -> io::Result<Option<T>> + Send + Sync + 'static,
244    >(
245        &'a self,
246        col: u32,
247        prefix: Option<&'a [u8]>,
248        mut context: C,
249        mut f: F,
250    ) -> KeyValueDBPinBoxFuture<'a, io::Result<(C, Option<T>)>> {
251        let this = self.clone();
252        Box::pin(async move {
253            if col >= this.unlocked_inner.columns {
254                return Err(io::Error::from(io::ErrorKind::NotFound));
255            }
256            if let Some(in_memory) = &this.unlocked_inner.in_memory {
257                in_memory.iter(col, prefix, context, f).await
258            } else {
259                let mut stream = indexed_db::idb_cursor(
260                    &this.unlocked_inner.indexed_db,
261                    col,
262                    None,
263                    prefix.map(|p| p.to_vec()),
264                )?;
265                while let Some(kv) = stream.next().await {
266                    match kv {
267                        Ok((key, value)) => {
268                            if let Some(out) = f(&mut context, (&key, &value))? {
269                                return Ok((context, Some(out)));
270                            }
271                        }
272                        Err(e) => {
273                            return Err(e);
274                        }
275                    }
276                }
277                Ok((context, None))
278            }
279        })
280    }
281
282    fn iter_keys<
283        'a,
284        T: Send + 'static,
285        C: Send + 'static,
286        F: FnMut(&mut C, DBKeyRef) -> io::Result<Option<T>> + Send + Sync + 'static,
287    >(
288        &'a self,
289        col: u32,
290        prefix: Option<&'a [u8]>,
291        mut context: C,
292        mut f: F,
293    ) -> KeyValueDBPinBoxFuture<'a, io::Result<(C, Option<T>)>> {
294        let this = self.clone();
295        Box::pin(async move {
296            if col >= this.unlocked_inner.columns {
297                return Err(io::Error::from(io::ErrorKind::NotFound));
298            }
299
300            if let Some(in_memory) = &this.unlocked_inner.in_memory {
301                in_memory.iter_keys(col, prefix, context, f).await
302            } else {
303                let mut stream = indexed_db::idb_cursor_keys(
304                    &this.unlocked_inner.indexed_db,
305                    col,
306                    prefix.map(|p| p.to_vec()),
307                )?;
308                while let Some(k) = stream.next().await {
309                    match k {
310                        Ok(key) => {
311                            if let Some(out) = f(&mut context, &key)? {
312                                return Ok((context, Some(out)));
313                            }
314                        }
315                        Err(e) => {
316                            return Err(e);
317                        }
318                    }
319                }
320                Ok((context, None))
321            }
322        })
323    }
324
325    fn num_columns(&self) -> Result<u32, io::Error> {
326        Ok(self.unlocked_inner.columns)
327    }
328
329    fn num_keys(&self, col: u32) -> KeyValueDBPinBoxFuture<'_, io::Result<u64>> {
330        let this = self.clone();
331        Box::pin(async move {
332            if col >= this.unlocked_inner.columns {
333                return Err(io::Error::from(io::ErrorKind::NotFound));
334            }
335
336            if let Some(in_memory) = &this.unlocked_inner.in_memory {
337                in_memory.num_keys(col).await
338            } else {
339                let mut stream =
340                    indexed_db::idb_get_key_count(&this.unlocked_inner.indexed_db, col, None)?;
341                if let Some(v) = stream.next().await {
342                    match v {
343                        Ok(value) => {
344                            return Ok(value as u64);
345                        }
346                        Err(e) => {
347                            return Err(e);
348                        }
349                    }
350                }
351                Err(io::Error::from(io::ErrorKind::InvalidData))
352            }
353        })
354    }
355}