mod blob_file_list;
mod diff;
pub mod edit;
pub mod edit_log;
mod framing;
mod optimize;
mod persist;
pub mod recovery;
pub mod run;
mod super_version;
#[cfg(test)]
mod tests;
pub use blob_file_list::BlobFileList;
pub use persist::{SNAPSHOT_TEMPORARY_SUFFIX, persist_version};
pub use run::Run;
pub use super_version::RetentionEffect;
pub use super_version::SnapshotRef;
pub use super_version::{SuperVersion, SuperVersions};
use crate::TreeType;
use crate::blob_tree::{FragmentationEntry, FragmentationMap};
use crate::checksum::ChecksumType;
use crate::coding::Encode;
use crate::compaction::state::hidden_set::HiddenSet;
use crate::version::recovery::Recovery;
use crate::{
HashSet, KeyRange, Table, TableId,
comparator::UserComparator,
vlog::{BlobFile, BlobFileId},
};
use alloc::sync::Arc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::ops::Deref;
#[doc(hidden)]
pub use optimize::optimize_key_ranges;
use optimize::{optimize_runs, order_by_age};
fn place_runs(
level_idx: usize,
runs: Vec<Run<Table>>,
cmp: &dyn crate::comparator::UserComparator,
) -> Vec<Run<Table>> {
if level_idx == 0 {
order_by_age(
runs.into_iter()
.flat_map(|mut run| core::mem::take(run.inner_mut())),
cmp,
)
} else {
optimize_runs(runs, cmp)
}
}
use run::Ranged;
fn runs_of_new_tables(tables: &[Table], cmp: &dyn UserComparator) -> Vec<Run<Table>> {
let follows = |before: &Table, after: &Table| {
cmp.compare(before.key_range().max(), after.key_range().min()) == core::cmp::Ordering::Less
};
if tables
.iter()
.zip(tables.iter().skip(1))
.all(|(before, after)| follows(before, after))
{
return Run::new(tables.to_vec()).into_iter().collect();
}
let mut sorted = tables.to_vec();
sorted.sort_by(|a, b| cmp.compare(a.key_range().min(), b.key_range().min()));
let mut runs: Vec<Vec<Table>> = Vec::new();
for table in sorted {
match runs
.iter_mut()
.find(|run| run.last().is_some_and(|last| follows(last, &table)))
{
Some(run) => run.push(table),
None => runs.push(vec![table]),
}
}
runs.into_iter().filter_map(Run::new).collect()
}
fn level_holds_any(level: &Level, named: impl Fn(TableId) -> bool) -> bool {
level
.iter()
.flat_map(|run| run.iter())
.any(|table| named(table.id()))
}
pub struct TransformContext<'a> {
comparator: &'a dyn UserComparator,
}
impl<'a> TransformContext<'a> {
pub fn new(comparator: &'a dyn UserComparator) -> Self {
Self { comparator }
}
pub fn comparator(&self) -> &'a dyn UserComparator {
self.comparator
}
}
pub const DEFAULT_LEVEL_COUNT: u8 = 7;
pub type VersionId = u64;
pub const RENUMBER_FROM: VersionId = 1 << 63;
impl Ranged for Table {
fn key_range(&self) -> &KeyRange {
&self.metadata.key_range
}
}
impl run::HoldsRangeTombstones for Table {
fn holds_range_tombstones(&self) -> bool {
!self.range_tombstones().is_empty()
}
}
pub struct GenericLevel<T: Ranged> {
runs: Vec<Arc<Run<T>>>,
}
impl<T: Ranged> core::ops::Deref for GenericLevel<T> {
type Target = [Arc<Run<T>>];
fn deref(&self) -> &Self::Target {
&self.runs
}
}
impl<T: Ranged> GenericLevel<T> {
pub fn new(runs: Vec<Arc<Run<T>>>) -> Self {
Self { runs }
}
pub fn table_count(&self) -> usize {
self.iter().map(|x| x.len()).sum()
}
pub fn run_count(&self) -> usize {
self.runs.len()
}
pub fn is_disjoint(&self) -> bool {
self.run_count() == 1
}
pub fn is_empty(&self) -> bool {
self.runs.is_empty()
}
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Arc<Run<T>>> {
self.runs.iter()
}
}
#[derive(Clone)]
pub struct Level(Arc<GenericLevel<Table>>);
impl core::ops::Deref for Level {
type Target = GenericLevel<Table>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Level {
pub fn empty() -> Self {
Self::from_runs(vec![])
}
pub fn from_runs(runs: Vec<Arc<Run<Table>>>) -> Self {
Self(Arc::new(GenericLevel { runs }))
}
pub fn list_ids(&self) -> HashSet<TableId> {
self.iter()
.flat_map(|run| run.iter())
.map(Table::id)
.collect()
}
pub fn first_run(&self) -> Option<&Arc<Run<Table>>> {
self.runs.first()
}
pub fn size(&self) -> u64 {
self.0
.iter()
.flat_map(|x| x.iter())
.map(Table::file_size)
.sum()
}
pub fn aggregate_key_range(&self) -> KeyRange {
self.aggregate_key_range_cmp(&crate::comparator::DefaultUserComparator)
}
pub fn aggregate_key_range_cmp<C: crate::comparator::UserComparator + ?Sized>(
&self,
cmp: &C,
) -> KeyRange {
if self.run_count() == 1 {
#[expect(
clippy::expect_used,
reason = "we check for run_count, so the first run must exist"
)]
self.runs
.first()
.expect("should exist")
.aggregate_key_range()
} else {
let key_ranges = self
.iter()
.map(|x| Run::aggregate_key_range(x))
.collect::<Vec<_>>();
KeyRange::aggregate_cmp(key_ranges.iter(), cmp)
}
}
}
#[derive(Clone)]
pub struct VersionInner {
id: VersionId,
tree_type: TreeType,
levels: Vec<Level>,
#[doc(hidden)]
pub blob_files: Arc<BlobFileList>,
gc_stats: Arc<FragmentationMap>,
retention_floor: crate::SeqNo,
dicts: Arc<[crate::file::DictId]>,
repair_report: Option<Arc<[u8]>>,
read_mix: Arc<ReadMix>,
}
#[derive(Default, Debug)]
pub struct ReadMix {
stripes: [ReadMixStripe; READ_MIX_STRIPES],
}
const READ_MIX_STRIPES: usize = 16;
const _: () = assert!(READ_MIX_STRIPES == 1 << 4);
#[derive(Default, Debug)]
#[repr(align(128))]
struct ReadMixStripe {
points: portable_atomic::AtomicU64,
scans: portable_atomic::AtomicU64,
}
impl ReadMix {
fn stripe(&self) -> &ReadMixStripe {
let marker = 0u8;
let at = core::ptr::from_ref(&marker) as usize as u64;
let mixed = usize::try_from(at.wrapping_mul(0x9E37_79B9_7F4A_7C15) >> 60).unwrap_or(0);
#[expect(
clippy::indexing_slicing,
reason = "the top four bits of the product index sixteen stripes"
)]
&self.stripes[mixed]
}
pub(crate) fn note_points(&self, keys: u64) {
self.stripe()
.points
.fetch_add(keys, portable_atomic::Ordering::Relaxed);
}
pub(crate) fn note_scan(&self) {
self.stripe()
.scans
.fetch_add(1, portable_atomic::Ordering::Relaxed);
}
pub(crate) fn counts(&self) -> (u64, u64) {
self.stripes.iter().fold((0, 0), |(points, scans), stripe| {
(
points + stripe.points.load(portable_atomic::Ordering::Relaxed),
scans + stripe.scans.load(portable_atomic::Ordering::Relaxed),
)
})
}
}
#[derive(Clone)]
pub struct Version {
inner: Arc<VersionInner>,
}
impl core::ops::Deref for Version {
type Target = VersionInner;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl Version {
pub fn tree_type(&self) -> TreeType {
self.tree_type
}
pub fn id(&self) -> VersionId {
self.id
}
pub fn gc_stats(&self) -> &FragmentationMap {
&self.gc_stats
}
pub(crate) fn read_mix(&self) -> &ReadMix {
&self.read_mix
}
#[must_use]
pub fn retention_floor(&self) -> crate::SeqNo {
self.retention_floor
}
#[must_use]
pub fn with_retention_floor(&self, floor: crate::SeqNo) -> Self {
if floor <= self.retention_floor {
return self.clone();
}
Self {
inner: Arc::new(VersionInner {
id: self.id,
tree_type: self.tree_type,
levels: self.levels.clone(),
blob_files: self.blob_files.clone(),
gc_stats: self.gc_stats.clone(),
retention_floor: floor,
dicts: self.dicts.clone(),
repair_report: self.repair_report.clone(),
read_mix: Arc::clone(&self.read_mix),
}),
}
}
#[must_use]
pub(crate) fn with_lowered_retention_floor(&self, floor: crate::SeqNo) -> Self {
debug_assert!(
floor <= self.retention_floor,
"a lowering never raises the floor ({floor} > {})",
self.retention_floor
);
Self {
inner: Arc::new(VersionInner {
id: self.id + 1,
retention_floor: floor,
..VersionInner::clone(&self.inner)
}),
}
}
#[must_use]
pub fn dicts(&self) -> &[crate::file::DictId] {
&self.dicts
}
#[must_use]
pub fn referenced_dicts(&self) -> Vec<crate::file::DictId> {
fn dict_of(compression: crate::CompressionType) -> Option<crate::file::DictId> {
match compression {
#[cfg(zstd_any)]
crate::CompressionType::ZstdDict { dict_id, .. } => Some(dict_id),
_ => None,
}
}
let mut ids: Vec<crate::file::DictId> = self
.iter_tables()
.filter_map(|table| dict_of(table.metadata.data_block_compression))
.chain(
self.blob_files
.iter()
.filter_map(|bf| dict_of(bf.compression())),
)
.collect();
ids.sort_unstable();
ids.dedup();
ids
}
#[must_use]
pub fn with_dict(&self, id: crate::file::DictId) -> Self {
if self.dicts.contains(&id) {
return self.clone();
}
let mut dicts = self.dicts.to_vec();
let at = dicts.partition_point(|held| *held < id);
dicts.insert(at, id);
self.with_id_and_dicts(self.id + 1, dicts)
}
#[must_use]
pub fn without_dicts(&self, ids: &[crate::file::DictId]) -> Self {
if !self.dicts.iter().any(|held| ids.contains(held)) {
return self.clone();
}
let dicts = self
.dicts
.iter()
.copied()
.filter(|held| !ids.contains(held))
.collect::<Vec<_>>();
self.with_id_and_dicts(self.id + 1, dicts)
}
#[must_use]
pub(crate) fn cleared(&self, still_written: &[crate::file::DictId]) -> Self {
let dicts = self
.dicts
.iter()
.copied()
.filter(|id| still_written.contains(id))
.collect();
Self::new(self.id + 1, self.tree_type)
.with_dicts(dicts)
.with_repair_report(self.repair_report.clone())
}
#[must_use]
pub(crate) fn repair_report(&self) -> Option<&[u8]> {
self.repair_report.as_deref()
}
#[must_use]
pub(crate) fn with_repair_report(&self, report: Option<Arc<[u8]>>) -> Self {
Self {
inner: Arc::new(VersionInner {
repair_report: report,
..VersionInner::clone(&self.inner)
}),
}
}
#[cfg(feature = "std")]
#[must_use]
pub(crate) fn without_repair_report(&self) -> Self {
Self {
inner: Arc::new(VersionInner {
id: self.id + 1,
repair_report: None,
..VersionInner::clone(&self.inner)
}),
}
}
#[must_use]
pub(crate) fn with_dicts(&self, dicts: Vec<crate::file::DictId>) -> Self {
self.with_id_and_dicts(self.id, dicts)
}
#[must_use]
pub(crate) fn reinstalled(&self) -> Self {
let next = if self.is_renumbered_on_open() {
0
} else {
self.id + 1
};
self.with_id_and_dicts(next, self.dicts.to_vec())
}
#[must_use]
pub(crate) fn is_renumbered_on_open(&self) -> bool {
self.id >= RENUMBER_FROM
}
fn with_id_and_dicts(&self, id: VersionId, dicts: Vec<crate::file::DictId>) -> Self {
Self {
inner: Arc::new(VersionInner {
id,
tree_type: self.tree_type,
levels: self.levels.clone(),
blob_files: self.blob_files.clone(),
gc_stats: self.gc_stats.clone(),
retention_floor: self.retention_floor,
dicts: Arc::from(dicts),
repair_report: self.repair_report.clone(),
read_mix: Arc::clone(&self.read_mix),
}),
}
}
pub(crate) fn set_retention_floor(&mut self, floor: crate::SeqNo) {
if floor <= self.retention_floor {
return;
}
if let Some(inner) = Arc::get_mut(&mut self.inner) {
inner.retention_floor = floor;
} else {
*self = self.with_retention_floor(floor);
}
}
pub fn l0(&self) -> &Level {
#[expect(clippy::expect_used)]
self.levels.first().expect("L0 should exist")
}
#[must_use]
pub fn level_is_busy(&self, idx: usize, hidden_set: &HiddenSet) -> bool {
self.level(idx).is_some_and(|level| {
level
.iter()
.flat_map(|run| run.iter())
.any(|table| hidden_set.is_hidden(table.id()))
})
}
pub fn new(id: VersionId, tree_type: TreeType) -> Self {
let levels = (0..DEFAULT_LEVEL_COUNT).map(|_| Level::empty()).collect();
Self {
inner: Arc::new(VersionInner {
id,
tree_type,
levels,
blob_files: Arc::default(),
gc_stats: Arc::default(),
retention_floor: 0,
dicts: Arc::from([]),
repair_report: None,
read_mix: Arc::default(),
}),
}
}
pub(crate) fn from_recovery(
recovery: Recovery,
tables: &[Table],
blob_files: &[BlobFile],
comparator: &dyn crate::comparator::UserComparator,
) -> crate::Result<Self> {
let version_levels = recovery
.table_ids
.iter()
.enumerate()
.map(|(level_idx, level)| {
let level_runs = level
.iter()
.map(|run| {
let run_tables = run
.iter()
.map(|table| {
let opened = tables
.iter()
.find(|x| x.id() == table.id)
.cloned()
.ok_or(crate::Error::Unrecoverable)?;
Ok(match recovery.restrictions.get(&table.id) {
Some(bound) => opened.with_restriction(bound.clone()),
None => opened,
})
})
.collect::<crate::Result<Vec<_>>>()?;
Ok(Arc::new(
#[expect(
clippy::expect_used,
reason = "empty runs should not exist, so there should not be any empty persisted runs"
)]
Run::new(run_tables).expect("persisted runs should not be empty"),
))
})
.collect::<crate::Result<Vec<_>>>()?;
if level_idx == 0 {
let persisted: Vec<&Run<Table>> =
level_runs.iter().map(Arc::as_ref).collect();
if !optimize::in_recency_order(&persisted, comparator) {
let tables = level_runs.iter().flat_map(|run| run.iter().cloned());
let level_runs = order_by_age(tables, comparator)
.into_iter()
.map(Arc::new)
.collect();
return Ok(Level::from_runs(level_runs));
}
}
Ok(Level::from_runs(level_runs))
})
.collect::<crate::Result<Vec<_>>>()?;
let recovered = Self::from_levels(
recovery.curr_version_id,
recovery.tree_type,
version_levels,
BlobFileList::new(blob_files.iter().cloned().map(|bf| (bf.id(), bf)).collect()),
recovery.gc_stats,
)
.with_retention_floor(recovery.retention_floor);
let mut dicts = recovery.dicts;
dicts.extend(recovered.referenced_dicts());
dicts.sort_unstable();
dicts.dedup();
Ok(recovered
.with_dicts(dicts)
.with_repair_report(recovery.repair_report.map(Arc::from)))
}
pub fn from_levels(
id: VersionId,
tree_type: TreeType,
levels: Vec<Level>,
blob_files: BlobFileList,
gc_stats: FragmentationMap,
) -> Self {
Self {
inner: Arc::new(VersionInner {
id,
tree_type,
levels,
blob_files: Arc::new(blob_files),
gc_stats: Arc::new(gc_stats),
retention_floor: 0,
dicts: Arc::from([]),
repair_report: None,
read_mix: Arc::default(),
}),
}
}
pub fn level_count(&self) -> usize {
self.levels.len()
}
pub fn iter_levels(&self) -> impl Iterator<Item = &Level> {
self.levels.iter()
}
pub fn table_count(&self) -> usize {
self.iter_levels().map(|x| x.table_count()).sum()
}
pub fn blob_file_count(&self) -> usize {
self.blob_files.len()
}
pub fn iter_tables(&self) -> impl Iterator<Item = &Table> {
self.levels
.iter()
.flat_map(|x| x.iter())
.flat_map(|x| x.iter())
}
pub(crate) fn get_table(&self, id: TableId) -> Option<&Table> {
self.iter_tables().find(|x| x.metadata.id == id)
}
pub fn level(&self, n: usize) -> Option<&Level> {
self.levels.get(n)
}
pub fn with_new_l0_run(
&self,
run: &[Table],
blob_files: Option<&[BlobFile]>,
diff: Option<FragmentationMap>,
ctx: &TransformContext<'_>,
) -> Self {
let comparator = ctx.comparator;
let id = self.id + 1;
let mut levels = vec![];
levels.push({
#[expect(clippy::expect_used, reason = "L0 always exists")]
let l0 = self.levels.first().expect("L0 should always exist");
let prev_runs = l0
.runs
.iter()
.map(|run| {
let run: Run<_> = run.deref().clone();
run
})
.collect::<Vec<_>>();
let mut runs = Vec::with_capacity(prev_runs.len() + run.len());
runs.extend(run.iter().cloned().map(|table| {
let Some(run) = Run::new(vec![table]) else {
unreachable!("single-table run should never be empty");
};
run
}));
runs.extend(prev_runs);
let runs = place_runs(0, runs, comparator);
Level::from_runs(runs.into_iter().map(Arc::new).collect())
});
levels.extend(self.levels.iter().skip(1).cloned());
let value_log = if let Some(blob_files) = blob_files {
let mut copy = self.blob_files.deref().clone();
copy.extend(blob_files.iter().cloned().map(|bf| (bf.id(), bf)));
copy.into()
} else {
self.blob_files.clone()
};
let gc_stats = if let Some(diff) = diff {
let mut copy = self.gc_stats.deref().clone();
diff.merge_into(&mut copy);
copy.prune(&value_log);
Arc::new(copy)
} else {
self.gc_stats.clone()
};
Self {
inner: Arc::new(VersionInner {
id,
tree_type: self.tree_type,
levels,
blob_files: value_log,
gc_stats,
retention_floor: self.retention_floor,
dicts: self.dicts.clone(),
repair_report: self.repair_report.clone(),
read_mix: Arc::clone(&self.read_mix),
}),
}
}
pub fn with_dropped(
&self,
ids: &[TableId],
dropped_blob_files: &mut Vec<BlobFile>,
memtable_refs: &dyn Fn(BlobFileId) -> bool,
ctx: &TransformContext<'_>,
) -> crate::Result<Self> {
let comparator = ctx.comparator;
let id = self.id + 1;
let mut levels = vec![];
let mut dropped_tables: Vec<Table> = vec![];
for (level_idx, level) in self.levels.iter().enumerate() {
if !level_holds_any(level, |id| ids.contains(&id)) {
levels.push(level.clone());
continue;
}
let runs = level
.runs
.iter()
.map(|run| {
let mut run: Run<_> = run.deref().clone();
let removed_tables = run
.inner_mut()
.extract_if(.., |x| ids.contains(&x.metadata.id));
dropped_tables.extend(removed_tables);
run
})
.filter(|x| !x.is_empty())
.collect::<Vec<_>>();
let runs = place_runs(level_idx, runs, comparator);
levels.push(Level::from_runs(runs.into_iter().map(Arc::new).collect()));
}
let gc_stats = if dropped_tables.is_empty() {
self.gc_stats.clone()
} else {
let mut copy = self.gc_stats.deref().clone();
for table in &dropped_tables {
let linked_blob_files = table.list_blob_file_references()?.unwrap_or_default();
for blob_file in linked_blob_files {
copy.entry(blob_file.blob_file_id)
.and_modify(|counter| {
counter.bytes += blob_file.bytes;
counter.len += blob_file.len;
})
.or_insert_with(|| {
FragmentationEntry::new(
blob_file.len,
blob_file.bytes,
blob_file.on_disk_bytes,
)
});
}
}
Arc::new(copy)
};
let value_log = if dropped_tables.is_empty() {
self.blob_files.clone()
} else {
let candidates: crate::HashSet<BlobFileId> = self
.blob_files
.iter()
.filter(|file| file.is_dead(&gc_stats))
.map(BlobFile::id)
.collect();
let mut linked = crate::HashSet::default();
if !candidates.is_empty() {
for table in levels
.iter()
.flat_map(|level| level.iter())
.flat_map(|run| run.iter())
{
for link in table.blob_links()? {
if candidates.contains(&link.blob_file_id) {
linked.insert(link.blob_file_id);
}
}
}
}
let mut copy = self.blob_files.deref().clone();
dropped_blob_files
.extend(copy.prune_dead(&gc_stats, |id| linked.contains(&id) || memtable_refs(id)));
Arc::new(copy)
};
Ok(Self {
inner: Arc::new(VersionInner {
id,
tree_type: self.tree_type,
levels,
blob_files: value_log,
gc_stats,
retention_floor: self.retention_floor,
dicts: self.dicts.clone(),
repair_report: self.repair_report.clone(),
read_mix: Arc::clone(&self.read_mix),
}),
})
}
#[expect(
clippy::too_many_arguments,
reason = "merge requires blob/GC params alongside context; further bundling planned"
)]
pub fn with_merge(
&self,
old_ids: &[TableId],
new_tables: &[Table],
dest_level: usize,
diff: Option<FragmentationMap>,
new_blob_files: Vec<BlobFile>,
blob_files_to_drop: &HashSet<BlobFileId>,
ctx: &TransformContext<'_>,
) -> Self {
let comparator = ctx.comparator;
let id = self.id + 1;
let mut levels = vec![];
for (level_idx, level) in self.levels.iter().enumerate() {
if level_idx != dest_level && !level_holds_any(level, |id| old_ids.contains(&id)) {
levels.push(level.clone());
continue;
}
let mut runs = level
.runs
.iter()
.map(|run| {
let mut run: Run<_> = run.deref().clone();
run.retain(|x| !old_ids.contains(&x.metadata.id));
run
})
.filter(|x| !x.is_empty())
.collect::<Vec<_>>();
if level_idx == dest_level {
let new_runs = runs_of_new_tables(new_tables, comparator);
if dest_level == 0 {
runs.extend(new_runs);
} else {
runs.splice(0..0, new_runs);
}
}
let runs = place_runs(level_idx, runs, comparator);
levels.push(Level::from_runs(runs.into_iter().map(Arc::new).collect()));
}
let has_diff = diff.is_some();
let value_log = if has_diff || !new_blob_files.is_empty() || !blob_files_to_drop.is_empty()
{
let mut copy = self.blob_files.deref().clone();
for blob_file in new_blob_files {
copy.insert(blob_file.id(), blob_file);
}
for &id in blob_files_to_drop {
copy.remove(id);
}
Arc::new(copy)
} else {
self.blob_files.clone()
};
let gc_stats = if has_diff || !blob_files_to_drop.is_empty() {
let mut copy = self.gc_stats.deref().clone();
if let Some(diff) = diff {
diff.merge_into(&mut copy);
}
copy.prune(&value_log);
Arc::new(copy)
} else {
self.gc_stats.clone()
};
Self {
inner: Arc::new(VersionInner {
id,
tree_type: self.tree_type,
levels,
blob_files: value_log,
gc_stats,
retention_floor: self.retention_floor,
dicts: self.dicts.clone(),
repair_report: self.repair_report.clone(),
read_mix: Arc::clone(&self.read_mix),
}),
}
}
pub fn with_moved(
&self,
ids: &[TableId],
dest_level: usize,
ctx: &TransformContext<'_>,
) -> Self {
let comparator = ctx.comparator;
let id = self.id + 1;
let mut moved_runs: Vec<Vec<Table>> = Vec::new();
for run in self.levels.iter().flat_map(|level| level.iter()) {
let mut moved: Vec<Table> = run
.iter()
.filter(|table| ids.contains(&table.id()))
.cloned()
.collect();
if moved.is_empty() {
continue;
}
moved.sort_by(|a, b| comparator.compare(a.key_range().min(), b.key_range().min()));
moved_runs.push(moved);
}
assert_eq!(
moved_runs.iter().map(Vec::len).sum::<usize>(),
ids.len(),
"invalid table IDs"
);
let disjoint = moved_runs.len() <= 1 || {
let mut all: Vec<&Table> = moved_runs.iter().flatten().collect();
all.sort_by(|a, b| comparator.compare(a.key_range().min(), b.key_range().min()));
all.iter().zip(all.iter().skip(1)).all(|(a, b)| {
comparator
.compare(a.key_range().max(), b.key_range().min())
.is_lt()
})
};
if disjoint && moved_runs.len() > 1 {
let mut all: Vec<Table> = core::mem::take(&mut moved_runs)
.into_iter()
.flatten()
.collect();
all.sort_by(|a, b| comparator.compare(a.key_range().min(), b.key_range().min()));
moved_runs.push(all);
}
let mut levels = vec![];
for (level_idx, level) in self.levels.iter().enumerate() {
if level_idx != dest_level && !level_holds_any(level, |id| ids.contains(&id)) {
levels.push(level.clone());
continue;
}
let mut runs = level
.runs
.iter()
.map(|run| {
let mut run: Run<_> = run.deref().clone();
run.retain(|x| !ids.contains(&x.metadata.id));
run
})
.filter(|x| !x.is_empty())
.collect::<Vec<_>>();
if level_idx == dest_level {
runs.splice(
0..0,
moved_runs
.iter()
.filter_map(|tables| Run::new(tables.clone())),
);
}
let runs = place_runs(level_idx, runs, comparator);
levels.push(Level::from_runs(runs.into_iter().map(Arc::new).collect()));
}
Self {
inner: Arc::new(VersionInner {
id,
tree_type: self.tree_type,
levels,
blob_files: self.blob_files.clone(),
gc_stats: self.gc_stats.clone(),
retention_floor: self.retention_floor,
dicts: self.dicts.clone(),
repair_report: self.repair_report.clone(),
read_mix: Arc::clone(&self.read_mix),
}),
}
}
#[must_use]
pub(crate) fn with_refreshed_table_checksum(
&self,
table_id: TableId,
checksum: crate::checksum::Checksum,
) -> Option<Self> {
if !self.iter_tables().any(|t| t.id() == table_id) {
return None;
}
let id = self.id + 1;
let mut levels = vec![];
for level in &self.levels {
let runs = level
.runs
.iter()
.map(|run| {
if run.iter().any(|t| t.id() == table_id) {
let mut run: Run<_> = run.deref().clone();
for t in run.inner_mut().iter_mut() {
if t.id() == table_id {
*t = t.with_refreshed_checksum(checksum);
}
}
Arc::new(run)
} else {
run.clone()
}
})
.collect::<Vec<_>>();
levels.push(Level::from_runs(runs));
}
Some(Self {
inner: Arc::new(VersionInner {
id,
tree_type: self.tree_type,
levels,
blob_files: self.blob_files.clone(),
gc_stats: self.gc_stats.clone(),
retention_floor: self.retention_floor,
dicts: self.dicts.clone(),
repair_report: self.repair_report.clone(),
read_mix: Arc::clone(&self.read_mix),
}),
})
}
#[expect(
clippy::too_many_arguments,
reason = "a slice install carries the SST swaps/removes/outputs plus the \
KV-separation blob delta (new files + GC diff); bundling would \
just move the argument list"
)]
pub fn with_tight_slice(
&self,
restricted: &[(TableId, Table)],
removed_ids: &[TableId],
outputs: &[Table],
new_blob_files: Vec<BlobFile>,
gc_diff: Option<FragmentationMap>,
dest_level: usize,
ctx: &TransformContext<'_>,
) -> Self {
let comparator = ctx.comparator;
let id = self.id + 1;
let mut levels = vec![];
for (level_idx, level) in self.levels.iter().enumerate() {
if level_idx != dest_level
&& !level_holds_any(level, |id| {
removed_ids.contains(&id) || restricted.iter().any(|(rid, _)| *rid == id)
})
{
levels.push(level.clone());
continue;
}
let mut runs = level
.runs
.iter()
.map(|run| {
let mut run: Run<_> = run.deref().clone();
run.retain(|t| !removed_ids.contains(&t.id()));
for t in run.inner_mut().iter_mut() {
if let Some((_, view)) = restricted.iter().find(|(rid, _)| *rid == t.id()) {
*t = view.clone();
}
}
run
})
.filter(|run| !run.is_empty())
.collect::<Vec<_>>();
if level_idx == dest_level {
let new_runs = runs_of_new_tables(outputs, comparator);
if dest_level == 0 {
runs.extend(new_runs);
} else {
runs.splice(0..0, new_runs);
}
}
let runs = place_runs(level_idx, runs, comparator);
levels.push(Level::from_runs(runs.into_iter().map(Arc::new).collect()));
}
let value_log = if gc_diff.is_some() || !new_blob_files.is_empty() {
let mut copy = self.blob_files.deref().clone();
for blob_file in new_blob_files {
copy.insert(blob_file.id(), blob_file);
}
Arc::new(copy)
} else {
self.blob_files.clone()
};
let gc_stats = if let Some(diff) = gc_diff {
let mut copy = self.gc_stats.deref().clone();
diff.merge_into(&mut copy);
Arc::new(copy)
} else {
self.gc_stats.clone()
};
Self {
inner: Arc::new(VersionInner {
id,
tree_type: self.tree_type,
levels,
blob_files: value_log,
gc_stats,
retention_floor: self.retention_floor,
dicts: self.dicts.clone(),
repair_report: self.repair_report.clone(),
read_mix: Arc::clone(&self.read_mix),
}),
}
}
}
impl Version {
pub(crate) fn encode_into(
&self,
writer: &mut crate::manifest_blocks::writer::ManifestArchiveWriter,
comparator_name: &str,
) -> Result<(), crate::Error> {
use crate::FormatVersion;
#[cfg(not(feature = "std"))]
use crate::io::Write;
use crate::io::{LittleEndian, WriteBytesExt};
#[cfg(feature = "std")]
use std::io::Write;
writer.start("format_version")?;
writer.write_u8(FormatVersion::V6.into())?;
writer.start("version_id")?;
writer.write_u64::<LittleEndian>(self.id())?;
writer.start("crate_version")?;
writer.write_all(env!("CARGO_PKG_VERSION").as_bytes())?;
writer.start("tree_type")?;
writer.write_u8(self.tree_type.into())?;
writer.start("level_count")?;
writer.write_u8(
u8::try_from(self.level_count())
.map_err(|_| crate::Error::InvalidHeader("level_count exceeds u8"))?,
)?;
writer.start("filter_hash_type")?;
writer.write_u8(u8::from(ChecksumType::Xxh3))?;
writer.start("comparator_name")?;
writer.write_all(comparator_name.as_bytes())?;
writer.start("tables")?;
let mut framing_scratch: Vec<u8> = Vec::with_capacity(64);
writer.write_u8(
u8::try_from(self.level_count())
.map_err(|_| crate::Error::InvalidHeader("level_count exceeds u8"))?,
)?;
for (level_idx, level) in self.iter_levels().enumerate() {
let level_no = u8::try_from(level_idx)
.map_err(|_| crate::Error::InvalidHeader("tables: level index exceeds u8"))?;
for (run_idx, run) in level.iter().enumerate() {
let place = recovery::TablePlace {
level: level_no,
run: u32::try_from(run_idx).map_err(|_| {
crate::Error::InvalidHeader("tables: run index exceeds u32")
})?,
};
for table in run.iter() {
framing::write_framed_record(writer, &mut framing_scratch, |payload| {
recovery::encode_table_entry_payload(
payload,
place,
table.id(),
table.checksum(),
table.global_seqno(),
table.l0_recency(),
)
})?;
}
}
}
writer.start("blob_files")?;
#[expect(
clippy::cast_possible_truncation,
reason = "there are always less than 4 billion blob files"
)]
writer.write_u32::<LittleEndian>(self.blob_files.len() as u32)?;
for file in self.blob_files.iter() {
framing::write_framed_record(writer, &mut framing_scratch, |payload| {
payload.write_u64::<LittleEndian>(file.id())?;
payload.write_u8(0)?; payload.write_u128::<LittleEndian>(file.0.checksum.into_u128())?;
Ok(())
})?;
}
writer.start("blob_gc_stats")?;
self.gc_stats.encode_into(writer)?;
writer.start("restrictions")?;
let restricted: Vec<(TableId, crate::UserKey)> = self
.iter_tables()
.filter_map(|t| t.restrict_lower_bound().map(|b| (t.id(), b.clone())))
.collect();
writer.write_u32::<LittleEndian>(
u32::try_from(restricted.len()).map_err(|_| crate::Error::Unrecoverable)?,
)?;
for (id, key) in &restricted {
writer.write_u64::<LittleEndian>(*id)?;
writer.write_u32::<LittleEndian>(
u32::try_from(key.len()).map_err(|_| crate::Error::Unrecoverable)?,
)?;
writer.write_all(key)?;
}
writer.start("blob_restrictions")?;
let blob_restricted: Vec<(crate::vlog::BlobFileId, u64)> = self
.blob_files
.iter()
.filter(|bf| bf.live_data_start() > 0)
.map(|bf| (bf.id(), bf.live_data_start()))
.collect();
writer.write_u32::<LittleEndian>(
u32::try_from(blob_restricted.len()).map_err(|_| crate::Error::Unrecoverable)?,
)?;
for (id, frontier) in &blob_restricted {
writer.write_u64::<LittleEndian>(*id)?;
writer.write_u64::<LittleEndian>(*frontier)?;
}
writer.start("retention_floor")?;
writer.write_u64::<LittleEndian>(self.retention_floor)?;
if !self.dicts.is_empty() {
writer.start("dicts")?;
writer.write_u32::<LittleEndian>(
u32::try_from(self.dicts.len()).map_err(|_| crate::Error::Unrecoverable)?,
)?;
for id in self.dicts.iter() {
writer.write_u32::<LittleEndian>(*id)?;
}
}
if let Some(report) = &self.repair_report {
writer.start("repair_report")?;
writer.write_all(report)?;
}
Ok(())
}
}