1use std::io::{self, Read, Write};
9
10use roaring::RoaringBitmap;
11
12use crate::closure::Closure;
13use crate::csr::{Csr, CsrError};
14use crate::ordinal::to_usize;
15
16const MAGIC: &[u8; 8] = b"FTGRAPH\0";
17const VERSION: u32 = 1;
18
19#[derive(Debug, thiserror::Error)]
21pub enum PersistError {
22 #[error("graph I/O failed")]
24 Io(#[from] io::Error),
25 #[error("not a graph artifact")]
27 Magic,
28 #[error("graph layout version {found}, expected {expected}")]
30 Version {
31 found: u32,
33 expected: u32,
35 },
36 #[error(transparent)]
38 Csr(#[from] CsrError),
39 #[error("{found} closure sets for {nodes} nodes")]
41 Count {
42 found: usize,
44 nodes: u32,
46 },
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct Hierarchy {
52 pub is_a: Csr,
54 pub closure: Closure,
56}
57
58impl Hierarchy {
59 #[must_use]
62 pub fn size_in_bytes(&self) -> usize {
63 self.is_a
64 .size_in_bytes()
65 .saturating_add(self.closure.size_in_bytes())
66 }
67
68 pub fn write_to(&self, out: &mut impl Write) -> Result<(), PersistError> {
74 out.write_all(MAGIC)?;
75 out.write_all(&VERSION.to_le_bytes())?;
76 write_u32s(out, self.is_a.offsets())?;
77 write_u32s(out, self.is_a.targets())?;
78 write_bitmaps(out, self.closure.ancestor_sets())?;
79 write_bitmaps(out, self.closure.descendant_sets())?;
80 Ok(())
81 }
82
83 pub fn read_from(input: &mut impl Read) -> Result<Self, PersistError> {
89 let mut magic = [0_u8; 8];
90 input.read_exact(&mut magic)?;
91 if &magic != MAGIC {
92 return Err(PersistError::Magic);
93 }
94 let version = read_u32(input)?;
95 if version != VERSION {
96 return Err(PersistError::Version {
97 found: version,
98 expected: VERSION,
99 });
100 }
101 let offsets = read_u32s(input)?;
102 let targets = read_u32s(input)?;
103 let is_a = Csr::from_parts(offsets, targets)?;
104 let nodes = is_a.nodes();
105 let ancestors = read_bitmaps(input)?;
106 let descendants = read_bitmaps(input)?;
107 for list in [&ancestors, &descendants] {
108 if list.len() != to_usize(nodes) {
109 return Err(PersistError::Count {
110 found: list.len(),
111 nodes,
112 });
113 }
114 }
115 Ok(Self {
116 is_a,
117 closure: Closure::from_parts(ancestors, descendants),
118 })
119 }
120}
121
122#[derive(Debug, thiserror::Error)]
124#[error("{what} exceeds the u32 the artifact layout stores")]
125pub(crate) struct TooLong {
126 what: &'static str,
127 #[source]
128 source: std::num::TryFromIntError,
129}
130
131pub(crate) fn u32_len(len: usize, what: &'static str) -> io::Result<u32> {
133 u32::try_from(len).map_err(|source| io::Error::other(TooLong { what, source }))
134}
135
136fn write_u32s(out: &mut impl Write, values: &[u32]) -> io::Result<()> {
137 let len = u32_len(values.len(), "an array length")?;
138 out.write_all(&len.to_le_bytes())?;
139 for value in values {
140 out.write_all(&value.to_le_bytes())?;
141 }
142 Ok(())
143}
144
145fn read_u32(input: &mut impl Read) -> io::Result<u32> {
146 let mut buffer = [0_u8; 4];
147 input.read_exact(&mut buffer)?;
148 Ok(u32::from_le_bytes(buffer))
149}
150
151fn read_u32s(input: &mut impl Read) -> io::Result<Vec<u32>> {
152 let len = read_u32(input)?;
153 let mut values = Vec::with_capacity(to_usize(len));
154 for _ in 0..len {
155 values.push(read_u32(input)?);
156 }
157 Ok(values)
158}
159
160fn write_bitmaps(out: &mut impl Write, sets: &[RoaringBitmap]) -> io::Result<()> {
161 let len = u32_len(sets.len(), "the set count")?;
162 out.write_all(&len.to_le_bytes())?;
163 for set in sets {
164 let size = u32_len(set.serialized_size(), "a set size")?;
165 out.write_all(&size.to_le_bytes())?;
166 set.serialize_into(&mut *out)?;
167 }
168 Ok(())
169}
170
171fn read_bitmaps(input: &mut impl Read) -> io::Result<Vec<RoaringBitmap>> {
172 let len = read_u32(input)?;
173 let mut sets = Vec::with_capacity(to_usize(len));
174 for _ in 0..len {
175 let size = read_u32(input)?;
176 let mut bytes = vec![0_u8; to_usize(size)];
177 input.read_exact(&mut bytes)?;
178 sets.push(RoaringBitmap::deserialize_from(bytes.as_slice())?);
179 }
180 Ok(sets)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::{Hierarchy, PersistError};
186 use crate::closure::Closure;
187 use crate::csr::Csr;
188 use crate::ordinal::Ordinal;
189
190 #[test]
191 fn the_layout_round_trips_and_rejects_foreign_bytes() {
192 let o = Ordinal::new;
193 let is_a = Csr::build(4, [(o(1), o(0)), (o(2), o(0)), (o(3), o(1)), (o(3), o(2))])
194 .expect("builds");
195 let closure = Closure::compute(&is_a).expect("acyclic");
196 let hierarchy = Hierarchy { is_a, closure };
197 let mut bytes = Vec::new();
198 hierarchy.write_to(&mut bytes).expect("writes");
199 let back = Hierarchy::read_from(&mut bytes.as_slice()).expect("reads");
200 assert_eq!(back, hierarchy);
201 assert!(matches!(
202 Hierarchy::read_from(&mut b"nope".as_slice()),
203 Err(PersistError::Io(_))
204 ));
205 assert!(matches!(
206 Hierarchy::read_from(&mut b"XXXXXXXX\0\0\0\0".as_slice()),
207 Err(PersistError::Magic)
208 ));
209 }
210}