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 pub fn insert(&mut self, refset: u64, concept: Ordinal) {
51 self.sets.entry(refset).or_default().insert(concept.index());
52 }
53
54 #[must_use]
56 pub fn size_in_bytes(&self) -> usize {
57 self.sets.values().fold(
58 crate::footprint::entries::<u64, RoaringBitmap>(self.sets.len()),
59 |total, set| total.saturating_add(crate::footprint::bitmap(set)),
60 )
61 }
62
63 #[must_use]
65 pub fn members(&self, refset: u64) -> Option<&RoaringBitmap> {
66 self.sets.get(&refset)
67 }
68
69 pub fn refsets(&self) -> impl Iterator<Item = u64> + '_ {
71 self.sets.keys().copied()
72 }
73
74 #[must_use]
76 pub fn len(&self) -> usize {
77 self.sets.len()
78 }
79
80 #[must_use]
82 pub fn is_empty(&self) -> bool {
83 self.sets.is_empty()
84 }
85
86 #[must_use]
88 pub fn total(&self) -> u64 {
89 self.sets.values().map(RoaringBitmap::len).sum()
90 }
91
92 pub fn write_to(&self, out: &mut impl Write) -> Result<(), MembersError> {
98 out.write_all(MAGIC)?;
99 out.write_all(&VERSION.to_le_bytes())?;
100 let count = crate::persist::u32_len(self.sets.len(), "the refset count")?;
101 out.write_all(&count.to_le_bytes())?;
102 for (refset, set) in &self.sets {
103 out.write_all(&refset.to_le_bytes())?;
104 let size = crate::persist::u32_len(set.serialized_size(), "a set size")?;
105 out.write_all(&size.to_le_bytes())?;
106 set.serialize_into(&mut *out)?;
107 }
108 Ok(())
109 }
110
111 pub fn read_from(input: &mut impl Read) -> Result<Self, MembersError> {
117 let mut magic = [0_u8; 8];
118 input.read_exact(&mut magic)?;
119 if &magic != MAGIC {
120 return Err(MembersError::Magic);
121 }
122 let version = read_u32(input)?;
123 if version != VERSION {
124 return Err(MembersError::Version {
125 found: version,
126 expected: VERSION,
127 });
128 }
129 let count = read_u32(input)?;
130 let mut sets = BTreeMap::new();
131 for _ in 0..count {
132 let mut long = [0_u8; 8];
133 input.read_exact(&mut long)?;
134 let size = read_u32(input)?;
135 let mut bytes = vec![0_u8; to_usize(size)];
136 input.read_exact(&mut bytes)?;
137 sets.insert(
138 u64::from_le_bytes(long),
139 RoaringBitmap::deserialize_from(bytes.as_slice())?,
140 );
141 }
142 Ok(Self { sets })
143 }
144}
145
146fn read_u32(input: &mut impl Read) -> io::Result<u32> {
147 let mut buffer = [0_u8; 4];
148 input.read_exact(&mut buffer)?;
149 Ok(u32::from_le_bytes(buffer))
150}
151
152#[cfg(test)]
153mod tests {
154 use roaring::RoaringBitmap;
155
156 use super::{MembersError, Memberships};
157 use crate::ordinal::Ordinal;
158
159 #[test]
160 fn memberships_round_trip_and_reject_foreign_bytes() {
161 let mut members = Memberships::default();
162 members.insert(31_000_147_101, Ordinal::new(2));
163 members.insert(31_000_147_101, Ordinal::new(3));
164 members.insert(900_000_000_000_497_000, Ordinal::new(3));
165 assert_eq!(members.len(), 2);
166 assert_eq!(members.total(), 3);
167 assert_eq!(
168 members.members(31_000_147_101).map(RoaringBitmap::len),
169 Some(2)
170 );
171 assert!(members.members(1).is_none());
172 assert_eq!(members.refsets().next(), Some(31_000_147_101));
173 let mut bytes = Vec::new();
174 members.write_to(&mut bytes).expect("writes");
175 assert_eq!(
176 Memberships::read_from(&mut bytes.as_slice()).expect("reads"),
177 members
178 );
179 assert!(matches!(
180 Memberships::read_from(&mut b"XXXXXXXX\0\0\0\0".as_slice()),
181 Err(MembersError::Magic)
182 ));
183 }
184}