Skip to main content

rudb_graph/
link.rs

1//! The forward link: for each child row, the [`Rid`] of its parent.
2//!
3//! spec/graph/03-the-file-format.md section 3.4. One per relationship, in child `rid` order, with
4//! [`NO_PARENT`] for a child whose key matched nothing. Two physical forms, chosen by measuring the
5//! column rather than by declaring it, exactly as the key map's three are.
6//!
7//! [`Form::Packed`] is the general one: the parent `rid`s bit-packed to `ceil(log2(parent + 1))`
8//! bits, with the maximum representable value meaning no parent. Random access is a shift and a
9//! mask. It answers the child to parent direction and nothing else.
10//!
11//! [`Form::Monotone`] is the one that makes the budget in section 3.7 livable. When the child table
12//! is physically clustered by the join key, which is how `dbgen` emits `lineitem` against `orders`
13//! and `partsupp` against `part`, the parent `rid`s are non-decreasing and storing one integer per
14//! child is a waste. The payload becomes a bit vector of `children + parents` bits: for each parent
15//! in `rid` order, a run of one bits, one per child that points at it, then a zero. On TPC-H SF100
16//! that is 93.8 MB against 2.10 GB for the packed form of the same relationship, and it answers
17//! both directions, so the backward adjacency of section 3.5 does not have to exist for it.
18//!
19//! # The two formulas, and the convention that decides them
20//!
21//! Section 3.4 gives `forward(child) = rank0(select1(child))` and a backward formula beside it. The
22//! two are only both true under one layout, and the one that makes the forward formula right is
23//! ones first: parent zero's children, then a zero, then parent one's children, then a zero. Under
24//! that layout a child's one bit has exactly as many zeros before it as its parent has `rid`, which
25//! is the forward formula, and the backward formula is `[cum(parent - 1), cum(parent))` where
26//! `cum(p) = select0(p) - p` is the number of children of every parent up to and including `p`.
27//! The forward direction is the hot one, so it is the one the layout is chosen for.
28//!
29//! # What the monotone form refuses
30//!
31//! A child with no parent. Under the packed form that is a reserved value; under this one there is
32//! nowhere to put it, because every bit is either a child of the parent whose run it is in or a
33//! parent boundary. A relationship with an unmatched child is therefore packed even when its
34//! matched children are in order, which is the honest answer and is what
35//! [`Link::build`] does without being asked.
36//!
37//! # Where the part-skip statistic comes from
38//!
39//! Section 5.5 prunes a whole part during a semi-join reduction using the minimum and maximum
40//! parent `rid` in that part. Section 3.4 expected that for free from the zone map of a stored
41//! column. This link is a section payload rather than a stored column, for the reason section 3.8
42//! sanctions: the parent's key map has to exist before the child's link can be built, so the link
43//! is built in a second pass at checkpoint time, and a second pass can append a section to a
44//! committed file but cannot go back and add a column to its stripes. So the statistic is stored
45//! here instead, as a minimum and a maximum per [`PART_ROWS`] children, which is sixteen bytes per
46//! thousand and change rows. The monotone form stores none, because a non-decreasing sequence's
47//! minimum and maximum over a range are its two ends and two selects are cheaper than nine
48//! megabytes.
49
50use rudb_common::{Error, Result};
51use rudb_encoding::bitpack;
52
53use crate::bits::BitVector;
54use crate::rid::{NO_PARENT, PART_ROWS, Rid};
55
56/// The payload layout version. See the same constant in `wire.rs` for why it is belt and braces.
57const LAYOUT: u8 = 1;
58
59/// Bytes of fixed header at the front of a forward link payload.
60///
61/// `children`, `parents`, `linked`, then the four bytes that say what shape the rest is.
62pub const HEADER_BYTES: usize = 32;
63
64/// Which physical form a forward link took.
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum Form {
67    /// One bit-packed parent `rid` per child, maximum value reserved for no parent.
68    Packed,
69    /// A bit vector of runs, one run per parent, answering both directions.
70    Monotone,
71}
72
73impl Form {
74    /// The byte that names this form in a section's `flags`.
75    #[must_use]
76    pub fn tag(self) -> u8 {
77        match self {
78            Self::Packed => 0,
79            Self::Monotone => 1,
80        }
81    }
82
83    /// What `rudb_links()` calls this form.
84    #[must_use]
85    pub fn label(self) -> &'static str {
86        match self {
87            Self::Packed => "packed",
88            Self::Monotone => "monotone",
89        }
90    }
91
92    /// The form a tag names.
93    ///
94    /// # Errors
95    ///
96    /// If the tag is not one this build knows, which by section 3.2 means a section to ignore.
97    pub fn from_tag(tag: u8) -> Result<Self> {
98        match tag {
99            0 => Ok(Self::Packed),
100            1 => Ok(Self::Monotone),
101            _ => Err(malformed(format!("forward link form {tag} is not one this build knows"))),
102        }
103    }
104}
105
106/// The minimum and maximum parent `rid` over one part of the child table.
107///
108/// `None` for a part in which no child has a parent, which is a part a reduction can skip outright
109/// rather than a part whose bounds happen to be empty.
110pub type Bounds = Option<(Rid, Rid)>;
111
112#[derive(Debug, Clone)]
113enum Body {
114    Packed {
115        /// Bit-packed parent `rid`s, `width` bits each.
116        bytes: Vec<u8>,
117        width: usize,
118        /// Minimum and maximum per [`PART_ROWS`] children, for section 5.5.
119        heads: Vec<Bounds>,
120    },
121    Monotone {
122        vector: BitVector,
123    },
124}
125
126/// A relationship's child to parent map.
127#[derive(Debug, Clone)]
128pub struct Link {
129    children: u64,
130    parents: u64,
131    linked: u64,
132    body: Body,
133}
134
135impl Link {
136    /// Builds a link from one parent `rid` per child, with [`NO_PARENT`] for the unmatched.
137    ///
138    /// The form is chosen here and not by the caller: monotone when every child has a parent and
139    /// the `rid`s are non-decreasing, packed otherwise. That decision is a pass over the slice the
140    /// caller already produced, so it costs a comparison per child on top of a build that was
141    /// already linear.
142    ///
143    /// # Errors
144    ///
145    /// If a parent `rid` is not [`NO_PARENT`] and is not below `parents`, which means the link and
146    /// the key map it was built against disagree about how many rows the parent table has. That is
147    /// a bug rather than a data condition, and a link built past the end of its parent resolves to
148    /// a row that is not there, which is the one failure in this layer that is a wrong answer.
149    pub fn build(parents_of: &[Rid], parents: u64) -> Result<Self> {
150        let children = count(parents_of.len());
151        let mut linked = 0_u64;
152        let mut monotone = true;
153        let mut previous = 0_u64;
154        for parent in parents_of {
155            if *parent == NO_PARENT {
156                monotone = false;
157                continue;
158            }
159            if *parent >= parents {
160                return Err(malformed(format!(
161                    "a forward link points at parent {parent} of a table with {parents} rows"
162                )));
163            }
164            if *parent < previous {
165                monotone = false;
166            }
167            previous = *parent;
168            linked += 1;
169        }
170        let body = if monotone && parents > 0 {
171            Body::Monotone { vector: runs(parents_of, parents)? }
172        } else {
173            packed(parents_of, parents)?
174        };
175        Ok(Self { children, parents, linked, body })
176    }
177
178    /// Which form the build chose.
179    #[must_use]
180    pub fn form(&self) -> Form {
181        match self.body {
182            Body::Packed { .. } => Form::Packed,
183            Body::Monotone { .. } => Form::Monotone,
184        }
185    }
186
187    /// Rows in the child table.
188    #[must_use]
189    pub fn children(&self) -> u64 {
190        self.children
191    }
192
193    /// Rows in the parent table.
194    #[must_use]
195    pub fn parents(&self) -> u64 {
196        self.parents
197    }
198
199    /// Children that found a parent.
200    #[must_use]
201    pub fn linked(&self) -> u64 {
202        self.linked
203    }
204
205    /// Bytes the body costs, not counting the header.
206    #[must_use]
207    pub fn bytes(&self) -> usize {
208        match &self.body {
209            Body::Packed { bytes, heads, .. } => bytes.len() + heads.len() * 16,
210            Body::Monotone { vector } => vector.bytes(),
211        }
212    }
213
214    /// The parent of a child row, or `None` if it has none or the child is past the end.
215    #[must_use]
216    pub fn forward(&self, child: Rid) -> Option<Rid> {
217        if child >= self.children {
218            return None;
219        }
220        match &self.body {
221            Body::Packed { bytes, width, .. } => {
222                // `tail_at` fails on a width past sixty four or an index past the end, and the
223                // bound above plus the width check at read rule out both, so an error here is not a
224                // condition to report but a bug. Section 3.1 says the answer to a broken section is
225                // no section, and `None` is what no section says about a child.
226                let value = bitpack::tail_at(bytes, *width, usize::try_from(child).ok()?).ok()?;
227                (value != reserved(*width)).then_some(value)
228            }
229            Body::Monotone { vector } => {
230                let at = vector.select1(child)?;
231                Some(vector.rank0(at))
232            }
233        }
234    }
235
236    /// The parents of a run of consecutive children, with [`NO_PARENT`] for the ones that have none.
237    ///
238    /// This is [`Link::forward`] over a range, and what it adds is the monotone form. Answering one
239    /// child there is a `select1`, which is a search, and walking a run of them one search at a time
240    /// is paying for random access on a read that is sequential. So the run is found once and then
241    /// read off the bitmap in order: a one bit is a child of the current parent and a zero bit moves
242    /// on to the next parent, which is a load per sixty four bits and a count of zeros per word.
243    ///
244    /// # Errors
245    ///
246    /// If the run goes past the last child.
247    pub fn forward_run(&self, first: Rid, out: &mut [Rid]) -> Result<()> {
248        let end = first.checked_add(count(out.len()));
249        if end.is_none_or(|end| end > self.children) {
250            return Err(Error::internal(format!(
251                "a run of {} children from {first} goes past the {} the link has",
252                out.len(),
253                self.children
254            )));
255        }
256        if out.is_empty() {
257            return Ok(());
258        }
259        match &self.body {
260            Body::Packed { bytes, width, .. } => {
261                let absent = reserved(*width);
262                let start = usize::try_from(first)
263                    .map_err(|_| malformed("a child past what fits in memory"))?;
264                for (at, slot) in out.iter_mut().enumerate() {
265                    let value = bitpack::tail_at(bytes, *width, start + at)?;
266                    *slot = if value == absent { NO_PARENT } else { value };
267                }
268            }
269            Body::Monotone { vector } => {
270                // Every child has a parent in this form, so the first child's parent is the count
271                // of zeros before its bit and every later one follows from the bits in between.
272                let Some(mut at) = vector.select1(first) else {
273                    return Err(malformed("a monotone link has fewer ones than children"));
274                };
275                let mut parent = vector.rank0(at);
276                let words = vector.words();
277                for slot in out.iter_mut() {
278                    // Skip the zeros up to the next one bit, a word at a time, counting each as a
279                    // parent boundary crossed.
280                    loop {
281                        let word = words.get(at / 64).copied().unwrap_or(0) >> (at % 64);
282                        if word == 0 {
283                            let skipped = 64 - at % 64;
284                            parent += count(skipped);
285                            at += skipped;
286                            if at >= vector.len() {
287                                return Err(malformed("a monotone link ran out of ones"));
288                            }
289                            continue;
290                        }
291                        let zeros = word.trailing_zeros() as usize;
292                        parent += count(zeros);
293                        at += zeros;
294                        break;
295                    }
296                    *slot = parent;
297                    at += 1;
298                }
299            }
300        }
301        Ok(())
302    }
303
304    /// The children of a parent row, as a half open range of child `rid`s.
305    ///
306    /// `None` for the packed form, which does not answer this direction, and for a parent past the
307    /// end. An empty range is a parent with no children and is not the same answer.
308    #[must_use]
309    pub fn backward(&self, parent: Rid) -> Option<std::ops::Range<Rid>> {
310        let Body::Monotone { vector } = &self.body else { return None };
311        if parent >= self.parents {
312            return None;
313        }
314        // `cum(p)` is the children of every parent up to and including `p`: the `p`th zero has `p`
315        // zeros and every earlier one bit before it, so subtracting the zeros leaves the ones.
316        let cum = |nth: Rid| -> Option<u64> { vector.select0(nth).map(|at| count(at) - nth) };
317        let from = if parent == 0 { 0 } else { cum(parent - 1)? };
318        Some(from..cum(parent)?)
319    }
320
321    /// The minimum and maximum parent `rid` over one part of the child table, for section 5.5.
322    ///
323    /// `None` for a part past the end of the table; `Some(None)` for a part in which no child has a
324    /// parent, which is a part a reduction skips.
325    #[must_use]
326    pub fn part_bounds(&self, part: usize) -> Option<Bounds> {
327        let first = count(part * PART_ROWS);
328        if first >= self.children {
329            return None;
330        }
331        match &self.body {
332            Body::Packed { heads, .. } => heads.get(part).copied(),
333            // A non-decreasing sequence's extremes over a range are its ends, so this is two
334            // selects rather than the nine megabytes SF100 would spend storing them.
335            Body::Monotone { .. } => {
336                let last = (first + count(PART_ROWS) - 1).min(self.children - 1);
337                match (self.forward(first), self.forward(last)) {
338                    (Some(low), Some(high)) => Some(Some((low, high))),
339                    _ => Some(None),
340                }
341            }
342        }
343    }
344
345    /// Appends the header and the body.
346    ///
347    /// # Errors
348    ///
349    /// If a length does not fit the width the layout gives it.
350    pub fn write(&self, out: &mut Vec<u8>) -> Result<()> {
351        let start = out.len();
352        out.extend_from_slice(&self.children.to_le_bytes());
353        out.extend_from_slice(&self.parents.to_le_bytes());
354        out.extend_from_slice(&self.linked.to_le_bytes());
355        out.push(self.form().tag());
356        out.push(match &self.body {
357            // The width is derivable from `parents`, and it is written anyway, because a packed
358            // array read at the wrong width is not an error but a page of plausible wrong numbers.
359            // This is the one place in this layer where a number stored twice earns its keep.
360            Body::Packed { width, .. } => u8::try_from(*width)
361                .map_err(|_| malformed("a forward link wider than a byte can name"))?,
362            Body::Monotone { .. } => 0,
363        });
364        out.push(LAYOUT);
365        // Five bytes of nothing, so that the header is thirty two and the body behind it starts on
366        // an eight byte boundary. The monotone form's body is an array of `u64`s and the packed
367        // form's head is pairs of them, and a payload whose reader has to handle both an aligned
368        // and an unaligned case for no reason is a payload with a second code path in it.
369        out.extend_from_slice(&[0; 5]);
370        debug_assert_eq!(
371            out.len() - start,
372            HEADER_BYTES,
373            "the forward link header is thirty two bytes"
374        );
375        match &self.body {
376            Body::Packed { bytes, heads, .. } => {
377                for head in heads {
378                    let (low, high) = head.unwrap_or((NO_PARENT, NO_PARENT));
379                    out.extend_from_slice(&low.to_le_bytes());
380                    out.extend_from_slice(&high.to_le_bytes());
381                }
382                out.extend_from_slice(bytes);
383            }
384            Body::Monotone { vector } => vector.write(out),
385        }
386        Ok(())
387    }
388
389    /// Reads a link from exactly the bytes [`Link::write`] produced.
390    ///
391    /// # Errors
392    ///
393    /// If the payload is shorter than its header, names a form or a layout this build does not
394    /// know, or holds a body that is not the size its header implies. Every one of those is a
395    /// section to drop rather than a query to fail, by section 3.1.
396    pub fn read(bytes: &[u8]) -> Result<Self> {
397        if bytes.len() < HEADER_BYTES {
398            return Err(malformed("a forward link payload is shorter than its header"));
399        }
400        let children = number(&bytes[0..8])?;
401        let parents = number(&bytes[8..16])?;
402        let linked = number(&bytes[16..24])?;
403        let form = Form::from_tag(bytes[24])?;
404        let width = bytes[25] as usize;
405        if bytes[26] != LAYOUT {
406            return Err(malformed(format!(
407                "forward link layout {} is not one this build knows",
408                bytes[26]
409            )));
410        }
411        let rest = &bytes[HEADER_BYTES..];
412        let body = match form {
413            Form::Packed => {
414                if width != width_for(parents) {
415                    return Err(malformed(
416                        "a forward link's width is not the one its parents imply",
417                    ));
418                }
419                let parts = usize::try_from(children.div_ceil(count(PART_ROWS)))
420                    .map_err(|_| malformed("a forward link with more parts than fit in memory"))?;
421                let head = parts * 16;
422                let rows = usize::try_from(children)
423                    .map_err(|_| malformed("a forward link longer than fits in memory"))?;
424                let packed = bitpack::tail_len(rows, width);
425                if rest.len() != head + packed {
426                    return Err(malformed(
427                        "a forward link's body is not the size its header implies",
428                    ));
429                }
430                let mut heads = Vec::with_capacity(parts);
431                for part in 0..parts {
432                    let low = number(&rest[part * 16..part * 16 + 8])?;
433                    let high = number(&rest[part * 16 + 8..part * 16 + 16])?;
434                    heads.push((low != NO_PARENT).then_some((low, high)));
435                }
436                Body::Packed { bytes: rest[head..].to_vec(), width, heads }
437            }
438            Form::Monotone => {
439                let len = usize::try_from(linked + parents)
440                    .map_err(|_| malformed("a forward link longer than fits in memory"))?;
441                Body::Monotone { vector: BitVector::read(rest, len)? }
442            }
443        };
444        Ok(Self { children, parents, linked, body })
445    }
446}
447
448/// The bit vector of runs, one run of ones per parent, each terminated by a zero.
449fn runs(parents_of: &[Rid], parents: u64) -> Result<BitVector> {
450    let len = usize::try_from(count(parents_of.len()) + parents)
451        .map_err(|_| malformed("a forward link longer than fits in memory"))?;
452    let mut words = vec![0_u64; len.div_ceil(64)];
453    let mut at = 0_usize;
454    let mut child = 0_usize;
455    for parent in 0..parents {
456        while child < parents_of.len() && parents_of[child] == parent {
457            words[at / 64] |= 1 << (at % 64);
458            at += 1;
459            child += 1;
460        }
461        at += 1;
462    }
463    debug_assert_eq!(at, len, "every child is a one and every parent is a zero");
464    BitVector::new(words, len)
465}
466
467/// The bit-packed array, plus the per-part bounds section 5.5 asks for.
468///
469/// The substituted values are collected before packing rather than written in place, because
470/// `bitpack::pack_linear` takes a slice and the packer's carry chain is what makes it worth
471/// reusing. That is eight bytes per child held twice for the length of one call, on top of the
472/// slice the caller already built, and it is the reason the monotone form matters: this is the
473/// path SF100's `lineitem` against `part` takes and it is expensive in both directions.
474fn packed(parents_of: &[Rid], parents: u64) -> Result<Body> {
475    let width = width_for(parents);
476    let absent = reserved(width);
477    let mut heads = Vec::with_capacity(parents_of.len().div_ceil(PART_ROWS));
478    for rows in parents_of.chunks(PART_ROWS) {
479        let mut bounds: Bounds = None;
480        for parent in rows {
481            if *parent == NO_PARENT {
482                continue;
483            }
484            bounds = Some(match bounds {
485                None => (*parent, *parent),
486                Some((low, high)) => (low.min(*parent), high.max(*parent)),
487            });
488        }
489        heads.push(bounds);
490    }
491    let values = parents_of
492        .iter()
493        .map(|parent| if *parent == NO_PARENT { absent } else { *parent })
494        .collect::<Vec<u64>>();
495    let mut bytes = Vec::with_capacity(bitpack::tail_len(values.len(), width));
496    bitpack::pack_linear(&values, width, &mut bytes)?;
497    Ok(Body::Packed { bytes, width, heads })
498}
499
500/// Bits per entry: enough for every parent `rid` and one more value meaning no parent.
501///
502/// The bit length of `parents`, which is `ceil(log2(parents + 1))` written the way a CPU computes
503/// it. A table of three parents has `rid`s zero, one and two and needs a fourth value for absent,
504/// which is two bits exactly; a table of four needs three.
505fn width_for(parents: u64) -> usize {
506    (u64::BITS - parents.leading_zeros()).max(1) as usize
507}
508
509/// The value that means no parent, which is the largest the width can hold.
510fn reserved(width: usize) -> u64 {
511    if width >= 64 { u64::MAX } else { (1_u64 << width) - 1 }
512}
513
514/// A row count as a `u64`, which is what every count in a header is.
515fn count(rows: usize) -> u64 {
516    u64::try_from(rows).unwrap_or(u64::MAX)
517}
518
519fn number(bytes: &[u8]) -> Result<u64> {
520    Ok(u64::from_le_bytes(
521        bytes.try_into().map_err(|_| malformed("a forward link header is torn"))?,
522    ))
523}
524
525fn malformed(message: impl Into<String>) -> Error {
526    Error::invalid_input(format!("invalid rudb forward link: {}", message.into()))
527}
528
529#[cfg(test)]
530mod tests {
531    use super::*;
532
533    /// Checks every child resolves to the parent it was built from, in the link and in a copy of it
534    /// that went through the payload.
535    fn resolves(parents_of: &[Rid], parents: u64) -> Link {
536        let built = Link::build(parents_of, parents).expect("build");
537        let mut bytes = Vec::new();
538        built.write(&mut bytes).expect("write");
539        let read = Link::read(&bytes).expect("read");
540        assert_eq!(read.form(), built.form(), "the form survives the round trip");
541        assert_eq!(read.children(), built.children());
542        assert_eq!(read.parents(), built.parents());
543        assert_eq!(read.linked(), built.linked());
544        for link in [&built, &read] {
545            for (child, parent) in parents_of.iter().enumerate() {
546                let want = (*parent != NO_PARENT).then_some(*parent);
547                assert_eq!(link.forward(child as Rid), want, "child {child}");
548            }
549            assert_eq!(link.forward(parents_of.len() as Rid), None, "past the last child");
550        }
551        built
552    }
553
554    #[test]
555    fn a_clustered_child_takes_the_monotone_form_and_answers_both_directions() {
556        // Three children of parent zero, none of parent one, two of parent two. This is the shape
557        // `lineitem` has against `orders` and it is the whole reason the form exists.
558        let link = resolves(&[0, 0, 0, 2, 2], 3);
559        assert_eq!(link.form(), Form::Monotone);
560        assert_eq!(link.backward(0), Some(0..3));
561        assert_eq!(link.backward(1), Some(3..3), "a parent with no children, not a missing parent");
562        assert_eq!(link.backward(2), Some(3..5));
563        assert_eq!(link.backward(3), None, "past the last parent");
564    }
565
566    #[test]
567    fn an_unclustered_child_takes_the_packed_form_and_answers_one_direction() {
568        let link = resolves(&[4, 1, 4, 0, 2], 5);
569        assert_eq!(link.form(), Form::Packed);
570        assert_eq!(link.backward(0), None, "the packed form does not answer backward");
571    }
572
573    #[test]
574    fn a_child_with_no_parent_keeps_the_link_out_of_the_monotone_form() {
575        // Every bit of the monotone vector is a child or a parent boundary, so there is nowhere to
576        // put an unmatched child. Ordered children plus one orphan is packed, and that is honest
577        // rather than a missed opportunity.
578        let link = resolves(&[0, 1, NO_PARENT, 2], 3);
579        assert_eq!(link.form(), Form::Packed);
580        assert_eq!(link.linked(), 3, "the orphan is not linked and the other three are");
581    }
582
583    #[test]
584    fn every_child_pointing_at_one_parent_is_one_run() {
585        let link = resolves(&[7; 50], 8);
586        assert_eq!(link.form(), Form::Monotone);
587        assert_eq!(link.backward(6), Some(0..0));
588        assert_eq!(link.backward(7), Some(0..50));
589    }
590
591    #[test]
592    fn a_link_with_no_children_builds_and_resolves_nothing() {
593        let link = resolves(&[], 10);
594        assert_eq!(link.children(), 0);
595        assert_eq!(link.forward(0), None);
596        assert_eq!(link.part_bounds(0), None, "there is no part zero of an empty table");
597    }
598
599    #[test]
600    fn a_link_whose_parent_table_is_empty_is_packed_and_matches_nothing() {
601        // Not monotone: a bit vector of zero runs has nowhere to put a child. The packed form with
602        // one reserved value is the answer, and every child is unmatched, which is what a link
603        // against an empty parent means.
604        let link = resolves(&[NO_PARENT, NO_PARENT], 0);
605        assert_eq!(link.form(), Form::Packed);
606        assert_eq!(link.linked(), 0);
607    }
608
609    #[test]
610    fn a_parent_rid_past_the_parent_table_is_refused_rather_than_stored() {
611        // The one failure in this layer that is a wrong answer rather than a slow one: a link built
612        // past the end of its parent resolves to a row that is not there.
613        let error = Link::build(&[0, 9], 5).expect_err("refused");
614        assert!(error.to_string().contains("parent 9"), "{error}");
615    }
616
617    #[test]
618    fn the_reserved_value_is_not_a_parent_rid_even_at_the_width_boundary() {
619        // Three parents need two bits for rids zero, one and two, and a third value for no parent,
620        // which is four values and therefore two bits exactly. Four parents need three.
621        assert_eq!(width_for(3), 2);
622        assert_eq!(width_for(4), 3);
623        assert_eq!(reserved(2), 3);
624        let link = resolves(&[2, 0, NO_PARENT], 3);
625        assert_eq!(link.form(), Form::Packed);
626    }
627
628    #[test]
629    fn a_packed_link_carries_the_bounds_of_every_part() {
630        let mut parents_of = vec![0_u64; PART_ROWS * 2 + 5];
631        for (child, parent) in parents_of.iter_mut().enumerate() {
632            // Descending within each part, so the part is not monotone and its bounds are not its
633            // ends, which is what the stored head is for.
634            *parent = (PART_ROWS - child % PART_ROWS) as u64;
635        }
636        let link = Link::build(&parents_of, PART_ROWS as u64 + 1).expect("build");
637        assert_eq!(link.form(), Form::Packed);
638        assert_eq!(link.part_bounds(0), Some(Some((1, PART_ROWS as u64))));
639        assert_eq!(link.part_bounds(2), Some(Some((PART_ROWS as u64 - 4, PART_ROWS as u64))));
640        assert_eq!(link.part_bounds(3), None, "there is no fourth part");
641    }
642
643    #[test]
644    fn a_part_in_which_nothing_matched_is_reported_as_skippable() {
645        let mut parents_of = vec![NO_PARENT; PART_ROWS * 2];
646        parents_of[PART_ROWS] = 3;
647        let link = Link::build(&parents_of, 10).expect("build");
648        assert_eq!(link.part_bounds(0), Some(None), "a part a reduction can skip outright");
649        assert_eq!(link.part_bounds(1), Some(Some((3, 3))));
650    }
651
652    #[test]
653    fn a_monotone_link_derives_its_part_bounds_from_its_ends() {
654        let parents_of = (0..PART_ROWS as u64 * 2).map(|child| child / 4).collect::<Vec<Rid>>();
655        let link = Link::build(&parents_of, PART_ROWS as u64).expect("build");
656        assert_eq!(link.form(), Form::Monotone);
657        assert_eq!(link.part_bounds(0), Some(Some((0, (PART_ROWS as u64 - 1) / 4))));
658        assert_eq!(
659            link.part_bounds(1),
660            Some(Some((PART_ROWS as u64 / 4, (PART_ROWS as u64 * 2 - 1) / 4)))
661        );
662    }
663
664    #[test]
665    fn the_monotone_form_is_a_bit_per_child_and_the_packed_form_is_a_rid_per_child() {
666        // Section 3.4's arithmetic in miniature. Ten thousand children of a thousand parents: the
667        // packed form is ten bits each and the monotone form is one bit each plus one per parent.
668        let ordered = (0..10_000_u64).map(|child| child / 10).collect::<Vec<Rid>>();
669        let monotone = Link::build(&ordered, 1000).expect("build");
670        assert_eq!(monotone.form(), Form::Monotone);
671        let mut shuffled = ordered.clone();
672        shuffled.swap(0, 9999);
673        let packed = Link::build(&shuffled, 1000).expect("build");
674        assert_eq!(packed.form(), Form::Packed);
675        assert!(
676            monotone.bytes() * 4 < packed.bytes(),
677            "monotone {} is not far below packed {}",
678            monotone.bytes(),
679            packed.bytes()
680        );
681    }
682
683    #[test]
684    fn a_payload_shorter_than_its_header_is_refused() {
685        let link = Link::build(&[0, 1], 2).expect("build");
686        let mut bytes = Vec::new();
687        link.write(&mut bytes).expect("write");
688        for cut in [0, 1, HEADER_BYTES - 1] {
689            assert!(Link::read(&bytes[..cut]).is_err(), "a payload of {cut} bytes is refused");
690        }
691    }
692
693    #[test]
694    fn a_form_or_a_layout_this_build_does_not_know_is_refused() {
695        let link = Link::build(&[0, 1], 2).expect("build");
696        let mut bytes = Vec::new();
697        link.write(&mut bytes).expect("write");
698        let mut wrong = bytes.clone();
699        wrong[24] = 9;
700        assert!(Link::read(&wrong).is_err(), "an unknown form is refused");
701        let mut wrong = bytes;
702        wrong[26] = LAYOUT + 1;
703        assert!(Link::read(&wrong).is_err(), "an unknown layout is refused");
704    }
705
706    #[test]
707    fn a_width_that_does_not_match_the_parents_is_refused_rather_than_read_at() {
708        // A packed array read at the wrong width is a page of plausible wrong numbers rather than
709        // an error, which is why the width is both stored and checked against what it should be.
710        let link = Link::build(&[1, 0], 2).expect("build");
711        let mut bytes = Vec::new();
712        link.write(&mut bytes).expect("write");
713        bytes[25] = 7;
714        let error = Link::read(&bytes).expect_err("refused");
715        assert!(error.to_string().contains("width"), "{error}");
716    }
717
718    #[test]
719    fn a_truncated_body_is_refused_for_either_form() {
720        for parents_of in [vec![0_u64, 0, 1, 2], vec![2_u64, 0, 1, 0]] {
721            let link = Link::build(&parents_of, 3).expect("build");
722            let mut bytes = Vec::new();
723            link.write(&mut bytes).expect("write");
724            let short = &bytes[..bytes.len() - 1];
725            assert!(Link::read(short).is_err(), "a truncated {:?} body is refused", link.form());
726        }
727    }
728
729    /// A run decodes to what the per child lookup says, from every starting point and across long
730    /// stretches of parents with no children, which is where the word at a time walk could slip.
731    #[test]
732    fn a_run_agrees_with_the_per_child_lookup_in_both_forms() {
733        let mut clustered = Vec::new();
734        for parent in 0..400_u64 {
735            // Parents with no children in runs of up to a few hundred, so a walk crosses whole
736            // words of zeros.
737            let children = if parent % 50 < 45 { 0 } else { parent % 7 + 1 };
738            clustered.extend(std::iter::repeat_n(parent, children as usize));
739        }
740        let scattered: Vec<Rid> = (0..3000_u64)
741            .map(|child| if child % 13 == 0 { NO_PARENT } else { (child * 37) % 500 })
742            .collect();
743        for (parents_of, parents) in [(clustered, 400), (scattered, 500)] {
744            let link = Link::build(&parents_of, parents).expect("build");
745            let children = link.children();
746            for first in [0, 1, 63, 64, 65, children / 2, children - 1] {
747                for len in [0, 1, 2, 100, children - first] {
748                    let len = len.min(children - first);
749                    let mut out = vec![0; len as usize];
750                    link.forward_run(first, &mut out).expect("in range");
751                    let want: Vec<Rid> = (first..first + len)
752                        .map(|child| link.forward(child).unwrap_or(NO_PARENT))
753                        .collect();
754                    assert_eq!(out, want, "{:?} from {first} for {len}", link.form());
755                }
756            }
757            let mut out = vec![0; 2];
758            assert!(link.forward_run(children - 1, &mut out).is_err(), "past the last child");
759        }
760    }
761}