Bug 1522638 - Add bulk insert to kvstore r=myk,mossop,nika
authorNan Jiang <njiang028@gmail.com>
Wed, 27 Mar 2019 14:16:59 +0000
changeset 466388 49ffc038ef84851f0b42bd14e90cb2868431e60e
parent 466387 a6367a5c6020a947763bbbf9293491d045e671ba
child 466389 70dfe7fae016bb4d37d618fe3deaf92250e7fe4a
push id81573
push usernajiang@mozilla.com
push dateWed, 27 Mar 2019 14:56:30 +0000
treeherderautoland@49ffc038ef84 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmyk, mossop, nika
bugs1522638
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1522638 - Add bulk insert to kvstore r=myk,mossop,nika This adds the bulk insert to kvstore as discussed in Bug 1522638 Differential Revision: https://phabricator.services.mozilla.com/D22032
Cargo.lock
security/manager/ssl/cert_storage/Cargo.toml
third_party/rust/rkv/.cargo-checksum.json
third_party/rust/rkv/Cargo.toml
third_party/rust/rkv/examples/simple-store.rs
third_party/rust/rkv/src/env.rs
third_party/rust/rkv/src/lib.rs
third_party/rust/rkv/src/readwrite.rs
third_party/rust/rkv/src/store/integer.rs
third_party/rust/rkv/src/store/integermulti.rs
third_party/rust/rkv/src/store/multi.rs
third_party/rust/rkv/src/store/single.rs
toolkit/components/kvstore/Cargo.toml
toolkit/components/kvstore/kvstore.jsm
toolkit/components/kvstore/nsIKeyValue.idl
toolkit/components/kvstore/src/lib.rs
toolkit/components/kvstore/src/task.rs
toolkit/components/kvstore/test/xpcshell/test_kvstore.js
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -431,17 +431,17 @@ source = "git+https://github.com/glandiu
 
 [[package]]
 name = "cert_storage"
 version = "0.0.1"
 dependencies = [
  "base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "nserror 0.1.0",
  "nsstring 0.1.0",
- "rkv 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rkv 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "xpcom 0.1.0",
 ]
 
 [[package]]
 name = "cexpr"
@@ -1476,18 +1476,19 @@ dependencies = [
  "crossbeam-utils 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
  "lmdb-rkv 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "moz_task 0.1.0",
  "nserror 0.1.0",
  "nsstring 0.1.0",
- "rkv 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rkv 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "storage_variant 0.1.0",
+ "thin-vec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "xpcom 0.1.0",
 ]
 
 [[package]]
 name = "lalrpop"
 version = "0.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -2342,17 +2343,17 @@ name = "regex-syntax"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "rkv"
-version = "0.9.3"
+version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lmdb-rkv 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "ordered-float 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3672,17 +3673,17 @@ dependencies = [
 "checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8"
 "checksum redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ab105df655884ede59d45b7070c8a65002d921461ee813a024558ca16030eea0"
 "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
 "checksum redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "214a97e49be64fd2c86f568dd0cb2c757d2cc53de95b273b6ad0a1c908482f26"
 "checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
 "checksum regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75ecf88252dce580404a22444fc7d626c01815debba56a7f4f536772a5ff19d3"
 "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
 "checksum regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1ac0f60d675cc6cf13a20ec076568254472551051ad5dd050364d70671bf6b"
-"checksum rkv 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "becd7f5278be3b97250a8035455116f9fc63f5fc68cc8293213051d7d751c373"
+"checksum rkv 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "238764bd8750927754d91e4a27155ac672ba88934a2bf698c992d55e5ae25e5b"
 "checksum ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "da06feaa07f69125ab9ddc769b11de29090122170b402547f64b86fe16ebc399"
 "checksum runloop 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d79b4b604167921892e84afbbaad9d5ad74e091bf6c511d9dbfb0593f09fabd"
 "checksum rust-ini 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8a654c5bda722c699be6b0fe4c0d90de218928da5b724c3e467fc48865c37263"
 "checksum rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "76d7ba1feafada44f2d38eed812bd2489a03c0f5abb975799251518b68848649"
 "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
 "checksum ryu 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0568787116e13c652377b6846f5931454a363a8fdf8ae50463ee40935b278b"
 "checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9"
 "checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
--- a/security/manager/ssl/cert_storage/Cargo.toml
+++ b/security/manager/ssl/cert_storage/Cargo.toml
@@ -2,13 +2,13 @@
 name = "cert_storage"
 version = "0.0.1"
 authors = ["Dana Keeler <dkeeler@mozilla.com>", "Mark Goodwin <mgoodwin@mozilla.com"]
 
 [dependencies]
 base64 = "0.10"
 nserror = { path = "../../../../xpcom/rust/nserror" }
 nsstring = { path = "../../../../xpcom/rust/nsstring" }
-rkv = "0.9.2"
+rkv = "^0.9"
 sha2 = "^0.7"
 style = { path = "../../../../servo/components/style" }
 time = "0.1"
-xpcom = { path = "../../../../xpcom/rust/xpcom" }
\ No newline at end of file
+xpcom = { path = "../../../../xpcom/rust/xpcom" }
--- a/third_party/rust/rkv/.cargo-checksum.json
+++ b/third_party/rust/rkv/.cargo-checksum.json
@@ -1,1 +1,1 @@
-{"files":{"Cargo.toml":"448959dc5f9f13c678fef187c10974637b79aa10e9ff396c78da92a5d923c2a7","LICENSE":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","README.md":"9dc24375b49fef42f35dec42e316e21827d7337622f9e7cf36243cd28808797a","examples/README.md":"143767fc145bf167ce269a65138cb3f7086cb715b8bc4f73626da82966e646f4","examples/iterator.rs":"ddc3997e394a30ad82d78d2675a48c4617353f88b89bb9a3df5a3804d59b8ef9","examples/simple-store.rs":"9cec5f5a1277edf395775c22a29a27b1d9907ca693a3faa6cbd8e0f0bbff4347","run-all-examples.sh":"7f9d11d01017f77e1c9d26e3e82dfca8c6930deaec85e864458e33a7fa267de0","src/env.rs":"9c633d163274a9f76b30b4ce8439120dddac5b778b5300e02bc8fd22a053c0d1","src/error.rs":"46632b8fcb1070a1860247e09a59d39772079ebfba5d3d1bbee03d08e1252275","src/lib.rs":"365cd108bec0e22e8aa010b738a7db2f0da4c6e4cbf1284a1e8ad7e2f1f05736","src/manager.rs":"f06b14ee64f2e58d890a3b37677790b707a02d328242c1af0ce3c74e9028edd8","src/readwrite.rs":"5d5dd64c9b36b7f75b69771e6909c6d48f109ee3725b357f6a9099ddb853e978","src/store.rs":"409d13b1ea0d1254dae947ecbce50e741fb71c3ca118a78803b734336dce6a8f","src/store/integer.rs":"a302c7fb70397b7dca6c116828a309d16c9bc664abe029342d8ebdd730d8b457","src/store/integermulti.rs":"f2c8f9c70d1615757ccb0a56a9642ad6769236fd4c406767f5a71fa84eeeaacf","src/store/multi.rs":"9456f5ff3cec3bf2fc27660b18483e1f0752b5f5f6279b4cfcd1898e236188cb","src/store/single.rs":"09f594b7150cbdad4b8a5dc208d4b0ce4962139b8c856276264dd24c98ac92a4","src/value.rs":"ad74ba4c9ab0a77f1c4f8ee2650ceeb148e4036b017d804affc35085e97944fb","tests/integer-store.rs":"f7e06c71b0dead2323c7c61fc8bcbffbdd3a4796eebf6138db9cce3dbba716a3","tests/manager.rs":"97ec61145dc227f4f5fbcb6449c096bbe5b9a09db4e61ff4491c0443fe9adf26","tests/multi-integer-store.rs":"83295b0135c502321304aa06b05d5a9eeab41b1438ed7ddf2cb1a3613dfef4d9"},"package":"becd7f5278be3b97250a8035455116f9fc63f5fc68cc8293213051d7d751c373"}
\ No newline at end of file
+{"files":{"Cargo.toml":"bb25bb1f8a98037fac1f33ffef7244b6363396e6220af4b856b9fe0616c71b81","LICENSE":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","README.md":"9dc24375b49fef42f35dec42e316e21827d7337622f9e7cf36243cd28808797a","examples/README.md":"143767fc145bf167ce269a65138cb3f7086cb715b8bc4f73626da82966e646f4","examples/iterator.rs":"ddc3997e394a30ad82d78d2675a48c4617353f88b89bb9a3df5a3804d59b8ef9","examples/simple-store.rs":"cae63e39f2f98ee6ac2f387dcb02d6b929828a74f32f7d18d69c7fc9c3cce765","run-all-examples.sh":"7f9d11d01017f77e1c9d26e3e82dfca8c6930deaec85e864458e33a7fa267de0","src/env.rs":"f886c42b8ea0633ed001d24fee2edcc246b7b0dd2b56dcfbdebf4aef4a36f7a0","src/error.rs":"46632b8fcb1070a1860247e09a59d39772079ebfba5d3d1bbee03d08e1252275","src/lib.rs":"67a1970626fcecf35c0a9ccb0305afbfb12b8a85e3d5060bff4c6617a3d1de78","src/manager.rs":"f06b14ee64f2e58d890a3b37677790b707a02d328242c1af0ce3c74e9028edd8","src/readwrite.rs":"fde695333e4845f4f53d63da6281f585919e2a3ac5cfe00d173cc139bc822763","src/store.rs":"409d13b1ea0d1254dae947ecbce50e741fb71c3ca118a78803b734336dce6a8f","src/store/integer.rs":"f386474c971f671c9b316a16ebff5b586be6837c886f443753ae13277a7e0070","src/store/integermulti.rs":"1a0912f97619297da31cc8c146e38941b88539d2857df81191a49c8dbd18625d","src/store/multi.rs":"2dec01c2202a2c9069cced4e1e42906b01d0b85df25d17e0ea810c05fa8395d0","src/store/single.rs":"c55c3600714f5ed9e820b16c2335ae00a0071174e0a32b9df89a34182a4b908c","src/value.rs":"ad74ba4c9ab0a77f1c4f8ee2650ceeb148e4036b017d804affc35085e97944fb","tests/integer-store.rs":"f7e06c71b0dead2323c7c61fc8bcbffbdd3a4796eebf6138db9cce3dbba716a3","tests/manager.rs":"97ec61145dc227f4f5fbcb6449c096bbe5b9a09db4e61ff4491c0443fe9adf26","tests/multi-integer-store.rs":"83295b0135c502321304aa06b05d5a9eeab41b1438ed7ddf2cb1a3613dfef4d9"},"package":"238764bd8750927754d91e4a27155ac672ba88934a2bf698c992d55e5ae25e5b"}
\ No newline at end of file
--- a/third_party/rust/rkv/Cargo.toml
+++ b/third_party/rust/rkv/Cargo.toml
@@ -8,17 +8,17 @@
 # If you believe there's an error in this file please file an
 # issue against the rust-lang/cargo repository. If you're
 # editing this file be aware that the upstream Cargo.toml
 # will likely look very different (and much more reasonable)
 
 [package]
 edition = "2018"
 name = "rkv"
-version = "0.9.3"
+version = "0.9.4"
 authors = ["Richard Newman <rnewman@twinql.com>", "Nan Jiang <najiang@mozilla.com>", "Myk Melez <myk@mykzilla.org>"]
 description = "a simple, humane, typed Rust interface to LMDB"
 homepage = "https://github.com/mozilla/rkv"
 readme = "README.md"
 keywords = ["lmdb", "database", "storage"]
 categories = ["database"]
 license = "Apache-2.0"
 repository = "https://github.com/mozilla/rkv"
--- a/third_party/rust/rkv/examples/simple-store.rs
+++ b/third_party/rust/rkv/examples/simple-store.rs
@@ -148,16 +148,30 @@ fn main() {
         writer.commit().unwrap();
 
         // Committing a transaction consumes the writer, preventing you
         // from reusing it by failing and reporting a compile-time error.
         // This line would report error[E0382]: use of moved value: `writer`.
         // store.put(&mut writer, "baz", &Value::Str("buz")).unwrap();
     }
 
+    println!("Clearing store...");
+    {
+        // Clearing a store deletes all the entries in that store
+        let mut writer = k.write().unwrap();
+        store.put(&mut writer, "foo", &Value::Str("bar")).unwrap();
+        store.put(&mut writer, "bar", &Value::Str("baz")).unwrap();
+        store.clear(&mut writer).unwrap();
+        writer.commit().unwrap();
+
+        let reader = k.read().expect("reader");
+        println!("It should be None! ({:?})", store.get(&reader, "foo").unwrap());
+        println!("It should be None! ({:?})", store.get(&reader, "bar").unwrap());
+    }
+
     println!("Write and read on multiple stores...");
     {
         let another_store = k.open_single("another_store", StoreOptions::create()).unwrap();
         let mut writer = k.write().unwrap();
         store.put(&mut writer, "foo", &Value::Str("bar")).unwrap();
         another_store.put(&mut writer, "foo", &Value::Str("baz")).unwrap();
         writer.commit().unwrap();
 
--- a/third_party/rust/rkv/src/env.rs
+++ b/third_party/rust/rkv/src/env.rs
@@ -189,22 +189,22 @@ impl Rkv {
     /// but the operating system may keep it buffered.
     /// LMDB always flushes the OS buffers upon commit as well,
     /// unless the environment was opened with `NO_SYNC` or in part `NO_META_SYNC`.
     ///
     /// `force`: if true, force a synchronous flush.
     /// Otherwise if the environment has the `NO_SYNC` flag set the flushes will be omitted,
     /// and with `MAP_ASYNC` they will be asynchronous.
     pub fn sync(&self, force: bool) -> Result<(), StoreError> {
-        self.env.sync(force).map_err(|e| e.into())
+        self.env.sync(force).map_err(Into::into)
     }
 
     /// Retrieves statistics about this environment.
     pub fn stat(&self) -> Result<Stat, StoreError> {
-        self.env.stat().map_err(|e| e.into())
+        self.env.stat().map_err(Into::into)
     }
 }
 
 #[allow(clippy::cyclomatic_complexity)]
 #[cfg(test)]
 mod tests {
     use byteorder::{
         ByteOrder,
@@ -450,16 +450,45 @@ mod tests {
             let r = k.read().unwrap();
             assert_eq!(sk.get(&r, "foo").expect("read"), None);
             assert_eq!(sk.get(&r, "bar").expect("read"), None);
             assert_eq!(sk.get(&r, "baz").expect("read"), None);
         }
     }
 
     #[test]
+    fn test_single_store_clear() {
+        let root = Builder::new().prefix("test_single_store_clear").tempdir().expect("tempdir");
+        fs::create_dir_all(root.path()).expect("dir created");
+        let k = Rkv::new(root.path()).expect("new succeeded");
+
+        let sk: SingleStore = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+        {
+            let mut writer = k.write().expect("writer");
+            sk.put(&mut writer, "foo", &Value::I64(1234)).expect("wrote");
+            sk.put(&mut writer, "bar", &Value::Bool(true)).expect("wrote");
+            sk.put(&mut writer, "baz", &Value::Str("héllo, yöu")).expect("wrote");
+            writer.commit().expect("committed");
+        }
+
+        {
+            let mut writer = k.write().expect("writer");
+            sk.clear(&mut writer).expect("cleared");
+            writer.commit().expect("committed");
+        }
+
+        {
+            let r = k.read().unwrap();
+            let iter = sk.iter_start(&r).expect("iter");
+            assert_eq!(iter.count(), 0);
+        }
+    }
+
+    #[test]
     fn test_multi_put_get_del() {
         let root = Builder::new().prefix("test_multi_put_get_del").tempdir().expect("tempdir");
         fs::create_dir_all(root.path()).expect("dir created");
         let k = Rkv::new(root.path()).expect("new succeeded");
         let multistore = k.open_multi("multistore", StoreOptions::create()).unwrap();
         let mut writer = k.write().unwrap();
         multistore.put(&mut writer, "str1", &Value::Str("str1 foo")).unwrap();
         multistore.put(&mut writer, "str1", &Value::Str("str1 bar")).unwrap();
@@ -486,16 +515,49 @@ mod tests {
         assert_eq!(multistore.get_first(&writer, "str2").unwrap(), Some(Value::Str("str2 foo")));
 
         multistore.delete_all(&mut writer, "str3").unwrap();
         assert_eq!(multistore.get_first(&writer, "str3").unwrap(), None);
         writer.commit().unwrap();
     }
 
     #[test]
+    fn test_multiple_store_clear() {
+        let root = Builder::new().prefix("test_multiple_store_clear").tempdir().expect("tempdir");
+        fs::create_dir_all(root.path()).expect("dir created");
+        let k = Rkv::new(root.path()).expect("new succeeded");
+
+        let multistore = k.open_multi("multistore", StoreOptions::create()).expect("opened");
+
+        {
+            let mut writer = k.write().expect("writer");
+            multistore.put(&mut writer, "str1", &Value::Str("str1 foo")).unwrap();
+            multistore.put(&mut writer, "str1", &Value::Str("str1 bar")).unwrap();
+            multistore.put(&mut writer, "str2", &Value::Str("str2 foo")).unwrap();
+            multistore.put(&mut writer, "str2", &Value::Str("str2 bar")).unwrap();
+            multistore.put(&mut writer, "str3", &Value::Str("str3 foo")).unwrap();
+            multistore.put(&mut writer, "str3", &Value::Str("str3 bar")).unwrap();
+            writer.commit().expect("committed");
+        }
+
+        {
+            let mut writer = k.write().expect("writer");
+            multistore.clear(&mut writer).expect("cleared");
+            writer.commit().expect("committed");
+        }
+
+        {
+            let r = k.read().unwrap();
+            assert_eq!(multistore.get_first(&r, "str1").expect("read"), None);
+            assert_eq!(multistore.get_first(&r, "str2").expect("read"), None);
+            assert_eq!(multistore.get_first(&r, "str3").expect("read"), None);
+        }
+    }
+
+    #[test]
     fn test_open_store_for_read() {
         let root = Builder::new().prefix("test_open_store_for_read").tempdir().expect("tempdir");
         fs::create_dir_all(root.path()).expect("dir created");
         let k = Rkv::new(root.path()).expect("new succeeded");
         // First create the store, and start a write transaction on it.
         let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
         let mut writer = k.write().expect("writer");
         sk.put(&mut writer, "foo", &Value::Str("bar")).expect("write");
@@ -525,17 +587,17 @@ mod tests {
         let k = Rkv::new(root.path()).expect("new succeeded");
         // First create the store
         let _sk = k.open_single("sk", StoreOptions::create()).expect("opened");
         // Open a reader on this store
         let _reader = k.read().expect("reader");
         // Open the same store for read while the reader is in progress will panic
         let store: Result<SingleStore, StoreError> = k.open_single("sk", StoreOptions::default());
         match store {
-            Err(StoreError::OpenAttemptedDuringTransaction(_thread_id)) => assert!(true),
+            Err(StoreError::OpenAttemptedDuringTransaction(_thread_id)) => (),
             _ => panic!("should panic"),
         }
     }
 
     #[test]
     fn test_read_before_write_num() {
         let root = Builder::new().prefix("test_read_before_write_num").tempdir().expect("tempdir");
         fs::create_dir_all(root.path()).expect("dir created");
--- a/third_party/rust/rkv/src/lib.rs
+++ b/third_party/rust/rkv/src/lib.rs
@@ -166,16 +166,40 @@
 //!         println!("Get bar {:?}", store.get(&reader, "bar").unwrap());
 //!     }
 //!
 //!     // Committing a transaction consumes the writer, preventing you
 //!     // from reusing it by failing at compile time with an error.
 //!     // This line would report error[E0382]: borrow of moved value: `writer`.
 //!     // store.put(&mut writer, "baz", &Value::Str("buz")).unwrap();
 //! }
+//!
+//! {
+//!     // Clearing all the entries in the store with a write transaction.
+//!     {
+//!         let mut writer = env.write().unwrap();
+//!         store.put(&mut writer, "foo", &Value::Str("bar")).unwrap();
+//!         store.put(&mut writer, "bar", &Value::Str("baz")).unwrap();
+//!         writer.commit().unwrap();
+//!     }
+//!
+//!     {
+//!         let mut writer = env.write().unwrap();
+//!         store.clear(&mut writer).unwrap();
+//!         writer.commit().unwrap();
+//!     }
+//!
+//!     {
+//!         let reader = env.read().expect("reader");
+//!         println!("It should be None! ({:?})", store.get(&reader, "foo").unwrap());
+//!         println!("It should be None! ({:?})", store.get(&reader, "bar").unwrap());
+//!     }
+//!
+//! }
+//!
 //! ```
 
 #![allow(dead_code)]
 
 pub use lmdb::{
     DatabaseFlags,
     EnvironmentBuilder,
     EnvironmentFlags,
--- a/third_party/rust/rkv/src/readwrite.rs
+++ b/third_party/rust/rkv/src/readwrite.rs
@@ -83,9 +83,13 @@ impl<'env> Writer<'env> {
     ) -> Result<(), StoreError> {
         // TODO: don't allocate twice.
         self.0.put(db, &k, &v.to_bytes()?, flags).map_err(StoreError::LmdbError)
     }
 
     pub(crate) fn delete<K: AsRef<[u8]>>(&mut self, db: Database, k: &K, v: Option<&[u8]>) -> Result<(), StoreError> {
         self.0.del(db, &k, v).map_err(StoreError::LmdbError)
     }
+
+    pub(crate) fn clear(&mut self, db: Database) -> Result<(), StoreError> {
+        self.0.clear_db(db).map_err(StoreError::LmdbError)
+    }
 }
--- a/third_party/rust/rkv/src/store/integer.rs
+++ b/third_party/rust/rkv/src/store/integer.rs
@@ -39,17 +39,17 @@ pub trait PrimitiveInt: EncodableKey {}
 impl PrimitiveInt for u32 {}
 
 impl<T> EncodableKey for T
 where
     T: Serialize,
 {
     fn to_bytes(&self) -> Result<Vec<u8>, DataError> {
         serialize(self) // TODO: limited key length.
-            .map_err(|e| e.into())
+            .map_err(Into::into)
     }
 }
 
 pub(crate) struct Key<K> {
     bytes: Vec<u8>,
     phantom: PhantomData<K>,
 }
 
@@ -100,16 +100,20 @@ where
 
     pub fn put(&self, writer: &mut Writer, k: K, v: &Value) -> Result<(), StoreError> {
         self.inner.put(writer, Key::new(&k)?, v)
     }
 
     pub fn delete(&self, writer: &mut Writer, k: K) -> Result<(), StoreError> {
         self.inner.delete(writer, Key::new(&k)?)
     }
+
+    pub fn clear(&self, writer: &mut Writer) -> Result<(), StoreError> {
+        self.inner.clear(writer)
+    }
 }
 
 #[cfg(test)]
 mod tests {
     use std::fs;
     use tempfile::Builder;
 
     use super::*;
@@ -133,9 +137,36 @@ mod tests {
                 let reader = k.read().expect("reader");
                 assert_eq!(s.get(&reader, $key).expect("read"), Some(Value::Str("hello!")));
             }};
         }
 
         test_integer_keys!(u32, std::u32::MIN);
         test_integer_keys!(u32, std::u32::MAX);
     }
+
+    #[test]
+    fn test_clear() {
+        let root = Builder::new().prefix("test_integer_clear").tempdir().expect("tempdir");
+        fs::create_dir_all(root.path()).expect("dir created");
+        let k = Rkv::new(root.path()).expect("new succeeded");
+        let s = k.open_integer("s", StoreOptions::create()).expect("open");
+
+        {
+            let mut writer = k.write().expect("writer");
+            s.put(&mut writer, 1, &Value::Str("hello!")).expect("write");
+            s.put(&mut writer, 2, &Value::Str("hello!")).expect("write");
+            s.put(&mut writer, 3, &Value::Str("hello!")).expect("write");
+            writer.commit().expect("committed");
+        }
+
+        {
+            let mut writer = k.write().expect("writer");
+            s.clear(&mut writer).expect("cleared");
+            writer.commit().expect("committed");
+
+            let reader = k.read().expect("reader");
+            assert_eq!(s.get(&reader, 1).expect("read"), None);
+            assert_eq!(s.get(&reader, 2).expect("read"), None);
+            assert_eq!(s.get(&reader, 3).expect("read"), None);
+        }
+    }
 }
--- a/third_party/rust/rkv/src/store/integermulti.rs
+++ b/third_party/rust/rkv/src/store/integermulti.rs
@@ -71,16 +71,20 @@ where
 
     pub fn delete_all(&self, writer: &mut Writer, k: K) -> Result<(), StoreError> {
         self.inner.delete_all(writer, Key::new(&k)?)
     }
 
     pub fn delete(&self, writer: &mut Writer, k: K, v: &Value) -> Result<(), StoreError> {
         self.inner.delete(writer, Key::new(&k)?, v)
     }
+
+    pub fn clear(&self, writer: &mut Writer) -> Result<(), StoreError> {
+        self.inner.clear(writer)
+    }
 }
 
 #[cfg(test)]
 mod tests {
     extern crate tempfile;
 
     use self::tempfile::Builder;
     use std::fs;
@@ -106,9 +110,35 @@ mod tests {
                 let reader = k.read().expect("reader");
                 assert_eq!(s.get_first(&reader, $key).expect("read"), Some(Value::Str("hello!")));
             }};
         }
 
         test_integer_keys!(u32, std::u32::MIN);
         test_integer_keys!(u32, std::u32::MAX);
     }
+
+    #[test]
+    fn test_clear() {
+        let root = Builder::new().prefix("test_multi_integer_clear").tempdir().expect("tempdir");
+        fs::create_dir_all(root.path()).expect("dir created");
+        let k = Rkv::new(root.path()).expect("new succeeded");
+        let s = k.open_multi_integer("s", StoreOptions::create()).expect("open");
+
+        {
+            let mut writer = k.write().expect("writer");
+            s.put(&mut writer, 1, &Value::Str("hello!")).expect("write");
+            s.put(&mut writer, 1, &Value::Str("hello1!")).expect("write");
+            s.put(&mut writer, 2, &Value::Str("hello!")).expect("write");
+            writer.commit().expect("committed");
+        }
+
+        {
+            let mut writer = k.write().expect("writer");
+            s.clear(&mut writer).expect("cleared");
+            writer.commit().expect("committed");
+
+            let reader = k.read().expect("reader");
+            assert_eq!(s.get_first(&reader, 1).expect("read"), None);
+            assert_eq!(s.get_first(&reader, 2).expect("read"), None);
+        }
+    }
 }
--- a/third_party/rust/rkv/src/store/multi.rs
+++ b/third_party/rust/rkv/src/store/multi.rs
@@ -100,16 +100,20 @@ impl MultiStore {
         let iter = cursor.iter_dup();
 
         Ok(MultiIter {
             iter,
             cursor,
         })
     }
     */
+
+    pub fn clear(self, writer: &mut Writer) -> Result<(), StoreError> {
+        writer.clear(self.db)
+    }
 }
 
 /*
 fn read_transform_owned(val: Result<&[u8], lmdb::Error>) -> Result<Option<OwnedValue>, StoreError> {
     match val {
         Ok(bytes) => Value::from_tagged_slice(bytes).map(|v| Some(OwnedValue::from(&v))).map_err(StoreError::DataError),
         Err(lmdb::Error::NotFound) => Ok(None),
         Err(e) => Err(StoreError::LmdbError(e)),
--- a/third_party/rust/rkv/src/store/single.rs
+++ b/third_party/rust/rkv/src/store/single.rs
@@ -77,16 +77,20 @@ impl SingleStore {
     pub fn iter_from<T: Readable, K: AsRef<[u8]>>(self, reader: &T, k: K) -> Result<Iter, StoreError> {
         let mut cursor = reader.open_ro_cursor(self.db)?;
         let iter = cursor.iter_from(k);
         Ok(Iter {
             iter,
             cursor,
         })
     }
+
+    pub fn clear(self, writer: &mut Writer) -> Result<(), StoreError> {
+        writer.clear(self.db)
+    }
 }
 
 impl<'env> Iterator for Iter<'env> {
     type Item = Result<(&'env [u8], Option<Value<'env>>), StoreError>;
 
     fn next(&mut self) -> Option<Self::Item> {
         match self.iter.next() {
             None => None,
--- a/toolkit/components/kvstore/Cargo.toml
+++ b/toolkit/components/kvstore/Cargo.toml
@@ -7,18 +7,19 @@ authors = ["Myk Melez <myk@mykzilla.org>
 atomic_refcell = "0.1"
 crossbeam-utils = "0.6.3"
 libc = "0.2"
 lmdb-rkv = "0.11.2"
 log = "0.4"
 moz_task = { path = "../../../xpcom/rust/moz_task" }
 nserror = { path = "../../../xpcom/rust/nserror" }
 nsstring = { path = "../../../xpcom/rust/nsstring" }
-rkv = "0.9.3"
+rkv = "0.9.4"
 storage_variant = { path = "../../../storage/variant" }
 xpcom = { path = "../../../xpcom/rust/xpcom" }
+thin-vec = { version = "0.1.0", features = ["gecko-ffi"] }
 
 # Get rid of failure's dependency on backtrace. Eventually
 # backtrace will move into Rust core, but we don't need it here.
 [dependencies.failure]
 version = "0.1"
 default_features = false
 features = ["derive"]
--- a/toolkit/components/kvstore/kvstore.jsm
+++ b/toolkit/components/kvstore/kvstore.jsm
@@ -55,40 +55,98 @@ class KeyValueService {
  * ```
  *     await database.put("foo", 1);
  *     await database.get("foo") === 1; // true
  *     await database.has("foo"); // true
  *     await database.delete("foo");
  *     await database.has("foo"); // false
  * ```
  *
+ * You can also call putMany() to put multiple key/value pairs:
+ *
+ * ```
+ *     await database.putMany({
+ *       key1: "value1",
+ *       key2: "value2",
+ *       key3: "value3",
+ *     });
+ * ```
+ *
  * And you can call its enumerate() method to retrieve a KeyValueEnumerator,
  * which is described below.
  */
 class KeyValueDatabase {
   constructor(database) {
     this.database = database;
   }
 
   put(key, value) {
     return promisify(this.database.put, key, value);
   }
 
+  /**
+   * Puts multiple key/value pairs to the database.
+   *
+   * @param pairs Pairs could be any of following types:
+   *        * An Object, all its properties and the corresponding values will
+   *          be used as key value pairs.
+   *        * A Map.
+   *        * An Array or an iterable whose elements are key-values pairs, such
+   *          as [["key1", "value1"], ["key2", "value2"]]. Note: given multiple
+   *          values with the same key, only the last value will be stored.
+   *
+   * @return A promise that is fulfilled when all the key/value pairs are written
+   *         to the database.
+   */
+  putMany(pairs) {
+    if (!pairs) {
+      throw new Error("putMany(): unexpected argument.");
+    }
+
+    let entries;
+
+    if (pairs instanceof Map || pairs instanceof Array ||
+        typeof(pairs[Symbol.iterator]) === "function") {
+      try {
+        // Let Map constructor validate the argument. Although Map accepts a
+        // different set of key/value types than that of kvstore, we do not
+        // need to check that here since it will be done later.
+        const map = pairs instanceof Map ? pairs : new Map(pairs);
+        entries = Array.from(map, ([key, value]) => ({key, value}));
+      } catch (error) {
+        throw new Error("putMany(): unexpected argument.");
+      }
+    } else if (typeof(pairs) === "object") {
+      entries = Array.from(Object.entries(pairs), ([key, value]) => ({key, value}));
+    } else {
+      throw new Error("putMany(): unexpected argument.");
+    }
+
+    if (entries.length) {
+      return promisify(this.database.putMany, entries);
+    }
+    return Promise.resolve();
+  }
+
   has(key) {
     return promisify(this.database.has, key);
   }
 
   get(key, defaultValue) {
     return promisify(this.database.get, key, defaultValue);
   }
 
   delete(key) {
     return promisify(this.database.delete, key);
   }
 
+  clear() {
+    return promisify(this.database.clear);
+  }
+
   async enumerate(from_key, to_key) {
     return new KeyValueEnumerator(
       await promisify(this.database.enumerate, from_key, to_key)
     );
   }
 }
 
 /**
--- a/toolkit/components/kvstore/nsIKeyValue.idl
+++ b/toolkit/components/kvstore/nsIKeyValue.idl
@@ -5,16 +5,17 @@
 #include "nsISupports.idl"
 #include "nsIVariant.idl"
 
 interface nsIKeyValueDatabaseCallback;
 interface nsIKeyValueEnumeratorCallback;
 interface nsIKeyValuePairCallback;
 interface nsIKeyValueVariantCallback;
 interface nsIKeyValueVoidCallback;
+interface nsIKeyValuePair;
 
 /**
  * The nsIKeyValue* interfaces provide a simple, asynchronous API to a key/value
  * storage engine.  Basic put/get/has/delete operations are supported, as is
  * enumeration of key/value pairs and the use of multiple named databases within
  * a single storage file.  Operations have ACID semantics.
  *
  * This API does not (yet) support transactions, so it will not be appropriate
@@ -60,16 +61,32 @@ interface nsIKeyValueDatabase : nsISuppo
      * Write the specified key/value pair to the database.
      */
     void put(
         in nsIKeyValueVoidCallback callback,
         in AUTF8String key,
         in nsIVariant value);
 
     /**
+     * Write multiple key/value pairs to the database.
+     *
+     * This features the "all-or-nothing" semantics, i.e. if any error occurs
+     * during the call, it will rollback the previous puts and terminate the
+     * put. In addition, putMany should be more efficient than calling "put"
+     * for every single key/value pair since it does all the puts in a single
+     * transaction.
+     *
+     * Note: if there are multiple values with the same key in the specified
+     * pairs, only the last value will be stored in the database.
+     */
+    void putMany(
+        in nsIKeyValueVoidCallback callback,
+        in Array<nsIKeyValuePair> pairs);
+
+    /**
      * Retrieve the value of the specified key from the database.
      *
      * If the key/value pair doesn't exist in the database, and you specify
      * a default value, then the default value will be returned.  Otherwise,
      * the callback's resolve() method will be called with a variant
      * of type VTYPE_EMPTY, which translates to the JS `null` value.
      */
     void get(
@@ -92,16 +109,21 @@ interface nsIKeyValueDatabase : nsISuppo
      * of its callback rather than reject().  If you want to know whether
      * or not a key exists when deleting it, call the has() method first.
      */
     void delete(
         in nsIKeyValueVoidCallback callback,
         in AUTF8String key);
 
     /**
+     * Clear all the key/value pairs from the database.
+     */
+    void clear(in nsIKeyValueVoidCallback callback);
+
+    /**
      * Enumerate key/value pairs, starting with the first key equal to
      * or greater than the "from" key (inclusive) and ending with the last key
      * less than the "to" key (exclusive) sorted lexicographically.
      *
      * If either key is omitted, the range extends to the first and/or last key
      * in the database.
      */
     void enumerate(
--- a/toolkit/components/kvstore/src/lib.rs
+++ b/toolkit/components/kvstore/src/lib.rs
@@ -9,16 +9,17 @@ extern crate failure;
 extern crate libc;
 extern crate lmdb;
 extern crate log;
 extern crate moz_task;
 extern crate nserror;
 extern crate nsstring;
 extern crate rkv;
 extern crate storage_variant;
+extern crate thin_vec;
 extern crate xpcom;
 
 mod error;
 mod owned_value;
 mod task;
 
 use atomic_refcell::AtomicRefCell;
 use error::KeyValueError;
@@ -28,23 +29,27 @@ use nserror::{nsresult, NS_ERROR_FAILURE
 use nsstring::{nsACString, nsCString};
 use owned_value::{owned_to_variant, variant_to_owned};
 use rkv::{OwnedValue, Rkv, SingleStore};
 use std::{
     ptr,
     sync::{Arc, RwLock},
     vec::IntoIter,
 };
-use task::{DeleteTask, EnumerateTask, GetOrCreateTask, GetTask, HasTask, PutTask};
+use task::{
+    ClearTask, DeleteTask, EnumerateTask, GetOrCreateTask, GetTask, HasTask,
+    PutTask, PutManyTask,
+};
+use thin_vec::ThinVec;
 use xpcom::{
     interfaces::{
         nsIKeyValueDatabaseCallback, nsIKeyValueEnumeratorCallback, nsIKeyValuePair,
         nsIKeyValueVariantCallback, nsIKeyValueVoidCallback, nsISupports, nsIThread, nsIVariant,
     },
-    nsIID, RefPtr, ThreadBoundRefPtr, xpcom, xpcom_method,
+    getter_addrefs, nsIID, RefPtr, ThreadBoundRefPtr, xpcom, xpcom_method,
 };
 
 type KeyValuePairResult = Result<(String, OwnedValue), KeyValueError>;
 
 #[no_mangle]
 pub unsafe extern "C" fn nsKeyValueServiceConstructor(
     outer: *const nsISupports,
     iid: &nsIID,
@@ -156,35 +161,75 @@ impl KeyValueDatabase {
     );
 
     fn put(
         &self,
         callback: &nsIKeyValueVoidCallback,
         key: &nsACString,
         value: &nsIVariant,
     ) -> Result<(), nsresult> {
-        let value = match variant_to_owned(value)? {
-            Some(value) => Ok(value),
-            None => Err(KeyValueError::UnexpectedValue),
-        }?;
+        let value = variant_to_owned(value)?
+            .ok_or(KeyValueError::UnexpectedValue)?;
 
         let task = Box::new(PutTask::new(
             RefPtr::new(callback),
             Arc::clone(&self.rkv),
             self.store,
             nsCString::from(key),
             value,
         ));
 
         let thread = self.thread.get_ref().ok_or(NS_ERROR_FAILURE)?;
 
         TaskRunnable::new("KVDatabase::Put", task)?.dispatch(thread)
     }
 
     xpcom_method!(
+        put_many => PutMany(
+            callback: *const nsIKeyValueVoidCallback,
+            pairs: *const ThinVec<RefPtr<nsIKeyValuePair>>
+        )
+    );
+
+    fn put_many(
+        &self,
+        callback: &nsIKeyValueVoidCallback,
+        pairs: &ThinVec<RefPtr<nsIKeyValuePair>>
+    ) -> Result<(), nsresult> {
+        let mut entries = Vec::with_capacity(pairs.len());
+
+        for pair in pairs {
+            let mut key = nsCString::new();
+            unsafe {
+                pair.GetKey(&mut *key)
+            }.to_result()?;
+            if key.is_empty() {
+                return Err(nsresult::from(KeyValueError::UnexpectedValue));
+            }
+
+            let val: RefPtr<nsIVariant> =
+                getter_addrefs(|p| unsafe { pair.GetValue(p) })?;
+            let value = variant_to_owned(&val)?
+                .ok_or(KeyValueError::UnexpectedValue)?;
+            entries.push((key, value));
+        }
+
+        let task = Box::new(PutManyTask::new(
+            RefPtr::new(callback),
+            Arc::clone(&self.rkv),
+            self.store,
+            entries,
+        ));
+
+        let thread = self.thread.get_ref().ok_or(NS_ERROR_FAILURE)?;
+
+        TaskRunnable::new("KVDatabase::PutMany", task)?.dispatch(thread)
+    }
+
+    xpcom_method!(
         get => Get(
             callback: *const nsIKeyValueVariantCallback,
             key: *const nsACString,
             default_value: *const nsIVariant
         )
     );
 
     fn get(
@@ -236,16 +281,35 @@ impl KeyValueDatabase {
         ));
 
         let thread = self.thread.get_ref().ok_or(NS_ERROR_FAILURE)?;
 
         TaskRunnable::new("KVDatabase::Delete", task)?.dispatch(thread)
     }
 
     xpcom_method!(
+        clear => Clear(callback: *const nsIKeyValueVoidCallback)
+    );
+
+    fn clear(
+        &self,
+        callback: &nsIKeyValueVoidCallback,
+    ) -> Result<(), nsresult> {
+        let task = Box::new(ClearTask::new(
+            RefPtr::new(callback),
+            Arc::clone(&self.rkv),
+            self.store
+        ));
+
+        let thread = self.thread.get_ref().ok_or(NS_ERROR_FAILURE)?;
+
+        TaskRunnable::new("KVDatabase::Clear", task)?.dispatch(thread)
+    }
+
+    xpcom_method!(
         enumerate => Enumerate(
             callback: *const nsIKeyValueEnumeratorCallback,
             from_key: *const nsACString,
             to_key: *const nsACString
         )
     );
 
     fn enumerate(
--- a/toolkit/components/kvstore/src/task.rs
+++ b/toolkit/components/kvstore/src/task.rs
@@ -176,16 +176,62 @@ impl Task for PutTask {
 
             Ok(())
         }()));
     }
 
     task_done!(void);
 }
 
+pub struct PutManyTask {
+    callback: AtomicCell<Option<ThreadBoundRefPtr<nsIKeyValueVoidCallback>>>,
+    rkv: Arc<RwLock<Rkv>>,
+    store: SingleStore,
+    pairs: Vec<(nsCString, OwnedValue)>,
+    result: AtomicCell<Option<Result<(), KeyValueError>>>,
+}
+
+impl PutManyTask {
+    pub fn new(
+        callback: RefPtr<nsIKeyValueVoidCallback>,
+        rkv: Arc<RwLock<Rkv>>,
+        store: SingleStore,
+        pairs: Vec<(nsCString, OwnedValue)>,
+    ) -> PutManyTask {
+        PutManyTask {
+            callback: AtomicCell::new(Some(ThreadBoundRefPtr::new(callback))),
+            rkv,
+            store,
+            pairs,
+            result: AtomicCell::default(),
+        }
+    }
+}
+
+impl Task for PutManyTask {
+    fn run(&self) {
+        // We do the work within a closure that returns a Result so we can
+        // use the ? operator to simplify the implementation.
+        self.result.store(Some(|| -> Result<(), KeyValueError> {
+            let env = self.rkv.read()?;
+            let mut writer = env.write()?;
+
+            for (key, value) in self.pairs.iter() {
+                let key = str::from_utf8(key)?;
+                self.store.put(&mut writer, key, &Value::from(value))?;
+            }
+            writer.commit()?;
+
+            Ok(())
+        }()));
+    }
+
+    task_done!(void);
+}
+
 pub struct GetTask {
     callback: AtomicCell<Option<ThreadBoundRefPtr<nsIKeyValueVariantCallback>>>,
     rkv: Arc<RwLock<Rkv>>,
     store: SingleStore,
     key: nsCString,
     default_value: Option<OwnedValue>,
     result: AtomicCell<Option<Result<Option<OwnedValue>, KeyValueError>>>,
 }
@@ -334,16 +380,55 @@ impl Task for DeleteTask {
 
             Ok(())
         }()));
     }
 
     task_done!(void);
 }
 
+pub struct ClearTask {
+    callback: AtomicCell<Option<ThreadBoundRefPtr<nsIKeyValueVoidCallback>>>,
+    rkv: Arc<RwLock<Rkv>>,
+    store: SingleStore,
+    result: AtomicCell<Option<Result<(), KeyValueError>>>,
+}
+
+impl ClearTask {
+    pub fn new(
+        callback: RefPtr<nsIKeyValueVoidCallback>,
+        rkv: Arc<RwLock<Rkv>>,
+        store: SingleStore,
+    ) -> ClearTask {
+        ClearTask {
+            callback: AtomicCell::new(Some(ThreadBoundRefPtr::new(callback))),
+            rkv,
+            store,
+            result: AtomicCell::default(),
+        }
+    }
+}
+
+impl Task for ClearTask {
+    fn run(&self) {
+        // We do the work within a closure that returns a Result so we can
+        // use the ? operator to simplify the implementation.
+        self.result.store(Some(|| -> Result<(), KeyValueError> {
+            let env = self.rkv.read()?;
+            let mut writer = env.write()?;
+            self.store.clear(&mut writer)?;
+            writer.commit()?;
+
+            Ok(())
+        }()));
+    }
+
+    task_done!(void);
+}
+
 pub struct EnumerateTask {
     callback: AtomicCell<Option<ThreadBoundRefPtr<nsIKeyValueEnumeratorCallback>>>,
     rkv: Arc<RwLock<Rkv>>,
     store: SingleStore,
     from_key: nsCString,
     to_key: nsCString,
     result: AtomicCell<Option<Result<Vec<KeyValuePairResult>, KeyValueError>>>,
 }
--- a/toolkit/components/kvstore/test/xpcshell/test_kvstore.js
+++ b/toolkit/components/kvstore/test/xpcshell/test_kvstore.js
@@ -125,16 +125,93 @@ add_task(async function extendedCharacte
 
   const enumerator = await database.enumerate();
   const { key } = enumerator.getNext();
   Assert.strictEqual(key, "Héllo, wőrld!");
 
   await database.delete("Héllo, wőrld!");
 });
 
+add_task(async function clear() {
+  const databaseDir = await makeDatabaseDir("clear");
+  const database = await KeyValueService.getOrCreate(databaseDir, "db");
+
+  await database.put("int-key", 1234);
+  await database.put("double-key", 56.78);
+  await database.put("string-key", "Héllo, wőrld!");
+  await database.put("bool-key", true);
+
+  Assert.strictEqual(await database.clear(), undefined);
+  Assert.strictEqual(await database.has("int-key"), false);
+  Assert.strictEqual(await database.has("double-key"), false);
+  Assert.strictEqual(await database.has("string-key"), false);
+  Assert.strictEqual(await database.has("bool-key"), false);
+});
+
+add_task(async function putMany() {
+  const databaseDir = await makeDatabaseDir("putMany");
+  const database = await KeyValueService.getOrCreate(databaseDir, "db");
+
+  async function test_helper(pairs) {
+    Assert.strictEqual(await database.putMany(pairs), undefined);
+    Assert.strictEqual(await database.get("int-key"), 1234);
+    Assert.strictEqual(await database.get("double-key"), 56.78);
+    Assert.strictEqual(await database.get("string-key"), "Héllo, wőrld!");
+    Assert.strictEqual(await database.get("bool-key"), true);
+    await database.clear();
+  }
+
+  // putMany with an empty object is OK
+  Assert.strictEqual(await database.putMany({}), undefined);
+
+  // putMany with an object
+  const pairs = {
+    "int-key": 1234,
+    "double-key": 56.78,
+    "string-key": "Héllo, wőrld!",
+    "bool-key": true,
+  };
+  await test_helper(pairs);
+
+  // putMany with an array of pairs
+  const arrayPairs = [
+    ["int-key", 1234],
+    ["double-key", 56.78],
+    ["string-key", "Héllo, wőrld!"],
+    ["bool-key", true],
+  ];
+  await test_helper(arrayPairs);
+
+  // putMany with a key/value generator
+  function* pairMaker() {
+    yield ["int-key", 1234];
+    yield ["double-key", 56.78];
+    yield ["string-key", "Héllo, wőrld!"];
+    yield ["bool-key", true];
+  }
+  await test_helper(pairMaker());
+
+  // putMany with a map
+  const mapPairs = new Map(arrayPairs);
+  await test_helper(mapPairs);
+});
+
+add_task(async function putManyFailureCases() {
+  const databaseDir = await makeDatabaseDir("putManyFailureCases");
+  const database = await KeyValueService.getOrCreate(databaseDir, "db");
+
+  Assert.throws(() => database.putMany(), /unexpected argument/);
+  Assert.throws(() => database.putMany("foo"), /unexpected argument/);
+  Assert.throws(() => database.putMany(["foo"]), /unexpected argument/);
+
+  const pairWithoutValue = {"key": undefined};
+  await Assert.rejects(database.putMany(pairWithoutValue), /NS_ERROR_UNEXPECTED/);
+  await Assert.rejects(database.putMany([["foo"]]), /NS_ERROR_UNEXPECTED/);
+});
+
 add_task(async function getOrCreateNamedDatabases() {
   const databaseDir = await makeDatabaseDir("getOrCreateNamedDatabases");
 
   let fooDB = await KeyValueService.getOrCreate(databaseDir, "foo");
   Assert.ok(fooDB, "retrieval of first named database works");
 
   let barDB = await KeyValueService.getOrCreate(databaseDir, "bar");
   Assert.ok(barDB, "retrieval of second named database works");