1use 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#[derive(Debug, thiserror::Error)]
25pub enum MembersError {
26 #[error("memberships I/O failed")]
28 Io(#[from] io::Error),
29 #[error("not a memberships artifact")]
31 Magic,
32 #[error("memberships layout version {found}, expected {expected}")]
34 Version {
35 found: u32,
37 expected: u32,
39 },
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Default)]
44pub struct Memberships {
45 sets: BTreeMap<u64, RoaringBitmap>,
46}
47
48impl Memberships {
49 #[must_use]
51 pub fn new() -> Self {
52 Self::default()
53 }
54
55 pub fn insert(&mut self, refset: u64, concept: Ordinal) {
57 self.sets.entry(refset).or_default().insert(concept.index());
58 }
59
60 #[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 #[must_use]
71 pub fn members(&self, refset: u64) -> Option<&RoaringBitmap> {
72 self.sets.get(&refset)
73 }
74
75 pub fn refsets(&self) -> impl Iterator<Item = u64> + '_ {
77 self.sets.keys().copied()
78 }
79
80 #[must_use]
82 pub fn len(&self) -> usize {
83 self.sets.len()
84 }
85
86 #[must_use]
88 pub fn is_empty(&self) -> bool {
89 self.sets.is_empty()
90 }
91
92 #[must_use]
94 pub fn total(&self) -> u64 {
95 self.sets.values().map(RoaringBitmap::len).sum()
96 }
97
98 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 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}