1use std::collections::{BTreeMap, HashMap};
12
13use crate::bm25::bm25_score;
14use crate::positions::walk;
15use crate::docblobs::put_varint;
16use crate::segment::TextSegment;
17
18pub struct FrozenBucket {
22 pub terms: BTreeMap<Vec<u8>, Vec<u8>>,
24 pub fwd: BTreeMap<Vec<u8>, Vec<u8>>,
28 pub n_docs: u64,
30 pub total_len: u64,
32}
33
34pub struct ColdEntry {
36 pub key: Vec<u8>,
38 pub tf: u32,
40 pub dl: u32,
42 pub positions: Vec<u8>,
45}
46
47pub fn encode_posting(docs: &[ColdEntry]) -> Vec<u8> {
50 let mut out = Vec::new();
51 put_varint(&mut out, docs.len() as u32);
52 for d in docs {
53 put_varint(&mut out, d.key.len() as u32);
54 out.extend_from_slice(&d.key);
55 put_varint(&mut out, d.tf);
56 put_varint(&mut out, d.dl);
57 put_varint(&mut out, d.positions.len() as u32);
58 out.extend_from_slice(&d.positions);
59 }
60 out
61}
62
63pub fn posting_df(payload: &[u8]) -> Option<u32> {
65 read_varint(payload, &mut 0)
66}
67
68pub struct FwdRecord {
71 pub dl: u32,
73 pub terms: Vec<Vec<u8>>,
75 pub values: Vec<Option<Vec<u8>>>,
78}
79
80pub fn encode_fwd(dl: u32, terms: &[&[u8]], values: &[Option<&[u8]>]) -> Vec<u8> {
87 let mut out = Vec::new();
88 put_varint(&mut out, dl);
89 put_varint(&mut out, terms.len() as u32);
90 for t in terms {
91 put_varint(&mut out, t.len() as u32);
92 out.extend_from_slice(t);
93 }
94 put_varint(&mut out, values.len() as u32);
95 for v in values {
96 match v {
97 None => put_varint(&mut out, 0),
98 Some(b) => {
99 put_varint(&mut out, 1);
100 put_varint(&mut out, b.len() as u32);
101 out.extend_from_slice(b);
102 }
103 }
104 }
105 out
106}
107
108pub fn decode_fwd(payload: &[u8]) -> Option<FwdRecord> {
110 let mut at = 0usize;
111 let dl = read_varint(payload, &mut at)?;
112 let n = read_varint(payload, &mut at)? as usize;
113 let mut terms = Vec::with_capacity(n);
114 for _ in 0..n {
115 let klen = read_varint(payload, &mut at)? as usize;
116 terms.push(payload.get(at..at + klen)?.to_vec());
117 at += klen;
118 }
119 let nv = read_varint(payload, &mut at)? as usize;
120 let mut values = Vec::with_capacity(nv);
121 for _ in 0..nv {
122 values.push(match read_varint(payload, &mut at)? {
123 0 => None,
124 1 => {
125 let vlen = read_varint(payload, &mut at)? as usize;
126 let v = payload.get(at..at + vlen)?.to_vec();
127 at += vlen;
128 Some(v)
129 }
130 _ => return None,
131 });
132 }
133 (at == payload.len()).then_some(FwdRecord { dl, terms, values })
134}
135
136pub fn decode_posting(payload: &[u8]) -> Option<Vec<ColdEntry>> {
139 let mut at = 0usize;
140 let n = read_varint(payload, &mut at)? as usize;
141 let mut out = Vec::with_capacity(n);
142 for _ in 0..n {
143 let klen = read_varint(payload, &mut at)? as usize;
144 let key = payload.get(at..at + klen)?.to_vec();
145 at += klen;
146 let tf = read_varint(payload, &mut at)?;
147 let dl = read_varint(payload, &mut at)?;
148 let plen = read_varint(payload, &mut at)? as usize;
149 let positions = payload.get(at..at + plen)?.to_vec();
150 at += plen;
151 out.push(ColdEntry { key, tf, dl, positions });
152 }
153 (at == payload.len()).then_some(out)
154}
155
156pub fn score_cold(
162 payload: &[u8],
163 term: &[u8],
164 stats: &crate::CorpusStats,
165 dead: &dyn Fn(&[u8]) -> bool,
166 acc: &mut HashMap<Vec<u8>, f64>,
167) -> Option<()> {
168 let entries = decode_posting(payload)?;
169 let df = f64::from(*stats.df.get(term).unwrap_or(&(entries.len() as u32)));
170 for e in entries {
171 if dead(&e.key) {
172 continue;
173 }
174 let s = bm25_score(f64::from(e.tf), df, stats.n_docs, f64::from(e.dl), stats.avgdl);
175 *acc.entry(e.key).or_insert(0.0) += s;
176 }
177 Some(())
178}
179
180pub fn score_cold_phrase(
190 payloads: &[Vec<u8>],
191 toks: &[Vec<u8>],
192 stats: &crate::CorpusStats,
193 dead: &dyn Fn(&[u8]) -> bool,
194 acc: &mut HashMap<Vec<u8>, f64>,
195) -> Option<()> {
196 if payloads.len() != toks.len() || toks.is_empty() {
197 return None;
198 }
199 let per_tok: Vec<HashMap<Vec<u8>, ColdEntry>> = payloads
200 .iter()
201 .map(|p| {
202 decode_posting(p).map(|es| es.into_iter().map(|e| (e.key.clone(), e)).collect())
203 })
204 .collect::<Option<_>>()?;
205 let distinct = crate::segment::distinct_tokens(toks);
206 for (key, first) in &per_tok[0] {
207 if dead(key) || !per_tok[1..].iter().all(|m| m.contains_key(key)) {
208 continue;
209 }
210 let adjacent = walk(&first.positions).any(|start| {
211 toks.iter().enumerate().skip(1).all(|(i, _)| {
212 walk(&per_tok[i][key].positions).any(|p| p == start + i as u32)
213 })
214 });
215 if !adjacent {
216 continue;
217 }
218 let dl = f64::from(first.dl);
219 let mut score = 0.0;
220 for t in &distinct {
221 let Some(pos) = toks.iter().position(|tt| tt == t) else { continue };
222 let e = &per_tok[pos][key];
223 let df = f64::from(
224 *stats.df.get(t).unwrap_or(&(per_tok[pos].len() as u32)),
225 );
226 score += bm25_score(f64::from(e.tf), df, stats.n_docs, dl, stats.avgdl);
227 }
228 *acc.entry(key.clone()).or_insert(0.0) += score;
229 }
230 Some(())
231}
232
233pub fn highlight_fields(
239 fields: &[Vec<u8>],
240 query: &[u8],
241) -> Vec<(usize, Vec<(usize, usize)>)> {
242 let (bare, phrases, prefixes) = crate::parse_clauses(query);
243 let terms: std::collections::HashSet<&[u8]> = bare.iter().map(Vec::as_slice).collect();
244 let mut out = Vec::new();
245 for (fi, text) in fields.iter().enumerate() {
246 let mut spans = crate::segment::field_spans(
247 &crate::tokenize_spans(text),
248 &terms,
249 &phrases,
250 &prefixes,
251 );
252 if !spans.is_empty() {
253 spans.sort_unstable();
254 spans.dedup();
255 out.push((fi, spans));
256 }
257 }
258 out
259}
260
261fn read_varint(b: &[u8], at: &mut usize) -> Option<u32> {
262 let mut cur = 0u32;
263 let mut shift = 0u32;
264 loop {
265 let byte = *b.get(*at)?;
266 *at += 1;
267 cur |= u32::from(byte & 0x7f) << shift;
268 if byte & 0x80 == 0 {
269 return Some(cur);
270 }
271 shift += 7;
272 if shift > 28 {
273 return None;
274 }
275 }
276}
277
278impl TextSegment {
279 pub fn freeze_docs(&mut self, keys: &[Vec<u8>]) -> Option<FrozenBucket> {
286 let mut terms: BTreeMap<Vec<u8>, Vec<ColdEntry>> = BTreeMap::new();
287 let mut fwd: BTreeMap<Vec<u8>, Vec<u8>> = BTreeMap::new();
288 let mut n_docs = 0u64;
289 let mut total_len = 0u64;
290 for key in keys {
291 let Some((id, dl, tf_map)) = self.doc_terms(key) else { continue };
292 n_docs += 1;
293 total_len += u64::from(dl);
294 let mut doc_terms: Vec<&[u8]> = tf_map.keys().map(Vec::as_slice).collect();
295 doc_terms.sort_unstable();
296 let values = self.doc_values_of(id);
297 fwd.insert(key.clone(), encode_fwd(dl, &doc_terms, &values));
298 for (t, tf) in &tf_map {
299 let positions = self.positions_blob(t, id).map(<[u8]>::to_vec).unwrap_or_default();
300 terms.entry(t.clone()).or_default().push(ColdEntry {
301 key: key.clone(),
302 tf: *tf,
303 dl,
304 positions,
305 });
306 }
307 }
308 if n_docs == 0 {
309 return None;
310 }
311 for key in keys {
313 self.apply_doc(key, None, &[]);
314 }
315 let terms = terms
316 .into_iter()
317 .map(|(t, entries)| {
318 let payload = encode_posting(&entries);
319 (t, payload)
320 })
321 .collect();
322 Some(FrozenBucket { terms, fwd, n_docs, total_len })
323 }
324}
325
326#[cfg(test)]
327#[path = "cold_tests.rs"]
328mod tests;