use super::*;
use crate::InternalValue;
use crate::ValueType::Value;
use crate::comparator::{self, SharedComparator};
use crate::key::InternalKey;
use alloc::collections::VecDeque;
use test_log::test;
struct VecSource {
items: VecDeque<InternalValue>,
comparator: SharedComparator,
}
impl VecSource {
fn new<I: IntoIterator<Item = InternalValue>>(items: I, comparator: SharedComparator) -> Self {
Self {
items: items.into_iter().collect(),
comparator,
}
}
}
impl MergeSource for VecSource {
fn next(&mut self) -> Option<IterItem> {
self.items.pop_front().map(Ok)
}
fn next_back(&mut self) -> Option<IterItem> {
self.items.pop_back().map(Ok)
}
fn seek(&mut self, target: &InternalKey) -> crate::Result<()> {
while let Some(front) = self.items.front() {
if front.key.compare_with(target, self.comparator.as_ref()) == Ordering::Less {
self.items.pop_front();
} else {
break;
}
}
Ok(())
}
}
impl CoherentMergeSource for VecSource {}
fn make_iv(key: &[u8], seqno: u64) -> InternalValue {
InternalValue::from_components(key, b"", seqno, Value)
}
fn k(v: &InternalValue) -> String {
String::from_utf8_lossy(&v.key.user_key).to_string()
}
#[test]
fn forward_only() {
let cmp = comparator::default_comparator();
let a = VecSource::new([make_iv(b"a", 0), make_iv(b"c", 0)], cmp.clone());
let b = VecSource::new([make_iv(b"b", 0), make_iv(b"d", 0)], cmp.clone());
let mut m = SeekingMerger::new(alloc::vec![a, b], cmp);
let keys: Vec<String> = (&mut m).map(|r| k(&r.unwrap())).collect();
assert_eq!(keys, ["a", "b", "c", "d"]);
}
#[test]
fn backward_only() {
let cmp = comparator::default_comparator();
let a = VecSource::new([make_iv(b"a", 0), make_iv(b"c", 0)], cmp.clone());
let b = VecSource::new([make_iv(b"b", 0), make_iv(b"d", 0)], cmp.clone());
let mut iter = SeekingMerger::new(alloc::vec![a, b], cmp);
let mut keys: Vec<String> = Vec::new();
while let Some(item) = iter.next_back() {
keys.push(k(&item.unwrap()));
}
assert_eq!(keys, ["d", "c", "b", "a"]);
}
#[test]
fn mixed_direction() {
let cmp = comparator::default_comparator();
let a = VecSource::new(
[make_iv(b"a", 0), make_iv(b"c", 0), make_iv(b"e", 0)],
cmp.clone(),
);
let b = VecSource::new(
[make_iv(b"b", 0), make_iv(b"d", 0), make_iv(b"f", 0)],
cmp.clone(),
);
let mut m = SeekingMerger::new(alloc::vec![a, b], cmp);
assert_eq!(k(&m.next().unwrap().unwrap()), "a");
assert_eq!(k(&m.next_back().unwrap().unwrap()), "f");
assert_eq!(k(&m.next().unwrap().unwrap()), "b");
assert_eq!(k(&m.next_back().unwrap().unwrap()), "e");
assert_eq!(k(&m.next().unwrap().unwrap()), "c");
assert_eq!(k(&m.next_back().unwrap().unwrap()), "d");
assert!(m.next().is_none());
assert!(m.next_back().is_none());
}
#[test]
fn empty_sources() {
let cmp = comparator::default_comparator();
let mut m: SeekingMerger<VecSource, _> = SeekingMerger::new(alloc::vec![], cmp);
assert!(Iterator::next(&mut m).is_none());
assert!(m.next_back().is_none());
}
#[test]
fn next_back_after_forward_exhausted_migrates_buffered_value() {
let cmp = comparator::default_comparator();
let src = VecSource::new([make_iv(b"a", 0), make_iv(b"z", 0)], cmp.clone());
let mut m = SeekingMerger::new(alloc::vec![src], cmp);
assert_eq!(k(&m.next().unwrap().unwrap()), "a");
assert_eq!(
k(&m.next_back().unwrap().unwrap()),
"z",
"migration must rescue `z` buffered in forward_tree",
);
assert!(m.next().is_none());
assert!(m.next_back().is_none());
}
#[test]
fn next_after_backward_exhausted_migrates_buffered_value() {
let cmp = comparator::default_comparator();
let src = VecSource::new([make_iv(b"a", 0), make_iv(b"z", 0)], cmp.clone());
let mut m = SeekingMerger::new(alloc::vec![src], cmp);
assert_eq!(k(&m.next_back().unwrap().unwrap()), "z");
assert_eq!(
k(&m.next().unwrap().unwrap()),
"a",
"migration must rescue `a` buffered in backward_tree",
);
assert!(m.next().is_none());
assert!(m.next_back().is_none());
}
#[test]
fn single_source_drain_both_directions() {
let cmp = comparator::default_comparator();
let a = VecSource::new(
[make_iv(b"a", 0), make_iv(b"b", 0), make_iv(b"c", 0)],
cmp.clone(),
);
let mut m = SeekingMerger::new(alloc::vec![a], cmp);
assert_eq!(k(&m.next().unwrap().unwrap()), "a");
assert_eq!(k(&m.next_back().unwrap().unwrap()), "c");
assert_eq!(k(&m.next().unwrap().unwrap()), "b");
assert!(m.next().is_none());
assert!(m.next_back().is_none());
}
struct ErrSource {
emit_forward_error: bool,
emit_backward_error: bool,
}
impl MergeSource for ErrSource {
fn next(&mut self) -> Option<IterItem> {
if self.emit_forward_error {
self.emit_forward_error = false;
Some(Err(crate::Error::Unrecoverable))
} else {
None
}
}
fn next_back(&mut self) -> Option<IterItem> {
if self.emit_backward_error {
self.emit_backward_error = false;
Some(Err(crate::Error::Unrecoverable))
} else {
None
}
}
fn seek(&mut self, _target: &InternalKey) -> crate::Result<()> {
Ok(())
}
}
impl CoherentMergeSource for ErrSource {}
#[test]
fn forward_init_propagates_error() {
let cmp = comparator::default_comparator();
let a = ErrSource {
emit_forward_error: true,
emit_backward_error: false,
};
let mut m = SeekingMerger::new(alloc::vec![a], cmp);
assert!(m.next().unwrap().is_err());
assert!(m.next().is_none());
}
#[test]
fn backward_init_propagates_error() {
let cmp = comparator::default_comparator();
let a = ErrSource {
emit_forward_error: false,
emit_backward_error: true,
};
let mut m = SeekingMerger::new(alloc::vec![a], cmp);
assert!(m.next_back().unwrap().is_err());
assert!(m.next_back().is_none());
}
#[test]
fn forward_init_keeps_earlier_prefetched_when_later_source_errs() {
let cmp = comparator::default_comparator();
let good: Box<dyn CoherentMergeSource> = Box::new(VecSource::new(
[make_iv(b"good_a", 0), make_iv(b"good_b", 0)],
cmp.clone(),
));
let bad: Box<dyn CoherentMergeSource> = Box::new(ErrSource {
emit_forward_error: true,
emit_backward_error: false,
});
let mut m = SeekingMerger::new(alloc::vec![good, bad], cmp);
assert!(m.next().unwrap().is_err());
assert_eq!(k(&m.next().unwrap().unwrap()), "good_a");
assert_eq!(k(&m.next().unwrap().unwrap()), "good_b");
assert!(m.next().is_none());
}
#[test]
fn refill_err_surfaces_before_unrelated_source_yields() {
let cmp = comparator::default_comparator();
let bad: Box<dyn CoherentMergeSource> = Box::new(LateErrSource {
first_value: Some(make_iv(b"x_bad", 0)),
already_errored: false,
});
let good: Box<dyn CoherentMergeSource> = Box::new(VecSource::new(
[
make_iv(b"y_good_1", 0),
make_iv(b"y_good_2", 0),
make_iv(b"y_good_3", 0),
],
cmp.clone(),
));
let mut m = SeekingMerger::new(alloc::vec![bad, good], cmp);
assert_eq!(k(&m.next().unwrap().unwrap()), "x_bad");
assert!(m.next().unwrap().is_err());
assert_eq!(k(&m.next().unwrap().unwrap()), "y_good_1");
assert_eq!(k(&m.next().unwrap().unwrap()), "y_good_2");
assert_eq!(k(&m.next().unwrap().unwrap()), "y_good_3");
assert!(m.next().is_none());
}
#[test]
fn cross_direction_surface_forward_pending_in_next_back() {
let cmp = comparator::default_comparator();
let a = LateErrSource {
first_value: Some(make_iv(b"only", 0)),
already_errored: false,
};
let mut m = SeekingMerger::new(alloc::vec![a], cmp);
assert_eq!(k(&m.next().unwrap().unwrap()), "only");
assert!(m.next_back().unwrap().is_err());
}
#[test]
fn cross_direction_surface_backward_pending_in_next() {
let cmp = comparator::default_comparator();
let a = LateErrSource {
first_value: Some(make_iv(b"only", 0)),
already_errored: false,
};
let mut m = SeekingMerger::new(alloc::vec![a], cmp);
assert_eq!(k(&m.next_back().unwrap().unwrap()), "only");
assert!(m.next().unwrap().is_err());
}
#[test]
fn backward_init_keeps_earlier_prefetched_when_later_source_errs() {
let cmp = comparator::default_comparator();
let good: Box<dyn CoherentMergeSource> = Box::new(VecSource::new(
[make_iv(b"good_a", 0), make_iv(b"good_b", 0)],
cmp.clone(),
));
let bad: Box<dyn CoherentMergeSource> = Box::new(ErrSource {
emit_forward_error: false,
emit_backward_error: true,
});
let mut m = SeekingMerger::new(alloc::vec![good, bad], cmp);
assert!(m.next_back().unwrap().is_err());
assert_eq!(k(&m.next_back().unwrap().unwrap()), "good_b");
assert_eq!(k(&m.next_back().unwrap().unwrap()), "good_a");
assert!(m.next_back().is_none());
}
struct LateErrSource {
first_value: Option<InternalValue>,
already_errored: bool,
}
impl MergeSource for LateErrSource {
fn next(&mut self) -> Option<IterItem> {
if let Some(v) = self.first_value.take() {
Some(Ok(v))
} else if !self.already_errored {
self.already_errored = true;
Some(Err(crate::Error::Unrecoverable))
} else {
None
}
}
fn next_back(&mut self) -> Option<IterItem> {
self.next()
}
fn seek(&mut self, _target: &InternalKey) -> crate::Result<()> {
Ok(())
}
}
impl CoherentMergeSource for LateErrSource {}
struct IndependentCursorSource {
items: Vec<crate::InternalValue>,
front_idx: usize,
back_idx: usize,
comparator: SharedComparator,
}
impl IndependentCursorSource {
fn new<I: IntoIterator<Item = crate::InternalValue>>(
items: I,
comparator: SharedComparator,
) -> Self {
let items: Vec<_> = items.into_iter().collect();
debug_assert!(
items.is_sorted_by(|a, b| {
a.key.compare_with(&b.key, comparator.as_ref()) != Ordering::Greater
}),
"IndependentCursorSource items must be sorted ascending by key",
);
let n = items.len();
Self {
items,
front_idx: 0,
back_idx: n,
comparator,
}
}
}
impl MergeSource for IndependentCursorSource {
fn next(&mut self) -> Option<IterItem> {
if self.front_idx >= self.back_idx {
return None;
}
#[expect(
clippy::indexing_slicing,
reason = "front_idx < back_idx <= items.len() by invariant"
)]
let v = self.items[self.front_idx].clone();
self.front_idx += 1;
Some(Ok(v))
}
fn next_back(&mut self) -> Option<IterItem> {
if self.front_idx >= self.back_idx {
return None;
}
self.back_idx -= 1;
#[expect(
clippy::indexing_slicing,
reason = "back_idx < items.len() after decrement, by invariant"
)]
let v = self.items[self.back_idx].clone();
Some(Ok(v))
}
fn seek(&mut self, target: &InternalKey) -> crate::Result<()> {
let idx = self.items.partition_point(|v| {
v.key.compare_with(target, self.comparator.as_ref()) == Ordering::Less
});
self.front_idx = self.front_idx.max(idx);
self.back_idx = self.back_idx.min(idx);
Ok(())
}
}
impl CoherentMergeSource for IndependentCursorSource {}
#[test]
fn switch_to_backward_after_drain_emits_no_duplicates() {
let cmp = comparator::default_comparator();
let src = IndependentCursorSource::new(
[
make_iv(b"a", 0),
make_iv(b"b", 0),
make_iv(b"c", 0),
make_iv(b"d", 0),
],
cmp.clone(),
);
let mut m = SeekingMerger::new(alloc::vec![src], cmp);
assert_eq!(k(&m.next().unwrap().unwrap()), "a");
assert_eq!(k(&m.next().unwrap().unwrap()), "b");
assert_eq!(k(&m.next().unwrap().unwrap()), "c");
assert_eq!(k(&m.next().unwrap().unwrap()), "d");
assert!(m.next().is_none(), "source exhausted forward");
assert!(
m.next_back().is_none(),
"backward must not re-emit forward-consumed items",
);
assert!(m.next_back().is_none(), "stays exhausted");
}
#[test]
fn mid_stream_alternation_emits_no_duplicates_independent_cursor() {
let cmp = comparator::default_comparator();
let src = IndependentCursorSource::new(
[
make_iv(b"a", 0),
make_iv(b"b", 0),
make_iv(b"c", 0),
make_iv(b"d", 0),
make_iv(b"e", 0),
make_iv(b"f", 0),
],
cmp.clone(),
);
let mut m = SeekingMerger::new(alloc::vec![src], cmp);
assert_eq!(k(&m.next().unwrap().unwrap()), "a");
assert_eq!(k(&m.next_back().unwrap().unwrap()), "f");
assert_eq!(k(&m.next().unwrap().unwrap()), "b");
assert_eq!(k(&m.next_back().unwrap().unwrap()), "e");
assert_eq!(k(&m.next().unwrap().unwrap()), "c");
assert_eq!(k(&m.next_back().unwrap().unwrap()), "d");
assert!(m.next().is_none());
assert!(m.next_back().is_none());
}
#[test]
fn forward_refill_error_yields_buffered_then_err_on_next_call() {
let cmp = comparator::default_comparator();
let a = LateErrSource {
first_value: Some(make_iv(b"first", 0)),
already_errored: false,
};
let mut m = SeekingMerger::new(alloc::vec![a], cmp);
assert_eq!(k(&m.next().unwrap().unwrap()), "first");
assert!(m.next().unwrap().is_err());
assert!(m.next().is_none());
}
#[test]
fn backward_refill_error_yields_buffered_then_err_on_next_call() {
let cmp = comparator::default_comparator();
let a = LateErrSource {
first_value: Some(make_iv(b"first", 0)),
already_errored: false,
};
let mut m = SeekingMerger::new(alloc::vec![a], cmp);
assert_eq!(k(&m.next_back().unwrap().unwrap()), "first");
assert!(m.next_back().unwrap().is_err());
assert!(m.next_back().is_none());
}
#[test]
fn forward_step_then_backward_drain_yields_every_item() {
let cmp = comparator::default_comparator();
let a = VecSource::new(
[
make_iv(b"a", 0),
make_iv(b"c", 0),
make_iv(b"e", 0),
make_iv(b"g", 0),
],
cmp.clone(),
);
let b = VecSource::new(
[
make_iv(b"b", 0),
make_iv(b"d", 0),
make_iv(b"f", 0),
make_iv(b"h", 0),
],
cmp.clone(),
);
let mut m = SeekingMerger::new(alloc::vec![a, b], cmp);
let mut keys = alloc::vec![k(&m.next().unwrap().unwrap())];
while let Some(item) = m.next_back() {
keys.push(k(&item.unwrap()));
}
keys.sort();
assert_eq!(
keys,
["a", "b", "c", "d", "e", "f", "g", "h"],
"an item was stranded between the two tournaments",
);
}
#[test]
fn backward_step_then_forward_drain_yields_every_item() {
let cmp = comparator::default_comparator();
let a = VecSource::new(
[
make_iv(b"a", 0),
make_iv(b"c", 0),
make_iv(b"e", 0),
make_iv(b"g", 0),
],
cmp.clone(),
);
let b = VecSource::new(
[
make_iv(b"b", 0),
make_iv(b"d", 0),
make_iv(b"f", 0),
make_iv(b"h", 0),
],
cmp.clone(),
);
let mut m = SeekingMerger::new(alloc::vec![a, b], cmp);
let mut keys = alloc::vec![k(&m.next_back().unwrap().unwrap())];
for item in &mut m {
keys.push(k(&item.unwrap()));
}
keys.sort();
assert_eq!(
keys,
["a", "b", "c", "d", "e", "f", "g", "h"],
"an item was stranded between the two tournaments",
);
}
#[test]
fn adopting_a_parked_leaf_keeps_the_forward_stream_sorted() {
let cmp = comparator::default_comparator();
let a = VecSource::new(
[make_iv(b"a", 0), make_iv(b"b", 0), make_iv(b"d", 0)],
cmp.clone(),
);
let b = VecSource::new([make_iv(b"c", 0)], cmp.clone());
let mut m = SeekingMerger::new(alloc::vec![a, b], cmp);
assert_eq!(k(&m.next_back().unwrap().unwrap()), "d");
let mut keys: Vec<String> = Vec::new();
for item in &mut m {
keys.push(k(&item.unwrap()));
}
assert_eq!(keys, ["a", "b", "c"], "the forward stream must stay sorted");
}
#[derive(Clone, Copy)]
enum OnJump {
Pass,
Refuse,
Fail,
FailRefill,
}
struct JumpSource {
items: VecDeque<InternalValue>,
on_jump: OnJump,
fail_next_pull: bool,
}
impl JumpSource {
fn new(items: impl IntoIterator<Item = InternalValue>, on_jump: OnJump) -> Self {
Self {
items: items.into_iter().collect(),
on_jump,
fail_next_pull: false,
}
}
fn pull(&mut self, from_back: bool) -> Option<IterItem> {
if core::mem::take(&mut self.fail_next_pull) {
return Some(Err(jump_error()));
}
if from_back {
self.items.pop_back().map(Ok)
} else {
self.items.pop_front().map(Ok)
}
}
fn at(&self, idx: usize, key: &[u8]) -> bool {
self.items
.get(idx)
.is_some_and(|item| &*item.key.user_key == key)
}
fn jumps(&mut self) -> crate::Result<bool> {
match self.on_jump {
OnJump::Pass => Ok(true),
OnJump::Refuse => Ok(false),
OnJump::Fail => Err(jump_error()),
OnJump::FailRefill => {
self.fail_next_pull = true;
Ok(true)
}
}
}
}
fn jump_error() -> crate::Error {
crate::Error::Io(crate::io::Error::other("jump"))
}
impl MergeSource for JumpSource {
fn next(&mut self) -> Option<IterItem> {
self.pull(false)
}
fn next_back(&mut self) -> Option<IterItem> {
self.pull(true)
}
fn seek(&mut self, _target: &InternalKey) -> crate::Result<()> {
Ok(())
}
}
impl CoherentMergeSource for JumpSource {}
impl crate::skip_key::SkipKey for JumpSource {
fn skip_front_past(&mut self, key: &[u8]) -> crate::Result<bool> {
if !self.jumps()? {
return Ok(false);
}
while self.at(0, key) {
self.items.pop_front();
}
Ok(true)
}
fn skip_back_to_newest(&mut self, key: &[u8], _seqno: u64) -> crate::Result<bool> {
if !self.jumps()? {
return Ok(false);
}
while let Some(last) = self.items.len().checked_sub(1)
&& last > 0
&& self.at(last, key)
&& self.at(last - 1, key)
{
self.items.pop_back();
}
Ok(self
.items
.len()
.checked_sub(1)
.is_some_and(|last| self.at(last, key)))
}
}
fn versions(key: &'static [u8], newest: u64, oldest: u64) -> impl Iterator<Item = InternalValue> {
(oldest..=newest)
.rev()
.map(move |seqno| make_iv(key, seqno))
}
fn shown(item: IterItem) -> String {
match item {
Ok(v) => format!("{}{}", k(&v), v.key.seqno),
Err(_) => "err".to_owned(),
}
}
fn front_jump(on_jump: OnJump) -> (bool, Vec<String>) {
let cmp = comparator::default_comparator();
let older = JumpSource::new(
core::iter::once(make_iv(b"a", 0))
.chain(versions(b"b", 5, 1))
.chain([make_iv(b"c", 0)]),
OnJump::Pass,
);
let newer = JumpSource::new(versions(b"b", 15, 6).chain([make_iv(b"d", 0)]), on_jump);
let mut m = SeekingMerger::new(alloc::vec![older, newer], cmp);
assert_eq!(m.next().map(shown).as_deref(), Some("a0"));
assert_eq!(m.next().map(shown).as_deref(), Some("b15"));
let jumped = crate::skip_key::SkipKey::skip_front_past(&mut m, b"b").unwrap();
(jumped, m.map(shown).collect())
}
#[test]
fn front_jump_passes_every_source_at_the_key() {
assert_eq!(
front_jump(OnJump::Pass),
(true, vec!["c0".into(), "d0".into()])
);
}
#[test]
fn front_jump_a_source_refuses_leaves_the_versions() {
let (jumped, rest) = front_jump(OnJump::Refuse);
assert!(!jumped);
let mut expected: Vec<String> = (6..=14).rev().map(|s| format!("b{s}")).collect();
expected.extend((1..=5).rev().map(|s| format!("b{s}")));
expected.extend(["c0".into(), "d0".into()]);
assert_eq!(rest, expected);
}
#[test]
fn front_jump_a_source_fails_queues_its_error() {
for on_jump in [OnJump::Fail, OnJump::FailRefill] {
assert_eq!(front_jump(on_jump), (true, vec!["err".into(), "c0".into()]));
}
}
#[test]
fn front_jump_after_the_back_was_pulled_does_not_jump() {
let cmp = comparator::default_comparator();
let source = JumpSource::new(versions(b"b", 20, 1), OnJump::Pass);
let mut m = SeekingMerger::new(alloc::vec![source], cmp);
assert_eq!(m.next_back().map(shown).as_deref(), Some("b1"));
assert_eq!(m.next().map(shown).as_deref(), Some("b20"));
assert!(!crate::skip_key::SkipKey::skip_front_past(&mut m, b"b").unwrap());
}
fn back_jump(on_jump: OnJump) -> (bool, Vec<String>) {
let cmp = comparator::default_comparator();
let newer = JumpSource::new(
core::iter::once(make_iv(b"a", 0)).chain(versions(b"b", 30, 21)),
OnJump::Pass,
);
let older = JumpSource::new(versions(b"b", 20, 1), on_jump);
let mut m = SeekingMerger::new(alloc::vec![newer, older], cmp);
for seqno in 1..=8 {
assert_eq!(
m.next_back().map(shown),
Some(format!("b{seqno}")),
"walked from the back"
);
}
let jumped = crate::skip_key::SkipKey::skip_back_to_newest(&mut m, b"b", u64::MAX).unwrap();
(jumped, m.rev().map(shown).collect())
}
#[test]
fn back_jump_moves_every_source_to_its_newest() {
assert_eq!(
back_jump(OnJump::Pass),
(true, vec!["b20".into(), "b30".into(), "a0".into()])
);
}
#[test]
fn back_jump_a_source_refuses_keeps_its_versions() {
let (jumped, rest) = back_jump(OnJump::Refuse);
assert!(jumped);
let mut expected: Vec<String> = (9..=20).map(|s| format!("b{s}")).collect();
expected.extend(["b30".into(), "a0".into()]);
assert_eq!(rest, expected);
}
#[test]
fn back_jump_a_source_fails_queues_its_error() {
for on_jump in [OnJump::Fail, OnJump::FailRefill] {
assert_eq!(
back_jump(on_jump),
(true, vec!["err".into(), "b30".into(), "a0".into()])
);
}
}
#[test]
fn back_jump_after_a_short_run_does_not_jump() {
let cmp = comparator::default_comparator();
let source = JumpSource::new(versions(b"b", 20, 1), OnJump::Pass);
let mut m = SeekingMerger::new(alloc::vec![source], cmp);
assert_eq!(m.next_back().map(shown).as_deref(), Some("b1"));
assert!(!crate::skip_key::SkipKey::skip_back_to_newest(&mut m, b"b", u64::MAX).unwrap());
assert_eq!(m.next_back().map(shown).as_deref(), Some("b2"));
}