1use super::{
10 keys, other_error, BTreeMap, DocId, KeyValueBatch, KeyValueInvertedIndex, OccurrenceRead,
11 StorageBackendResult, TokenTermKey,
12};
13use crate::block_max_index::{BlockMaxIndex, BlockMaxScorer, DEFAULT_BLOCK_SIZE};
14use crate::key_value::{
15 codec::{decode_u64_value, u64_value},
16 occurrence_format::BlockMaxValue,
17};
18
19impl KeyValueInvertedIndex {
20 pub fn flush_skip_pointers(&self) -> StorageBackendResult<()> {
21 self.mutate(|view, batch| view.rebuild_skips(batch))
22 }
23
24 pub fn skip_to(
25 &self,
26 field: &str,
27 term: &str,
28 target: DocId,
29 ) -> StorageBackendResult<(DocId, usize)> {
30 self.read(|view| view.skip_to(field, &TokenTermKey::from_text(term), target))
31 }
32
33 pub fn build_block_max_scores_key<S: BlockMaxScorer + ?Sized>(
34 &self,
35 field: &str,
36 term: &TokenTermKey,
37 scorer: &S,
38 fingerprint: &str,
39 ) -> StorageBackendResult<()> {
40 self.mutate(|view, batch| {
41 view.build_bounds(batch, field, Some(term), scorer, fingerprint, false)
42 })
43 }
44
45 pub fn build_all_block_max_scores<S: BlockMaxScorer + ?Sized>(
46 &self,
47 field: &str,
48 scorer: &S,
49 ) -> StorageBackendResult<()> {
50 self.mutate(|view, batch| view.build_bounds(batch, field, None, scorer, "", false))
51 }
52
53 pub(super) fn rebuild_block_max(
54 &self,
55 field: &str,
56 scorer: &dyn BlockMaxScorer,
57 fingerprint: &str,
58 ) -> StorageBackendResult<bool> {
59 if fingerprint.is_empty() {
60 return Err(other_error(
61 "persisted block-max scorer fingerprint must not be empty",
62 ));
63 }
64 self.mutate(|view, batch| {
65 view.build_bounds(batch, field, None, scorer, fingerprint, true)
66 })?;
67 Ok(true)
68 }
69
70 pub fn get_block_max_score(
71 &self,
72 field: &str,
73 term: &str,
74 block: usize,
75 ) -> StorageBackendResult<f64> {
76 self.read(|view| {
77 view.require_graph_format()?;
78 let block = u64::try_from(block).map_err(|_| other_error("block index exceeds u64"))?;
79 let key = keys::cluster_key(
80 view.table,
81 keys::BLOCK_MAX,
82 field,
83 &TokenTermKey::from_text(term),
84 block,
85 )?;
86 view.store
87 .get(&key)?
88 .map_or(Ok(0.0), |bytes| Ok(BlockMaxValue::decode(&bytes)?.score))
89 })
90 }
91
92 pub fn get_all_block_max_scores_key(
93 &self,
94 field: &str,
95 term: &TokenTermKey,
96 ) -> StorageBackendResult<Vec<f64>> {
97 self.read(|view| Ok(view.bounds(field, term, None)?.unwrap_or_default()))
98 }
99
100 pub fn get_versioned_block_max_scores_keys_bulk(
101 &self,
102 field: &str,
103 terms: &[TokenTermKey],
104 fingerprint: &str,
105 ) -> StorageBackendResult<Vec<Option<Vec<f64>>>> {
106 self.read(|view| {
107 terms
108 .iter()
109 .map(|term| view.bounds(field, term, Some(fingerprint)))
110 .collect()
111 })
112 }
113
114 pub fn load_block_max_into(&self, target: &mut BlockMaxIndex) -> StorageBackendResult<()> {
115 self.read(|view| {
116 view.require_graph_format()?;
117 let prefix = keys::kind_prefix(view.table, keys::BLOCK_MAX)?;
118 let mut by_term = BTreeMap::<(String, TokenTermKey), Vec<f64>>::new();
119 view.store.visit_prefix(&prefix, &mut |key, bytes| {
120 view.store.control().check()?;
121 let (field, term, ordinal) = keys::read_cluster(key, keys::BLOCK_MAX)?;
122 let scores = by_term.entry((field, term)).or_default();
123 if ordinal != scores.len() as u64 {
124 return Err(other_error("non-contiguous occurrence block-max bounds"));
125 }
126 scores.push(BlockMaxValue::decode(bytes)?.score);
127 Ok(())
128 })?;
129 for ((field, term), scores) in by_term {
130 target.set_block_maxes_key(view.table, &field, &term, scores)?;
131 }
132 Ok(())
133 })
134 }
135}
136
137impl OccurrenceRead<'_> {
138 pub(super) fn invalidate_accelerators(
139 &self,
140 batch: &mut dyn KeyValueBatch,
141 ) -> StorageBackendResult<()> {
142 for kind in [keys::SKIP, keys::BLOCK_MAX] {
143 batch.invalidate_occurrence_prefix(&keys::kind_prefix(self.table, kind)?)?;
144 }
145 Ok(())
146 }
147
148 fn fence_accelerators(&self, batch: &mut dyn KeyValueBatch) -> StorageBackendResult<()> {
149 batch.put(
151 &keys::kind_prefix(self.table, keys::FORMAT)?,
152 keys::FORMAT_NAME,
153 )
154 }
155
156 fn rebuild_skips(&self, batch: &mut dyn KeyValueBatch) -> StorageBackendResult<()> {
157 self.require_graph_format()?;
158 batch.delete_prefix(&keys::kind_prefix(self.table, keys::SKIP)?)?;
159 let fields = self.field_names()?;
160 for field in &fields {
161 for term in self.vocabulary_keys(field)? {
162 let mut cursor = self.posting_cursor_key(field, &term)?;
163 while let Some(entry) = cursor.current() {
164 self.store.control().check()?;
165 let offset = cursor.ordinal();
166 if offset % DEFAULT_BLOCK_SIZE as u64 == 0 {
167 batch.put(
168 &keys::cluster_key(self.table, keys::SKIP, field, &term, entry.doc_id)?,
169 &u64_value(offset),
170 )?;
171 }
172 cursor.advance()?;
173 }
174 }
175 }
176 if !fields.is_empty() {
177 self.fence_accelerators(batch)?;
178 }
179 Ok(())
180 }
181
182 fn skip_to(
183 &self,
184 field: &str,
185 term: &TokenTermKey,
186 target: DocId,
187 ) -> StorageBackendResult<(DocId, usize)> {
188 self.require_graph_format()?;
189 let prefix = keys::term_prefix(self.table, keys::SKIP, field, term)?;
190 let mut found = (0, 0);
191 self.store.visit_prefix(&prefix, &mut |key, bytes| {
192 self.store.control().check()?;
193 let (_, _, doc_id) = keys::read_cluster(key, keys::SKIP)?;
194 let offset = usize::try_from(decode_u64_value(bytes)?)
195 .map_err(|_| other_error("skip offset exceeds usize"))?;
196 if doc_id <= target {
197 found = (doc_id, offset);
198 }
199 Ok(())
200 })?;
201 Ok(found)
202 }
203
204 fn build_bounds<S: BlockMaxScorer + ?Sized>(
205 &self,
206 batch: &mut dyn KeyValueBatch,
207 field: &str,
208 term: Option<&TokenTermKey>,
209 scorer: &S,
210 fingerprint: &str,
211 replace: bool,
212 ) -> StorageBackendResult<()> {
213 self.require_graph_format()?;
214 if replace {
215 batch.delete_prefix(&keys::field_prefix(self.table, keys::BLOCK_MAX, field)?)?;
216 }
217 if self.field_doc_count(field)? == 0 {
218 return Ok(());
219 }
220 if let Some(term) = term {
221 self.build_term_bounds(batch, field, term, scorer, fingerprint)?;
222 } else {
223 for term in self.vocabulary_keys(field)? {
224 self.build_term_bounds(batch, field, &term, scorer, fingerprint)?;
225 }
226 }
227 self.fence_accelerators(batch)
228 }
229
230 fn build_term_bounds<S: BlockMaxScorer + ?Sized>(
231 &self,
232 batch: &mut dyn KeyValueBatch,
233 field: &str,
234 term: &TokenTermKey,
235 scorer: &S,
236 fingerprint: &str,
237 ) -> StorageBackendResult<()> {
238 batch.delete_prefix(&keys::term_prefix(
239 self.table,
240 keys::BLOCK_MAX,
241 field,
242 term,
243 )?)?;
244 let mut cursor = self.posting_cursor_key(field, term)?;
245 let mut count = 0usize;
246 let mut block = 0u64;
247 let mut maximum = 0.0_f64;
248 while let Some(entry) = cursor.current() {
249 self.store.control().check()?;
250 let score = scorer.score(entry.term_freq, entry.doc_length, cursor.doc_freq());
251 crate::block_max_index::validate_score(score)?;
252 maximum = maximum.max(score);
253 count += 1;
254 if count == DEFAULT_BLOCK_SIZE {
255 self.put_bound(batch, field, term, block, maximum, fingerprint)?;
256 block = block
257 .checked_add(1)
258 .ok_or_else(|| other_error("block index overflow"))?;
259 count = 0;
260 maximum = 0.0;
261 }
262 cursor.advance()?;
263 }
264 if count != 0 {
265 self.put_bound(batch, field, term, block, maximum, fingerprint)?;
266 }
267 Ok(())
268 }
269
270 fn put_bound(
271 &self,
272 batch: &mut dyn KeyValueBatch,
273 field: &str,
274 term: &TokenTermKey,
275 block: u64,
276 score: f64,
277 fingerprint: &str,
278 ) -> StorageBackendResult<()> {
279 let value = BlockMaxValue { score, fingerprint }.encode(self.store.control())?;
280 batch.put(
281 &keys::cluster_key(self.table, keys::BLOCK_MAX, field, term, block)?,
282 &value,
283 )
284 }
285
286 fn bounds(
287 &self,
288 field: &str,
289 term: &TokenTermKey,
290 fingerprint: Option<&str>,
291 ) -> StorageBackendResult<Option<Vec<f64>>> {
292 self.require_graph_format()?;
293 let prefix = keys::term_prefix(self.table, keys::BLOCK_MAX, field, term)?;
294 let mut scores = Vec::new();
295 let mut matches = true;
296 self.store.visit_prefix(&prefix, &mut |key, bytes| {
297 self.store.control().check()?;
298 let (_, _, block) = keys::read_cluster(key, keys::BLOCK_MAX)?;
299 if block != scores.len() as u64 {
300 return Err(other_error("non-contiguous occurrence block-max bounds"));
301 }
302 let bound = BlockMaxValue::decode(bytes)?;
303 matches &= fingerprint.is_none_or(|fingerprint| bound.fingerprint == fingerprint);
304 scores.push(bound.score);
305 Ok(())
306 })?;
307 Ok((matches && !scores.is_empty()).then_some(scores))
308 }
309}