use super::align_row_pages;
use crate::{InternalValue, ValueType};
fn row(key: u32, seqno: u64, value: &str) -> InternalValue {
InternalValue::from_components(
format!("k{key:04}").into_bytes(),
value.as_bytes(),
seqno,
ValueType::Value,
)
}
fn source() -> (Vec<InternalValue>, Vec<u32>) {
((0..12).map(|k| row(k, 1, "v")).collect(), vec![4, 4, 4])
}
#[test]
fn align_one_changed_row_copies_the_other_row_pages() {
let (source, pages) = source();
let mut emitted = source.clone();
emitted[5] = row(5, 1, "changed");
assert_eq!(
align_row_pages(&source, &pages, &emitted),
Some((vec![4, 4, 4], vec![true, false, true]))
);
}
#[test]
fn align_inserted_row_grows_its_row_page_only() {
let (source, pages) = source();
let mut emitted = source.clone();
emitted.insert(1, row(1, 9, "newer"));
assert_eq!(
align_row_pages(&source, &pages, &emitted),
Some((vec![5, 4, 4], vec![false, true, true]))
);
}
#[test]
fn align_emptied_row_page_refuses() {
let (source, pages) = source();
let emitted: Vec<_> = source
.iter()
.enumerate()
.filter(|(i, _)| !(4..8).contains(i))
.map(|(_, r)| r.clone())
.collect();
assert_eq!(align_row_pages(&source, &pages, &emitted), None);
}
#[test]
fn align_rows_past_the_last_row_page_join_it() {
let (source, pages) = source();
let mut emitted = source.clone();
emitted.push(row(11, 0, "older"));
assert_eq!(
align_row_pages(&source, &pages, &emitted),
Some((vec![4, 4, 5], vec![true, true, false]))
);
}
#[test]
fn align_every_row_page_changed_refuses() {
let (source, pages) = source();
let emitted: Vec<_> = (0..12).map(|k| row(k, 1, "changed")).collect();
assert_eq!(align_row_pages(&source, &pages, &emitted), None);
}
#[test]
fn align_changed_run_spreads_over_its_row_pages() {
let source: Vec<_> = (0..16).map(|k| row(k, 1, "v")).collect();
let pages = vec![4, 4, 4, 4];
let mut emitted = source.clone();
emitted[5] = row(5, 1, "changed");
emitted[9] = row(9, 1, "changed");
emitted.remove(6);
assert_eq!(
align_row_pages(&source, &pages, &emitted),
Some((vec![4, 4, 3, 4], vec![true, false, false, true]))
);
}
#[test]
fn changed_pages_fit_weighs_only_the_pages_written_anew() {
use super::changed_pages_fit;
let big = "x".repeat(100);
let rows: Vec<_> = (0..4).map(|k| row(k, 1, &big)).collect();
assert!(changed_pages_fit(&rows, (&[2, 2], &[false, false]), 300));
assert!(!changed_pages_fit(&rows, (&[2, 2], &[false, true]), 150));
assert!(changed_pages_fit(&rows, (&[2, 2], &[true, true]), 150));
assert!(changed_pages_fit(&rows, (&[1, 1, 1, 1], &[false; 4]), 10));
assert!(!changed_pages_fit(&rows, (&[3, 3], &[false, false]), 1_000));
}
#[test]
fn shares_a_row_finds_a_row_left_as_read() {
use super::shares_a_row;
let cmp = crate::comparator::default_comparator();
let source = vec![row(1, 5, "a"), row(2, 5, "b"), row(3, 5, "c")];
let interleaved = vec![
row(1, 9, "new"),
row(1, 0, "a"),
row(2, 9, "new"),
row(2, 5, "b"),
];
assert!(shares_a_row(&source, &interleaved, &cmp));
let zeroed: Vec<_> = source.iter().map(|r| row_at(r, 0)).collect();
assert!(!shares_a_row(&source, &zeroed, &cmp));
let rewritten = vec![row(1, 5, "x"), row(2, 5, "y"), row(3, 5, "z")];
assert!(!shares_a_row(&source, &rewritten, &cmp));
assert!(!shares_a_row(&source, &[], &cmp));
}
fn row_at(read: &InternalValue, seqno: u64) -> InternalValue {
InternalValue::from_components(
read.key.user_key.clone(),
read.value.clone(),
seqno,
read.key.value_type,
)
}
std::thread_local! {
static PEAK_HELD: core::cell::Cell<u64> = const { core::cell::Cell::new(0) };
}
pub(super) fn note_held(rows: &[InternalValue]) {
let bytes = rows
.iter()
.map(|row| (row.key.user_key.len() + row.value.len()) as u64)
.sum::<u64>();
PEAK_HELD.with(|peak| peak.set(peak.get().max(bytes)));
}
fn columnar_tree(folder: &std::path::Path) -> crate::Result<crate::Tree> {
let crate::AnyTree::Standard(tree) = crate::Config::new(
folder,
crate::SequenceNumberCounter::default(),
crate::SequenceNumberCounter::default(),
)
.open()?
else {
panic!("expected standard tree");
};
tree.update_runtime_config(|cfg| {
cfg.columnar = true;
cfg.data_block_compression_policy =
crate::config::CompressionPolicy::all(crate::CompressionType::None);
})?;
Ok(tree)
}
fn key(i: u32) -> Vec<u8> {
format!("k{i:04}").into_bytes()
}
#[test]
fn held_rows_are_bounded_by_their_bytes() -> crate::Result<()> {
use crate::AbstractTree;
let folder = crate::get_tmp_folder();
let tree = columnar_tree(folder.path())?;
for i in 0..1_000 {
tree.insert(key(i), vec![b's'; 16], 1);
}
tree.flush_active_memtable(0)?;
for i in (0..300).step_by(3) {
tree.insert(key(i), vec![b'L'; 64 * 1024], 2);
}
tree.flush_active_memtable(0)?;
PEAK_HELD.with(|peak| peak.set(0));
tree.major_compact(64 * 1024 * 1024, 0)?;
let peak = PEAK_HELD.with(core::cell::Cell::get);
assert!(peak < 256 * 1024, "{peak} bytes held for one group");
assert_eq!(tree.iter(crate::SeqNo::MAX, None).count(), 1_000);
assert_eq!(
tree.get(key(3), crate::SeqNo::MAX)?.map(|v| v.len()),
Some(64 * 1024)
);
Ok(())
}
#[cfg(feature = "metrics")]
#[test]
fn a_changed_page_past_a_group_is_written_not_rebuilt() -> crate::Result<()> {
use crate::AbstractTree;
for (len, rebuilt) in [(40, true), (8 * 1024, false)] {
let folder = crate::get_tmp_folder();
let tree = columnar_tree(folder.path())?;
for i in 0..1_000 {
tree.insert(key(i), vec![b's'; 16], 1);
}
tree.flush_active_memtable(0)?;
for i in 1..4 {
tree.insert(key(i), vec![b'L'; len], 2);
}
tree.flush_active_memtable(0)?;
tree.major_compact(64 * 1024 * 1024, 0)?;
assert_eq!(
tree.metrics().compaction_groups_partly_carried() > 0,
rebuilt,
"values of {len} bytes"
);
assert_eq!(tree.iter(crate::SeqNo::MAX, None).count(), 1_000);
assert_eq!(
tree.get(key(2), crate::SeqNo::MAX)?.map(|v| v.len()),
Some(len)
);
}
Ok(())
}