use std::collections::BTreeMap;
#[cfg(test)]
use std::collections::btree_map::Entry;
use std::iter;
use byteorder::LittleEndian;
#[cfg(test)]
use crate::common::bitpacking::make_bitmask;
use crate::common::bitvec::{BitSlice, BitVec};
use crate::common::types::PointOffsetType;
use itertools::Itertools;
#[cfg(test)]
use rand::RngExt;
use rand::distr::Distribution;
#[cfg(test)]
use rand::rngs::StdRng;
#[cfg(test)]
use rand::seq::SliceRandom as _;
use uuid::Uuid;
use crate::segment::types::PointIdType;
pub type FileEndianess = LittleEndian;
#[derive(Clone, PartialEq, Default, Debug)]
pub struct PointMappings {
deleted: BitVec,
internal_to_external: Vec<PointIdType>,
external_to_internal_num: BTreeMap<u64, PointOffsetType>,
external_to_internal_uuid: BTreeMap<Uuid, PointOffsetType>,
deferred_internal_id: Option<PointOffsetType>,
deferred_deleted_count: usize,
}
impl PointMappings {
pub fn new(
deleted: BitVec,
internal_to_external: Vec<PointIdType>,
external_to_internal_num: BTreeMap<u64, PointOffsetType>,
external_to_internal_uuid: BTreeMap<Uuid, PointOffsetType>,
deferred_internal_id: Option<PointOffsetType>,
) -> Self {
let deferred_deleted_count = deferred_internal_id
.map(|deferred_from| {
let total = deleted.len();
if total <= deferred_from as usize {
0
} else {
deleted[deferred_from as usize..total].count_ones()
}
})
.unwrap_or(0);
Self {
deleted,
internal_to_external,
external_to_internal_num,
external_to_internal_uuid,
deferred_internal_id,
deferred_deleted_count,
}
}
pub fn deconstruct(
self,
) -> (
BitVec,
Vec<PointIdType>,
BTreeMap<u64, PointOffsetType>,
BTreeMap<Uuid, PointOffsetType>,
) {
(
self.deleted,
self.internal_to_external,
self.external_to_internal_num,
self.external_to_internal_uuid,
)
}
pub(crate) fn available_point_count(&self) -> usize {
self.external_to_internal_num.len() + self.external_to_internal_uuid.len()
}
pub(crate) fn deleted(&self) -> &BitSlice {
&self.deleted
}
pub(crate) fn internal_id(&self, external_id: &PointIdType) -> Option<PointOffsetType> {
match external_id {
PointIdType::NumId(num) => self.external_to_internal_num.get(num).copied(),
PointIdType::Uuid(uuid) => self.external_to_internal_uuid.get(uuid).copied(),
}
}
pub(crate) fn external_id(&self, internal_id: PointOffsetType) -> Option<PointIdType> {
if *self.deleted.get(internal_id as usize)? {
return None;
}
self.internal_to_external
.get(internal_id as usize)
.map(Into::into)
}
pub(crate) fn drop(&mut self, external_id: PointIdType) -> Option<PointOffsetType> {
let internal_id = match external_id {
PointIdType::NumId(num) => self.external_to_internal_num.remove(&num),
PointIdType::Uuid(uuid) => self.external_to_internal_uuid.remove(&uuid),
};
if let Some(internal_id) = internal_id {
self.internal_to_external[internal_id as usize] = PointIdType::NumId(u64::MAX);
}
if let Some(internal_id) = &internal_id {
let was_already_deleted = *self
.deleted
.get(*internal_id as usize)
.as_deref()
.unwrap_or(&true);
self.deleted.set(*internal_id as usize, true);
if !was_already_deleted
&& self
.deferred_internal_id
.is_some_and(|deferred_from| *internal_id >= deferred_from)
{
self.deferred_deleted_count += 1;
}
}
internal_id
}
pub(crate) fn iter_random(
&self,
) -> Box<dyn Iterator<Item = (PointIdType, PointOffsetType)> + '_> {
let rng = rand::rng();
let max_internal = self.internal_to_external.len();
if max_internal == 0 {
return Box::new(iter::empty());
}
let uniform = rand::distr::Uniform::new(0, max_internal)
.expect("above check guarantees max_internal > 0");
let iter = Distribution::sample_iter(uniform, rng)
.unique()
.take(max_internal)
.filter_map(move |i| {
if self.deleted[i] {
None
} else {
Some((self.internal_to_external[i], i as PointOffsetType))
}
});
Box::new(iter)
}
pub(crate) fn iter_from(
&self,
external_id: Option<PointIdType>,
) -> Box<dyn Iterator<Item = (PointIdType, PointOffsetType)> + '_> {
let full_num_iter = || {
self.external_to_internal_num
.iter()
.map(|(k, v)| (PointIdType::NumId(*k), *v))
};
let offset_num_iter = |offset: u64| {
self.external_to_internal_num
.range(offset..)
.map(|(k, v)| (PointIdType::NumId(*k), *v))
};
let full_uuid_iter = || {
self.external_to_internal_uuid
.iter()
.map(|(k, v)| (PointIdType::Uuid(*k), *v))
};
let offset_uuid_iter = |offset: Uuid| {
self.external_to_internal_uuid
.range(offset..)
.map(|(k, v)| (PointIdType::Uuid(*k), *v))
};
match external_id {
None => {
let iter_num = full_num_iter();
let iter_uuid = full_uuid_iter();
Box::new(iter_num.chain(iter_uuid))
}
Some(offset) => match offset {
PointIdType::NumId(idx) => {
let iter_num = offset_num_iter(idx);
let iter_uuid = full_uuid_iter();
Box::new(iter_num.chain(iter_uuid))
}
PointIdType::Uuid(uuid) => {
Box::new(offset_uuid_iter(uuid))
}
},
}
}
pub(crate) fn iter_external(&self) -> Box<dyn Iterator<Item = PointIdType> + '_> {
let iter_num = self
.external_to_internal_num
.keys()
.map(|i| PointIdType::NumId(*i));
let iter_uuid = self
.external_to_internal_uuid
.keys()
.map(|i| PointIdType::Uuid(*i));
Box::new(iter_num.chain(iter_uuid))
}
pub(crate) fn iter_internal(&self) -> Box<dyn Iterator<Item = PointOffsetType> + '_> {
Box::new(
(0..self.internal_to_external.len() as PointOffsetType)
.filter(move |i| !self.deleted[*i as usize]),
)
}
#[cfg(test)]
pub(crate) fn iter_internal_raw(
&self,
) -> impl Iterator<Item = (PointOffsetType, PointIdType)> + '_ {
self.internal_to_external
.iter()
.enumerate()
.map(|(offset, point_id)| (offset as _, *point_id))
}
pub(crate) fn is_deleted_point(&self, key: PointOffsetType) -> bool {
let key = key as usize;
if key >= self.deleted.len() {
return true;
}
self.deleted[key]
}
pub(crate) fn set_link(
&mut self,
external_id: PointIdType,
internal_id: PointOffsetType,
) -> Option<PointOffsetType> {
let old_internal_id = match external_id {
PointIdType::NumId(idx) => self.external_to_internal_num.insert(idx, internal_id),
PointIdType::Uuid(uuid) => self.external_to_internal_uuid.insert(uuid, internal_id),
};
let internal_id = internal_id as usize;
if internal_id >= self.internal_to_external.len() {
self.internal_to_external
.resize(internal_id + 1, PointIdType::NumId(u64::MAX));
}
if internal_id >= self.deleted.len() {
self.deleted.resize(internal_id + 1, true);
}
if let Some(old_internal_id) = &old_internal_id {
let old_internal_id = *old_internal_id as usize;
if old_internal_id != internal_id {
self.deleted.set(old_internal_id, true);
}
}
self.internal_to_external[internal_id] = external_id;
self.deleted.set(internal_id, false);
old_internal_id
}
pub(crate) fn total_point_count(&self) -> usize {
self.internal_to_external.len()
}
pub(crate) fn deferred_internal_id(&self) -> Option<PointOffsetType> {
self.deferred_internal_id
}
pub(crate) fn deferred_deleted_count(&self) -> usize {
self.deferred_deleted_count
}
#[cfg(test)]
pub fn random(rand: &mut StdRng, total_size: u32) -> Self {
Self::random_with_params(rand, total_size, 128)
}
#[cfg(test)]
pub fn random_with_params(rand: &mut StdRng, total_size: u32, bits_in_id: u8) -> Self {
let mask: u128 = make_bitmask(bits_in_id);
let mask_u64: u64 = mask as u64;
const UUID_LIKELYNESS: f64 = 0.5;
let mut external_to_internal_num = BTreeMap::new();
let mut external_to_internal_uuid = BTreeMap::new();
let mut internal_ids = (0..total_size).collect_vec();
internal_ids.shuffle(rand);
let mut deleted = BitVec::repeat(true, total_size as usize);
for id in &internal_ids {
deleted.set(*id as usize, false);
}
let internal_to_external = (0..total_size)
.map(|pos| {
loop {
if rand.random_bool(UUID_LIKELYNESS) {
let uuid = Uuid::from_u128(rand.random_range(0..=mask));
if let Entry::Vacant(e) = external_to_internal_uuid.entry(uuid) {
e.insert(pos);
return PointIdType::Uuid(uuid);
}
} else {
let num = rand.random_range(0..=mask_u64);
if let Entry::Vacant(e) = external_to_internal_num.entry(num) {
e.insert(pos);
return PointIdType::NumId(num);
}
}
}
})
.collect();
Self {
deleted,
internal_to_external,
external_to_internal_num,
external_to_internal_uuid,
deferred_internal_id: None,
deferred_deleted_count: 0,
}
}
#[cfg(debug_assertions)]
pub fn assert_mappings(&self) {
for (external_id, internal_id) in self.external_to_internal_num.iter() {
debug_assert!(
self.internal_to_external[*internal_id as usize]
== PointIdType::NumId(*external_id),
"Internal id {internal_id} is mapped to external id {}, but should be {}",
self.internal_to_external[*internal_id as usize],
PointIdType::NumId(*external_id),
);
}
}
pub fn ram_usage_bytes(&self) -> usize {
let Self {
deleted,
internal_to_external,
external_to_internal_num,
external_to_internal_uuid,
deferred_internal_id: _,
deferred_deleted_count: _,
} = self;
let deleted_bytes = deleted.capacity().div_ceil(u8::BITS as usize);
let internal_to_external_bytes =
internal_to_external.capacity() * std::mem::size_of::<PointIdType>();
let btree_node_overhead = std::mem::size_of::<usize>() * 3;
let num_entry_size = std::mem::size_of::<u64>()
+ std::mem::size_of::<PointOffsetType>()
+ btree_node_overhead;
let uuid_entry_size = std::mem::size_of::<Uuid>()
+ std::mem::size_of::<PointOffsetType>()
+ btree_node_overhead;
let num_map_bytes = external_to_internal_num.len() * num_entry_size;
let uuid_map_bytes = external_to_internal_uuid.len() * uuid_entry_size;
deleted_bytes + internal_to_external_bytes + num_map_bytes + uuid_map_bytes
}
}