use crate::utils::address::RowAddress;
use crate::{Error, Result};
use roaring::{RoaringBitmap, RoaringTreemap};
use std::collections::HashMap;
#[derive(Clone)]
pub enum RowAddrRemap {
Compact(CompactRowAddrRemap),
Direct(HashMap<u64, Option<u64>>),
}
impl RowAddrRemap {
pub fn compact(groups: impl IntoIterator<Item = GroupInput>) -> Result<Self> {
Ok(Self::Compact(CompactRowAddrRemap::new(groups)?))
}
pub fn direct(map: HashMap<u64, Option<u64>>) -> Self {
Self::Direct(map)
}
pub fn empty() -> Self {
Self::Direct(HashMap::new())
}
#[inline]
pub fn get(&self, addr: u64) -> Option<Option<u64>> {
match self {
Self::Compact(c) => c.get(addr),
Self::Direct(m) => m.get(&addr).copied(),
}
}
pub fn is_empty(&self) -> bool {
match self {
Self::Compact(c) => c.is_empty(),
Self::Direct(m) => m.is_empty(),
}
}
pub fn affected_fragments(&self) -> RoaringBitmap {
match self {
Self::Compact(c) => RoaringBitmap::from_iter(c.frag_to_group.keys().copied()),
Self::Direct(m) => RoaringBitmap::from_iter(m.keys().map(|addr| (addr >> 32) as u32)),
}
}
pub fn fully_deleted_fragments(&self) -> Option<RoaringBitmap> {
match self {
Self::Compact(c) => c.fully_deleted_fragments(),
Self::Direct(m) => {
if m.values().all(|v| v.is_none()) {
Some(RoaringBitmap::from_iter(
m.keys().map(|addr| (addr >> 32) as u32),
))
} else {
None
}
}
}
}
}
pub struct GroupInput {
pub rewritten_old_row_addrs: RoaringTreemap,
pub old_frag_ids: Vec<u32>,
pub new_frags: Vec<(u32, u32)>,
}
#[derive(Clone)]
struct GroupRemap {
frags: HashMap<u32, (RoaringBitmap, u64)>,
new_frag_row_ranges: Vec<(u32, u64, u32)>,
}
impl GroupRemap {
fn new(input: GroupInput) -> Result<Self> {
let mut new_frag_row_ranges = Vec::with_capacity(input.new_frags.len());
let mut rewritten_rows_before = 0u64;
let mut prev_frag_id: Option<u32> = None;
for (frag_id, physical_rows) in input.new_frags {
if physical_rows == 0 {
continue;
}
if let Some(prev) = prev_frag_id
&& frag_id <= prev
{
return Err(Error::invalid_input(format!(
"compaction new fragments must be in ascending id (write) order, but fragment {frag_id} follows {prev}",
)));
}
prev_frag_id = Some(frag_id);
new_frag_row_ranges.push((frag_id, rewritten_rows_before, physical_rows));
rewritten_rows_before += physical_rows as u64;
}
let total_new_rows = rewritten_rows_before;
let mut per_frag: HashMap<u32, RoaringBitmap> = input
.rewritten_old_row_addrs
.bitmaps()
.map(|(frag_id, bitmap)| (frag_id, bitmap.clone()))
.collect();
let mut frags = HashMap::new();
let mut rewritten_rows_before = 0u64;
for &frag_id in &input.old_frag_ids {
if let Some(bitmap) = per_frag.remove(&frag_id) {
let num_rewritten_rows = bitmap.len();
frags.insert(frag_id, (bitmap, rewritten_rows_before));
rewritten_rows_before += num_rewritten_rows;
}
}
if !per_frag.is_empty() {
return Err(Error::invalid_input(format!(
"compaction rewritten old row addresses reference fragments {:?} not in the rewrite group's old fragments {:?}",
per_frag.keys().collect::<Vec<_>>(),
input.old_frag_ids,
)));
}
let total_rewritten_old_rows = input.rewritten_old_row_addrs.len();
if total_new_rows != total_rewritten_old_rows {
return Err(Error::invalid_input(format!(
"compaction rewrote {total_rewritten_old_rows} old rows from fragments {:?} but the new fragments hold {total_new_rows} rows",
input.old_frag_ids,
)));
}
Ok(Self {
frags,
new_frag_row_ranges,
})
}
fn compute_new_addr(&self, rewritten_row_index: u64) -> u64 {
let idx =
match self
.new_frag_row_ranges
.binary_search_by(|(_, rewritten_rows_before, _)| {
rewritten_rows_before.cmp(&rewritten_row_index)
}) {
Ok(i) => i,
Err(i) => i - 1,
};
let (frag_id, rewritten_rows_before, _rows) = self.new_frag_row_ranges[idx];
let offset = (rewritten_row_index - rewritten_rows_before) as u32;
u64::from(RowAddress::new_from_parts(frag_id, offset))
}
#[inline]
fn get(&self, frag: u32, offset: u32) -> Option<u64> {
match self.frags.get(&frag) {
Some((bitmap, rewritten_rows_before)) if bitmap.contains(offset) => {
let rewritten_row_index = rewritten_rows_before + bitmap.rank(offset) - 1;
Some(self.compute_new_addr(rewritten_row_index))
}
_ => None,
}
}
}
#[derive(Clone)]
pub struct CompactRowAddrRemap {
groups: Vec<GroupRemap>,
frag_to_group: HashMap<u32, usize>,
}
impl CompactRowAddrRemap {
fn new(groups: impl IntoIterator<Item = GroupInput>) -> Result<Self> {
let mut frag_to_group = HashMap::new();
let mut group_remaps = Vec::new();
for input in groups {
let gi = group_remaps.len();
for &frag_id in &input.old_frag_ids {
frag_to_group.insert(frag_id, gi);
}
group_remaps.push(GroupRemap::new(input)?);
}
Ok(Self {
groups: group_remaps,
frag_to_group,
})
}
#[inline]
pub fn get(&self, addr: u64) -> Option<Option<u64>> {
let frag = (addr >> 32) as u32;
let gi = *self.frag_to_group.get(&frag)?;
Some(self.groups[gi].get(frag, addr as u32))
}
pub fn is_empty(&self) -> bool {
self.groups.is_empty()
}
fn fully_deleted_fragments(&self) -> Option<RoaringBitmap> {
if self.groups.iter().any(|g| !g.frags.is_empty()) {
return None;
}
Some(RoaringBitmap::from_iter(self.frag_to_group.keys().copied()))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn addr(frag: u32, offset: u32) -> u64 {
u64::from(RowAddress::new_from_parts(frag, offset))
}
#[test]
fn test_compact_lookup() {
let group_a = GroupInput {
rewritten_old_row_addrs: RoaringTreemap::from_iter([
addr(4, 0),
addr(4, 2),
addr(4, 4),
addr(3, 0),
addr(3, 1),
]),
old_frag_ids: vec![4, 3],
new_frags: vec![(10, 2), (11, 0), (12, 3)],
};
let group_b = GroupInput {
rewritten_old_row_addrs: RoaringTreemap::new(),
old_frag_ids: vec![7],
new_frags: vec![],
};
let remap = RowAddrRemap::compact([group_a, group_b]).unwrap();
assert_eq!(remap.get(addr(4, 0)), Some(Some(addr(10, 0))));
assert_eq!(remap.get(addr(4, 2)), Some(Some(addr(10, 1))));
assert_eq!(remap.get(addr(4, 4)), Some(Some(addr(12, 0))));
assert_eq!(remap.get(addr(3, 0)), Some(Some(addr(12, 1))));
assert_eq!(remap.get(addr(3, 1)), Some(Some(addr(12, 2))));
assert_eq!(remap.get(addr(4, 1)), Some(None));
assert_eq!(remap.get(addr(4, 3)), Some(None));
assert_eq!(remap.get(addr(7, 0)), Some(None));
assert_eq!(remap.get(addr(9, 0)), None);
assert!(!remap.is_empty());
}
#[test]
fn test_fragment_sets() {
let dead = RowAddrRemap::compact([GroupInput {
rewritten_old_row_addrs: RoaringTreemap::new(),
old_frag_ids: vec![3, 7],
new_frags: vec![],
}])
.unwrap();
assert_eq!(
dead.fully_deleted_fragments(),
Some(RoaringBitmap::from_iter([3u32, 7u32]))
);
assert_eq!(
dead.affected_fragments(),
RoaringBitmap::from_iter([3u32, 7u32])
);
let alive = RowAddrRemap::compact([GroupInput {
rewritten_old_row_addrs: RoaringTreemap::from_iter([addr(0, 0)]),
old_frag_ids: vec![0, 1],
new_frags: vec![(10, 1)],
}])
.unwrap();
assert!(alive.fully_deleted_fragments().is_none());
assert_eq!(
alive.affected_fragments(),
RoaringBitmap::from_iter([0u32, 1u32])
);
}
#[test]
fn test_compact_rejects_rewritten_addrs_outside_old_frags() {
let input = GroupInput {
rewritten_old_row_addrs: RoaringTreemap::from_iter([addr(0, 0), addr(5, 0)]),
old_frag_ids: vec![0],
new_frags: vec![(10, 2)],
};
assert!(RowAddrRemap::compact([input]).is_err());
}
#[test]
fn test_compact_rejects_new_frags_out_of_write_order() {
let input = GroupInput {
rewritten_old_row_addrs: RoaringTreemap::from_iter([addr(0, 0), addr(0, 1)]),
old_frag_ids: vec![0],
new_frags: vec![(12, 1), (11, 1)],
};
assert!(RowAddrRemap::compact([input]).is_err());
}
#[test]
fn test_direct_and_empty() {
let mut map = HashMap::new();
map.insert(addr(2, 0), Some(addr(9, 9)));
map.insert(addr(5, 1), None);
let remap = RowAddrRemap::direct(map);
assert_eq!(remap.get(addr(2, 0)), Some(Some(addr(9, 9))));
assert_eq!(remap.get(addr(5, 1)), Some(None));
assert_eq!(remap.get(addr(2, 1)), None);
assert_eq!(
remap.affected_fragments(),
RoaringBitmap::from_iter([2u32, 5u32])
);
let empty = RowAddrRemap::empty();
assert!(empty.is_empty());
assert_eq!(empty.get(addr(0, 0)), None);
}
}