Skip to main content

uqa_storage/key_value/inverted_index/
trait_impl.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Key/value index provider operations and retained revision installation.
8
9use super::{
10    Analyzer, AnalyzerPhase, Arc, BTreeMap, DocId, FieldName, IndexStats, IndexedFieldMetadata,
11    InvertedIndex, KeyValueInvertedIndex, OccurrencePosting, PostingCursor, PostingList,
12    StorageBackendResult, TokenTermKey,
13};
14
15impl InvertedIndex for KeyValueInvertedIndex {
16    fn persisted_block_max_scores_keys_bulk(
17        &self,
18        field: &str,
19        terms: &[TokenTermKey],
20        scorer_fingerprint: &str,
21    ) -> StorageBackendResult<Vec<Option<Vec<f64>>>> {
22        if scorer_fingerprint.is_empty() {
23            return Ok(vec![None; terms.len()]);
24        }
25        self.get_versioned_block_max_scores_keys_bulk(field, terms, scorer_fingerprint)
26    }
27
28    fn persisted_block_max_scores_bulk(
29        &self,
30        field: &str,
31        terms: &[String],
32        scorer_fingerprint: &str,
33    ) -> StorageBackendResult<Vec<Option<Vec<f64>>>> {
34        let terms = terms
35            .iter()
36            .map(|term| TokenTermKey::from_text(term))
37            .collect::<Vec<_>>();
38        self.persisted_block_max_scores_keys_bulk(field, &terms, scorer_fingerprint)
39    }
40
41    fn persisted_block_max_scores(
42        &self,
43        field: &str,
44        term: &str,
45        scorer_fingerprint: &str,
46    ) -> StorageBackendResult<Option<Vec<f64>>> {
47        Ok(self
48            .persisted_block_max_scores_keys_bulk(
49                field,
50                &[TokenTermKey::from_text(term)],
51                scorer_fingerprint,
52            )?
53            .pop()
54            .expect("one term"))
55    }
56
57    fn rebuild_persisted_block_max(
58        &mut self,
59        field: &str,
60        scorer: &dyn crate::block_max_index::BlockMaxScorer,
61        scorer_fingerprint: &str,
62    ) -> StorageBackendResult<bool> {
63        self.rebuild_block_max(field, scorer, scorer_fingerprint)
64    }
65
66    fn posting_read_cursor_key_budgeted<'a>(
67        &'a self,
68        field: &'a str,
69        term: &'a TokenTermKey,
70        control: &crate::read_control::StorageReadControl,
71    ) -> StorageBackendResult<crate::clustered_postings::BudgetedPostingReadCursor<'a>> {
72        control.check()?;
73        crate::clustered_postings::open_controlled_cursor(self.snapshot()?, field, term, control)
74    }
75
76    fn visit_score_clusters(
77        &self,
78        field: &str,
79        term: &TokenTermKey,
80        after: Option<u64>,
81        limit: usize,
82        control: &crate::read_control::StorageReadControl,
83        visit: &mut crate::clustered_postings::ScoreClusterVisitor<'_>,
84    ) -> StorageBackendResult<()> {
85        self.read(|view| view.visit_score_clusters(field, term, after, limit, control, visit))
86    }
87
88    fn get_occurrences_budgeted(
89        &self,
90        doc_id: DocId,
91        field: &str,
92        term: &TokenTermKey,
93        control: &crate::read_control::StorageReadControl,
94    ) -> StorageBackendResult<uqa_core::memory::Budgeted<Vec<uqa_core::TokenOccurrence>>> {
95        self.read(|view| view.get_occurrences_budgeted(doc_id, field, term, control))
96    }
97
98    fn field_stats_scalar_budgeted(
99        &self,
100        field: &str,
101        control: &crate::read_control::StorageReadControl,
102    ) -> StorageBackendResult<IndexStats> {
103        self.read(|view| view.field_stats_scalar_budgeted(field, control))
104    }
105
106    fn analyzer(&self) -> &Analyzer {
107        self.bindings.default_configuration()
108    }
109
110    fn default_analyzer_binding(
111        &self,
112    ) -> StorageBackendResult<crate::inverted_index::AnalyzerDefault> {
113        Ok(self.bindings.default_binding())
114    }
115
116    fn source_rebuild_required(&self) -> StorageBackendResult<bool> {
117        self.read(|view| view.source_rebuild_required())
118    }
119
120    fn add_document(
121        &mut self,
122        doc_id: DocId,
123        fields: BTreeMap<FieldName, String>,
124    ) -> StorageBackendResult<()> {
125        self.add_documents(vec![(doc_id, fields)])
126    }
127
128    fn try_add_documents(
129        &mut self,
130        documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
131    ) -> StorageBackendResult<()> {
132        self.add_documents(documents)
133    }
134
135    fn try_add_documents_observed(
136        &mut self,
137        documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
138        visit: &mut crate::inverted_index::InvertedIndexChangeVisitor<'_>,
139    ) -> StorageBackendResult<()> {
140        self.mutate(|view, batch| view.add_documents(batch, documents, Some(visit)))
141    }
142
143    fn remove_document(&mut self, doc_id: DocId) -> StorageBackendResult<()> {
144        self.add_documents(vec![(doc_id, BTreeMap::new())])
145    }
146
147    fn try_rebuild_documents(
148        &mut self,
149        documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
150    ) -> StorageBackendResult<()> {
151        self.rebuild_documents(documents)
152    }
153
154    fn try_rebuild_documents_cancellable(
155        &mut self,
156        documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
157        cancellation: &uqa_core::CancellationToken,
158    ) -> StorageBackendResult<()> {
159        self.rebuild_documents_inner(documents, Some(cancellation))
160    }
161
162    fn clear(&mut self) -> StorageBackendResult<()> {
163        self.mutate(|view, batch| view.clear_index_batch(batch))
164    }
165
166    fn get_posting_list(&self, field: &str, term: &str) -> StorageBackendResult<PostingList> {
167        self.read(|view| view.get_posting_list(field, term))
168    }
169
170    fn get_posting_list_key(
171        &self,
172        field: &str,
173        term: &TokenTermKey,
174    ) -> StorageBackendResult<PostingList> {
175        self.read(|view| view.get_posting_list_key(field, term))
176    }
177
178    fn posting_cursor(
179        &self,
180        field: &str,
181        term: &str,
182    ) -> StorageBackendResult<Box<dyn PostingCursor>> {
183        self.read(|view| view.posting_cursor(field, term))
184    }
185
186    fn posting_cursor_key(
187        &self,
188        field: &str,
189        term: &TokenTermKey,
190    ) -> StorageBackendResult<Box<dyn PostingCursor>> {
191        self.read(|view| view.posting_cursor_key(field, term))
192    }
193
194    fn get_occurrence_postings(
195        &self,
196        field: &str,
197        term: &TokenTermKey,
198    ) -> StorageBackendResult<Vec<OccurrencePosting>> {
199        self.read(|view| view.get_occurrence_postings(field, term))
200    }
201
202    fn get_occurrences(
203        &self,
204        doc_id: DocId,
205        field: &str,
206        term: &TokenTermKey,
207    ) -> StorageBackendResult<Vec<uqa_core::TokenOccurrence>> {
208        self.read(|view| view.get_occurrences(doc_id, field, term))
209    }
210
211    fn indexed_field_metadata(
212        &self,
213        doc_id: DocId,
214        field: &str,
215    ) -> StorageBackendResult<Option<IndexedFieldMetadata>> {
216        self.read(|view| view.indexed_field_metadata(doc_id, field))
217    }
218
219    fn for_each_term_freq(
220        &self,
221        field: &str,
222        term: &str,
223        visit: &mut dyn FnMut(DocId, u64),
224    ) -> StorageBackendResult<()> {
225        let mut cursor = self.posting_cursor(field, term)?;
226        while let Some(entry) = cursor.current() {
227            visit(entry.doc_id, entry.term_freq);
228            cursor.advance()?;
229        }
230        Ok(())
231    }
232
233    fn doc_freq(&self, field: &str, term: &str) -> StorageBackendResult<u64> {
234        self.read(|view| view.doc_freq(field, term))
235    }
236
237    fn doc_freq_key(&self, field: &str, term: &TokenTermKey) -> StorageBackendResult<u64> {
238        self.read(|view| view.doc_freq_key(field, term))
239    }
240
241    fn get_doc_length(&self, doc_id: DocId, field: &str) -> StorageBackendResult<u64> {
242        self.read(|view| view.get_doc_length(doc_id, field))
243    }
244
245    fn get_scoring_inputs_bulk(
246        &self,
247        doc_ids: &[DocId],
248        field: &str,
249        terms: &[String],
250    ) -> StorageBackendResult<Vec<(u64, Vec<u64>)>> {
251        self.read(|view| view.get_scoring_inputs_bulk(doc_ids, field, terms))
252    }
253
254    fn get_scoring_inputs_keys_bulk(
255        &self,
256        doc_ids: &[DocId],
257        field: &str,
258        terms: &[TokenTermKey],
259    ) -> StorageBackendResult<Vec<(u64, Vec<u64>)>> {
260        self.read(|view| view.get_scoring_inputs_keys_bulk(doc_ids, field, terms))
261    }
262
263    fn get_term_freq(&self, doc_id: DocId, field: &str, term: &str) -> StorageBackendResult<u64> {
264        self.read(|view| view.get_term_freq(doc_id, field, term))
265    }
266
267    fn get_term_freq_key(
268        &self,
269        doc_id: DocId,
270        field: &str,
271        term: &TokenTermKey,
272    ) -> StorageBackendResult<u64> {
273        self.read(|view| view.get_term_freq_key(doc_id, field, term))
274    }
275
276    fn doc_count(&self) -> StorageBackendResult<u64> {
277        self.read(|view| view.doc_count())
278    }
279
280    fn total_field_length(&self, field: &str) -> StorageBackendResult<u64> {
281        self.read(|view| view.total_field_length(field))
282    }
283
284    fn field_doc_count(&self, field: &str) -> StorageBackendResult<u64> {
285        self.read(|view| view.field_doc_count(field))
286    }
287
288    fn vocabulary_terms(&self, field: &str) -> StorageBackendResult<Vec<String>> {
289        self.read(|view| view.vocabulary_terms(field))
290    }
291
292    fn vocabulary_keys(&self, field: &str) -> StorageBackendResult<Vec<TokenTermKey>> {
293        self.read(|view| view.vocabulary_keys(field))
294    }
295
296    fn stats(&self) -> StorageBackendResult<IndexStats> {
297        self.read(|view| view.stats())
298    }
299
300    fn posting_count(&self, field: Option<&str>) -> StorageBackendResult<u64> {
301        self.read(|view| view.posting_count(field))
302    }
303
304    fn doc_length_count(&self, field: Option<&str>) -> StorageBackendResult<u64> {
305        self.read(|view| view.doc_length_count(field))
306    }
307
308    fn term_count(&self, field: Option<&str>) -> StorageBackendResult<u64> {
309        self.read(|view| view.term_count(field))
310    }
311
312    fn snapshot(&self) -> StorageBackendResult<Arc<dyn InvertedIndex>> {
313        self.retained_snapshot()
314    }
315
316    fn field_names(&self) -> StorageBackendResult<Vec<FieldName>> {
317        self.read(|view| view.field_names())
318    }
319
320    fn set_field_analyzer(
321        &mut self,
322        field: &str,
323        analyzer: Analyzer,
324        phase: AnalyzerPhase,
325    ) -> Result<(), String> {
326        self.ensure_writable().map_err(|error| error.to_string())?;
327        let mut candidate = self.bindings.clone();
328        candidate
329            .bind(field, &analyzer, phase)
330            .map_err(|error| error.to_string())?;
331        self.read(|view| view.validate_index_revision_change(field, &candidate))
332            .map_err(|error| error.to_string())?;
333        self.bindings = candidate;
334        Ok(())
335    }
336
337    fn remove_field_analyzers(&mut self, field: &str) -> Result<(), String> {
338        self.ensure_writable().map_err(|error| error.to_string())?;
339        let mut candidate = self.bindings.clone();
340        candidate.remove(field);
341        self.read(|view| view.validate_index_revision_change(field, &candidate))
342            .map_err(|error| error.to_string())?;
343        self.bindings = candidate;
344        Ok(())
345    }
346
347    fn get_field_analyzer(&self, field: &str) -> Analyzer {
348        self.bindings.index_configuration(field).clone()
349    }
350    fn get_search_analyzer(&self, field: &str) -> Analyzer {
351        self.bindings.search_configuration(field).clone()
352    }
353    fn index_analyzer_revision(
354        &self,
355        field: &str,
356    ) -> StorageBackendResult<Arc<uqa_analysis::CompiledAnalyzer>> {
357        Ok(self.bindings.index_revision(field)?)
358    }
359    fn search_analyzer_revision(
360        &self,
361        field: &str,
362    ) -> StorageBackendResult<Arc<uqa_analysis::CompiledAnalyzer>> {
363        Ok(self.bindings.search_revision(field)?)
364    }
365
366    fn set_field_analyzer_revision(
367        &mut self,
368        field: &str,
369        revision: Arc<uqa_analysis::CompiledAnalyzer>,
370        phase: AnalyzerPhase,
371    ) -> Result<(), String> {
372        self.ensure_writable().map_err(|error| error.to_string())?;
373        let mut candidate = self.bindings.clone();
374        candidate
375            .bind_revision(field, revision, phase)
376            .map_err(|error| error.to_string())?;
377        self.read(|view| view.validate_index_revision_change(field, &candidate))
378            .map_err(|error| error.to_string())?;
379        self.bindings = candidate;
380        Ok(())
381    }
382
383    fn set_field_analyzer_revisions(
384        &mut self,
385        field: &str,
386        index: Arc<uqa_analysis::CompiledAnalyzer>,
387        search: Arc<uqa_analysis::CompiledAnalyzer>,
388    ) -> Result<(), String> {
389        self.ensure_writable().map_err(|error| error.to_string())?;
390        let mut candidate = self.bindings.clone();
391        candidate
392            .bind_revisions(field, index, search)
393            .map_err(|error| error.to_string())?;
394        self.read(|view| view.validate_index_revision_change(field, &candidate))
395            .map_err(|error| error.to_string())?;
396        self.bindings = candidate;
397        Ok(())
398    }
399
400    fn rebuild_with_analyzer_revision(
401        &mut self,
402        field: &str,
403        revision: Arc<uqa_analysis::CompiledAnalyzer>,
404        phase: AnalyzerPhase,
405        documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
406    ) -> StorageBackendResult<()> {
407        self.ensure_writable()?;
408        let mut replacement = self.clone();
409        replacement.bindings.bind_revision(field, revision, phase)?;
410        replacement.rebuild_documents(documents)?;
411        *self = replacement;
412        Ok(())
413    }
414
415    fn rebuild_with_analyzer_revision_cancellable(
416        &mut self,
417        field: &str,
418        revision: Arc<uqa_analysis::CompiledAnalyzer>,
419        phase: AnalyzerPhase,
420        documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
421        cancellation: &uqa_core::CancellationToken,
422    ) -> StorageBackendResult<()> {
423        cancellation.check()?;
424        self.ensure_writable()?;
425        let mut replacement = self.clone();
426        replacement.bindings.bind_revision(field, revision, phase)?;
427        replacement.rebuild_documents_inner(documents, Some(cancellation))?;
428        *self = replacement;
429        Ok(())
430    }
431
432    fn get_posting_lists_bulk(
433        &self,
434        field: &str,
435        terms: &[String],
436    ) -> StorageBackendResult<Vec<PostingList>> {
437        self.read(|view| {
438            terms
439                .iter()
440                .map(|term| view.get_posting_list(field, term))
441                .collect()
442        })
443    }
444
445    fn posting_cursors_bulk(
446        &self,
447        field: &str,
448        terms: &[String],
449    ) -> StorageBackendResult<Vec<Box<dyn PostingCursor>>> {
450        self.read(|view| {
451            terms
452                .iter()
453                .map(|term| view.posting_cursor(field, term))
454                .collect()
455        })
456    }
457
458    fn posting_cursors_keys_bulk(
459        &self,
460        field: &str,
461        terms: &[TokenTermKey],
462    ) -> StorageBackendResult<Vec<Box<dyn PostingCursor>>> {
463        self.read(|view| {
464            terms
465                .iter()
466                .map(|term| view.posting_cursor_key(field, term))
467                .collect()
468        })
469    }
470
471    fn get_posting_lists_keys_bulk(
472        &self,
473        field: &str,
474        terms: &[TokenTermKey],
475    ) -> StorageBackendResult<Vec<PostingList>> {
476        self.read(|view| {
477            terms
478                .iter()
479                .map(|term| view.get_posting_list_key(field, term))
480                .collect()
481        })
482    }
483
484    fn field_stats(&self, field: &str) -> StorageBackendResult<IndexStats> {
485        self.read(|view| {
486            let mut stats = view.stats()?;
487            let field_docs = view.field_doc_count(field)?;
488            stats.total_docs = field_docs;
489            stats.avg_doc_length = if field_docs > 0 {
490                view.total_field_length(field)? as f64 / field_docs as f64
491            } else {
492                0.0
493            };
494            Ok(stats)
495        })
496    }
497
498    fn field_stats_scalar(&self, field: &str) -> StorageBackendResult<IndexStats> {
499        self.read(|view| {
500            let mut stats = IndexStats::default();
501            let field_docs = view.field_doc_count(field)?;
502            stats.total_docs = field_docs;
503            stats.avg_doc_length = if field_docs > 0 {
504                view.total_field_length(field)? as f64 / field_docs as f64
505            } else {
506                0.0
507            };
508            Ok(stats)
509        })
510    }
511
512    fn get_posting_list_any_field(&self, term: &str) -> StorageBackendResult<PostingList> {
513        self.read(|view| {
514            let mut result = PostingList::new();
515            for field in view.field_names()? {
516                let pl = view.get_posting_list(&field, term)?;
517                result = result.merge_union(&pl);
518            }
519            Ok(result)
520        })
521    }
522
523    fn doc_freq_any_field(&self, term: &str) -> StorageBackendResult<u64> {
524        self.read(|view| {
525            let mut total = 0_u64;
526            for field in view.field_names()? {
527                total = total
528                    .checked_add(view.doc_freq(&field, term)?)
529                    .ok_or_else(|| super::other_error("document frequency overflow"))?;
530            }
531            Ok(total)
532        })
533    }
534
535    fn get_total_doc_length(&self, doc_id: DocId) -> StorageBackendResult<u64> {
536        self.read(|view| {
537            let mut total = 0_u64;
538            for field in view.field_names()? {
539                total = total
540                    .checked_add(view.get_doc_length(doc_id, &field)?)
541                    .ok_or_else(|| super::other_error("document length overflow"))?;
542            }
543            Ok(total)
544        })
545    }
546
547    fn get_total_term_freq(&self, doc_id: DocId, term: &str) -> StorageBackendResult<u64> {
548        self.read(|view| {
549            let mut total = 0_u64;
550            for field in view.field_names()? {
551                total = total
552                    .checked_add(view.get_term_freq(doc_id, &field, term)?)
553                    .ok_or_else(|| super::other_error("term frequency overflow"))?;
554            }
555            Ok(total)
556        })
557    }
558
559    fn get_doc_lengths_bulk(
560        &self,
561        doc_ids: &[DocId],
562        field: &str,
563    ) -> StorageBackendResult<BTreeMap<DocId, u64>> {
564        self.read(|view| {
565            let mut out = BTreeMap::new();
566            for doc_id in doc_ids {
567                out.insert(*doc_id, view.get_doc_length(*doc_id, field)?);
568            }
569            Ok(out)
570        })
571    }
572
573    fn get_term_freqs_bulk(
574        &self,
575        doc_ids: &[DocId],
576        field: &str,
577        term: &str,
578    ) -> StorageBackendResult<BTreeMap<DocId, u64>> {
579        self.read(|view| {
580            let mut out = BTreeMap::new();
581            for doc_id in doc_ids {
582                out.insert(*doc_id, view.get_term_freq(*doc_id, field, term)?);
583            }
584            Ok(out)
585        })
586    }
587}