uqa_storage/inverted_index/
contract.rs1use super::{
8 counter_error, usize_to_u64, Analyzer, Arc, BTreeMap, BlockMaxScorer, DocId, FieldName,
9 IndexStats, PostingEntry, PostingList, StorageBackendError, StorageBackendResult,
10};
11use crate::clustered_postings::{MaterializedPostingCursor, PostingCursor, PostingScore};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum AnalyzerPhase {
16 Index,
18 Search,
20 Both,
22}
23
24impl AnalyzerPhase {
25 pub fn parse(s: &str) -> Result<Self, String> {
26 match s {
27 "index" => Ok(AnalyzerPhase::Index),
28 "search" | "query" => Ok(AnalyzerPhase::Search),
29 "both" => Ok(AnalyzerPhase::Both),
30 _ => Err(format!("phase must be 'index'|'search'|'both', got `{s}`")),
31 }
32 }
33}
34
35impl std::str::FromStr for AnalyzerPhase {
36 type Err = String;
37
38 fn from_str(s: &str) -> Result<Self, Self::Err> {
39 Self::parse(s)
40 }
41}
42
43pub trait InvertedIndex: Send + Sync {
44 fn analyzer(&self) -> &Analyzer;
45
46 fn add_document(
47 &mut self,
48 doc_id: DocId,
49 fields: BTreeMap<FieldName, String>,
50 ) -> StorageBackendResult<()>;
51
52 fn try_add_document(
53 &mut self,
54 doc_id: DocId,
55 fields: BTreeMap<FieldName, String>,
56 ) -> StorageBackendResult<()> {
57 self.add_document(doc_id, fields)
58 }
59
60 fn try_add_documents(
62 &mut self,
63 documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
64 ) -> StorageBackendResult<()> {
65 for (doc_id, fields) in documents {
66 self.try_add_document(doc_id, fields)?;
67 }
68 Ok(())
69 }
70
71 fn remove_document(&mut self, doc_id: DocId) -> StorageBackendResult<()>;
72
73 fn try_remove_document(&mut self, doc_id: DocId) -> StorageBackendResult<()> {
74 self.remove_document(doc_id)
75 }
76
77 fn clear(&mut self) -> StorageBackendResult<()>;
78
79 fn try_clear(&mut self) -> StorageBackendResult<()> {
80 self.clear()
81 }
82
83 fn try_rebuild_documents(
84 &mut self,
85 documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
86 ) -> StorageBackendResult<()> {
87 self.try_clear()?;
88 for (doc_id, fields) in documents {
89 if !fields.is_empty() {
90 self.try_add_document(doc_id, fields)?;
91 }
92 }
93 Ok(())
94 }
95
96 fn get_posting_list(&self, field: &str, term: &str) -> StorageBackendResult<PostingList>;
97
98 fn get_posting_lists_bulk(
99 &self,
100 field: &str,
101 terms: &[String],
102 ) -> StorageBackendResult<Vec<PostingList>> {
103 terms
104 .iter()
105 .map(|term| self.get_posting_list(field, term))
106 .collect()
107 }
108
109 fn posting_cursor(
116 &self,
117 field: &str,
118 term: &str,
119 ) -> StorageBackendResult<Box<dyn PostingCursor>> {
120 let posting_list = self.get_posting_list(field, term)?;
121 let mut entries = Vec::with_capacity(posting_list.len());
122 for posting in posting_list {
123 let term_freq = usize_to_u64(posting.payload.positions.len().max(1), "term frequency")?;
124 entries.push(PostingScore {
125 doc_id: posting.doc_id,
126 term_freq,
127 doc_length: self.get_doc_length(posting.doc_id, field)?.max(term_freq),
128 });
129 }
130 Ok(Box::new(MaterializedPostingCursor::new(entries)?))
131 }
132
133 fn posting_cursors_bulk(
134 &self,
135 field: &str,
136 terms: &[String],
137 ) -> StorageBackendResult<Vec<Box<dyn PostingCursor>>> {
138 terms
139 .iter()
140 .map(|term| self.posting_cursor(field, term))
141 .collect()
142 }
143
144 fn rebuild_persisted_block_max(
150 &mut self,
151 _field: &str,
152 _scorer: &dyn BlockMaxScorer,
153 _scorer_fingerprint: &str,
154 ) -> StorageBackendResult<bool> {
155 Ok(false)
156 }
157
158 fn persisted_block_max_scores(
161 &self,
162 _field: &str,
163 _term: &str,
164 _scorer_fingerprint: &str,
165 ) -> StorageBackendResult<Option<Vec<f64>>> {
166 Ok(None)
167 }
168
169 fn persisted_block_max_scores_bulk(
171 &self,
172 field: &str,
173 terms: &[String],
174 scorer_fingerprint: &str,
175 ) -> StorageBackendResult<Vec<Option<Vec<f64>>>> {
176 terms
177 .iter()
178 .map(|term| self.persisted_block_max_scores(field, term, scorer_fingerprint))
179 .collect()
180 }
181
182 fn for_each_posting(
191 &self,
192 field: &str,
193 term: &str,
194 visit: &mut dyn FnMut(&PostingEntry),
195 ) -> StorageBackendResult<()> {
196 for entry in &self.get_posting_list(field, term)? {
197 visit(entry);
198 }
199 Ok(())
200 }
201
202 fn for_each_term_freq(
207 &self,
208 field: &str,
209 term: &str,
210 visit: &mut dyn FnMut(DocId, u64),
211 ) -> StorageBackendResult<()> {
212 for entry in &self.get_posting_list(field, term)? {
213 visit(
214 entry.doc_id,
215 usize_to_u64(entry.payload.positions.len(), "term frequency")?,
216 );
217 }
218 Ok(())
219 }
220
221 fn doc_freq(&self, field: &str, term: &str) -> StorageBackendResult<u64>;
222
223 fn get_doc_length(&self, doc_id: DocId, field: &str) -> StorageBackendResult<u64>;
224
225 fn get_term_freq(&self, doc_id: DocId, field: &str, term: &str) -> StorageBackendResult<u64>;
226
227 fn doc_count(&self) -> StorageBackendResult<u64>;
228
229 fn total_field_length(&self, field: &str) -> StorageBackendResult<u64>;
230
231 fn field_doc_count(&self, field: &str) -> StorageBackendResult<u64> {
233 self.doc_length_count(Some(field))
234 }
235
236 fn field_stats(&self, field: &str) -> StorageBackendResult<IndexStats> {
242 let mut stats = self.stats()?;
243 let field_docs = self.field_doc_count(field)?;
244 stats.total_docs = field_docs;
245 stats.avg_doc_length = if field_docs > 0 {
246 self.total_field_length(field)? as f64 / field_docs as f64
247 } else {
248 0.0
249 };
250 Ok(stats)
251 }
252
253 fn field_stats_scalar(&self, field: &str) -> StorageBackendResult<IndexStats> {
261 let mut stats = IndexStats::default();
262 let field_docs = self.field_doc_count(field)?;
263 stats.total_docs = field_docs;
264 stats.avg_doc_length = if field_docs > 0 {
265 self.total_field_length(field)? as f64 / field_docs as f64
266 } else {
267 0.0
268 };
269 Ok(stats)
270 }
271
272 fn vocabulary_terms(&self, _field: &str) -> StorageBackendResult<Vec<String>> {
278 Ok(Vec::new())
279 }
280
281 fn stats(&self) -> StorageBackendResult<IndexStats>;
284
285 fn posting_count(&self, _field: Option<&str>) -> StorageBackendResult<u64> {
288 Ok(0)
289 }
290
291 fn doc_length_count(&self, _field: Option<&str>) -> StorageBackendResult<u64> {
294 Ok(0)
295 }
296
297 fn term_count(&self, _field: Option<&str>) -> StorageBackendResult<u64> {
300 Ok(0)
301 }
302
303 fn snapshot(&self) -> StorageBackendResult<Arc<dyn InvertedIndex>>;
305
306 fn writable_snapshot(&self) -> StorageBackendResult<Box<dyn InvertedIndex>> {
309 Err(StorageBackendError::Other(
310 "writable inverted-index snapshots are not supported by this backend".into(),
311 ))
312 }
313
314 fn field_names(&self) -> StorageBackendResult<Vec<FieldName>> {
320 Ok(Vec::new())
321 }
322
323 fn get_posting_list_any_field(&self, term: &str) -> StorageBackendResult<PostingList> {
327 let mut result = PostingList::new();
328 for field in self.field_names()? {
329 let pl = self.get_posting_list(&field, term)?;
330 result = result.merge_union(&pl);
331 }
332 Ok(result)
333 }
334
335 fn doc_freq_any_field(&self, term: &str) -> StorageBackendResult<u64> {
337 let mut total = 0_u64;
338 for field in self.field_names()? {
339 total = total
340 .checked_add(self.doc_freq(&field, term)?)
341 .ok_or_else(|| counter_error("document frequency"))?;
342 }
343 Ok(total)
344 }
345
346 fn get_total_doc_length(&self, doc_id: DocId) -> StorageBackendResult<u64> {
348 let mut total = 0_u64;
349 for field in self.field_names()? {
350 total = total
351 .checked_add(self.get_doc_length(doc_id, &field)?)
352 .ok_or_else(|| counter_error("document length"))?;
353 }
354 Ok(total)
355 }
356
357 fn get_doc_lengths_bulk(
359 &self,
360 doc_ids: &[DocId],
361 field: &str,
362 ) -> StorageBackendResult<BTreeMap<DocId, u64>> {
363 let mut out = BTreeMap::new();
364 for doc_id in doc_ids {
365 out.insert(*doc_id, self.get_doc_length(*doc_id, field)?);
366 }
367 Ok(out)
368 }
369
370 fn get_term_freqs_bulk(
372 &self,
373 doc_ids: &[DocId],
374 field: &str,
375 term: &str,
376 ) -> StorageBackendResult<BTreeMap<DocId, u64>> {
377 let mut out = BTreeMap::new();
378 for doc_id in doc_ids {
379 out.insert(*doc_id, self.get_term_freq(*doc_id, field, term)?);
380 }
381 Ok(out)
382 }
383
384 fn get_scoring_inputs_bulk(
389 &self,
390 doc_ids: &[DocId],
391 field: &str,
392 terms: &[String],
393 ) -> StorageBackendResult<Vec<(u64, Vec<u64>)>> {
394 let mut out = Vec::with_capacity(doc_ids.len());
395 for doc_id in doc_ids {
396 let mut term_freqs = Vec::with_capacity(terms.len());
397 for term in terms {
398 term_freqs.push(self.get_term_freq(*doc_id, field, term)?);
399 }
400 out.push((self.get_doc_length(*doc_id, field)?, term_freqs));
401 }
402 Ok(out)
403 }
404
405 fn get_total_term_freq(&self, doc_id: DocId, term: &str) -> StorageBackendResult<u64> {
408 let mut total = 0_u64;
409 for field in self.field_names()? {
410 total = total
411 .checked_add(self.get_term_freq(doc_id, &field, term)?)
412 .ok_or_else(|| counter_error("term frequency"))?;
413 }
414 Ok(total)
415 }
416
417 fn set_field_analyzer(
422 &mut self,
423 _field: &str,
424 _analyzer: Analyzer,
425 _phase: AnalyzerPhase,
426 ) -> Result<(), String> {
427 Err("set_field_analyzer not supported by this InvertedIndex backend".into())
428 }
429
430 fn remove_field_analyzers(&mut self, _field: &str) -> Result<(), String> {
435 Err("remove_field_analyzers not supported by this InvertedIndex backend".into())
436 }
437
438 fn get_field_analyzer(&self, _field: &str) -> Analyzer {
441 self.analyzer().clone()
442 }
443
444 fn get_search_analyzer(&self, field: &str) -> Analyzer {
447 self.get_field_analyzer(field)
448 }
449}