use super::run::Ranged;
use crate::comparator::UserComparator;
use crate::version::Run;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
pub fn optimize_runs<T: Clone + Ranged>(
runs: Vec<Run<T>>,
cmp: &dyn UserComparator,
) -> Vec<Run<T>> {
if runs.len() <= 1 {
runs
} else {
let mut new_runs: Vec<Run<T>> = Vec::new();
for run in &runs {
for table in run.iter() {
let last_overlap = new_runs.iter().rposition(|existing_run| {
!existing_run
.get_overlapping_cmp(table.key_range(), cmp)
.is_empty()
});
let target = match last_overlap {
Some(idx) => new_runs.get_mut(idx + 1),
None => new_runs.first_mut(),
};
if let Some(target) = target {
target.insert_sorted_cmp(table.clone(), cmp);
} else {
#[expect(
clippy::expect_used,
reason = "we pass in a table, so the run cannot be None"
)]
new_runs.push(Run::new(vec![table.clone()]).expect("run should not be empty"));
}
}
}
new_runs
}
}
pub trait Aged {
type Age: Ord;
fn age(&self) -> Self::Age;
}
impl Aged for crate::table::Table {
type Age = (crate::table::TableId, crate::table::TableId);
fn age(&self) -> Self::Age {
(self.l0_recency(), self.id())
}
}
pub fn order_by_age<T: Clone + Ranged + Aged>(
tables: impl IntoIterator<Item = T>,
cmp: &dyn UserComparator,
) -> Vec<Run<T>> {
let mut tables: Vec<T> = tables.into_iter().collect();
tables.sort_by_key(|table| core::cmp::Reverse(table.age()));
let runs = tables
.into_iter()
.filter_map(|table| Run::new(alloc::vec![table]))
.collect();
optimize_runs(runs, cmp)
}
pub fn in_recency_order<T: Ranged + Aged>(runs: &[&Run<T>], cmp: &dyn UserComparator) -> bool {
let mut placed: Vec<(usize, &T)> = runs
.iter()
.enumerate()
.flat_map(|(at, run)| run.iter().map(move |table| (at, table)))
.collect();
let mut points: Vec<&[u8]> = placed
.iter()
.flat_map(|&(_, table)| {
let range = table.key_range();
[range.min().as_ref(), range.max().as_ref()]
})
.collect();
points.sort_by(|a, b| cmp.compare(a, b));
points.dedup_by(|a, b| cmp.compare(a, b) == core::cmp::Ordering::Equal);
let index = |key: &[u8]| {
points
.binary_search_by(|point| cmp.compare(point, key))
.unwrap_or_else(|at| at)
};
placed.sort_by(|(_, a), (_, b)| b.age().cmp(&a.age()));
let mut newer_runs = RangeMax::new(points.len());
placed.iter().all(|(run, table)| {
let range = table.key_range();
let (lo, hi) = (index(range.min()), index(range.max()));
let in_order = newer_runs.max(lo, hi) <= run + 1;
newer_runs.raise(lo, hi, run + 1);
in_order
})
}
struct RangeMax {
len: usize,
whole: Vec<usize>,
any: Vec<usize>,
}
impl RangeMax {
fn new(len: usize) -> Self {
let nodes = 4 * len.max(1);
Self {
len,
whole: alloc::vec![0; nodes],
any: alloc::vec![0; nodes],
}
}
fn raise(&mut self, lo: usize, hi: usize, value: usize) {
if self.len > 0 {
self.raise_at(1, 0, self.len - 1, lo, hi, value);
}
}
fn raise_at(&mut self, node: usize, l: usize, r: usize, lo: usize, hi: usize, value: usize) {
if hi < l || r < lo {
return;
}
if let Some(any) = self.any.get_mut(node) {
*any = (*any).max(value);
}
if lo <= l && r <= hi {
if let Some(whole) = self.whole.get_mut(node) {
*whole = (*whole).max(value);
}
return;
}
let mid = l + (r - l) / 2;
self.raise_at(2 * node, l, mid, lo, hi, value);
self.raise_at(2 * node + 1, mid + 1, r, lo, hi, value);
}
fn max(&self, lo: usize, hi: usize) -> usize {
if self.len == 0 {
return 0;
}
self.max_at(1, 0, self.len - 1, lo, hi)
}
fn max_at(&self, node: usize, l: usize, r: usize, lo: usize, hi: usize) -> usize {
if hi < l || r < lo {
return 0;
}
if lo <= l && r <= hi {
return self.any.get(node).copied().unwrap_or(0);
}
let mid = l + (r - l) / 2;
let whole = self.whole.get(node).copied().unwrap_or(0);
whole
.max(self.max_at(2 * node, l, mid, lo, hi))
.max(self.max_at(2 * node + 1, mid + 1, r, lo, hi))
}
}
#[derive(Clone)]
struct RangedId {
id: u64,
key_range: crate::KeyRange,
}
impl Ranged for RangedId {
fn key_range(&self) -> &crate::KeyRange {
&self.key_range
}
}
#[doc(hidden)]
#[must_use]
pub fn optimize_key_ranges(runs: Vec<Vec<(u64, crate::KeyRange)>>) -> Vec<Vec<u64>> {
let runs = runs
.into_iter()
.filter_map(|run| {
Run::new(
run.into_iter()
.map(|(id, key_range)| RangedId { id, key_range })
.collect(),
)
})
.collect();
optimize_runs(runs, &crate::comparator::DefaultUserComparator)
.iter()
.map(|run| run.iter().map(|table| table.id).collect())
.collect()
}
#[cfg(test)]
#[expect(clippy::unwrap_used)]
mod tests;