use std::collections::HashMap;
use crate::buckets::Buckets;
use crate::docvalues::DocValues;
use crate::fields::FieldStats;
use crate::positions::Positions;
use crate::token::tokenize;
#[derive(Debug, Clone, PartialEq)]
pub struct TextMatch {
pub key: Vec<u8>,
pub score: f64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct TextStats {
pub docs: u64,
pub tokens: u64,
pub postings: u64,
pub approx_bytes: u64,
}
pub struct CorpusStats {
pub n_docs: f64,
pub avgdl: f64,
pub df: std::collections::HashMap<Vec<u8>, u32>,
}
#[derive(Clone, Copy, Default)]
pub struct SegmentShape {
pub fields: usize,
pub positions: bool,
pub values: usize,
}
#[derive(Clone, Copy)]
pub struct Filter<'a> {
pub field: usize,
pub test: &'a dyn Fn(&[u8]) -> bool,
}
type IndexedField = (Vec<u8>, f32);
type DocRecord = (u32, u32, Vec<IndexedField>);
#[derive(Debug, Default)]
pub struct TextSegment {
postings: HashMap<Vec<u8>, Buckets>,
docs: HashMap<Vec<u8>, DocRecord>,
id_key: Vec<Option<Vec<u8>>>,
id_dl: Vec<u32>,
free_ids: Vec<u32>,
total_len: u64,
positions: Option<Positions>,
fields: Option<FieldStats>,
values: Option<DocValues>,
}
impl TextSegment {
pub fn new() -> Self {
Self::default()
}
pub fn with_positions() -> Self {
Self::with_shape(SegmentShape { positions: true, ..SegmentShape::default() })
}
pub fn with_shape(shape: SegmentShape) -> Self {
Self {
positions: shape.positions.then(Positions::default),
fields: (shape.fields > 1).then(|| FieldStats::new(shape.fields)),
values: (shape.values > 0).then(|| DocValues::new(shape.values)),
..Self::default()
}
}
pub fn has_positions(&self) -> bool {
self.positions.is_some()
}
pub fn field_arity(&self) -> usize {
self.fields.as_ref().map_or(1, FieldStats::arity)
}
pub fn value_arity(&self) -> usize {
self.values.as_ref().map_or(0, DocValues::arity)
}
pub fn stored_value(&self, key: &[u8], field: usize) -> Option<&[u8]> {
let (id, _, _) = self.docs.get(key)?;
self.values.as_ref()?.get(*id, field)
}
pub fn apply(&mut self, key: &[u8], text: Option<&[u8]>) {
match text {
Some(t) => self.apply_fields(key, Some(&[(t.to_vec(), 1.0)])),
None => self.apply_fields(key, None),
}
}
pub fn apply_fields(&mut self, key: &[u8], fields: Option<&[IndexedField]>) {
self.apply_doc(key, fields, &[]);
}
pub fn apply_doc(
&mut self,
key: &[u8],
fields: Option<&[IndexedField]>,
values: &[Option<&[u8]>],
) {
self.withdraw(key);
let Some(fields) = fields else { return };
let (per_field, lens) = field_tf(fields);
let (tf_map, dl) = merge_field_tf(&per_field, &lens);
if tf_map.is_empty() {
return;
}
let id = self.take_id(key, dl);
self.docs.insert(key.to_vec(), (id, dl, fields.to_vec()));
self.total_len += u64::from(dl);
for (t, tf) in tf_map {
match self.postings.entry(t) {
std::collections::hash_map::Entry::Occupied(mut e) => {
e.get_mut().insert(tf, dl, id);
}
std::collections::hash_map::Entry::Vacant(v) => {
v.insert(Buckets::new_one(tf, dl, id));
}
}
}
self.index_side_channels(id, fields, &per_field, &lens);
if let Some(dv) = self.values.as_mut() {
dv.set(id, values);
}
}
fn take_id(&mut self, key: &[u8], dl: u32) -> u32 {
if let Some(id) = self.free_ids.pop() {
self.id_key[id as usize] = Some(key.to_vec());
self.id_dl[id as usize] = dl;
id
} else {
self.id_key.push(Some(key.to_vec()));
self.id_dl.push(dl);
(self.id_key.len() - 1) as u32
}
}
fn index_side_channels(
&mut self,
id: u32,
fields: &[IndexedField],
per_field: &[HashMap<Vec<u8>, u32>],
lens: &[u32],
) {
if let Some(pos) = self.positions.as_mut() {
for (t, offsets) in token_offsets(fields) {
pos.set(&t, id, &offsets);
}
}
let Some(fs) = self.fields.as_mut() else { return };
fs.set_doc_len(id, lens);
let arity = fs.arity();
let mut by_token: HashMap<&[u8], Vec<u32>> = HashMap::new();
for (f, m) in per_field.iter().enumerate() {
for (t, v) in m {
let row = by_token.entry(t).or_insert_with(|| vec![0; arity]);
if let Some(slot) = row.get_mut(f) {
*slot = *v;
}
}
}
for (t, row) in by_token {
fs.set(t, id, &row);
}
}
fn withdraw(&mut self, key: &[u8]) {
let Some((old_id, old_len, old_fields)) = self.docs.remove(key) else {
return;
};
self.total_len -= u64::from(old_len);
for (t, tf) in weighted_tf(&old_fields).0 {
if let Some(list) = self.postings.get_mut(&t) {
list.remove(tf, old_len, old_id);
if list.is_empty() {
self.postings.remove(&t);
}
}
if let Some(pos) = self.positions.as_mut() {
pos.remove(&t, old_id);
}
if let Some(fs) = self.fields.as_mut() {
fs.remove(&t, old_id);
}
}
if let Some(fs) = self.fields.as_mut() {
fs.clear_doc_len(old_id);
}
if let Some(dv) = self.values.as_mut() {
dv.clear(old_id);
}
self.id_key[old_id as usize] = None;
self.free_ids.push(old_id);
}
}
fn weighted_tf(fields: &[IndexedField]) -> (HashMap<Vec<u8>, u32>, u32) {
let (per_field, lens) = field_tf(fields);
merge_field_tf(&per_field, &lens)
}
fn field_tf(fields: &[IndexedField]) -> (Vec<HashMap<Vec<u8>, u32>>, Vec<u32>) {
let mut per_field = Vec::with_capacity(fields.len());
let mut lens = Vec::with_capacity(fields.len());
for (text, weight) in fields {
let toks = tokenize(text);
lens.push(toks.len() as u32);
let scaled = tf_of(&toks)
.into_iter()
.map(|(t, n)| (t, (f64::from(n) * f64::from(*weight)).ceil().max(1.0) as u32))
.collect();
per_field.push(scaled);
}
(per_field, lens)
}
fn merge_field_tf(
per_field: &[HashMap<Vec<u8>, u32>],
lens: &[u32],
) -> (HashMap<Vec<u8>, u32>, u32) {
let mut out: HashMap<Vec<u8>, u32> = HashMap::new();
for m in per_field {
for (t, v) in m {
let slot = out.entry(t.clone()).or_insert(0);
*slot = slot.saturating_add(*v);
}
}
let dl = lens.iter().fold(0u32, |a, &b| a.saturating_add(b));
(out, dl)
}
fn tf_of(toks: &[Vec<u8>]) -> HashMap<Vec<u8>, u32> {
let mut tf = HashMap::new();
for t in toks {
*tf.entry(t.clone()).or_insert(0) += 1;
}
tf
}
fn token_offsets(fields: &[IndexedField]) -> HashMap<Vec<u8>, Vec<u32>> {
let mut out: HashMap<Vec<u8>, Vec<u32>> = HashMap::new();
let mut pos = 0u32;
for (text, _weight) in fields {
for tok in tokenize(text) {
out.entry(tok).or_default().push(pos);
pos += 1;
}
}
out
}
#[path = "segment_opts.rs"]
mod segment_opts;
pub use segment_opts::{Bucket, Distinct, Facet, FacetedMatches, QueryOpts, Sort};
#[path = "segment_query.rs"]
mod segment_query;
pub use segment_query::sorted_order;
#[path = "segment_stats.rs"]
mod segment_stats;
#[path = "segment_phrase.rs"]
mod segment_phrase;
#[path = "segment_scope.rs"]
mod segment_scope;
#[cfg(test)]
#[path = "segment_tests.rs"]
mod tests;