use std::collections::HashMap;
use nodedb_types::{Surrogate, SurrogateBitmap};
use crate::codec::smallfloat;
use crate::posting::Bm25Params;
use crate::search::doc_score::term_score;
use crate::search::query_terms::ResolvedQuery;
#[derive(Debug, Clone)]
pub struct StagedDoc {
pub doc_id: Surrogate,
pub tokens: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct StagedView {
hidden: SurrogateBitmap,
hide_all: bool,
docs: Vec<StagedDoc>,
by_id: HashMap<Surrogate, usize>,
}
impl StagedView {
pub fn new(mut hidden: SurrogateBitmap, hide_all: bool, docs: Vec<StagedDoc>) -> Self {
let by_id = docs
.iter()
.enumerate()
.map(|(idx, doc)| {
hidden.insert(doc.doc_id);
(doc.doc_id, idx)
})
.collect();
Self {
hidden,
hide_all,
docs,
by_id,
}
}
pub fn hides(&self, doc_id: Surrogate) -> bool {
self.hide_all || self.hidden.contains(doc_id)
}
pub fn hides_all(&self) -> bool {
self.hide_all
}
pub fn hidden(&self) -> &SurrogateBitmap {
&self.hidden
}
pub fn docs(&self) -> &[StagedDoc] {
&self.docs
}
pub fn doc(&self, doc_id: Surrogate) -> Option<&StagedDoc> {
self.by_id.get(&doc_id).and_then(|idx| self.docs.get(*idx))
}
pub(crate) fn holds_term(&self, term: &str) -> bool {
self.docs.iter().any(|d| d.tokens.iter().any(|t| t == term))
}
pub(crate) fn vocabulary(&self) -> Vec<&str> {
let mut terms: Vec<&str> = self
.docs
.iter()
.flat_map(|d| d.tokens.iter().map(String::as_str))
.collect();
terms.sort_unstable();
terms.dedup();
terms
}
}
pub(crate) fn staged_contributions(
query: &ResolvedQuery,
doc: &StagedDoc,
params: &Bm25Params,
) -> Option<Vec<Option<f32>>> {
let mut tf: HashMap<&str, u32> = HashMap::new();
for token in &doc.tokens {
*tf.entry(token.as_str()).or_insert(0) += 1;
}
if query
.negative_terms
.iter()
.any(|term| tf.contains_key(term.as_str()))
{
return None;
}
let doc_len = u32::try_from(doc.tokens.len()).unwrap_or(u32::MAX);
let fieldnorm = smallfloat::encode(doc_len);
Some(
query
.texts
.iter()
.zip(&query.blocks)
.map(|(text, blocks)| {
tf.get(text.as_str()).map(|freq| {
term_score(
crate::bm25::idf(blocks.df, query.total_docs),
*freq,
fieldnorm,
query.avg_doc_len,
params,
)
})
})
.collect(),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn staged_docs_are_hidden_in_the_index() {
let view = StagedView::new(
SurrogateBitmap::new(),
false,
vec![StagedDoc {
doc_id: Surrogate(7),
tokens: vec!["rust".into()],
}],
);
assert!(view.hides(Surrogate(7)));
assert!(!view.hides(Surrogate(8)));
assert!(view.doc(Surrogate(7)).is_some());
assert!(view.holds_term("rust"));
assert!(!view.holds_term("python"));
}
#[test]
fn truncate_hides_every_indexed_document() {
let view = StagedView::new(SurrogateBitmap::new(), true, Vec::new());
assert!(view.hides(Surrogate(1)));
assert!(view.hides_all());
}
}