use std::sync::{Arc, OnceLock};
use super::image::ServingImage;
use super::index::DocumentStore;
use super::postings::Postings;
use super::search::{LexicalSource, PostingSet};
pub(crate) struct Base {
pub(super) image: Arc<ServingImage>,
dead_slots: Vec<u64>,
dead_documents: Vec<u64>,
dead_document_count: usize,
dead_chunks: usize,
dead_words: usize,
document_slots: OnceLock<(Vec<u32>, Vec<u32>)>,
}
fn bit(bits: &[u64], i: usize) -> bool {
bits[i / 64] & (1 << (i % 64)) != 0
}
impl Base {
pub(crate) fn new(image: Arc<ServingImage>) -> Self {
Self {
dead_slots: vec![0; image.slot_total().div_ceil(64)],
dead_documents: vec![0; image.document_total().div_ceil(64)],
image,
dead_document_count: 0,
dead_chunks: 0,
dead_words: 0,
document_slots: OnceLock::new(),
}
}
pub(super) fn intact(&self) -> bool {
self.dead_document_count == 0
}
pub(super) fn live(&self, id: &str) -> Option<usize> {
self.image
.ordinal(id)
.filter(|&ordinal| !bit(&self.dead_documents, ordinal))
}
pub(super) fn slot_dead(&self, slot: usize) -> bool {
bit(&self.dead_slots, slot)
}
pub(super) fn dead_ordinals(&self) -> impl Iterator<Item = usize> + '_ {
(0..self.image.document_total()).filter(|&ordinal| bit(&self.dead_documents, ordinal))
}
pub(super) fn live_chunks(&self) -> usize {
self.image.chunk_count() - self.dead_chunks
}
pub(super) fn live_words(&self) -> usize {
self.image.word_count() - self.dead_words
}
pub(super) fn kill(&mut self, ordinal: usize) {
debug_assert!(!bit(&self.dead_documents, ordinal));
let (offsets, slots) = self.document_slots.get_or_init(|| {
let documents = self.image.document_total();
let mut offsets = vec![0u32; documents + 1];
for slot in 0..self.image.slot_total() {
if let Some(owner) = self.image.slot_owner(slot) {
offsets[owner + 1] += 1;
}
}
for i in 0..documents {
offsets[i + 1] += offsets[i];
}
let mut next = offsets.clone();
let mut slots = vec![0u32; offsets[documents] as usize];
for slot in 0..self.image.slot_total() {
if let Some(owner) = self.image.slot_owner(slot) {
slots[next[owner] as usize] = slot as u32;
next[owner] += 1;
}
}
(offsets, slots)
});
for &slot in &slots[offsets[ordinal] as usize..offsets[ordinal + 1] as usize] {
let slot = slot as usize;
self.dead_slots[slot / 64] |= 1 << (slot % 64);
self.dead_chunks += 1;
self.dead_words += self.image.slot_word_count(slot) as usize;
}
self.dead_documents[ordinal / 64] |= 1 << (ordinal % 64);
self.dead_document_count += 1;
}
}
pub(super) struct Gathered(Vec<(u32, u32)>);
impl PostingSet for Gathered {
fn len(&self) -> usize {
self.0.len()
}
fn for_each(&self, mut f: impl FnMut(usize, u32)) {
for &(slot, frequency) in &self.0 {
f(slot as usize, frequency);
}
}
}
pub(super) struct Overlaid<'a> {
pub(super) base: &'a Base,
pub(super) store: &'a DocumentStore,
}
impl Overlaid<'_> {
pub(super) fn split(&self) -> usize {
self.base.image.slot_total()
}
}
impl LexicalSource for Overlaid<'_> {
type List<'b>
= Gathered
where
Self: 'b;
fn slot_count(&self) -> usize {
self.split() + self.store.slot_count
}
fn chunk_count(&self) -> usize {
self.base.live_chunks() + self.store.chunk_count
}
fn word_count(&self) -> usize {
self.base.live_words() + self.store.word_count
}
fn state(&self) -> (super::index::StoreIdentity, u64) {
(self.store.identity, self.store.generation)
}
fn postings(&self, form: usize, term: &str) -> Option<Gathered> {
let base: Option<Postings<'_>> = self.base.image.postings(form, term);
let live = self.store.postings[form].get(term);
let capacity = base.map_or(0, |list| list.len()) + live.map_or(0, |list| list.len());
let mut gathered = Vec::with_capacity(capacity);
if let Some(list) = base {
list.for_each(|slot, frequency| {
if !self.base.slot_dead(slot) {
gathered.push((slot as u32, frequency));
}
});
}
if let Some(list) = live {
let split = self.split();
list.for_each(|slot, frequency| gathered.push(((split + slot) as u32, frequency)));
}
Some(Gathered(gathered))
}
fn slot_word_count(&self, slot: usize) -> u32 {
match slot.checked_sub(self.split()) {
None => self.base.image.slot_word_count(slot),
Some(slot) => self.store.slot_word_count(slot),
}
}
fn slot_order(&self, slot: usize) -> (&str, usize) {
match slot.checked_sub(self.split()) {
None => self.base.image.slot_order(slot),
Some(slot) => self.store.slot_order(slot),
}
}
}