// 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) { 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::(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::(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::(); 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::(); builder.set_max_dbs(2); let k = Rkv::from_builder(root.path(), builder).expect("rkv"); check_rkv(&k); } { let mut builder = Rkv::environment_builder::(); 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::(); builder.set_max_dbs(2); let k = Rkv::from_builder(root.path(), builder).expect("rkv"); check_rkv(&k); } { let mut builder = Rkv::environment_builder::(); 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::(); 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::(); 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::(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::(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::(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::(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::(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::(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::(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 // // // 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::(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::(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::(); // 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::(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::(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::(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::(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::(root.path()).expect("new succeeded"); let multistore = k.open_multi("multistore", StoreOptions::create()).expect("opened"); { let mut writer = k.write().expect("writer"); multistore.put(&mut writer, "str1", &Value::Str("str1 foo")).unwrap(); multistore.put(&mut writer, "str1", &Value::Str("str1 bar")).unwrap(); multistore.put(&mut writer, "str2", &Value::Str("str2 foo")).unwrap(); multistore.put(&mut writer, "str2", &Value::Str("str2 bar")).unwrap(); multistore.put(&mut writer, "str3", &Value::Str("str3 foo")).unwrap(); multistore.put(&mut writer, "str3", &Value::Str("str3 bar")).unwrap(); writer.commit().expect("committed"); } { let mut writer = k.write().expect("writer"); multistore.clear(&mut writer).expect("cleared"); writer.commit().expect("committed"); } { let r = k.read().unwrap(); assert_eq!(multistore.get_first(&r, "str1").expect("read"), None); assert_eq!(multistore.get_first(&r, "str2").expect("read"), None); assert_eq!(multistore.get_first(&r, "str3").expect("read"), None); } } #[test] fn test_open_store_for_read() { let root = Builder::new().prefix("test_open_store_for_read").tempdir().expect("tempdir"); fs::create_dir_all(root.path()).expect("dir created"); let k = Rkv::new::(root.path()).expect("new succeeded"); // First create the store, and start a write transaction on it. let sk = k.open_single("sk", StoreOptions::create()).expect("opened"); let mut writer = k.write().expect("writer"); sk.put(&mut writer, "foo", &Value::Str("bar")).expect("write"); // 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::(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::(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::(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::(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, writer: &Writer) -> Option { 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::(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, writer: &Writer) -> Option { 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::(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::(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::(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 { let mut dst = vec![0; 2 * src.len()]; LittleEndian::write_u16_into(src, &mut dst); dst } fn u8_to_u16(src: &[u8]) -> Vec { 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::(); 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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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"); } }