use std::path::{Path, PathBuf};
use regolith::{Db, DurabilityMode, IngestOptions, Options, SstFileWriter};
use tempfile::TempDir;
const PER_FILE: u32 = 2000;
fn opts(block_cache_size: usize) -> Options {
Options {
write_buffer_size: 64 * 1024 * 1024,
durability: DurabilityMode::Eventual,
block_cache_size,
..Options::default()
}
}
fn make(path: &Path, prefix: &str) {
let mut w = SstFileWriter::create(path, &Options::default()).expect("create");
for i in 0..PER_FILE {
w.put(
format!("{prefix}{i:08}").as_bytes(),
format!("val_{prefix}{i:08}").as_bytes(),
)
.expect("put");
}
w.finish().expect("finish");
}
fn missing_after_ingest(block_cache_size: usize, prefixes: &[&str], one_call: bool) -> Vec<String> {
let dir = TempDir::new().expect("tempdir");
let db = Db::open(dir.path(), opts(block_cache_size)).expect("open");
let staged: Vec<PathBuf> = prefixes
.iter()
.enumerate()
.map(|(i, p)| {
let path = dir.path().join(format!("src{i}.sst"));
make(&path, p);
path
})
.collect();
if one_call {
db.ingest_external_files(&staged, IngestOptions::default())
.expect("ingest reported success");
} else {
for path in staged {
db.ingest_external_files(&[path], IngestOptions::default())
.expect("ingest reported success");
}
}
let mut missing = Vec::new();
for prefix in prefixes {
for i in 0..PER_FILE {
let k = format!("{prefix}{i:08}");
if db.get(k.as_bytes()).expect("get").is_none() {
missing.push(k);
}
}
}
drop(db);
missing
}
#[test]
fn ingesting_more_than_one_external_table_must_not_silently_drop_entries() {
let prefixes = ["aaa_", "bbb_", "ccc_"];
let total = PER_FILE as usize * prefixes.len();
let missing = missing_after_ingest(512 * 1024 * 1024, &prefixes, true);
assert!(
missing.is_empty(),
"ingest_external_files returned Ok(()) but {} of {total} entries are gone. \
First missing: {:?}. Cause: the sources share a block-cache namespace, so \
the cache serves the first source's blocks to the later ones.",
missing.len(),
&missing[..missing.len().min(5)],
);
}
#[test]
fn ingesting_two_external_tables_in_separate_calls_must_not_drop_the_second() {
let missing = missing_after_ingest(512 * 1024 * 1024, &["aaa_", "bbb_"], false);
assert!(
missing.is_empty(),
"two separate ingest calls each returned Ok(()) but {} entries are gone, \
first {:?}",
missing.len(),
&missing[..missing.len().min(5)],
);
}
#[test]
fn with_the_block_cache_disabled_the_same_ingest_loses_nothing() {
let prefixes = ["aaa_", "bbb_", "ccc_"];
let missing = missing_after_ingest(0, &prefixes, true);
assert!(
missing.is_empty(),
"the control itself lost {} entries, so the diagnosis is wrong",
missing.len(),
);
println!(
"control: {} entries ingested from {} files with block_cache_size = 0, none lost",
PER_FILE as usize * prefixes.len(),
prefixes.len(),
);
}