Skip to main content

uqa_storage/read_only_snapshot/
inverted.rs

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