From 013e6a4b905aafdb1756361240347fe749f842cb Mon Sep 17 00:00:00 2001 From: Ray Liu Date: Mon, 7 Sep 2026 19:49:50 -0400 Subject: [PATCH] [5/7][store] implement tombstone deletion --- src/store.rs | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/src/store.rs b/src/store.rs index 5e206d0..0c809b2 100644 --- a/src/store.rs +++ b/src/store.rs @@ -33,6 +33,25 @@ impl Store { self.index.set_head(hash, address); } + pub fn delete(&mut self, key: &[u8]) -> bool { + let hash = hash(key); + + let Some(current) = self.find_address(hash, key) else { + return false; + }; + + if self.log.read(current).is_tombstone() { + return false; + } + + let previous = self.index.head(hash); + let address = self.log.append_tombstone(previous, key); + + self.index.set_head(hash, address); + + true + } + fn find_address(&self, hash: u64, key: &[u8]) -> Option
{ let mut address = self.index.head(hash); @@ -54,6 +73,15 @@ impl Store { mod tests { use super::*; + fn same_tag_keys() -> ([u8; 8], [u8; 8]) { + let first = 1_u64.to_le_bytes(); + let second = 7_313_u64.to_le_bytes(); + + assert_eq!(hash(&first) >> 49, hash(&second) >> 49); + + (first, second) + } + #[test] fn missing_key_returns_none() { let store = Store::new(8); @@ -126,4 +154,89 @@ mod tests { assert_eq!(store.get(b""), Some(b"".as_slice())); assert_eq!(store.get(b"key"), Some(b"".as_slice())); } + + #[test] + fn deletes_existing_key() { + let mut store = Store::new(8); + store.upsert(b"foo", b"value"); + + assert!(store.delete(b"foo")); + assert_eq!(store.get(b"foo"), None); + } + + #[test] + fn deleting_missing_key_returns_false() { + let mut store = Store::new(8); + + assert!(!store.delete(b"missing")); + } + + #[test] + fn repeated_delete_returns_false() { + let mut store = Store::new(8); + store.upsert(b"foo", b"value"); + + assert!(store.delete(b"foo")); + assert!(!store.delete(b"foo")); + } + + #[test] + fn upsert_after_delete_restores_key() { + let mut store = Store::new(8); + store.upsert(b"foo", b"old"); + store.delete(b"foo"); + + store.upsert(b"foo", b"new"); + + assert_eq!(store.get(b"foo"), Some(b"new".as_slice())); + } + + #[test] + fn deleting_one_key_preserves_colliding_keys() { + let mut store = Store::new(1); + store.upsert(b"foo", b"foo-value"); + store.upsert(b"bar", b"bar-value"); + + assert!(store.delete(b"foo")); + assert_eq!(store.get(b"foo"), None); + assert_eq!(store.get(b"bar"), Some(b"bar-value".as_slice())); + } + + #[test] + fn different_keys_with_the_same_tag_remain_readable() { + let mut store = Store::new(1); + let (first, second) = same_tag_keys(); + + store.upsert(&first, b"first-value"); + store.upsert(&second, b"second-value"); + + assert_eq!(store.get(&first), Some(b"first-value".as_slice())); + assert_eq!(store.get(&second), Some(b"second-value".as_slice())); + } + + #[test] + fn updating_one_same_tag_key_preserves_the_other() { + let mut store = Store::new(1); + let (first, second) = same_tag_keys(); + + store.upsert(&first, b"first-old"); + store.upsert(&second, b"second-value"); + store.upsert(&first, b"first-new"); + + assert_eq!(store.get(&first), Some(b"first-new".as_slice())); + assert_eq!(store.get(&second), Some(b"second-value".as_slice())); + } + + #[test] + fn deleting_one_same_tag_key_preserves_the_other() { + let mut store = Store::new(1); + let (first, second) = same_tag_keys(); + + store.upsert(&first, b"first-value"); + store.upsert(&second, b"second-value"); + + assert!(store.delete(&first)); + assert_eq!(store.get(&first), None); + assert_eq!(store.get(&second), Some(b"second-value".as_slice())); + } }