Skip to main content

concept_graph/
members.rs

1// SPDX-FileCopyrightText: Cadasto B.V.
2// SPDX-License-Identifier: BUSL-1.1
3
4//! Reference set memberships: for every reference set of an edition, the
5//! bitmap of the concepts that are its active members.
6//!
7//! `?fhir_vs=refset/[sctid]` and the `concept in [sctid]` filter of the FHIR
8//! SNOMED CT page read one bitmap; `?fhir_vs=refset` reads the keys. No spec
9//! governs the layout: our own design. Little-endian, a magic and version
10//! prefix, a count, then per reference set its SCTID as `u64`, the bitmap's
11//! serialized length, and the bitmap in roaring's portable serialization.
12
13use std::collections::BTreeMap;
14use std::io::{self, Read, Write};
15
16use roaring::RoaringBitmap;
17
18use crate::ordinal::{Ordinal, to_usize};
19
20const MAGIC: &[u8; 8] = b"FTRSETS\0";
21const VERSION: u32 = 1;
22
23/// A failure while reading or writing the memberships.
24#[derive(Debug, thiserror::Error)]
25pub enum MembersError {
26    /// An I/O failure.
27    #[error("memberships I/O failed")]
28    Io(#[from] io::Error),
29    /// The bytes do not start with the memberships magic.
30    #[error("not a memberships artifact")]
31    Magic,
32    /// The layout version is not the one this build reads.
33    #[error("memberships layout version {found}, expected {expected}")]
34    Version {
35        /// The version found.
36        found: u32,
37        /// The version this build reads.
38        expected: u32,
39    },
40}
41
42/// The members of every reference set, by reference set SCTID.
43#[derive(Debug, Clone, PartialEq, Eq, Default)]
44pub struct Memberships {
45    sets: BTreeMap<u64, RoaringBitmap>,
46}
47
48impl Memberships {
49    /// No reference sets.
50    #[must_use]
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Adds `concept` to the reference set `refset`.
56    pub fn insert(&mut self, refset: u64, concept: Ordinal) {
57        self.sets.entry(refset).or_default().insert(concept.index());
58    }
59
60    /// The heap bytes the membership bitmaps hold ([`crate::footprint`]).
61    #[must_use]
62    pub fn size_in_bytes(&self) -> usize {
63        self.sets.values().fold(
64            crate::footprint::entries::<u64, RoaringBitmap>(self.sets.len()),
65            |total, set| total.saturating_add(crate::footprint::bitmap(set)),
66        )
67    }
68
69    /// The members of `refset`, when the edition has it.
70    #[must_use]
71    pub fn members(&self, refset: u64) -> Option<&RoaringBitmap> {
72        self.sets.get(&refset)
73    }
74
75    /// The reference set SCTIDs, ascending.
76    pub fn refsets(&self) -> impl Iterator<Item = u64> + '_ {
77        self.sets.keys().copied()
78    }
79
80    /// The number of reference sets.
81    #[must_use]
82    pub fn len(&self) -> usize {
83        self.sets.len()
84    }
85
86    /// Whether there are no reference sets.
87    #[must_use]
88    pub fn is_empty(&self) -> bool {
89        self.sets.is_empty()
90    }
91
92    /// The number of memberships over every reference set.
93    #[must_use]
94    pub fn total(&self) -> u64 {
95        self.sets.values().map(RoaringBitmap::len).sum()
96    }
97
98    /// Writes the layout.
99    ///
100    /// # Errors
101    ///
102    /// Returns [`MembersError::Io`] when writing fails.
103    pub fn write_to(&self, out: &mut impl Write) -> Result<(), MembersError> {
104        out.write_all(MAGIC)?;
105        out.write_all(&VERSION.to_le_bytes())?;
106        let count = crate::persist::u32_len(self.sets.len(), "the refset count")?;
107        out.write_all(&count.to_le_bytes())?;
108        for (refset, set) in &self.sets {
109            out.write_all(&refset.to_le_bytes())?;
110            let size = crate::persist::u32_len(set.serialized_size(), "a set size")?;
111            out.write_all(&size.to_le_bytes())?;
112            set.serialize_into(&mut *out)?;
113        }
114        Ok(())
115    }
116
117    /// Reads the layout.
118    ///
119    /// # Errors
120    ///
121    /// Returns [`MembersError`] for a truncated or foreign artifact.
122    pub fn read_from(input: &mut impl Read) -> Result<Self, MembersError> {
123        let mut magic = [0_u8; 8];
124        input.read_exact(&mut magic)?;
125        if &magic != MAGIC {
126            return Err(MembersError::Magic);
127        }
128        let version = read_u32(input)?;
129        if version != VERSION {
130            return Err(MembersError::Version {
131                found: version,
132                expected: VERSION,
133            });
134        }
135        let count = read_u32(input)?;
136        let mut sets = BTreeMap::new();
137        for _ in 0..count {
138            let mut long = [0_u8; 8];
139            input.read_exact(&mut long)?;
140            let size = read_u32(input)?;
141            let mut bytes = vec![0_u8; to_usize(size)];
142            input.read_exact(&mut bytes)?;
143            sets.insert(
144                u64::from_le_bytes(long),
145                RoaringBitmap::deserialize_from(bytes.as_slice())?,
146            );
147        }
148        Ok(Self { sets })
149    }
150}
151
152fn read_u32(input: &mut impl Read) -> io::Result<u32> {
153    let mut buffer = [0_u8; 4];
154    input.read_exact(&mut buffer)?;
155    Ok(u32::from_le_bytes(buffer))
156}
157
158#[cfg(test)]
159mod tests {
160    use roaring::RoaringBitmap;
161
162    use super::{MembersError, Memberships};
163    use crate::ordinal::Ordinal;
164
165    #[test]
166    fn memberships_round_trip_and_reject_foreign_bytes() {
167        let mut members = Memberships::new();
168        members.insert(31_000_147_101, Ordinal::new(2));
169        members.insert(31_000_147_101, Ordinal::new(3));
170        members.insert(900_000_000_000_497_000, Ordinal::new(3));
171        assert_eq!(members.len(), 2);
172        assert_eq!(members.total(), 3);
173        assert_eq!(
174            members.members(31_000_147_101).map(RoaringBitmap::len),
175            Some(2)
176        );
177        assert!(members.members(1).is_none());
178        assert_eq!(members.refsets().next(), Some(31_000_147_101));
179        let mut bytes = Vec::new();
180        members.write_to(&mut bytes).expect("writes");
181        assert_eq!(
182            Memberships::read_from(&mut bytes.as_slice()).expect("reads"),
183            members
184        );
185        assert!(matches!(
186            Memberships::read_from(&mut b"XXXXXXXX\0\0\0\0".as_slice()),
187            Err(MembersError::Magic)
188        ));
189    }
190}