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:
Nan Jiang 2019-03-27 14:16:59 +00:00
parent 4657bbfafd
commit 719f34cee5
18 changed files with 500 additions and 19 deletions

9
Cargo.lock generated
View File

@ -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"

View File

@ -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" }

View File

@ -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"}

View File

@ -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"

View File

@ -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();

View File

@ -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"),
}
}

View File

@ -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)]

View File

@ -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)
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -105,6 +105,10 @@ impl MultiStore {
})
}
*/
pub fn clear(self, writer: &mut Writer) -> Result<(), StoreError> {
writer.clear(self.db)
}
}
/*

View File

@ -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> {

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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>>,

View File

@ -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");