Skip to content

Commit 66dea48

Browse files
committed
refactor: if a key does not exist at all, fetch_and_set(key, None) should not insert a tombstone
1 parent 45fbb37 commit 66dea48

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

src/mvcc/scoped_view.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ where
4949
value: Option<V>,
5050
) -> Result<(SeqMarked<V>, SeqMarked<V>), io::Error> {
5151
let old_value = self.get(key.clone()).await?;
52+
53+
if old_value.is_not_found() && value.is_none() {
54+
// No such entry at all, no need to create a tombstone for delete
55+
return Ok((old_value, SeqMarked::new_tombstone(0)));
56+
}
57+
5258
let order_key = self.set(key.clone(), value.clone());
5359
let new_value = match value {
5460
Some(v) => order_key.map(|_| v),
@@ -356,4 +362,27 @@ mod tests {
356362
let current_value = view.get(key("initial_key")).await.unwrap();
357363
assert_eq!(current_value, SeqMarked::new_normal(2, value("version2")));
358364
}
365+
366+
#[tokio::test]
367+
async fn test_scoped_view_trait_fetch_and_set_delete_nonexistent() {
368+
let mut view = MockScopedView::new();
369+
370+
// Try to delete a key that doesn't exist
371+
let (old_value, new_value) = view
372+
.fetch_and_set(key("nonexistent_key"), None)
373+
.await
374+
.unwrap();
375+
376+
// Should return not_found for old value
377+
assert_eq!(old_value, SeqMarked::new_not_found());
378+
// Should return tombstone with seq 0 (no tombstone created)
379+
assert_eq!(new_value, SeqMarked::new_tombstone(0));
380+
381+
// Verify the key still doesn't exist
382+
let current_value = view.get(key("nonexistent_key")).await.unwrap();
383+
assert_eq!(current_value, SeqMarked::new_not_found());
384+
385+
// Verify no entry was created in the mock data
386+
assert!(!view.data.contains_key(&key("nonexistent_key")));
387+
}
359388
}

src/mvcc/view.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,12 @@ where
288288
value: Option<V>,
289289
) -> Result<(SeqMarked<V>, SeqMarked<V>), io::Error> {
290290
let old_value = self.get(space, key.clone()).await?;
291+
292+
if old_value.is_not_found() && value.is_none() {
293+
// No such entry at all, no need to create a tombstone for delete
294+
return Ok((old_value, SeqMarked::new_tombstone(0)));
295+
}
296+
291297
let order_key = self.set(space, key, value.clone());
292298
let new_value = match value {
293299
Some(v) => order_key.map(|_| v),
@@ -1387,4 +1393,43 @@ mod tests {
13871393
SeqMarked::new_normal(11, value("resurrected"))
13881394
);
13891395
}
1396+
1397+
#[tokio::test]
1398+
async fn test_fetch_and_set_delete_nonexistent() {
1399+
let mut view = create_view(create_base_view());
1400+
1401+
// Try to delete a key that doesn't exist
1402+
let (old_value, new_value) = view
1403+
.fetch_and_set(TestSpace::Space1, key("nonexistent_key"), None)
1404+
.await
1405+
.unwrap();
1406+
1407+
// Should return not_found for old value
1408+
assert_eq!(old_value, SeqMarked::new_not_found());
1409+
// Should return tombstone with seq 0 (no tombstone created)
1410+
assert_eq!(new_value, SeqMarked::new_tombstone(0));
1411+
1412+
// Verify no tombstone was actually created in the changes
1413+
let key_exists_in_table = view
1414+
.changes
1415+
.get(&TestSpace::Space1)
1416+
.map(|table| {
1417+
table
1418+
.inner
1419+
.keys()
1420+
.any(|(k, _)| k == &key("nonexistent_key"))
1421+
})
1422+
.unwrap_or(false);
1423+
assert!(!key_exists_in_table);
1424+
1425+
// Verify the key still doesn't exist
1426+
let current_value = view
1427+
.get(TestSpace::Space1, key("nonexistent_key"))
1428+
.await
1429+
.unwrap();
1430+
assert_eq!(current_value, SeqMarked::new_not_found());
1431+
1432+
// Verify last_seq was not incremented
1433+
assert_eq!(view.last_seq, InternalSeq::new(10));
1434+
}
13901435
}

0 commit comments

Comments
 (0)