Skip to main content

uqa_storage/key_value/inverted_index/
view.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! One reader owns an occurrence operation; retained indexes own their selected bytes.
8
9use super::{AnalyzerBindings, Arc, DocId, FieldName, InvertedIndex, KeyValueInvertedIndex};
10use crate::key_value::{index_view, KeyValueBatch, KeyValueRead, OccurrenceStorage};
11use crate::StorageBackendResult;
12use uqa_core::memory::BudgetedVec;
13
14type Entry = (BudgetedVec<u8>, BudgetedVec<u8>);
15
16#[derive(Clone)]
17pub(super) enum OccurrenceSource {
18    Live(Arc<dyn OccurrenceStorage>),
19    Retained(Arc<dyn KeyValueRead + Send + Sync>),
20}
21
22pub(super) struct OccurrenceRead<'a> {
23    pub(super) store: &'a dyn KeyValueRead,
24    pub(super) table: &'a str,
25    pub(super) bindings: &'a AnalyzerBindings,
26    pub(super) format_requires_rebuild: std::cell::Cell<Option<bool>>,
27}
28
29impl OccurrenceRead<'_> {
30    pub(super) fn scan_prefix(&self, prefix: &[u8]) -> StorageBackendResult<BudgetedVec<Entry>> {
31        let mut rows = BudgetedVec::new(self.store.control().memory());
32        append_rows(self.store, prefix, &mut rows)?;
33        Ok(rows)
34    }
35}
36
37fn append_rows(
38    read: &dyn KeyValueRead,
39    prefix: &[u8],
40    rows: &mut BudgetedVec<Entry>,
41) -> StorageBackendResult<()> {
42    read.visit_prefix(prefix, &mut |key, value| {
43        read.control().check()?;
44        rows.reserve(1)?;
45        let mut owned_key = BudgetedVec::new(read.control().memory());
46        owned_key.extend_from_slice(key)?;
47        let mut owned_value = BudgetedVec::new(read.control().memory());
48        owned_value.extend_from_slice(value)?;
49        rows.push((owned_key, owned_value))?;
50        Ok(())
51    })
52}
53
54impl KeyValueInvertedIndex {
55    pub(super) fn read<T>(
56        &self,
57        operation: impl FnOnce(OccurrenceRead<'_>) -> StorageBackendResult<T>,
58    ) -> StorageBackendResult<T> {
59        let evaluate = |store: &dyn KeyValueRead| {
60            operation(OccurrenceRead {
61                store,
62                table: &self.table,
63                bindings: &self.bindings,
64                format_requires_rebuild: std::cell::Cell::new(None),
65            })
66        };
67        match &self.source {
68            OccurrenceSource::Live(store) => {
69                index_view::read_scope(|operation| store.read(operation), evaluate)
70            }
71            OccurrenceSource::Retained(read) => {
72                read.control().check()?;
73                evaluate(read.as_ref())
74            }
75        }
76    }
77
78    pub(super) fn ensure_writable(&self) -> StorageBackendResult<()> {
79        match self.source {
80            OccurrenceSource::Live(_) => Ok(()),
81            OccurrenceSource::Retained(_) => {
82                Err(super::other_error("inverted-index snapshots are read-only"))
83            }
84        }
85    }
86
87    pub(super) fn mutate(
88        &self,
89        operation: impl FnOnce(&OccurrenceRead<'_>, &mut dyn KeyValueBatch) -> StorageBackendResult<()>,
90    ) -> StorageBackendResult<()> {
91        self.ensure_writable()?;
92        let OccurrenceSource::Live(store) = &self.source else {
93            unreachable!()
94        };
95        index_view::mutation_scope(
96            |operation| store.mutate(operation),
97            |read, batch| {
98                operation(
99                    &OccurrenceRead {
100                        store: read,
101                        table: &self.table,
102                        bindings: &self.bindings,
103                        format_requires_rebuild: std::cell::Cell::new(None),
104                    },
105                    batch,
106                )
107            },
108        )
109    }
110
111    pub(super) fn add_documents(
112        &self,
113        documents: Vec<(DocId, std::collections::BTreeMap<FieldName, String>)>,
114    ) -> StorageBackendResult<()> {
115        self.mutate(|view, batch| view.add_documents(batch, documents, None))
116    }
117
118    pub(super) fn rebuild_documents(
119        &self,
120        source: &mut dyn crate::inverted_index::TextIndexSource,
121    ) -> StorageBackendResult<()> {
122        self.rebuild_documents_inner(source, None)
123    }
124
125    /// Replace the index with the documents `source` reads, staged before the mutation opens: a source may read the provider's session, which the mutation holds.
126    pub(super) fn rebuild_documents_inner(
127        &self,
128        source: &mut dyn crate::inverted_index::TextIndexSource,
129        cancellation: Option<&uqa_core::CancellationToken>,
130    ) -> StorageBackendResult<()> {
131        self.ensure_writable()?;
132        let control = self.read(|view| Ok(view.store.control().clone()))?;
133        let staged = super::rebuild::stage_source(&self.bindings, &control, source, cancellation)?;
134        self.mutate(|view, batch| view.write_rebuild(batch, &staged, cancellation))
135    }
136
137    /// Capture a provider's selected occurrence view without first cloning its analyzer bindings or table metadata into an intermediate live index.
138    pub fn snapshot_from_read(
139        read: &dyn KeyValueRead,
140        table: &str,
141        bindings: &AnalyzerBindings,
142    ) -> StorageBackendResult<Arc<dyn InvertedIndex>> {
143        read.control().check()?;
144        let prefixes = super::format::retained_prefixes(table, read.control())?;
145        let borrowed: [&[u8]; 8] = std::array::from_fn(|slot| &prefixes[slot][..]);
146        let source = read.retain(&borrowed)?;
147        Self::snapshot_from_source(source, table, bindings)
148    }
149
150    pub(super) fn retained_snapshot(&self) -> StorageBackendResult<Arc<dyn InvertedIndex>> {
151        match &self.source {
152            OccurrenceSource::Retained(source) => {
153                Self::snapshot_from_source(Arc::clone(source), &self.table, &self.bindings)
154            }
155            OccurrenceSource::Live(_) => {
156                self.read(|view| Self::snapshot_from_read(view.store, view.table, view.bindings))
157            }
158        }
159    }
160
161    fn snapshot_from_source(
162        source: Arc<dyn KeyValueRead + Send + Sync>,
163        table: &str,
164        bindings: &AnalyzerBindings,
165    ) -> StorageBackendResult<Arc<dyn InvertedIndex>> {
166        use crate::ReadOnlySnapshot;
167        use uqa_core::memory::BudgetedString;
168
169        let control = source.control().clone();
170        control.check()?;
171        let bindings = bindings.retained(&control)?;
172        let mut name = BudgetedString::new(control.memory());
173        name.reserve(table.len())?;
174        for (offset, character) in table.chars().enumerate() {
175            if offset % 1024 == 0 {
176                control.check()?;
177            }
178            name.push(character)?;
179        }
180        let (table, mut memory) = name.into_parts();
181        memory.grow(size_of::<Self>())?;
182        memory.grow(size_of::<ReadOnlySnapshot<dyn InvertedIndex>>())?;
183        let snapshot: Arc<dyn InvertedIndex> = Arc::new(Self {
184            source: OccurrenceSource::Retained(source),
185            table,
186            bindings,
187        });
188        let snapshot = ReadOnlySnapshot::with_retention(snapshot, memory)?
189            .with_inverted_read_control(&control)?;
190        Ok(Arc::new(snapshot))
191    }
192}