use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use tempfile::TempDir;
use test_log::test;
use crate::clock::{LogicalClock, MockLogicalClock};
use crate::comparator::{BytewiseComparator, InternalKeyComparator};
use crate::iter::{BoxedLSMIterator, CompactionIterator, MergingIterator};
use crate::sstable::table::{Table, TableWriter};
use crate::vfs::File;
use crate::vlog::{VLog, ValueLocation};
use crate::{InternalKey, InternalKeyKind, LSMIterator, Options, VLogChecksumLevel, Value};
static TEST_TABLE_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
fn create_internal_key(user_key: &str, sequence: u64, kind: InternalKeyKind) -> InternalKey {
InternalKey::new(user_key.as_bytes().to_vec(), sequence, kind, 0)
}
fn create_internal_key_with_timestamp(
user_key: &str,
sequence: u64,
kind: InternalKeyKind,
timestamp: u64,
) -> InternalKey {
InternalKey::new(user_key.as_bytes().to_vec(), sequence, kind, timestamp)
}
fn create_test_vlog() -> (Arc<VLog>, TempDir) {
let temp_dir = TempDir::new().unwrap();
let opts = Options {
vlog_checksum_verification: VLogChecksumLevel::Full,
path: temp_dir.path().to_path_buf(),
..Default::default()
};
std::fs::create_dir_all(opts.vlog_dir()).unwrap();
let vlog = Arc::new(VLog::new(Arc::new(opts)).unwrap());
(vlog, temp_dir)
}
fn create_vlog_value(vlog: &Arc<VLog>, key: &[u8], value: &[u8]) -> Value {
let pointer = vlog.append(key, value).unwrap();
ValueLocation::with_pointer(pointer).encode()
}
fn create_comparator() -> Arc<InternalKeyComparator> {
Arc::new(InternalKeyComparator::new(Arc::new(BytewiseComparator::default())))
}
fn wrap_buffer(src: Vec<u8>) -> Arc<dyn File> {
Arc::new(src)
}
fn build_table_iterator(entries: Vec<(InternalKey, Value)>) -> BoxedLSMIterator<'static> {
let table_id = TEST_TABLE_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
let mut buf = Vec::with_capacity(512);
let opts = Arc::new(Options::new());
{
let mut writer = TableWriter::new(&mut buf, table_id, Arc::clone(&opts), 0);
for (key, value) in entries {
writer.add(key, &value).unwrap();
}
writer.finish().unwrap();
}
let size = buf.len() as u64;
let table = Table::new(table_id, opts, wrap_buffer(buf), size).unwrap();
let table: &'static Table = Box::leak(Box::new(table));
Box::new(table.iter(None).unwrap())
}
#[test]
fn test_merge_iterator_sequence_ordering() {
let mut items1 = Vec::new();
for i in 0..10 {
if i % 2 == 0 {
let key = create_internal_key(&format!("key-{i:03}"), 200, InternalKeyKind::Delete);
let empty_value: Vec<u8> = Vec::new();
items1.push((key, empty_value));
}
}
let mut items2 = Vec::new();
for i in 0..20 {
let key = create_internal_key(&format!("key-{i:03}"), 100, InternalKeyKind::Set);
let value_str = format!("value-{i}");
let value_vec: Vec<u8> = value_str.into_bytes();
items2.push((key, value_vec));
}
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut merge_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false,
false, 0,
Arc::new(MockLogicalClock::new()),
vec![],
);
let mut result = Vec::new();
for item in merge_iter.by_ref() {
let (key, _) = item.unwrap();
let key_str = String::from_utf8_lossy(&key.user_key).to_string();
let seq = key.seq_num();
let kind = key.kind();
result.push((key_str, seq, kind));
}
for i in 1..result.len() {
assert!(
result[i - 1].0 <= result[i].0,
"Keys not in ascending order: {} vs {}",
result[i - 1].0,
result[i].0
);
}
for i in 0..10 {
if i % 2 == 0 {
let key = format!("key-{i:03}");
let entries: Vec<_> = result.iter().filter(|(k, _, _)| k == &key).collect();
assert_eq!(entries.len(), 1, "Key {key} has multiple entries");
let (_, seq, kind) = entries[0];
assert_eq!(*seq, 200, "Key {key} has wrong sequence");
assert_eq!(*kind, InternalKeyKind::Delete, "Key {key} has wrong kind");
}
}
assert_eq!(result.len(), 20, "Wrong number of entries");
}
#[test]
fn test_compaction_iterator_hard_delete_filtering() {
let mut items1 = Vec::new();
for i in 0..10 {
if i % 2 == 0 {
let key = create_internal_key(&format!("key-{i:03}"), 200, InternalKeyKind::Delete);
let empty_value: Vec<u8> = Vec::new();
items1.push((key, empty_value));
}
}
let mut items2 = Vec::new();
for i in 0..20 {
let key = create_internal_key(&format!("key-{i:03}"), 100, InternalKeyKind::Set);
let value_str = format!("value-{i}");
let value_vec: Vec<u8> = value_str.into_bytes();
items2.push((key, value_vec));
}
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false,
false,
0,
Arc::new(MockLogicalClock::new()),
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
let key_str = String::from_utf8_lossy(&key.user_key).to_string();
let seq = key.seq_num();
let kind = key.kind();
result.push((key_str, seq, kind));
}
for i in 1..result.len() {
assert!(
result[i - 1].0 <= result[i].0,
"Keys not in ascending order: {} vs {}",
result[i - 1].0,
result[i].0
);
}
for entry in &result {
let (key, seq, kind) = entry;
let key_num = key.strip_prefix("key-").unwrap().parse::<i32>().unwrap();
if key_num % 2 == 0 && key_num < 10 {
assert_eq!(*seq, 200, "Even key {key} has wrong sequence");
assert_eq!(*kind, InternalKeyKind::Delete, "Even key {key} has wrong kind");
} else {
assert_eq!(*seq, 100, "Key {key} has wrong sequence");
assert_eq!(*kind, InternalKeyKind::Set, "Key {key} has wrong kind");
}
}
let mut items1 = Vec::new();
for i in 0..10 {
if i % 2 == 0 {
let key = create_internal_key(&format!("key-{i:03}"), 200, InternalKeyKind::Delete);
let empty_value: Vec<u8> = Vec::new();
items1.push((key, empty_value));
}
}
let mut items2 = Vec::new();
for i in 0..20 {
let key = create_internal_key(&format!("key-{i:03}"), 100, InternalKeyKind::Set);
let value_str = format!("value-{i}");
let value_vec: Vec<u8> = value_str.into_bytes();
items2.push((key, value_vec));
}
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
true, false,
0,
Arc::new(MockLogicalClock::new()),
vec![],
);
let mut bottom_result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
let key_str = String::from_utf8_lossy(&key.user_key).to_string();
bottom_result.push(key_str);
}
for i in 0..20 {
let key = format!("key-{i:03}");
if i % 2 == 0 && i < 10 {
assert!(!bottom_result.contains(&key), "Key {key} should be removed at bottom level");
} else {
assert!(bottom_result.contains(&key), "Key {key} should exist at bottom level");
}
}
}
#[test(tokio::test)]
async fn test_combined_iterator_returns_latest_version() {
let (vlog, _temp_dir) = create_test_vlog();
let user_key = "key1";
let key_v1 = create_internal_key(user_key, 100, InternalKeyKind::Set);
let key_v2 = create_internal_key(user_key, 200, InternalKeyKind::Set);
let key_v3 = create_internal_key(user_key, 300, InternalKeyKind::Set);
let value_v1 = create_vlog_value(&vlog, user_key.as_bytes(), b"value1");
let value_v2 = create_vlog_value(&vlog, user_key.as_bytes(), b"value2");
let value_v3 = create_vlog_value(&vlog, user_key.as_bytes(), b"value3");
let items1 = vec![(key_v1, value_v1)]; let items2 = vec![(key_v2, value_v2)]; let items3 = vec![(key_v3, value_v3.clone())];
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let iter3 = build_table_iterator(items3);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2, iter3],
create_comparator(),
false, false,
0,
Arc::new(MockLogicalClock::default()),
vec![],
);
let result: Vec<_> = comp_iter.by_ref().map(|r| r.unwrap()).collect();
assert_eq!(result.len(), 1);
let (returned_key, returned_value) = &result[0];
assert_eq!(&returned_key.user_key, user_key.as_bytes());
assert_eq!(returned_key.seq_num(), 300);
assert_eq!(returned_key.kind(), InternalKeyKind::Set);
assert_eq!(
returned_value, &value_v3,
"Should return the value corresponding to the latest version (seq=300)"
);
}
#[test(tokio::test)]
async fn test_combined_iterator_adds_older_versions_to_delete_list() {
let (vlog, _temp_dir) = create_test_vlog();
let user_key = "key1";
let key_v1 = create_internal_key(user_key, 100, InternalKeyKind::Set);
let key_v2 = create_internal_key(user_key, 200, InternalKeyKind::Set);
let key_v3 = create_internal_key(user_key, 300, InternalKeyKind::Set);
let value_v1 = create_vlog_value(&vlog, user_key.as_bytes(), b"value1");
let value_v2 = create_vlog_value(&vlog, user_key.as_bytes(), b"value2");
let value_v3 = create_vlog_value(&vlog, user_key.as_bytes(), b"value3");
let items1 = vec![(key_v1, value_v1)];
let items2 = vec![(key_v2, value_v2)];
let items3 = vec![(key_v3, value_v3.clone())];
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let iter3 = build_table_iterator(items3);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2, iter3],
create_comparator(),
false, false,
0,
Arc::new(MockLogicalClock::default()),
vec![],
);
let result: Vec<_> = comp_iter.by_ref().map(|r| r.unwrap()).collect();
assert_eq!(result.len(), 1);
let (returned_key, returned_value) = &result[0];
assert_eq!(returned_key.seq_num(), 300);
assert_eq!(
returned_value, &value_v3,
"Should return the value corresponding to the latest version (seq=300)"
);
}
#[test(tokio::test)]
async fn test_hard_delete_at_bottom_level() {
let (vlog, _temp_dir) = create_test_vlog();
let user_key = "key1";
let hard_delete_key = create_internal_key(user_key, 200, InternalKeyKind::Delete);
let value_key = create_internal_key(user_key, 100, InternalKeyKind::Set);
let empty_value: Vec<u8> = Vec::new();
let actual_value = create_vlog_value(&vlog, user_key.as_bytes(), b"value1");
let items1 = vec![(hard_delete_key, empty_value)];
let items2 = vec![(value_key, actual_value)];
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
true, false,
0,
Arc::new(MockLogicalClock::default()),
vec![],
);
let result: Vec<_> = comp_iter.by_ref().map(|r| r.unwrap()).collect();
assert_eq!(result.len(), 0, "At bottom level, hard_delete should not be returned");
}
#[test(tokio::test)]
async fn test_hard_delete_at_non_bottom_level() {
let (vlog, _temp_dir) = create_test_vlog();
let user_key = "key1";
let hard_delete_key = create_internal_key(user_key, 200, InternalKeyKind::Delete);
let value_key = create_internal_key(user_key, 100, InternalKeyKind::Set);
let empty_value: Vec<u8> = Vec::new();
let actual_value = create_vlog_value(&vlog, user_key.as_bytes(), b"value1");
let items1 = vec![(hard_delete_key, empty_value)];
let items2 = vec![(value_key, actual_value)];
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, false,
0,
Arc::new(MockLogicalClock::default()),
vec![],
);
let result: Vec<_> = comp_iter.by_ref().map(|r| r.unwrap()).collect();
assert_eq!(result.len(), 1, "At non-bottom level, hard_delete should be returned");
let (returned_key, returned_value) = &result[0];
assert_eq!(&returned_key.user_key, user_key.as_bytes());
assert_eq!(returned_key.seq_num(), 200);
assert_eq!(returned_key.kind(), InternalKeyKind::Delete);
assert_eq!(returned_value.len(), 0, "hard_delete should have empty value");
}
#[test(tokio::test)]
async fn test_multiple_keys_with_mixed_scenarios() {
let (vlog, _temp_dir) = create_test_vlog();
let key1_v1 = create_internal_key("key1", 100, InternalKeyKind::Set);
let key1_v2 = create_internal_key("key1", 200, InternalKeyKind::Set);
let key1_val1 = create_vlog_value(&vlog, b"key1", b"value1_old");
let key1_val2 = create_vlog_value(&vlog, b"key1", b"value1_new");
let key2_v1 = create_internal_key("key2", 110, InternalKeyKind::Set);
let key2_v2 = create_internal_key("key2", 210, InternalKeyKind::Delete);
let key2_val1 = create_vlog_value(&vlog, b"key2", b"value2");
let key2_val2: Vec<u8> = Vec::new();
let key3_v1 = create_internal_key("key3", 150, InternalKeyKind::Set);
let key3_val1 = create_vlog_value(&vlog, b"key3", b"value3");
let items1 = vec![(key1_v2, key1_val2.clone()), (key2_v2, key2_val2)];
let items2 = vec![(key1_v1, key1_val1), (key3_v1, key3_val1.clone())];
let items3 = vec![(key2_v1, key2_val1)];
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let iter3 = build_table_iterator(items3);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2, iter3],
create_comparator(),
false, false,
0,
Arc::new(MockLogicalClock::default()),
vec![],
);
let result: Vec<_> = comp_iter.by_ref().map(|r| r.unwrap()).collect();
assert_eq!(result.len(), 3);
let keys: Vec<String> =
result.iter().map(|(k, _)| String::from_utf8_lossy(&k.user_key).to_string()).collect();
assert_eq!(keys, vec!["key1", "key2", "key3"]);
assert_eq!(result[0].0.seq_num(), 200); assert_eq!(result[1].0.seq_num(), 210); assert_eq!(result[2].0.seq_num(), 150);
assert_eq!(result[0].0.kind(), InternalKeyKind::Set);
assert_eq!(result[1].0.kind(), InternalKeyKind::Delete);
assert_eq!(result[2].0.kind(), InternalKeyKind::Set);
assert_eq!(result[0].1, key1_val2, "key1 should have the latest value (key1_val2)");
assert_eq!(result[1].1.len(), 0, "key2 hard_delete should have empty value");
assert_eq!(result[2].1, key3_val1, "key3 should have its only value (key3_val1)");
}
#[test]
fn test_no_vlog_no_delete_list() {
let user_key = "key1";
let key_v1 = create_internal_key(user_key, 100, InternalKeyKind::Set);
let key_v2 = create_internal_key(user_key, 200, InternalKeyKind::Set);
let value_v1: Vec<u8> = b"value1".to_vec();
let value_v2: Vec<u8> = b"value2".to_vec();
let items1 = vec![(key_v2, value_v2)];
let items2 = vec![(key_v1, value_v1)];
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, false,
0,
Arc::new(MockLogicalClock::default()),
vec![],
);
let result: Vec<_> = comp_iter.by_ref().map(|r| r.unwrap()).collect();
assert_eq!(result.len(), 1);
let (returned_key, returned_value) = &result[0];
assert_eq!(&returned_key.user_key, user_key.as_bytes());
assert_eq!(returned_key.seq_num(), 200);
assert_eq!(
returned_value.as_slice(),
b"value2",
"Should return the value corresponding to the latest version (seq=200)"
);
}
#[test(tokio::test)]
async fn test_sequence_ordering_across_iterators() {
let (vlog, _temp_dir) = create_test_vlog();
let key_a_v3 = create_internal_key("key_a", 300, InternalKeyKind::Set);
let key_c_v3 = create_internal_key("key_c", 350, InternalKeyKind::Delete);
let val_a_v3 = create_vlog_value(&vlog, b"key_a", b"value_a_latest");
let val_c_v3: Vec<u8> = Vec::new();
let key_a_v2 = create_internal_key("key_a", 200, InternalKeyKind::Set);
let key_b_v2 = create_internal_key("key_b", 250, InternalKeyKind::Set);
let key_c_v2 = create_internal_key("key_c", 220, InternalKeyKind::Set);
let val_a_v2 = create_vlog_value(&vlog, b"key_a", b"value_a_middle");
let val_b_v2 = create_vlog_value(&vlog, b"key_b", b"value_b");
let val_c_v2 = create_vlog_value(&vlog, b"key_c", b"value_c_middle");
let key_a_v1 = create_internal_key("key_a", 100, InternalKeyKind::Set);
let key_c_v1 = create_internal_key("key_c", 120, InternalKeyKind::Set);
let key_d_v1 = create_internal_key("key_d", 150, InternalKeyKind::Set);
let val_a_v1 = create_vlog_value(&vlog, b"key_a", b"value_a_oldest");
let val_c_v1 = create_vlog_value(&vlog, b"key_c", b"value_c_oldest");
let val_d_v1 = create_vlog_value(&vlog, b"key_d", b"value_d");
let items1 = vec![(key_a_v3, val_a_v3.clone()), (key_c_v3, val_c_v3)];
let items2 = vec![(key_a_v2, val_a_v2), (key_b_v2, val_b_v2.clone()), (key_c_v2, val_c_v2)];
let items3 = vec![(key_a_v1, val_a_v1), (key_c_v1, val_c_v1), (key_d_v1, val_d_v1.clone())];
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let iter3 = build_table_iterator(items3);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2, iter3],
create_comparator(),
false, false,
0,
Arc::new(MockLogicalClock::default()),
vec![],
);
let result: Vec<_> = comp_iter.by_ref().map(|r| r.unwrap()).collect();
assert_eq!(result.len(), 4);
let keys: Vec<String> =
result.iter().map(|(k, _)| String::from_utf8_lossy(&k.user_key).to_string()).collect();
assert_eq!(keys, vec!["key_a", "key_b", "key_c", "key_d"]);
assert_eq!(result[0].0.seq_num(), 300); assert_eq!(result[1].0.seq_num(), 250); assert_eq!(result[2].0.seq_num(), 350); assert_eq!(result[3].0.seq_num(), 150);
assert_eq!(result[0].0.kind(), InternalKeyKind::Set);
assert_eq!(result[1].0.kind(), InternalKeyKind::Set);
assert_eq!(result[2].0.kind(), InternalKeyKind::Delete);
assert_eq!(result[3].0.kind(), InternalKeyKind::Set);
assert_eq!(result[0].1, val_a_v3, "key_a should have the latest value (val_a_v3)");
assert_eq!(result[1].1, val_b_v2, "key_b should have its only value (val_b_v2)");
assert_eq!(result[2].1.len(), 0, "key_c hard_delete should have empty value");
assert_eq!(result[3].1, val_d_v1, "key_d should have its only value (val_d_v1)");
}
#[test(tokio::test)]
async fn test_compaction_iterator_versioning_retention_logic() {
let (vlog, _temp_dir) = create_test_vlog();
let current_time = 1000000000000; let retention_period_ns = 5_000_000_000;
let recent_time = current_time - 1_000_000_000; let old_time = current_time - 3_000_000_000; let very_old_time = current_time - 10_000_000_000;
let key1_recent_set =
create_internal_key_with_timestamp("key1", 100, InternalKeyKind::Set, recent_time);
let key1_old_set =
create_internal_key_with_timestamp("key1", 200, InternalKeyKind::Set, old_time);
let key1_recent_delete =
create_internal_key_with_timestamp("key1", 300, InternalKeyKind::Delete, recent_time);
let key2_old_set =
create_internal_key_with_timestamp("key2", 400, InternalKeyKind::Set, old_time);
let key2_recent_set =
create_internal_key_with_timestamp("key2", 500, InternalKeyKind::Set, recent_time);
let key2_old_delete =
create_internal_key_with_timestamp("key2", 600, InternalKeyKind::Delete, old_time);
let key3_very_old_set1 =
create_internal_key_with_timestamp("key3", 700, InternalKeyKind::Set, very_old_time);
let key3_very_old_set2 =
create_internal_key_with_timestamp("key3", 800, InternalKeyKind::Set, very_old_time);
let key3_recent_set =
create_internal_key_with_timestamp("key3", 900, InternalKeyKind::Set, recent_time);
let val1_recent = create_vlog_value(&vlog, b"key1", b"value1_recent");
let val1_old = create_vlog_value(&vlog, b"key1", b"value1_old");
let val2_old = create_vlog_value(&vlog, b"key2", b"value2_old");
let val2_recent = create_vlog_value(&vlog, b"key2", b"value2_recent");
let val3_very_old1 = create_vlog_value(&vlog, b"key3", b"value3_very_old1");
let val3_very_old2 = create_vlog_value(&vlog, b"key3", b"value3_very_old2");
let val3_recent = create_vlog_value(&vlog, b"key3", b"value3_recent");
let items1 = vec![
(key1_recent_delete, Vec::new()), (key1_old_set, val1_old),
(key1_recent_set, val1_recent),
];
let items2 = vec![
(key2_old_delete, Vec::new()), (key2_recent_set, val2_recent),
(key2_old_set, val2_old),
];
let items3 = vec![
(key3_recent_set, val3_recent),
(key3_very_old_set2, val3_very_old2),
(key3_very_old_set1, val3_very_old1),
];
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let iter3 = build_table_iterator(items3);
let clock = Arc::new(MockLogicalClock::with_timestamp(current_time));
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2, iter3],
create_comparator(),
false, true, retention_period_ns,
clock,
vec![],
);
let result: Vec<_> = comp_iter.by_ref().map(|r| r.unwrap()).collect();
assert_eq!(result.len(), 7, "Expected 7 items: 3 from key1, 3 from key2, 1 from key3");
let key1_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key1").collect();
assert_eq!(key1_versions.len(), 3, "key1 should have 3 versions within retention");
let key2_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key2").collect();
assert_eq!(key2_versions.len(), 3, "key2 should have 3 versions within retention");
let key3_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key3").collect();
assert_eq!(
key3_versions.len(),
1,
"key3 should have only 1 version (recent), very old versions filtered"
);
assert_eq!(key1_versions[0].0.seq_num(), 300); assert_eq!(key1_versions[1].0.seq_num(), 200); assert_eq!(key1_versions[2].0.seq_num(), 100);
assert_eq!(key2_versions[0].0.seq_num(), 600); assert_eq!(key2_versions[1].0.seq_num(), 500); assert_eq!(key2_versions[2].0.seq_num(), 400);
assert_eq!(key3_versions[0].0.seq_num(), 900); }
#[test(tokio::test)]
async fn test_compaction_iterator_versioning_retention_bottom_level() {
let (vlog, _temp_dir) = create_test_vlog();
let current_time = 1000000000000; let retention_period_ns = 5_000_000_000;
let recent_time = current_time - 1_000_000_000; let old_time = current_time - 3_000_000_000; let very_old_time = current_time - 10_000_000_000;
let key1_recent_set =
create_internal_key_with_timestamp("key1", 100, InternalKeyKind::Set, recent_time);
let key1_old_set =
create_internal_key_with_timestamp("key1", 200, InternalKeyKind::Set, old_time);
let key1_recent_delete =
create_internal_key_with_timestamp("key1", 300, InternalKeyKind::Delete, recent_time);
let key2_delete =
create_internal_key_with_timestamp("key2", 400, InternalKeyKind::Delete, recent_time);
let key3_very_old_set1 =
create_internal_key_with_timestamp("key3", 500, InternalKeyKind::Set, very_old_time);
let key3_very_old_set2 =
create_internal_key_with_timestamp("key3", 600, InternalKeyKind::Set, very_old_time);
let key3_recent_set =
create_internal_key_with_timestamp("key3", 700, InternalKeyKind::Set, recent_time);
let key4_old_set =
create_internal_key_with_timestamp("key4", 800, InternalKeyKind::Set, old_time);
let key4_very_old_delete =
create_internal_key_with_timestamp("key4", 900, InternalKeyKind::Delete, very_old_time);
let val1_recent = create_vlog_value(&vlog, b"key1", b"value1_recent");
let val1_old = create_vlog_value(&vlog, b"key1", b"value1_old");
let val3_very_old1 = create_vlog_value(&vlog, b"key3", b"value3_very_old1");
let val3_very_old2 = create_vlog_value(&vlog, b"key3", b"value3_very_old2");
let val3_recent = create_vlog_value(&vlog, b"key3", b"value3_recent");
let val4_old = create_vlog_value(&vlog, b"key4", b"value4_old");
let items1 = vec![
(key1_recent_delete, Vec::new()), (key1_old_set, val1_old),
(key1_recent_set, val1_recent),
];
let items2 = vec![
(key2_delete, Vec::new()), ];
let items3 = vec![
(key3_recent_set, val3_recent),
(key3_very_old_set2, val3_very_old2),
(key3_very_old_set1, val3_very_old1),
];
let items4 = vec![
(key4_very_old_delete, Vec::new()), (key4_old_set, val4_old),
];
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let iter3 = build_table_iterator(items3);
let iter4 = build_table_iterator(items4);
let clock = Arc::new(MockLogicalClock::with_timestamp(current_time));
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2, iter3, iter4],
create_comparator(),
true, true, retention_period_ns,
clock,
vec![],
);
let result: Vec<_> = comp_iter.by_ref().map(|r| r.unwrap()).collect();
assert_eq!(result.len(), 1, "Expected 1 item: only key3 with recent SET");
let key1_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key1").collect();
assert_eq!(
key1_versions.len(),
0,
"key1 should have 0 versions (DELETE is latest at bottom level, entire key discarded)"
);
let key2_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key2").collect();
assert_eq!(key2_versions.len(), 0, "key2 should have 0 versions (DELETE-only at bottom level)");
let key3_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key3").collect();
assert_eq!(
key3_versions.len(),
1,
"key3 should have only 1 version (recent), very old versions filtered"
);
assert_eq!(key3_versions[0].0.seq_num(), 700);
let key4_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key4").collect();
assert_eq!(
key4_versions.len(),
0,
"key4 should have 0 versions (DELETE is latest at bottom level, entire key discarded)"
);
}
#[test(tokio::test)]
async fn test_compaction_iterator_no_versioning_non_bottom_level() {
let (vlog, _temp_dir) = create_test_vlog();
let current_time = 1000000000000; let retention_period_ns = 0;
let recent_time = current_time - 1_000_000_000; let old_time = current_time - 3_000_000_000; let very_old_time = current_time - 10_000_000_000;
let key1_very_old_set =
create_internal_key_with_timestamp("key1", 100, InternalKeyKind::Set, very_old_time);
let key1_old_set =
create_internal_key_with_timestamp("key1", 200, InternalKeyKind::Set, old_time);
let key1_recent_set =
create_internal_key_with_timestamp("key1", 300, InternalKeyKind::Set, recent_time);
let key2_old_set =
create_internal_key_with_timestamp("key2", 400, InternalKeyKind::Set, old_time);
let key2_recent_set =
create_internal_key_with_timestamp("key2", 500, InternalKeyKind::Set, recent_time);
let key2_delete =
create_internal_key_with_timestamp("key2", 600, InternalKeyKind::Delete, recent_time);
let key3_delete =
create_internal_key_with_timestamp("key3", 700, InternalKeyKind::Delete, recent_time);
let val1_very_old = create_vlog_value(&vlog, b"key1", b"value1_very_old");
let val1_old = create_vlog_value(&vlog, b"key1", b"value1_old");
let val1_recent = create_vlog_value(&vlog, b"key1", b"value1_recent");
let val2_old = create_vlog_value(&vlog, b"key2", b"value2_old");
let val2_recent = create_vlog_value(&vlog, b"key2", b"value2_recent");
let items1 = vec![
(key1_recent_set, val1_recent),
(key1_old_set, val1_old),
(key1_very_old_set, val1_very_old),
];
let items2 = vec![
(key2_delete, Vec::new()), (key2_recent_set, val2_recent),
(key2_old_set, val2_old),
];
let items3 = vec![(key3_delete, Vec::new())];
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let iter3 = build_table_iterator(items3);
let clock = Arc::new(MockLogicalClock::with_timestamp(current_time));
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2, iter3],
create_comparator(),
false, false, retention_period_ns,
clock,
vec![],
);
let result: Vec<_> = comp_iter.by_ref().map(|r| r.unwrap()).collect();
assert_eq!(result.len(), 3, "Expected 3 items: latest version of each key");
let key1_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key1").collect();
assert_eq!(
key1_versions.len(),
1,
"key1 should have only 1 version (latest, versioning disabled)"
);
assert_eq!(key1_versions[0].0.seq_num(), 300);
let key2_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key2").collect();
assert_eq!(
key2_versions.len(),
1,
"key2 should have only 1 version (latest DELETE, versioning disabled)"
);
assert_eq!(key2_versions[0].0.seq_num(), 600);
let key3_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key3").collect();
assert_eq!(key3_versions.len(), 1, "key3 should have only 1 version (DELETE)");
assert_eq!(key3_versions[0].0.seq_num(), 700); }
#[test(tokio::test)]
async fn test_compaction_iterator_no_versioning_bottom_level() {
let (vlog, _temp_dir) = create_test_vlog();
let current_time = 1000000000000; let retention_period_ns = 0;
let recent_time = current_time - 1_000_000_000; let old_time = current_time - 3_000_000_000; let very_old_time = current_time - 10_000_000_000;
let key1_very_old_set =
create_internal_key_with_timestamp("key1", 100, InternalKeyKind::Set, very_old_time);
let key1_old_set =
create_internal_key_with_timestamp("key1", 200, InternalKeyKind::Set, old_time);
let key1_recent_set =
create_internal_key_with_timestamp("key1", 300, InternalKeyKind::Set, recent_time);
let key2_old_set =
create_internal_key_with_timestamp("key2", 400, InternalKeyKind::Set, old_time);
let key2_recent_set =
create_internal_key_with_timestamp("key2", 500, InternalKeyKind::Set, recent_time);
let key2_delete =
create_internal_key_with_timestamp("key2", 600, InternalKeyKind::Delete, recent_time);
let key3_delete =
create_internal_key_with_timestamp("key3", 700, InternalKeyKind::Delete, recent_time);
let val1_very_old = create_vlog_value(&vlog, b"key1", b"value1_very_old");
let val1_old = create_vlog_value(&vlog, b"key1", b"value1_old");
let val1_recent = create_vlog_value(&vlog, b"key1", b"value1_recent");
let val2_old = create_vlog_value(&vlog, b"key2", b"value2_old");
let val2_recent = create_vlog_value(&vlog, b"key2", b"value2_recent");
let items1 = vec![
(key1_recent_set, val1_recent),
(key1_old_set, val1_old),
(key1_very_old_set, val1_very_old),
];
let items2 = vec![
(key2_delete, Vec::new()), (key2_recent_set, val2_recent),
(key2_old_set, val2_old),
];
let items3 = vec![(key3_delete, Vec::new())];
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let iter3 = build_table_iterator(items3);
let clock = Arc::new(MockLogicalClock::with_timestamp(current_time));
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2, iter3],
create_comparator(),
true, false, retention_period_ns,
clock,
vec![],
);
let result: Vec<_> = comp_iter.by_ref().map(|r| r.unwrap()).collect();
assert_eq!(result.len(), 1, "Expected 1 item: only key1 with latest SET");
let key1_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key1").collect();
assert_eq!(
key1_versions.len(),
1,
"key1 should have only 1 version (latest SET, versioning disabled)"
);
assert_eq!(key1_versions[0].0.seq_num(), 300);
let key2_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key2").collect();
assert_eq!(
key2_versions.len(),
0,
"key2 should have 0 versions (DELETE is latest at bottom level)"
);
let key3_versions: Vec<_> = result.iter().filter(|(k, _)| &k.user_key == b"key3").collect();
assert_eq!(key3_versions.len(), 0, "key3 should have 0 versions (DELETE-only at bottom level)");
}
#[test(tokio::test)]
async fn test_compaction_iterator_set_with_delete_behavior() {
let (vlog, _tmp_dir) = create_test_vlog();
let clock = Arc::new(MockLogicalClock::new());
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key2 = create_internal_key("test_key", 200, InternalKeyKind::Set);
let value2 = create_vlog_value(&vlog, b"test_key", b"value2");
items1.push((key2, value2));
let key1 = create_internal_key("test_key", 100, InternalKeyKind::Set);
let value1 = create_vlog_value(&vlog, b"test_key", b"value1");
items1.push((key1, value1));
let key3 = create_internal_key("test_key", 300, InternalKeyKind::Replace);
let value3 = create_vlog_value(&vlog, b"test_key", b"set_with_delete_value");
items2.push((key3, value3));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, true, 1000, clock,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 300);
assert!(returned_key.is_replace());
}
#[test(tokio::test)]
async fn test_compaction_iterator_set_with_delete_marks_older_versions_stale() {
let (vlog, _tmp_dir) = create_test_vlog();
let clock = Arc::new(MockLogicalClock::new());
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key3 = create_internal_key("test_key", 250, InternalKeyKind::Set);
let value3 = create_vlog_value(&vlog, b"test_key", b"value3");
items1.push((key3, value3));
let key2 = create_internal_key("test_key", 200, InternalKeyKind::Set);
let value2 = create_vlog_value(&vlog, b"test_key", b"value2");
items1.push((key2, value2));
let key1 = create_internal_key("test_key", 100, InternalKeyKind::Set);
let value1 = create_vlog_value(&vlog, b"test_key", b"value1");
items1.push((key1, value1));
let key5 = create_internal_key("test_key", 400, InternalKeyKind::Set);
let value5 = create_vlog_value(&vlog, b"test_key", b"final_value");
items2.push((key5, value5));
let key4 = create_internal_key("test_key", 300, InternalKeyKind::Replace);
let value4 = create_vlog_value(&vlog, b"test_key", b"set_with_delete_value");
items2.push((key4, value4));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, true, 1000, clock,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 400);
assert!(!returned_key.is_replace());
}
#[test(tokio::test)]
async fn test_compaction_iterator_set_with_delete_latest_version() {
let (vlog, _tmp_dir) = create_test_vlog();
let clock = Arc::new(MockLogicalClock::new());
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key2 = create_internal_key("test_key", 200, InternalKeyKind::Set);
let value2 = create_vlog_value(&vlog, b"test_key", b"value2");
items1.push((key2, value2));
let key1 = create_internal_key("test_key", 100, InternalKeyKind::Set);
let value1 = create_vlog_value(&vlog, b"test_key", b"value1");
items1.push((key1, value1));
let key3 = create_internal_key("test_key", 300, InternalKeyKind::Replace);
let value3 = create_vlog_value(&vlog, b"test_key", b"set_with_delete_final");
items2.push((key3, value3));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, true, 1000, clock,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 300);
assert!(returned_key.is_replace());
}
#[test(tokio::test)]
async fn test_compaction_iterator_set_with_delete_mixed_with_hard_delete() {
let (vlog, _tmp_dir) = create_test_vlog();
let clock = Arc::new(MockLogicalClock::new());
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key2 = create_internal_key("test_key", 200, InternalKeyKind::Delete);
let value2 = Value::from(vec![]); items1.push((key2, value2));
let key1 = create_internal_key("test_key", 100, InternalKeyKind::Set);
let value1 = create_vlog_value(&vlog, b"test_key", b"value1");
items1.push((key1, value1));
let key3 = create_internal_key("test_key", 300, InternalKeyKind::Replace);
let value3 = create_vlog_value(&vlog, b"test_key", b"set_with_delete_value");
items2.push((key3, value3));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, true, 1000, clock,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 300);
assert!(returned_key.is_replace());
}
#[test(tokio::test)]
async fn test_compaction_iterator_multiple_replace_operations() {
let (vlog, _tmp_dir) = create_test_vlog();
let clock = Arc::new(MockLogicalClock::new());
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key2 = create_internal_key("test_key", 200, InternalKeyKind::Replace);
let value2 = create_vlog_value(&vlog, b"test_key", b"replace_value2");
items1.push((key2, value2));
let key1 = create_internal_key("test_key", 100, InternalKeyKind::Replace);
let value1 = create_vlog_value(&vlog, b"test_key", b"replace_value1");
items1.push((key1, value1));
let key3 = create_internal_key("test_key", 300, InternalKeyKind::Replace);
let value3 = create_vlog_value(&vlog, b"test_key", b"replace_value3");
items2.push((key3, value3));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, true, 1000, Arc::clone(&clock) as Arc<dyn LogicalClock>,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 300);
assert!(returned_key.is_replace());
}
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key2 = create_internal_key("test_key2", 500, InternalKeyKind::Replace);
let value2 = create_vlog_value(&vlog, b"test_key2", b"replace_value5");
items1.push((key2, value2));
let key1 = create_internal_key("test_key2", 400, InternalKeyKind::Replace);
let value1 = create_vlog_value(&vlog, b"test_key2", b"replace_value4");
items1.push((key1, value1));
let key3 = create_internal_key("test_key2", 600, InternalKeyKind::Replace);
let value3 = create_vlog_value(&vlog, b"test_key2", b"replace_value6");
items2.push((key3, value3));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
true, true, 1000, Arc::clone(&clock) as Arc<dyn LogicalClock>,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 600);
assert!(returned_key.is_replace());
}
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key3 = create_internal_key("test_key3", 850, InternalKeyKind::Set);
let value3 = create_vlog_value(&vlog, b"test_key3", b"set_value3");
items1.push((key3, value3));
let key2 = create_internal_key("test_key3", 800, InternalKeyKind::Set);
let value2 = create_vlog_value(&vlog, b"test_key3", b"set_value2");
items1.push((key2, value2));
let key1 = create_internal_key("test_key3", 700, InternalKeyKind::Set);
let value1 = create_vlog_value(&vlog, b"test_key3", b"set_value1");
items1.push((key1, value1));
let key4 = create_internal_key("test_key3", 900, InternalKeyKind::Replace);
let value4 = create_vlog_value(&vlog, b"test_key3", b"replace_value7");
items2.push((key4, value4));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, true, 1000, Arc::clone(&clock) as Arc<dyn LogicalClock>,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 900);
assert!(returned_key.is_replace());
}
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key1 = create_internal_key("key_a", 1000, InternalKeyKind::Replace);
let value1 = create_vlog_value(&vlog, b"key_a", b"replace_a1");
items1.push((key1, value1));
let key2 = create_internal_key("key_b", 1100, InternalKeyKind::Replace);
let value2 = create_vlog_value(&vlog, b"key_b", b"replace_b1");
items1.push((key2, value2));
let key3 = create_internal_key("key_a", 1200, InternalKeyKind::Replace);
let value3 = create_vlog_value(&vlog, b"key_a", b"replace_a2");
items2.push((key3, value3));
let key4 = create_internal_key("key_b", 1300, InternalKeyKind::Replace);
let value4 = create_vlog_value(&vlog, b"key_b", b"replace_b2");
items2.push((key4, value4));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, true, 1000, Arc::clone(&clock) as Arc<dyn LogicalClock>,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 2);
result.sort_by(|a, b| a.0.user_key.cmp(&b.0.user_key));
let (returned_key_a, _) = &result[0];
assert_eq!(&returned_key_a.user_key, b"key_a");
assert_eq!(returned_key_a.seq_num(), 1200);
assert!(returned_key_a.is_replace());
let (returned_key_b, _) = &result[1];
assert_eq!(&returned_key_b.user_key, b"key_b");
assert_eq!(returned_key_b.seq_num(), 1300);
assert!(returned_key_b.is_replace());
}
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let mut items3 = Vec::new();
let key2 = create_internal_key("test_key4", 1500, InternalKeyKind::Set);
let value2 = create_vlog_value(&vlog, b"test_key4", b"set_value2");
items1.push((key2, value2));
let key1 = create_internal_key("test_key4", 1400, InternalKeyKind::Set);
let value1 = create_vlog_value(&vlog, b"test_key4", b"set_value1");
items1.push((key1, value1));
let key3 = create_internal_key("test_key4", 1600, InternalKeyKind::Replace);
let value3 = create_vlog_value(&vlog, b"test_key4", b"replace_value");
items2.push((key3, value3));
let key4 = create_internal_key("test_key4", 1700, InternalKeyKind::Set);
let value4 = create_vlog_value(&vlog, b"test_key4", b"final_set_value");
items3.push((key4, value4));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let iter3 = build_table_iterator(items3);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2, iter3],
create_comparator(),
false, true, 1000, Arc::clone(&clock) as Arc<dyn LogicalClock>,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 1700);
assert!(!returned_key.is_replace());
assert_eq!(returned_key.kind(), InternalKeyKind::Set);
}
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key1 = create_internal_key("test_key5", 1800, InternalKeyKind::Replace);
let value1 = create_vlog_value(&vlog, b"test_key5", b"replace_value");
items1.push((key1, value1));
let key2 = create_internal_key("test_key5", 1900, InternalKeyKind::Delete);
let value2 = Vec::new(); items2.push((key2, value2));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, true, 1000, Arc::clone(&clock) as Arc<dyn LogicalClock>,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 1900);
assert!(returned_key.is_hard_delete_marker());
assert_eq!(returned_key.kind(), InternalKeyKind::Delete);
}
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key1 = create_internal_key("test_key6", 2000, InternalKeyKind::Replace);
let value1 = create_vlog_value(&vlog, b"test_key6", b"replace_value");
items1.push((key1, value1));
let key2 = create_internal_key("test_key6", 2100, InternalKeyKind::Delete);
let value2 = Vec::new(); items2.push((key2, value2));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
true, true, 1000, Arc::clone(&clock) as Arc<dyn LogicalClock>,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 0);
}
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let mut items3 = Vec::new();
let key2 = create_internal_key("test_key7", 2300, InternalKeyKind::Replace);
let value2 = create_vlog_value(&vlog, b"test_key7", b"replace_value2");
items1.push((key2, value2));
let key1 = create_internal_key("test_key7", 2200, InternalKeyKind::Replace);
let value1 = create_vlog_value(&vlog, b"test_key7", b"replace_value1");
items1.push((key1, value1));
let key3 = create_internal_key("test_key7", 2400, InternalKeyKind::Replace);
let value3 = create_vlog_value(&vlog, b"test_key7", b"replace_value3");
items2.push((key3, value3));
let key4 = create_internal_key("test_key7", 2500, InternalKeyKind::Delete);
let value4 = Vec::new(); items3.push((key4, value4));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let iter3 = build_table_iterator(items3);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2, iter3],
create_comparator(),
false, true, 1000, Arc::clone(&clock) as Arc<dyn LogicalClock>,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 2500);
assert!(returned_key.is_hard_delete_marker());
assert_eq!(returned_key.kind(), InternalKeyKind::Delete);
}
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key2 = create_internal_key("test_key8", 2700, InternalKeyKind::Replace);
let value2 = create_vlog_value(&vlog, b"test_key8", b"replace_value2");
items1.push((key2, value2));
let key1 = create_internal_key("test_key8", 2600, InternalKeyKind::Replace);
let value1 = create_vlog_value(&vlog, b"test_key8", b"replace_value1");
items1.push((key1, value1));
let key3 = create_internal_key("test_key8", 2800, InternalKeyKind::Replace);
let value3 = create_vlog_value(&vlog, b"test_key8", b"replace_value3");
items2.push((key3, value3));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, false, 1000, Arc::clone(&clock) as Arc<dyn LogicalClock>,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 2800);
assert!(returned_key.is_replace());
}
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let mut items3 = Vec::new();
let key1 = create_internal_key("test_key9", 2900, InternalKeyKind::Set);
let value1 = create_vlog_value(&vlog, b"test_key9", b"set_value1");
items1.push((key1, value1));
let key2 = create_internal_key("test_key9", 3000, InternalKeyKind::Replace);
let value2 = create_vlog_value(&vlog, b"test_key9", b"replace_value");
items2.push((key2, value2));
let key3 = create_internal_key("test_key9", 3100, InternalKeyKind::Set);
let value3 = create_vlog_value(&vlog, b"test_key9", b"final_set_value");
items3.push((key3, value3));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let iter3 = build_table_iterator(items3);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2, iter3],
create_comparator(),
false, false, 1000, Arc::clone(&clock) as Arc<dyn LogicalClock>,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 3100);
assert!(!returned_key.is_replace());
assert_eq!(returned_key.kind(), InternalKeyKind::Set);
}
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key1 = create_internal_key("test_key10", 3200, InternalKeyKind::Replace);
let value1 = create_vlog_value(&vlog, b"test_key10", b"replace_value");
items1.push((key1, value1));
let key2 = create_internal_key("test_key10", 3300, InternalKeyKind::Delete);
let value2 = Vec::new(); items2.push((key2, value2));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
false, false, 1000, Arc::clone(&clock) as Arc<dyn LogicalClock>,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 1);
let (returned_key, _) = &result[0];
assert_eq!(returned_key.seq_num(), 3300);
assert!(returned_key.is_hard_delete_marker());
assert_eq!(returned_key.kind(), InternalKeyKind::Delete);
}
{
let mut items1 = Vec::new();
let mut items2 = Vec::new();
let key1 = create_internal_key("test_key11", 3400, InternalKeyKind::Replace);
let value1 = create_vlog_value(&vlog, b"test_key11", b"replace_value");
items1.push((key1, value1));
let key2 = create_internal_key("test_key11", 3500, InternalKeyKind::Delete);
let value2 = Vec::new(); items2.push((key2, value2));
let iter1 = build_table_iterator(items1);
let iter2 = build_table_iterator(items2);
let mut comp_iter = CompactionIterator::new(
vec![iter1, iter2],
create_comparator(),
true, false, 1000, clock,
vec![],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
result.push(item.unwrap());
}
assert_eq!(result.len(), 0);
}
}
#[test]
fn test_merging_iterator_direction_switch_with_duplicate_keys() {
let iter0 = build_table_iterator(vec![
(create_internal_key("key-1", 100, InternalKeyKind::Set), Value::from(b"v1".to_vec())),
(create_internal_key("key-3", 200, InternalKeyKind::Set), Value::from(b"v3-L0".to_vec())),
(create_internal_key("key-5", 100, InternalKeyKind::Set), Value::from(b"v5".to_vec())),
]);
let iter1 = build_table_iterator(vec![
(create_internal_key("key-2", 100, InternalKeyKind::Set), Value::from(b"v2".to_vec())),
(create_internal_key("key-3", 100, InternalKeyKind::Set), Value::from(b"v3-L1".to_vec())),
(create_internal_key("key-6", 100, InternalKeyKind::Set), Value::from(b"v6".to_vec())),
]);
let cmp = create_comparator();
let mut merge_iter = MergingIterator::new(vec![iter0, iter1], cmp);
assert!(merge_iter.seek_first().unwrap());
assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-1");
assert!(merge_iter.next().unwrap());
assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-2");
assert!(merge_iter.next().unwrap());
assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-3");
assert_eq!(merge_iter.current_key().seq_num(), 200);
assert!(merge_iter.next().unwrap());
assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-3");
assert_eq!(merge_iter.current_key().seq_num(), 100);
assert!(merge_iter.prev().unwrap());
assert_eq!(
String::from_utf8_lossy(merge_iter.current_key().user_key()),
"key-3",
"After prev() from key-3(seq=100), should be at key-3(seq=200), not key-2"
);
assert_eq!(merge_iter.current_key().seq_num(), 200);
assert!(merge_iter.prev().unwrap());
assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-2");
assert!(merge_iter.prev().unwrap());
assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-1");
assert!(!merge_iter.prev().unwrap());
}
#[test]
fn test_merging_iterator_direction_switch_backward_to_forward() {
let iter0 = build_table_iterator(vec![
(create_internal_key("key-1", 100, InternalKeyKind::Set), Value::from(b"v1".to_vec())),
(create_internal_key("key-3", 200, InternalKeyKind::Set), Value::from(b"v3-L0".to_vec())),
(create_internal_key("key-5", 100, InternalKeyKind::Set), Value::from(b"v5".to_vec())),
]);
let iter1 = build_table_iterator(vec![
(create_internal_key("key-2", 100, InternalKeyKind::Set), Value::from(b"v2".to_vec())),
(create_internal_key("key-3", 100, InternalKeyKind::Set), Value::from(b"v3-L1".to_vec())),
(create_internal_key("key-6", 100, InternalKeyKind::Set), Value::from(b"v6".to_vec())),
]);
let cmp = create_comparator();
let mut merge_iter = MergingIterator::new(vec![iter0, iter1], cmp);
assert!(merge_iter.seek_last().unwrap());
assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-6");
assert!(merge_iter.prev().unwrap());
assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-5");
assert!(merge_iter.prev().unwrap());
assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-3");
assert_eq!(merge_iter.current_key().seq_num(), 100);
assert!(merge_iter.next().unwrap());
assert_eq!(
String::from_utf8_lossy(merge_iter.current_key().user_key()),
"key-5",
"After next() from key-3(seq=100), should be at key-5"
);
assert!(merge_iter.next().unwrap());
assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-6");
assert!(!merge_iter.next().unwrap());
}
#[test]
fn test_merging_iterator_multiple_direction_switches() {
let iter0 = build_table_iterator(vec![
(create_internal_key("key-1", 100, InternalKeyKind::Set), Value::from(b"v1".to_vec())),
(create_internal_key("key-3", 200, InternalKeyKind::Set), Value::from(b"v3-L0".to_vec())),
]);
let iter1 = build_table_iterator(vec![
(create_internal_key("key-2", 100, InternalKeyKind::Set), Value::from(b"v2".to_vec())),
(create_internal_key("key-3", 100, InternalKeyKind::Set), Value::from(b"v3-L1".to_vec())),
]);
let cmp = create_comparator();
let mut merge_iter = MergingIterator::new(vec![iter0, iter1], cmp);
assert!(merge_iter.seek_first().unwrap()); assert!(merge_iter.next().unwrap()); assert!(merge_iter.next().unwrap()); assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-3");
assert_eq!(merge_iter.current_key().seq_num(), 200);
assert!(merge_iter.prev().unwrap()); assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-2");
assert!(merge_iter.next().unwrap()); assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-3");
assert_eq!(merge_iter.current_key().seq_num(), 200);
assert!(merge_iter.next().unwrap()); assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-3");
assert_eq!(merge_iter.current_key().seq_num(), 100);
assert!(merge_iter.prev().unwrap()); assert_eq!(String::from_utf8_lossy(merge_iter.current_key().user_key()), "key-3");
assert_eq!(merge_iter.current_key().seq_num(), 200);
}
#[test]
fn test_snapshot_compaction_no_snapshots_keeps_only_latest() {
let items = vec![
(create_internal_key("key1", 100, InternalKeyKind::Set), b"v3".to_vec()),
(create_internal_key("key1", 50, InternalKeyKind::Set), b"v2".to_vec()),
(create_internal_key("key1", 20, InternalKeyKind::Set), b"v1".to_vec()),
];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
false, false, 0,
Arc::new(MockLogicalClock::new()),
vec![], );
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
result.push((String::from_utf8_lossy(&key.user_key).to_string(), key.seq_num()));
}
assert_eq!(result.len(), 1, "Only latest version should be kept with no snapshots");
assert_eq!(result[0], ("key1".to_string(), 100));
}
#[test]
fn test_snapshot_compaction_single_snapshot_preserves_visible_version() {
let items = vec![
(create_internal_key("key1", 100, InternalKeyKind::Set), b"v3".to_vec()),
(create_internal_key("key1", 30, InternalKeyKind::Set), b"v2".to_vec()),
(create_internal_key("key1", 10, InternalKeyKind::Set), b"v1".to_vec()),
];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
false,
false, 0,
Arc::new(MockLogicalClock::new()),
vec![50], );
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
result.push((String::from_utf8_lossy(&key.user_key).to_string(), key.seq_num()));
}
assert_eq!(result.len(), 2, "Latest and snapshot-visible version should be kept");
assert!(result.contains(&("key1".to_string(), 100)), "Latest should be kept");
assert!(result.contains(&("key1".to_string(), 30)), "Snapshot-visible version should be kept");
}
#[test]
fn test_snapshot_compaction_multiple_snapshots_different_boundaries() {
let items = vec![
(create_internal_key("key1", 200, InternalKeyKind::Set), b"v4".to_vec()),
(create_internal_key("key1", 120, InternalKeyKind::Set), b"v3".to_vec()),
(create_internal_key("key1", 80, InternalKeyKind::Set), b"v2".to_vec()),
(create_internal_key("key1", 30, InternalKeyKind::Set), b"v1".to_vec()),
];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
false,
false,
0,
Arc::new(MockLogicalClock::new()),
vec![50, 150], );
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
result.push((String::from_utf8_lossy(&key.user_key).to_string(), key.seq_num()));
}
assert_eq!(result.len(), 3, "Three versions should be kept (one per boundary)");
assert!(result.contains(&("key1".to_string(), 200)), "Latest should be kept");
assert!(
result.contains(&("key1".to_string(), 120)),
"Version visible to snap 150 should be kept"
);
assert!(
result.contains(&("key1".to_string(), 30)),
"Version visible to snap 50 should be kept"
);
assert!(
!result.contains(&("key1".to_string(), 80)),
"Version hidden by newer version in same boundary should be dropped"
);
}
#[test]
fn test_snapshot_compaction_newer_version_hides_older_in_same_boundary() {
let items = vec![
(create_internal_key("key1", 100, InternalKeyKind::Set), b"v2".to_vec()),
(create_internal_key("key1", 50, InternalKeyKind::Set), b"v1".to_vec()),
];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
false,
false,
0,
Arc::new(MockLogicalClock::new()),
vec![150], );
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
result.push((String::from_utf8_lossy(&key.user_key).to_string(), key.seq_num()));
}
assert_eq!(result.len(), 1, "Only newer version in same boundary should be kept");
assert_eq!(result[0], ("key1".to_string(), 100));
}
#[test]
fn test_snapshot_compaction_versions_at_tip_hidden_by_newer() {
let items = vec![
(create_internal_key("key1", 300, InternalKeyKind::Set), b"v3".to_vec()),
(create_internal_key("key1", 200, InternalKeyKind::Set), b"v2".to_vec()),
(create_internal_key("key1", 150, InternalKeyKind::Set), b"v1".to_vec()),
];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
false,
false,
0,
Arc::new(MockLogicalClock::new()),
vec![50], );
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
result.push((String::from_utf8_lossy(&key.user_key).to_string(), key.seq_num()));
}
assert_eq!(result.len(), 1, "Only latest version should be kept when all newer than snapshots");
assert_eq!(result[0], ("key1".to_string(), 300));
}
#[test]
fn test_snapshot_compaction_multiple_keys() {
let items = vec![
(create_internal_key("key1", 100, InternalKeyKind::Set), b"k1v2".to_vec()),
(create_internal_key("key1", 30, InternalKeyKind::Set), b"k1v1".to_vec()),
(create_internal_key("key2", 80, InternalKeyKind::Set), b"k2v2".to_vec()),
(create_internal_key("key2", 40, InternalKeyKind::Set), b"k2v1".to_vec()),
];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
false,
false,
0,
Arc::new(MockLogicalClock::new()),
vec![50], );
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
result.push((String::from_utf8_lossy(&key.user_key).to_string(), key.seq_num()));
}
assert_eq!(result.len(), 4, "All versions in different boundaries should be kept");
}
#[test]
fn test_snapshot_compaction_tombstone_visible_to_snapshot() {
let items = vec![
(create_internal_key("key1", 100, InternalKeyKind::Delete), b"".to_vec()),
(create_internal_key("key1", 50, InternalKeyKind::Set), b"v1".to_vec()),
];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
false, false,
0,
Arc::new(MockLogicalClock::new()),
vec![150],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
result.push((
String::from_utf8_lossy(&key.user_key).to_string(),
key.seq_num(),
key.kind(),
));
}
assert_eq!(result.len(), 1);
assert_eq!(result[0], ("key1".to_string(), 100, InternalKeyKind::Delete));
}
#[test]
fn test_snapshot_compaction_tombstone_at_bottom_with_snapshot() {
let items = vec![
(create_internal_key("key1", 100, InternalKeyKind::Delete), b"".to_vec()),
(create_internal_key("key1", 50, InternalKeyKind::Set), b"v1".to_vec()),
];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
true, false,
0,
Arc::new(MockLogicalClock::new()),
vec![150],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
result.push((String::from_utf8_lossy(&key.user_key).to_string(), key.seq_num()));
}
assert_eq!(result.len(), 0, "Bottom level delete should drop all versions");
}
#[test]
fn test_snapshot_compaction_exact_sequence_match() {
let items = vec![
(create_internal_key("key1", 100, InternalKeyKind::Set), b"v2".to_vec()),
(create_internal_key("key1", 50, InternalKeyKind::Set), b"v1".to_vec()),
];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
false,
false,
0,
Arc::new(MockLogicalClock::new()),
vec![50], );
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
result.push((String::from_utf8_lossy(&key.user_key).to_string(), key.seq_num()));
}
assert_eq!(result.len(), 2, "Both versions should be kept with exact snapshot match");
assert!(result.contains(&("key1".to_string(), 100)));
assert!(result.contains(&("key1".to_string(), 50)));
}
#[test]
fn test_snapshot_compaction_version_older_than_all_snapshots() {
let items = vec![
(create_internal_key("key1", 200, InternalKeyKind::Set), b"v3".to_vec()),
(create_internal_key("key1", 100, InternalKeyKind::Set), b"v2".to_vec()),
(create_internal_key("key1", 30, InternalKeyKind::Set), b"v1".to_vec()),
];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
false,
false,
0,
Arc::new(MockLogicalClock::new()),
vec![50, 150],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
result.push((String::from_utf8_lossy(&key.user_key).to_string(), key.seq_num()));
}
assert_eq!(result.len(), 3, "All versions in different boundaries should be kept");
}
#[test]
fn test_snapshot_visibility_states() {
let items = vec![(create_internal_key("key1", 100, InternalKeyKind::Set), b"v1".to_vec())];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
false,
false,
0,
Arc::new(MockLogicalClock::new()),
vec![], );
let mut count = 0;
for _ in comp_iter.by_ref() {
count += 1;
}
assert_eq!(count, 1);
}
#[test]
fn test_snapshot_compaction_with_versioning_enabled() {
let clock = Arc::new(MockLogicalClock::new());
clock.set_time(1000);
let items = vec![
(
create_internal_key_with_timestamp("key1", 100, InternalKeyKind::Set, 900),
b"v3".to_vec(),
),
(create_internal_key_with_timestamp("key1", 50, InternalKeyKind::Set, 800), b"v2".to_vec()),
(create_internal_key_with_timestamp("key1", 20, InternalKeyKind::Set, 500), b"v1".to_vec()),
];
let iter = build_table_iterator(items);
let mut comp_iter = CompactionIterator::new(
vec![iter],
create_comparator(),
false,
true, 300, clock, vec![60],
);
let mut result = Vec::new();
for item in comp_iter.by_ref() {
let (key, _) = item.unwrap();
result.push((String::from_utf8_lossy(&key.user_key).to_string(), key.seq_num()));
}
assert_eq!(result.len(), 2, "Latest and snapshot-visible should be kept");
assert!(result.contains(&("key1".to_string(), 100)));
assert!(result.contains(&("key1".to_string(), 50)));
}