use crate::{
index::{
storage::{push_displaced, Cursor as CursorImpl, IndexEntry, Overflow, Values},
Cursor as CursorTrait, Unordered,
},
translator::Translator,
};
use commonware_runtime::{
telemetry::metrics::{Counter, Gauge, MetricsExt as _},
Metrics,
};
use std::collections::{
hash_map::{Entry, OccupiedEntry, VacantEntry},
HashMap,
};
const INITIAL_CAPACITY: usize = 256;
impl<K: Send + Sync, V: Send + Sync> IndexEntry<V> for OccupiedEntry<'_, K, V> {
type Key = K;
fn key(&self) -> &K {
OccupiedEntry::key(self)
}
fn get_mut(&mut self) -> &mut V {
self.get_mut()
}
fn remove(self) {
OccupiedEntry::remove(self);
}
}
pub type Cursor<'a, K, V, S> = CursorImpl<'a, K, V, OccupiedEntry<'a, K, V>, S>;
pub struct Index<T: Translator, V: Send + Sync> {
translator: T,
map: HashMap<T::Key, V, T>,
overflow: Overflow<T::Key, V, T>,
keys: Gauge,
items: Gauge,
pruned: Counter,
}
impl<T: Translator, V: Send + Sync> Index<T, V> {
fn create(keys: &Gauge, items: &Gauge, vacant: VacantEntry<'_, T::Key, V>, v: V) {
keys.inc();
items.inc();
vacant.insert(v);
}
pub fn new(ctx: impl Metrics, translator: T) -> Self {
Self {
translator: translator.clone(),
overflow: HashMap::with_hasher(translator.clone()),
map: HashMap::with_capacity_and_hasher(INITIAL_CAPACITY, translator),
keys: ctx.gauge("keys", "Number of translated keys in the index"),
items: ctx.gauge("items", "Number of items in the index"),
pruned: ctx.counter("pruned", "Number of items pruned"),
}
}
}
impl<T: Translator, V: Send + Sync> super::Factory<T> for Index<T, V> {
fn new(ctx: impl commonware_runtime::Metrics, translator: T) -> Self {
Self::new(ctx, translator)
}
}
impl<T: Translator, V: Send + Sync> Unordered for Index<T, V> {
type Value = V;
type Cursor<'a>
= Cursor<'a, T::Key, V, T>
where
Self: 'a;
fn get<'a>(&'a self, key: &[u8]) -> impl Iterator<Item = &'a V> + 'a
where
V: 'a,
{
let k = self.translator.transform(key);
Values::new(self.map.get(&k), &self.overflow, k)
}
fn get_mut<'a>(&'a mut self, key: &[u8]) -> Option<Self::Cursor<'a>> {
let k = self.translator.transform(key);
match self.map.entry(k) {
Entry::Occupied(entry) => Some(Cursor::<'_, T::Key, V, T>::new(
entry,
&mut self.overflow,
&self.keys,
&self.items,
&self.pruned,
)),
Entry::Vacant(_) => None,
}
}
fn get_mut_or_insert<'a>(&'a mut self, key: &[u8], value: V) -> Option<Self::Cursor<'a>> {
let k = self.translator.transform(key);
match self.map.entry(k) {
Entry::Occupied(entry) => Some(Cursor::<'_, T::Key, V, T>::new(
entry,
&mut self.overflow,
&self.keys,
&self.items,
&self.pruned,
)),
Entry::Vacant(entry) => {
Self::create(&self.keys, &self.items, entry, value);
None
}
}
}
fn insert(&mut self, key: &[u8], v: V) {
let k = self.translator.transform(key);
match self.map.entry(k) {
Entry::Occupied(mut entry) => {
let old = std::mem::replace(entry.get_mut(), v);
push_displaced(&mut self.overflow, k, old);
self.items.inc();
}
Entry::Vacant(entry) => {
Self::create(&self.keys, &self.items, entry, v);
}
}
}
fn insert_and_retain(&mut self, key: &[u8], value: V, should_retain: impl Fn(&V) -> bool) {
let k = self.translator.transform(key);
match self.map.entry(k) {
Entry::Occupied(mut entry) => {
#[allow(clippy::map_entry)]
if !self.overflow.contains_key(&k) {
match (should_retain(entry.get()), should_retain(&value)) {
(true, true) => {
self.overflow.insert(k, vec![value]);
self.items.inc();
}
(false, true) => {
*entry.get_mut() = value;
self.pruned.inc();
}
(false, false) => {
entry.remove();
self.keys.dec();
self.items.dec();
self.pruned.inc();
}
(true, false) => {}
}
return;
}
let mut cursor = Cursor::<'_, T::Key, V, T>::new(
entry,
&mut self.overflow,
&self.keys,
&self.items,
&self.pruned,
);
cursor.retain(&should_retain);
if should_retain(&value) {
cursor.insert(value);
}
}
Entry::Vacant(entry) => {
if should_retain(&value) {
Self::create(&self.keys, &self.items, entry, value);
}
}
}
}
fn remove(&mut self, key: &[u8]) {
let k = self.translator.transform(key);
if self.map.remove(&k).is_some() {
self.keys.dec();
self.items.dec();
self.pruned.inc();
if !self.overflow.is_empty() {
if let Some(chain) = self.overflow.remove(&k) {
self.items.dec_by(chain.len() as i64);
self.pruned.inc_by(chain.len() as u64);
}
}
}
}
#[cfg(test)]
fn keys(&self) -> usize {
self.map.len()
}
#[cfg(test)]
fn items(&self) -> usize {
self.items.get() as usize
}
#[cfg(test)]
fn pruned(&self) -> usize {
self.pruned.get() as usize
}
}