1use super::IndexedFieldMetadata;
8use super::{
9 counter_error, Analyzer, Arc, BTreeMap, BlockMaxScorer, DocId, FieldName, IndexStats,
10 PostingEntry, PostingList, StorageBackendError, StorageBackendResult,
11};
12use crate::clustered_postings::BudgetedPostingReadCursor;
13use crate::clustered_postings::{
14 MaterializedPostingCursor, OccurrencePosting, PostingCursor, PostingScore,
15};
16use crate::read_control::StorageReadControl;
17use crate::TokenTermKey;
18use uqa_core::memory::Budgeted;
19use uqa_core::TokenOccurrence;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum AnalyzerPhase {
25 Index,
27 Search,
29 Both,
31}
32
33impl AnalyzerPhase {
34 pub fn parse(s: &str) -> Result<Self, String> {
35 match s {
36 "index" => Ok(AnalyzerPhase::Index),
37 "search" | "query" => Ok(AnalyzerPhase::Search),
38 "both" => Ok(AnalyzerPhase::Both),
39 _ => Err(format!("phase must be 'index'|'search'|'both', got `{s}`")),
40 }
41 }
42}
43
44impl std::str::FromStr for AnalyzerPhase {
45 type Err = String;
46
47 fn from_str(s: &str) -> Result<Self, Self::Err> {
48 Self::parse(s)
49 }
50}
51
52pub trait InvertedIndex: Send + Sync {
53 fn source_rebuild_required(&self) -> StorageBackendResult<bool> {
55 Ok(false)
56 }
57
58 fn analyzer(&self) -> &Analyzer;
59
60 fn add_document(
61 &mut self,
62 doc_id: DocId,
63 fields: BTreeMap<FieldName, String>,
64 ) -> StorageBackendResult<()>;
65
66 fn try_add_document(
67 &mut self,
68 doc_id: DocId,
69 fields: BTreeMap<FieldName, String>,
70 ) -> StorageBackendResult<()> {
71 self.add_document(doc_id, fields)
72 }
73
74 fn try_add_documents(
76 &mut self,
77 documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
78 ) -> StorageBackendResult<()> {
79 for (doc_id, fields) in documents {
80 self.try_add_document(doc_id, fields)?;
81 }
82 Ok(())
83 }
84
85 fn remove_document(&mut self, doc_id: DocId) -> StorageBackendResult<()>;
86
87 fn try_remove_document(&mut self, doc_id: DocId) -> StorageBackendResult<()> {
88 self.remove_document(doc_id)
89 }
90
91 fn clear(&mut self) -> StorageBackendResult<()>;
92
93 fn try_clear(&mut self) -> StorageBackendResult<()> {
94 self.clear()
95 }
96
97 fn try_rebuild_documents(
98 &mut self,
99 documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
100 ) -> StorageBackendResult<()> {
101 self.try_clear()?;
102 for (doc_id, fields) in documents {
103 if !fields.is_empty() {
104 self.try_add_document(doc_id, fields)?;
105 }
106 }
107 Ok(())
108 }
109
110 fn get_posting_list(&self, field: &str, term: &str) -> StorageBackendResult<PostingList>;
111
112 fn get_posting_list_key(
114 &self,
115 field: &str,
116 term: &TokenTermKey,
117 ) -> StorageBackendResult<PostingList> {
118 self.get_posting_list(field, &term.to_term().into_string()?)
119 }
120
121 fn posting_cursor_key(
123 &self,
124 field: &str,
125 term: &TokenTermKey,
126 ) -> StorageBackendResult<Box<dyn PostingCursor>> {
127 self.posting_cursor(field, &term.to_term().into_string()?)
128 }
129
130 fn posting_read_cursor_key<'a>(
132 &'a self,
133 field: &'a str,
134 term: &TokenTermKey,
135 ) -> StorageBackendResult<Box<dyn crate::clustered_postings::PostingReadCursor + 'a>> {
136 Ok(Box::new(crate::clustered_postings::OwnedPostingReadCursor(
137 self.posting_cursor_key(field, term)?,
138 )))
139 }
140
141 fn posting_read_cursor_key_budgeted<'a>(
143 &'a self,
144 field: &'a str,
145 term: &'a TokenTermKey,
146 control: &StorageReadControl,
147 ) -> StorageBackendResult<BudgetedPostingReadCursor<'a>> {
148 crate::clustered_postings::open_controlled_cursor(self, field, term, control)
149 }
150
151 fn visit_score_clusters(
153 &self,
154 _field: &str,
155 _term: &TokenTermKey,
156 _after: Option<u64>,
157 _limit: usize,
158 control: &StorageReadControl,
159 _visit: &mut crate::clustered_postings::ScoreClusterVisitor<'_>,
160 ) -> StorageBackendResult<()> {
161 control.check()?;
162 Err(StorageBackendError::Other(
163 "controlled score cluster reads are not supported by this backend".into(),
164 ))
165 }
166
167 fn get_occurrences_budgeted(
169 &self,
170 _doc_id: DocId,
171 _field: &str,
172 _term: &TokenTermKey,
173 control: &StorageReadControl,
174 ) -> StorageBackendResult<Budgeted<Vec<TokenOccurrence>>> {
175 control.check()?;
176 Err(StorageBackendError::Other(
177 "controlled occurrence reads are not supported by this backend".into(),
178 ))
179 }
180
181 fn get_occurrence_postings(
183 &self,
184 _field: &str,
185 _term: &TokenTermKey,
186 ) -> StorageBackendResult<Vec<OccurrencePosting>> {
187 Err(StorageBackendError::Other(
188 "lossless occurrence storage is not supported by this backend".into(),
189 ))
190 }
191
192 fn get_occurrences(
194 &self,
195 doc_id: DocId,
196 field: &str,
197 term: &TokenTermKey,
198 ) -> StorageBackendResult<Vec<TokenOccurrence>> {
199 Ok(self
200 .get_occurrence_postings(field, term)?
201 .into_iter()
202 .find(|posting| posting.doc_id == doc_id)
203 .map_or_else(Vec::new, |posting| posting.occurrences))
204 }
205
206 fn indexed_field_metadata(
208 &self,
209 _doc_id: DocId,
210 _field: &str,
211 ) -> StorageBackendResult<Option<IndexedFieldMetadata>> {
212 Err(StorageBackendError::Other(
213 "indexed field analysis metadata is not supported by this backend".into(),
214 ))
215 }
216
217 fn doc_freq_key(&self, field: &str, term: &TokenTermKey) -> StorageBackendResult<u64> {
218 self.doc_freq(field, &term.to_term().into_string()?)
219 }
220
221 fn get_term_freq_key(
222 &self,
223 doc_id: DocId,
224 field: &str,
225 term: &TokenTermKey,
226 ) -> StorageBackendResult<u64> {
227 self.get_term_freq(doc_id, field, &term.to_term().into_string()?)
228 }
229
230 fn vocabulary_keys(&self, field: &str) -> StorageBackendResult<Vec<TokenTermKey>> {
232 Ok(self
233 .vocabulary_terms(field)?
234 .iter()
235 .map(|term| TokenTermKey::from_text(term))
236 .collect())
237 }
238
239 fn get_posting_lists_bulk(
240 &self,
241 field: &str,
242 terms: &[String],
243 ) -> StorageBackendResult<Vec<PostingList>> {
244 terms
245 .iter()
246 .map(|term| self.get_posting_list(field, term))
247 .collect()
248 }
249
250 fn posting_cursor(
257 &self,
258 field: &str,
259 term: &str,
260 ) -> StorageBackendResult<Box<dyn PostingCursor>> {
261 let posting_list = self.get_posting_list(field, term)?;
262 let mut entries = Vec::with_capacity(posting_list.len());
263 for posting in posting_list {
264 let term_freq = self.get_term_freq(posting.doc_id, field, term)?;
265 entries.push(PostingScore {
266 doc_id: posting.doc_id,
267 term_freq,
268 doc_length: self.get_doc_length(posting.doc_id, field)?,
269 });
270 }
271 Ok(Box::new(MaterializedPostingCursor::new(entries)?))
272 }
273
274 fn posting_cursors_bulk(
275 &self,
276 field: &str,
277 terms: &[String],
278 ) -> StorageBackendResult<Vec<Box<dyn PostingCursor>>> {
279 terms
280 .iter()
281 .map(|term| self.posting_cursor(field, term))
282 .collect()
283 }
284
285 fn posting_cursors_keys_bulk(
287 &self,
288 field: &str,
289 terms: &[TokenTermKey],
290 ) -> StorageBackendResult<Vec<Box<dyn PostingCursor>>> {
291 if let Some(scalar) = terms
292 .iter()
293 .map(|key| key.as_str().map(str::to_owned))
294 .collect::<Option<Vec<_>>>()
295 {
296 return self.posting_cursors_bulk(field, &scalar);
297 }
298 terms
299 .iter()
300 .map(|term| self.posting_cursor_key(field, term))
301 .collect()
302 }
303
304 fn get_posting_lists_keys_bulk(
306 &self,
307 field: &str,
308 terms: &[TokenTermKey],
309 ) -> StorageBackendResult<Vec<PostingList>> {
310 if let Some(scalar) = terms
311 .iter()
312 .map(|key| key.as_str().map(str::to_owned))
313 .collect::<Option<Vec<_>>>()
314 {
315 return self.get_posting_lists_bulk(field, &scalar);
316 }
317 terms
318 .iter()
319 .map(|term| self.get_posting_list_key(field, term))
320 .collect()
321 }
322
323 fn persisted_block_max_scores_keys_bulk(
325 &self,
326 field: &str,
327 terms: &[TokenTermKey],
328 scorer_fingerprint: &str,
329 ) -> StorageBackendResult<Vec<Option<Vec<f64>>>> {
330 if let Some(scalar) = terms
331 .iter()
332 .map(|key| key.as_str().map(str::to_owned))
333 .collect::<Option<Vec<_>>>()
334 {
335 return self.persisted_block_max_scores_bulk(field, &scalar, scorer_fingerprint);
336 }
337 terms
338 .iter()
339 .map(|key| match key.as_str() {
340 Some(term) => self.persisted_block_max_scores(field, term, scorer_fingerprint),
341 None => Ok(None),
342 })
343 .collect()
344 }
345
346 fn get_scoring_inputs_keys_bulk(
348 &self,
349 doc_ids: &[DocId],
350 field: &str,
351 terms: &[TokenTermKey],
352 ) -> StorageBackendResult<Vec<(u64, Vec<u64>)>> {
353 if let Some(scalar) = terms
354 .iter()
355 .map(|key| key.as_str().map(str::to_owned))
356 .collect::<Option<Vec<_>>>()
357 {
358 return self.get_scoring_inputs_bulk(doc_ids, field, &scalar);
359 }
360 doc_ids
361 .iter()
362 .map(|id| {
363 Ok((
364 self.get_doc_length(*id, field)?,
365 terms
366 .iter()
367 .map(|key| self.get_term_freq_key(*id, field, key))
368 .collect::<StorageBackendResult<_>>()?,
369 ))
370 })
371 .collect()
372 }
373
374 fn rebuild_persisted_block_max(
380 &mut self,
381 _field: &str,
382 _scorer: &dyn BlockMaxScorer,
383 _scorer_fingerprint: &str,
384 ) -> StorageBackendResult<bool> {
385 Ok(false)
386 }
387
388 fn persisted_block_max_scores(
391 &self,
392 _field: &str,
393 _term: &str,
394 _scorer_fingerprint: &str,
395 ) -> StorageBackendResult<Option<Vec<f64>>> {
396 Ok(None)
397 }
398
399 fn persisted_block_max_scores_bulk(
401 &self,
402 field: &str,
403 terms: &[String],
404 scorer_fingerprint: &str,
405 ) -> StorageBackendResult<Vec<Option<Vec<f64>>>> {
406 terms
407 .iter()
408 .map(|term| self.persisted_block_max_scores(field, term, scorer_fingerprint))
409 .collect()
410 }
411
412 fn for_each_posting(
421 &self,
422 field: &str,
423 term: &str,
424 visit: &mut dyn FnMut(&PostingEntry),
425 ) -> StorageBackendResult<()> {
426 for entry in &self.get_posting_list(field, term)? {
427 visit(entry);
428 }
429 Ok(())
430 }
431
432 fn for_each_term_freq(
437 &self,
438 field: &str,
439 term: &str,
440 visit: &mut dyn FnMut(DocId, u64),
441 ) -> StorageBackendResult<()> {
442 for entry in &self.get_posting_list(field, term)? {
443 visit(entry.doc_id, self.get_term_freq(entry.doc_id, field, term)?);
444 }
445 Ok(())
446 }
447
448 fn doc_freq(&self, field: &str, term: &str) -> StorageBackendResult<u64>;
449
450 fn get_doc_length(&self, doc_id: DocId, field: &str) -> StorageBackendResult<u64>;
451
452 fn get_term_freq(&self, doc_id: DocId, field: &str, term: &str) -> StorageBackendResult<u64>;
453
454 fn doc_count(&self) -> StorageBackendResult<u64>;
455
456 fn total_field_length(&self, field: &str) -> StorageBackendResult<u64>;
457
458 fn field_doc_count(&self, field: &str) -> StorageBackendResult<u64> {
460 self.doc_length_count(Some(field))
461 }
462
463 fn field_stats(&self, field: &str) -> StorageBackendResult<IndexStats> {
469 let mut stats = self.stats()?;
470 let field_docs = self.field_doc_count(field)?;
471 stats.total_docs = field_docs;
472 stats.avg_doc_length = if field_docs > 0 {
473 self.total_field_length(field)? as f64 / field_docs as f64
474 } else {
475 0.0
476 };
477 Ok(stats)
478 }
479
480 fn field_stats_scalar(&self, field: &str) -> StorageBackendResult<IndexStats> {
488 let mut stats = IndexStats::default();
489 let field_docs = self.field_doc_count(field)?;
490 stats.total_docs = field_docs;
491 stats.avg_doc_length = if field_docs > 0 {
492 self.total_field_length(field)? as f64 / field_docs as f64
493 } else {
494 0.0
495 };
496 Ok(stats)
497 }
498
499 fn field_stats_scalar_budgeted(
501 &self,
502 _field: &str,
503 control: &StorageReadControl,
504 ) -> StorageBackendResult<IndexStats> {
505 control.check()?;
506 Err(StorageBackendError::Other(
507 "controlled field statistics are not supported by this backend".into(),
508 ))
509 }
510
511 fn vocabulary_terms(&self, _field: &str) -> StorageBackendResult<Vec<String>> {
517 Ok(Vec::new())
518 }
519
520 fn stats(&self) -> StorageBackendResult<IndexStats>;
523
524 fn posting_count(&self, _field: Option<&str>) -> StorageBackendResult<u64> {
527 Ok(0)
528 }
529
530 fn doc_length_count(&self, _field: Option<&str>) -> StorageBackendResult<u64> {
533 Ok(0)
534 }
535
536 fn term_count(&self, _field: Option<&str>) -> StorageBackendResult<u64> {
539 Ok(0)
540 }
541
542 fn snapshot(&self) -> StorageBackendResult<Arc<dyn InvertedIndex>>;
544
545 fn writable_snapshot(&self) -> StorageBackendResult<Box<dyn InvertedIndex>> {
548 Err(StorageBackendError::Other(
549 "writable inverted-index snapshots are not supported by this backend".into(),
550 ))
551 }
552
553 fn field_names(&self) -> StorageBackendResult<Vec<FieldName>> {
559 Ok(Vec::new())
560 }
561
562 fn get_posting_list_any_field(&self, term: &str) -> StorageBackendResult<PostingList> {
566 let mut result = PostingList::new();
567 for field in self.field_names()? {
568 let pl = self.get_posting_list(&field, term)?;
569 result = result.merge_union(&pl);
570 }
571 Ok(result)
572 }
573
574 fn doc_freq_any_field(&self, term: &str) -> StorageBackendResult<u64> {
576 let mut total = 0_u64;
577 for field in self.field_names()? {
578 total = total
579 .checked_add(self.doc_freq(&field, term)?)
580 .ok_or_else(|| counter_error("document frequency"))?;
581 }
582 Ok(total)
583 }
584
585 fn get_total_doc_length(&self, doc_id: DocId) -> StorageBackendResult<u64> {
587 let mut total = 0_u64;
588 for field in self.field_names()? {
589 total = total
590 .checked_add(self.get_doc_length(doc_id, &field)?)
591 .ok_or_else(|| counter_error("document length"))?;
592 }
593 Ok(total)
594 }
595
596 fn get_doc_lengths_bulk(
598 &self,
599 doc_ids: &[DocId],
600 field: &str,
601 ) -> StorageBackendResult<BTreeMap<DocId, u64>> {
602 let mut out = BTreeMap::new();
603 for doc_id in doc_ids {
604 out.insert(*doc_id, self.get_doc_length(*doc_id, field)?);
605 }
606 Ok(out)
607 }
608
609 fn get_term_freqs_bulk(
611 &self,
612 doc_ids: &[DocId],
613 field: &str,
614 term: &str,
615 ) -> StorageBackendResult<BTreeMap<DocId, u64>> {
616 let mut out = BTreeMap::new();
617 for doc_id in doc_ids {
618 out.insert(*doc_id, self.get_term_freq(*doc_id, field, term)?);
619 }
620 Ok(out)
621 }
622
623 fn get_scoring_inputs_bulk(
628 &self,
629 doc_ids: &[DocId],
630 field: &str,
631 terms: &[String],
632 ) -> StorageBackendResult<Vec<(u64, Vec<u64>)>> {
633 let mut out = Vec::with_capacity(doc_ids.len());
634 for doc_id in doc_ids {
635 let mut term_freqs = Vec::with_capacity(terms.len());
636 for term in terms {
637 term_freqs.push(self.get_term_freq(*doc_id, field, term)?);
638 }
639 out.push((self.get_doc_length(*doc_id, field)?, term_freqs));
640 }
641 Ok(out)
642 }
643
644 fn get_total_term_freq(&self, doc_id: DocId, term: &str) -> StorageBackendResult<u64> {
647 let mut total = 0_u64;
648 for field in self.field_names()? {
649 total = total
650 .checked_add(self.get_term_freq(doc_id, &field, term)?)
651 .ok_or_else(|| counter_error("term frequency"))?;
652 }
653 Ok(total)
654 }
655
656 fn set_field_analyzer(
661 &mut self,
662 _field: &str,
663 _analyzer: Analyzer,
664 _phase: AnalyzerPhase,
665 ) -> Result<(), String> {
666 Err("set_field_analyzer not supported by this InvertedIndex backend".into())
667 }
668
669 fn remove_field_analyzers(&mut self, _field: &str) -> Result<(), String> {
674 Err("remove_field_analyzers not supported by this InvertedIndex backend".into())
675 }
676
677 fn get_field_analyzer(&self, _field: &str) -> Analyzer {
680 self.analyzer().clone()
681 }
682
683 fn get_search_analyzer(&self, field: &str) -> Analyzer {
685 self.get_field_analyzer(field)
686 }
687
688 fn index_analyzer_revision(
690 &self,
691 field: &str,
692 ) -> StorageBackendResult<Arc<uqa_analysis::CompiledAnalyzer>> {
693 Ok(self.get_field_analyzer(field).compile()?)
694 }
695
696 fn search_analyzer_revision(
698 &self,
699 field: &str,
700 ) -> StorageBackendResult<Arc<uqa_analysis::CompiledAnalyzer>> {
701 Ok(self.get_search_analyzer(field).compile()?)
702 }
703
704 fn set_field_analyzer_revision(
706 &mut self,
707 _field: &str,
708 _revision: Arc<uqa_analysis::CompiledAnalyzer>,
709 _phase: AnalyzerPhase,
710 ) -> Result<(), String> {
711 Err("immutable analyzer revisions are not supported by this backend".into())
712 }
713
714 fn set_field_analyzer_revisions(
716 &mut self,
717 _field: &str,
718 _index: Arc<uqa_analysis::CompiledAnalyzer>,
719 _search: Arc<uqa_analysis::CompiledAnalyzer>,
720 ) -> Result<(), String> {
721 Err("atomic analyzer revision pairs are not supported by this backend".into())
722 }
723
724 fn rebuild_with_analyzer_revision(
726 &mut self,
727 _field: &str,
728 _revision: Arc<uqa_analysis::CompiledAnalyzer>,
729 _phase: AnalyzerPhase,
730 _documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
731 ) -> StorageBackendResult<()> {
732 Err(StorageBackendError::Other(
733 "atomic analyzer revision rebuild is not supported by this backend".into(),
734 ))
735 }
736
737 fn try_rebuild_documents_cancellable(
739 &mut self,
740 _documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
741 cancellation: &uqa_core::CancellationToken,
742 ) -> StorageBackendResult<()> {
743 cancellation.check()?;
744 Err(StorageBackendError::Other(
745 "cancellable atomic index rebuild is not supported by this backend".into(),
746 ))
747 }
748
749 fn rebuild_with_analyzer_revision_cancellable(
751 &mut self,
752 _field: &str,
753 _revision: Arc<uqa_analysis::CompiledAnalyzer>,
754 _phase: AnalyzerPhase,
755 _documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
756 cancellation: &uqa_core::CancellationToken,
757 ) -> StorageBackendResult<()> {
758 cancellation.check()?;
759 Err(StorageBackendError::Other(
760 "cancellable atomic analyzer revision rebuild is not supported by this backend".into(),
761 ))
762 }
763}