Skip to main content

nodedb_fts/index/
fieldnorm.rs

1//! Fieldnorm storage: SmallFloat-encoded document lengths per collection.
2//!
3//! Stores a compact `Vec<u8>` array indexed by u32 doc_id. Each byte is
4//! a SmallFloat-encoded document length. Persisted as metadata blob via
5//! the backend's `read_meta`/`write_meta`.
6
7use crate::backend::FtsBackend;
8use crate::codec::smallfloat;
9use crate::index::FtsIndex;
10
11impl<B: FtsBackend> FtsIndex<B> {
12    /// Get the fieldnorm (SmallFloat-encoded doc length) for a doc.
13    ///
14    /// Returns the decoded approximate u32 length, or `None` if not stored.
15    pub fn read_fieldnorm(
16        &self,
17        tid: u32,
18        collection: &str,
19        doc_id: u32,
20    ) -> Result<Option<u32>, B::Error> {
21        let data = self.backend.read_meta(tid, collection, "fieldnorms")?;
22        match data {
23            Some(bytes) if (doc_id as usize) < bytes.len() => {
24                Ok(Some(smallfloat::decode(bytes[doc_id as usize])))
25            }
26            _ => Ok(None),
27        }
28    }
29
30    /// Write a fieldnorm byte for a doc_id. Grows the array if needed.
31    pub fn write_fieldnorm(
32        &self,
33        tid: u32,
34        collection: &str,
35        doc_id: u32,
36        doc_length: u32,
37    ) -> Result<(), B::Error> {
38        let mut data = self
39            .backend
40            .read_meta(tid, collection, "fieldnorms")?
41            .unwrap_or_default();
42
43        let idx = doc_id as usize;
44        if idx >= data.len() {
45            data.resize(idx + 1, 0);
46        }
47        data[idx] = smallfloat::encode(doc_length);
48
49        self.backend
50            .write_meta(tid, collection, "fieldnorms", &data)
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use crate::backend::memory::MemoryBackend;
57    use crate::codec::smallfloat;
58    use crate::index::FtsIndex;
59
60    const T: u32 = 1;
61
62    #[test]
63    fn fieldnorm_roundtrip() {
64        let idx = FtsIndex::new(MemoryBackend::new());
65        idx.write_fieldnorm(T, "col", 0, 100).unwrap();
66        idx.write_fieldnorm(T, "col", 5, 50).unwrap();
67
68        let norm0 = idx.read_fieldnorm(T, "col", 0).unwrap().unwrap();
69        let norm5 = idx.read_fieldnorm(T, "col", 5).unwrap().unwrap();
70
71        assert!(norm0 <= 100);
72        assert!(norm5 <= 50);
73        assert_eq!(norm0, smallfloat::decode(smallfloat::encode(100)));
74        assert_eq!(norm5, smallfloat::decode(smallfloat::encode(50)));
75    }
76
77    #[test]
78    fn fieldnorm_missing_doc() {
79        let idx = FtsIndex::new(MemoryBackend::new());
80        assert_eq!(idx.read_fieldnorm(T, "col", 99).unwrap(), None);
81    }
82
83    #[test]
84    fn fieldnorm_overwrite() {
85        let idx = FtsIndex::new(MemoryBackend::new());
86        idx.write_fieldnorm(T, "col", 0, 100).unwrap();
87        idx.write_fieldnorm(T, "col", 0, 200).unwrap();
88
89        let norm = idx.read_fieldnorm(T, "col", 0).unwrap().unwrap();
90        assert_eq!(norm, smallfloat::decode(smallfloat::encode(200)));
91    }
92}