diff --git a/convert-to-heed3.sh b/convert-to-heed3.sh index 00738969..6260b815 100755 --- a/convert-to-heed3.sh +++ b/convert-to-heed3.sh @@ -19,7 +19,7 @@ else fi # ...and replaces the `heed::` string by the `heed3::` one. -for file in $(find heed/src -type f -name "*.rs"); do +for file in $(find heed/src -type f -name "*.rs") examples/prev-snapshot.rs; do if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' 's/heed::/heed3::/g' "$file" else @@ -37,6 +37,5 @@ git diff -q --exit-code || git commit -am 'remove-me: heed3 changes generated by git config --local --unset user.email git config --local --unset user.name - echo "Heed3 crate setup completed successfully. Configurations for the heed crate have been copied and modified." echo "A commit (starting with remove-me) has been generated and must be deleted before merging into the main branch." diff --git a/examples/prev-snapshot.rs b/examples/prev-snapshot.rs new file mode 100644 index 00000000..79b03bdd --- /dev/null +++ b/examples/prev-snapshot.rs @@ -0,0 +1,85 @@ +use std::error::Error; + +use heed::types::*; +use heed::{Database, EnvFlags, EnvOpenOptions}; + +// In this test we are checking that we can move to a previous environement snapshot. +fn main() -> Result<(), Box> { + let env_path = tempfile::tempdir()?; + + let env = unsafe { + EnvOpenOptions::new() + .map_size(10 * 1024 * 1024) // 10MB + .max_dbs(3) + .open(&env_path)? + }; + + let mut wtxn = env.write_txn()?; + let db: Database = env.create_database(&mut wtxn, None)?; + + // We fill the db database with entries. + db.put(&mut wtxn, "I am here", "to test things")?; + db.put(&mut wtxn, "I am here too", "for the same purpose")?; + + wtxn.commit()?; + + env.prepare_for_closing().wait(); + + // We can get the env state from before the last commit + // and therefore see an empty env. + let env = unsafe { + EnvOpenOptions::new() + .map_size(10 * 1024 * 1024) // 10MB + .max_dbs(3) + .flags(EnvFlags::PREV_SNAPSHOT) + .open(&env_path)? + }; + + let mut wtxn = env.write_txn()?; + let db: Database = env.create_database(&mut wtxn, None)?; + + assert!(db.is_empty(&wtxn)?); + + wtxn.abort(); + env.prepare_for_closing().wait(); + + // However, if we don't commit we can still get + // back the latest version of the env. + let env = unsafe { + EnvOpenOptions::new() + .map_size(10 * 1024 * 1024) // 10MB + .max_dbs(3) + .open(&env_path)? + }; + + let mut wtxn = env.write_txn()?; + let db: Database = env.create_database(&mut wtxn, None)?; + + assert_eq!(db.get(&wtxn, "I am here")?, Some("to test things")); + assert_eq!(db.get(&wtxn, "I am here too")?, Some("for the same purpose")); + + // And write new stuff in the env. + db.put(&mut wtxn, "I will fade away", "I am so sad")?; + + wtxn.commit()?; + env.prepare_for_closing().wait(); + + // Once again we can get back the previous version + // of the env and see some entries disappear. + let env = unsafe { + EnvOpenOptions::new() + .map_size(10 * 1024 * 1024) // 10MB + .max_dbs(3) + .flags(EnvFlags::PREV_SNAPSHOT) + .open(&env_path)? + }; + + let rtxn = env.read_txn()?; + let db: Database = env.open_database(&rtxn, None)?.unwrap(); + + assert_eq!(db.get(&rtxn, "I am here")?, Some("to test things")); + assert_eq!(db.get(&rtxn, "I am here too")?, Some("for the same purpose")); + assert_eq!(db.get(&rtxn, "I will fade away")?, None); + + Ok(()) +} diff --git a/heed/Cargo.toml b/heed/Cargo.toml index 5dd0f87c..d1f7e7f9 100644 --- a/heed/Cargo.toml +++ b/heed/Cargo.toml @@ -131,6 +131,10 @@ path = "../examples/multi-env.rs" name = "nested" path = "../examples/nested.rs" +[[example]] +name = "prev-snapshot" +path = "../examples/prev-snapshot.rs" + [[example]] name = "rmp-serde" path = "../examples/rmp-serde.rs" diff --git a/heed/src/mdb/lmdb_ffi.rs b/heed/src/mdb/lmdb_ffi.rs index 1f68da0e..4c44cdc1 100644 --- a/heed/src/mdb/lmdb_ffi.rs +++ b/heed/src/mdb/lmdb_ffi.rs @@ -6,9 +6,9 @@ pub use ffi::{ mdb_env_get_fd, mdb_env_get_flags, mdb_env_get_maxkeysize, mdb_env_info, mdb_env_open, mdb_env_set_flags, mdb_env_set_mapsize, mdb_env_set_maxdbs, mdb_env_set_maxreaders, mdb_env_stat, mdb_env_sync, mdb_filehandle_t, mdb_get, mdb_put, mdb_reader_check, - mdb_set_compare, mdb_stat, mdb_txn_abort, mdb_txn_begin, mdb_txn_commit, mdb_version, - MDB_cursor, MDB_dbi, MDB_env, MDB_stat, MDB_txn, MDB_val, MDB_CP_COMPACT, MDB_CURRENT, - MDB_RDONLY, MDB_RESERVE, + mdb_set_compare, mdb_stat, mdb_txn_abort, mdb_txn_begin, mdb_txn_commit, mdb_txn_id, + mdb_version, MDB_cursor, MDB_dbi, MDB_env, MDB_stat, MDB_txn, MDB_val, MDB_CP_COMPACT, + MDB_CURRENT, MDB_RDONLY, MDB_RESERVE, }; #[cfg(master3)] pub use ffi::{mdb_env_set_encrypt, MDB_enc_func}; diff --git a/heed/src/mdb/lmdb_flags.rs b/heed/src/mdb/lmdb_flags.rs index 091f91ed..0d0e7733 100644 --- a/heed/src/mdb/lmdb_flags.rs +++ b/heed/src/mdb/lmdb_flags.rs @@ -19,6 +19,8 @@ bitflags! { const NO_SUB_DIR = ffi::MDB_NOSUBDIR; /// Don't fsync after commit. const NO_SYNC = ffi::MDB_NOSYNC; + /// Open the previous transaction. + const PREV_SNAPSHOT = ffi::MDB_PREVSNAPSHOT; /// Read only. const READ_ONLY = ffi::MDB_RDONLY; /// Don't fsync metapage after commit. diff --git a/heed/src/txn.rs b/heed/src/txn.rs index 829117c0..6ac2210d 100644 --- a/heed/src/txn.rs +++ b/heed/src/txn.rs @@ -88,6 +88,15 @@ impl<'e, T> RoTxn<'e, T> { self.env.env_mut_ptr() } + /// Return the transaction's ID. + /// + /// This returns the identifier associated with this transaction. For a + /// [`RoTxn`], this corresponds to the snapshot being read; + /// concurrent readers will frequently have the same transaction ID. + pub fn id(&self) -> usize { + unsafe { ffi::mdb_txn_id(self.txn.unwrap().as_ptr()) } + } + /// Commit a read transaction. /// /// Synchronizing some [`Env`] metadata with the global handle. diff --git a/heed3/Cargo.toml b/heed3/Cargo.toml index e0b1b3e2..01f768fb 100644 --- a/heed3/Cargo.toml +++ b/heed3/Cargo.toml @@ -106,6 +106,10 @@ longer-keys = ["lmdb-master3-sys/longer-keys"] # Examples are located outside the standard heed/examples directory to prevent # conflicts between heed3 and heed examples when working on both crates. +[[example]] +name = "prev-snapshot" +path = "../examples/prev-snapshot.rs" + [[example]] name = "heed3-encrypted" path = "../examples/heed3-encrypted.rs"