use super::*;
use crate::KeyRange;
use crate::comparator::DefaultUserComparator;
use test_log::test;
fn default_cmp() -> &'static DefaultUserComparator {
&DefaultUserComparator
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct FakeTable {
id: u64,
key_range: KeyRange,
}
impl Ranged for FakeTable {
fn key_range(&self) -> &KeyRange {
&self.key_range
}
}
impl Aged for FakeTable {
type Age = u64;
fn age(&self) -> u64 {
self.id
}
}
fn s(id: u64, min: &str, max: &str) -> FakeTable {
FakeTable {
id,
key_range: KeyRange::new((min.as_bytes().into(), max.as_bytes().into())),
}
}
#[test]
fn optimize_runs_empty() {
let runs = vec![];
let runs = optimize_runs::<FakeTable>(runs, default_cmp());
assert_eq!(Vec::<Run<FakeTable>>::new(), &*runs);
}
#[test]
fn optimize_runs_one() {
let runs = vec![Run::new(vec![s(0, "a", "b")]).unwrap()];
let runs = optimize_runs::<FakeTable>(runs, default_cmp());
assert_eq!(vec![Run::new(vec![s(0, "a", "b")]).unwrap()], &*runs);
}
#[test]
fn optimize_runs_two_overlap() {
let runs = vec![
Run::new(vec![s(0, "a", "b")]).unwrap(),
Run::new(vec![s(1, "a", "b")]).unwrap(),
];
let runs = optimize_runs::<FakeTable>(runs, default_cmp());
assert_eq!(
vec![
Run::new(vec![s(0, "a", "b")]).unwrap(),
Run::new(vec![s(1, "a", "b")]).unwrap(),
],
&*runs
);
}
#[test]
fn optimize_runs_two_overlap_2() {
let runs = vec![
Run::new(vec![s(0, "a", "z")]).unwrap(),
Run::new(vec![s(1, "c", "f")]).unwrap(),
];
let runs = optimize_runs::<FakeTable>(runs, default_cmp());
assert_eq!(
vec![
Run::new(vec![s(0, "a", "z")]).unwrap(),
Run::new(vec![s(1, "c", "f")]).unwrap(),
],
&*runs
);
}
#[test]
fn optimize_runs_two_overlap_3() {
let runs = vec![
Run::new(vec![s(0, "c", "f")]).unwrap(),
Run::new(vec![s(1, "a", "z")]).unwrap(),
];
let runs = optimize_runs::<FakeTable>(runs, default_cmp());
assert_eq!(
vec![
Run::new(vec![s(0, "c", "f")]).unwrap(),
Run::new(vec![s(1, "a", "z")]).unwrap()
],
&*runs
);
}
#[test]
fn optimize_runs_two_disjoint() {
let runs = vec![
Run::new(vec![s(0, "a", "c")]).unwrap(),
Run::new(vec![s(1, "d", "f")]).unwrap(),
];
let runs = optimize_runs::<FakeTable>(runs, default_cmp());
assert_eq!(
vec![Run::new(vec![s(0, "a", "c"), s(1, "d", "f")]).unwrap()],
&*runs,
);
}
#[test]
fn optimize_runs_overlap_transitive_keeps_newest_in_front() {
let runs = vec![
Run::new(vec![s(2, "m", "p")]).unwrap(),
Run::new(vec![s(1, "a", "z")]).unwrap(),
Run::new(vec![s(0, "a", "c")]).unwrap(),
];
let runs = optimize_runs::<FakeTable>(runs, default_cmp());
assert_eq!(
vec![
Run::new(vec![s(2, "m", "p")]).unwrap(),
Run::new(vec![s(1, "a", "z")]).unwrap(),
Run::new(vec![s(0, "a", "c")]).unwrap(),
],
&*runs
);
}
#[test]
fn l0_order_puts_a_newer_flush_ahead_of_an_output_given_in_front() {
let runs = [
Run::new(vec![s(15, "a", "f")]).unwrap(),
Run::new(vec![s(30, "d", "e")]).unwrap(),
];
let runs = order_by_age(
runs.iter().flat_map(|run| run.iter().cloned()),
default_cmp(),
);
assert_eq!(
vec![
Run::new(vec![s(30, "d", "e")]).unwrap(),
Run::new(vec![s(15, "a", "f")]).unwrap(),
],
&*runs
);
}
#[test]
fn l0_order_repairs_a_persisted_order_with_a_newer_table_behind() {
let runs = [
Run::new(vec![s(1, "m", "p")]).unwrap(),
Run::new(vec![s(0, "a", "c"), s(2, "n", "z")]).unwrap(),
];
let runs = order_by_age(
runs.iter().flat_map(|run| run.iter().cloned()),
default_cmp(),
);
let run_of = |id: u64| {
runs.iter()
.position(|run| run.iter().any(|t| t.id == id))
.unwrap()
};
assert!(
run_of(2) < run_of(1),
"C goes ahead of the older B it overlaps"
);
}
#[test]
fn l0_order_of_a_wide_disjoint_run_compares_n_log_n_keys() {
use core::sync::atomic::{AtomicU64, Ordering};
struct Counting(AtomicU64);
impl UserComparator for Counting {
fn name(&self) -> &'static str {
"counting"
}
fn compare(&self, a: &[u8], b: &[u8]) -> core::cmp::Ordering {
self.0.fetch_add(1, Ordering::Relaxed);
a.cmp(b)
}
}
let n: u64 = 512;
let tables: Vec<FakeTable> = (0..n)
.map(|i| {
let key = format!("k{i:06}");
s(i, &key, &key)
})
.collect();
let cmp = Counting(AtomicU64::new(0));
let runs = order_by_age(tables, &cmp);
assert_eq!(runs.len(), 1, "disjoint tables share one run");
assert_eq!(runs.first().map(|run| run.len()), Some(512));
let compared = cmp.0.load(Ordering::Relaxed);
let bound = 4 * n * (u64::from(n.ilog2()) + 1);
assert!(
compared <= bound,
"laying out {n} disjoint tables compared {compared} keys, over {bound}"
);
}
#[test]
fn recency_order_is_checked_only_among_overlapping_tables() {
let broken = [
Run::new(vec![s(1, "m", "p")]).unwrap(),
Run::new(vec![s(0, "a", "c"), s(2, "n", "z")]).unwrap(),
];
assert!(!in_recency_order(
&broken.iter().collect::<Vec<_>>(),
default_cmp()
));
let per_table = [
Run::new(vec![s(2, "a", "z")]).unwrap(),
Run::new(vec![s(1, "b", "c")]).unwrap(),
Run::new(vec![s(0, "x", "y")]).unwrap(),
];
assert!(in_recency_order(
&per_table.iter().collect::<Vec<_>>(),
default_cmp()
));
let disjoint = [Run::new(vec![s(0, "a", "b"), s(5, "c", "d"), s(3, "e", "f")]).unwrap()];
assert!(in_recency_order(
&disjoint.iter().collect::<Vec<_>>(),
default_cmp()
));
}
#[test]
fn recency_order_check_matches_comparing_every_pair() {
use proptest::prelude::*;
let range = (0u8..12, 0u8..12).prop_map(|(x, y)| (x.min(y), x.max(y)));
proptest!(|(tables in proptest::collection::vec((range, any::<u16>()), 0..30))| {
let runs: Vec<Run<FakeTable>> = tables
.iter()
.enumerate()
.map(|(at, ((lo, hi), age))| {
Run::new(vec![FakeTable {
id: (u64::from(*age) << 8) | at as u64,
key_range: KeyRange::new((vec![b'a' + lo].into(), vec![b'a' + hi].into())),
}])
.unwrap()
})
.collect();
let refs: Vec<&Run<FakeTable>> = runs.iter().collect();
let every_pair = refs.iter().enumerate().all(|(i, a)| {
refs.iter().skip(i + 1).all(|b| {
a.iter().zip(b.iter()).all(|(a, b)| {
!a.key_range().overlaps_with_key_range_cmp(b.key_range(), default_cmp())
|| a.id >= b.id
})
})
});
prop_assert_eq!(in_recency_order(&refs, default_cmp()), every_pair);
});
}
#[test]
fn l0_order_holds_recency_order_for_any_input_layout() {
use proptest::prelude::*;
let range = (0u8..26, 0u8..26).prop_map(|(x, y)| (x.min(y), x.max(y)));
proptest!(|(ranges in proptest::collection::vec(range, 1..40), seed in any::<u64>())| {
let mut tables: Vec<FakeTable> = ranges
.iter()
.enumerate()
.map(|(id, (lo, hi))| FakeTable {
id: id as u64,
key_range: KeyRange::new((vec![b'a' + lo].into(), vec![b'a' + hi].into())),
})
.collect();
let mut state = seed | 1;
for i in (1..tables.len()).rev() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
let j = usize::try_from(state % (i as u64 + 1)).unwrap();
tables.swap(i, j);
}
let runs = order_by_age(tables, default_cmp());
let placed: Vec<(usize, &FakeTable)> = runs
.iter()
.enumerate()
.flat_map(|(at, run)| run.iter().map(move |t| (at, t)))
.collect();
prop_assert_eq!(placed.len(), ranges.len());
for (i, (run_a, a)) in placed.iter().enumerate() {
for (run_b, b) in placed.iter().skip(i + 1) {
if !a.key_range().overlaps_with_key_range_cmp(b.key_range(), default_cmp()) {
continue;
}
prop_assert_ne!(run_a, run_b, "overlapping tables share a run");
let (newer_run, older_run) =
if a.id > b.id { (run_a, run_b) } else { (run_b, run_a) };
prop_assert!(newer_run < older_run, "a newer table behind an older one");
}
}
});
}
#[test]
fn optimize_runs_flush_sequence_keeps_recency_order_and_disjoint_runs() {
use proptest::prelude::*;
fn overlaps(a: &FakeTable, b: &FakeTable) -> bool {
a.key_range()
.overlaps_with_key_range_cmp(b.key_range(), default_cmp())
}
let range = (0u8..26, 0u8..26).prop_map(|(x, y)| (x.min(y), x.max(y)));
proptest!(|(ranges in proptest::collection::vec(range, 1..40))| {
let mut runs: Vec<Run<FakeTable>> = Vec::new();
for (id, (lo, hi)) in ranges.iter().enumerate() {
let table = FakeTable {
id: id as u64,
key_range: KeyRange::new((
vec![b'a' + lo].into(),
vec![b'a' + hi].into(),
)),
};
let mut input = vec![Run::new(vec![table]).unwrap()];
input.extend(runs);
runs = optimize_runs(input, default_cmp());
let placed: Vec<(usize, &FakeTable)> = runs
.iter()
.enumerate()
.flat_map(|(at, run)| run.iter().map(move |t| (at, t)))
.collect();
let mut ids: Vec<u64> = placed.iter().map(|(_, t)| t.id).collect();
ids.sort_unstable();
prop_assert_eq!(ids, (0..=id as u64).collect::<Vec<_>>());
for (i, (run_a, a)) in placed.iter().enumerate() {
for (run_b, b) in placed.iter().skip(i + 1) {
if !overlaps(a, b) {
continue;
}
prop_assert_ne!(run_a, run_b, "overlapping tables share a run");
let (newer_run, older_run) =
if a.id > b.id { (run_a, run_b) } else { (run_b, run_a) };
prop_assert!(
newer_run < older_run,
"a newer table sits behind an older one it overlaps"
);
}
}
}
});
}
#[test]
fn optimize_runs_two_disjoint_2() {
let runs = vec![
Run::new(vec![s(1, "d", "f")]).unwrap(),
Run::new(vec![s(0, "a", "c")]).unwrap(),
];
let runs = optimize_runs::<FakeTable>(runs, default_cmp());
assert_eq!(
vec![Run::new(vec![s(0, "a", "c"), s(1, "d", "f")]).unwrap()],
&*runs,
);
}