1use std::io::{self, Read, Write};
13
14use crate::ordinal::{Ordinal, to_usize};
15
16const MAGIC: &[u8; 8] = b"FTRELS\0\0";
17const VERSION: u32 = 1;
18
19#[derive(Debug, thiserror::Error)]
21pub enum RelationsError {
22 #[error("edge ({from}, {kind}, {target}) is out of range for {nodes} nodes and {types} types")]
24 OutOfRange {
25 from: u32,
27 kind: u32,
29 target: u32,
31 nodes: u32,
33 types: u32,
35 },
36 #[error("relations I/O failed")]
38 Io(#[from] io::Error),
39 #[error("not a relations artifact")]
41 Magic,
42 #[error("relations layout version {found}, expected {expected}")]
44 Version {
45 found: u32,
47 expected: u32,
49 },
50 #[error("the relations arrays are inconsistent")]
52 Inconsistent,
53 #[error("a relationship type name is not UTF-8")]
55 Name(#[from] std::string::FromUtf8Error),
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Default)]
60struct Adjacency {
61 offsets: Vec<u32>,
63 kinds: Vec<u32>,
64 ends: Vec<u32>,
65}
66
67impl Adjacency {
68 fn build(nodes: u32, mut edges: Vec<(u32, u32, u32)>) -> Self {
69 edges.sort_unstable();
70 edges.dedup();
71 let mut offsets = Vec::with_capacity(to_usize(nodes).saturating_add(1));
72 let mut kinds = Vec::with_capacity(edges.len());
73 let mut ends = Vec::with_capacity(edges.len());
74 let mut cursor = 0usize;
75 for node in 0..nodes {
76 offsets.push(u32::try_from(kinds.len()).unwrap_or(u32::MAX));
77 while let Some(&(from, kind, to)) = edges.get(cursor) {
78 if from != node {
79 break;
80 }
81 kinds.push(kind);
82 ends.push(to);
83 cursor = cursor.saturating_add(1);
84 }
85 }
86 offsets.push(u32::try_from(kinds.len()).unwrap_or(u32::MAX));
87 Self {
88 offsets,
89 kinds,
90 ends,
91 }
92 }
93
94 fn edges(&self, node: Ordinal) -> impl Iterator<Item = (u32, u32)> + '_ {
95 let index = to_usize(node.index());
96 let (start, end) = match (
97 self.offsets.get(index),
98 self.offsets.get(index.saturating_add(1)),
99 ) {
100 (Some(&s), Some(&e)) => (to_usize(s), to_usize(e)),
101 _ => (0, 0),
102 };
103 let kinds = self.kinds.get(start..end).unwrap_or_default();
104 let ends = self.ends.get(start..end).unwrap_or_default();
105 kinds.iter().copied().zip(ends.iter().copied())
106 }
107
108 fn check(&self, nodes: u32) -> Result<(), RelationsError> {
109 let consistent = self.offsets.len() == to_usize(nodes).saturating_add(1)
110 && self.kinds.len() == self.ends.len()
111 && self
112 .offsets
113 .last()
114 .is_some_and(|&l| to_usize(l) == self.kinds.len())
115 && self.offsets.windows(2).all(|w| w.first() <= w.get(1));
116 consistent.then_some(()).ok_or(RelationsError::Inconsistent)
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Default)]
122pub struct Relations {
123 types: Vec<String>,
125 outgoing: Adjacency,
126 incoming: Adjacency,
127}
128
129impl Relations {
130 pub fn build(
137 nodes: u32,
138 types: Vec<String>,
139 edges: Vec<(Ordinal, u32, Ordinal)>,
140 ) -> Result<Self, RelationsError> {
141 let type_count = u32::try_from(types.len()).unwrap_or(u32::MAX);
142 let mut forward = Vec::with_capacity(edges.len());
143 let mut backward = Vec::with_capacity(edges.len());
144 for (source, kind, target) in edges {
145 let (s, t) = (source.index(), target.index());
146 if s >= nodes || t >= nodes || kind >= type_count {
147 return Err(RelationsError::OutOfRange {
148 from: s,
149 kind,
150 target: t,
151 nodes,
152 types: type_count,
153 });
154 }
155 forward.push((s, kind, t));
156 backward.push((t, kind, s));
157 }
158 Ok(Self {
159 types,
160 outgoing: Adjacency::build(nodes, forward),
161 incoming: Adjacency::build(nodes, backward),
162 })
163 }
164
165 #[must_use]
167 pub fn types(&self) -> &[String] {
168 &self.types
169 }
170
171 #[must_use]
173 pub fn kind(&self, name: &str) -> Option<u32> {
174 self.types
175 .iter()
176 .position(|t| t == name)
177 .and_then(|i| u32::try_from(i).ok())
178 }
179
180 #[must_use]
182 pub fn nodes(&self) -> u32 {
183 u32::try_from(self.outgoing.offsets.len().saturating_sub(1)).unwrap_or(u32::MAX)
184 }
185
186 #[must_use]
188 pub fn edges(&self) -> usize {
189 self.outgoing.ends.len()
190 }
191
192 pub fn outgoing(&self, node: Ordinal) -> impl Iterator<Item = (u32, Ordinal)> + '_ {
194 self.outgoing.edges(node).map(|(k, n)| (k, Ordinal::new(n)))
195 }
196
197 pub fn incoming(&self, node: Ordinal) -> impl Iterator<Item = (u32, Ordinal)> + '_ {
199 self.incoming.edges(node).map(|(k, n)| (k, Ordinal::new(n)))
200 }
201
202 pub fn sources(&self, node: Ordinal, kind: u32) -> impl Iterator<Item = Ordinal> + '_ {
204 self.incoming(node)
205 .filter(move |(k, _)| *k == kind)
206 .map(|(_, n)| n)
207 }
208
209 pub fn targets(&self, node: Ordinal, kind: u32) -> impl Iterator<Item = Ordinal> + '_ {
211 self.outgoing(node)
212 .filter(move |(k, _)| *k == kind)
213 .map(|(_, n)| n)
214 }
215
216 pub fn write_to(&self, out: &mut impl Write) -> Result<(), RelationsError> {
222 out.write_all(MAGIC)?;
223 out.write_all(&VERSION.to_le_bytes())?;
224 let count = crate::persist::u32_len(self.types.len(), "the type count")?;
225 out.write_all(&count.to_le_bytes())?;
226 for name in &self.types {
227 let len = crate::persist::u32_len(name.len(), "a type name length")?;
228 out.write_all(&len.to_le_bytes())?;
229 out.write_all(name.as_bytes())?;
230 }
231 for side in [&self.outgoing, &self.incoming] {
232 write_u32s(out, &side.offsets)?;
233 write_u32s(out, &side.kinds)?;
234 write_u32s(out, &side.ends)?;
235 }
236 Ok(())
237 }
238
239 pub fn read_from(input: &mut impl Read) -> Result<Self, RelationsError> {
245 let mut magic = [0_u8; 8];
246 input.read_exact(&mut magic)?;
247 if &magic != MAGIC {
248 return Err(RelationsError::Magic);
249 }
250 let version = read_u32(input)?;
251 if version != VERSION {
252 return Err(RelationsError::Version {
253 found: version,
254 expected: VERSION,
255 });
256 }
257 let count = read_u32(input)?;
258 let mut types = Vec::with_capacity(to_usize(count));
259 for _ in 0..count {
260 let len = read_u32(input)?;
261 let mut bytes = vec![0_u8; to_usize(len)];
262 input.read_exact(&mut bytes)?;
263 types.push(String::from_utf8(bytes)?);
264 }
265 let mut sides = Vec::with_capacity(2);
266 for _ in 0..2 {
267 sides.push(Adjacency {
268 offsets: read_u32s(input)?,
269 kinds: read_u32s(input)?,
270 ends: read_u32s(input)?,
271 });
272 }
273 let incoming = sides.pop().ok_or(RelationsError::Inconsistent)?;
274 let outgoing = sides.pop().ok_or(RelationsError::Inconsistent)?;
275 let Ok(nodes) = u32::try_from(outgoing.offsets.len().saturating_sub(1)) else {
278 return Err(RelationsError::Inconsistent);
279 };
280 outgoing.check(nodes)?;
281 incoming.check(nodes)?;
282 Ok(Self {
283 types,
284 outgoing,
285 incoming,
286 })
287 }
288}
289
290fn write_u32s(out: &mut impl Write, values: &[u32]) -> io::Result<()> {
291 let len = crate::persist::u32_len(values.len(), "an array length")?;
292 out.write_all(&len.to_le_bytes())?;
293 for value in values {
294 out.write_all(&value.to_le_bytes())?;
295 }
296 Ok(())
297}
298
299fn read_u32(input: &mut impl Read) -> io::Result<u32> {
300 let mut buffer = [0_u8; 4];
301 input.read_exact(&mut buffer)?;
302 Ok(u32::from_le_bytes(buffer))
303}
304
305fn read_u32s(input: &mut impl Read) -> io::Result<Vec<u32>> {
306 let len = read_u32(input)?;
307 let mut values = Vec::with_capacity(to_usize(len));
308 for _ in 0..len {
309 values.push(read_u32(input)?);
310 }
311 Ok(values)
312}
313
314#[cfg(test)]
315mod tests {
316 use super::{Relations, RelationsError};
317 use crate::ordinal::Ordinal;
318
319 #[test]
320 fn edges_are_answered_both_ways_and_round_trip() {
321 let o = Ordinal::new;
322 let types = vec![String::from("has_ingredient"), String::from("isa")];
323 let relations = Relations::build(
324 4,
325 types,
326 vec![
327 (o(2), 0, o(0)),
328 (o(3), 0, o(0)),
329 (o(3), 1, o(2)),
330 (o(2), 0, o(0)),
331 ],
332 )
333 .expect("builds");
334 assert_eq!(relations.edges(), 3, "duplicates collapse");
335 assert_eq!(relations.kind("isa"), Some(1));
336 assert_eq!(relations.kind("part_of"), None);
337 let sources: Vec<u32> = relations.sources(o(0), 0).map(Ordinal::index).collect();
338 assert_eq!(sources, [2, 3]);
339 let targets: Vec<u32> = relations.targets(o(3), 1).map(Ordinal::index).collect();
340 assert_eq!(targets, [2]);
341 assert_eq!(relations.outgoing(o(1)).count(), 0);
342 let mut bytes = Vec::new();
343 relations.write_to(&mut bytes).expect("writes");
344 let back = Relations::read_from(&mut bytes.as_slice()).expect("reads");
345 assert_eq!(back, relations);
346 assert!(matches!(
347 Relations::read_from(&mut b"XXXXXXXX\0\0\0\0".as_slice()),
348 Err(RelationsError::Magic)
349 ));
350 assert!(matches!(
351 Relations::build(2, Vec::new(), vec![(o(0), 0, o(1))]),
352 Err(RelationsError::OutOfRange { .. })
353 ));
354 }
355}