1use super::{
10 clustered_result, decode_index_u64, encode_index_u64, invalidate_block_max_tables, params,
11 params_from_iter, posting_cursor_from_rows, quote_ident, Analyzer, AnalyzerPhase, Arc,
12 BTreeMap, BTreeSet, BlockMaxScorer, DocId, FieldName, IndexStats, InvertedIndex,
13 OptionalExtension, Payload, PostingCursor, PostingEntry, PostingList, SQLiteError,
14 SQLiteInvertedIndex, SqlValue, StorageBackendResult,
15};
16use crate::clustered_postings::{cluster_id, decode_all_scores, decode_cluster};
17
18impl InvertedIndex for SQLiteInvertedIndex {
19 fn analyzer(&self) -> &Analyzer {
20 &self.analyzer
21 }
22
23 fn add_document(
24 &mut self,
25 doc_id: DocId,
26 fields: BTreeMap<FieldName, String>,
27 ) -> StorageBackendResult<()> {
28 Ok(self.add_document_inner(doc_id, fields)?)
29 }
30
31 fn try_add_documents(
32 &mut self,
33 documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
34 ) -> StorageBackendResult<()> {
35 Ok(self.add_documents_inner(documents)?)
36 }
37
38 fn remove_document(&mut self, doc_id: DocId) -> StorageBackendResult<()> {
39 Ok(self.remove_document_inner(doc_id)?)
40 }
41
42 fn clear(&mut self) -> StorageBackendResult<()> {
43 self.conn.with_mut(|conn| {
44 let tx = conn.savepoint()?;
45 invalidate_block_max_tables(&tx, &self.table)?;
46 tx.execute(
47 "DELETE FROM _posting_clusters WHERE table_name = ?1",
48 params![self.table],
49 )?;
50 tx.execute(
51 "DELETE FROM _posting_documents WHERE table_name = ?1",
52 params![self.table],
53 )?;
54 tx.execute(
55 "DELETE FROM _doc_lengths WHERE table_name = ?1",
56 params![self.table],
57 )?;
58 tx.execute(
59 "DELETE FROM _field_stats WHERE table_name = ?1",
60 params![self.table],
61 )?;
62 tx.commit()?;
63 Ok(())
64 })?;
65 Ok(())
66 }
67
68 fn try_rebuild_documents(
69 &mut self,
70 documents: Vec<(DocId, BTreeMap<FieldName, String>)>,
71 ) -> StorageBackendResult<()> {
72 Ok(self.rebuild_documents_inner(documents)?)
73 }
74
75 fn get_posting_list(&self, field: &str, term: &str) -> StorageBackendResult<PostingList> {
76 Ok(self.conn.with(|c| {
77 let mut stmt = c.prepare(
78 "SELECT cluster_id, posting_count, score_blob, positions_blob
79 FROM _posting_clusters
80 WHERE table_name = ?1 AND field = ?2 AND term = ?3
81 ORDER BY cluster_id",
82 )?;
83 let rows = stmt.query_map(params![self.table, field, term], |r| {
84 Ok((
85 r.get::<_, i64>(0)?,
86 r.get::<_, i64>(1)?,
87 r.get::<_, Vec<u8>>(2)?,
88 r.get::<_, Vec<u8>>(3)?,
89 ))
90 })?;
91 let mut entries = Vec::new();
92 for row in rows {
93 let (stored_cluster, stored_count, score_blob, positions_blob) = row?;
94 let posting_cluster = decode_index_u64("posting cluster", stored_cluster)?;
95 let stored_count = decode_index_u64("posting count", stored_count)?;
96 let decoded = clustered_result(decode_cluster(
97 posting_cluster,
98 &score_blob,
99 &positions_blob,
100 ))?;
101 if stored_count != decoded.len() as u64 {
102 return Err(SQLiteError::StorageBackend(
103 "corrupt clustered posting: stored posting count mismatch".into(),
104 ));
105 }
106 entries.extend(decoded.into_iter().map(|entry| {
107 PostingEntry::new(
108 entry.doc_id,
109 Payload {
110 positions: entry.positions,
111 score: 0.0,
112 fields: BTreeMap::new(),
113 },
114 )
115 }));
116 }
117 Ok(PostingList::from_sorted_unchecked(entries))
118 })?)
119 }
120
121 fn get_posting_lists_bulk(
122 &self,
123 field: &str,
124 terms: &[String],
125 ) -> StorageBackendResult<Vec<PostingList>> {
126 if terms.is_empty() {
127 return Ok(Vec::new());
128 }
129 let unique_terms = terms
130 .iter()
131 .cloned()
132 .collect::<BTreeSet<_>>()
133 .into_iter()
134 .collect::<Vec<_>>();
135 let posting_entries = self.conn.with(|c| {
136 let mut by_term = BTreeMap::<String, Vec<PostingEntry>>::new();
137 for chunk in unique_terms.chunks(900) {
138 let placeholders = std::iter::repeat_n("?", chunk.len())
139 .collect::<Vec<_>>()
140 .join(", ");
141 let sql = format!(
142 "SELECT term, cluster_id, posting_count, score_blob, positions_blob
143 FROM _posting_clusters
144 WHERE table_name = ? AND field = ? AND term IN ({placeholders})
145 ORDER BY term, cluster_id"
146 );
147 let mut values = Vec::with_capacity(chunk.len() + 2);
148 values.push(SqlValue::Text(self.table.clone()));
149 values.push(SqlValue::Text(field.to_string()));
150 values.extend(chunk.iter().cloned().map(SqlValue::Text));
151 let mut stmt = c.prepare(&sql)?;
152 let rows = stmt.query_map(params_from_iter(values), |r| {
153 Ok((
154 r.get::<_, String>(0)?,
155 r.get::<_, i64>(1)?,
156 r.get::<_, i64>(2)?,
157 r.get::<_, Vec<u8>>(3)?,
158 r.get::<_, Vec<u8>>(4)?,
159 ))
160 })?;
161 for row in rows {
162 let (term, stored_cluster, stored_count, score_blob, positions_blob) = row?;
163 let posting_cluster = decode_index_u64("posting cluster", stored_cluster)?;
164 let stored_count = decode_index_u64("posting count", stored_count)?;
165 let decoded = clustered_result(decode_cluster(
166 posting_cluster,
167 &score_blob,
168 &positions_blob,
169 ))?;
170 if stored_count != decoded.len() as u64 {
171 return Err(SQLiteError::StorageBackend(
172 "corrupt clustered posting: stored posting count mismatch".into(),
173 ));
174 }
175 by_term
176 .entry(term)
177 .or_default()
178 .extend(decoded.into_iter().map(|entry| {
179 PostingEntry::new(
180 entry.doc_id,
181 Payload {
182 positions: entry.positions,
183 score: 0.0,
184 fields: BTreeMap::new(),
185 },
186 )
187 }));
188 }
189 }
190 Ok(by_term)
191 })?;
192
193 Ok(terms
194 .iter()
195 .map(|term| {
196 PostingList::from_sorted_unchecked(
197 posting_entries.get(term).cloned().unwrap_or_default(),
198 )
199 })
200 .collect())
201 }
202
203 fn posting_cursor(
204 &self,
205 field: &str,
206 term: &str,
207 ) -> StorageBackendResult<Box<dyn PostingCursor>> {
208 Ok(self.conn.with(|connection| {
209 let mut statement = connection.prepare_cached(
210 "SELECT cluster_id, posting_count, score_blob FROM _posting_clusters
211 WHERE table_name = ?1 AND field = ?2 AND term = ?3
212 ORDER BY cluster_id",
213 )?;
214 let rows = statement
215 .query_map(params![self.table, field, term], |row| {
216 Ok((
217 row.get::<_, i64>(0)?,
218 row.get::<_, i64>(1)?,
219 row.get::<_, Vec<u8>>(2)?,
220 ))
221 })?
222 .collect::<Result<Vec<_>, _>>()?;
223 posting_cursor_from_rows(rows)
224 })?)
225 }
226
227 fn posting_cursors_bulk(
228 &self,
229 field: &str,
230 terms: &[String],
231 ) -> StorageBackendResult<Vec<Box<dyn PostingCursor>>> {
232 if terms.is_empty() {
233 return Ok(Vec::new());
234 }
235 let unique_terms = terms.iter().cloned().collect::<BTreeSet<_>>();
236 let cursors = self.conn.with(|connection| {
237 let mut by_term = BTreeMap::<String, Vec<(i64, i64, Vec<u8>)>>::new();
238 let unique_terms = unique_terms.into_iter().collect::<Vec<_>>();
239 for chunk in unique_terms.chunks(900) {
240 let placeholders = std::iter::repeat_n("?", chunk.len())
241 .collect::<Vec<_>>()
242 .join(", ");
243 let sql = format!(
244 "SELECT term, cluster_id, posting_count, score_blob FROM _posting_clusters
245 WHERE table_name = ? AND field = ? AND term IN ({placeholders})
246 ORDER BY term, cluster_id"
247 );
248 let mut values = Vec::with_capacity(chunk.len() + 2);
249 values.push(SqlValue::Text(self.table.clone()));
250 values.push(SqlValue::Text(field.to_string()));
251 values.extend(chunk.iter().cloned().map(SqlValue::Text));
252 let mut statement = connection.prepare(&sql)?;
253 let rows = statement.query_map(params_from_iter(values), |row| {
254 Ok((
255 row.get::<_, String>(0)?,
256 row.get::<_, i64>(1)?,
257 row.get::<_, i64>(2)?,
258 row.get::<_, Vec<u8>>(3)?,
259 ))
260 })?;
261 for row in rows {
262 let (term, cluster_id, posting_count, score_blob) = row?;
263 by_term
264 .entry(term)
265 .or_default()
266 .push((cluster_id, posting_count, score_blob));
267 }
268 }
269 let mut cursors = BTreeMap::new();
270 for term in unique_terms {
271 cursors.insert(
272 term.clone(),
273 posting_cursor_from_rows(by_term.remove(&term).unwrap_or_default())?,
274 );
275 }
276 Ok(cursors)
277 })?;
278 Ok(terms.iter().map(|term| cursors[term].clone()).collect())
279 }
280
281 fn rebuild_persisted_block_max(
282 &mut self,
283 field: &str,
284 scorer: &dyn BlockMaxScorer,
285 scorer_fingerprint: &str,
286 ) -> StorageBackendResult<bool> {
287 if scorer_fingerprint.is_empty() {
288 return Err(SQLiteError::StorageBackend(
289 "persisted block-max scorer fingerprint must not be empty".into(),
290 )
291 .into());
292 }
293 let terms = self.terms_for_field(field)?;
294 self.ensure_aux_tables(field)?;
295 let table = self.blockmax_table_name(field);
296 self.conn.with_mut(|conn| {
297 conn.execute(&format!("DELETE FROM {}", quote_ident(&table)), [])?;
298 Ok(())
299 })?;
300 for term in terms {
301 self.build_block_max_scores_versioned(field, &term, scorer, scorer_fingerprint)?;
302 }
303 Ok(true)
304 }
305
306 fn persisted_block_max_scores(
307 &self,
308 field: &str,
309 term: &str,
310 scorer_fingerprint: &str,
311 ) -> StorageBackendResult<Option<Vec<f64>>> {
312 if scorer_fingerprint.is_empty() {
313 return Ok(None);
314 }
315 self.get_versioned_block_max_scores(field, term, scorer_fingerprint)
316 }
317
318 fn persisted_block_max_scores_bulk(
319 &self,
320 field: &str,
321 terms: &[String],
322 scorer_fingerprint: &str,
323 ) -> StorageBackendResult<Vec<Option<Vec<f64>>>> {
324 if scorer_fingerprint.is_empty() {
325 return Ok(vec![None; terms.len()]);
326 }
327 self.get_versioned_block_max_scores_bulk(field, terms, scorer_fingerprint)
328 }
329
330 fn for_each_term_freq(
331 &self,
332 field: &str,
333 term: &str,
334 visit: &mut dyn FnMut(DocId, u64),
335 ) -> StorageBackendResult<()> {
336 let mut cursor = self.posting_cursor(field, term)?;
337 while let Some(entry) = cursor.current() {
338 visit(entry.doc_id, entry.term_freq);
339 cursor.advance()?;
340 }
341 Ok(())
342 }
343
344 fn doc_freq(&self, field: &str, term: &str) -> StorageBackendResult<u64> {
345 Ok(self.conn.with(|c| {
346 let n: i64 = c.query_row(
347 "SELECT COALESCE(SUM(posting_count), 0) FROM _posting_clusters
348 WHERE table_name = ?1 AND field = ?2 AND term = ?3",
349 params![self.table, field, term],
350 |r| r.get(0),
351 )?;
352 decode_index_u64("document frequency", n)
353 })?)
354 }
355
356 fn get_doc_length(&self, doc_id: DocId, field: &str) -> StorageBackendResult<u64> {
357 let doc_id = encode_index_u64("document", doc_id)?;
358 Ok(self.conn.with(|c| {
359 let n: Option<i64> = c
360 .query_row(
361 "SELECT length FROM _doc_lengths
362 WHERE table_name = ?1 AND doc_id = ?2 AND field = ?3",
363 params![self.table, doc_id, field],
364 |r| r.get(0),
365 )
366 .optional()?;
367 n.map_or(Ok(0), |length| decode_index_u64("document length", length))
368 })?)
369 }
370
371 fn get_doc_lengths_bulk(
372 &self,
373 doc_ids: &[DocId],
374 field: &str,
375 ) -> StorageBackendResult<BTreeMap<DocId, u64>> {
376 if doc_ids.is_empty() {
377 return Ok(BTreeMap::new());
378 }
379 Ok(self.conn.with(|c| {
380 let mut out = BTreeMap::new();
381 for chunk in doc_ids.chunks(900) {
382 let placeholders = std::iter::repeat_n("?", chunk.len())
383 .collect::<Vec<_>>()
384 .join(", ");
385 let sql = format!(
386 "SELECT doc_id, length FROM _doc_lengths
387 WHERE table_name = ? AND field = ? AND doc_id IN ({placeholders})"
388 );
389 let mut values = Vec::with_capacity(chunk.len() + 2);
390 values.push(SqlValue::Text(self.table.clone()));
391 values.push(SqlValue::Text(field.to_string()));
392 for doc_id in chunk {
393 values.push(SqlValue::Integer(encode_index_u64("document", *doc_id)?));
394 }
395 let mut stmt = c.prepare(&sql)?;
396 let rows = stmt.query_map(params_from_iter(values), |r| {
397 Ok((r.get::<_, i64>(0)?, r.get::<_, i64>(1)?))
398 })?;
399 for row in rows {
400 let (doc_id, length) = row?;
401 let doc_id = decode_index_u64("document id", doc_id)?;
402 let length = decode_index_u64("document length", length)?;
403 out.insert(doc_id, length);
404 }
405 }
406 Ok(out)
407 })?)
408 }
409
410 fn get_scoring_inputs_bulk(
411 &self,
412 doc_ids: &[DocId],
413 field: &str,
414 terms: &[String],
415 ) -> StorageBackendResult<Vec<(u64, Vec<u64>)>> {
416 if doc_ids.is_empty() {
417 return Ok(Vec::new());
418 }
419
420 let doc_lengths = self.get_doc_lengths_bulk(doc_ids, field)?;
421 let mut inputs: Vec<(u64, Vec<u64>)> = doc_ids
422 .iter()
423 .map(|doc_id| {
424 (
425 doc_lengths.get(doc_id).copied().unwrap_or(0),
426 vec![0; terms.len()],
427 )
428 })
429 .collect();
430 if terms.is_empty() {
431 return Ok(inputs);
432 }
433
434 let mut output_positions = BTreeMap::<DocId, Vec<usize>>::new();
435 for (position, doc_id) in doc_ids.iter().copied().enumerate() {
436 output_positions.entry(doc_id).or_default().push(position);
437 }
438 for (term_index, mut cursor) in self
439 .posting_cursors_bulk(field, terms)?
440 .into_iter()
441 .enumerate()
442 {
443 while let Some(entry) = cursor.current() {
444 if let Some(positions) = output_positions.get(&entry.doc_id) {
445 for position in positions {
446 inputs[*position].1[term_index] = entry.term_freq;
447 inputs[*position].0 = entry.doc_length;
448 }
449 }
450 cursor.advance()?;
451 }
452 }
453 Ok(inputs)
454 }
455
456 fn get_term_freq(&self, doc_id: DocId, field: &str, term: &str) -> StorageBackendResult<u64> {
457 let posting_cluster = encode_index_u64("posting cluster", cluster_id(doc_id))?;
458 Ok(self.conn.with(|c| {
459 let blob: Option<Vec<u8>> = c
460 .query_row(
461 "SELECT score_blob FROM _posting_clusters
462 WHERE table_name = ?1 AND field = ?2
463 AND term = ?3 AND cluster_id = ?4",
464 params![self.table, field, term, posting_cluster],
465 |r| r.get(0),
466 )
467 .optional()?;
468 match blob {
469 Some(blob) => {
470 let scores = clustered_result(decode_all_scores(cluster_id(doc_id), &blob))?;
471 Ok(scores
472 .binary_search_by_key(&doc_id, |entry| entry.doc_id)
473 .ok()
474 .map_or(0, |position| scores[position].term_freq))
475 }
476 None => Ok(0),
477 }
478 })?)
479 }
480
481 fn doc_count(&self) -> StorageBackendResult<u64> {
482 Ok(self.conn.with(|c| {
483 let n: i64 = c.query_row(
484 "SELECT COUNT(DISTINCT doc_id) FROM _doc_lengths
485 WHERE table_name = ?1",
486 params![self.table],
487 |r| r.get(0),
488 )?;
489 decode_index_u64("document count", n)
490 })?)
491 }
492
493 fn total_field_length(&self, field: &str) -> StorageBackendResult<u64> {
494 Ok(self.conn.with(|c| {
495 let n: Option<i64> = c
496 .query_row(
497 "SELECT total_length FROM _field_stats
498 WHERE table_name = ?1 AND field = ?2",
499 params![self.table, field],
500 |r| r.get(0),
501 )
502 .optional()?;
503 n.map_or(Ok(0), |length| {
504 decode_index_u64("total field length", length)
505 })
506 })?)
507 }
508
509 fn vocabulary_terms(&self, field: &str) -> StorageBackendResult<Vec<String>> {
510 self.terms_for_field(field)
511 }
512
513 fn stats(&self) -> StorageBackendResult<IndexStats> {
514 let doc_count = self.doc_count()?;
515 let mut s = IndexStats::default();
516 s.total_docs = doc_count;
517 if doc_count > 0 {
518 let total: u64 = self.conn.with(|c| {
519 let n: i64 = c.query_row(
520 "SELECT COALESCE(SUM(total_length), 0) FROM _field_stats
521 WHERE table_name = ?1",
522 params![self.table],
523 |r| r.get(0),
524 )?;
525 decode_index_u64("total indexed length", n)
526 })?;
527 s.avg_doc_length = total as f64 / doc_count as f64;
528 }
529 let pairs: Vec<(String, String, u64)> = self.conn.with(|c| {
531 let mut stmt = c.prepare(
532 "SELECT field, term, SUM(posting_count) FROM _posting_clusters
533 WHERE table_name = ?1
534 GROUP BY field, term",
535 )?;
536 let rows = stmt.query_map(params![self.table], |r| {
537 Ok((
538 r.get::<_, String>(0)?,
539 r.get::<_, String>(1)?,
540 r.get::<_, i64>(2)?,
541 ))
542 })?;
543 let mut out = Vec::new();
544 for row in rows {
545 let (field, term, doc_frequency) = row?;
546 out.push((
547 field,
548 term,
549 decode_index_u64("document frequency", doc_frequency)?,
550 ));
551 }
552 Ok(out)
553 })?;
554 for (field, term, df) in pairs {
555 s.set_doc_freq(field, term, df);
556 }
557 Ok(s)
558 }
559
560 fn posting_count(&self, field: Option<&str>) -> StorageBackendResult<u64> {
561 Ok(self.conn.with(|c| {
562 let n: i64 = if let Some(field) = field {
563 c.query_row(
564 "SELECT COALESCE(SUM(posting_count), 0) FROM _posting_clusters
565 WHERE table_name = ?1 AND field = ?2",
566 params![self.table, field],
567 |r| r.get(0),
568 )?
569 } else {
570 c.query_row(
571 "SELECT COALESCE(SUM(posting_count), 0) FROM _posting_clusters
572 WHERE table_name = ?1",
573 params![self.table],
574 |r| r.get(0),
575 )?
576 };
577 decode_index_u64("posting count", n)
578 })?)
579 }
580
581 fn doc_length_count(&self, field: Option<&str>) -> StorageBackendResult<u64> {
582 Ok(self.conn.with(|c| {
583 let n: i64 = if let Some(field) = field {
584 c.query_row(
585 "SELECT COUNT(*) FROM _doc_lengths
586 WHERE table_name = ?1 AND field = ?2",
587 params![self.table, field],
588 |r| r.get(0),
589 )?
590 } else {
591 c.query_row(
592 "SELECT COUNT(*) FROM _doc_lengths WHERE table_name = ?1",
593 params![self.table],
594 |r| r.get(0),
595 )?
596 };
597 decode_index_u64("document length count", n)
598 })?)
599 }
600
601 fn term_count(&self, field: Option<&str>) -> StorageBackendResult<u64> {
602 Ok(self.conn.with(|c| {
603 let n: i64 = if let Some(field) = field {
604 c.query_row(
605 "SELECT COUNT(DISTINCT term) FROM _posting_clusters
606 WHERE table_name = ?1 AND field = ?2",
607 params![self.table, field],
608 |r| r.get(0),
609 )?
610 } else {
611 c.query_row(
612 "SELECT COUNT(DISTINCT term) FROM _posting_clusters WHERE table_name = ?1",
613 params![self.table],
614 |r| r.get(0),
615 )?
616 };
617 decode_index_u64("term count", n)
618 })?)
619 }
620
621 fn snapshot(&self) -> StorageBackendResult<Arc<dyn InvertedIndex>> {
622 Ok(Arc::new(self.clone()))
623 }
624
625 fn field_names(&self) -> StorageBackendResult<Vec<FieldName>> {
626 Ok(self.conn.with(|c| {
627 let mut stmt =
628 c.prepare("SELECT DISTINCT field FROM _doc_lengths WHERE table_name = ?1")?;
629 let rows = stmt.query_map([&self.table], |row| row.get::<_, String>(0))?;
630 let mut fields = Vec::new();
631 for row in rows {
632 fields.push(row?);
633 }
634 Ok(fields)
635 })?)
636 }
637
638 fn set_field_analyzer(
639 &mut self,
640 field: &str,
641 analyzer: Analyzer,
642 phase: AnalyzerPhase,
643 ) -> Result<(), String> {
644 match phase {
645 AnalyzerPhase::Index => {
646 self.index_field_analyzers
647 .insert(field.to_string(), analyzer);
648 }
649 AnalyzerPhase::Search => {
650 self.search_field_analyzers
651 .insert(field.to_string(), analyzer);
652 }
653 AnalyzerPhase::Both => {
654 self.index_field_analyzers
655 .insert(field.to_string(), analyzer.clone());
656 self.search_field_analyzers
657 .insert(field.to_string(), analyzer);
658 }
659 }
660 Ok(())
661 }
662
663 fn remove_field_analyzers(&mut self, field: &str) -> Result<(), String> {
664 self.index_field_analyzers.remove(field);
665 self.search_field_analyzers.remove(field);
666 Ok(())
667 }
668
669 fn get_field_analyzer(&self, field: &str) -> Analyzer {
670 self.index_field_analyzers
671 .get(field)
672 .cloned()
673 .unwrap_or_else(|| self.analyzer.clone())
674 }
675
676 fn get_search_analyzer(&self, field: &str) -> Analyzer {
677 if let Some(a) = self.search_field_analyzers.get(field) {
678 return a.clone();
679 }
680 if let Some(a) = self.index_field_analyzers.get(field) {
681 return a.clone();
682 }
683 self.analyzer.clone()
684 }
685}