use std::collections::BTreeMap;
use std::io::{self, Read, Write};
use roaring::RoaringBitmap;
use crate::ordinal::{Ordinal, to_usize};
const MAGIC: &[u8; 8] = b"FTRSETS\0";
const VERSION: u32 = 1;
#[derive(Debug, thiserror::Error)]
pub enum MembersError {
#[error("memberships I/O failed")]
Io(#[from] io::Error),
#[error("not a memberships artifact")]
Magic,
#[error("memberships layout version {found}, expected {expected}")]
Version {
found: u32,
expected: u32,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Memberships {
sets: BTreeMap<u64, RoaringBitmap>,
}
impl Memberships {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, refset: u64, concept: Ordinal) {
self.sets.entry(refset).or_default().insert(concept.index());
}
#[must_use]
pub fn members(&self, refset: u64) -> Option<&RoaringBitmap> {
self.sets.get(&refset)
}
pub fn refsets(&self) -> impl Iterator<Item = u64> + '_ {
self.sets.keys().copied()
}
#[must_use]
pub fn len(&self) -> usize {
self.sets.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.sets.is_empty()
}
#[must_use]
pub fn total(&self) -> u64 {
self.sets.values().map(RoaringBitmap::len).sum()
}
pub fn write_to(&self, out: &mut impl Write) -> Result<(), MembersError> {
out.write_all(MAGIC)?;
out.write_all(&VERSION.to_le_bytes())?;
let count =
u32::try_from(self.sets.len()).map_err(|_| io::Error::other("too many refsets"))?;
out.write_all(&count.to_le_bytes())?;
for (refset, set) in &self.sets {
out.write_all(&refset.to_le_bytes())?;
let size = u32::try_from(set.serialized_size())
.map_err(|_| io::Error::other("set too large"))?;
out.write_all(&size.to_le_bytes())?;
set.serialize_into(&mut *out)?;
}
Ok(())
}
pub fn read_from(input: &mut impl Read) -> Result<Self, MembersError> {
let mut magic = [0_u8; 8];
input.read_exact(&mut magic)?;
if &magic != MAGIC {
return Err(MembersError::Magic);
}
let version = read_u32(input)?;
if version != VERSION {
return Err(MembersError::Version {
found: version,
expected: VERSION,
});
}
let count = read_u32(input)?;
let mut sets = BTreeMap::new();
for _ in 0..count {
let mut long = [0_u8; 8];
input.read_exact(&mut long)?;
let size = read_u32(input)?;
let mut bytes = vec![0_u8; to_usize(size)];
input.read_exact(&mut bytes)?;
sets.insert(
u64::from_le_bytes(long),
RoaringBitmap::deserialize_from(bytes.as_slice())?,
);
}
Ok(Self { sets })
}
}
fn read_u32(input: &mut impl Read) -> io::Result<u32> {
let mut buffer = [0_u8; 4];
input.read_exact(&mut buffer)?;
Ok(u32::from_le_bytes(buffer))
}
#[cfg(test)]
mod tests {
use roaring::RoaringBitmap;
use super::{MembersError, Memberships};
use crate::ordinal::Ordinal;
#[test]
fn memberships_round_trip_and_reject_foreign_bytes() {
let mut members = Memberships::new();
members.insert(31_000_147_101, Ordinal::new(2));
members.insert(31_000_147_101, Ordinal::new(3));
members.insert(900_000_000_000_497_000, Ordinal::new(3));
assert_eq!(members.len(), 2);
assert_eq!(members.total(), 3);
assert_eq!(
members.members(31_000_147_101).map(RoaringBitmap::len),
Some(2)
);
assert!(members.members(1).is_none());
assert_eq!(members.refsets().next(), Some(31_000_147_101));
let mut bytes = Vec::new();
members.write_to(&mut bytes).expect("writes");
assert_eq!(
Memberships::read_from(&mut bytes.as_slice()).expect("reads"),
members
);
assert!(matches!(
Memberships::read_from(&mut b"XXXXXXXX\0\0\0\0".as_slice()),
Err(MembersError::Magic)
));
}
}