Skip to main content

nodedb_fts/index/
stats.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Corpus statistics for BM25 scoring.
4
5use crate::backend::FtsBackend;
6use crate::index::FtsIndex;
7use crate::scope::IndexScope;
8
9impl<B: FtsBackend> FtsIndex<B> {
10    /// Get total document count and average document length of one index.
11    ///
12    /// Returns `(total_docs, avg_doc_len)`. If the index is empty,
13    /// returns `(0, 1.0)` to avoid division by zero.
14    pub fn index_stats<'a>(
15        &self,
16        database_id: u64,
17        tid: u64,
18        index: impl Into<IndexScope<'a>>,
19    ) -> Result<(u32, f32), B::Error> {
20        let (count, total_len) = self
21            .backend
22            .collection_stats(database_id, tid, index.into())?;
23        let avg = if count > 0 {
24            total_len as f32 / count as f32
25        } else {
26            1.0
27        };
28        Ok((count, avg))
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use crate::backend::memory::MemoryBackend;
35    use crate::index::FtsIndex;
36    use crate::test_support::test_governor;
37    use nodedb_types::Surrogate;
38
39    const DB: u64 = 0;
40    const T: u64 = 1;
41
42    #[test]
43    fn empty_collection_stats() {
44        let idx: FtsIndex<MemoryBackend> = FtsIndex::new(MemoryBackend::new(), test_governor());
45        let (count, avg) = idx.index_stats(DB, T, "empty").unwrap();
46        assert_eq!(count, 0);
47        assert!((avg - 1.0).abs() < f32::EPSILON);
48    }
49
50    #[test]
51    fn stats_after_indexing() {
52        let idx = FtsIndex::new(MemoryBackend::new(), test_governor());
53        idx.index_document(DB, T, "docs", Surrogate(1), "hello world greeting")
54            .unwrap();
55        idx.index_document(DB, T, "docs", Surrogate(2), "hello rust")
56            .unwrap();
57
58        let (count, avg) = idx.index_stats(DB, T, "docs").unwrap();
59        assert_eq!(count, 2);
60        assert!(avg > 0.0);
61    }
62}