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