diff --git a/src/transaction.rs b/src/transaction.rs index 5bc60c2..2480d2e 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -778,10 +778,7 @@ impl Drop for Transaction { #[cfg(test)] mod tests { use bytes::Bytes; - use rand::rngs::StdRng; - use rand::{Rng, SeedableRng}; use std::collections::HashSet; - use std::mem::size_of; use tempdir::TempDir; use super::*; @@ -806,2801 +803,2872 @@ mod tests { ) } - #[test] - fn basic_transaction() { - let (store, temp_dir) = create_store(false); + // Basic transaction tests + mod basic_transaction_tests { + use super::*; - // Define key-value pairs for the test - let key1 = Bytes::from("foo1"); - let key2 = Bytes::from("foo2"); - let value1 = Bytes::from("baz"); - let value2 = Bytes::from("bar"); + #[test] + fn basic_transaction() { + let (store, temp_dir) = create_store(false); - { - // Start a new read-write transaction (txn1) - let mut txn1 = store.begin().unwrap(); - txn1.set(&key1, &value1).unwrap(); - txn1.set(&key2, &value1).unwrap(); - txn1.commit().unwrap(); + // Define key-value pairs for the test + let key1 = Bytes::from("foo1"); + let key2 = Bytes::from("foo2"); + let value1 = Bytes::from("baz"); + let value2 = Bytes::from("bar"); + + { + // Start a new read-write transaction (txn1) + let mut txn1 = store.begin().unwrap(); + txn1.set(&key1, &value1).unwrap(); + txn1.set(&key2, &value1).unwrap(); + txn1.commit().unwrap(); + } + + { + // Start a read-only transaction (txn3) + let mut txn3 = store.begin().unwrap(); + let val = txn3.get(&key1).unwrap().unwrap(); + assert_eq!(val, value1.as_ref()); + } + + { + // Start another read-write transaction (txn2) + let mut txn2 = store.begin().unwrap(); + txn2.set(&key1, &value2).unwrap(); + txn2.set(&key2, &value2).unwrap(); + txn2.commit().unwrap(); + } + + // Drop the store to simulate closing it + store.close().unwrap(); + + // Create a new Core instance with VariableSizeKey after dropping the previous one + let mut opts = Options::new(); + opts.dir = temp_dir.path().to_path_buf(); + let store = Store::new(opts).expect("should create store"); + + // Start a read-only transaction (txn4) + let mut txn4 = store.begin().unwrap(); + let val = txn4.get(&key1).unwrap().unwrap(); + + // Assert that the value retrieved in txn4 matches value2 + assert_eq!(val, value2.as_ref()); } - { - // Start a read-only transaction (txn3) - let mut txn3 = store.begin().unwrap(); - let val = txn3.get(&key1).unwrap().unwrap(); - assert_eq!(val, value1.as_ref()); + #[test] + fn transaction_delete_scan() { + let (store, _) = create_store(false); + + // Define key-value pairs for the test + let key1 = Bytes::from("k1"); + let value1 = Bytes::from("baz"); + + { + // Start a new read-write transaction (txn1) + let mut txn1 = store.begin().unwrap(); + txn1.set(&key1, &value1).unwrap(); + txn1.set(&key1, &value1).unwrap(); + txn1.commit().unwrap(); + } + + { + // Start a read-only transaction (txn) + let mut txn = store.begin().unwrap(); + txn.delete(&key1).unwrap(); + txn.commit().unwrap(); + } + + { + // Start another read-write transaction (txn) + let mut txn = store.begin().unwrap(); + assert!(txn.get(&key1).unwrap().is_none()); + } + + { + let range = "k1".as_bytes()..="k3".as_bytes(); + let mut txn = store.begin().unwrap(); + let results: Vec<_> = txn.scan(range, None).collect(); + assert_eq!(results.len(), 0); + } } - { - // Start another read-write transaction (txn2) - let mut txn2 = store.begin().unwrap(); - txn2.set(&key1, &value2).unwrap(); - txn2.set(&key2, &value2).unwrap(); - txn2.commit().unwrap(); + #[test] + fn ryow() { + let temp_dir = create_temp_directory(); + let mut opts = Options::new(); + opts.dir = temp_dir.path().to_path_buf(); + + let store = Store::new(opts.clone()).expect("should create store"); + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let key3 = Bytes::from("k3"); + let value1 = Bytes::from("v1"); + let value2 = Bytes::from("v2"); + + // Set a key, delete it and read it in the same transaction. Should return None. + { + // Start a new read-write transaction (txn1) + let mut txn1 = store.begin().unwrap(); + txn1.set(&key1, &value1).unwrap(); + txn1.delete(&key1).unwrap(); + let res = txn1.get(&key1).unwrap(); + assert!(res.is_none()); + txn1.commit().unwrap(); + } + + { + let mut txn = store.begin().unwrap(); + txn.set(&key1, &value1).unwrap(); + txn.commit().unwrap(); + } + + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.set(&key1, &value2).unwrap(); + assert_eq!(txn.get(&key1).unwrap().unwrap(), value2.as_ref()); + assert!(txn.get(&key3).unwrap().is_none()); + txn.set(&key2, &value1).unwrap(); + assert_eq!(txn.get(&key2).unwrap().unwrap(), value1.as_ref()); + txn.commit().unwrap(); + } } - // Drop the store to simulate closing it - store.close().unwrap(); + #[test] + fn transaction_delete_from_index() { + let (store, temp_dir) = create_store(false); - // Create a new Core instance with VariableSizeKey after dropping the previous one - let mut opts = Options::new(); - opts.dir = temp_dir.path().to_path_buf(); - let store = Store::new(opts).expect("should create store"); + // Define key-value pairs for the test + let key1 = Bytes::from("foo1"); + let value = Bytes::from("baz"); + let key2 = Bytes::from("foo2"); - // Start a read-only transaction (txn4) - let mut txn4 = store.begin().unwrap(); - let val = txn4.get(&key1).unwrap().unwrap(); + { + // Start a new read-write transaction (txn1) + let mut txn1 = store.begin().unwrap(); + txn1.set(&key1, &value).unwrap(); + txn1.set(&key2, &value).unwrap(); + txn1.commit().unwrap(); + } - // Assert that the value retrieved in txn4 matches value2 - assert_eq!(val, value2.as_ref()); - } + { + // Start another read-write transaction (txn2) + let mut txn2 = store.begin().unwrap(); + txn2.delete(&key1).unwrap(); + txn2.commit().unwrap(); + } - #[test] - fn transaction_delete_scan() { - let (store, _) = create_store(false); + { + // Start a read-only transaction (txn3) + let mut txn3 = store.begin().unwrap(); + let val = txn3.get(&key1).unwrap(); + assert!(val.is_none()); + let val = txn3.get(&key2).unwrap().unwrap(); + assert_eq!(val, value.as_ref()); + } - // Define key-value pairs for the test - let key1 = Bytes::from("k1"); - let value1 = Bytes::from("baz"); + // Drop the store to simulate closing it + store.close().unwrap(); - { - // Start a new read-write transaction (txn1) - let mut txn1 = store.begin().unwrap(); - txn1.set(&key1, &value1).unwrap(); - txn1.set(&key1, &value1).unwrap(); - txn1.commit().unwrap(); - } + // sleep for a while to ensure the store is closed + std::thread::sleep(std::time::Duration::from_millis(10)); - { - // Start a read-only transaction (txn) - let mut txn = store.begin().unwrap(); - txn.delete(&key1).unwrap(); - txn.commit().unwrap(); - } + // Create a new Core instance with VariableSizeKey after dropping the previous one + let mut opts = Options::new(); + opts.dir = temp_dir.path().to_path_buf(); + let store = Store::new(opts).expect("should create store"); - { - // Start another read-write transaction (txn) - let mut txn = store.begin().unwrap(); - assert!(txn.get(&key1).unwrap().is_none()); + // Start a read-only transaction (txn4) + let mut txn4 = store.begin().unwrap(); + let val = txn4.get(&key1).unwrap(); + assert!(val.is_none()); + let val = txn4.get(&key2).unwrap().unwrap(); + assert_eq!(val, value.as_ref()); } - { - let range = "k1".as_bytes()..="k3".as_bytes(); - let mut txn = store.begin().unwrap(); - let results: Vec<_> = txn.scan(range, None).collect(); - assert_eq!(results.len(), 0); - } - } + #[test] + fn test_insert_clear_read_key() { + let (store, _) = create_store(false); - #[test] - fn keys_with_tombstones() { - for is_ssi in [false, true] { - let (store, _) = create_store(is_ssi); + // Key-value pair for the test + let key = Bytes::from("test_key"); + let value1 = Bytes::from("test_value1"); + let value2 = Bytes::from("test_value2"); - let key = Bytes::from("k"); - let value = Bytes::from("v"); + // Insert key-value pair in a new transaction + { + let mut txn = store.begin().unwrap(); + txn.set(&key, &value1).unwrap(); + txn.commit().unwrap(); + } - // First, insert the key. - let mut txn1 = store.begin().unwrap(); - txn1.set(&key, &value).unwrap(); - txn1.commit().unwrap(); + { + let mut txn = store.begin().unwrap(); + txn.set(&key, &value2).unwrap(); + txn.commit().unwrap(); + } - // Then, soft-delete it. - let mut txn2 = store.begin().unwrap(); - txn2.soft_delete(&key).unwrap(); - txn2.commit().unwrap(); + // Clear the key in a separate transaction + { + let mut txn = store.begin().unwrap(); + txn.soft_delete(&key).unwrap(); + txn.commit().unwrap(); + } - // keys_with_tombstones() should still return `k` - // despite it being soft-deleted. - let range = "k".as_bytes()..="k".as_bytes(); - let txn3 = store.begin().unwrap(); - let results: Vec<_> = txn3.keys_with_tombstones(range, None).collect(); - assert_eq!(results, vec![b"k"]); + // Read the key in a new transaction to verify it does not exist + { + let mut txn = store.begin().unwrap(); + assert!(txn.get(&key).unwrap().is_none()); + } } } - fn mvcc_tests(is_ssi: bool) { - let (store, _) = create_store(is_ssi); + // MVCC and isolation level tests + mod mvcc_tests { + use super::*; - let key1 = Bytes::from("key1"); - let key2 = Bytes::from("key2"); - let value1 = Bytes::from("baz"); - let value2 = Bytes::from("bar"); + fn mvcc_tests(is_ssi: bool) { + let (store, _) = create_store(is_ssi); - // no conflict - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + let key1 = Bytes::from("key1"); + let key2 = Bytes::from("key2"); + let value1 = Bytes::from("baz"); + let value2 = Bytes::from("bar"); - txn1.set(&key1, &value1).unwrap(); - txn1.commit().unwrap(); + // no conflict + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); - assert!(txn2.get(&key2).unwrap().is_none()); - txn2.set(&key2, &value2).unwrap(); - txn2.commit().unwrap(); - } + txn1.set(&key1, &value1).unwrap(); + txn1.commit().unwrap(); - // conflict when the read key was updated by another transaction - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + assert!(txn2.get(&key2).unwrap().is_none()); + txn2.set(&key2, &value2).unwrap(); + txn2.commit().unwrap(); + } - txn1.set(&key1, &value1).unwrap(); - txn1.commit().unwrap(); + // conflict when the read key was updated by another transaction + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + + txn1.set(&key1, &value1).unwrap(); + txn1.commit().unwrap(); + + assert!(txn2.get(&key1).is_ok()); + txn2.set(&key1, &value2).unwrap(); + assert!(match txn2.commit() { + Err(err) => { + if !is_ssi { + matches!(err, Error::TransactionWriteConflict) + } else { + matches!(err, Error::TransactionReadConflict) + } + } + _ => false, + }); + } + + // blind writes should not succeed + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); - assert!(txn2.get(&key1).is_ok()); - txn2.set(&key1, &value2).unwrap(); - assert!(match txn2.commit() { - Err(err) => { - if !is_ssi { + txn1.set(&key1, &value1).unwrap(); + txn2.set(&key1, &value2).unwrap(); + + txn1.commit().unwrap(); + assert!(match txn2.commit() { + Err(err) => { matches!(err, Error::TransactionWriteConflict) - } else { - matches!(err, Error::TransactionReadConflict) } - } - _ => false, - }); - } + _ => { + false + } + }); + } - // blind writes should not succeed - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + // conflict when the read key was updated by another transaction + { + let key = Bytes::from("key3"); + + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + + txn1.set(&key, &value1).unwrap(); + txn1.commit().unwrap(); + + assert!(txn2.get(&key).unwrap().is_none()); + txn2.set(&key, &value1).unwrap(); + assert!(match txn2.commit() { + Err(err) => { + if !is_ssi { + matches!(err, Error::TransactionWriteConflict) + } else { + matches!(err, Error::TransactionReadConflict) + } + } + _ => false, + }); + } - txn1.set(&key1, &value1).unwrap(); - txn2.set(&key1, &value2).unwrap(); + // write-skew: read conflict when the read key was deleted by another transaction + { + let key = Bytes::from("key4"); - txn1.commit().unwrap(); - assert!(match txn2.commit() { - Err(err) => { - matches!(err, Error::TransactionWriteConflict) - } - _ => { - false + let mut txn1 = store.begin().unwrap(); + txn1.set(&key, &value1).unwrap(); + txn1.commit().unwrap(); + + let mut txn2 = store.begin().unwrap(); + let mut txn3 = store.begin().unwrap(); + + txn2.delete(&key).unwrap(); + assert!(txn2.commit().is_ok()); + + assert!(txn3.get(&key).is_ok()); + txn3.set(&key, &value2).unwrap(); + let result = txn3.commit(); + if is_ssi { + assert!(matches!(result, Err(Error::TransactionReadConflict))); + } else { + assert!(result.is_ok()); } - }); + } } - // conflict when the read key was updated by another transaction - { - let key = Bytes::from("key3"); + #[test] + fn mvcc_serialized_snapshot_isolation() { + mvcc_tests(true); + } - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + #[test] + fn mvcc_snapshot_isolation() { + mvcc_tests(false); + } - txn1.set(&key, &value1).unwrap(); - txn1.commit().unwrap(); + fn mvcc_with_scan_tests(is_ssi: bool) { + let (store, _) = create_store(is_ssi); - assert!(txn2.get(&key).unwrap().is_none()); - txn2.set(&key, &value1).unwrap(); - assert!(match txn2.commit() { - Err(err) => { - if !is_ssi { - matches!(err, Error::TransactionWriteConflict) - } else { - matches!(err, Error::TransactionReadConflict) + let key1 = Bytes::from("key1"); + let key2 = Bytes::from("key2"); + let key3 = Bytes::from("key3"); + let key4 = Bytes::from("key4"); + let value1 = Bytes::from("value1"); + let value2 = Bytes::from("value2"); + let value3 = Bytes::from("value3"); + let value4 = Bytes::from("value4"); + let value5 = Bytes::from("value5"); + let value6 = Bytes::from("value6"); + + // conflict when scan keys have been updated in another transaction + { + let mut txn1 = store.begin().unwrap(); + + txn1.set(&key1, &value1).unwrap(); + txn1.commit().unwrap(); + + let mut txn2 = store.begin().unwrap(); + let mut txn3 = store.begin().unwrap(); + + txn2.set(&key1, &value4).unwrap(); + txn2.set(&key2, &value2).unwrap(); + txn2.set(&key3, &value3).unwrap(); + txn2.commit().unwrap(); + + let range = "key1".as_bytes()..="key4".as_bytes(); + let results: Vec<_> = txn3.scan(range, None).collect(); + assert_eq!(results.len(), 1); + txn3.set(&key2, &value5).unwrap(); + txn3.set(&key3, &value6).unwrap(); + + assert!(match txn3.commit() { + Err(err) => { + if !is_ssi { + matches!(err, Error::TransactionWriteConflict) + } else { + matches!(err, Error::TransactionReadConflict) + } } - } - _ => false, - }); - } + _ => false, + }); + } - // write-skew: read conflict when the read key was deleted by another transaction - { - let key = Bytes::from("key4"); + // write-skew: read conflict when read keys are deleted by other transaction + { + let mut txn1 = store.begin().unwrap(); - let mut txn1 = store.begin().unwrap(); - txn1.set(&key, &value1).unwrap(); - txn1.commit().unwrap(); + txn1.set(&key4, &value1).unwrap(); + txn1.commit().unwrap(); - let mut txn2 = store.begin().unwrap(); - let mut txn3 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + let mut txn3 = store.begin().unwrap(); - txn2.delete(&key).unwrap(); - assert!(txn2.commit().is_ok()); + txn2.delete(&key4).unwrap(); + txn2.commit().unwrap(); - assert!(txn3.get(&key).is_ok()); - txn3.set(&key, &value2).unwrap(); - let result = txn3.commit(); - if is_ssi { - assert!(matches!(result, Err(Error::TransactionReadConflict))); - } else { - assert!(result.is_ok()); + let range = "key1".as_bytes()..="key5".as_bytes(); + let _: Vec<_> = txn3.scan(range, None).collect(); + txn3.set(&key4, &value2).unwrap(); + let result = txn3.commit(); + if is_ssi { + assert!(matches!(result, Err(Error::TransactionReadConflict))); + } else { + assert!(result.is_ok()); + } } } - } - #[test] - fn mvcc_serialized_snapshot_isolation() { - mvcc_tests(true); - } + #[test] + fn mvcc_serialized_snapshot_isolation_scan() { + mvcc_with_scan_tests(true); + } - #[test] - fn mvcc_snapshot_isolation() { - mvcc_tests(false); - } + #[test] + fn mvcc_snapshot_isolation_scan() { + mvcc_with_scan_tests(false); + } + + #[test] + fn ordered_writes() { + // This test ensures that the real time order of writes + // is preserved within a transaction. + use crate::entry::Record; + use crate::log::Error as LogError; + use crate::log::{MultiSegmentReader, SegmentRef}; + use crate::reader::{Reader, RecordReader}; - #[test] - fn basic_scan_single_key() { - let (store, _) = create_store(false); - // Define key-value pairs for the test - let keys_to_insert = vec![Bytes::from("key1")]; + let k1 = Bytes::from("k1"); + let v1 = Bytes::from("v1"); + let k2 = Bytes::from("k2"); + let v2 = Bytes::from("v2"); + let v3 = Bytes::from("v3"); - for key in &keys_to_insert { + let (store, tmp_dir) = create_store(false); let mut txn = store.begin().unwrap(); - txn.set(key, key).unwrap(); + txn.set(&k1, &v1).unwrap(); + txn.set(&k2, &v2).unwrap(); + txn.set(&k1, &v3).unwrap(); txn.commit().unwrap(); - } - - let range = "key1".as_bytes()..="key3".as_bytes(); + store.close().unwrap(); + + let clog_subdir = tmp_dir.path().join("clog"); + let sr = SegmentRef::read_segments_from_directory(clog_subdir.as_path()).unwrap(); + let reader = MultiSegmentReader::new(sr).unwrap(); + let reader = Reader::new_from(reader); + let mut tx_reader = RecordReader::new(reader); + + // Expect ("k2", "v2") + let mut log_rec = Record::new(); + tx_reader.read_into(&mut log_rec).unwrap(); + assert_eq!(log_rec.key, k2); + assert_eq!(log_rec.value, v2); + + // Expect ("k1", "v3") + let mut log_rec = Record::new(); + tx_reader.read_into(&mut log_rec).unwrap(); + assert_eq!(log_rec.key, k1); + assert_eq!(log_rec.value, v3); + + // Eof + let mut log_rec = Record::new(); + assert!(matches!( + tx_reader.read_into(&mut log_rec), + Err(Error::LogError(LogError::Eof)) + ),); + } + } + + // Scan operation tests + mod scan_tests { + use super::*; + + #[test] + fn basic_scan_single_key() { + let (store, _) = create_store(false); + // Define key-value pairs for the test + let keys_to_insert = vec![Bytes::from("key1")]; + + for key in &keys_to_insert { + let mut txn = store.begin().unwrap(); + txn.set(key, key).unwrap(); + txn.commit().unwrap(); + } - let mut txn = store.begin().unwrap(); - let results: Vec<_> = txn.scan(range, None).collect(); - assert_eq!(results.len(), keys_to_insert.len()); - } + let range = "key1".as_bytes()..="key3".as_bytes(); - #[test] - fn basic_scan_multiple_keys() { - let (store, _) = create_store(false); - // Define key-value pairs for the test - let keys_to_insert = vec![ - Bytes::from("key1"), - Bytes::from("key2"), - Bytes::from("key3"), - Bytes::from("lemon"), - ]; - - for key in &keys_to_insert { let mut txn = store.begin().unwrap(); - txn.set(key, key).unwrap(); - txn.commit().unwrap(); + let results: Vec<_> = txn.scan(range, None).collect(); + assert_eq!(results.len(), keys_to_insert.len()); } - let range = "key1".as_bytes()..="key3".as_bytes(); + #[test] + fn basic_scan_multiple_keys() { + let (store, _) = create_store(false); + // Define key-value pairs for the test + let keys_to_insert = vec![ + Bytes::from("key1"), + Bytes::from("key2"), + Bytes::from("key3"), + Bytes::from("lemon"), + ]; - let mut txn = store.begin().unwrap(); - let results: Vec<_> = txn.scan(range, None).collect(); - assert_eq!(results.len(), 3); - assert_eq!(results[0].as_ref().unwrap().1, keys_to_insert[0]); - assert_eq!(results[1].as_ref().unwrap().1, keys_to_insert[1]); - assert_eq!(results[2].as_ref().unwrap().1, keys_to_insert[2]); - } + for key in &keys_to_insert { + let mut txn = store.begin().unwrap(); + txn.set(key, key).unwrap(); + txn.commit().unwrap(); + } - #[test] - fn scan_multiple_keys_within_single_transaction() { - let (store, _) = create_store(false); - // Define key-value pairs for the test - let keys_to_insert = vec![ - Bytes::from("test1"), - Bytes::from("test2"), - Bytes::from("test3"), - ]; - - let mut txn = store.begin().unwrap(); - for key in &keys_to_insert { - txn.set(key, key).unwrap(); - } - txn.commit().unwrap(); - - let range = "test1".as_bytes()..="test7".as_bytes(); - - let mut txn = store.begin().unwrap(); - let results = txn - .scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(results.len(), 3); - assert_eq!(results[0].0, keys_to_insert[0]); - assert_eq!(results[1].0, keys_to_insert[1]); - assert_eq!(results[2].0, keys_to_insert[2]); - assert_eq!(results[0].1, keys_to_insert[0]); - assert_eq!(results[1].1, keys_to_insert[1]); - assert_eq!(results[2].1, keys_to_insert[2]); - } + let range = "key1".as_bytes()..="key3".as_bytes(); - fn mvcc_with_scan_tests(is_ssi: bool) { - let (store, _) = create_store(is_ssi); - - let key1 = Bytes::from("key1"); - let key2 = Bytes::from("key2"); - let key3 = Bytes::from("key3"); - let key4 = Bytes::from("key4"); - let value1 = Bytes::from("value1"); - let value2 = Bytes::from("value2"); - let value3 = Bytes::from("value3"); - let value4 = Bytes::from("value4"); - let value5 = Bytes::from("value5"); - let value6 = Bytes::from("value6"); - - // conflict when scan keys have been updated in another transaction - { - let mut txn1 = store.begin().unwrap(); + let mut txn = store.begin().unwrap(); + let results: Vec<_> = txn.scan(range, None).collect(); + assert_eq!(results.len(), 3); + assert_eq!(results[0].as_ref().unwrap().1, keys_to_insert[0]); + assert_eq!(results[1].as_ref().unwrap().1, keys_to_insert[1]); + assert_eq!(results[2].as_ref().unwrap().1, keys_to_insert[2]); + } + + #[test] + fn scan_multiple_keys_within_single_transaction() { + let (store, _) = create_store(false); + // Define key-value pairs for the test + let keys_to_insert = vec![ + Bytes::from("test1"), + Bytes::from("test2"), + Bytes::from("test3"), + ]; - txn1.set(&key1, &value1).unwrap(); - txn1.commit().unwrap(); + let mut txn = store.begin().unwrap(); + for key in &keys_to_insert { + txn.set(key, key).unwrap(); + } + txn.commit().unwrap(); - let mut txn2 = store.begin().unwrap(); - let mut txn3 = store.begin().unwrap(); + let range = "test1".as_bytes()..="test7".as_bytes(); - txn2.set(&key1, &value4).unwrap(); - txn2.set(&key2, &value2).unwrap(); - txn2.set(&key3, &value3).unwrap(); - txn2.commit().unwrap(); + let mut txn = store.begin().unwrap(); + let results = txn + .scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(results.len(), 3); + assert_eq!(results[0].0, keys_to_insert[0]); + assert_eq!(results[1].0, keys_to_insert[1]); + assert_eq!(results[2].0, keys_to_insert[2]); + assert_eq!(results[0].1, keys_to_insert[0]); + assert_eq!(results[1].1, keys_to_insert[1]); + assert_eq!(results[2].1, keys_to_insert[2]); + } - let range = "key1".as_bytes()..="key4".as_bytes(); - let results: Vec<_> = txn3.scan(range, None).collect(); - assert_eq!(results.len(), 1); - txn3.set(&key2, &value5).unwrap(); - txn3.set(&key3, &value6).unwrap(); + #[test] + fn empty_scan_should_not_return_an_error() { + let (store, _) = create_store(false); - assert!(match txn3.commit() { - Err(err) => { - if !is_ssi { - matches!(err, Error::TransactionWriteConflict) - } else { - matches!(err, Error::TransactionReadConflict) - } - } - _ => false, - }); - } + let range = "key1".as_bytes()..="key3".as_bytes(); - // write-skew: read conflict when read keys are deleted by other transaction - { - let mut txn1 = store.begin().unwrap(); + let mut txn = store.begin().unwrap(); + let results: Vec<_> = txn.scan(range, None).collect(); + assert_eq!(results.len(), 0); + } - txn1.set(&key4, &value1).unwrap(); - txn1.commit().unwrap(); + #[test] + fn transaction_delete_and_scan_test() { + let (store, _) = create_store(false); - let mut txn2 = store.begin().unwrap(); - let mut txn3 = store.begin().unwrap(); + // Define key-value pairs for the test + let key1 = Bytes::from("k1"); + let value1 = Bytes::from("baz"); - txn2.delete(&key4).unwrap(); - txn2.commit().unwrap(); + { + // Start a new read-write transaction (txn1) + let mut txn1 = store.begin().unwrap(); + txn1.set(&key1, &value1).unwrap(); + txn1.commit().unwrap(); + } - let range = "key1".as_bytes()..="key5".as_bytes(); - let _: Vec<_> = txn3.scan(range, None).collect(); - txn3.set(&key4, &value2).unwrap(); - let result = txn3.commit(); - if is_ssi { - assert!(matches!(result, Err(Error::TransactionReadConflict))); - } else { - assert!(result.is_ok()); + { + // Start a read-only transaction (txn) + let mut txn = store.begin().unwrap(); + txn.get(&key1).unwrap(); + txn.delete(&key1).unwrap(); + let range = "k1".as_bytes()..="k3".as_bytes(); + let results = txn + .scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(results.len(), 0); + txn.commit().unwrap(); + } + { + let range = "k1".as_bytes()..="k3".as_bytes(); + let mut txn = store.begin().unwrap(); + let results = txn + .scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(results.len(), 0); } } - } - #[test] - fn mvcc_serialized_snapshot_isolation_scan() { - mvcc_with_scan_tests(true); - } + #[test] + fn test_scan_includes_entries_before_commit() { + let (store, _) = create_store(false); - #[test] - fn mvcc_snapshot_isolation_scan() { - mvcc_with_scan_tests(false); - } + // Define key-value pairs for the test + let key1 = Bytes::from("key1"); + let key2 = Bytes::from("key2"); + let key3 = Bytes::from("key3"); + let value1 = Bytes::from("value1"); + let value2 = Bytes::from("value2"); + let value3 = Bytes::from("value3"); - #[test] - fn ryow() { - let temp_dir = create_temp_directory(); - let mut opts = Options::new(); - opts.dir = temp_dir.path().to_path_buf(); + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.set(&key1, &value1).unwrap(); + txn.set(&key2, &value2).unwrap(); + txn.set(&key3, &value3).unwrap(); - let store = Store::new(opts.clone()).expect("should create store"); - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let key3 = Bytes::from("k3"); - let value1 = Bytes::from("v1"); - let value2 = Bytes::from("v2"); + // Define the range for the scan + let range = "key1".as_bytes()..="key3".as_bytes(); + let results = txn + .scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); - // Set a key, delete it and read it in the same transaction. Should return None. - { - // Start a new read-write transaction (txn1) - let mut txn1 = store.begin().unwrap(); - txn1.set(&key1, &value1).unwrap(); - txn1.delete(&key1).unwrap(); - let res = txn1.get(&key1).unwrap(); - assert!(res.is_none()); - txn1.commit().unwrap(); + // Verify the results + assert_eq!(results.len(), 3); + assert_eq!(results[0].0, key1); + assert_eq!(results[0].1, value1); + assert_eq!(results[1].0, key2); + assert_eq!(results[1].1, value2); + assert_eq!(results[2].0, key3); + assert_eq!(results[2].1, value3); + } + + #[test] + fn test_keys_api() { + for is_ssi in [false, true] { + let (store, _) = create_store(is_ssi); + + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let key3 = Bytes::from("k3"); + let value = Bytes::from("v"); + + // Insert the keys. + let mut txn1 = store.begin().unwrap(); + txn1.set(&key1, &value).unwrap(); + txn1.set(&key2, &value).unwrap(); + txn1.set(&key3, &value).unwrap(); + txn1.commit().unwrap(); + + // Soft-delete key2. + let mut txn2 = store.begin().unwrap(); + txn2.soft_delete(&key2).unwrap(); + txn2.commit().unwrap(); + + // Test the keys function without a limit. + let range = "k1".as_bytes()..="k3".as_bytes(); + let txn3 = store.begin_with_mode(Mode::ReadOnly).unwrap(); + let results: Vec<_> = txn3.keys(range.clone(), None).collect(); + assert_eq!(results, vec![&b"k1"[..], &b"k3"[..]]); + + // Test the keys function with a limit of 2. + let txn4 = store.begin_with_mode(Mode::ReadOnly).unwrap(); + let limited_results: Vec<_> = txn4.keys(range.clone(), Some(2)).collect(); + assert_eq!(limited_results, vec![&b"k1"[..], &b"k3"[..]]); + + // Test the keys function with a limit of 1. + let txn5 = store.begin_with_mode(Mode::ReadOnly).unwrap(); + let limited_results: Vec<_> = txn5.keys(range, Some(1)).collect(); + assert_eq!(limited_results, vec![&b"k1"[..]]); + } } + } - { + // Hermitage tests (transaction isolation tests) + // The following tests are taken from hermitage (https://github.com/ept/hermitage) + // Specifically, the tests are derived from FoundationDB tests: https://github.com/ept/hermitage/blob/master/foundationdb.md + mod hermitage_tests { + use super::*; + + // Common setup logic for creating a store + fn create_hermitage_store(is_ssi: bool) -> Store { + let (store, _) = create_store(is_ssi); + + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value1 = Bytes::from("v1"); + let value2 = Bytes::from("v2"); + // Start a new read-write transaction (txn) let mut txn = store.begin().unwrap(); txn.set(&key1, &value1).unwrap(); + txn.set(&key2, &value2).unwrap(); txn.commit().unwrap(); + + store } - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.set(&key1, &value2).unwrap(); - assert_eq!(txn.get(&key1).unwrap().unwrap(), value2.as_ref()); - assert!(txn.get(&key3).unwrap().is_none()); - txn.set(&key2, &value1).unwrap(); - assert_eq!(txn.get(&key2).unwrap().unwrap(), value1.as_ref()); - txn.commit().unwrap(); + // G0: Write Cycles (dirty writes) + fn g0_tests(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value3 = Bytes::from("v3"); + let value4 = Bytes::from("v4"); + let value5 = Bytes::from("v5"); + let value6 = Bytes::from("v6"); + + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + + assert!(txn1.get(&key1).is_ok()); + assert!(txn1.get(&key2).is_ok()); + assert!(txn2.get(&key1).is_ok()); + assert!(txn2.get(&key2).is_ok()); + + txn1.set(&key1, &value3).unwrap(); + txn2.set(&key1, &value4).unwrap(); + + txn1.set(&key2, &value5).unwrap(); + + txn1.commit().unwrap(); + + txn2.set(&key2, &value6).unwrap(); + assert!(match txn2.commit() { + Err(err) => { + if !is_ssi { + matches!(err, Error::TransactionWriteConflict) + } else { + matches!(err, Error::TransactionReadConflict) + } + } + _ => false, + }); + } + + { + let mut txn3 = store.begin().unwrap(); + let val1 = txn3.get(&key1).unwrap().unwrap(); + assert_eq!(val1, value3.as_ref()); + let val2 = txn3.get(&key2).unwrap().unwrap(); + assert_eq!(val2, value5.as_ref()); + } } - } - // Common setup logic for creating a store - fn create_hermitage_store(is_ssi: bool) -> Store { - let (store, _) = create_store(is_ssi); - - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value1 = Bytes::from("v1"); - let value2 = Bytes::from("v2"); - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.set(&key1, &value1).unwrap(); - txn.set(&key2, &value2).unwrap(); - txn.commit().unwrap(); - - store - } + #[test] + fn g0() { + g0_tests(false); // snapshot isolation + g0_tests(true); // serializable snapshot isolation + } - // The following tests are taken from hermitage (https://github.com/ept/hermitage) - // Specifically, the tests are derived from FoundationDB tests: https://github.com/ept/hermitage/blob/master/foundationdb.md + // G1a: Aborted Reads (dirty reads, cascaded aborts) + fn g1a_tests(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value1 = Bytes::from("v1"); + let value2 = Bytes::from("v2"); + let value3 = Bytes::from("v3"); - // G0: Write Cycles (dirty writes) - fn g0_tests(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value3 = Bytes::from("v3"); - let value4 = Bytes::from("v4"); - let value5 = Bytes::from("v5"); - let value6 = Bytes::from("v6"); + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + assert!(txn1.get(&key1).is_ok()); - assert!(txn1.get(&key1).is_ok()); - assert!(txn1.get(&key2).is_ok()); - assert!(txn2.get(&key1).is_ok()); - assert!(txn2.get(&key2).is_ok()); + txn1.set(&key1, &value3).unwrap(); - txn1.set(&key1, &value3).unwrap(); - txn2.set(&key1, &value4).unwrap(); + let range = "k1".as_bytes()..="k3".as_bytes(); + let res = txn2 + .scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].1, value1); - txn1.set(&key2, &value5).unwrap(); + drop(txn1); - txn1.commit().unwrap(); + let res = txn2 + .scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].1, value1); - txn2.set(&key2, &value6).unwrap(); - assert!(match txn2.commit() { - Err(err) => { - if !is_ssi { - matches!(err, Error::TransactionWriteConflict) - } else { - matches!(err, Error::TransactionReadConflict) - } - } - _ => false, - }); + txn2.commit().unwrap(); + } + + { + let mut txn3 = store.begin().unwrap(); + let val1 = txn3.get(&key1).unwrap().unwrap(); + assert_eq!(val1, value1.as_ref()); + let val2 = txn3.get(&key2).unwrap().unwrap(); + assert_eq!(val2, value2.as_ref()); + } } - { - let mut txn3 = store.begin().unwrap(); - let val1 = txn3.get(&key1).unwrap().unwrap(); - assert_eq!(val1, value3.as_ref()); - let val2 = txn3.get(&key2).unwrap().unwrap(); - assert_eq!(val2, value5.as_ref()); + #[test] + fn g1a() { + g1a_tests(false); // snapshot isolation + g1a_tests(true); // serializable snapshot isolation } - } - #[test] - fn g0() { - g0_tests(false); // snapshot isolation - g0_tests(true); // serializable snapshot isolation - } + // G1b: Intermediate Reads (dirty reads) + fn g1b_tests(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); - // G1a: Aborted Reads (dirty reads, cascaded aborts) - fn g1a_tests(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value1 = Bytes::from("v1"); - let value2 = Bytes::from("v2"); - let value3 = Bytes::from("v3"); + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value1 = Bytes::from("v1"); + let value3 = Bytes::from("v3"); + let value4 = Bytes::from("v4"); + + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + + assert!(txn1.get(&key1).is_ok()); + assert!(txn1.get(&key2).is_ok()); + + txn1.set(&key1, &value3).unwrap(); + + let range = "k1".as_bytes()..="k3".as_bytes(); + let res = txn2 + .scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].1, value1); + + txn1.set(&key1, &value4).unwrap(); + txn1.commit().unwrap(); + + let res = txn2 + .scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].1, value1); + + txn2.commit().unwrap(); + } + } - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + #[test] + fn g1b() { + g1b_tests(false); // snapshot isolation + g1b_tests(true); // serializable snapshot isolation + } - assert!(txn1.get(&key1).is_ok()); + // G1c: Circular Information Flow (dirty reads) + fn g1c_tests(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); - txn1.set(&key1, &value3).unwrap(); + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value1 = Bytes::from("v1"); + let value2 = Bytes::from("v2"); + let value3 = Bytes::from("v3"); + let value4 = Bytes::from("v4"); - let range = "k1".as_bytes()..="k3".as_bytes(); - let res = txn2 - .scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 2); - assert_eq!(res[0].1, value1); + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); - drop(txn1); + assert!(txn1.get(&key1).is_ok()); + assert!(txn2.get(&key2).is_ok()); - let res = txn2 - .scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 2); - assert_eq!(res[0].1, value1); + txn1.set(&key1, &value3).unwrap(); + txn2.set(&key2, &value4).unwrap(); - txn2.commit().unwrap(); - } + assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2.as_ref()); + assert_eq!(txn2.get(&key1).unwrap().unwrap(), value1.as_ref()); - { - let mut txn3 = store.begin().unwrap(); - let val1 = txn3.get(&key1).unwrap().unwrap(); - assert_eq!(val1, value1.as_ref()); - let val2 = txn3.get(&key2).unwrap().unwrap(); - assert_eq!(val2, value2.as_ref()); + txn1.commit().unwrap(); + assert!(match txn2.commit() { + Err(err) => { + matches!(err, Error::TransactionReadConflict) + } + _ => false, + }); + } } - } - #[test] - fn g1a() { - g1a_tests(false); // snapshot isolation - g1a_tests(true); // serializable snapshot isolation - } + #[test] + fn g1c() { + g1c_tests(true); + } - // G1b: Intermediate Reads (dirty reads) - fn g1b_tests(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); + // PMP: Predicate-Many-Preceders + fn pmp_tests(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value1 = Bytes::from("v1"); - let value3 = Bytes::from("v3"); - let value4 = Bytes::from("v4"); + let key3 = Bytes::from("k3"); + let value1 = Bytes::from("v1"); + let value2 = Bytes::from("v2"); + let value3 = Bytes::from("v3"); + + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + + // k3 should not be visible to txn1 + let range = "k1".as_bytes()..="k3".as_bytes(); + let res = txn1 + .scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].1, value1); + assert_eq!(res[1].1, value2); + + // k3 is committed by txn2 + txn2.set(&key3, &value3).unwrap(); + txn2.commit().unwrap(); + + // k3 should still not be visible to txn1 + let range = "k1".as_bytes()..="k3".as_bytes(); + let res = txn1 + .scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].1, value1); + assert_eq!(res[1].1, value2); + } + } - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + #[test] + fn pmp() { + pmp_tests(false); + pmp_tests(true); + } - assert!(txn1.get(&key1).is_ok()); - assert!(txn1.get(&key2).is_ok()); + // PMP-Write: Circular Information Flow (dirty reads) + fn pmp_write_tests(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); - txn1.set(&key1, &value3).unwrap(); + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value1 = Bytes::from("v1"); + let value2 = Bytes::from("v2"); + let value3 = Bytes::from("v3"); + + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + + assert!(txn1.get(&key1).is_ok()); + txn1.set(&key1, &value3).unwrap(); + + let range = "k1".as_bytes()..="k2".as_bytes(); + let res = txn2 + .scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].1, value1); + assert_eq!(res[1].1, value2); + + txn2.delete(&key2).unwrap(); + txn1.commit().unwrap(); + + let range = "k1".as_bytes()..="k3".as_bytes(); + let res = txn2 + .scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 1); + assert_eq!(res[0].1, value1); + + assert!(match txn2.commit() { + Err(err) => { + matches!(err, Error::TransactionReadConflict) + } + _ => false, + }); + } + } - let range = "k1".as_bytes()..="k3".as_bytes(); - let res = txn2 - .scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 2); - assert_eq!(res[0].1, value1); + #[test] + fn pmp_write() { + pmp_write_tests(true); + } - txn1.set(&key1, &value4).unwrap(); - txn1.commit().unwrap(); + // P4: Lost Update + fn p4_tests(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); - let res = txn2 - .scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 2); - assert_eq!(res[0].1, value1); + let key1 = Bytes::from("k1"); + let value3 = Bytes::from("v3"); - txn2.commit().unwrap(); - } - } + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); - #[test] - fn g1b() { - g1b_tests(false); // snapshot isolation - g1b_tests(true); // serializable snapshot isolation - } + assert!(txn1.get(&key1).is_ok()); + assert!(txn2.get(&key1).is_ok()); - // G1c: Circular Information Flow (dirty reads) - fn g1c_tests(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); + txn1.set(&key1, &value3).unwrap(); + txn2.set(&key1, &value3).unwrap(); - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value1 = Bytes::from("v1"); - let value2 = Bytes::from("v2"); - let value3 = Bytes::from("v3"); - let value4 = Bytes::from("v4"); + txn1.commit().unwrap(); - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + assert!(match txn2.commit() { + Err(err) => { + if !is_ssi { + matches!(err, Error::TransactionWriteConflict) + } else { + matches!(err, Error::TransactionReadConflict) + } + } + _ => false, + }); + } + } - assert!(txn1.get(&key1).is_ok()); - assert!(txn2.get(&key2).is_ok()); + #[test] + fn p4() { + p4_tests(false); + p4_tests(true); + } - txn1.set(&key1, &value3).unwrap(); - txn2.set(&key2, &value4).unwrap(); + // G-single: Single Anti-dependency Cycles (read skew) + fn g_single_tests(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); - assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2.as_ref()); - assert_eq!(txn2.get(&key1).unwrap().unwrap(), value1.as_ref()); + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value1 = Bytes::from("v1"); + let value2 = Bytes::from("v2"); + let value3 = Bytes::from("v3"); + let value4 = Bytes::from("v4"); - txn1.commit().unwrap(); - assert!(match txn2.commit() { - Err(err) => { - matches!(err, Error::TransactionReadConflict) - } - _ => false, - }); - } - } + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); - #[test] - fn g1c() { - g1c_tests(true); - } + assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1.as_ref()); + assert_eq!(txn2.get(&key1).unwrap().unwrap(), value1.as_ref()); + assert_eq!(txn2.get(&key2).unwrap().unwrap(), value2.as_ref()); + txn2.set(&key1, &value3).unwrap(); + txn2.set(&key2, &value4).unwrap(); - // PMP: Predicate-Many-Preceders - fn pmp_tests(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); + txn2.commit().unwrap(); - let key3 = Bytes::from("k3"); - let value1 = Bytes::from("v1"); - let value2 = Bytes::from("v2"); - let value3 = Bytes::from("v3"); + assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2.as_ref()); + txn1.commit().unwrap(); + } + } - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + #[test] + fn g_single() { + g_single_tests(false); + g_single_tests(true); + } - // k3 should not be visible to txn1 - let range = "k1".as_bytes()..="k3".as_bytes(); - let res = txn1 - .scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 2); - assert_eq!(res[0].1, value1); - assert_eq!(res[1].1, value2); + // G-single-write-1: Single Anti-dependency Cycles (read skew) + fn g_single_write_1_tests(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); - // k3 is committed by txn2 - txn2.set(&key3, &value3).unwrap(); - txn2.commit().unwrap(); + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value1 = Bytes::from("v1"); + let value2 = Bytes::from("v2"); + let value3 = Bytes::from("v3"); + let value4 = Bytes::from("v4"); + + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + + assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1.as_ref()); + + let range = "k1".as_bytes()..="k2".as_bytes(); + let res = txn2 + .scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].1, value1); + assert_eq!(res[1].1, value2); + + txn2.set(&key1, &value3).unwrap(); + txn2.set(&key2, &value4).unwrap(); + + txn2.commit().unwrap(); + + txn1.delete(&key2).unwrap(); + assert!(txn1.get(&key2).unwrap().is_none()); + assert!(match txn1.commit() { + Err(err) => { + if !is_ssi { + matches!(err, Error::TransactionWriteConflict) + } else { + matches!(err, Error::TransactionReadConflict) + } + } + _ => false, + }); + } + } - // k3 should still not be visible to txn1 - let range = "k1".as_bytes()..="k3".as_bytes(); - let res = txn1 - .scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 2); - assert_eq!(res[0].1, value1); - assert_eq!(res[1].1, value2); + #[test] + fn g_single_write_1() { + g_single_write_1_tests(false); + g_single_write_1_tests(true); } - } - #[test] - fn pmp() { - pmp_tests(false); - pmp_tests(true); - } + // G-single-write-2: Single Anti-dependency Cycles (read skew) + fn g_single_write_2_tests(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); - // PMP-Write: Circular Information Flow (dirty reads) - fn pmp_write_tests(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value1 = Bytes::from("v1"); + let value2 = Bytes::from("v2"); + let value3 = Bytes::from("v3"); + let value4 = Bytes::from("v4"); - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value1 = Bytes::from("v1"); - let value2 = Bytes::from("v2"); - let value3 = Bytes::from("v3"); + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1.as_ref()); + let range = "k1".as_bytes()..="k2".as_bytes(); + let res = txn2 + .scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].1, value1); + assert_eq!(res[1].1, value2); - assert!(txn1.get(&key1).is_ok()); - txn1.set(&key1, &value3).unwrap(); + txn2.set(&key1, &value3).unwrap(); - let range = "k1".as_bytes()..="k2".as_bytes(); - let res = txn2 - .scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 2); - assert_eq!(res[0].1, value1); - assert_eq!(res[1].1, value2); + txn1.delete(&key2).unwrap(); - txn2.delete(&key2).unwrap(); - txn1.commit().unwrap(); + txn2.set(&key2, &value4).unwrap(); - let range = "k1".as_bytes()..="k3".as_bytes(); - let res = txn2 - .scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 1); - assert_eq!(res[0].1, value1); + drop(txn1); - assert!(match txn2.commit() { - Err(err) => { - matches!(err, Error::TransactionReadConflict) - } - _ => false, - }); + txn2.commit().unwrap(); + } } - } - #[test] - fn pmp_write() { - pmp_write_tests(true); - } + #[test] + fn g_single_write_2() { + g_single_write_2_tests(false); + g_single_write_2_tests(true); + } - // P4: Lost Update - fn p4_tests(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); + fn g2_item_tests(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); - let key1 = Bytes::from("k1"); - let value3 = Bytes::from("v3"); + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value1 = Bytes::from("v1"); + let value2 = Bytes::from("v2"); + let value3 = Bytes::from("v3"); + let value4 = Bytes::from("v4"); + + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + + let range = "k1".as_bytes()..="k2".as_bytes(); + let res = txn1 + .scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].1, value1); + assert_eq!(res[1].1, value2); + + let res = txn2 + .scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].1, value1); + assert_eq!(res[1].1, value2); + + txn1.set(&key1, &value3).unwrap(); + txn2.set(&key2, &value4).unwrap(); + + txn1.commit().unwrap(); + + assert!(match txn2.commit() { + Err(err) => { + matches!(err, Error::TransactionReadConflict) + } + _ => false, + }); + } + } - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + #[test] + fn g2_item() { + g2_item_tests(true); + } - assert!(txn1.get(&key1).is_ok()); - assert!(txn2.get(&key1).is_ok()); + fn g2_item_predicate(is_ssi: bool) { + let store = create_hermitage_store(is_ssi); - txn1.set(&key1, &value3).unwrap(); - txn2.set(&key1, &value3).unwrap(); + let key3 = Bytes::from("k3"); + let key4 = Bytes::from("k4"); + let key5 = Bytes::from("k5"); + let key6 = Bytes::from("k6"); + let key7 = Bytes::from("k7"); + let value3 = Bytes::from("v3"); + let value4 = Bytes::from("v4"); + + // inserts into read ranges of already-committed transaction(s) should fail + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + + let range = "k1".as_bytes()..="k4".as_bytes(); + txn1.scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + txn2.scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + + txn1.set(&key3, &value3).unwrap(); + txn2.set(&key4, &value4).unwrap(); + + txn1.commit().unwrap(); + + assert!(match txn2.commit() { + Err(err) => { + matches!(err, Error::TransactionReadConflict) + } + _ => false, + }); + } - txn1.commit().unwrap(); + // k1, k2, k3 already committed + // inserts beyond scan range should pass + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + + let range = "k1".as_bytes()..="k3".as_bytes(); + txn1.scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + txn2.scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + + txn1.set(&key4, &value3).unwrap(); + txn2.set(&key5, &value4).unwrap(); + + txn1.commit().unwrap(); + txn2.commit().unwrap(); + } - assert!(match txn2.commit() { - Err(err) => { - if !is_ssi { - matches!(err, Error::TransactionWriteConflict) - } else { + // k1, k2, k3, k4, k5 already committed + // inserts in subset scan ranges should fail + { + let mut txn1 = store.begin().unwrap(); + let mut txn2 = store.begin().unwrap(); + + let range = "k1".as_bytes()..="k7".as_bytes(); + txn1.scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + let range = "k3".as_bytes()..="k7".as_bytes(); + txn2.scan(range.clone(), None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + + txn1.set(&key6, &value3).unwrap(); + txn2.set(&key7, &value4).unwrap(); + + txn1.commit().unwrap(); + assert!(match txn2.commit() { + Err(err) => { matches!(err, Error::TransactionReadConflict) } - } - _ => false, - }); + _ => false, + }); + } } - } - #[test] - fn p4() { - p4_tests(false); - p4_tests(true); + #[test] + fn g2_predicate() { + g2_item_predicate(true); + } } - // G-single: Single Anti-dependency Cycles (read skew) - fn g_single_tests(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); + // Version management tests + mod version_tests { + use super::*; - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value1 = Bytes::from("v1"); - let value2 = Bytes::from("v2"); - let value3 = Bytes::from("v3"); - let value4 = Bytes::from("v4"); + #[test] + fn test_scan_all_versions_single_key_multiple_versions() { + let (store, _) = create_store(false); + let key = Bytes::from("key1"); - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + // Insert multiple versions of the same key + let values = [ + Bytes::from("value1"), + Bytes::from("value2"), + Bytes::from("value3"), + ]; - assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1.as_ref()); - assert_eq!(txn2.get(&key1).unwrap().unwrap(), value1.as_ref()); - assert_eq!(txn2.get(&key2).unwrap().unwrap(), value2.as_ref()); - txn2.set(&key1, &value3).unwrap(); - txn2.set(&key2, &value4).unwrap(); + for (i, value) in values.iter().enumerate() { + let mut txn = store.begin().unwrap(); + let version = (i + 1) as u64; // Incremental version + txn.set_at_ts(&key, value, version).unwrap(); + txn.commit().unwrap(); + } - txn2.commit().unwrap(); + let range = key.as_ref()..=key.as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, None) + .collect::>>() + .unwrap(); - assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2.as_ref()); - txn1.commit().unwrap(); + // Verify that the output contains all the versions of the key + assert_eq!(results.len(), values.len()); + for (i, (k, v, version, is_deleted)) in results.iter().enumerate() { + assert_eq!(k, &key); + assert_eq!(v, &values[i]); + assert_eq!(*version, (i + 1) as u64); + assert!(!(*is_deleted)); + } } - } - - #[test] - fn g_single() { - g_single_tests(false); - g_single_tests(true); - } - - // G-single-write-1: Single Anti-dependency Cycles (read skew) - fn g_single_write_1_tests(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value1 = Bytes::from("v1"); - let value2 = Bytes::from("v2"); - let value3 = Bytes::from("v3"); - let value4 = Bytes::from("v4"); + #[test] + fn test_scan_all_versions_multiple_keys_single_version_each() { + let (store, _) = create_store(false); + let keys = vec![ + Bytes::from("key1"), + Bytes::from("key2"), + Bytes::from("key3"), + ]; + let value = Bytes::from("value1"); - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + for key in &keys { + let mut txn = store.begin().unwrap(); + txn.set_at_ts(key, &value, 1).unwrap(); + txn.commit().unwrap(); + } - assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1.as_ref()); + let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, None) + .collect::>>() + .unwrap(); - let range = "k1".as_bytes()..="k2".as_bytes(); - let res = txn2 - .scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 2); - assert_eq!(res[0].1, value1); - assert_eq!(res[1].1, value2); + assert_eq!(results.len(), keys.len()); + for (i, (k, v, version, is_deleted)) in results.iter().enumerate() { + assert_eq!(k, &keys[i]); + assert_eq!(v, &value); + assert_eq!(*version, 1); + assert!(!(*is_deleted)); + } + } - txn2.set(&key1, &value3).unwrap(); - txn2.set(&key2, &value4).unwrap(); + #[test] + fn test_scan_all_versions_multiple_keys_multiple_versions_each() { + let (store, _) = create_store(false); + let keys = vec![ + Bytes::from("key1"), + Bytes::from("key2"), + Bytes::from("key3"), + ]; + let values = [ + Bytes::from("value1"), + Bytes::from("value2"), + Bytes::from("value3"), + ]; + + for key in &keys { + for (i, value) in values.iter().enumerate() { + let mut txn = store.begin().unwrap(); + let version = (i + 1) as u64; + txn.set_at_ts(key, value, version).unwrap(); + txn.commit().unwrap(); + } + } - txn2.commit().unwrap(); + let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, None) + .collect::>>() + .unwrap(); - txn1.delete(&key2).unwrap(); - assert!(txn1.get(&key2).unwrap().is_none()); - assert!(match txn1.commit() { - Err(err) => { - if !is_ssi { - matches!(err, Error::TransactionWriteConflict) - } else { - matches!(err, Error::TransactionReadConflict) - } + let mut expected_results = Vec::new(); + for key in &keys { + for (i, value) in values.iter().enumerate() { + expected_results.push((key.clone(), value.clone(), (i + 1) as u64, false)); } - _ => false, - }); + } + + assert_eq!(results.len(), expected_results.len()); + for (result, expected) in results.iter().zip(expected_results.iter()) { + let (k, v, version, is_deleted) = result; + let (expected_key, expected_value, expected_version, expected_is_deleted) = + expected; + assert_eq!(k, expected_key); + assert_eq!(v, expected_value); + assert_eq!(*version, *expected_version); + assert_eq!(*is_deleted, *expected_is_deleted); + } } - } - #[test] - fn g_single_write_1() { - g_single_write_1_tests(false); - g_single_write_1_tests(true); - } + #[test] + fn test_scan_all_versions_deleted_records() { + let (store, _) = create_store(false); + let key = Bytes::from("key1"); + let value = Bytes::from("value1"); - // G-single-write-2: Single Anti-dependency Cycles (read skew) - fn g_single_write_2_tests(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); + let mut txn = store.begin().unwrap(); + txn.set_at_ts(&key, &value, 1).unwrap(); + txn.commit().unwrap(); - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value1 = Bytes::from("v1"); - let value2 = Bytes::from("v2"); - let value3 = Bytes::from("v3"); - let value4 = Bytes::from("v4"); + let mut txn = store.begin().unwrap(); + txn.soft_delete(&key).unwrap(); + txn.commit().unwrap(); - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + let range = key.as_ref()..=key.as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, None) + .collect::>>() + .unwrap(); - assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1.as_ref()); - let range = "k1".as_bytes()..="k2".as_bytes(); - let res = txn2 - .scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 2); - assert_eq!(res[0].1, value1); - assert_eq!(res[1].1, value2); + assert_eq!(results.len(), 2); + let (k, v, version, is_deleted) = &results[0]; + assert_eq!(k, &key); + assert_eq!(v, &value); + assert_eq!(*version, 1); + assert!(!(*is_deleted)); - txn2.set(&key1, &value3).unwrap(); + let (k, v, _, is_deleted) = &results[1]; + assert_eq!(k, &key); + assert_eq!(v, &Bytes::new()); + assert!(*is_deleted); + } - txn1.delete(&key2).unwrap(); + #[test] + fn test_scan_all_versions_multiple_keys_single_version_each_deleted() { + let (store, _) = create_store(false); + let keys = vec![ + Bytes::from("key1"), + Bytes::from("key2"), + Bytes::from("key3"), + ]; + let value = Bytes::from("value1"); - txn2.set(&key2, &value4).unwrap(); + for key in &keys { + let mut txn = store.begin().unwrap(); + txn.set_at_ts(key, &value, 1).unwrap(); + txn.commit().unwrap(); + } - drop(txn1); + for key in &keys { + let mut txn = store.begin().unwrap(); + txn.soft_delete(key).unwrap(); + txn.commit().unwrap(); + } - txn2.commit().unwrap(); + let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, None) + .collect::>>() + .unwrap(); + + assert_eq!(results.len(), keys.len() * 2); + for (i, (k, v, version, is_deleted)) in results.iter().enumerate() { + let key_index = i / 2; + let is_deleted_version = i % 2 == 1; + assert_eq!(k, &keys[key_index]); + if is_deleted_version { + assert_eq!(v, &Bytes::new()); + assert!(*is_deleted); + } else { + assert_eq!(v, &value); + assert_eq!(*version, 1); + assert!(!(*is_deleted)); + } + } } - } - #[test] - fn g_single_write_2() { - g_single_write_2_tests(false); - g_single_write_2_tests(true); - } + #[test] + fn test_scan_all_versions_multiple_keys_multiple_versions_each_deleted() { + let (store, _) = create_store(false); + let keys = vec![ + Bytes::from("key1"), + Bytes::from("key2"), + Bytes::from("key3"), + ]; + let values = [ + Bytes::from("value1"), + Bytes::from("value2"), + Bytes::from("value3"), + ]; + + for key in &keys { + for (i, value) in values.iter().enumerate() { + let mut txn = store.begin().unwrap(); + let version = (i + 1) as u64; + txn.set_at_ts(key, value, version).unwrap(); + txn.commit().unwrap(); + } + } - fn g2_item_tests(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); + for key in &keys { + let mut txn = store.begin().unwrap(); + txn.soft_delete(key).unwrap(); + txn.commit().unwrap(); + } - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value1 = Bytes::from("v1"); - let value2 = Bytes::from("v2"); - let value3 = Bytes::from("v3"); - let value4 = Bytes::from("v4"); + let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, None) + .collect::>>() + .unwrap(); - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + let mut expected_results = Vec::new(); + for key in &keys { + for (i, value) in values.iter().enumerate() { + expected_results.push((key.clone(), value.clone(), (i + 1) as u64, false)); + } + expected_results.push((key.clone(), Bytes::new(), 0, true)); + } - let range = "k1".as_bytes()..="k2".as_bytes(); - let res = txn1 - .scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 2); - assert_eq!(res[0].1, value1); - assert_eq!(res[1].1, value2); + assert_eq!(results.len(), expected_results.len()); + for (result, expected) in results.iter().zip(expected_results.iter()) { + let (k, v, version, is_deleted) = result; + let (expected_key, expected_value, expected_version, expected_is_deleted) = + expected; + assert_eq!(k, expected_key); + assert_eq!(v, expected_value); + if !expected_is_deleted { + assert_eq!(*version, *expected_version); + } + assert_eq!(*is_deleted, *expected_is_deleted); + } + } - let res = txn2 - .scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(res.len(), 2); - assert_eq!(res[0].1, value1); - assert_eq!(res[1].1, value2); + #[test] + fn test_scan_all_versions_soft_and_hard_delete() { + let (store, _) = create_store(false); + let key = Bytes::from("key1"); + let value = Bytes::from("value1"); - txn1.set(&key1, &value3).unwrap(); - txn2.set(&key2, &value4).unwrap(); + let mut txn = store.begin().unwrap(); + txn.set_at_ts(&key, &value, 1).unwrap(); + txn.commit().unwrap(); - txn1.commit().unwrap(); - - assert!(match txn2.commit() { - Err(err) => { - matches!(err, Error::TransactionReadConflict) - } - _ => false, - }); - } - } - - #[test] - fn g2_item() { - g2_item_tests(true); - } - - fn require_send(_: T) {} - fn require_sync(_: T) {} - - #[test] - fn is_send_sync() { - let (db, _) = create_store(false); - - let txn = db.begin().unwrap(); - require_send(txn); + let mut txn = store.begin().unwrap(); + txn.soft_delete(&key).unwrap(); + txn.commit().unwrap(); - let txn = db.begin().unwrap(); - require_sync(txn); - } + let mut txn = store.begin().unwrap(); + txn.delete(&key).unwrap(); + txn.commit().unwrap(); - const ENTRIES: usize = 400_000; - const KEY_SIZE: usize = 24; - const VALUE_SIZE: usize = 150; - const RNG_SEED: u64 = 3; + let range = key.as_ref()..=key.as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, None) + .collect::>>() + .unwrap(); - fn fill_slice(slice: &mut [u8], rng: &mut StdRng) { - let mut i = 0; - while i + size_of::() < slice.len() { - let tmp = rng.gen::(); - slice[i..(i + size_of::())].copy_from_slice(&tmp.to_le_bytes()); - i += size_of::() - } - if i + size_of::() < slice.len() { - let tmp = rng.gen::(); - slice[i..(i + size_of::())].copy_from_slice(&tmp.to_le_bytes()); - i += size_of::() - } - if i + size_of::() < slice.len() { - let tmp = rng.gen::(); - slice[i..(i + size_of::())].copy_from_slice(&tmp.to_le_bytes()); - i += size_of::() - } - if i + size_of::() < slice.len() { - let tmp = rng.gen::(); - slice[i..(i + size_of::())].copy_from_slice(&tmp.to_le_bytes()); - i += size_of::() - } - if i + size_of::() < slice.len() { - slice[i] = rng.gen::(); + assert_eq!(results.len(), 0); } - } - - fn gen_pair(rng: &mut StdRng) -> ([u8; KEY_SIZE], Vec) { - let mut key = [0u8; KEY_SIZE]; - fill_slice(&mut key, rng); - let mut value = vec![0u8; VALUE_SIZE]; - fill_slice(&mut value, rng); - (key, value) - } + #[test] + fn test_scan_all_versions_range_boundaries() { + let (store, _) = create_store(false); + let keys = vec![ + Bytes::from("key1"), + Bytes::from("key2"), + Bytes::from("key3"), + ]; + let value = Bytes::from("value1"); - fn make_rng() -> StdRng { - StdRng::seed_from_u64(RNG_SEED) - } - - #[ignore] - #[test] - fn insert_large_txn_and_get() { - let temp_dir = create_temp_directory(); - let mut opts = Options::new(); - opts.dir = temp_dir.path().to_path_buf(); + for key in &keys { + let mut txn = store.begin().unwrap(); + txn.set_at_ts(key, &value, 1).unwrap(); + txn.commit().unwrap(); + } - let store = Store::new(opts.clone()).expect("should create store"); - let mut rng = make_rng(); + // Inclusive range + let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, None) + .collect::>>() + .unwrap(); + assert_eq!(results.len(), keys.len()); - let mut txn = store.begin().unwrap(); - for _ in 0..ENTRIES { - let (key, value) = gen_pair(&mut rng); - txn.set(&key, &value).unwrap(); - } - txn.commit().unwrap(); - drop(txn); + // Exclusive range + let range = keys.first().unwrap().as_ref()..keys.last().unwrap().as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, None) + .collect::>>() + .unwrap(); + assert_eq!(results.len(), keys.len() - 1); - // Read the keys from the store - let mut rng = make_rng(); - let mut txn = store.begin_with_mode(Mode::ReadOnly).unwrap(); - for _i in 0..ENTRIES { - let (key, _) = gen_pair(&mut rng); - txn.get(&key).unwrap(); + // Unbounded range + let range = ..; + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, None) + .collect::>>() + .unwrap(); + assert_eq!(results.len(), keys.len()); } - } - #[test] - fn empty_scan_should_not_return_an_error() { - let (store, _) = create_store(false); + #[test] + fn test_scan_all_versions_with_limit() { + let (store, _) = create_store(false); + let keys = vec![ + Bytes::from("key1"), + Bytes::from("key2"), + Bytes::from("key3"), + ]; + let value = Bytes::from("value1"); - let range = "key1".as_bytes()..="key3".as_bytes(); - - let mut txn = store.begin().unwrap(); - let results: Vec<_> = txn.scan(range, None).collect(); - assert_eq!(results.len(), 0); - } - - #[test] - fn transaction_delete_and_scan_test() { - let (store, _) = create_store(false); - - // Define key-value pairs for the test - let key1 = Bytes::from("k1"); - let value1 = Bytes::from("baz"); + for key in &keys { + let mut txn = store.begin().unwrap(); + txn.set_at_ts(key, &value, 1).unwrap(); + txn.commit().unwrap(); + } - { - // Start a new read-write transaction (txn1) - let mut txn1 = store.begin().unwrap(); - txn1.set(&key1, &value1).unwrap(); - txn1.commit().unwrap(); - } + let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, Some(2)) + .collect::>>() + .unwrap(); - { - // Start a read-only transaction (txn) - let mut txn = store.begin().unwrap(); - txn.get(&key1).unwrap(); - txn.delete(&key1).unwrap(); - let range = "k1".as_bytes()..="k3".as_bytes(); - let results = txn - .scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(results.len(), 0); - txn.commit().unwrap(); - } - { - let range = "k1".as_bytes()..="k3".as_bytes(); - let mut txn = store.begin().unwrap(); - let results = txn - .scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(results.len(), 0); + assert_eq!(results.len(), 2); } - } - - #[test] - fn sdb_delete_record_id_bug() { - let (store, _) = create_store(false); - - // Define key-value pairs for the test - let key1 = Bytes::copy_from_slice(&[ - 47, 33, 110, 100, 166, 192, 229, 30, 101, 24, 73, 242, 185, 36, 233, 242, 54, 96, 72, - 52, - ]); - let key2 = Bytes::copy_from_slice(&[ - 47, 33, 104, 98, 0, 0, 1, 141, 141, 42, 113, 8, 47, 166, 192, 229, 30, 101, 24, 73, - 242, 185, 36, 233, 242, 54, 96, 72, 52, - ]); - let value1 = Bytes::from("baz"); - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.set(&key1, &value1).unwrap(); - txn.set(&key2, &value1).unwrap(); - txn.commit().unwrap(); - } + #[test] + fn test_scan_all_versions_single_key_single_version() { + let (store, _) = create_store(false); + let key = Bytes::from("key1"); + let value = Bytes::from("value1"); - let key3 = Bytes::copy_from_slice(&[47, 33, 117, 115, 114, 111, 111, 116, 0]); - { - // Start a new read-write transaction (txn) let mut txn = store.begin().unwrap(); - txn.set(&key3, &value1).unwrap(); + txn.set_at_ts(&key, &value, 1).unwrap(); txn.commit().unwrap(); - } - - let key4 = Bytes::copy_from_slice(&[47, 33, 117, 115, 114, 111, 111, 116, 0]); - let mut txn1 = store.begin().unwrap(); - txn1.get(&key4).unwrap(); - - { - let mut txn2 = store.begin().unwrap(); - txn2.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, 72, - 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, 33, 116, - 98, 117, 115, 101, 114, 0, - ])) - .unwrap(); - txn2.get(&Bytes::copy_from_slice(&[ - 47, 33, 110, 115, 116, 101, 115, 116, 45, 110, 115, 0, - ])) - .unwrap(); - txn2.get(&Bytes::copy_from_slice(&[ - 47, 33, 110, 115, 116, 101, 115, 116, 45, 110, 115, 0, - ])) - .unwrap(); - txn2.set( - &Bytes::copy_from_slice(&[47, 33, 110, 115, 116, 101, 115, 116, 45, 110, 115, 0]), - &value1, - ) - .unwrap(); - - txn2.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, 72, - 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, - ])) - .unwrap(); - txn2.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, 72, - 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, - ])) - .unwrap(); - txn2.set( - &Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, - 72, 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, - 69, 0, - ]), - &value1, - ) - .unwrap(); - - txn2.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, 72, - 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, 33, 116, - 98, 117, 115, 101, 114, 0, - ])) - .unwrap(); - txn2.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, 72, - 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, 33, 116, - 98, 117, 115, 101, 114, 0, - ])) - .unwrap(); - txn2.set( - &Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, - 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, - 33, 116, 98, 117, 115, 101, 114, 0, - ]), - &value1, - ) - .unwrap(); - - txn2.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, 72, - 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, 33, 116, - 98, 117, 115, 101, 114, 0, - ])) - .unwrap(); - txn2.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, 72, - 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, 42, 117, - 115, 101, 114, 0, 42, 0, 0, 0, 1, 106, 111, 104, 110, 0, - ])) - .unwrap(); - txn2.set( - &Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, - 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, - 42, 117, 115, 101, 114, 0, 42, 0, 0, 0, 1, 106, 111, 104, 110, 0, - ]), - &value1, - ) - .unwrap(); - txn2.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, 72, - 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, - ])) - .unwrap(); - - txn2.commit().unwrap(); - } + let range = key.as_ref()..=key.as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, None) + .collect::>>() + .unwrap(); - { - let mut txn3 = store.begin().unwrap(); - txn3.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, 72, - 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, 42, 117, - 115, 101, 114, 0, 42, 0, 0, 0, 1, 106, 111, 104, 110, 0, - ])) - .unwrap(); - txn3.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, 72, - 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, 33, 116, - 98, 117, 115, 101, 114, 0, - ])) - .unwrap(); - txn3.delete(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, 72, - 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, 42, 117, - 115, 101, 114, 0, 42, 0, 0, 0, 1, 106, 111, 104, 110, 0, - ])) - .unwrap(); - txn3.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, 72, - 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, - ])) - .unwrap(); - txn3.get(&Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, 72, - 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, - ])) - .unwrap(); - txn3.set( - &Bytes::copy_from_slice(&[ - 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, - 72, 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, - 69, 0, - ]), - &value1, - ) - .unwrap(); - txn3.commit().unwrap(); + assert_eq!(results.len(), 1); + let (k, v, version, is_deleted) = &results[0]; + assert_eq!(k, &key); + assert_eq!(v, &value); + assert_eq!(*version, 1); + assert!(!(*is_deleted)); } - } - - fn g2_item_predicate(is_ssi: bool) { - let store = create_hermitage_store(is_ssi); - - let key3 = Bytes::from("k3"); - let key4 = Bytes::from("k4"); - let key5 = Bytes::from("k5"); - let key6 = Bytes::from("k6"); - let key7 = Bytes::from("k7"); - let value3 = Bytes::from("v3"); - let value4 = Bytes::from("v4"); - - // inserts into read ranges of already-committed transaction(s) should fail - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); - - let range = "k1".as_bytes()..="k4".as_bytes(); - txn1.scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - txn2.scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - - txn1.set(&key3, &value3).unwrap(); - txn2.set(&key4, &value4).unwrap(); - txn1.commit().unwrap(); - - assert!(match txn2.commit() { - Err(err) => { - matches!(err, Error::TransactionReadConflict) + #[test] + fn test_scan_all_versions_with_limit_with_multiple_versions_per_key() { + let (store, _) = create_store(false); + let keys = vec![ + Bytes::from("key1"), + Bytes::from("key2"), + Bytes::from("key3"), + ]; + let values = [ + Bytes::from("value1"), + Bytes::from("value2"), + Bytes::from("value3"), + ]; + + // Insert multiple versions for each key + for key in &keys { + for (i, value) in values.iter().enumerate() { + let mut txn = store.begin().unwrap(); + let version = (i + 1) as u64; + txn.set_at_ts(key, value, version).unwrap(); + txn.commit().unwrap(); } - _ => false, - }); - } + } - // k1, k2, k3 already committed - // inserts beyond scan range should pass - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); + let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(range, Some(2)) + .collect::>>() + .unwrap(); + assert_eq!(results.len(), 6); // 3 versions for each of 2 keys - let range = "k1".as_bytes()..="k3".as_bytes(); - txn1.scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - txn2.scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); + // Collect unique keys from the results + let unique_keys: HashSet<_> = results.iter().map(|(k, _, _, _)| k.to_vec()).collect(); - txn1.set(&key4, &value3).unwrap(); - txn2.set(&key5, &value4).unwrap(); + // Verify that the number of unique keys is equal to the limit + assert_eq!(unique_keys.len(), 2); - txn1.commit().unwrap(); - txn2.commit().unwrap(); + // Verify that the results contain all versions for each key + for key in unique_keys { + let key_versions: Vec<_> = + results.iter().filter(|(k, _, _, _)| k == &key).collect(); + + assert_eq!(key_versions.len(), 3); // Should have all 3 versions + + // Check the latest version + let latest = key_versions + .iter() + .max_by_key(|(_, _, version, _)| version) + .unwrap(); + assert_eq!(latest.1, *values.last().unwrap()); + assert_eq!(latest.2, values.len() as u64); + } } - // k1, k2, k3, k4, k5 already committed - // inserts in subset scan ranges should fail - { - let mut txn1 = store.begin().unwrap(); - let mut txn2 = store.begin().unwrap(); - - let range = "k1".as_bytes()..="k7".as_bytes(); - txn1.scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - let range = "k3".as_bytes()..="k7".as_bytes(); - txn2.scan(range.clone(), None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - - txn1.set(&key6, &value3).unwrap(); - txn2.set(&key7, &value4).unwrap(); + #[test] + fn test_scan_all_versions_with_subsets() { + let (store, _) = create_store(false); + let keys = vec![ + Bytes::from("key1"), + Bytes::from("key2"), + Bytes::from("key3"), + Bytes::from("key4"), + Bytes::from("key5"), + Bytes::from("key6"), + Bytes::from("key7"), + ]; + let values = [ + Bytes::from("value1"), + Bytes::from("value2"), + Bytes::from("value3"), + ]; + + // Insert multiple versions for each key + for key in &keys { + for (i, value) in values.iter().enumerate() { + let mut txn = store.begin().unwrap(); + let version = (i + 1) as u64; + txn.set_at_ts(key, value, version).unwrap(); + txn.commit().unwrap(); + } + } - txn1.commit().unwrap(); - assert!(match txn2.commit() { - Err(err) => { - matches!(err, Error::TransactionReadConflict) + // Define subsets of the entire range + let subsets = vec![ + (keys[0].as_ref()..=keys[2].as_ref()), + (keys[1].as_ref()..=keys[3].as_ref()), + (keys[2].as_ref()..=keys[4].as_ref()), + (keys[3].as_ref()..=keys[5].as_ref()), + (keys[4].as_ref()..=keys[6].as_ref()), + ]; + + // Scan each subset and collect versions + for subset in subsets { + let txn = store.begin().unwrap(); + let results: Vec<_> = txn + .scan_all_versions(subset, None) + .collect::>>() + .unwrap(); + + // Collect unique keys from the results + let unique_keys: HashSet<_> = + results.iter().map(|(k, _, _, _)| k.to_vec()).collect(); + + // Verify that the results contain all versions for each key in the subset + for key in unique_keys { + for (i, value) in values.iter().enumerate() { + let version = (i + 1) as u64; + let result = results + .iter() + .find(|(k, v, ver, _)| k == &key && v == value && *ver == version) + .unwrap(); + assert_eq!(result.1, *value); + assert_eq!(result.2, version); + } } - _ => false, - }); + } } - } - #[test] - fn g2_predicate() { - g2_item_predicate(true); - } + #[test] + fn test_scan_all_versions_with_batches() { + let (store, _) = create_store(false); + let keys = [ + Bytes::from("key1"), + Bytes::from("key2"), + Bytes::from("key3"), + Bytes::from("key4"), + Bytes::from("key5"), + ]; + let versions = [ + vec![ + Bytes::from("v1"), + Bytes::from("v2"), + Bytes::from("v3"), + Bytes::from("v4"), + ], + vec![Bytes::from("v1"), Bytes::from("v2")], + vec![ + Bytes::from("v1"), + Bytes::from("v2"), + Bytes::from("v3"), + Bytes::from("v4"), + ], + vec![Bytes::from("v1")], + vec![Bytes::from("v1")], + ]; + + // Insert multiple versions for each key + for (key, key_versions) in keys.iter().zip(versions.iter()) { + for (i, value) in key_versions.iter().enumerate() { + let mut txn = store.begin().unwrap(); + let version = (i + 1) as u64; + txn.set_at_ts(key, value, version).unwrap(); + txn.commit().unwrap(); + } + } - #[test] - fn transaction_delete_from_index() { - let (store, temp_dir) = create_store(false); + // Set the batch size + let batch_size: usize = 2; + + // Define a function to scan in batches + fn scan_in_batches( + store: &Store, + batch_size: usize, + ) -> Vec> { + let mut all_results = Vec::new(); + let mut last_key = Vec::new(); + let mut first_iteration = true; + + loop { + let txn = store.begin().unwrap(); + + // Create range using a clone of last_key + let key_clone = last_key.clone(); + let range = if first_iteration { + (Bound::Unbounded, Bound::Unbounded) + } else { + (Bound::Excluded(&key_clone[..]), Bound::Unbounded) + }; + + let mut batch_results = Vec::new(); + for result in txn.scan_all_versions(range, Some(batch_size)) { + let (k, v, ts, is_deleted) = result.unwrap(); + // Convert borrowed key to owned immediately + let key_bytes = Bytes::copy_from_slice(k); + let val_bytes = Bytes::from(v); + batch_results.push((key_bytes, val_bytes, ts, is_deleted)); + + // Update last_key with a new vector + last_key = k.to_vec(); + } - // Define key-value pairs for the test - let key1 = Bytes::from("foo1"); - let value = Bytes::from("baz"); - let key2 = Bytes::from("foo2"); + if batch_results.is_empty() { + break; + } - { - // Start a new read-write transaction (txn1) - let mut txn1 = store.begin().unwrap(); - txn1.set(&key1, &value).unwrap(); - txn1.set(&key2, &value).unwrap(); - txn1.commit().unwrap(); - } + first_iteration = false; + all_results.push(batch_results); + } - { - // Start another read-write transaction (txn2) - let mut txn2 = store.begin().unwrap(); - txn2.delete(&key1).unwrap(); - txn2.commit().unwrap(); - } + all_results + } - { - // Start a read-only transaction (txn3) - let mut txn3 = store.begin().unwrap(); - let val = txn3.get(&key1).unwrap(); - assert!(val.is_none()); - let val = txn3.get(&key2).unwrap().unwrap(); - assert_eq!(val, value.as_ref()); + // Scan in batches and collect the results + let all_results = scan_in_batches(&store, batch_size); + + // Verify the results + let expected_results = [ + vec![ + (Bytes::from("key1"), Bytes::from("v1"), 1, false), + (Bytes::from("key1"), Bytes::from("v2"), 2, false), + (Bytes::from("key1"), Bytes::from("v3"), 3, false), + (Bytes::from("key1"), Bytes::from("v4"), 4, false), + (Bytes::from("key2"), Bytes::from("v1"), 1, false), + (Bytes::from("key2"), Bytes::from("v2"), 2, false), + ], + vec![ + (Bytes::from("key3"), Bytes::from("v1"), 1, false), + (Bytes::from("key3"), Bytes::from("v2"), 2, false), + (Bytes::from("key3"), Bytes::from("v3"), 3, false), + (Bytes::from("key3"), Bytes::from("v4"), 4, false), + (Bytes::from("key4"), Bytes::from("v1"), 1, false), + ], + vec![(Bytes::from("key5"), Bytes::from("v1"), 1, false)], + ]; + + assert_eq!(all_results.len(), expected_results.len()); + + for (batch, expected_batch) in all_results.iter().zip(expected_results.iter()) { + assert_eq!(batch.len(), expected_batch.len()); + for (result, expected) in batch.iter().zip(expected_batch.iter()) { + assert_eq!(result, expected); + } + } } - - // Drop the store to simulate closing it - store.close().unwrap(); - - // sleep for a while to ensure the store is closed - std::thread::sleep(std::time::Duration::from_millis(10)); - - // Create a new Core instance with VariableSizeKey after dropping the previous one - let mut opts = Options::new(); - opts.dir = temp_dir.path().to_path_buf(); - let store = Store::new(opts).expect("should create store"); - - // Start a read-only transaction (txn4) - let mut txn4 = store.begin().unwrap(); - let val = txn4.get(&key1).unwrap(); - assert!(val.is_none()); - let val = txn4.get(&key2).unwrap().unwrap(); - assert_eq!(val, value.as_ref()); } - #[test] - fn test_insert_clear_read_key() { - let (store, _) = create_store(false); + // Concurrent operation tests + mod concurrency_tests { + use super::*; - // Key-value pair for the test - let key = Bytes::from("test_key"); - let value1 = Bytes::from("test_value1"); - let value2 = Bytes::from("test_value2"); - - // Insert key-value pair in a new transaction - { - let mut txn = store.begin().unwrap(); - txn.set(&key, &value1).unwrap(); - txn.commit().unwrap(); - } + #[test] + fn test_concurrent_transactions() { + let (store, _) = create_store(false); + let store = Arc::new(store); - { - let mut txn = store.begin().unwrap(); - txn.set(&key, &value2).unwrap(); - txn.commit().unwrap(); - } + // Define the number of concurrent transactions + let num_transactions = 1000; - // Clear the key in a separate transaction - { - let mut txn = store.begin().unwrap(); - txn.soft_delete(&key).unwrap(); - txn.commit().unwrap(); - } + // Define key-value pairs for the test + let keys: Vec = (0..num_transactions) + .map(|i| Bytes::from(format!("key{}", i))) + .collect(); + let values: Vec = (0..num_transactions) + .map(|i| Bytes::from(format!("value{}", i))) + .collect(); - // Read the key in a new transaction to verify it does not exist - { - let mut txn = store.begin().unwrap(); - assert!(txn.get(&key).unwrap().is_none()); - } - } + // Create a vector to store the handles of the spawned tasks + let mut handles = vec![]; - #[test] - fn multiple_savepoints_without_ssi() { - let (store, _) = create_store(false); - - // Key-value pair for the test - let key1 = Bytes::from("test_key1"); - let value1 = Bytes::from("test_value1"); - let key2 = Bytes::from("test_key2"); - let value2 = Bytes::from("test_value2"); - let key3 = Bytes::from("test_key3"); - let value3 = Bytes::from("test_value3"); - - // Start the transaction and write key1. - let mut txn1 = store.begin().unwrap(); - txn1.set(&key1, &value1).unwrap(); - - // Set the first savepoint. - txn1.set_savepoint().unwrap(); - - // Write key2 after the savepoint. - txn1.set(&key2, &value2).unwrap(); - - // Set another savepoint, stacking it onto the first one. - txn1.set_savepoint().unwrap(); - - txn1.set(&key3, &value3).unwrap(); - - // Just a sanity check that all three keys are present. - assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); - assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2); - assert_eq!(txn1.get(&key3).unwrap().unwrap(), value3); - - // Rollback to the latest (second) savepoint. This should make key3 - // go away while keeping key1 and key2. - txn1.rollback_to_savepoint().unwrap(); - assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); - assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2); - assert!(txn1.get(&key3).unwrap().is_none()); - - // Now roll back to the first savepoint. This should only - // keep key1 around. - txn1.rollback_to_savepoint().unwrap(); - assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); - assert!(txn1.get(&key2).unwrap().is_none()); - assert!(txn1.get(&key3).unwrap().is_none()); - - // Check that without any savepoints set the error is returned. - assert!(matches!( - txn1.rollback_to_savepoint(), - Err(Error::TransactionWithoutSavepoint) - ),); - - // Commit the transaction. - txn1.commit().unwrap(); - drop(txn1); - - // Start another transaction and check again for the keys. - let mut txn2 = store.begin().unwrap(); - assert_eq!(txn2.get(&key1).unwrap().unwrap(), value1); - assert!(txn2.get(&key2).unwrap().is_none()); - assert!(txn2.get(&key3).unwrap().is_none()); - txn2.commit().unwrap(); - } + // Create a vector to store the transaction IDs + let mut transaction_ids = vec![]; - #[test] - fn multiple_savepoints_with_ssi() { - let (store, _) = create_store(true); - - // Key-value pair for the test - let key1 = Bytes::from("test_key1"); - let value1 = Bytes::from("test_value1"); - let key2 = Bytes::from("test_key2"); - let value2 = Bytes::from("test_value2"); - let key3 = Bytes::from("test_key3"); - let value3 = Bytes::from("test_value3"); - - // Start the transaction and write key1. - let mut txn1 = store.begin().unwrap(); - txn1.set(&key1, &value1).unwrap(); - - // Set the first savepoint. - txn1.set_savepoint().unwrap(); - - // Write key2 after the savepoint. - txn1.set(&key2, &value2).unwrap(); - - // Set another savepoint, stacking it onto the first one. - txn1.set_savepoint().unwrap(); - - txn1.set(&key3, &value3).unwrap(); - - // Just a sanity check that all three keys are present. - assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); - assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2); - assert_eq!(txn1.get(&key3).unwrap().unwrap(), value3); - - // Rollback to the latest (second) savepoint. This should make key3 - // go away while keeping key1 and key2. - txn1.rollback_to_savepoint().unwrap(); - assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); - assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2); - assert!(txn1.get(&key3).unwrap().is_none()); - - // Now roll back to the first savepoint. This should only - // keep key1 around. - txn1.rollback_to_savepoint().unwrap(); - assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); - assert!(txn1.get(&key2).unwrap().is_none()); - assert!(txn1.get(&key3).unwrap().is_none()); - - // Check that without any savepoints set the error is returned. - assert!(matches!( - txn1.rollback_to_savepoint(), - Err(Error::TransactionWithoutSavepoint) - ),); - - // Commit the transaction. - txn1.commit().unwrap(); - drop(txn1); - - // Start another transaction and check again for the keys. - let mut txn2 = store.begin().unwrap(); - assert_eq!(txn2.get(&key1).unwrap().unwrap(), value1); - assert!(txn2.get(&key2).unwrap().is_none()); - assert!(txn2.get(&key3).unwrap().is_none()); - txn2.commit().unwrap(); - } + // Create a vector to store the commit timestamps + let mut commit_timestamps = vec![]; - #[test] - fn savepont_with_concurrent_read_txn_without_ssi() { - let (store, _) = create_store(false); - - // Key-value pair for the test - let key = Bytes::from("test_key1"); - let value = Bytes::from("test_value"); - let updated_value = Bytes::from("updated_test_value"); - let key2 = Bytes::from("test_key2"); - let value2 = Bytes::from("test_value2"); - - // Store the entry. - let mut txn1 = store.begin().unwrap(); - txn1.set(&key, &value).unwrap(); - txn1.commit().unwrap(); - drop(txn1); - - // Now open two concurrent transaction, where one - // is reading the value and the other one is - // updating it. Normally this would lead to a read-write - // conflict aborting the reading transaction. - // However, if the reading transaction rolls back - // to a savepoint before reading the vealu, as - // if it never happened, there should be no conflict. - let mut read_txn = store.begin().unwrap(); - let mut update_txn = store.begin().unwrap(); - - // This write is needed to force oracle.new_commit_ts(). - read_txn.set(&key2, &value2).unwrap(); - - read_txn.set_savepoint().unwrap(); - - read_txn.get(&key).unwrap().unwrap(); - update_txn.set(&key, &updated_value).unwrap(); - - // Commit the transaction. - update_txn.commit().unwrap(); - // Read transaction should commit without conflict after - // rolling back to the savepoint before reading `key`. - read_txn.rollback_to_savepoint().unwrap(); - read_txn.commit().unwrap(); - } + // Spawn concurrent transactions + for (key, value) in keys.iter().zip(values.iter()) { + let store = Arc::clone(&store); + let key = key.clone(); + let value = value.clone(); - #[test] - fn savepont_with_concurrent_read_txn_with_ssi() { - let (store, _) = create_store(true); + let handle = std::thread::spawn(move || { + // Start a new read-write transaction + let mut txn = store.begin().unwrap(); + txn.set(&key, &value).unwrap(); + txn.commit().unwrap(); + txn.versionstamp + }); - let key = Bytes::from("test_key1"); - let value = Bytes::from("test_value"); - let updated_value = Bytes::from("updated_test_value"); - let key2 = Bytes::from("test_key2"); - let value2 = Bytes::from("test_value2"); + handles.push(handle); + } - let mut txn1 = store.begin().unwrap(); - txn1.set(&key, &value).unwrap(); - txn1.commit().unwrap(); - drop(txn1); + // Wait for all tasks to complete and collect the results + for handle in handles { + let result = handle.join().unwrap(); + if let Some((transaction_id, commit_ts)) = result { + transaction_ids.push(transaction_id); + commit_timestamps.push(commit_ts); + } + } - let mut read_txn = store.begin().unwrap(); - let mut update_txn = store.begin().unwrap(); + // Verify that all transactions committed + assert_eq!(transaction_ids.len(), keys.len()); - read_txn.set(&key2, &value2).unwrap(); + // Sort the transaction IDs and commit timestamps because we just + // want to verify if the transaction ids are incremental and unique + transaction_ids.sort(); + commit_timestamps.sort(); - read_txn.set_savepoint().unwrap(); + // Verify that transaction IDs are incremental + for i in 1..transaction_ids.len() { + assert!(transaction_ids[i] > transaction_ids[i - 1]); + } - read_txn.get(&key).unwrap().unwrap(); - update_txn.set(&key, &updated_value).unwrap(); + // Verify that commit timestamps are incremental + for i in 1..commit_timestamps.len() { + assert!(commit_timestamps[i] >= commit_timestamps[i - 1]); + } - update_txn.commit().unwrap(); - read_txn.rollback_to_savepoint().unwrap(); - read_txn.commit().unwrap(); + // Drop the store to simulate closing it + store.close().unwrap(); + } } - #[test] - fn savepont_with_concurrent_scan_txn() { - // Scan set is only considered for SSI. - let (store, _) = create_store(true); - - let k1 = Bytes::from("k1"); - let value = Bytes::from("test_value"); - let updated_value = Bytes::from("updated_test_value"); - let k2 = Bytes::from("k2"); - let value2 = Bytes::from("test_value2"); - - let mut txn1 = store.begin().unwrap(); - txn1.set(&k1, &value).unwrap(); - txn1.commit().unwrap(); - drop(txn1); - - let mut read_txn = store.begin().unwrap(); - let mut update_txn = store.begin().unwrap(); + // Utility tests + mod utility_tests { + use super::*; - read_txn.set(&k2, &value2).unwrap(); + fn require_send(_: T) {} + fn require_sync(_: T) {} - read_txn.set_savepoint().unwrap(); + #[test] + fn is_send_sync() { + let (db, _) = create_store(false); - let range = "k0".as_bytes()..="k10".as_bytes(); - read_txn - .scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - update_txn.set(&k1, &updated_value).unwrap(); + let txn = db.begin().unwrap(); + require_send(txn); - update_txn.commit().unwrap(); - read_txn.rollback_to_savepoint().unwrap(); - read_txn.commit().unwrap(); - } - - #[test] - fn savepont_with_concurrent_scan_txn_with_new_write() { - // Scan set is only considered for SSI. - let (store, _) = create_store(true); - - let k1 = Bytes::from("k1"); - let value = Bytes::from("test_value"); - let k2 = Bytes::from("k2"); - let value2 = Bytes::from("test_value2"); - let k3 = Bytes::from("k3"); - let value3 = Bytes::from("test_value3"); - - let mut txn1 = store.begin().unwrap(); - txn1.set(&k1, &value).unwrap(); - txn1.commit().unwrap(); - drop(txn1); - - let mut read_txn = store.begin().unwrap(); - let mut update_txn = store.begin().unwrap(); - - read_txn.set(&k2, &value2).unwrap(); - // Put k1 into the read_txn's read set in order - // to force the conflict resolution check. - read_txn.get(&k1).unwrap(); - - read_txn.set_savepoint().unwrap(); - - let range = "k1".as_bytes()..="k3".as_bytes(); - read_txn - .scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - update_txn.set(&k3, &value3).unwrap(); - - update_txn.commit().unwrap(); - read_txn.rollback_to_savepoint().unwrap(); - read_txn.commit().unwrap(); + let txn = db.begin().unwrap(); + require_sync(txn); + } } - #[test] - fn savepoint_rollback_on_updated_key() { - let (store, _) = create_store(false); + // Performance/large data tests + #[cfg(not(debug_assertions))] + mod performance_tests { + use super::*; - let k1 = Bytes::from("k1"); - let value1 = Bytes::from("value1"); - let value2 = Bytes::from("value2"); - let value3 = Bytes::from("value3"); + const ENTRIES: usize = 400_000; + const KEY_SIZE: usize = 24; + const VALUE_SIZE: usize = 150; + const RNG_SEED: u64 = 3; - let mut txn1 = store.begin().unwrap(); - txn1.set(&k1, &value1).unwrap(); - txn1.set(&k1, &value2).unwrap(); - txn1.set_savepoint().unwrap(); - txn1.set(&k1, &value3).unwrap(); - txn1.rollback_to_savepoint().unwrap(); + fn fill_slice(slice: &mut [u8], rng: &mut StdRng) { + let mut i = 0; + while i + size_of::() < slice.len() { + let tmp = rng.gen::(); + slice[i..(i + size_of::())].copy_from_slice(&tmp.to_le_bytes()); + i += size_of::() + } + if i + size_of::() < slice.len() { + let tmp = rng.gen::(); + slice[i..(i + size_of::())].copy_from_slice(&tmp.to_le_bytes()); + i += size_of::() + } + if i + size_of::() < slice.len() { + let tmp = rng.gen::(); + slice[i..(i + size_of::())].copy_from_slice(&tmp.to_le_bytes()); + i += size_of::() + } + if i + size_of::() < slice.len() { + let tmp = rng.gen::(); + slice[i..(i + size_of::())].copy_from_slice(&tmp.to_le_bytes()); + i += size_of::() + } + if i + size_of::() < slice.len() { + slice[i] = rng.gen::(); + } + } - // The read value should be the one before the savepoint. - assert_eq!(txn1.get(&k1).unwrap().unwrap(), value2); - } + fn gen_pair(rng: &mut StdRng) -> ([u8; KEY_SIZE], Vec) { + let mut key = [0u8; KEY_SIZE]; + fill_slice(&mut key, rng); + let mut value = vec![0u8; VALUE_SIZE]; + fill_slice(&mut value, rng); - #[test] - fn savepoint_rollback_on_updated_key_with_scan() { - let (store, _) = create_store(false); - - let k1 = Bytes::from("k1"); - let value = Bytes::from("value1"); - let value2 = Bytes::from("value2"); - - let mut txn1 = store.begin().unwrap(); - txn1.set(&k1, &value).unwrap(); - txn1.set_savepoint().unwrap(); - txn1.set(&k1, &value2).unwrap(); - txn1.rollback_to_savepoint().unwrap(); - - // The scanned value should be the one before the savepoint. - let range = "k1".as_bytes()..="k3".as_bytes(); - let sr = txn1 - .scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - assert_eq!(sr[0].0, k1); - assert_eq!( - sr[0].1, - value.to_vec(), - "{}", - String::from_utf8_lossy(&sr[0].1) - ); - } + (key, value) + } - #[test] - fn ordered_writes() { - // This test ensures that the real time order of writes - // is preserved within a transaction. - use crate::entry::Record; - use crate::log::Error as LogError; - use crate::log::{MultiSegmentReader, SegmentRef}; - use crate::reader::{Reader, RecordReader}; - - let k1 = Bytes::from("k1"); - let v1 = Bytes::from("v1"); - let k2 = Bytes::from("k2"); - let v2 = Bytes::from("v2"); - let v3 = Bytes::from("v3"); - - let (store, tmp_dir) = create_store(false); - let mut txn = store.begin().unwrap(); - txn.set(&k1, &v1).unwrap(); - txn.set(&k2, &v2).unwrap(); - txn.set(&k1, &v3).unwrap(); - txn.commit().unwrap(); - store.close().unwrap(); - - let clog_subdir = tmp_dir.path().join("clog"); - let sr = SegmentRef::read_segments_from_directory(clog_subdir.as_path()).unwrap(); - let reader = MultiSegmentReader::new(sr).unwrap(); - let reader = Reader::new_from(reader); - let mut tx_reader = RecordReader::new(reader); - - // Expect ("k2", "v2") - let mut log_rec = Record::new(); - tx_reader.read_into(&mut log_rec).unwrap(); - assert_eq!(log_rec.key, k2); - assert_eq!(log_rec.value, v2); - - // Expect ("k1", "v3") - let mut log_rec = Record::new(); - tx_reader.read_into(&mut log_rec).unwrap(); - assert_eq!(log_rec.key, k1); - assert_eq!(log_rec.value, v3); - - // Eof - let mut log_rec = Record::new(); - assert!(matches!( - tx_reader.read_into(&mut log_rec), - Err(Error::LogError(LogError::Eof)) - ),); - } + fn make_rng() -> StdRng { + StdRng::seed_from_u64(RNG_SEED) + } - #[test] - fn test_scan_all_versions_single_key_multiple_versions() { - let (store, _) = create_store(false); - let key = Bytes::from("key1"); + #[ignore] + #[test] + fn insert_large_txn_and_get() { + let temp_dir = create_temp_directory(); + let mut opts = Options::new(); + opts.dir = temp_dir.path().to_path_buf(); - // Insert multiple versions of the same key - let values = [ - Bytes::from("value1"), - Bytes::from("value2"), - Bytes::from("value3"), - ]; + let store = Store::new(opts.clone()).expect("should create store"); + let mut rng = make_rng(); - for (i, value) in values.iter().enumerate() { let mut txn = store.begin().unwrap(); - let version = (i + 1) as u64; // Incremental version - txn.set_at_ts(&key, value, version).unwrap(); + for _ in 0..ENTRIES { + let (key, value) = gen_pair(&mut rng); + txn.set(&key, &value).unwrap(); + } txn.commit().unwrap(); - } - - let range = key.as_ref()..=key.as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, None) - .collect::>>() - .unwrap(); - - // Verify that the output contains all the versions of the key - assert_eq!(results.len(), values.len()); - for (i, (k, v, version, is_deleted)) in results.iter().enumerate() { - assert_eq!(k, &key); - assert_eq!(v, &values[i]); - assert_eq!(*version, (i + 1) as u64); - assert!(!(*is_deleted)); + drop(txn); + + // Read the keys from the store + let mut rng = make_rng(); + let mut txn = store.begin_with_mode(Mode::ReadOnly).unwrap(); + for _i in 0..ENTRIES { + let (key, _) = gen_pair(&mut rng); + txn.get(&key).unwrap(); + } } } - #[test] - fn test_scan_all_versions_multiple_keys_single_version_each() { - let (store, _) = create_store(false); - let keys = vec![ - Bytes::from("key1"), - Bytes::from("key2"), - Bytes::from("key3"), - ]; - let value = Bytes::from("value1"); - - for key in &keys { - let mut txn = store.begin().unwrap(); - txn.set_at_ts(key, &value, 1).unwrap(); - txn.commit().unwrap(); - } + // Edge case tests + mod edge_case_tests { + use super::*; - let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, None) - .collect::>>() - .unwrap(); + #[test] + fn sdb_delete_record_id_bug() { + let (store, _) = create_store(false); - assert_eq!(results.len(), keys.len()); - for (i, (k, v, version, is_deleted)) in results.iter().enumerate() { - assert_eq!(k, &keys[i]); - assert_eq!(v, &value); - assert_eq!(*version, 1); - assert!(!(*is_deleted)); - } - } + // Define key-value pairs for the test + let key1 = Bytes::copy_from_slice(&[ + 47, 33, 110, 100, 166, 192, 229, 30, 101, 24, 73, 242, 185, 36, 233, 242, 54, 96, + 72, 52, + ]); + let key2 = Bytes::copy_from_slice(&[ + 47, 33, 104, 98, 0, 0, 1, 141, 141, 42, 113, 8, 47, 166, 192, 229, 30, 101, 24, 73, + 242, 185, 36, 233, 242, 54, 96, 72, 52, + ]); + let value1 = Bytes::from("baz"); - #[test] - fn test_scan_all_versions_multiple_keys_multiple_versions_each() { - let (store, _) = create_store(false); - let keys = vec![ - Bytes::from("key1"), - Bytes::from("key2"), - Bytes::from("key3"), - ]; - let values = [ - Bytes::from("value1"), - Bytes::from("value2"), - Bytes::from("value3"), - ]; - - for key in &keys { - for (i, value) in values.iter().enumerate() { + { + // Start a new read-write transaction (txn) let mut txn = store.begin().unwrap(); - let version = (i + 1) as u64; - txn.set_at_ts(key, value, version).unwrap(); + txn.set(&key1, &value1).unwrap(); + txn.set(&key2, &value1).unwrap(); txn.commit().unwrap(); } - } - let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, None) - .collect::>>() - .unwrap(); - - let mut expected_results = Vec::new(); - for key in &keys { - for (i, value) in values.iter().enumerate() { - expected_results.push((key.clone(), value.clone(), (i + 1) as u64, false)); + let key3 = Bytes::copy_from_slice(&[47, 33, 117, 115, 114, 111, 111, 116, 0]); + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.set(&key3, &value1).unwrap(); + txn.commit().unwrap(); } - } - assert_eq!(results.len(), expected_results.len()); - for (result, expected) in results.iter().zip(expected_results.iter()) { - let (k, v, version, is_deleted) = result; - let (expected_key, expected_value, expected_version, expected_is_deleted) = expected; - assert_eq!(k, expected_key); - assert_eq!(v, expected_value); - assert_eq!(*version, *expected_version); - assert_eq!(*is_deleted, *expected_is_deleted); - } - } + let key4 = Bytes::copy_from_slice(&[47, 33, 117, 115, 114, 111, 111, 116, 0]); + let mut txn1 = store.begin().unwrap(); + txn1.get(&key4).unwrap(); - #[test] - fn test_scan_all_versions_deleted_records() { - let (store, _) = create_store(false); - let key = Bytes::from("key1"); - let value = Bytes::from("value1"); - - let mut txn = store.begin().unwrap(); - txn.set_at_ts(&key, &value, 1).unwrap(); - txn.commit().unwrap(); - - let mut txn = store.begin().unwrap(); - txn.soft_delete(&key).unwrap(); - txn.commit().unwrap(); - - let range = key.as_ref()..=key.as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, None) - .collect::>>() - .unwrap(); - - assert_eq!(results.len(), 2); - let (k, v, version, is_deleted) = &results[0]; - assert_eq!(k, &key); - assert_eq!(v, &value); - assert_eq!(*version, 1); - assert!(!(*is_deleted)); - - let (k, v, _, is_deleted) = &results[1]; - assert_eq!(k, &key); - assert_eq!(v, &Bytes::new()); - assert!(*is_deleted); - } + { + let mut txn2 = store.begin().unwrap(); + txn2.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, + 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, + 33, 116, 98, 117, 115, 101, 114, 0, + ])) + .unwrap(); + txn2.get(&Bytes::copy_from_slice(&[ + 47, 33, 110, 115, 116, 101, 115, 116, 45, 110, 115, 0, + ])) + .unwrap(); + txn2.get(&Bytes::copy_from_slice(&[ + 47, 33, 110, 115, 116, 101, 115, 116, 45, 110, 115, 0, + ])) + .unwrap(); + txn2.set( + &Bytes::copy_from_slice(&[ + 47, 33, 110, 115, 116, 101, 115, 116, 45, 110, 115, 0, + ]), + &value1, + ) + .unwrap(); - #[test] - fn test_scan_all_versions_multiple_keys_single_version_each_deleted() { - let (store, _) = create_store(false); - let keys = vec![ - Bytes::from("key1"), - Bytes::from("key2"), - Bytes::from("key3"), - ]; - let value = Bytes::from("value1"); - - for key in &keys { - let mut txn = store.begin().unwrap(); - txn.set_at_ts(key, &value, 1).unwrap(); - txn.commit().unwrap(); - } + txn2.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, + 72, 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, + 69, 0, + ])) + .unwrap(); + txn2.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, + 72, 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, + 69, 0, + ])) + .unwrap(); + txn2.set( + &Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, + 54, 72, 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, + 80, 74, 69, 0, + ]), + &value1, + ) + .unwrap(); - for key in &keys { - let mut txn = store.begin().unwrap(); - txn.soft_delete(key).unwrap(); - txn.commit().unwrap(); - } + txn2.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, + 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, + 33, 116, 98, 117, 115, 101, 114, 0, + ])) + .unwrap(); + txn2.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, + 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, + 33, 116, 98, 117, 115, 101, 114, 0, + ])) + .unwrap(); + txn2.set( + &Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, + 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, + 69, 0, 33, 116, 98, 117, 115, 101, 114, 0, + ]), + &value1, + ) + .unwrap(); - let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, None) - .collect::>>() - .unwrap(); - - assert_eq!(results.len(), keys.len() * 2); - for (i, (k, v, version, is_deleted)) in results.iter().enumerate() { - let key_index = i / 2; - let is_deleted_version = i % 2 == 1; - assert_eq!(k, &keys[key_index]); - if is_deleted_version { - assert_eq!(v, &Bytes::new()); - assert!(*is_deleted); - } else { - assert_eq!(v, &value); - assert_eq!(*version, 1); - assert!(!(*is_deleted)); + txn2.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, + 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, + 33, 116, 98, 117, 115, 101, 114, 0, + ])) + .unwrap(); + txn2.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, + 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, + 42, 117, 115, 101, 114, 0, 42, 0, 0, 0, 1, 106, 111, 104, 110, 0, + ])) + .unwrap(); + txn2.set( + &Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, + 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, + 69, 0, 42, 117, 115, 101, 114, 0, 42, 0, 0, 0, 1, 106, 111, 104, 110, 0, + ]), + &value1, + ) + .unwrap(); + + txn2.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, + 72, 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, + 69, 0, + ])) + .unwrap(); + + txn2.commit().unwrap(); } - } - } - #[test] - fn test_scan_all_versions_multiple_keys_multiple_versions_each_deleted() { - let (store, _) = create_store(false); - let keys = vec![ - Bytes::from("key1"), - Bytes::from("key2"), - Bytes::from("key3"), - ]; - let values = [ - Bytes::from("value1"), - Bytes::from("value2"), - Bytes::from("value3"), - ]; - - for key in &keys { - for (i, value) in values.iter().enumerate() { - let mut txn = store.begin().unwrap(); - let version = (i + 1) as u64; - txn.set_at_ts(key, value, version).unwrap(); - txn.commit().unwrap(); + { + let mut txn3 = store.begin().unwrap(); + txn3.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, + 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, + 42, 117, 115, 101, 114, 0, 42, 0, 0, 0, 1, 106, 111, 104, 110, 0, + ])) + .unwrap(); + txn3.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, + 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, + 33, 116, 98, 117, 115, 101, 114, 0, + ])) + .unwrap(); + txn3.delete(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 42, 48, 49, 72, 80, 54, 72, 71, + 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, 69, 0, + 42, 117, 115, 101, 114, 0, 42, 0, 0, 0, 1, 106, 111, 104, 110, 0, + ])) + .unwrap(); + txn3.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, + 72, 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, + 69, 0, + ])) + .unwrap(); + txn3.get(&Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, 54, + 72, 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, 80, 74, + 69, 0, + ])) + .unwrap(); + txn3.set( + &Bytes::copy_from_slice(&[ + 47, 42, 116, 101, 115, 116, 45, 110, 115, 0, 33, 100, 98, 48, 49, 72, 80, + 54, 72, 71, 72, 50, 50, 51, 89, 82, 49, 54, 51, 90, 52, 78, 56, 72, 69, 90, + 80, 74, 69, 0, + ]), + &value1, + ) + .unwrap(); + txn3.commit().unwrap(); } } - for key in &keys { - let mut txn = store.begin().unwrap(); - txn.soft_delete(key).unwrap(); - txn.commit().unwrap(); - } - - let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, None) - .collect::>>() - .unwrap(); + #[test] + fn sdb_bug_complex_key_handling_with_null_bytes_and_range_scan() { + let (store, _) = create_store(false); - let mut expected_results = Vec::new(); - for key in &keys { - for (i, value) in values.iter().enumerate() { - expected_results.push((key.clone(), value.clone(), (i + 1) as u64, false)); - } - expected_results.push((key.clone(), Bytes::new(), 0, true)); - } - - assert_eq!(results.len(), expected_results.len()); - for (result, expected) in results.iter().zip(expected_results.iter()) { - let (k, v, version, is_deleted) = result; - let (expected_key, expected_value, expected_version, expected_is_deleted) = expected; - assert_eq!(k, expected_key); - assert_eq!(v, expected_value); - if !expected_is_deleted { - assert_eq!(*version, *expected_version); - } - assert_eq!(*is_deleted, *expected_is_deleted); - } - } + // Define key-value pairs for the test + let k1 = Bytes::from("/*test\0*test\0*user\0!fdtest\0"); + let k2 = Bytes::from("/!nstest\0"); + let k3 = Bytes::from("/*test\0!dbtest\0"); + let k4 = Bytes::from("/*test\0*test\0!tbuser\0"); + let k5 = Bytes::from("/*test\0*test\0*user\0!fdtest\0"); - #[test] - fn test_scan_all_versions_soft_and_hard_delete() { - let (store, _) = create_store(false); - let key = Bytes::from("key1"); - let value = Bytes::from("value1"); + let v1 = Bytes::from("\u{3}\0\u{1}\u{4}test\0\0\0"); + let v2 = Bytes::from("\u{3}\0\u{1}\u{4}test\0\0\0\0"); + let v3 = Bytes::from( + "\u{4}\0\u{1}\u{4}user\0\0\0\u{1}\u{1}\0\u{1}\0\u{1}\0\u{1}\0\0\0\0\u{1}\0\0", + ); + let v4 = Bytes::from("\u{4}\u{1}\u{1}\u{2}\u{4}\u{1}\u{4}test\u{1}\u{4}user\0\0\0\0\0\0\u{1}\u{1}\u{1}\u{1}\u{1}\u{1}\u{1}\u{1}\u{1}\0\0\0"); - let mut txn = store.begin().unwrap(); - txn.set_at_ts(&key, &value, 1).unwrap(); - txn.commit().unwrap(); + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.get(&k1).unwrap(); + txn.get(&k2).unwrap(); + txn.get(&k2).unwrap(); + txn.set(&k2, &v1).unwrap(); - let mut txn = store.begin().unwrap(); - txn.soft_delete(&key).unwrap(); - txn.commit().unwrap(); + txn.get(&k3).unwrap(); + txn.get(&k3).unwrap(); + txn.set(&k3, &v2).unwrap(); - let mut txn = store.begin().unwrap(); - txn.delete(&key).unwrap(); - txn.commit().unwrap(); + txn.get(&k4).unwrap(); + txn.get(&k4).unwrap(); + txn.set(&k4, &v3).unwrap(); - let range = key.as_ref()..=key.as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, None) - .collect::>>() - .unwrap(); + txn.set(&k5, &v4).unwrap(); - assert_eq!(results.len(), 0); - } + txn.commit().unwrap(); + } - #[test] - fn test_scan_all_versions_range_boundaries() { - let (store, _) = create_store(false); - let keys = vec![ - Bytes::from("key1"), - Bytes::from("key2"), - Bytes::from("key3"), - ]; - let value = Bytes::from("value1"); - - for key in &keys { - let mut txn = store.begin().unwrap(); - txn.set_at_ts(key, &value, 1).unwrap(); - txn.commit().unwrap(); - } + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.get(&k5).unwrap(); + txn.delete(&k5).unwrap(); - // Inclusive range - let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, None) - .collect::>>() - .unwrap(); - assert_eq!(results.len(), keys.len()); - - // Exclusive range - let range = keys.first().unwrap().as_ref()..keys.last().unwrap().as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, None) - .collect::>>() - .unwrap(); - assert_eq!(results.len(), keys.len() - 1); - - // Unbounded range - let range = ..; - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, None) - .collect::>>() - .unwrap(); - assert_eq!(results.len(), keys.len()); - } + txn.commit().unwrap(); + } - #[test] - fn test_scan_all_versions_with_limit() { - let (store, _) = create_store(false); - let keys = vec![ - Bytes::from("key1"), - Bytes::from("key2"), - Bytes::from("key3"), - ]; - let value = Bytes::from("value1"); - - for key in &keys { - let mut txn = store.begin().unwrap(); - txn.set_at_ts(key, &value, 1).unwrap(); - txn.commit().unwrap(); - } + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.get(&k5).unwrap(); + txn.get(&k2).unwrap(); + txn.get(&k3).unwrap(); + txn.get(&k4).unwrap(); + txn.set(&k5, &v4).unwrap(); - let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, Some(2)) - .collect::>>() - .unwrap(); + let start_key = "/*test\0*test\0*user\0!fd\0"; + let end_key = "/*test\0*test\0*user\0!fd�"; - assert_eq!(results.len(), 2); - } + let range = start_key.as_bytes()..end_key.as_bytes(); + txn.scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); - #[test] - fn test_scan_all_versions_single_key_single_version() { - let (store, _) = create_store(false); - let key = Bytes::from("key1"); - let value = Bytes::from("value1"); - - let mut txn = store.begin().unwrap(); - txn.set_at_ts(&key, &value, 1).unwrap(); - txn.commit().unwrap(); - - let range = key.as_ref()..=key.as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, None) - .collect::>>() - .unwrap(); - - assert_eq!(results.len(), 1); - let (k, v, version, is_deleted) = &results[0]; - assert_eq!(k, &key); - assert_eq!(v, &value); - assert_eq!(*version, 1); - assert!(!(*is_deleted)); + txn.commit().unwrap(); + } + } } - #[test] - fn sdb_bug_complex_key_handling_with_null_bytes_and_range_scan() { - let (store, _) = create_store(false); - - // Define key-value pairs for the test - let k1 = Bytes::from("/*test\0*test\0*user\0!fdtest\0"); - let k2 = Bytes::from("/!nstest\0"); - let k3 = Bytes::from("/*test\0!dbtest\0"); - let k4 = Bytes::from("/*test\0*test\0!tbuser\0"); - let k5 = Bytes::from("/*test\0*test\0*user\0!fdtest\0"); - - let v1 = Bytes::from("\u{3}\0\u{1}\u{4}test\0\0\0"); - let v2 = Bytes::from("\u{3}\0\u{1}\u{4}test\0\0\0\0"); - let v3 = Bytes::from( - "\u{4}\0\u{1}\u{4}user\0\0\0\u{1}\u{1}\0\u{1}\0\u{1}\0\u{1}\0\0\0\0\u{1}\0\0", - ); - let v4 = Bytes::from("\u{4}\u{1}\u{1}\u{2}\u{4}\u{1}\u{4}test\u{1}\u{4}user\0\0\0\0\0\0\u{1}\u{1}\u{1}\u{1}\u{1}\u{1}\u{1}\u{1}\u{1}\0\0\0"); + // Delete tests + mod delete_tests { + use super::*; - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.get(&k1).unwrap(); - txn.get(&k2).unwrap(); - txn.get(&k2).unwrap(); - txn.set(&k2, &v1).unwrap(); + #[test] + fn test_soft_delete_and_reinsert() { + let (store, _) = create_store(false); - txn.get(&k3).unwrap(); - txn.get(&k3).unwrap(); - txn.set(&k3, &v2).unwrap(); + // Define key-value pairs for the test + let key1 = Bytes::from("k1"); + let value1 = Bytes::from("baz"); - txn.get(&k4).unwrap(); - txn.get(&k4).unwrap(); - txn.set(&k4, &v3).unwrap(); + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.set(&key1, &value1).unwrap(); + txn.commit().unwrap(); + } - txn.set(&k5, &v4).unwrap(); + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.soft_delete(&key1).unwrap(); + txn.commit().unwrap(); + } - txn.commit().unwrap(); - } + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + // txn.get(&key1).unwrap(); + txn.set(&key1, &value1).unwrap(); + let range = "k1".as_bytes()..="k3".as_bytes(); - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.get(&k5).unwrap(); - txn.delete(&k5).unwrap(); + txn.scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); - txn.commit().unwrap(); + txn.commit().unwrap(); + } } - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.get(&k5).unwrap(); - txn.get(&k2).unwrap(); - txn.get(&k3).unwrap(); - txn.get(&k4).unwrap(); - txn.set(&k5, &v4).unwrap(); + #[test] + fn test_hard_delete_and_reinsert() { + let (store, _) = create_store(false); - let start_key = "/*test\0*test\0*user\0!fd\0"; - let end_key = "/*test\0*test\0*user\0!fd�"; + // Define key-value pairs for the test + let key1 = Bytes::from("k1"); + let value1 = Bytes::from("baz"); - let range = start_key.as_bytes()..end_key.as_bytes(); - txn.scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.set(&key1, &value1).unwrap(); + txn.commit().unwrap(); + } - txn.commit().unwrap(); - } - } + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.delete(&key1).unwrap(); + txn.commit().unwrap(); + } - #[test] - fn test_scan_all_versions_with_limit_with_multiple_versions_per_key() { - let (store, _) = create_store(false); - let keys = vec![ - Bytes::from("key1"), - Bytes::from("key2"), - Bytes::from("key3"), - ]; - let values = [ - Bytes::from("value1"), - Bytes::from("value2"), - Bytes::from("value3"), - ]; - - // Insert multiple versions for each key - for key in &keys { - for (i, value) in values.iter().enumerate() { + { + // Start a new read-write transaction (txn) let mut txn = store.begin().unwrap(); - let version = (i + 1) as u64; - txn.set_at_ts(key, value, version).unwrap(); + // txn.get(&key1).unwrap(); + txn.set(&key1, &value1).unwrap(); + let range = "k1".as_bytes()..="k3".as_bytes(); + + txn.scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + txn.commit().unwrap(); } } - let range = keys.first().unwrap().as_ref()..=keys.last().unwrap().as_ref(); - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(range, Some(2)) - .collect::>>() - .unwrap(); - assert_eq!(results.len(), 6); // 3 versions for each of 2 keys + #[test] + fn test_hard_delete_and_reinsert_with_multiple_keys() { + let (store, _) = create_store(false); + + // Define key-value pairs for the test + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value1 = Bytes::from("baz"); - // Collect unique keys from the results - let unique_keys: HashSet<_> = results.iter().map(|(k, _, _, _)| k.to_vec()).collect(); + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.set(&key1, &value1).unwrap(); + txn.set(&key2, &value1).unwrap(); + txn.commit().unwrap(); + } - // Verify that the number of unique keys is equal to the limit - assert_eq!(unique_keys.len(), 2); + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.delete(&key1).unwrap(); + txn.commit().unwrap(); + } - // Verify that the results contain all versions for each key - for key in unique_keys { - let key_versions: Vec<_> = results.iter().filter(|(k, _, _, _)| k == &key).collect(); + { + // Start a new read-write transaction (txn) + let mut txn = store.begin().unwrap(); + txn.get(&key1).unwrap(); + txn.get(&key2).unwrap(); + txn.set(&key1, &value1).unwrap(); + let range = "k1".as_bytes()..="k3".as_bytes(); - assert_eq!(key_versions.len(), 3); // Should have all 3 versions + txn.scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); - // Check the latest version - let latest = key_versions - .iter() - .max_by_key(|(_, _, version, _)| version) - .unwrap(); - assert_eq!(latest.1, *values.last().unwrap()); - assert_eq!(latest.2, values.len() as u64); + txn.commit().unwrap(); + } } } - #[test] - fn test_soft_delete_and_reinsert() { - let (store, _) = create_store(false); + // Savepoint tests + mod savepoint_tests { + use super::*; - // Define key-value pairs for the test - let key1 = Bytes::from("k1"); - let value1 = Bytes::from("baz"); + #[test] + fn multiple_savepoints_without_ssi() { + let (store, _) = create_store(false); - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.set(&key1, &value1).unwrap(); - txn.commit().unwrap(); - } + // Key-value pair for the test + let key1 = Bytes::from("test_key1"); + let value1 = Bytes::from("test_value1"); + let key2 = Bytes::from("test_key2"); + let value2 = Bytes::from("test_value2"); + let key3 = Bytes::from("test_key3"); + let value3 = Bytes::from("test_value3"); - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.soft_delete(&key1).unwrap(); - txn.commit().unwrap(); - } + // Start the transaction and write key1. + let mut txn1 = store.begin().unwrap(); + txn1.set(&key1, &value1).unwrap(); - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - // txn.get(&key1).unwrap(); - txn.set(&key1, &value1).unwrap(); - let range = "k1".as_bytes()..="k3".as_bytes(); + // Set the first savepoint. + txn1.set_savepoint().unwrap(); - txn.scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); + // Write key2 after the savepoint. + txn1.set(&key2, &value2).unwrap(); - txn.commit().unwrap(); - } - } + // Set another savepoint, stacking it onto the first one. + txn1.set_savepoint().unwrap(); - #[test] - fn test_hard_delete_and_reinsert() { - let (store, _) = create_store(false); + txn1.set(&key3, &value3).unwrap(); - // Define key-value pairs for the test - let key1 = Bytes::from("k1"); - let value1 = Bytes::from("baz"); + // Just a sanity check that all three keys are present. + assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); + assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2); + assert_eq!(txn1.get(&key3).unwrap().unwrap(), value3); + + // Rollback to the latest (second) savepoint. This should make key3 + // go away while keeping key1 and key2. + txn1.rollback_to_savepoint().unwrap(); + assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); + assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2); + assert!(txn1.get(&key3).unwrap().is_none()); + + // Now roll back to the first savepoint. This should only + // keep key1 around. + txn1.rollback_to_savepoint().unwrap(); + assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); + assert!(txn1.get(&key2).unwrap().is_none()); + assert!(txn1.get(&key3).unwrap().is_none()); - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.set(&key1, &value1).unwrap(); - txn.commit().unwrap(); - } + // Check that without any savepoints set the error is returned. + assert!(matches!( + txn1.rollback_to_savepoint(), + Err(Error::TransactionWithoutSavepoint) + ),); - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.delete(&key1).unwrap(); - txn.commit().unwrap(); - } + // Commit the transaction. + txn1.commit().unwrap(); + drop(txn1); - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - // txn.get(&key1).unwrap(); - txn.set(&key1, &value1).unwrap(); - let range = "k1".as_bytes()..="k3".as_bytes(); + // Start another transaction and check again for the keys. + let mut txn2 = store.begin().unwrap(); + assert_eq!(txn2.get(&key1).unwrap().unwrap(), value1); + assert!(txn2.get(&key2).unwrap().is_none()); + assert!(txn2.get(&key3).unwrap().is_none()); + txn2.commit().unwrap(); + } - txn.scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); + #[test] + fn multiple_savepoints_with_ssi() { + let (store, _) = create_store(true); - txn.commit().unwrap(); - } - } + // Key-value pair for the test + let key1 = Bytes::from("test_key1"); + let value1 = Bytes::from("test_value1"); + let key2 = Bytes::from("test_key2"); + let value2 = Bytes::from("test_value2"); + let key3 = Bytes::from("test_key3"); + let value3 = Bytes::from("test_value3"); - #[test] - fn test_hard_delete_and_reinsert_with_multiple_keys() { - let (store, _) = create_store(false); + // Start the transaction and write key1. + let mut txn1 = store.begin().unwrap(); + txn1.set(&key1, &value1).unwrap(); - // Define key-value pairs for the test - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value1 = Bytes::from("baz"); + // Set the first savepoint. + txn1.set_savepoint().unwrap(); - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.set(&key1, &value1).unwrap(); - txn.set(&key2, &value1).unwrap(); - txn.commit().unwrap(); - } + // Write key2 after the savepoint. + txn1.set(&key2, &value2).unwrap(); - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.delete(&key1).unwrap(); - txn.commit().unwrap(); - } + // Set another savepoint, stacking it onto the first one. + txn1.set_savepoint().unwrap(); - { - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.get(&key1).unwrap(); - txn.get(&key2).unwrap(); - txn.set(&key1, &value1).unwrap(); - let range = "k1".as_bytes()..="k3".as_bytes(); + txn1.set(&key3, &value3).unwrap(); - txn.scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); + // Just a sanity check that all three keys are present. + assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); + assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2); + assert_eq!(txn1.get(&key3).unwrap().unwrap(), value3); + + // Rollback to the latest (second) savepoint. This should make key3 + // go away while keeping key1 and key2. + txn1.rollback_to_savepoint().unwrap(); + assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); + assert_eq!(txn1.get(&key2).unwrap().unwrap(), value2); + assert!(txn1.get(&key3).unwrap().is_none()); + + // Now roll back to the first savepoint. This should only + // keep key1 around. + txn1.rollback_to_savepoint().unwrap(); + assert_eq!(txn1.get(&key1).unwrap().unwrap(), value1); + assert!(txn1.get(&key2).unwrap().is_none()); + assert!(txn1.get(&key3).unwrap().is_none()); - txn.commit().unwrap(); - } - } + // Check that without any savepoints set the error is returned. + assert!(matches!( + txn1.rollback_to_savepoint(), + Err(Error::TransactionWithoutSavepoint) + ),); - #[test] - fn test_scan_includes_entries_before_commit() { - let (store, _) = create_store(false); - - // Define key-value pairs for the test - let key1 = Bytes::from("key1"); - let key2 = Bytes::from("key2"); - let key3 = Bytes::from("key3"); - let value1 = Bytes::from("value1"); - let value2 = Bytes::from("value2"); - let value3 = Bytes::from("value3"); - - // Start a new read-write transaction (txn) - let mut txn = store.begin().unwrap(); - txn.set(&key1, &value1).unwrap(); - txn.set(&key2, &value2).unwrap(); - txn.set(&key3, &value3).unwrap(); - - // Define the range for the scan - let range = "key1".as_bytes()..="key3".as_bytes(); - let results = txn - .scan(range, None) - .collect::, u64)>>>() - .expect("Scan should succeed"); - - // Verify the results - assert_eq!(results.len(), 3); - assert_eq!(results[0].0, key1); - assert_eq!(results[0].1, value1); - assert_eq!(results[1].0, key2); - assert_eq!(results[1].1, value2); - assert_eq!(results[2].0, key3); - assert_eq!(results[2].1, value3); - } + // Commit the transaction. + txn1.commit().unwrap(); + drop(txn1); - #[test] - fn test_scan_all_versions_with_subsets() { - let (store, _) = create_store(false); - let keys = vec![ - Bytes::from("key1"), - Bytes::from("key2"), - Bytes::from("key3"), - Bytes::from("key4"), - Bytes::from("key5"), - Bytes::from("key6"), - Bytes::from("key7"), - ]; - let values = [ - Bytes::from("value1"), - Bytes::from("value2"), - Bytes::from("value3"), - ]; - - // Insert multiple versions for each key - for key in &keys { - for (i, value) in values.iter().enumerate() { - let mut txn = store.begin().unwrap(); - let version = (i + 1) as u64; - txn.set_at_ts(key, value, version).unwrap(); - txn.commit().unwrap(); - } + // Start another transaction and check again for the keys. + let mut txn2 = store.begin().unwrap(); + assert_eq!(txn2.get(&key1).unwrap().unwrap(), value1); + assert!(txn2.get(&key2).unwrap().is_none()); + assert!(txn2.get(&key3).unwrap().is_none()); + txn2.commit().unwrap(); } - // Define subsets of the entire range - let subsets = vec![ - (keys[0].as_ref()..=keys[2].as_ref()), - (keys[1].as_ref()..=keys[3].as_ref()), - (keys[2].as_ref()..=keys[4].as_ref()), - (keys[3].as_ref()..=keys[5].as_ref()), - (keys[4].as_ref()..=keys[6].as_ref()), - ]; + #[test] + fn savepont_with_concurrent_read_txn_without_ssi() { + let (store, _) = create_store(false); - // Scan each subset and collect versions - for subset in subsets { - let txn = store.begin().unwrap(); - let results: Vec<_> = txn - .scan_all_versions(subset, None) - .collect::>>() - .unwrap(); + // Key-value pair for the test + let key = Bytes::from("test_key1"); + let value = Bytes::from("test_value"); + let updated_value = Bytes::from("updated_test_value"); + let key2 = Bytes::from("test_key2"); + let value2 = Bytes::from("test_value2"); - // Collect unique keys from the results - let unique_keys: HashSet<_> = results.iter().map(|(k, _, _, _)| k.to_vec()).collect(); + // Store the entry. + let mut txn1 = store.begin().unwrap(); + txn1.set(&key, &value).unwrap(); + txn1.commit().unwrap(); + drop(txn1); - // Verify that the results contain all versions for each key in the subset - for key in unique_keys { - for (i, value) in values.iter().enumerate() { - let version = (i + 1) as u64; - let result = results - .iter() - .find(|(k, v, ver, _)| k == &key && v == value && *ver == version) - .unwrap(); - assert_eq!(result.1, *value); - assert_eq!(result.2, version); - } - } - } - } + // Now open two concurrent transaction, where one + // is reading the value and the other one is + // updating it. Normally this would lead to a read-write + // conflict aborting the reading transaction. + // However, if the reading transaction rolls back + // to a savepoint before reading the vealu, as + // if it never happened, there should be no conflict. + let mut read_txn = store.begin().unwrap(); + let mut update_txn = store.begin().unwrap(); - #[test] - fn test_scan_all_versions_with_batches() { - let (store, _) = create_store(false); - let keys = [ - Bytes::from("key1"), - Bytes::from("key2"), - Bytes::from("key3"), - Bytes::from("key4"), - Bytes::from("key5"), - ]; - let versions = [ - vec![ - Bytes::from("v1"), - Bytes::from("v2"), - Bytes::from("v3"), - Bytes::from("v4"), - ], - vec![Bytes::from("v1"), Bytes::from("v2")], - vec![ - Bytes::from("v1"), - Bytes::from("v2"), - Bytes::from("v3"), - Bytes::from("v4"), - ], - vec![Bytes::from("v1")], - vec![Bytes::from("v1")], - ]; - - // Insert multiple versions for each key - for (key, key_versions) in keys.iter().zip(versions.iter()) { - for (i, value) in key_versions.iter().enumerate() { - let mut txn = store.begin().unwrap(); - let version = (i + 1) as u64; - txn.set_at_ts(key, value, version).unwrap(); - txn.commit().unwrap(); - } - } + // This write is needed to force oracle.new_commit_ts(). + read_txn.set(&key2, &value2).unwrap(); - // Set the batch size - let batch_size: usize = 2; + read_txn.set_savepoint().unwrap(); - // Define a function to scan in batches - fn scan_in_batches( - store: &Store, - batch_size: usize, - ) -> Vec> { - let mut all_results = Vec::new(); - let mut last_key = Vec::new(); - let mut first_iteration = true; + read_txn.get(&key).unwrap().unwrap(); + update_txn.set(&key, &updated_value).unwrap(); - loop { - let txn = store.begin().unwrap(); + // Commit the transaction. + update_txn.commit().unwrap(); + // Read transaction should commit without conflict after + // rolling back to the savepoint before reading `key`. + read_txn.rollback_to_savepoint().unwrap(); + read_txn.commit().unwrap(); + } - // Create range using a clone of last_key - let key_clone = last_key.clone(); - let range = if first_iteration { - (Bound::Unbounded, Bound::Unbounded) - } else { - (Bound::Excluded(&key_clone[..]), Bound::Unbounded) - }; - - let mut batch_results = Vec::new(); - for result in txn.scan_all_versions(range, Some(batch_size)) { - let (k, v, ts, is_deleted) = result.unwrap(); - // Convert borrowed key to owned immediately - let key_bytes = Bytes::copy_from_slice(k); - let val_bytes = Bytes::from(v); - batch_results.push((key_bytes, val_bytes, ts, is_deleted)); - - // Update last_key with a new vector - last_key = k.to_vec(); - } + #[test] + fn savepont_with_concurrent_read_txn_with_ssi() { + let (store, _) = create_store(true); - if batch_results.is_empty() { - break; - } + let key = Bytes::from("test_key1"); + let value = Bytes::from("test_value"); + let updated_value = Bytes::from("updated_test_value"); + let key2 = Bytes::from("test_key2"); + let value2 = Bytes::from("test_value2"); - first_iteration = false; - all_results.push(batch_results); - } + let mut txn1 = store.begin().unwrap(); + txn1.set(&key, &value).unwrap(); + txn1.commit().unwrap(); + drop(txn1); - all_results - } + let mut read_txn = store.begin().unwrap(); + let mut update_txn = store.begin().unwrap(); - // Scan in batches and collect the results - let all_results = scan_in_batches(&store, batch_size); + read_txn.set(&key2, &value2).unwrap(); - // Verify the results - let expected_results = [ - vec![ - (Bytes::from("key1"), Bytes::from("v1"), 1, false), - (Bytes::from("key1"), Bytes::from("v2"), 2, false), - (Bytes::from("key1"), Bytes::from("v3"), 3, false), - (Bytes::from("key1"), Bytes::from("v4"), 4, false), - (Bytes::from("key2"), Bytes::from("v1"), 1, false), - (Bytes::from("key2"), Bytes::from("v2"), 2, false), - ], - vec![ - (Bytes::from("key3"), Bytes::from("v1"), 1, false), - (Bytes::from("key3"), Bytes::from("v2"), 2, false), - (Bytes::from("key3"), Bytes::from("v3"), 3, false), - (Bytes::from("key3"), Bytes::from("v4"), 4, false), - (Bytes::from("key4"), Bytes::from("v1"), 1, false), - ], - vec![(Bytes::from("key5"), Bytes::from("v1"), 1, false)], - ]; + read_txn.set_savepoint().unwrap(); - assert_eq!(all_results.len(), expected_results.len()); + read_txn.get(&key).unwrap().unwrap(); + update_txn.set(&key, &updated_value).unwrap(); - for (batch, expected_batch) in all_results.iter().zip(expected_results.iter()) { - assert_eq!(batch.len(), expected_batch.len()); - for (result, expected) in batch.iter().zip(expected_batch.iter()) { - assert_eq!(result, expected); - } + update_txn.commit().unwrap(); + read_txn.rollback_to_savepoint().unwrap(); + read_txn.commit().unwrap(); } - } - - #[test] - fn test_concurrent_transactions() { - let (store, _) = create_store(false); - let store = Arc::new(store); - // Define the number of concurrent transactions - let num_transactions = 1000; + #[test] + fn savepont_with_concurrent_scan_txn() { + // Scan set is only considered for SSI. + let (store, _) = create_store(true); - // Define key-value pairs for the test - let keys: Vec = (0..num_transactions) - .map(|i| Bytes::from(format!("key{}", i))) - .collect(); - let values: Vec = (0..num_transactions) - .map(|i| Bytes::from(format!("value{}", i))) - .collect(); + let k1 = Bytes::from("k1"); + let value = Bytes::from("test_value"); + let updated_value = Bytes::from("updated_test_value"); + let k2 = Bytes::from("k2"); + let value2 = Bytes::from("test_value2"); - // Create a vector to store the handles of the spawned tasks - let mut handles = vec![]; + let mut txn1 = store.begin().unwrap(); + txn1.set(&k1, &value).unwrap(); + txn1.commit().unwrap(); + drop(txn1); - // Create a vector to store the transaction IDs - let mut transaction_ids = vec![]; + let mut read_txn = store.begin().unwrap(); + let mut update_txn = store.begin().unwrap(); - // Create a vector to store the commit timestamps - let mut commit_timestamps = vec![]; + read_txn.set(&k2, &value2).unwrap(); - // Spawn concurrent transactions - for (key, value) in keys.iter().zip(values.iter()) { - let store = Arc::clone(&store); - let key = key.clone(); - let value = value.clone(); + read_txn.set_savepoint().unwrap(); - let handle = std::thread::spawn(move || { - // Start a new read-write transaction - let mut txn = store.begin().unwrap(); - txn.set(&key, &value).unwrap(); - txn.commit().unwrap(); - txn.versionstamp - }); + let range = "k0".as_bytes()..="k10".as_bytes(); + read_txn + .scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + update_txn.set(&k1, &updated_value).unwrap(); - handles.push(handle); + update_txn.commit().unwrap(); + read_txn.rollback_to_savepoint().unwrap(); + read_txn.commit().unwrap(); } - // Wait for all tasks to complete and collect the results - for handle in handles { - let result = handle.join().unwrap(); - if let Some((transaction_id, commit_ts)) = result { - transaction_ids.push(transaction_id); - commit_timestamps.push(commit_ts); - } - } + #[test] + fn savepont_with_concurrent_scan_txn_with_new_write() { + // Scan set is only considered for SSI. + let (store, _) = create_store(true); - // Verify that all transactions committed - assert_eq!(transaction_ids.len(), keys.len()); + let k1 = Bytes::from("k1"); + let value = Bytes::from("test_value"); + let k2 = Bytes::from("k2"); + let value2 = Bytes::from("test_value2"); + let k3 = Bytes::from("k3"); + let value3 = Bytes::from("test_value3"); - // Sort the transaction IDs and commit timestamps because we just - // want to verify if the transaction ids are incremental and unique - transaction_ids.sort(); - commit_timestamps.sort(); + let mut txn1 = store.begin().unwrap(); + txn1.set(&k1, &value).unwrap(); + txn1.commit().unwrap(); + drop(txn1); - // Verify that transaction IDs are incremental - for i in 1..transaction_ids.len() { - assert!(transaction_ids[i] > transaction_ids[i - 1]); - } + let mut read_txn = store.begin().unwrap(); + let mut update_txn = store.begin().unwrap(); - // Verify that commit timestamps are incremental - for i in 1..commit_timestamps.len() { - assert!(commit_timestamps[i] >= commit_timestamps[i - 1]); - } + read_txn.set(&k2, &value2).unwrap(); + // Put k1 into the read_txn's read set in order + // to force the conflict resolution check. + read_txn.get(&k1).unwrap(); - // Drop the store to simulate closing it - store.close().unwrap(); - } + read_txn.set_savepoint().unwrap(); - #[test] - fn keys_with_tombstones_with_limit() { - for is_ssi in [false, true] { - let (store, _) = create_store(is_ssi); + let range = "k1".as_bytes()..="k3".as_bytes(); + read_txn + .scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + update_txn.set(&k3, &value3).unwrap(); - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let value = Bytes::from("v"); + update_txn.commit().unwrap(); + read_txn.rollback_to_savepoint().unwrap(); + read_txn.commit().unwrap(); + } - // First, insert the keys. - let mut txn1 = store.begin().unwrap(); - txn1.set(&key1, &value).unwrap(); - txn1.set(&key2, &value).unwrap(); - txn1.commit().unwrap(); + #[test] + fn savepoint_rollback_on_updated_key() { + let (store, _) = create_store(false); - // Then, soft-delete them. - let mut txn2 = store.begin().unwrap(); - txn2.soft_delete(&key1).unwrap(); - txn2.soft_delete(&key2).unwrap(); - txn2.commit().unwrap(); + let k1 = Bytes::from("k1"); + let value1 = Bytes::from("value1"); + let value2 = Bytes::from("value2"); + let value3 = Bytes::from("value3"); - // keys_with_tombstones() should still return `k1` and `k2` - // despite them being soft-deleted. - let range = "k1".as_bytes()..="k2".as_bytes(); - let txn3 = store.begin().unwrap(); - let results: Vec<_> = txn3.keys_with_tombstones(range.clone(), None).collect(); - assert_eq!(results, vec![&b"k1"[..], &b"k2"[..]]); + let mut txn1 = store.begin().unwrap(); + txn1.set(&k1, &value1).unwrap(); + txn1.set(&k1, &value2).unwrap(); + txn1.set_savepoint().unwrap(); + txn1.set(&k1, &value3).unwrap(); + txn1.rollback_to_savepoint().unwrap(); - // Check if the limit works correctly. - let txn4 = store.begin().unwrap(); - let limited_results: Vec<_> = txn4.keys_with_tombstones(range, Some(1)).collect(); - assert_eq!(limited_results.len(), 1); - assert_eq!(limited_results, vec![&b"k1"[..]]); + // The read value should be the one before the savepoint. + assert_eq!(txn1.get(&k1).unwrap().unwrap(), value2); } - } - #[test] - fn test_keys_api() { - for is_ssi in [false, true] { - let (store, _) = create_store(is_ssi); + #[test] + fn savepoint_rollback_on_updated_key_with_scan() { + let (store, _) = create_store(false); - let key1 = Bytes::from("k1"); - let key2 = Bytes::from("k2"); - let key3 = Bytes::from("k3"); - let value = Bytes::from("v"); + let k1 = Bytes::from("k1"); + let value = Bytes::from("value1"); + let value2 = Bytes::from("value2"); - // Insert the keys. let mut txn1 = store.begin().unwrap(); - txn1.set(&key1, &value).unwrap(); - txn1.set(&key2, &value).unwrap(); - txn1.set(&key3, &value).unwrap(); - txn1.commit().unwrap(); - - // Soft-delete key2. - let mut txn2 = store.begin().unwrap(); - txn2.soft_delete(&key2).unwrap(); - txn2.commit().unwrap(); + txn1.set(&k1, &value).unwrap(); + txn1.set_savepoint().unwrap(); + txn1.set(&k1, &value2).unwrap(); + txn1.rollback_to_savepoint().unwrap(); - // Test the keys function without a limit. + // The scanned value should be the one before the savepoint. let range = "k1".as_bytes()..="k3".as_bytes(); - let txn3 = store.begin_with_mode(Mode::ReadOnly).unwrap(); - let results: Vec<_> = txn3.keys(range.clone(), None).collect(); - assert_eq!(results, vec![&b"k1"[..], &b"k3"[..]]); - - // Test the keys function with a limit of 2. - let txn4 = store.begin_with_mode(Mode::ReadOnly).unwrap(); - let limited_results: Vec<_> = txn4.keys(range.clone(), Some(2)).collect(); - assert_eq!(limited_results, vec![&b"k1"[..], &b"k3"[..]]); - - // Test the keys function with a limit of 1. - let txn5 = store.begin_with_mode(Mode::ReadOnly).unwrap(); - let limited_results: Vec<_> = txn5.keys(range, Some(1)).collect(); - assert_eq!(limited_results, vec![&b"k1"[..]]); + let sr = txn1 + .scan(range, None) + .collect::, u64)>>>() + .expect("Scan should succeed"); + assert_eq!(sr[0].0, k1); + assert_eq!( + sr[0].1, + value.to_vec(), + "{}", + String::from_utf8_lossy(&sr[0].1) + ); + } + } + + // Tombstones tests + mod tombstones_tests { + use super::*; + + #[test] + fn keys_with_tombstones() { + for is_ssi in [false, true] { + let (store, _) = create_store(is_ssi); + + let key = Bytes::from("k"); + let value = Bytes::from("v"); + + // First, insert the key. + let mut txn1 = store.begin().unwrap(); + txn1.set(&key, &value).unwrap(); + txn1.commit().unwrap(); + + // Then, soft-delete it. + let mut txn2 = store.begin().unwrap(); + txn2.soft_delete(&key).unwrap(); + txn2.commit().unwrap(); + + // keys_with_tombstones() should still return `k` + // despite it being soft-deleted. + let range = "k".as_bytes()..="k".as_bytes(); + let txn3 = store.begin().unwrap(); + let results: Vec<_> = txn3.keys_with_tombstones(range, None).collect(); + assert_eq!(results, vec![b"k"]); + } + } + + #[test] + fn keys_with_tombstones_with_limit() { + for is_ssi in [false, true] { + let (store, _) = create_store(is_ssi); + + let key1 = Bytes::from("k1"); + let key2 = Bytes::from("k2"); + let value = Bytes::from("v"); + + // First, insert the keys. + let mut txn1 = store.begin().unwrap(); + txn1.set(&key1, &value).unwrap(); + txn1.set(&key2, &value).unwrap(); + txn1.commit().unwrap(); + + // Then, soft-delete them. + let mut txn2 = store.begin().unwrap(); + txn2.soft_delete(&key1).unwrap(); + txn2.soft_delete(&key2).unwrap(); + txn2.commit().unwrap(); + + // keys_with_tombstones() should still return `k1` and `k2` + // despite them being soft-deleted. + let range = "k1".as_bytes()..="k2".as_bytes(); + let txn3 = store.begin().unwrap(); + let results: Vec<_> = txn3.keys_with_tombstones(range.clone(), None).collect(); + assert_eq!(results, vec![&b"k1"[..], &b"k2"[..]]); + + // Check if the limit works correctly. + let txn4 = store.begin().unwrap(); + let limited_results: Vec<_> = txn4.keys_with_tombstones(range, Some(1)).collect(); + assert_eq!(limited_results.len(), 1); + assert_eq!(limited_results, vec![&b"k1"[..]]); + } } } }