use crate::compression::ZstdDictionary;
use crate::config::CompressionPolicy;
use crate::{AbstractTree, CompressionType, Config, SequenceNumberCounter};
use std::sync::Arc;
use test_log::test;
fn training_corpus() -> Vec<u8> {
let mut samples = Vec::new();
for i in 0u32..500 {
samples.extend_from_slice(format!("key-{i:05}").as_bytes());
samples.extend_from_slice(format!("value-{i:05}-padding-to-make-it-longer").as_bytes());
}
samples
}
fn config(path: &std::path::Path) -> Config {
Config::new(
path,
SequenceNumberCounter::default(),
SequenceNumberCounter::default(),
)
}
#[test]
fn a_collection_under_a_held_pause_leaves_the_dictionary_alone() -> crate::Result<()> {
let dir = tempfile::tempdir()?;
let dict = ZstdDictionary::new(&training_corpus());
let dict_id = dict.id();
let compression = CompressionType::zstd_dict(3, dict_id)?;
{
let tree = config(dir.path())
.data_block_compression_policy(CompressionPolicy::all(compression))
.zstd_dictionary(Some(Arc::new(dict)))
.open()?;
for i in 0u32..100 {
tree.insert(
format!("key-{i:05}").as_bytes(),
b"value-under-the-dictionary",
u64::from(i),
);
}
tree.flush_active_memtable(0)?;
}
let tree = config(dir.path())
.data_block_compression_policy(CompressionPolicy::all(CompressionType::None))
.open()?;
tree.major_compact(u64::MAX, 0)?;
let crate::AnyTree::Standard(tree) = tree else {
panic!("a standard tree");
};
let dict_path = dir
.path()
.join(crate::file::DICTS_FOLDER)
.join(dict_id.to_string());
assert!(dict_path.exists(), "collectable, but still on disk");
{
let _pause = tree.deletion_pause.acquire();
assert_eq!(
tree.collect_unreferenced_dictionaries()?,
0,
"a collection under a held pause removes nothing itself",
);
assert!(
dict_path.exists(),
"a file a checkpoint may still link is not unlinked under the pause",
);
}
assert!(
dict_path.exists(),
"closing the pause must not fire a removal the collection never queued",
);
assert_eq!(tree.collect_unreferenced_dictionaries()?, 1);
assert!(!dict_path.exists(), "a later collection removes it");
Ok(())
}
#[test]
fn a_dictionary_registered_during_a_pause_survives_it() -> crate::Result<()> {
let dir = tempfile::tempdir()?;
let dict = Arc::new(ZstdDictionary::new(&training_corpus()));
let dict_id = dict.id();
let compression = CompressionType::zstd_dict(3, dict_id)?;
{
let tree = config(dir.path())
.data_block_compression_policy(CompressionPolicy::all(compression))
.zstd_dictionary(Some(Arc::clone(&dict)))
.open()?;
for i in 0u32..100 {
tree.insert(
format!("key-{i:05}").as_bytes(),
b"value-under-the-dictionary",
u64::from(i),
);
}
tree.flush_active_memtable(0)?;
}
{
let tree = config(dir.path())
.data_block_compression_policy(CompressionPolicy::all(CompressionType::None))
.open()?;
tree.major_compact(u64::MAX, 0)?;
let crate::AnyTree::Standard(tree) = tree else {
panic!("a standard tree");
};
tree.collect_unreferenced_dictionaries()?;
}
let tree = config(dir.path())
.data_block_compression_policy(CompressionPolicy::all(CompressionType::None))
.open()?;
let crate::AnyTree::Standard(tree) = tree else {
panic!("a standard tree");
};
let dict_path = dir
.path()
.join(crate::file::DICTS_FOLDER)
.join(dict_id.to_string());
{
let _pause = tree.deletion_pause.acquire();
tree.collect_unreferenced_dictionaries()?;
tree.register_zstd_dictionary(Arc::clone(&dict))?;
}
assert!(
dict_path.exists(),
"a dictionary registered during the pause must survive it closing",
);
assert!(
tree.zstd_dictionaries().get(dict_id).is_some(),
"and stay resolvable",
);
drop(tree);
let reopened = config(dir.path())
.data_block_compression_policy(CompressionPolicy::all(CompressionType::None))
.open()?;
let crate::AnyTree::Standard(reopened) = reopened else {
panic!("a standard tree");
};
assert!(
reopened.zstd_dictionaries().get(dict_id).is_some(),
"the registration survives a reopen, not just the live set",
);
Ok(())
}
#[test]
fn a_recovered_version_registers_what_its_files_reference() -> crate::Result<()> {
let dir = tempfile::tempdir()?;
let target = tempfile::tempdir()?;
let checkpoint = target.path().join("snapshot");
let dict = ZstdDictionary::new(&training_corpus());
let dict_id = dict.id();
let compression = CompressionType::zstd_dict(3, dict_id)?;
{
let tree = config(dir.path())
.data_block_compression_policy(CompressionPolicy::all(compression))
.zstd_dictionary(Some(Arc::new(dict)))
.open()?;
for i in 0u32..100 {
tree.insert(
format!("key-{i:05}").as_bytes(),
b"value-under-the-dictionary",
u64::from(i),
);
}
tree.flush_active_memtable(0)?;
let crate::AnyTree::Standard(tree) = tree else {
panic!("a standard tree");
};
let unregistered = tree.current_version().without_dicts(&[dict_id]);
crate::version::persist_version(
dir.path(),
&unregistered,
tree.config.comparator.name(),
&*tree.config.fs,
tree.runtime_config.load_full(),
tree.config.encryption.clone(),
tree.config.sync_mode,
)?;
}
let tree = config(dir.path())
.data_block_compression_policy(CompressionPolicy::all(CompressionType::None))
.open()?;
tree.create_checkpoint(&checkpoint)?;
drop(tree);
let restored = config(&checkpoint).open()?;
assert_eq!(
restored.get(b"key-00042", crate::MAX_SEQNO)?.as_deref(),
Some(b"value-under-the-dictionary".as_slice()),
"the checkpoint carries the dictionary its tables are compressed against",
);
Ok(())
}
#[test]
fn a_collected_dictionary_is_evicted_from_the_live_registry() -> crate::Result<()> {
let dir = tempfile::tempdir()?;
let dict = ZstdDictionary::new(&training_corpus());
let dict_id = dict.id();
let compression = CompressionType::zstd_dict(3, dict_id)?;
{
let tree = config(dir.path())
.data_block_compression_policy(CompressionPolicy::all(compression))
.zstd_dictionary(Some(Arc::new(dict)))
.open()?;
for i in 0u32..100 {
tree.insert(
format!("key-{i:05}").as_bytes(),
b"value-under-the-dictionary",
u64::from(i),
);
}
tree.flush_active_memtable(0)?;
}
let tree = config(dir.path())
.data_block_compression_policy(CompressionPolicy::all(CompressionType::None))
.open()?;
tree.major_compact(u64::MAX, 0)?;
let crate::AnyTree::Standard(tree) = tree else {
panic!("a standard tree");
};
assert!(
tree.zstd_dictionaries().get(dict_id).is_some(),
"loaded at open, before the collection",
);
assert_eq!(tree.collect_unreferenced_dictionaries()?, 1);
assert!(
tree.zstd_dictionaries().get(dict_id).is_none(),
"a collected dictionary is dropped from the live set as well as the disk",
);
Ok(())
}