mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 11:25:00 +00:00
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 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
4657bbfafd
commit
719f34cee5
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -436,7 +436,7 @@ 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)",
|
||||
@ -1481,8 +1481,9 @@ dependencies = [
|
||||
"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",
|
||||
]
|
||||
|
||||
@ -2347,7 +2348,7 @@ dependencies = [
|
||||
|
||||
[[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)",
|
||||
@ -3677,7 +3678,7 @@ dependencies = [
|
||||
"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"
|
||||
|
@ -7,8 +7,8 @@ authors = ["Dana Keeler <dkeeler@mozilla.com>", "Mark Goodwin <mgoodwin@mozilla.
|
||||
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" }
|
||||
xpcom = { path = "../../../../xpcom/rust/xpcom" }
|
||||
|
2
third_party/rust/rkv/.cargo-checksum.json
vendored
2
third_party/rust/rkv/.cargo-checksum.json
vendored
@ -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"}
|
||||
{"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"}
|
2
third_party/rust/rkv/Cargo.toml
vendored
2
third_party/rust/rkv/Cargo.toml
vendored
@ -13,7 +13,7 @@
|
||||
[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"
|
||||
|
14
third_party/rust/rkv/examples/simple-store.rs
vendored
14
third_party/rust/rkv/examples/simple-store.rs
vendored
@ -153,6 +153,20 @@ fn main() {
|
||||
// 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();
|
||||
|
68
third_party/rust/rkv/src/env.rs
vendored
68
third_party/rust/rkv/src/env.rs
vendored
@ -194,12 +194,12 @@ impl Rkv {
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -454,6 +454,35 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[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");
|
||||
@ -490,6 +519,39 @@ mod tests {
|
||||
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");
|
||||
@ -530,7 +592,7 @@ mod tests {
|
||||
// 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"),
|
||||
}
|
||||
}
|
||||
|
24
third_party/rust/rkv/src/lib.rs
vendored
24
third_party/rust/rkv/src/lib.rs
vendored
@ -171,6 +171,30 @@
|
||||
//! // 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)]
|
||||
|
4
third_party/rust/rkv/src/readwrite.rs
vendored
4
third_party/rust/rkv/src/readwrite.rs
vendored
@ -88,4 +88,8 @@ impl<'env> Writer<'env> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
33
third_party/rust/rkv/src/store/integer.rs
vendored
33
third_party/rust/rkv/src/store/integer.rs
vendored
@ -44,7 +44,7 @@ where
|
||||
{
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, DataError> {
|
||||
serialize(self) // TODO: limited key length.
|
||||
.map_err(|e| e.into())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +105,10 @@ where
|
||||
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)]
|
||||
@ -138,4 +142,31 @@ mod tests {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
third_party/rust/rkv/src/store/integermulti.rs
vendored
30
third_party/rust/rkv/src/store/integermulti.rs
vendored
@ -76,6 +76,10 @@ where
|
||||
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)]
|
||||
@ -111,4 +115,30 @@ mod tests {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
third_party/rust/rkv/src/store/multi.rs
vendored
4
third_party/rust/rkv/src/store/multi.rs
vendored
@ -105,6 +105,10 @@ impl MultiStore {
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn clear(self, writer: &mut Writer) -> Result<(), StoreError> {
|
||||
writer.clear(self.db)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
4
third_party/rust/rkv/src/store/single.rs
vendored
4
third_party/rust/rkv/src/store/single.rs
vendored
@ -82,6 +82,10 @@ impl SingleStore {
|
||||
cursor,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clear(self, writer: &mut Writer) -> Result<(), StoreError> {
|
||||
writer.clear(self.db)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> Iterator for Iter<'env> {
|
||||
|
@ -12,9 +12,10 @@ 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.
|
||||
|
@ -60,6 +60,16 @@ class KeyValueService {
|
||||
* 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.
|
||||
*/
|
||||
@ -72,6 +82,50 @@ class KeyValueDatabase {
|
||||
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);
|
||||
}
|
||||
@ -84,6 +138,10 @@ class KeyValueDatabase {
|
||||
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)
|
||||
|
@ -10,6 +10,7 @@ interface nsIKeyValueEnumeratorCallback;
|
||||
interface nsIKeyValuePairCallback;
|
||||
interface nsIKeyValueVariantCallback;
|
||||
interface nsIKeyValueVoidCallback;
|
||||
interface nsIKeyValuePair;
|
||||
|
||||
/**
|
||||
* The nsIKeyValue* interfaces provide a simple, asynchronous API to a key/value
|
||||
@ -64,6 +65,22 @@ interface nsIKeyValueDatabase : nsISupports {
|
||||
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.
|
||||
*
|
||||
@ -96,6 +113,11 @@ interface nsIKeyValueDatabase : nsISupports {
|
||||
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
|
||||
|
@ -14,6 +14,7 @@ extern crate nserror;
|
||||
extern crate nsstring;
|
||||
extern crate rkv;
|
||||
extern crate storage_variant;
|
||||
extern crate thin_vec;
|
||||
extern crate xpcom;
|
||||
|
||||
mod error;
|
||||
@ -33,13 +34,17 @@ use std::{
|
||||
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>;
|
||||
@ -161,10 +166,8 @@ impl KeyValueDatabase {
|
||||
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),
|
||||
@ -179,6 +182,48 @@ impl KeyValueDatabase {
|
||||
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,
|
||||
@ -240,6 +285,25 @@ impl KeyValueDatabase {
|
||||
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,
|
||||
|
@ -181,6 +181,52 @@ impl Task for PutTask {
|
||||
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>>,
|
||||
@ -339,6 +385,45 @@ impl Task for DeleteTask {
|
||||
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>>,
|
||||
|
@ -130,6 +130,83 @@ add_task(async function extendedCharacterKey() {
|
||||
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");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user