summaryrefslogtreecommitdiffstats
path: root/third_party/rust/rkv/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/rkv/tests
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--third_party/rust/rkv/tests/env-all.rs328
-rw-r--r--third_party/rust/rkv/tests/env-lmdb.rs1647
-rw-r--r--third_party/rust/rkv/tests/env-migration.rs607
-rw-r--r--third_party/rust/rkv/tests/env-safe.rs1370
-rw-r--r--third_party/rust/rkv/tests/integer-store.rs88
-rw-r--r--third_party/rust/rkv/tests/manager.rs380
-rw-r--r--third_party/rust/rkv/tests/multi-integer-store.rs122
-rw-r--r--third_party/rust/rkv/tests/test_txn.rs131
8 files changed, 4673 insertions, 0 deletions
diff --git a/third_party/rust/rkv/tests/env-all.rs b/third_party/rust/rkv/tests/env-all.rs
new file mode 100644
index 0000000000..a8d93c786e
--- /dev/null
+++ b/third_party/rust/rkv/tests/env-all.rs
@@ -0,0 +1,328 @@
+// Copyright 2018-2019 Mozilla
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+// this file except in compliance with the License. You may obtain a copy of the
+// License at http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software distributed
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations under the License.
+
+use std::fs;
+
+use tempfile::Builder;
+
+use rkv::{
+ backend::{Lmdb, SafeMode},
+ Rkv, StoreOptions, Value,
+};
+
+#[test]
+fn test_open_safe_same_dir_as_lmdb() {
+ let root = Builder::new()
+ .prefix("test_open_safe_same_dir_as_lmdb")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ // Create database of type A and save to disk.
+ {
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = 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");
+ assert_eq!(
+ sk.get(&writer, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&writer, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&writer, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ writer.commit().expect("committed");
+ k.sync(true).expect("synced");
+ }
+ // Verify that database of type A was written to disk.
+ {
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+ // Create database of type B and verify that it is empty.
+ {
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let _ = k
+ .open_single("sk", StoreOptions::default())
+ .expect_err("not opened");
+ }
+ // Verify that database of type A wasn't changed.
+ {
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+ // Create database of type B and save to disk (type A exists at the same path).
+ {
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ let mut writer = k.write().expect("writer");
+ sk.put(&mut writer, "foo1", &Value::I64(5678))
+ .expect("wrote");
+ sk.put(&mut writer, "bar1", &Value::Bool(false))
+ .expect("wrote");
+ sk.put(&mut writer, "baz1", &Value::Str("héllo~ yöu"))
+ .expect("wrote");
+ assert_eq!(
+ sk.get(&writer, "foo1").expect("read"),
+ Some(Value::I64(5678))
+ );
+ assert_eq!(
+ sk.get(&writer, "bar1").expect("read"),
+ Some(Value::Bool(false))
+ );
+ assert_eq!(
+ sk.get(&writer, "baz1").expect("read"),
+ Some(Value::Str("héllo~ yöu"))
+ );
+ writer.commit().expect("committed");
+ k.sync(true).expect("synced");
+ }
+ // Verify that database of type B was written to disk.
+ {
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk.get(&reader, "foo1").expect("read"),
+ Some(Value::I64(5678))
+ );
+ assert_eq!(
+ sk.get(&reader, "bar1").expect("read"),
+ Some(Value::Bool(false))
+ );
+ assert_eq!(
+ sk.get(&reader, "baz1").expect("read"),
+ Some(Value::Str("héllo~ yöu"))
+ );
+ }
+ // Verify that database of type A still wasn't changed.
+ {
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+}
+
+#[test]
+fn test_open_lmdb_same_dir_as_safe() {
+ let root = Builder::new()
+ .prefix("test_open_lmdb_same_dir_as_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ // Create database of type A and save to disk.
+ {
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = 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");
+ assert_eq!(
+ sk.get(&writer, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&writer, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&writer, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ writer.commit().expect("committed");
+ k.sync(true).expect("synced");
+ }
+ // Verify that database of type A was written to disk.
+ {
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+ // Create database of type B and verify that it is empty.
+ {
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let _ = k
+ .open_single("sk", StoreOptions::default())
+ .expect_err("not opened");
+ }
+ // Verify that database of type A wasn't changed.
+ {
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+ // Create database of type B and save to disk (type A exists at the same path).
+ {
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ let mut writer = k.write().expect("writer");
+ sk.put(&mut writer, "foo1", &Value::I64(5678))
+ .expect("wrote");
+ sk.put(&mut writer, "bar1", &Value::Bool(false))
+ .expect("wrote");
+ sk.put(&mut writer, "baz1", &Value::Str("héllo~ yöu"))
+ .expect("wrote");
+ assert_eq!(
+ sk.get(&writer, "foo1").expect("read"),
+ Some(Value::I64(5678))
+ );
+ assert_eq!(
+ sk.get(&writer, "bar1").expect("read"),
+ Some(Value::Bool(false))
+ );
+ assert_eq!(
+ sk.get(&writer, "baz1").expect("read"),
+ Some(Value::Str("héllo~ yöu"))
+ );
+ writer.commit().expect("committed");
+ k.sync(true).expect("synced");
+ }
+ // Verify that database of type B was written to disk.
+ {
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk.get(&reader, "foo1").expect("read"),
+ Some(Value::I64(5678))
+ );
+ assert_eq!(
+ sk.get(&reader, "bar1").expect("read"),
+ Some(Value::Bool(false))
+ );
+ assert_eq!(
+ sk.get(&reader, "baz1").expect("read"),
+ Some(Value::Str("héllo~ yöu"))
+ );
+ }
+ // Verify that database of type A still wasn't changed.
+ {
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+}
diff --git a/third_party/rust/rkv/tests/env-lmdb.rs b/third_party/rust/rkv/tests/env-lmdb.rs
new file mode 100644
index 0000000000..e3aca394ab
--- /dev/null
+++ b/third_party/rust/rkv/tests/env-lmdb.rs
@@ -0,0 +1,1647 @@
+// Copyright 2018-2019 Mozilla
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+// this file except in compliance with the License. You may obtain a copy of the
+// License at http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software distributed
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations under the License.
+
+// TODO: change this back to `clippy::cognitive_complexity` when Clippy stable
+// deprecates `clippy::cyclomatic_complexity`.
+#![allow(clippy::complexity)]
+
+use std::{
+ fs,
+ path::Path,
+ str,
+ sync::{Arc, RwLock},
+ thread,
+};
+
+use byteorder::{ByteOrder, LittleEndian};
+use tempfile::Builder;
+
+use rkv::{
+ backend::{
+ BackendEnvironmentBuilder, BackendInfo, BackendStat, Lmdb, LmdbDatabase, LmdbEnvironment,
+ LmdbRwTransaction,
+ },
+ EnvironmentFlags, Rkv, SingleStore, StoreError, StoreOptions, Value, Writer,
+};
+
+fn check_rkv(k: &Rkv<LmdbEnvironment>) {
+ let _ = k
+ .open_single(None, StoreOptions::create())
+ .expect("created default");
+
+ let s = k.open_single("s", StoreOptions::create()).expect("opened");
+ let reader = k.read().expect("reader");
+
+ let result = s.get(&reader, "foo");
+ assert_eq!(None, result.expect("success but no value"));
+}
+
+// The default size is 1MB.
+const DEFAULT_SIZE: usize = 1024 * 1024;
+
+/// We can't open a directory that doesn't exist.
+#[test]
+fn test_open_fails() {
+ let root = Builder::new()
+ .prefix("test_open_fails")
+ .tempdir()
+ .expect("tempdir");
+ assert!(root.path().exists());
+
+ let nope = root.path().join("nope/");
+ assert!(!nope.exists());
+
+ let pb = nope.to_path_buf();
+ match Rkv::new::<Lmdb>(nope.as_path()).err() {
+ Some(StoreError::UnsuitableEnvironmentPath(p)) => {
+ assert_eq!(pb, p);
+ }
+ _ => panic!("expected error"),
+ };
+}
+
+#[test]
+fn test_open() {
+ let root = Builder::new()
+ .prefix("test_open")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ check_rkv(&k);
+}
+
+#[test]
+fn test_open_from_builder() {
+ let root = Builder::new()
+ .prefix("test_open_from_builder")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let mut builder = Rkv::environment_builder::<Lmdb>();
+ builder.set_max_dbs(2);
+
+ let k = Rkv::from_builder(root.path(), builder).expect("rkv");
+ check_rkv(&k);
+}
+
+#[test]
+fn test_open_from_builder_with_no_subdir_1() {
+ let root = Builder::new()
+ .prefix("test_open_from_builder")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ {
+ let mut builder = Rkv::environment_builder::<Lmdb>();
+ builder.set_max_dbs(2);
+
+ let k = Rkv::from_builder(root.path(), builder).expect("rkv");
+ check_rkv(&k);
+ }
+ {
+ let mut builder = Rkv::environment_builder::<Lmdb>();
+ builder.set_flags(EnvironmentFlags::NO_SUB_DIR);
+ builder.set_max_dbs(2);
+
+ let mut datamdb = root.path().to_path_buf();
+ datamdb.push("data.mdb");
+
+ let k = Rkv::from_builder(&datamdb, builder).expect("rkv");
+ check_rkv(&k);
+ }
+}
+
+#[test]
+#[should_panic(expected = "rkv: UnsuitableEnvironmentPath")]
+fn test_open_from_builder_with_no_subdir_2() {
+ let root = Builder::new()
+ .prefix("test_open_from_builder")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ {
+ let mut builder = Rkv::environment_builder::<Lmdb>();
+ builder.set_max_dbs(2);
+
+ let k = Rkv::from_builder(root.path(), builder).expect("rkv");
+ check_rkv(&k);
+ }
+ {
+ let mut builder = Rkv::environment_builder::<Lmdb>();
+ builder.set_flags(EnvironmentFlags::NO_SUB_DIR);
+ builder.set_max_dbs(2);
+
+ let mut datamdb = root.path().to_path_buf();
+ datamdb.push("bogus.mdb");
+
+ let k = Rkv::from_builder(&datamdb, builder).expect("rkv");
+ check_rkv(&k);
+ }
+}
+
+#[test]
+fn test_open_from_builder_with_dir_1() {
+ let root = Builder::new()
+ .prefix("test_open_from_builder")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+
+ let mut builder = Rkv::environment_builder::<Lmdb>();
+ builder.set_max_dbs(2);
+ builder.set_make_dir_if_needed(true);
+
+ let k = Rkv::from_builder(root.path(), builder).expect("rkv");
+ check_rkv(&k);
+}
+
+#[test]
+#[should_panic(expected = "rkv: UnsuitableEnvironmentPath(\"bogus\")")]
+fn test_open_from_builder_with_dir_2() {
+ let root = Path::new("bogus");
+ println!("Root path: {root:?}");
+ assert!(!root.is_dir());
+
+ let mut builder = Rkv::environment_builder::<Lmdb>();
+ builder.set_max_dbs(2);
+
+ let k = Rkv::from_builder(root, builder).expect("rkv");
+ check_rkv(&k);
+}
+
+#[test]
+#[should_panic(expected = "opened: DbsFull")]
+fn test_create_with_capacity_1() {
+ let root = Builder::new()
+ .prefix("test_create_with_capacity")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<Lmdb>(root.path(), 1).expect("rkv");
+ check_rkv(&k);
+
+ // This errors with "opened: DbsFull" because we specified a capacity of one (database),
+ // and check_rkv already opened one (plus the default database, which doesn't count
+ // against the limit).
+ let _zzz = k
+ .open_single("zzz", StoreOptions::create())
+ .expect("opened");
+}
+
+#[test]
+fn test_create_with_capacity_2() {
+ let root = Builder::new()
+ .prefix("test_create_with_capacity")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<Lmdb>(root.path(), 1).expect("rkv");
+ check_rkv(&k);
+
+ // This doesn't error with "opened: DbsFull" with because even though we specified a
+ // capacity of one (database), and check_rkv already opened one, the default database
+ // doesn't count against the limit.
+ let _zzz = k.open_single(None, StoreOptions::create()).expect("opened");
+}
+
+#[test]
+#[should_panic(expected = "opened: DbsFull")]
+fn test_open_with_capacity_1() {
+ let root = Builder::new()
+ .prefix("test_open_with_capacity")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<Lmdb>(root.path(), 1).expect("rkv");
+ check_rkv(&k);
+
+ let _zzz = k
+ .open_single("zzz", StoreOptions::default())
+ .expect("opened");
+}
+
+#[test]
+fn test_open_with_capacity_2() {
+ let root = Builder::new()
+ .prefix("test_open_with_capacity")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<Lmdb>(root.path(), 1).expect("rkv");
+ check_rkv(&k);
+
+ let _zzz = k
+ .open_single(None, StoreOptions::default())
+ .expect("opened");
+}
+
+#[test]
+fn test_list_dbs_1() {
+ let root = Builder::new()
+ .prefix("test_list_dbs")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<Lmdb>(root.path(), 1).expect("rkv");
+ check_rkv(&k);
+
+ let dbs = k.get_dbs().unwrap();
+ assert_eq!(dbs, vec![Some("s".to_owned())]);
+}
+
+#[test]
+fn test_list_dbs_2() {
+ let root = Builder::new()
+ .prefix("test_list_dbs")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<Lmdb>(root.path(), 2).expect("rkv");
+ check_rkv(&k);
+
+ let _ = k
+ .open_single("zzz", StoreOptions::create())
+ .expect("opened");
+
+ let dbs = k.get_dbs().unwrap();
+ assert_eq!(dbs, vec![Some("s".to_owned()), Some("zzz".to_owned())]);
+}
+
+#[test]
+fn test_list_dbs_3() {
+ let root = Builder::new()
+ .prefix("test_list_dbs")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<Lmdb>(root.path(), 0).expect("rkv");
+
+ let _ = k.open_single(None, StoreOptions::create()).expect("opened");
+
+ let dbs = k.get_dbs().unwrap();
+ assert_eq!(dbs, vec![None]);
+}
+
+fn get_larger_than_default_map_size_value() -> usize {
+ // The LMDB C library and lmdb Rust crate docs for setting the map size
+ // <http://www.lmdb.tech/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5>
+ // <https://docs.rs/lmdb/0.8.0/lmdb/struct.EnvironmentBuilder.html#method.set_map_size>
+ // both say that the default map size is 10,485,760 bytes, i.e. 10MiB.
+ //
+ // But the DEFAULT_MAPSIZE define in the LMDB code
+ // https://github.com/LMDB/lmdb/blob/26c7df88e44e31623d0802a564f24781acdefde3/libraries/liblmdb/mdb.c#L729
+ // sets the default map size to 1,048,576 bytes, i.e. 1MiB.
+ //
+ DEFAULT_SIZE + 1 /* 1,048,576 + 1 bytes, i.e. 1MiB + 1 byte */
+}
+
+#[test]
+#[should_panic(expected = "wrote: MapFull")]
+fn test_exceed_map_size() {
+ let root = Builder::new()
+ .prefix("test_exceed_map_size")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k
+ .open_single("test", StoreOptions::create())
+ .expect("opened");
+
+ // Writing a large enough value should cause LMDB to fail on MapFull.
+ // We write a string that is larger than the default map size.
+ let val = "x".repeat(get_larger_than_default_map_size_value());
+ let mut writer = k.write().expect("writer");
+ sk.put(&mut writer, "foo", &Value::Str(&val))
+ .expect("wrote");
+}
+
+#[test]
+#[should_panic(expected = "wrote: KeyValuePairBadSize")]
+fn test_exceed_key_size_limit() {
+ let root = Builder::new()
+ .prefix("test_exceed_key_size_limit")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k
+ .open_single("test", StoreOptions::create())
+ .expect("opened");
+
+ let key = "k".repeat(512);
+ let mut writer = k.write().expect("writer");
+ sk.put(&mut writer, key, &Value::Str("val")).expect("wrote");
+}
+
+#[test]
+fn test_increase_map_size() {
+ let root = Builder::new()
+ .prefix("test_open_with_map_size")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let mut builder = Rkv::environment_builder::<Lmdb>();
+ // Set the map size to the size of the value we'll store in it + 100KiB,
+ // which ensures that there's enough space for the value and metadata.
+ builder.set_map_size(
+ get_larger_than_default_map_size_value() + 100 * 1024, /* 100KiB */
+ );
+ builder.set_max_dbs(2);
+ let k = Rkv::from_builder(root.path(), builder).unwrap();
+ let sk = k
+ .open_single("test", StoreOptions::create())
+ .expect("opened");
+ let val = "x".repeat(get_larger_than_default_map_size_value());
+
+ let mut writer = k.write().expect("writer");
+ sk.put(&mut writer, "foo", &Value::Str(&val))
+ .expect("wrote");
+ writer.commit().expect("committed");
+
+ let reader = k.read().unwrap();
+ assert_eq!(
+ sk.get(&reader, "foo").expect("read"),
+ Some(Value::Str(&val))
+ );
+}
+
+#[test]
+fn test_round_trip_and_transactions() {
+ let root = Builder::new()
+ .prefix("test_round_trip_and_transactions")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = 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, "noo", &Value::F64(1234.0.into()))
+ .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");
+ assert_eq!(
+ sk.get(&writer, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&writer, "noo").expect("read"),
+ Some(Value::F64(1234.0.into()))
+ );
+ assert_eq!(
+ sk.get(&writer, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&writer, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+
+ // Isolation. Reads won't return values.
+ 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);
+ }
+
+ // Dropped: tx rollback. Reads will still return nothing.
+
+ {
+ 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);
+ }
+
+ {
+ 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");
+ assert_eq!(
+ sk.get(&writer, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&writer, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&writer, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+
+ writer.commit().expect("committed");
+ }
+
+ // Committed. Reads will succeed.
+
+ {
+ let r = k.read().unwrap();
+ assert_eq!(sk.get(&r, "foo").expect("read"), Some(Value::I64(1234)));
+ assert_eq!(sk.get(&r, "bar").expect("read"), Some(Value::Bool(true)));
+ assert_eq!(
+ sk.get(&r, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+
+ {
+ let mut writer = k.write().expect("writer");
+ sk.delete(&mut writer, "foo").expect("deleted");
+ sk.delete(&mut writer, "bar").expect("deleted");
+ sk.delete(&mut writer, "baz").expect("deleted");
+ assert_eq!(sk.get(&writer, "foo").expect("read"), None);
+ assert_eq!(sk.get(&writer, "bar").expect("read"), None);
+ assert_eq!(sk.get(&writer, "baz").expect("read"), None);
+
+ // Isolation. Reads still return values.
+ let r = k.read().unwrap();
+ assert_eq!(sk.get(&r, "foo").expect("read"), Some(Value::I64(1234)));
+ assert_eq!(sk.get(&r, "bar").expect("read"), Some(Value::Bool(true)));
+ assert_eq!(
+ sk.get(&r, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+
+ // Dropped: tx rollback. Reads will still return values.
+
+ {
+ let r = k.read().unwrap();
+ assert_eq!(sk.get(&r, "foo").expect("read"), Some(Value::I64(1234)));
+ assert_eq!(sk.get(&r, "bar").expect("read"), Some(Value::Bool(true)));
+ assert_eq!(
+ sk.get(&r, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+
+ {
+ let mut writer = k.write().expect("writer");
+ sk.delete(&mut writer, "foo").expect("deleted");
+ sk.delete(&mut writer, "bar").expect("deleted");
+ sk.delete(&mut writer, "baz").expect("deleted");
+ assert_eq!(sk.get(&writer, "foo").expect("read"), None);
+ assert_eq!(sk.get(&writer, "bar").expect("read"), None);
+ assert_eq!(sk.get(&writer, "baz").expect("read"), None);
+
+ writer.commit().expect("committed");
+ }
+
+ // Committed. Reads will succeed but return None to indicate a missing value.
+
+ {
+ 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::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = 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]
+#[should_panic(expected = "KeyValuePairNotFound")]
+fn test_single_store_delete_nonexistent() {
+ let root = Builder::new()
+ .prefix("test_single_store_delete_nonexistent")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ let mut writer = k.write().expect("writer");
+ sk.delete(&mut writer, "bogus").unwrap();
+}
+
+#[test]
+#[cfg(feature = "db-dup-sort")]
+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::<Lmdb>(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();
+ 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().unwrap();
+
+ let writer = k.write().unwrap();
+ {
+ let mut iter = multistore.get(&writer, "str1").unwrap();
+ let (id, val) = iter.next().unwrap().unwrap();
+ assert_eq!((id, val), (&b"str1"[..], Value::Str("str1 bar")));
+ let (id, val) = iter.next().unwrap().unwrap();
+ assert_eq!((id, val), (&b"str1"[..], Value::Str("str1 foo")));
+ }
+ writer.commit().unwrap();
+
+ let mut writer = k.write().unwrap();
+ multistore
+ .delete(&mut writer, "str1", &Value::Str("str1 foo"))
+ .unwrap();
+ assert_eq!(
+ multistore.get_first(&writer, "str1").unwrap(),
+ Some(Value::Str("str1 bar"))
+ );
+ multistore
+ .delete(&mut writer, "str2", &Value::Str("str2 bar"))
+ .unwrap();
+ 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]
+#[cfg(feature = "db-dup-sort")]
+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::<Lmdb>(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::<Lmdb>(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");
+
+ // Open the same store for read, note that the write transaction is still in progress,
+ // it should not block the reader though.
+ let sk_readonly = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+ writer.commit().expect("commit");
+
+ // Now the write transaction is committed, any followed reads should see its change.
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk_readonly.get(&reader, "foo").expect("read"),
+ Some(Value::Str("bar"))
+ );
+}
+
+#[test]
+#[should_panic(expected = "open a missing store")]
+fn test_open_a_missing_store() {
+ let root = Builder::new()
+ .prefix("test_open_a_missing_store")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let _sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("open a missing store");
+}
+
+#[test]
+#[should_panic(expected = "new failed: FileInvalid")]
+fn test_open_a_broken_store() {
+ let root = Builder::new()
+ .prefix("test_open_a_missing_store")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let dbfile = root.path().join("data.mdb");
+ fs::write(dbfile, "bogus").expect("dbfile created");
+
+ let _ = Rkv::new::<Lmdb>(root.path()).expect("new failed");
+}
+
+#[test]
+fn test_open_fail_with_badrslot() {
+ let root = Builder::new()
+ .prefix("test_open_fail_with_badrslot")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(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 = k.open_single("sk", StoreOptions::default());
+ match store {
+ 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");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ // Test reading a number, modifying it, and then writing it back.
+ // We have to be done with the Value::I64 before calling Writer::put,
+ // as the Value::I64 borrows an immutable reference to the Writer.
+ // So we extract and copy its primitive value.
+
+ fn get_existing_foo(
+ store: SingleStore<LmdbDatabase>,
+ writer: &Writer<LmdbRwTransaction>,
+ ) -> Option<i64> {
+ match store.get(writer, "foo").expect("read") {
+ Some(Value::I64(val)) => Some(val),
+ _ => None,
+ }
+ }
+
+ let mut writer = k.write().expect("writer");
+ let mut existing = get_existing_foo(sk, &writer).unwrap_or(99);
+ existing += 1;
+ sk.put(&mut writer, "foo", &Value::I64(existing))
+ .expect("success");
+
+ let updated = get_existing_foo(sk, &writer).unwrap_or(99);
+ assert_eq!(updated, 100);
+ writer.commit().expect("commit");
+}
+
+#[test]
+fn test_read_before_write_str() {
+ let root = Builder::new()
+ .prefix("test_read_before_write_str")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ // Test reading a string, modifying it, and then writing it back.
+ // We have to be done with the Value::Str before calling Writer::put,
+ // as the Value::Str (and its underlying &str) borrows an immutable
+ // reference to the Writer. So we copy it to a String.
+
+ fn get_existing_foo(
+ store: SingleStore<LmdbDatabase>,
+ writer: &Writer<LmdbRwTransaction>,
+ ) -> Option<String> {
+ match store.get(writer, "foo").expect("read") {
+ Some(Value::Str(val)) => Some(val.to_string()),
+ _ => None,
+ }
+ }
+
+ let mut writer = k.write().expect("writer");
+ let mut existing = get_existing_foo(sk, &writer).unwrap_or_default();
+ existing.push('…');
+ sk.put(&mut writer, "foo", &Value::Str(&existing))
+ .expect("write");
+
+ let updated = get_existing_foo(sk, &writer).unwrap_or_default();
+ assert_eq!(updated, "…");
+ writer.commit().expect("commit");
+}
+
+#[test]
+fn test_concurrent_read_transactions_prohibited() {
+ let root = Builder::new()
+ .prefix("test_concurrent_reads_prohibited")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let _first = k.read().expect("reader");
+ let second = k.read();
+
+ match second {
+ Err(StoreError::ReadTransactionAlreadyExists(t)) => {
+ println!("Thread was {t:?}");
+ }
+ Err(e) => {
+ println!("Got error {e:?}");
+ }
+ _ => {
+ panic!("Expected error.");
+ }
+ }
+}
+
+#[test]
+fn test_isolation() {
+ let root = Builder::new()
+ .prefix("test_isolation")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let s = k.open_single("s", StoreOptions::create()).expect("opened");
+
+ // Add one field.
+ {
+ let mut writer = k.write().expect("writer");
+ s.put(&mut writer, "foo", &Value::I64(1234)).expect("wrote");
+ writer.commit().expect("committed");
+ }
+
+ {
+ let reader = k.read().unwrap();
+ assert_eq!(s.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
+ }
+
+ // Establish a long-lived reader that outlasts a writer.
+ let reader = k.read().expect("reader");
+ assert_eq!(s.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
+
+ // Start a write transaction.
+ let mut writer = k.write().expect("writer");
+ s.put(&mut writer, "foo", &Value::I64(999)).expect("wrote");
+
+ // The reader and writer are isolated.
+ assert_eq!(s.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
+ assert_eq!(s.get(&writer, "foo").expect("read"), Some(Value::I64(999)));
+
+ // If we commit the writer, we still have isolation.
+ writer.commit().expect("committed");
+ assert_eq!(s.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
+
+ // A new reader sees the committed value. Note that LMDB doesn't allow two
+ // read transactions to exist in the same thread, so we abort the previous one.
+ reader.abort();
+ let reader = k.read().expect("reader");
+ assert_eq!(s.get(&reader, "foo").expect("read"), Some(Value::I64(999)));
+}
+
+#[test]
+fn test_blob() {
+ let root = Builder::new()
+ .prefix("test_round_trip_blob")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ let mut writer = k.write().expect("writer");
+ assert_eq!(sk.get(&writer, "foo").expect("read"), None);
+ sk.put(&mut writer, "foo", &Value::Blob(&[1, 2, 3, 4]))
+ .expect("wrote");
+ assert_eq!(
+ sk.get(&writer, "foo").expect("read"),
+ Some(Value::Blob(&[1, 2, 3, 4]))
+ );
+
+ fn u16_to_u8(src: &[u16]) -> Vec<u8> {
+ let mut dst = vec![0; 2 * src.len()];
+ LittleEndian::write_u16_into(src, &mut dst);
+ dst
+ }
+
+ fn u8_to_u16(src: &[u8]) -> Vec<u16> {
+ let mut dst = vec![0; src.len() / 2];
+ LittleEndian::read_u16_into(src, &mut dst);
+ dst
+ }
+
+ // When storing UTF-16 strings as blobs, we'll need to convert
+ // their [u16] backing storage to [u8]. Test that converting, writing,
+ // reading, and converting back works as expected.
+ let u16_array = [1000, 10000, 54321, 65535];
+ assert_eq!(sk.get(&writer, "bar").expect("read"), None);
+ sk.put(&mut writer, "bar", &Value::Blob(&u16_to_u8(&u16_array)))
+ .expect("wrote");
+ let u8_array = match sk.get(&writer, "bar").expect("read") {
+ Some(Value::Blob(val)) => val,
+ _ => &[],
+ };
+ assert_eq!(u8_to_u16(u8_array), u16_array);
+}
+
+#[test]
+fn test_sync() {
+ let root = Builder::new()
+ .prefix("test_sync")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let mut builder = Rkv::environment_builder::<Lmdb>();
+ builder.set_max_dbs(1);
+ builder.set_flags(EnvironmentFlags::NO_SYNC);
+ {
+ let k = Rkv::from_builder(root.path(), builder).expect("new succeeded");
+ let sk = 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");
+ writer.commit().expect("committed");
+ k.sync(true).expect("synced");
+ }
+ }
+ let k = Rkv::from_builder(root.path(), builder).expect("new succeeded");
+ let sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+}
+
+#[test]
+#[cfg(feature = "db-int-key")]
+fn test_stat() {
+ let root = Builder::new()
+ .prefix("test_stat")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ for i in 0..5 {
+ let sk = k
+ .open_integer(&format!("sk{i}")[..], StoreOptions::create())
+ .expect("opened");
+ {
+ let mut writer = k.write().expect("writer");
+ sk.put(&mut writer, i, &Value::I64(i64::from(i)))
+ .expect("wrote");
+ writer.commit().expect("committed");
+ }
+ }
+ assert_eq!(k.stat().expect("stat").depth(), 1);
+ assert_eq!(k.stat().expect("stat").entries(), 5);
+ assert_eq!(k.stat().expect("stat").branch_pages(), 0);
+ assert_eq!(k.stat().expect("stat").leaf_pages(), 1);
+}
+
+#[test]
+fn test_info() {
+ let root = Builder::new()
+ .prefix("test_info")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ 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("wrote");
+ writer.commit().expect("commited");
+
+ let info = k.info().expect("info");
+
+ // The default size is 1MB.
+ assert_eq!(info.map_size(), DEFAULT_SIZE);
+ // Should greater than 0 after the write txn.
+ assert!(info.last_pgno() > 0);
+ // A txn to open_single + a txn to write.
+ assert_eq!(info.last_txnid(), 2);
+ // The default max readers is 126.
+ assert_eq!(info.max_readers(), 126);
+ assert_eq!(info.num_readers(), 0);
+
+ // A new reader should increment the reader counter.
+ let _reader = k.read().expect("reader");
+ let info = k.info().expect("info");
+
+ assert_eq!(info.num_readers(), 1);
+}
+
+#[test]
+fn test_load_ratio() {
+ let root = Builder::new()
+ .prefix("test_load_ratio")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ 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("wrote");
+ writer.commit().expect("commited");
+ let ratio = k.load_ratio().expect("ratio").unwrap();
+ assert!(ratio > 0.0_f32 && ratio < 1.0_f32);
+
+ // Put data to database should increase the load ratio.
+ let mut writer = k.write().expect("writer");
+ sk.put(
+ &mut writer,
+ "bar",
+ &Value::Str(&"more-than-4KB".repeat(1000)),
+ )
+ .expect("wrote");
+ writer.commit().expect("commited");
+ let new_ratio = k.load_ratio().expect("ratio").unwrap();
+ assert!(new_ratio > ratio);
+
+ // Clear the database so that all the used pages should go to freelist, hence the ratio
+ // should decrease.
+ let mut writer = k.write().expect("writer");
+ sk.clear(&mut writer).expect("clear");
+ writer.commit().expect("commited");
+ let after_clear_ratio = k.load_ratio().expect("ratio").unwrap();
+ assert!(after_clear_ratio < new_ratio);
+}
+
+#[test]
+fn test_set_map_size() {
+ let root = Builder::new()
+ .prefix("test_size_map_size")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ assert_eq!(k.info().expect("info").map_size(), DEFAULT_SIZE);
+
+ k.set_map_size(2 * DEFAULT_SIZE).expect("resized");
+
+ // Should be able to write.
+ let mut writer = k.write().expect("writer");
+ sk.put(&mut writer, "foo", &Value::Str("bar"))
+ .expect("wrote");
+ writer.commit().expect("commited");
+
+ assert_eq!(k.info().expect("info").map_size(), 2 * DEFAULT_SIZE);
+}
+
+#[test]
+fn test_iter() {
+ let root = Builder::new()
+ .prefix("test_iter")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ // An iterator over an empty store returns no values.
+ {
+ let reader = k.read().unwrap();
+ let mut iter = sk.iter_start(&reader).unwrap();
+ assert!(iter.next().is_none());
+ }
+
+ let mut writer = k.write().expect("writer");
+ sk.put(&mut writer, "foo", &Value::I64(1234))
+ .expect("wrote");
+ sk.put(&mut writer, "noo", &Value::F64(1234.0.into()))
+ .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");
+ sk.put(&mut writer, "héllò, töűrîst", &Value::Str("Emil.RuleZ!"))
+ .expect("wrote");
+ sk.put(&mut writer, "你好,遊客", &Value::Str("米克規則"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+
+ let reader = k.read().unwrap();
+
+ // Reader.iter() returns (key, value) tuples ordered by key.
+ let mut iter = sk.iter_start(&reader).unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "bar");
+ assert_eq!(val, Value::Bool(true));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "baz");
+ assert_eq!(val, Value::Str("héllo, yöu"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "foo");
+ assert_eq!(val, Value::I64(1234));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "héllò, töűrîst");
+ assert_eq!(val, Value::Str("Emil.RuleZ!"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterators don't loop. Once one returns None, additional calls
+ // to its next() method will always return None.
+ assert!(iter.next().is_none());
+
+ // Reader.iter_from() begins iteration at the first key equal to
+ // or greater than the given key.
+ let mut iter = sk.iter_from(&reader, "moo").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Reader.iter_from() works as expected when the given key is a prefix
+ // of a key in the store.
+ let mut iter = sk.iter_from(&reader, "no").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+}
+
+#[test]
+fn test_iter_from_key_greater_than_existing() {
+ let root = Builder::new()
+ .prefix("test_iter_from_key_greater_than_existing")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let sk = 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, "noo", &Value::F64(1234.0.into()))
+ .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 reader = k.read().unwrap();
+ let mut iter = sk.iter_from(&reader, "nuu").unwrap();
+ assert!(iter.next().is_none());
+}
+
+#[test]
+fn test_multiple_store_read_write() {
+ let root = Builder::new()
+ .prefix("test_multiple_store_read_write")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let s1 = k
+ .open_single("store_1", StoreOptions::create())
+ .expect("opened");
+ let s2 = k
+ .open_single("store_2", StoreOptions::create())
+ .expect("opened");
+ let s3 = k
+ .open_single("store_3", StoreOptions::create())
+ .expect("opened");
+
+ let mut writer = k.write().expect("writer");
+ s1.put(&mut writer, "foo", &Value::Str("bar"))
+ .expect("wrote");
+ s2.put(&mut writer, "foo", &Value::I64(123)).expect("wrote");
+ s3.put(&mut writer, "foo", &Value::Bool(true))
+ .expect("wrote");
+
+ assert_eq!(
+ s1.get(&writer, "foo").expect("read"),
+ Some(Value::Str("bar"))
+ );
+ assert_eq!(s2.get(&writer, "foo").expect("read"), Some(Value::I64(123)));
+ assert_eq!(
+ s3.get(&writer, "foo").expect("read"),
+ Some(Value::Bool(true))
+ );
+
+ writer.commit().expect("committed");
+
+ let reader = k.read().expect("unbound_reader");
+ assert_eq!(
+ s1.get(&reader, "foo").expect("read"),
+ Some(Value::Str("bar"))
+ );
+ assert_eq!(s2.get(&reader, "foo").expect("read"), Some(Value::I64(123)));
+ assert_eq!(
+ s3.get(&reader, "foo").expect("read"),
+ Some(Value::Bool(true))
+ );
+ reader.abort();
+
+ // test delete across multiple stores
+ let mut writer = k.write().expect("writer");
+ s1.delete(&mut writer, "foo").expect("deleted");
+ s2.delete(&mut writer, "foo").expect("deleted");
+ s3.delete(&mut writer, "foo").expect("deleted");
+ writer.commit().expect("committed");
+
+ let reader = k.read().expect("reader");
+ assert_eq!(s1.get(&reader, "key").expect("value"), None);
+ assert_eq!(s2.get(&reader, "key").expect("value"), None);
+ assert_eq!(s3.get(&reader, "key").expect("value"), None);
+}
+
+#[test]
+fn test_multiple_store_iter() {
+ let root = Builder::new()
+ .prefix("test_multiple_store_iter")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let s1 = k
+ .open_single("store_1", StoreOptions::create())
+ .expect("opened");
+ let s2 = k
+ .open_single("store_2", StoreOptions::create())
+ .expect("opened");
+
+ let mut writer = k.write().expect("writer");
+ // Write to "s1"
+ s1.put(&mut writer, "foo", &Value::I64(1234))
+ .expect("wrote");
+ s1.put(&mut writer, "noo", &Value::F64(1234.0.into()))
+ .expect("wrote");
+ s1.put(&mut writer, "bar", &Value::Bool(true))
+ .expect("wrote");
+ s1.put(&mut writer, "baz", &Value::Str("héllo, yöu"))
+ .expect("wrote");
+ s1.put(&mut writer, "héllò, töűrîst", &Value::Str("Emil.RuleZ!"))
+ .expect("wrote");
+ s1.put(&mut writer, "你好,遊客", &Value::Str("米克規則"))
+ .expect("wrote");
+ // &mut writer to "s2"
+ s2.put(&mut writer, "foo", &Value::I64(1234))
+ .expect("wrote");
+ s2.put(&mut writer, "noo", &Value::F64(1234.0.into()))
+ .expect("wrote");
+ s2.put(&mut writer, "bar", &Value::Bool(true))
+ .expect("wrote");
+ s2.put(&mut writer, "baz", &Value::Str("héllo, yöu"))
+ .expect("wrote");
+ s2.put(&mut writer, "héllò, töűrîst", &Value::Str("Emil.RuleZ!"))
+ .expect("wrote");
+ s2.put(&mut writer, "你好,遊客", &Value::Str("米克規則"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+
+ let reader = k.read().unwrap();
+
+ // Iterate through the whole store in "s1"
+ let mut iter = s1.iter_start(&reader).unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "bar");
+ assert_eq!(val, Value::Bool(true));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "baz");
+ assert_eq!(val, Value::Str("héllo, yöu"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "foo");
+ assert_eq!(val, Value::I64(1234));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "héllò, töűrîst");
+ assert_eq!(val, Value::Str("Emil.RuleZ!"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterate through the whole store in "s2"
+ let mut iter = s2.iter_start(&reader).unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "bar");
+ assert_eq!(val, Value::Bool(true));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "baz");
+ assert_eq!(val, Value::Str("héllo, yöu"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "foo");
+ assert_eq!(val, Value::I64(1234));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "héllò, töűrîst");
+ assert_eq!(val, Value::Str("Emil.RuleZ!"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterate from a given key in "s1"
+ let mut iter = s1.iter_from(&reader, "moo").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterate from a given key in "s2"
+ let mut iter = s2.iter_from(&reader, "moo").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterate from a given prefix in "s1"
+ let mut iter = s1.iter_from(&reader, "no").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterate from a given prefix in "s2"
+ let mut iter = s2.iter_from(&reader, "no").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+}
+
+#[test]
+fn test_store_multiple_thread() {
+ let root = Builder::new()
+ .prefix("test_multiple_thread")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let rkv_arc = Arc::new(RwLock::new(
+ Rkv::new::<Lmdb>(root.path()).expect("new succeeded"),
+ ));
+ let store = rkv_arc
+ .read()
+ .unwrap()
+ .open_single("test", StoreOptions::create())
+ .expect("opened");
+
+ let num_threads = 10;
+ let mut write_handles = Vec::with_capacity(num_threads as usize);
+ let mut read_handles = Vec::with_capacity(num_threads as usize);
+
+ // Note that this isn't intended to demonstrate a good use of threads.
+ // For this shape of data, it would be more performant to write/read
+ // all values using one transaction in a single thread. The point here
+ // is just to confirm that a store can be shared by multiple threads.
+
+ // For each KV pair, spawn a thread that writes it to the store.
+ for i in 0..num_threads {
+ let rkv_arc = rkv_arc.clone();
+ write_handles.push(thread::spawn(move || {
+ let rkv = rkv_arc.write().expect("rkv");
+ let mut writer = rkv.write().expect("writer");
+ store
+ .put(&mut writer, i.to_string(), &Value::U64(i))
+ .expect("written");
+ writer.commit().unwrap();
+ }));
+ }
+ for handle in write_handles {
+ handle.join().expect("joined");
+ }
+
+ // For each KV pair, spawn a thread that reads it from the store
+ // and returns its value.
+ for i in 0..num_threads {
+ let rkv_arc = rkv_arc.clone();
+ read_handles.push(thread::spawn(move || {
+ let rkv = rkv_arc.read().expect("rkv");
+ let reader = rkv.read().expect("reader");
+ let value = match store.get(&reader, i.to_string()) {
+ Ok(Some(Value::U64(value))) => value,
+ Ok(Some(_)) => panic!("value type unexpected"),
+ Ok(None) => panic!("value not found"),
+ Err(err) => panic!("{}", err),
+ };
+ assert_eq!(value, i);
+ value
+ }));
+ }
+
+ // Sum the values returned from the threads and confirm that they're
+ // equal to the sum of values written to the threads.
+ let thread_sum: u64 = read_handles
+ .into_iter()
+ .map(|handle| handle.join().expect("value"))
+ .sum();
+ assert_eq!(thread_sum, (0..num_threads).sum());
+}
+
+#[test]
+fn test_use_value_as_key() {
+ let root = Builder::new()
+ .prefix("test_use_value_as_key")
+ .tempdir()
+ .expect("tempdir");
+ let rkv = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let store = rkv
+ .open_single("store", StoreOptions::create())
+ .expect("opened");
+
+ {
+ let mut writer = rkv.write().expect("writer");
+ store
+ .put(&mut writer, "foo", &Value::Str("bar"))
+ .expect("wrote");
+ store
+ .put(&mut writer, "bar", &Value::Str("baz"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+ }
+
+ // It's possible to retrieve a value with a Reader and then use it
+ // as a key with a Writer.
+ {
+ let reader = &rkv.read().unwrap();
+ if let Some(Value::Str(key)) = store.get(reader, "foo").expect("read") {
+ let mut writer = rkv.write().expect("writer");
+ store.delete(&mut writer, key).expect("deleted");
+ writer.commit().expect("committed");
+ }
+ }
+
+ {
+ let mut writer = rkv.write().expect("writer");
+ store
+ .put(&mut writer, "bar", &Value::Str("baz"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+ }
+
+ // You can also retrieve a Value with a Writer and then use it as a key
+ // with the same Writer if you copy the value to an owned type
+ // so the Writer isn't still being borrowed by the retrieved value
+ // when you try to borrow the Writer again to modify that value.
+ {
+ let mut writer = rkv.write().expect("writer");
+ if let Some(Value::Str(value)) = store.get(&writer, "foo").expect("read") {
+ let key = value.to_owned();
+ store.delete(&mut writer, key).expect("deleted");
+ writer.commit().expect("committed");
+ }
+ }
+
+ {
+ let name1 = rkv
+ .open_single("name1", StoreOptions::create())
+ .expect("opened");
+ let name2 = rkv
+ .open_single("name2", StoreOptions::create())
+ .expect("opened");
+ let mut writer = rkv.write().expect("writer");
+ name1
+ .put(&mut writer, "key1", &Value::Str("bar"))
+ .expect("wrote");
+ name1
+ .put(&mut writer, "bar", &Value::Str("baz"))
+ .expect("wrote");
+ name2
+ .put(&mut writer, "key2", &Value::Str("bar"))
+ .expect("wrote");
+ name2
+ .put(&mut writer, "bar", &Value::Str("baz"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+ }
+
+ // You can also iterate (store, key) pairs to retrieve foreign keys,
+ // then iterate those foreign keys to modify/delete them.
+ //
+ // You need to open the stores in advance, since opening a store
+ // uses a write transaction internally, so opening them while a writer
+ // is extant will hang.
+ //
+ // And you need to copy the values to an owned type so the Writer isn't
+ // still being borrowed by a retrieved value when you try to borrow
+ // the Writer again to modify another value.
+ let fields = vec![
+ (
+ rkv.open_single("name1", StoreOptions::create())
+ .expect("opened"),
+ "key1",
+ ),
+ (
+ rkv.open_single("name2", StoreOptions::create())
+ .expect("opened"),
+ "key2",
+ ),
+ ];
+ {
+ let mut foreignkeys = Vec::new();
+ let mut writer = rkv.write().expect("writer");
+ for (store, key) in fields.iter() {
+ if let Some(Value::Str(value)) = store.get(&writer, key).expect("read") {
+ foreignkeys.push((store, value.to_owned()));
+ }
+ }
+ for (store, key) in foreignkeys.iter() {
+ store.delete(&mut writer, key).expect("deleted");
+ }
+ writer.commit().expect("committed");
+ }
+}
diff --git a/third_party/rust/rkv/tests/env-migration.rs b/third_party/rust/rkv/tests/env-migration.rs
new file mode 100644
index 0000000000..7026c25de8
--- /dev/null
+++ b/third_party/rust/rkv/tests/env-migration.rs
@@ -0,0 +1,607 @@
+// Copyright 2018-2019 Mozilla
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+// this file except in compliance with the License. You may obtain a copy of the
+// License at http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software distributed
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations under the License.
+
+use std::{fs, path::Path};
+
+use tempfile::Builder;
+
+use rkv::{
+ backend::{Lmdb, LmdbEnvironment, SafeMode, SafeModeEnvironment},
+ Manager, Migrator, Rkv, StoreOptions, Value,
+};
+
+macro_rules! populate_store {
+ ($env:expr) => {
+ let store = $env
+ .open_single("store", StoreOptions::create())
+ .expect("opened");
+ let mut writer = $env.write().expect("writer");
+ store
+ .put(&mut writer, "foo", &Value::I64(1234))
+ .expect("wrote");
+ store
+ .put(&mut writer, "bar", &Value::Bool(true))
+ .expect("wrote");
+ store
+ .put(&mut writer, "baz", &Value::Str("héllo, yöu"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+ };
+}
+
+#[test]
+fn test_open_migrator_lmdb_to_safe() {
+ let root = Builder::new()
+ .prefix("test_open_migrator_lmdb_to_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ // Populate source environment and persist to disk.
+ {
+ let src_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ populate_store!(&src_env);
+ src_env.sync(true).expect("synced");
+ }
+ // Check if the files were written to disk.
+ {
+ let mut datamdb = root.path().to_path_buf();
+ let mut lockmdb = root.path().to_path_buf();
+ datamdb.push("data.mdb");
+ lockmdb.push("lock.mdb");
+ assert!(datamdb.exists());
+ assert!(lockmdb.exists());
+ }
+ // Verify that database was written to disk.
+ {
+ let src_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let store = src_env
+ .open_single("store", StoreOptions::default())
+ .expect("opened");
+ let reader = src_env.read().expect("reader");
+ assert_eq!(
+ store.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ store.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ store.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+ // Open and migrate.
+ {
+ let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ Migrator::open_and_migrate_lmdb_to_safe_mode(root.path(), |builder| builder, &dst_env)
+ .expect("migrated");
+ }
+ // Verify that the database was indeed migrated.
+ {
+ let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let store = dst_env
+ .open_single("store", StoreOptions::default())
+ .expect("opened");
+ let reader = dst_env.read().expect("reader");
+ assert_eq!(
+ store.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ store.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ store.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+ // Check if the old files were deleted from disk.
+ {
+ let mut datamdb = root.path().to_path_buf();
+ let mut lockmdb = root.path().to_path_buf();
+ datamdb.push("data.mdb");
+ lockmdb.push("lock.mdb");
+ assert!(!datamdb.exists());
+ assert!(!lockmdb.exists());
+ }
+}
+
+#[test]
+fn test_open_migrator_safe_to_lmdb() {
+ let root = Builder::new()
+ .prefix("test_open_migrator_safe_to_lmdb")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ // Populate source environment and persist to disk.
+ {
+ let src_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ populate_store!(&src_env);
+ src_env.sync(true).expect("synced");
+ }
+ // Check if the files were written to disk.
+ {
+ let mut safebin = root.path().to_path_buf();
+ safebin.push("data.safe.bin");
+ assert!(safebin.exists());
+ }
+ // Verify that database was written to disk.
+ {
+ let src_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let store = src_env
+ .open_single("store", StoreOptions::default())
+ .expect("opened");
+ let reader = src_env.read().expect("reader");
+ assert_eq!(
+ store.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ store.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ store.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+ // Open and migrate.
+ {
+ let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ Migrator::open_and_migrate_safe_mode_to_lmdb(root.path(), |builder| builder, &dst_env)
+ .expect("migrated");
+ }
+ // Verify that the database was indeed migrated.
+ {
+ let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let store = dst_env
+ .open_single("store", StoreOptions::default())
+ .expect("opened");
+ let reader = dst_env.read().expect("reader");
+ assert_eq!(
+ store.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ store.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ store.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+ // Check if the old files were deleted from disk.
+ {
+ let mut safebin = root.path().to_path_buf();
+ safebin.push("data.safe.bin");
+ assert!(!safebin.exists());
+ }
+}
+
+#[test]
+fn test_open_migrator_round_trip() {
+ let root = Builder::new()
+ .prefix("test_open_migrator_lmdb_to_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ // Populate source environment and persist to disk.
+ {
+ let src_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ populate_store!(&src_env);
+ src_env.sync(true).expect("synced");
+ }
+ // Open and migrate.
+ {
+ let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ Migrator::open_and_migrate_lmdb_to_safe_mode(root.path(), |builder| builder, &dst_env)
+ .expect("migrated");
+ }
+ // Open and migrate back.
+ {
+ let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ Migrator::open_and_migrate_safe_mode_to_lmdb(root.path(), |builder| builder, &dst_env)
+ .expect("migrated");
+ }
+ // Verify that the database was indeed migrated twice.
+ {
+ let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let store = dst_env
+ .open_single("store", StoreOptions::default())
+ .expect("opened");
+ let reader = dst_env.read().expect("reader");
+ assert_eq!(
+ store.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ store.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ store.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+ // Check if the right files are finally present on disk.
+ {
+ let mut datamdb = root.path().to_path_buf();
+ let mut lockmdb = root.path().to_path_buf();
+ let mut safebin = root.path().to_path_buf();
+ datamdb.push("data.mdb");
+ lockmdb.push("lock.mdb");
+ safebin.push("data.safe.bin");
+ assert!(datamdb.exists());
+ assert!(lockmdb.exists());
+ assert!(!safebin.exists());
+ }
+}
+
+#[test]
+fn test_easy_migrator_no_dir_1() {
+ let root = Builder::new()
+ .prefix("test_easy_migrator_no_dir")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ // This won't fail with IoError even though the path is a bogus path, because this
+ // is the "easy mode" migration which automatically handles (ignores) this error.
+ let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ Migrator::easy_migrate_lmdb_to_safe_mode(Path::new("bogus"), &dst_env).expect("migrated");
+
+ let mut datamdb = root.path().to_path_buf();
+ let mut lockmdb = root.path().to_path_buf();
+ let mut safebin = root.path().to_path_buf();
+ datamdb.push("data.mdb");
+ lockmdb.push("lock.mdb");
+ safebin.push("data.safe.bin");
+ assert!(!datamdb.exists());
+ assert!(!lockmdb.exists());
+ assert!(!safebin.exists()); // safe mode doesn't write an empty db to disk
+}
+
+#[test]
+fn test_easy_migrator_no_dir_2() {
+ let root = Builder::new()
+ .prefix("test_easy_migrator_no_dir")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ // This won't fail with IoError even though the path is a bogus path, because this
+ // is the "easy mode" migration which automatically handles (ignores) this error.
+ let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ Migrator::easy_migrate_safe_mode_to_lmdb(Path::new("bogus"), &dst_env).expect("migrated");
+
+ let mut datamdb = root.path().to_path_buf();
+ let mut lockmdb = root.path().to_path_buf();
+ let mut safebin = root.path().to_path_buf();
+ datamdb.push("data.mdb");
+ lockmdb.push("lock.mdb");
+ safebin.push("data.safe.bin");
+ assert!(datamdb.exists()); // lmdb writes an empty db to disk
+ assert!(lockmdb.exists());
+ assert!(!safebin.exists());
+}
+
+#[test]
+fn test_easy_migrator_invalid_1() {
+ let root = Builder::new()
+ .prefix("test_easy_migrator_invalid")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let dbfile = root.path().join("data.mdb");
+ fs::write(dbfile, "bogus").expect("dbfile created");
+
+ // This won't fail with FileInvalid even though the database is a bogus file, because this
+ // is the "easy mode" migration which automatically handles (ignores) this error.
+ let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ Migrator::easy_migrate_lmdb_to_safe_mode(root.path(), &dst_env).expect("migrated");
+
+ let mut datamdb = root.path().to_path_buf();
+ let mut lockmdb = root.path().to_path_buf();
+ let mut safebin = root.path().to_path_buf();
+ datamdb.push("data.mdb");
+ lockmdb.push("lock.mdb");
+ safebin.push("data.safe.bin");
+ assert!(datamdb.exists()); // corrupted db isn't deleted
+ assert!(lockmdb.exists());
+ assert!(!safebin.exists());
+}
+
+#[test]
+fn test_easy_migrator_invalid_2() {
+ let root = Builder::new()
+ .prefix("test_easy_migrator_invalid")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let dbfile = root.path().join("data.safe.bin");
+ fs::write(dbfile, "bogus").expect("dbfile created");
+
+ // This won't fail with FileInvalid even though the database is a bogus file, because this
+ // is the "easy mode" migration which automatically handles (ignores) this error.
+ let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ Migrator::easy_migrate_safe_mode_to_lmdb(root.path(), &dst_env).expect("migrated");
+
+ let mut datamdb = root.path().to_path_buf();
+ let mut lockmdb = root.path().to_path_buf();
+ let mut safebin = root.path().to_path_buf();
+ datamdb.push("data.mdb");
+ lockmdb.push("lock.mdb");
+ safebin.push("data.safe.bin");
+ assert!(datamdb.exists()); // lmdb writes an empty db to disk
+ assert!(lockmdb.exists());
+ assert!(safebin.exists()); // corrupted db isn't deleted
+}
+
+#[test]
+#[should_panic(expected = "migrated: SourceEmpty")]
+fn test_migrator_lmdb_to_safe_1() {
+ let root = Builder::new()
+ .prefix("test_migrate_lmdb_to_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let src_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ Migrator::migrate_lmdb_to_safe_mode(&src_env, &dst_env).expect("migrated");
+}
+
+#[test]
+#[should_panic(expected = "migrated: DestinationNotEmpty")]
+fn test_migrator_lmdb_to_safe_2() {
+ let root = Builder::new()
+ .prefix("test_migrate_lmdb_to_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let src_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ populate_store!(&src_env);
+ let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ populate_store!(&dst_env);
+ Migrator::migrate_lmdb_to_safe_mode(&src_env, &dst_env).expect("migrated");
+}
+
+#[test]
+fn test_migrator_lmdb_to_safe_3() {
+ let root = Builder::new()
+ .prefix("test_migrate_lmdb_to_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let src_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ populate_store!(&src_env);
+ let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ Migrator::migrate_lmdb_to_safe_mode(&src_env, &dst_env).expect("migrated");
+
+ let store = dst_env
+ .open_single("store", StoreOptions::default())
+ .expect("opened");
+ let reader = dst_env.read().expect("reader");
+ assert_eq!(
+ store.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ store.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ store.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+}
+
+#[test]
+#[should_panic(expected = "migrated: SourceEmpty")]
+fn test_migrator_safe_to_lmdb_1() {
+ let root = Builder::new()
+ .prefix("test_migrate_safe_to_lmdb")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let src_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ Migrator::migrate_safe_mode_to_lmdb(&src_env, &dst_env).expect("migrated");
+}
+
+#[test]
+#[should_panic(expected = "migrated: DestinationNotEmpty")]
+fn test_migrator_safe_to_lmdb_2() {
+ let root = Builder::new()
+ .prefix("test_migrate_safe_to_lmdb")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let src_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ populate_store!(&src_env);
+ let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ populate_store!(&dst_env);
+ Migrator::migrate_safe_mode_to_lmdb(&src_env, &dst_env).expect("migrated");
+}
+
+#[test]
+fn test_migrator_safe_to_lmdb_3() {
+ let root = Builder::new()
+ .prefix("test_migrate_safe_to_lmdb")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let src_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ populate_store!(&src_env);
+ let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ Migrator::migrate_safe_mode_to_lmdb(&src_env, &dst_env).expect("migrated");
+
+ let store = dst_env
+ .open_single("store", StoreOptions::default())
+ .expect("opened");
+ let reader = dst_env.read().expect("reader");
+ assert_eq!(
+ store.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ store.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ store.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+}
+
+#[test]
+fn test_easy_migrator_failed_migration_1() {
+ let root = Builder::new()
+ .prefix("test_easy_migrator_failed_migration_1")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let dbfile = root.path().join("data.mdb");
+ fs::write(&dbfile, "bogus").expect("bogus dbfile created");
+
+ // This won't fail with FileInvalid even though the database is a bogus file, because this
+ // is the "easy mode" migration which automatically handles (ignores) this error.
+ let dst_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ Migrator::easy_migrate_lmdb_to_safe_mode(root.path(), &dst_env).expect("migrated");
+
+ // Populate destination environment and persist to disk.
+ populate_store!(&dst_env);
+ dst_env.sync(true).expect("synced");
+
+ // Delete bogus file and create a valid source environment in its place.
+ fs::remove_file(&dbfile).expect("bogus dbfile removed");
+ let src_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ populate_store!(&src_env);
+ src_env.sync(true).expect("synced");
+
+ // Attempt to migrate again. This should *NOT* fail with DestinationNotEmpty.
+ Migrator::easy_migrate_lmdb_to_safe_mode(root.path(), &dst_env).expect("migrated");
+}
+
+#[test]
+fn test_easy_migrator_failed_migration_2() {
+ let root = Builder::new()
+ .prefix("test_easy_migrator_failed_migration_2")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let dbfile = root.path().join("data.safe.bin");
+ fs::write(&dbfile, "bogus").expect("bogus dbfile created");
+
+ // This won't fail with FileInvalid even though the database is a bogus file, because this
+ // is the "easy mode" migration which automatically handles (ignores) this error.
+ let dst_env = Rkv::new::<Lmdb>(root.path()).expect("new succeeded");
+ Migrator::easy_migrate_safe_mode_to_lmdb(root.path(), &dst_env).expect("migrated");
+
+ // Populate destination environment and persist to disk.
+ populate_store!(&dst_env);
+ dst_env.sync(true).expect("synced");
+
+ // Delete bogus file and create a valid source environment in its place.
+ fs::remove_file(&dbfile).expect("bogus dbfile removed");
+ let src_env = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ populate_store!(&src_env);
+ src_env.sync(true).expect("synced");
+
+ // Attempt to migrate again. This should *NOT* fail with DestinationNotEmpty.
+ Migrator::easy_migrate_safe_mode_to_lmdb(root.path(), &dst_env).expect("migrated");
+}
+
+fn test_easy_migrator_from_manager_failed_migration_1() {
+ let root = Builder::new()
+ .prefix("test_easy_migrator_from_manager_failed_migration_1")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ {
+ let mut src_manager = Manager::<LmdbEnvironment>::singleton().write().unwrap();
+ let created_src_arc = src_manager
+ .get_or_create(root.path(), Rkv::new::<Lmdb>)
+ .unwrap();
+ let src_env = created_src_arc.read().unwrap();
+ populate_store!(&src_env);
+ src_env.sync(true).expect("synced");
+ }
+ {
+ let mut dst_manager = Manager::<SafeModeEnvironment>::singleton().write().unwrap();
+ let created_dst_arc_1 = dst_manager
+ .get_or_create(root.path(), Rkv::new::<SafeMode>)
+ .unwrap();
+ let dst_env_1 = created_dst_arc_1.read().unwrap();
+ populate_store!(&dst_env_1);
+ dst_env_1.sync(true).expect("synced");
+ }
+
+ // Attempt to migrate again in a new env. This should *NOT* fail with DestinationNotEmpty.
+ let dst_manager = Manager::<SafeModeEnvironment>::singleton().read().unwrap();
+ let created_dst_arc_2 = dst_manager.get(root.path()).unwrap().unwrap();
+ let dst_env_2 = created_dst_arc_2.read().unwrap();
+ Migrator::easy_migrate_lmdb_to_safe_mode(root.path(), dst_env_2).expect("migrated");
+}
+
+fn test_easy_migrator_from_manager_failed_migration_2() {
+ let root = Builder::new()
+ .prefix("test_easy_migrator_from_manager_failed_migration_2")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ {
+ let mut src_manager = Manager::<SafeModeEnvironment>::singleton().write().unwrap();
+ let created_src_arc = src_manager
+ .get_or_create(root.path(), Rkv::new::<SafeMode>)
+ .unwrap();
+ let src_env = created_src_arc.read().unwrap();
+ populate_store!(&src_env);
+ src_env.sync(true).expect("synced");
+ }
+ {
+ let mut dst_manager = Manager::<LmdbEnvironment>::singleton().write().unwrap();
+ let created_dst_arc_1 = dst_manager
+ .get_or_create(root.path(), Rkv::new::<Lmdb>)
+ .unwrap();
+ let dst_env_1 = created_dst_arc_1.read().unwrap();
+ populate_store!(&dst_env_1);
+ dst_env_1.sync(true).expect("synced");
+ }
+
+ // Attempt to migrate again in a new env. This should *NOT* fail with DestinationNotEmpty.
+ let dst_manager = Manager::<LmdbEnvironment>::singleton().read().unwrap();
+ let created_dst_arc_2 = dst_manager.get(root.path()).unwrap().unwrap();
+ let dst_env_2 = created_dst_arc_2.read().unwrap();
+ Migrator::easy_migrate_safe_mode_to_lmdb(root.path(), dst_env_2).expect("migrated");
+}
+
+#[test]
+fn test_easy_migrator_from_manager_failed_migration() {
+ test_easy_migrator_from_manager_failed_migration_1();
+ test_easy_migrator_from_manager_failed_migration_2();
+}
diff --git a/third_party/rust/rkv/tests/env-safe.rs b/third_party/rust/rkv/tests/env-safe.rs
new file mode 100644
index 0000000000..9c20e39507
--- /dev/null
+++ b/third_party/rust/rkv/tests/env-safe.rs
@@ -0,0 +1,1370 @@
+// Copyright 2018-2019 Mozilla
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+// this file except in compliance with the License. You may obtain a copy of the
+// License at http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software distributed
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations under the License.
+
+// TODO: change this back to `clippy::cognitive_complexity` when Clippy stable
+// deprecates `clippy::cyclomatic_complexity`.
+#![allow(clippy::complexity)]
+
+use std::{
+ fs,
+ path::Path,
+ str,
+ sync::{Arc, RwLock},
+ thread,
+};
+
+use byteorder::{ByteOrder, LittleEndian};
+use tempfile::Builder;
+
+use rkv::{
+ backend::{
+ BackendEnvironmentBuilder, SafeMode, SafeModeDatabase, SafeModeEnvironment,
+ SafeModeRwTransaction,
+ },
+ Rkv, SingleStore, StoreError, StoreOptions, Value, Writer,
+};
+
+fn check_rkv(k: &Rkv<SafeModeEnvironment>) {
+ let _ = k
+ .open_single(None, StoreOptions::create())
+ .expect("created default");
+
+ let s = k.open_single("s", StoreOptions::create()).expect("opened");
+ let reader = k.read().expect("reader");
+
+ let result = s.get(&reader, "foo");
+ assert_eq!(None, result.expect("success but no value"));
+}
+
+/// We can't open a directory that doesn't exist.
+#[test]
+fn test_open_fails_safe() {
+ let root = Builder::new()
+ .prefix("test_open_fails_safe")
+ .tempdir()
+ .expect("tempdir");
+ assert!(root.path().exists());
+
+ let nope = root.path().join("nope/");
+ assert!(!nope.exists());
+
+ let pb = nope.to_path_buf();
+ match Rkv::new::<SafeMode>(nope.as_path()).err() {
+ Some(StoreError::UnsuitableEnvironmentPath(p)) => {
+ assert_eq!(pb, p);
+ }
+ _ => panic!("expected error"),
+ };
+}
+
+#[test]
+fn test_open_safe() {
+ let root = Builder::new()
+ .prefix("test_open_safe")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ check_rkv(&k);
+}
+
+#[test]
+fn test_open_from_builder_safe() {
+ let root = Builder::new()
+ .prefix("test_open_from_builder_safe")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let mut builder = Rkv::environment_builder::<SafeMode>();
+ builder.set_max_dbs(2);
+
+ let k = Rkv::from_builder(root.path(), builder).expect("rkv");
+ check_rkv(&k);
+}
+
+#[test]
+fn test_open_from_builder_with_dir_safe_1() {
+ let root = Builder::new()
+ .prefix("test_open_from_builder_safe")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+
+ let mut builder = Rkv::environment_builder::<SafeMode>();
+ builder.set_max_dbs(2);
+ builder.set_make_dir_if_needed(true);
+
+ let k = Rkv::from_builder(root.path(), builder).expect("rkv");
+ check_rkv(&k);
+}
+
+#[test]
+#[should_panic(expected = "rkv: UnsuitableEnvironmentPath(\"bogus\")")]
+fn test_open_from_builder_with_dir_safe_2() {
+ let root = Path::new("bogus");
+ println!("Root path: {root:?}");
+ assert!(!root.is_dir());
+
+ let mut builder = Rkv::environment_builder::<SafeMode>();
+ builder.set_max_dbs(2);
+
+ let k = Rkv::from_builder(root, builder).expect("rkv");
+ check_rkv(&k);
+}
+
+#[test]
+#[should_panic(expected = "opened: DbsFull")]
+fn test_create_with_capacity_safe_1() {
+ let root = Builder::new()
+ .prefix("test_create_with_capacity_safe")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<SafeMode>(root.path(), 1).expect("rkv");
+ check_rkv(&k);
+
+ // This errors with "opened: DbsFull" because we specified a capacity of one (database),
+ // and check_rkv already opened one (plus the default database, which doesn't count
+ // against the limit).
+ let _zzz = k
+ .open_single("zzz", StoreOptions::create())
+ .expect("opened");
+}
+
+#[test]
+fn test_create_with_capacity_safe_2() {
+ let root = Builder::new()
+ .prefix("test_create_with_capacity_safe")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<SafeMode>(root.path(), 1).expect("rkv");
+ check_rkv(&k);
+
+ // This doesn't error with "opened: DbsFull" because even though we specified a capacity
+ // of one (database), and check_rkv already opened one, the default database doesn't
+ // count against the limit).
+ let _zzz = k.open_single(None, StoreOptions::create()).expect("opened");
+}
+
+#[test]
+#[should_panic(expected = "opened: SafeModeError(DbNotFoundError)")]
+fn test_open_with_capacity_safe_1() {
+ let root = Builder::new()
+ .prefix("test_open_with_capacity_safe")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<SafeMode>(root.path(), 1).expect("rkv");
+ check_rkv(&k);
+
+ let _zzz = k
+ .open_single("zzz", StoreOptions::default())
+ .expect("opened");
+}
+
+#[test]
+fn test_open_with_capacity_safe_2() {
+ let root = Builder::new()
+ .prefix("test_open_with_capacity_safe")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<SafeMode>(root.path(), 1).expect("rkv");
+ check_rkv(&k);
+
+ let _zzz = k
+ .open_single(None, StoreOptions::default())
+ .expect("opened");
+}
+
+#[test]
+fn test_list_dbs_safe_1() {
+ let root = Builder::new()
+ .prefix("test_list_dbs_safe")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<SafeMode>(root.path(), 1).expect("rkv");
+ check_rkv(&k);
+
+ let mut dbs = k.get_dbs().unwrap();
+ dbs.sort();
+ assert_eq!(dbs, vec![None, Some("s".to_owned())]);
+}
+
+#[test]
+fn test_list_dbs_safe_2() {
+ let root = Builder::new()
+ .prefix("test_list_dbs_safe")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<SafeMode>(root.path(), 2).expect("rkv");
+ check_rkv(&k);
+
+ let _ = k
+ .open_single("zzz", StoreOptions::create())
+ .expect("opened");
+
+ let mut dbs = k.get_dbs().unwrap();
+ dbs.sort();
+ assert_eq!(
+ dbs,
+ vec![None, Some("s".to_owned()), Some("zzz".to_owned())]
+ );
+}
+
+#[test]
+fn test_list_dbs_safe_3() {
+ let root = Builder::new()
+ .prefix("test_list_dbs_safe")
+ .tempdir()
+ .expect("tempdir");
+ println!("Root path: {:?}", root.path());
+ fs::create_dir_all(root.path()).expect("dir created");
+ assert!(root.path().is_dir());
+
+ let k = Rkv::with_capacity::<SafeMode>(root.path(), 0).expect("rkv");
+
+ let _ = k.open_single(None, StoreOptions::create()).expect("opened");
+
+ let mut dbs = k.get_dbs().unwrap();
+ dbs.sort();
+ assert_eq!(dbs, vec![None]);
+}
+
+#[test]
+fn test_round_trip_and_transactions_safe() {
+ let root = Builder::new()
+ .prefix("test_round_trip_and_transactions_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = 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, "noo", &Value::F64(1234.0.into()))
+ .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");
+ assert_eq!(
+ sk.get(&writer, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&writer, "noo").expect("read"),
+ Some(Value::F64(1234.0.into()))
+ );
+ assert_eq!(
+ sk.get(&writer, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&writer, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+
+ // Isolation. Reads won't return values.
+ 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);
+ }
+
+ // Dropped: tx rollback. Reads will still return nothing.
+
+ {
+ 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);
+ }
+
+ {
+ 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");
+ assert_eq!(
+ sk.get(&writer, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ sk.get(&writer, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ sk.get(&writer, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+
+ writer.commit().expect("committed");
+ }
+
+ // Committed. Reads will succeed.
+
+ {
+ let r = k.read().unwrap();
+ assert_eq!(sk.get(&r, "foo").expect("read"), Some(Value::I64(1234)));
+ assert_eq!(sk.get(&r, "bar").expect("read"), Some(Value::Bool(true)));
+ assert_eq!(
+ sk.get(&r, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+
+ {
+ let mut writer = k.write().expect("writer");
+ sk.delete(&mut writer, "foo").expect("deleted");
+ sk.delete(&mut writer, "bar").expect("deleted");
+ sk.delete(&mut writer, "baz").expect("deleted");
+ assert_eq!(sk.get(&writer, "foo").expect("read"), None);
+ assert_eq!(sk.get(&writer, "bar").expect("read"), None);
+ assert_eq!(sk.get(&writer, "baz").expect("read"), None);
+
+ // Isolation. Reads still return values.
+ let r = k.read().unwrap();
+ assert_eq!(sk.get(&r, "foo").expect("read"), Some(Value::I64(1234)));
+ assert_eq!(sk.get(&r, "bar").expect("read"), Some(Value::Bool(true)));
+ assert_eq!(
+ sk.get(&r, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+
+ // Dropped: tx rollback. Reads will still return values.
+
+ {
+ let r = k.read().unwrap();
+ assert_eq!(sk.get(&r, "foo").expect("read"), Some(Value::I64(1234)));
+ assert_eq!(sk.get(&r, "bar").expect("read"), Some(Value::Bool(true)));
+ assert_eq!(
+ sk.get(&r, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ }
+
+ {
+ let mut writer = k.write().expect("writer");
+ sk.delete(&mut writer, "foo").expect("deleted");
+ sk.delete(&mut writer, "bar").expect("deleted");
+ sk.delete(&mut writer, "baz").expect("deleted");
+ assert_eq!(sk.get(&writer, "foo").expect("read"), None);
+ assert_eq!(sk.get(&writer, "bar").expect("read"), None);
+ assert_eq!(sk.get(&writer, "baz").expect("read"), None);
+
+ writer.commit().expect("committed");
+ }
+
+ // Committed. Reads will succeed but return None to indicate a missing value.
+
+ {
+ 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_safe() {
+ let root = Builder::new()
+ .prefix("test_single_store_clear_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = 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]
+#[should_panic(expected = "KeyValuePairNotFound")]
+fn test_single_store_delete_nonexistent_safe() {
+ let root = Builder::new()
+ .prefix("test_single_store_delete_nonexistent_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ let mut writer = k.write().expect("writer");
+ sk.delete(&mut writer, "bogus").unwrap();
+}
+
+#[test]
+#[cfg(feature = "db-dup-sort")]
+fn test_multi_put_get_del_safe() {
+ let root = Builder::new()
+ .prefix("test_multi_put_get_del_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(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();
+ 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().unwrap();
+
+ let writer = k.write().unwrap();
+ {
+ let mut iter = multistore.get(&writer, "str1").unwrap();
+ let (id, val) = iter.next().unwrap().unwrap();
+ assert_eq!((id, val), (&b"str1"[..], Value::Str("str1 bar")));
+ let (id, val) = iter.next().unwrap().unwrap();
+ assert_eq!((id, val), (&b"str1"[..], Value::Str("str1 foo")));
+ }
+ writer.commit().unwrap();
+
+ let mut writer = k.write().unwrap();
+ multistore
+ .delete(&mut writer, "str1", &Value::Str("str1 foo"))
+ .unwrap();
+ assert_eq!(
+ multistore.get_first(&writer, "str1").unwrap(),
+ Some(Value::Str("str1 bar"))
+ );
+ multistore
+ .delete(&mut writer, "str2", &Value::Str("str2 bar"))
+ .unwrap();
+ 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]
+#[cfg(feature = "db-dup-sort")]
+fn test_multiple_store_clear_safe() {
+ let root = Builder::new()
+ .prefix("test_multiple_store_clear_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(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_safe() {
+ let root = Builder::new()
+ .prefix("test_open_store_for_read_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(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");
+
+ // Open the same store for read, note that the write transaction is still in progress,
+ // it should not block the reader though.
+ let sk_readonly = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+ writer.commit().expect("commit");
+
+ // Now the write transaction is committed, any followed reads should see its change.
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk_readonly.get(&reader, "foo").expect("read"),
+ Some(Value::Str("bar"))
+ );
+}
+
+#[test]
+#[should_panic(expected = "open a missing store")]
+fn test_open_a_missing_store_safe() {
+ let root = Builder::new()
+ .prefix("test_open_a_missing_store_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let _sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("open a missing store");
+}
+
+#[test]
+#[should_panic(expected = "new failed: FileInvalid")]
+fn test_open_a_broken_store_safe() {
+ let root = Builder::new()
+ .prefix("test_open_a_missing_store_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let dbfile = root.path().join("data.safe.bin");
+ fs::write(dbfile, "bogus").expect("dbfile created");
+
+ let _ = Rkv::new::<SafeMode>(root.path()).expect("new failed");
+}
+
+#[test]
+fn test_open_fail_with_badrslot_safe() {
+ let root = Builder::new()
+ .prefix("test_open_fail_with_badrslot_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(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 = k.open_single("sk", StoreOptions::default());
+ match store {
+ Err(StoreError::OpenAttemptedDuringTransaction(_thread_id)) => (),
+ _ => panic!("should panic"),
+ }
+}
+
+#[test]
+fn test_create_fail_with_badrslot_safe() {
+ let root = Builder::new()
+ .prefix("test_create_fail_with_badrslot_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(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 = k.open_single("sk", StoreOptions::create());
+ match store {
+ Err(StoreError::OpenAttemptedDuringTransaction(_thread_id)) => (),
+ _ => panic!("should panic"),
+ }
+}
+
+#[test]
+fn test_read_before_write_num_safe() {
+ let root = Builder::new()
+ .prefix("test_read_before_write_num_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ // Test reading a number, modifying it, and then writing it back.
+ // We have to be done with the Value::I64 before calling Writer::put,
+ // as the Value::I64 borrows an immutable reference to the Writer.
+ // So we extract and copy its primitive value.
+
+ fn get_existing_foo(
+ store: SingleStore<SafeModeDatabase>,
+ writer: &Writer<SafeModeRwTransaction>,
+ ) -> Option<i64> {
+ match store.get(writer, "foo").expect("read") {
+ Some(Value::I64(val)) => Some(val),
+ _ => None,
+ }
+ }
+
+ let mut writer = k.write().expect("writer");
+ let mut existing = get_existing_foo(sk, &writer).unwrap_or(99);
+ existing += 1;
+ sk.put(&mut writer, "foo", &Value::I64(existing))
+ .expect("success");
+
+ let updated = get_existing_foo(sk, &writer).unwrap_or(99);
+ assert_eq!(updated, 100);
+ writer.commit().expect("commit");
+}
+
+#[test]
+fn test_read_before_write_str_safe() {
+ let root = Builder::new()
+ .prefix("test_read_before_write_str_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ // Test reading a string, modifying it, and then writing it back.
+ // We have to be done with the Value::Str before calling Writer::put,
+ // as the Value::Str (and its underlying &str) borrows an immutable
+ // reference to the Writer. So we copy it to a String.
+
+ fn get_existing_foo(
+ store: SingleStore<SafeModeDatabase>,
+ writer: &Writer<SafeModeRwTransaction>,
+ ) -> Option<String> {
+ match store.get(writer, "foo").expect("read") {
+ Some(Value::Str(val)) => Some(val.to_string()),
+ _ => None,
+ }
+ }
+
+ let mut writer = k.write().expect("writer");
+ let mut existing = get_existing_foo(sk, &writer).unwrap_or_default();
+ existing.push('…');
+ sk.put(&mut writer, "foo", &Value::Str(&existing))
+ .expect("write");
+
+ let updated = get_existing_foo(sk, &writer).unwrap_or_default();
+ assert_eq!(updated, "…");
+ writer.commit().expect("commit");
+}
+
+#[test]
+fn test_isolation_safe() {
+ let root = Builder::new()
+ .prefix("test_isolation_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let s = k.open_single("s", StoreOptions::create()).expect("opened");
+
+ // Add one field.
+ {
+ let mut writer = k.write().expect("writer");
+ s.put(&mut writer, "foo", &Value::I64(1234)).expect("wrote");
+ writer.commit().expect("committed");
+ }
+
+ {
+ let reader = k.read().unwrap();
+ assert_eq!(s.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
+ }
+
+ // Establish a long-lived reader that outlasts a writer.
+ let reader = k.read().expect("reader");
+ assert_eq!(s.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
+
+ // Start a write transaction.
+ let mut writer = k.write().expect("writer");
+ s.put(&mut writer, "foo", &Value::I64(999)).expect("wrote");
+
+ // The reader and writer are isolated.
+ assert_eq!(s.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
+ assert_eq!(s.get(&writer, "foo").expect("read"), Some(Value::I64(999)));
+
+ // If we commit the writer, we still have isolation.
+ writer.commit().expect("committed");
+ assert_eq!(s.get(&reader, "foo").expect("read"), Some(Value::I64(1234)));
+
+ // A new reader sees the committed value. Note that LMDB doesn't allow two
+ // read transactions to exist in the same thread, so we abort the previous one.
+ reader.abort();
+ let reader = k.read().expect("reader");
+ assert_eq!(s.get(&reader, "foo").expect("read"), Some(Value::I64(999)));
+}
+
+#[test]
+fn test_blob_safe() {
+ let root = Builder::new()
+ .prefix("test_round_trip_blob_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ let mut writer = k.write().expect("writer");
+ assert_eq!(sk.get(&writer, "foo").expect("read"), None);
+ sk.put(&mut writer, "foo", &Value::Blob(&[1, 2, 3, 4]))
+ .expect("wrote");
+ assert_eq!(
+ sk.get(&writer, "foo").expect("read"),
+ Some(Value::Blob(&[1, 2, 3, 4]))
+ );
+
+ fn u16_to_u8(src: &[u16]) -> Vec<u8> {
+ let mut dst = vec![0; 2 * src.len()];
+ LittleEndian::write_u16_into(src, &mut dst);
+ dst
+ }
+
+ fn u8_to_u16(src: &[u8]) -> Vec<u16> {
+ let mut dst = vec![0; src.len() / 2];
+ LittleEndian::read_u16_into(src, &mut dst);
+ dst
+ }
+
+ // When storing UTF-16 strings as blobs, we'll need to convert
+ // their [u16] backing storage to [u8]. Test that converting, writing,
+ // reading, and converting back works as expected.
+ let u16_array = [1000, 10000, 54321, 65535];
+ assert_eq!(sk.get(&writer, "bar").expect("read"), None);
+ sk.put(&mut writer, "bar", &Value::Blob(&u16_to_u8(&u16_array)))
+ .expect("wrote");
+ let u8_array = match sk.get(&writer, "bar").expect("read") {
+ Some(Value::Blob(val)) => val,
+ _ => &[],
+ };
+ assert_eq!(u8_to_u16(u8_array), u16_array);
+}
+
+#[test]
+fn test_sync_safe() {
+ let root = Builder::new()
+ .prefix("test_sync_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let mut builder = Rkv::environment_builder::<SafeMode>();
+ builder.set_max_dbs(1);
+ {
+ let k = Rkv::from_builder(root.path(), builder).expect("new succeeded");
+ let sk = 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");
+ writer.commit().expect("committed");
+ k.sync(true).expect("synced");
+ }
+ }
+ let k = Rkv::from_builder(root.path(), builder).expect("new succeeded");
+ let sk = k
+ .open_single("sk", StoreOptions::default())
+ .expect("opened");
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ sk.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+}
+
+#[test]
+fn test_iter_safe() {
+ let root = Builder::new()
+ .prefix("test_iter_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = k.open_single("sk", StoreOptions::create()).expect("opened");
+
+ // An iterator over an empty store returns no values.
+ {
+ let reader = k.read().unwrap();
+ let mut iter = sk.iter_start(&reader).unwrap();
+ assert!(iter.next().is_none());
+ }
+
+ let mut writer = k.write().expect("writer");
+ sk.put(&mut writer, "foo", &Value::I64(1234))
+ .expect("wrote");
+ sk.put(&mut writer, "noo", &Value::F64(1234.0.into()))
+ .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");
+ sk.put(&mut writer, "héllò, töűrîst", &Value::Str("Emil.RuleZ!"))
+ .expect("wrote");
+ sk.put(&mut writer, "你好,遊客", &Value::Str("米克規則"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+
+ let reader = k.read().unwrap();
+
+ // Reader.iter() returns (key, value) tuples ordered by key.
+ let mut iter = sk.iter_start(&reader).unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "bar");
+ assert_eq!(val, Value::Bool(true));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "baz");
+ assert_eq!(val, Value::Str("héllo, yöu"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "foo");
+ assert_eq!(val, Value::I64(1234));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "héllò, töűrîst");
+ assert_eq!(val, Value::Str("Emil.RuleZ!"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterators don't loop. Once one returns None, additional calls
+ // to its next() method will always return None.
+ assert!(iter.next().is_none());
+
+ // Reader.iter_from() begins iteration at the first key equal to
+ // or greater than the given key.
+ let mut iter = sk.iter_from(&reader, "moo").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Reader.iter_from() works as expected when the given key is a prefix
+ // of a key in the store.
+ let mut iter = sk.iter_from(&reader, "no").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+}
+
+#[test]
+fn test_iter_from_key_greater_than_existing_safe() {
+ let root = Builder::new()
+ .prefix("test_iter_from_key_greater_than_existing_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let sk = 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, "noo", &Value::F64(1234.0.into()))
+ .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 reader = k.read().unwrap();
+ let mut iter = sk.iter_from(&reader, "nuu").unwrap();
+ assert!(iter.next().is_none());
+}
+
+#[test]
+fn test_multiple_store_read_write_safe() {
+ let root = Builder::new()
+ .prefix("test_multiple_store_read_write_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let s1 = k
+ .open_single("store_1", StoreOptions::create())
+ .expect("opened");
+ let s2 = k
+ .open_single("store_2", StoreOptions::create())
+ .expect("opened");
+ let s3 = k
+ .open_single("store_3", StoreOptions::create())
+ .expect("opened");
+
+ let mut writer = k.write().expect("writer");
+ s1.put(&mut writer, "foo", &Value::Str("bar"))
+ .expect("wrote");
+ s2.put(&mut writer, "foo", &Value::I64(123)).expect("wrote");
+ s3.put(&mut writer, "foo", &Value::Bool(true))
+ .expect("wrote");
+
+ assert_eq!(
+ s1.get(&writer, "foo").expect("read"),
+ Some(Value::Str("bar"))
+ );
+ assert_eq!(s2.get(&writer, "foo").expect("read"), Some(Value::I64(123)));
+ assert_eq!(
+ s3.get(&writer, "foo").expect("read"),
+ Some(Value::Bool(true))
+ );
+
+ writer.commit().expect("committed");
+
+ let reader = k.read().expect("unbound_reader");
+ assert_eq!(
+ s1.get(&reader, "foo").expect("read"),
+ Some(Value::Str("bar"))
+ );
+ assert_eq!(s2.get(&reader, "foo").expect("read"), Some(Value::I64(123)));
+ assert_eq!(
+ s3.get(&reader, "foo").expect("read"),
+ Some(Value::Bool(true))
+ );
+ reader.abort();
+
+ // test delete across multiple stores
+ let mut writer = k.write().expect("writer");
+ s1.delete(&mut writer, "foo").expect("deleted");
+ s2.delete(&mut writer, "foo").expect("deleted");
+ s3.delete(&mut writer, "foo").expect("deleted");
+ writer.commit().expect("committed");
+
+ let reader = k.read().expect("reader");
+ assert_eq!(s1.get(&reader, "key").expect("value"), None);
+ assert_eq!(s2.get(&reader, "key").expect("value"), None);
+ assert_eq!(s3.get(&reader, "key").expect("value"), None);
+}
+
+#[test]
+fn test_multiple_store_iter_safe() {
+ let root = Builder::new()
+ .prefix("test_multiple_store_iter_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let s1 = k
+ .open_single("store_1", StoreOptions::create())
+ .expect("opened");
+ let s2 = k
+ .open_single("store_2", StoreOptions::create())
+ .expect("opened");
+
+ let mut writer = k.write().expect("writer");
+ // Write to "s1"
+ s1.put(&mut writer, "foo", &Value::I64(1234))
+ .expect("wrote");
+ s1.put(&mut writer, "noo", &Value::F64(1234.0.into()))
+ .expect("wrote");
+ s1.put(&mut writer, "bar", &Value::Bool(true))
+ .expect("wrote");
+ s1.put(&mut writer, "baz", &Value::Str("héllo, yöu"))
+ .expect("wrote");
+ s1.put(&mut writer, "héllò, töűrîst", &Value::Str("Emil.RuleZ!"))
+ .expect("wrote");
+ s1.put(&mut writer, "你好,遊客", &Value::Str("米克規則"))
+ .expect("wrote");
+ // &mut writer to "s2"
+ s2.put(&mut writer, "foo", &Value::I64(1234))
+ .expect("wrote");
+ s2.put(&mut writer, "noo", &Value::F64(1234.0.into()))
+ .expect("wrote");
+ s2.put(&mut writer, "bar", &Value::Bool(true))
+ .expect("wrote");
+ s2.put(&mut writer, "baz", &Value::Str("héllo, yöu"))
+ .expect("wrote");
+ s2.put(&mut writer, "héllò, töűrîst", &Value::Str("Emil.RuleZ!"))
+ .expect("wrote");
+ s2.put(&mut writer, "你好,遊客", &Value::Str("米克規則"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+
+ let reader = k.read().unwrap();
+
+ // Iterate through the whole store in "s1"
+ let mut iter = s1.iter_start(&reader).unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "bar");
+ assert_eq!(val, Value::Bool(true));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "baz");
+ assert_eq!(val, Value::Str("héllo, yöu"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "foo");
+ assert_eq!(val, Value::I64(1234));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "héllò, töűrîst");
+ assert_eq!(val, Value::Str("Emil.RuleZ!"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterate through the whole store in "s2"
+ let mut iter = s2.iter_start(&reader).unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "bar");
+ assert_eq!(val, Value::Bool(true));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "baz");
+ assert_eq!(val, Value::Str("héllo, yöu"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "foo");
+ assert_eq!(val, Value::I64(1234));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "héllò, töűrîst");
+ assert_eq!(val, Value::Str("Emil.RuleZ!"));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterate from a given key in "s1"
+ let mut iter = s1.iter_from(&reader, "moo").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterate from a given key in "s2"
+ let mut iter = s2.iter_from(&reader, "moo").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterate from a given prefix in "s1"
+ let mut iter = s1.iter_from(&reader, "no").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+
+ // Iterate from a given prefix in "s2"
+ let mut iter = s2.iter_from(&reader, "no").unwrap();
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "noo");
+ assert_eq!(val, Value::F64(1234.0.into()));
+ let (key, val) = iter.next().unwrap().unwrap();
+ assert_eq!(str::from_utf8(key).expect("key"), "你好,遊客");
+ assert_eq!(val, Value::Str("米克規則"));
+ assert!(iter.next().is_none());
+}
+
+#[test]
+fn test_store_multiple_thread_safe() {
+ let root = Builder::new()
+ .prefix("test_multiple_thread_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let rkv_arc = Arc::new(RwLock::new(
+ Rkv::new::<SafeMode>(root.path()).expect("new succeeded"),
+ ));
+ let store = rkv_arc
+ .read()
+ .unwrap()
+ .open_single("test", StoreOptions::create())
+ .expect("opened");
+
+ let num_threads = 10;
+ let mut write_handles = Vec::with_capacity(num_threads as usize);
+ let mut read_handles = Vec::with_capacity(num_threads as usize);
+
+ // Note that this isn't intended to demonstrate a good use of threads.
+ // For this shape of data, it would be more performant to write/read
+ // all values using one transaction in a single thread. The point here
+ // is just to confirm that a store can be shared by multiple threads.
+
+ // For each KV pair, spawn a thread that writes it to the store.
+ for i in 0..num_threads {
+ let rkv_arc = rkv_arc.clone();
+ write_handles.push(thread::spawn(move || {
+ let rkv = rkv_arc.write().expect("rkv");
+ let mut writer = rkv.write().expect("writer");
+ store
+ .put(&mut writer, i.to_string(), &Value::U64(i))
+ .expect("written");
+ writer.commit().unwrap();
+ }));
+ }
+ for handle in write_handles {
+ handle.join().expect("joined");
+ }
+
+ // For each KV pair, spawn a thread that reads it from the store
+ // and returns its value.
+ for i in 0..num_threads {
+ let rkv_arc = rkv_arc.clone();
+ read_handles.push(thread::spawn(move || {
+ let rkv = rkv_arc.read().expect("rkv");
+ let reader = rkv.read().expect("reader");
+ let value = match store.get(&reader, i.to_string()) {
+ Ok(Some(Value::U64(value))) => value,
+ Ok(Some(_)) => panic!("value type unexpected"),
+ Ok(None) => panic!("value not found"),
+ Err(err) => panic!("{}", err),
+ };
+ assert_eq!(value, i);
+ value
+ }));
+ }
+
+ // Sum the values returned from the threads and confirm that they're
+ // equal to the sum of values written to the threads.
+ let thread_sum: u64 = read_handles
+ .into_iter()
+ .map(|handle| handle.join().expect("value"))
+ .sum();
+ assert_eq!(thread_sum, (0..num_threads).sum());
+}
+
+#[test]
+fn test_use_value_as_key_safe() {
+ let root = Builder::new()
+ .prefix("test_use_value_as_key_safe")
+ .tempdir()
+ .expect("tempdir");
+ let rkv = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let store = rkv
+ .open_single("store", StoreOptions::create())
+ .expect("opened");
+
+ {
+ let mut writer = rkv.write().expect("writer");
+ store
+ .put(&mut writer, "foo", &Value::Str("bar"))
+ .expect("wrote");
+ store
+ .put(&mut writer, "bar", &Value::Str("baz"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+ }
+
+ // It's possible to retrieve a value with a Reader and then use it
+ // as a key with a Writer.
+ {
+ let reader = &rkv.read().unwrap();
+ if let Some(Value::Str(key)) = store.get(reader, "foo").expect("read") {
+ let mut writer = rkv.write().expect("writer");
+ store.delete(&mut writer, key).expect("deleted");
+ writer.commit().expect("committed");
+ }
+ }
+
+ {
+ let mut writer = rkv.write().expect("writer");
+ store
+ .put(&mut writer, "bar", &Value::Str("baz"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+ }
+
+ // You can also retrieve a Value with a Writer and then use it as a key
+ // with the same Writer if you copy the value to an owned type
+ // so the Writer isn't still being borrowed by the retrieved value
+ // when you try to borrow the Writer again to modify that value.
+ {
+ let mut writer = rkv.write().expect("writer");
+ if let Some(Value::Str(value)) = store.get(&writer, "foo").expect("read") {
+ let key = value.to_owned();
+ store.delete(&mut writer, key).expect("deleted");
+ writer.commit().expect("committed");
+ }
+ }
+
+ {
+ let name1 = rkv
+ .open_single("name1", StoreOptions::create())
+ .expect("opened");
+ let name2 = rkv
+ .open_single("name2", StoreOptions::create())
+ .expect("opened");
+ let mut writer = rkv.write().expect("writer");
+ name1
+ .put(&mut writer, "key1", &Value::Str("bar"))
+ .expect("wrote");
+ name1
+ .put(&mut writer, "bar", &Value::Str("baz"))
+ .expect("wrote");
+ name2
+ .put(&mut writer, "key2", &Value::Str("bar"))
+ .expect("wrote");
+ name2
+ .put(&mut writer, "bar", &Value::Str("baz"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+ }
+
+ // You can also iterate (store, key) pairs to retrieve foreign keys,
+ // then iterate those foreign keys to modify/delete them.
+ //
+ // You need to open the stores in advance, since opening a store
+ // uses a write transaction internally, so opening them while a writer
+ // is extant will hang.
+ //
+ // And you need to copy the values to an owned type so the Writer isn't
+ // still being borrowed by a retrieved value when you try to borrow
+ // the Writer again to modify another value.
+ let fields = vec![
+ (
+ rkv.open_single("name1", StoreOptions::create())
+ .expect("opened"),
+ "key1",
+ ),
+ (
+ rkv.open_single("name2", StoreOptions::create())
+ .expect("opened"),
+ "key2",
+ ),
+ ];
+ {
+ let mut foreignkeys = Vec::new();
+ let mut writer = rkv.write().expect("writer");
+ for (store, key) in fields.iter() {
+ if let Some(Value::Str(value)) = store.get(&writer, key).expect("read") {
+ foreignkeys.push((store, value.to_owned()));
+ }
+ }
+ for (store, key) in foreignkeys.iter() {
+ store.delete(&mut writer, key).expect("deleted");
+ }
+ writer.commit().expect("committed");
+ }
+}
diff --git a/third_party/rust/rkv/tests/integer-store.rs b/third_party/rust/rkv/tests/integer-store.rs
new file mode 100644
index 0000000000..4f27776e79
--- /dev/null
+++ b/third_party/rust/rkv/tests/integer-store.rs
@@ -0,0 +1,88 @@
+// Copyright 2018-2019 Mozilla
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+// this file except in compliance with the License. You may obtain a copy of the
+// License at http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software distributed
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations under the License.
+#![cfg(feature = "db-int-key")]
+#![allow(clippy::many_single_char_names)]
+
+use std::fs;
+
+use serde_derive::Serialize;
+use tempfile::Builder;
+
+use rkv::{backend::SafeMode, PrimitiveInt, Rkv, StoreOptions, Value};
+
+#[test]
+fn test_integer_keys() {
+ let root = Builder::new()
+ .prefix("test_integer_keys")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let s = k.open_integer("s", StoreOptions::create()).expect("open");
+
+ macro_rules! test_integer_keys {
+ ($store:expr, $key:expr) => {{
+ let mut writer = k.write().expect("writer");
+
+ $store
+ .put(&mut writer, $key, &Value::Str("hello!"))
+ .expect("write");
+ assert_eq!(
+ $store.get(&writer, $key).expect("read"),
+ Some(Value::Str("hello!"))
+ );
+ writer.commit().expect("committed");
+
+ let reader = k.read().expect("reader");
+ assert_eq!(
+ $store.get(&reader, $key).expect("read"),
+ Some(Value::Str("hello!"))
+ );
+ }};
+ }
+
+ // The integer module provides only the u32 integer key variant
+ // of IntegerStore, so we can use it without further ado.
+ test_integer_keys!(s, std::u32::MIN);
+ test_integer_keys!(s, std::u32::MAX);
+
+ // If you want to use another integer key variant, you need to implement
+ // a newtype, implement PrimitiveInt, and implement or derive Serialize
+ // for it. Here we do so for the i32 type.
+
+ // DANGER! Doing this enables you to open a store with multiple,
+ // different integer key types, which may result in unexpected behavior.
+ // Make sure you know what you're doing!
+
+ let t = k.open_integer("s", StoreOptions::create()).expect("open");
+
+ #[derive(Serialize)]
+ struct I32(i32);
+ impl PrimitiveInt for I32 {}
+ test_integer_keys!(t, I32(std::i32::MIN));
+ test_integer_keys!(t, I32(std::i32::MAX));
+
+ let u = k.open_integer("s", StoreOptions::create()).expect("open");
+
+ #[derive(Serialize)]
+ struct U16(u16);
+ impl PrimitiveInt for U16 {}
+ test_integer_keys!(u, U16(std::u16::MIN));
+ test_integer_keys!(u, U16(std::u16::MAX));
+
+ let v = k.open_integer("s", StoreOptions::create()).expect("open");
+
+ #[derive(Serialize)]
+ struct U64(u64);
+ impl PrimitiveInt for U64 {}
+ test_integer_keys!(v, U64(std::u64::MIN));
+ test_integer_keys!(v, U64(std::u64::MAX));
+}
diff --git a/third_party/rust/rkv/tests/manager.rs b/third_party/rust/rkv/tests/manager.rs
new file mode 100644
index 0000000000..fe6b6cd026
--- /dev/null
+++ b/third_party/rust/rkv/tests/manager.rs
@@ -0,0 +1,380 @@
+// Copyright 2018-2019 Mozilla
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+// this file except in compliance with the License. You may obtain a copy of the
+// License at http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software distributed
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations under the License.
+
+use std::{fs, sync::Arc};
+
+use tempfile::Builder;
+
+#[cfg(feature = "lmdb")]
+use rkv::backend::{Lmdb, LmdbEnvironment};
+use rkv::{
+ backend::{BackendEnvironmentBuilder, SafeMode, SafeModeEnvironment},
+ CloseOptions, Rkv, StoreOptions, Value,
+};
+
+/// Test that a manager can be created with simple type inference.
+#[cfg(feature = "lmdb")]
+#[test]
+#[allow(clippy::let_underscore_lock)]
+fn test_simple() {
+ type Manager = rkv::Manager<LmdbEnvironment>;
+
+ let _unused = Manager::singleton().write().unwrap();
+}
+
+/// Test that a manager can be created with simple type inference.
+#[test]
+#[allow(clippy::let_underscore_lock)]
+fn test_simple_safe() {
+ type Manager = rkv::Manager<SafeModeEnvironment>;
+
+ let _unused = Manager::singleton().write().unwrap();
+}
+
+/// Test that a shared Rkv instance can be created with simple type inference.
+#[cfg(feature = "lmdb")]
+#[test]
+fn test_simple_2() {
+ type Manager = rkv::Manager<LmdbEnvironment>;
+
+ let root = Builder::new()
+ .prefix("test_simple_2")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let mut manager = Manager::singleton().write().unwrap();
+ let _ = manager
+ .get_or_create(root.path(), Rkv::new::<Lmdb>)
+ .unwrap();
+}
+
+/// Test that a shared Rkv instance can be created with simple type inference.
+#[test]
+fn test_simple_safe_2() {
+ type Manager = rkv::Manager<SafeModeEnvironment>;
+
+ let root = Builder::new()
+ .prefix("test_simple_safe_2")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let mut manager = Manager::singleton().write().unwrap();
+ let _ = manager
+ .get_or_create(root.path(), Rkv::new::<SafeMode>)
+ .unwrap();
+}
+
+/// Test that the manager will return the same Rkv instance each time for each path.
+#[cfg(feature = "lmdb")]
+#[test]
+fn test_same() {
+ type Manager = rkv::Manager<LmdbEnvironment>;
+
+ let root = Builder::new()
+ .prefix("test_same")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let p = root.path();
+ assert!(Manager::singleton()
+ .read()
+ .unwrap()
+ .get(p)
+ .expect("success")
+ .is_none());
+
+ let created_arc = Manager::singleton()
+ .write()
+ .unwrap()
+ .get_or_create(p, Rkv::new::<Lmdb>)
+ .expect("created");
+ let fetched_arc = Manager::singleton()
+ .read()
+ .unwrap()
+ .get(p)
+ .expect("success")
+ .expect("existed");
+ assert!(Arc::ptr_eq(&created_arc, &fetched_arc));
+}
+
+/// Test that the manager will return the same Rkv instance each time for each path.
+#[test]
+fn test_same_safe() {
+ type Manager = rkv::Manager<SafeModeEnvironment>;
+
+ let root = Builder::new()
+ .prefix("test_same_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let p = root.path();
+ assert!(Manager::singleton()
+ .read()
+ .unwrap()
+ .get(p)
+ .expect("success")
+ .is_none());
+
+ let created_arc = Manager::singleton()
+ .write()
+ .unwrap()
+ .get_or_create(p, Rkv::new::<SafeMode>)
+ .expect("created");
+ let fetched_arc = Manager::singleton()
+ .read()
+ .unwrap()
+ .get(p)
+ .expect("success")
+ .expect("existed");
+ assert!(Arc::ptr_eq(&created_arc, &fetched_arc));
+}
+
+/// Test that the manager will return the same Rkv instance each time for each path.
+#[cfg(feature = "lmdb")]
+#[test]
+fn test_same_with_capacity() {
+ type Manager = rkv::Manager<LmdbEnvironment>;
+
+ let root = Builder::new()
+ .prefix("test_same_with_capacity")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let mut manager = Manager::singleton().write().unwrap();
+
+ let p = root.path();
+ assert!(manager.get(p).expect("success").is_none());
+
+ let created_arc = manager
+ .get_or_create_with_capacity(p, 10, Rkv::with_capacity::<Lmdb>)
+ .expect("created");
+ let fetched_arc = manager.get(p).expect("success").expect("existed");
+ assert!(Arc::ptr_eq(&created_arc, &fetched_arc));
+}
+
+/// Test that the manager will return the same Rkv instance each time for each path.
+#[test]
+fn test_same_with_capacity_safe() {
+ type Manager = rkv::Manager<SafeModeEnvironment>;
+
+ let root = Builder::new()
+ .prefix("test_same_with_capacity_safe")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let mut manager = Manager::singleton().write().unwrap();
+
+ let p = root.path();
+ assert!(manager.get(p).expect("success").is_none());
+
+ let created_arc = manager
+ .get_or_create_with_capacity(p, 10, Rkv::with_capacity::<SafeMode>)
+ .expect("created");
+ let fetched_arc = manager.get(p).expect("success").expect("existed");
+ assert!(Arc::ptr_eq(&created_arc, &fetched_arc));
+}
+
+/// Some storage drivers are able to discard when the database is corrupted at runtime.
+/// Test how these managers can discard corrupted databases and re-open.
+#[test]
+fn test_safe_mode_corrupt_while_open_1() {
+ type Manager = rkv::Manager<SafeModeEnvironment>;
+
+ let root = Builder::new()
+ .prefix("test_safe_mode_corrupt_while_open_1")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ // Create environment.
+ let mut manager = Manager::singleton().write().unwrap();
+ let shared_env = manager
+ .get_or_create(root.path(), Rkv::new::<SafeMode>)
+ .expect("created");
+ let env = shared_env.read().unwrap();
+
+ // Write some data.
+ let store = env
+ .open_single("store", StoreOptions::create())
+ .expect("opened");
+ let mut writer = env.write().expect("writer");
+ store
+ .put(&mut writer, "foo", &Value::I64(1234))
+ .expect("wrote");
+ store
+ .put(&mut writer, "bar", &Value::Bool(true))
+ .expect("wrote");
+ store
+ .put(&mut writer, "baz", &Value::Str("héllo, yöu"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+ env.sync(true).expect("synced");
+
+ // Verify it was flushed to disk.
+ let mut safebin = root.path().to_path_buf();
+ safebin.push("data.safe.bin");
+ assert!(safebin.exists());
+
+ // Oops, corruption.
+ fs::write(&safebin, "bogus").expect("dbfile corrupted");
+
+ // Close everything.
+ drop(env);
+ drop(shared_env);
+ manager
+ .try_close(root.path(), CloseOptions::default())
+ .expect("closed without deleting");
+ assert!(manager.get(root.path()).expect("success").is_none());
+
+ // Recreating environment fails.
+ manager
+ .get_or_create(root.path(), Rkv::new::<SafeMode>)
+ .expect_err("not created");
+ assert!(manager.get(root.path()).expect("success").is_none());
+
+ // But we can use a builder and pass `discard_if_corrupted` to deal with it.
+ let mut builder = Rkv::environment_builder::<SafeMode>();
+ builder.set_discard_if_corrupted(true);
+ manager
+ .get_or_create_from_builder(root.path(), builder, Rkv::from_builder::<SafeMode>)
+ .expect("created");
+ assert!(manager.get(root.path()).expect("success").is_some());
+}
+
+/// Some storage drivers are able to recover when the database is corrupted at runtime.
+/// Test how these managers can recover corrupted databases while open.
+#[test]
+fn test_safe_mode_corrupt_while_open_2() {
+ type Manager = rkv::Manager<SafeModeEnvironment>;
+
+ let root = Builder::new()
+ .prefix("test_safe_mode_corrupt_while_open_2")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ // Create environment.
+ let mut manager = Manager::singleton().write().unwrap();
+ let shared_env = manager
+ .get_or_create(root.path(), Rkv::new::<SafeMode>)
+ .expect("created");
+ let env = shared_env.read().unwrap();
+
+ // Write some data.
+ let store = env
+ .open_single("store", StoreOptions::create())
+ .expect("opened");
+ let mut writer = env.write().expect("writer");
+ store
+ .put(&mut writer, "foo", &Value::I64(1234))
+ .expect("wrote");
+ store
+ .put(&mut writer, "bar", &Value::Bool(true))
+ .expect("wrote");
+ store
+ .put(&mut writer, "baz", &Value::Str("héllo, yöu"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+ env.sync(true).expect("synced");
+
+ // Verify it was flushed to disk.
+ let mut safebin = root.path().to_path_buf();
+ safebin.push("data.safe.bin");
+ assert!(safebin.exists());
+
+ // Oops, corruption.
+ fs::write(&safebin, "bogus").expect("dbfile corrupted");
+
+ // Reading still works. Magic.
+ let store = env
+ .open_single("store", StoreOptions::default())
+ .expect("opened");
+ let reader = env.read().expect("reader");
+ assert_eq!(
+ store.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ store.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ store.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ reader.abort();
+
+ // Writing still works, dbfile will be un-corrupted.
+ let store = env
+ .open_single("store", StoreOptions::default())
+ .expect("opened");
+ let mut writer = env.write().expect("writer");
+ store
+ .put(&mut writer, "foo2", &Value::I64(5678))
+ .expect("wrote");
+ store
+ .put(&mut writer, "bar2", &Value::Bool(false))
+ .expect("wrote");
+ store
+ .put(&mut writer, "baz2", &Value::Str("byé, yöu"))
+ .expect("wrote");
+ writer.commit().expect("committed");
+ env.sync(true).expect("synced");
+
+ // Close everything.
+ drop(env);
+ drop(shared_env);
+ manager
+ .try_close(root.path(), CloseOptions::default())
+ .expect("closed without deleting");
+ assert!(manager.get(root.path()).expect("success").is_none());
+
+ // Recreate environment.
+ let shared_env = manager
+ .get_or_create(root.path(), Rkv::new::<SafeMode>)
+ .expect("created");
+ let env = shared_env.read().unwrap();
+
+ // Verify that the dbfile is not corrupted.
+ let store = env
+ .open_single("store", StoreOptions::default())
+ .expect("opened");
+ let reader = env.read().expect("reader");
+ assert_eq!(
+ store.get(&reader, "foo").expect("read"),
+ Some(Value::I64(1234))
+ );
+ assert_eq!(
+ store.get(&reader, "bar").expect("read"),
+ Some(Value::Bool(true))
+ );
+ assert_eq!(
+ store.get(&reader, "baz").expect("read"),
+ Some(Value::Str("héllo, yöu"))
+ );
+ assert_eq!(
+ store.get(&reader, "foo2").expect("read"),
+ Some(Value::I64(5678))
+ );
+ assert_eq!(
+ store.get(&reader, "bar2").expect("read"),
+ Some(Value::Bool(false))
+ );
+ assert_eq!(
+ store.get(&reader, "baz2").expect("read"),
+ Some(Value::Str("byé, yöu"))
+ );
+}
diff --git a/third_party/rust/rkv/tests/multi-integer-store.rs b/third_party/rust/rkv/tests/multi-integer-store.rs
new file mode 100644
index 0000000000..bce0060288
--- /dev/null
+++ b/third_party/rust/rkv/tests/multi-integer-store.rs
@@ -0,0 +1,122 @@
+// Copyright 2018 Mozilla
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+// this file except in compliance with the License. You may obtain a copy of the
+// License at http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software distributed
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations under the License.
+#![cfg(all(feature = "db-dup-sort", feature = "db-int-key"))]
+#![allow(clippy::many_single_char_names)]
+
+use std::fs;
+
+use serde_derive::Serialize;
+use tempfile::Builder;
+
+use rkv::{backend::SafeMode, PrimitiveInt, Rkv, StoreOptions, Value};
+
+#[test]
+fn test_multi_integer_keys() {
+ let root = Builder::new()
+ .prefix("test_integer_keys")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let s = k
+ .open_multi_integer("s", StoreOptions::create())
+ .expect("open");
+
+ macro_rules! test_integer_keys {
+ ($store:expr, $key:expr) => {{
+ let mut writer = k.write().expect("writer");
+
+ $store
+ .put(&mut writer, $key, &Value::Str("hello1"))
+ .expect("write");
+ $store
+ .put(&mut writer, $key, &Value::Str("hello2"))
+ .expect("write");
+ $store
+ .put(&mut writer, $key, &Value::Str("hello3"))
+ .expect("write");
+ let vals = $store
+ .get(&writer, $key)
+ .expect("read")
+ .map(|result| result.expect("ok"))
+ .map(|(_, v)| v)
+ .collect::<Vec<Value>>();
+ assert_eq!(
+ vals,
+ vec![
+ Value::Str("hello1"),
+ Value::Str("hello2"),
+ Value::Str("hello3")
+ ]
+ );
+ writer.commit().expect("committed");
+
+ let reader = k.read().expect("reader");
+ let vals = $store
+ .get(&reader, $key)
+ .expect("read")
+ .map(|result| result.expect("ok"))
+ .map(|(_, v)| v)
+ .collect::<Vec<Value>>();
+ assert_eq!(
+ vals,
+ vec![
+ Value::Str("hello1"),
+ Value::Str("hello2"),
+ Value::Str("hello3")
+ ]
+ );
+ }};
+ }
+
+ // The integer module provides only the u32 integer key variant
+ // of IntegerStore, so we can use it without further ado.
+ test_integer_keys!(s, std::u32::MIN);
+ test_integer_keys!(s, std::u32::MAX);
+
+ // If you want to use another integer key variant, you need to implement
+ // a newtype, implement PrimitiveInt, and implement or derive Serialize
+ // for it. Here we do so for the i32 type.
+
+ // DANGER! Doing this enables you to open a store with multiple,
+ // different integer key types, which may result in unexpected behavior.
+ // Make sure you know what you're doing!
+
+ let t = k
+ .open_multi_integer("s", StoreOptions::create())
+ .expect("open");
+
+ #[derive(Serialize)]
+ struct I32(i32);
+ impl PrimitiveInt for I32 {}
+ test_integer_keys!(t, I32(std::i32::MIN));
+ test_integer_keys!(t, I32(std::i32::MAX));
+
+ let u = k
+ .open_multi_integer("s", StoreOptions::create())
+ .expect("open");
+
+ #[derive(Serialize)]
+ struct U16(u16);
+ impl PrimitiveInt for U16 {}
+ test_integer_keys!(u, U16(std::u16::MIN));
+ test_integer_keys!(u, U16(std::u16::MAX));
+
+ let v = k
+ .open_multi_integer("s", StoreOptions::create())
+ .expect("open");
+
+ #[derive(Serialize)]
+ struct U64(u64);
+ impl PrimitiveInt for U64 {}
+ test_integer_keys!(v, U64(std::u64::MIN));
+ test_integer_keys!(v, U64(std::u64::MAX));
+}
diff --git a/third_party/rust/rkv/tests/test_txn.rs b/third_party/rust/rkv/tests/test_txn.rs
new file mode 100644
index 0000000000..1e8fdfb416
--- /dev/null
+++ b/third_party/rust/rkv/tests/test_txn.rs
@@ -0,0 +1,131 @@
+// Copyright 2018-2019 Mozilla
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+// this file except in compliance with the License. You may obtain a copy of the
+// License at http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software distributed
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations under the License.
+#![cfg(feature = "db-dup-sort")]
+
+use std::fs;
+
+use tempfile::Builder;
+
+use rkv::{
+ backend::{SafeMode, SafeModeDatabase, SafeModeRoCursor, SafeModeRwTransaction},
+ Readable, Rkv, StoreOptions, Value, Writer,
+};
+
+/// Consider a struct like this:
+/// struct Sample {
+/// id: u64,
+/// value: String,
+/// date: String,
+/// }
+/// We would like to index all of the fields so that we can search for the struct not only
+/// by ID but also by value and date. When we index the fields individually in their own
+/// tables, it is important that we run all operations within a single transaction to
+/// ensure coherence of the indices.
+/// This test features helper functions for reading and writing the parts of the struct.
+/// Note that the reader functions take `Readable` because they might run within a Read
+/// Transaction or a Write Transaction. The test demonstrates fetching values via both.
+
+type SingleStore = rkv::SingleStore<SafeModeDatabase>;
+type MultiStore = rkv::MultiStore<SafeModeDatabase>;
+
+#[test]
+fn read_many() {
+ let root = Builder::new()
+ .prefix("test_txns")
+ .tempdir()
+ .expect("tempdir");
+ fs::create_dir_all(root.path()).expect("dir created");
+ let k = Rkv::new::<SafeMode>(root.path()).expect("new succeeded");
+ let samplestore = k.open_single("s", StoreOptions::create()).expect("open");
+ let datestore = k.open_multi("m", StoreOptions::create()).expect("open");
+ let valuestore = k.open_multi("m", StoreOptions::create()).expect("open");
+
+ {
+ let mut writer = k.write().expect("env write lock");
+
+ for id in 0..30_u64 {
+ let value = format!("value{id}");
+ let date = format!("2019-06-{id}");
+ put_id_field(&mut writer, datestore, &date, id);
+ put_id_field(&mut writer, valuestore, &value, id);
+ put_sample(&mut writer, samplestore, id, &value);
+ }
+
+ // now we read in the same transaction
+ for id in 0..30_u64 {
+ let value = format!("value{id}");
+ let date = format!("2019-06-{id}");
+ let ids = get_ids_by_field(&writer, datestore, &date);
+ let ids2 = get_ids_by_field(&writer, valuestore, &value);
+ let samples = get_samples(&writer, samplestore, &ids);
+ let samples2 = get_samples(&writer, samplestore, &ids2);
+ println!("{samples:?}, {samples2:?}");
+ }
+ }
+
+ {
+ let reader = k.read().expect("env read lock");
+ for id in 0..30_u64 {
+ let value = format!("value{id}");
+ let date = format!("2019-06-{id}");
+ let ids = get_ids_by_field(&reader, datestore, &date);
+ let ids2 = get_ids_by_field(&reader, valuestore, &value);
+ let samples = get_samples(&reader, samplestore, &ids);
+ let samples2 = get_samples(&reader, samplestore, &ids2);
+ println!("{samples:?}, {samples2:?}");
+ }
+ }
+}
+
+fn get_ids_by_field<'t, T>(txn: &'t T, store: MultiStore, field: &'t str) -> Vec<u64>
+where
+ T: Readable<'t, Database = SafeModeDatabase, RoCursor = SafeModeRoCursor<'t>>,
+{
+ store
+ .get(txn, field)
+ .expect("get iterator")
+ .map(|id| match id.expect("field") {
+ (_, Value::U64(id)) => id,
+ _ => panic!("getting value in iter"),
+ })
+ .collect::<Vec<u64>>()
+}
+
+fn get_samples<'t, T>(txn: &'t T, samplestore: SingleStore, ids: &[u64]) -> Vec<String>
+where
+ T: Readable<'t, Database = SafeModeDatabase, RoCursor = SafeModeRoCursor<'t>>,
+{
+ ids.iter()
+ .map(|id| {
+ let bytes = id.to_be_bytes();
+ match samplestore.get(txn, &bytes).expect("fetch sample") {
+ Some(Value::Str(sample)) => String::from(sample),
+ Some(_) => panic!("wrong type"),
+ None => panic!("no sample for this id!"),
+ }
+ })
+ .collect::<Vec<String>>()
+}
+
+fn put_sample(
+ txn: &mut Writer<SafeModeRwTransaction>,
+ samplestore: SingleStore,
+ id: u64,
+ value: &str,
+) {
+ let idbytes = id.to_be_bytes();
+ samplestore
+ .put(txn, &idbytes, &Value::Str(value))
+ .expect("put id");
+}
+
+fn put_id_field(txn: &mut Writer<SafeModeRwTransaction>, store: MultiStore, field: &str, id: u64) {
+ store.put(txn, field, &Value::U64(id)).expect("put id");
+}