use rudb_common::{Error, Result};
use rudb_encoding::bitpack;
use crate::bits::BitVector;
use crate::rid::{NO_PARENT, Rid};
use crate::rids::Rids;
const LAYOUT: u8 = 1;
pub const HEADER_BYTES: usize = 32;
#[derive(Debug, Clone)]
pub struct Adjacency {
children: u64,
parents: u64,
edges: u64,
starts: BitVector,
rows: Vec<u8>,
width: usize,
}
impl Adjacency {
pub fn build(parents_of: &[Rid], parents: u64) -> Result<Self> {
let parent_rows = usize::try_from(parents)
.map_err(|_| malformed("a parent table larger than fits in memory"))?;
let mut starts = vec![0_usize; parent_rows + 1];
for &parent in parents_of {
if parent == NO_PARENT {
continue;
}
if parent >= parents {
return Err(malformed(format!(
"a child points at parent {parent} of a table with {parents} rows"
)));
}
starts[parent as usize + 1] += 1;
}
for at in 1..starts.len() {
starts[at] += starts[at - 1];
}
let edges = starts[parent_rows];
let mut placed = starts.clone();
let mut grouped = vec![0_u64; edges];
for (child, &parent) in parents_of.iter().enumerate() {
if parent == NO_PARENT {
continue;
}
let slot = &mut placed[parent as usize];
grouped[*slot] = count(child);
*slot += 1;
}
let len = edges + parent_rows;
let mut words = vec![0_u64; len.div_ceil(64)];
for parent in 0..parent_rows {
for at in starts[parent] + parent..starts[parent + 1] + parent {
words[at / 64] |= 1 << (at % 64);
}
}
let children = count(parents_of.len());
let width = width_for(children);
let mut rows = Vec::with_capacity(bitpack::tail_len(edges, width));
bitpack::pack_linear(&grouped, width, &mut rows)?;
Ok(Self {
children,
parents,
edges: count(edges),
starts: BitVector::new(words, len)?,
rows,
width,
})
}
#[must_use]
pub fn children(&self) -> u64 {
self.children
}
#[must_use]
pub fn parents(&self) -> u64 {
self.parents
}
#[must_use]
pub fn edges(&self) -> u64 {
self.edges
}
#[must_use]
pub fn bytes(&self) -> usize {
self.starts.bytes() + self.rows.len()
}
fn list(&self, parent: Rid) -> Option<std::ops::Range<usize>> {
if parent >= self.parents {
return None;
}
let cum = |nth: Rid| -> Option<usize> {
self.starts.select0(nth).map(|at| at - usize::try_from(nth).unwrap_or(usize::MAX))
};
let from = if parent == 0 { 0 } else { cum(parent - 1)? };
Some(from..cum(parent)?)
}
pub fn children_of(&self, parent: Rid, out: &mut Vec<Rid>) -> Result<()> {
let list = self.list(parent).ok_or_else(|| {
malformed(format!("parent {parent} of {} is past the end", self.parents))
})?;
for at in list {
out.push(bitpack::tail_at(&self.rows, self.width, at)?);
}
Ok(())
}
#[must_use]
pub fn reached(&self, held: &Rids) -> u64 {
held.iter().filter_map(|parent| self.list(parent)).map(|list| count(list.len())).sum()
}
pub fn push(&self, held: &Rids) -> Result<Rids> {
if held.rows() != self.parents {
return Err(Error::internal(format!(
"a set over {} rows pushed through an adjacency over {} parents",
held.rows(),
self.parents
)));
}
let mut words = vec![0_u64; usize::try_from(self.children.div_ceil(64)).unwrap_or(0)];
for parent in held.iter() {
let list = self.list(parent).ok_or_else(|| {
malformed(format!("parent {parent} of {} is past the end", self.parents))
})?;
for at in list {
let child = bitpack::tail_at(&self.rows, self.width, at)?;
let word = words
.get_mut(usize::try_from(child / 64).unwrap_or(usize::MAX))
.ok_or_else(|| malformed(format!("child {child} past the end")))?;
*word |= 1 << (child % 64);
}
}
Rids::from_words(self.children, words)
}
pub fn write(&self, out: &mut Vec<u8>) -> Result<()> {
out.extend_from_slice(&self.children.to_le_bytes());
out.extend_from_slice(&self.parents.to_le_bytes());
out.extend_from_slice(&self.edges.to_le_bytes());
out.push(u8::try_from(self.width).map_err(|_| malformed("a width past a byte"))?);
out.push(LAYOUT);
out.extend_from_slice(&[0; 6]);
self.starts.write(out);
out.extend_from_slice(&self.rows);
Ok(())
}
pub fn read(bytes: &[u8]) -> Result<Self> {
if bytes.len() < HEADER_BYTES {
return Err(malformed("a payload shorter than its header"));
}
let children = number(&bytes[0..8])?;
let parents = number(&bytes[8..16])?;
let edges = number(&bytes[16..24])?;
let width = bytes[24] as usize;
if bytes[25] != LAYOUT {
return Err(malformed(format!("layout {} is not one this build knows", bytes[25])));
}
if width != width_for(children) || edges > children {
return Err(malformed("a header whose numbers do not agree"));
}
let len = usize::try_from(edges + parents)
.map_err(|_| malformed("a list longer than fits in memory"))?;
let edge_count =
usize::try_from(edges).map_err(|_| malformed("more edges than fit in memory"))?;
let rest = &bytes[HEADER_BYTES..];
let split = BitVector::bytes_for(len);
if rest.len() != split + bitpack::tail_len(edge_count, width) {
return Err(malformed("a body that is not the size its header implies"));
}
let starts = BitVector::read(&rest[..split], len)?;
if starts.ones() != edges {
return Err(malformed("lists that do not hold the edges the header counts"));
}
Ok(Self { children, parents, edges, starts, rows: rest[split..].to_vec(), width })
}
}
fn width_for(children: u64) -> usize {
(u64::BITS - children.saturating_sub(1).leading_zeros()).max(1) as usize
}
fn count(rows: usize) -> u64 {
u64::try_from(rows).unwrap_or(u64::MAX)
}
fn number(bytes: &[u8]) -> Result<u64> {
Ok(u64::from_le_bytes(bytes.try_into().map_err(|_| malformed("a header is torn"))?))
}
fn malformed(message: impl Into<String>) -> Error {
Error::invalid_input(format!("invalid rudb backward adjacency: {}", message.into()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::link::Link;
fn scattered() -> (Vec<Rid>, u64) {
let parents = 50;
let parents_of = (0..3_000_u64)
.map(|child| if child % 97 == 0 { NO_PARENT } else { (child * 7919 + 13) % 49 })
.collect();
(parents_of, parents)
}
#[test]
fn each_parent_lists_exactly_the_children_that_point_at_it_in_row_order() {
let (parents_of, parents) = scattered();
let adjacency = Adjacency::build(&parents_of, parents).expect("build");
for parent in 0..parents {
let mut listed = Vec::new();
adjacency.children_of(parent, &mut listed).expect("list");
let expected: Vec<Rid> = (0..count(parents_of.len()))
.filter(|&child| parents_of[child as usize] == parent)
.collect();
assert_eq!(listed, expected, "parent {parent}");
}
assert!(adjacency.children_of(parents, &mut Vec::new()).is_err(), "past the end");
}
#[test]
fn a_push_is_the_set_a_forward_push_through_the_link_gives() {
let (parents_of, parents) = scattered();
let adjacency = Adjacency::build(&parents_of, parents).expect("build");
let link = Link::build(&parents_of, parents).expect("link");
let held = Rids::from_sorted(parents, vec![0, 3, 17, 48, 49]).expect("held");
let pushed = adjacency.push(&held).expect("push");
let forward = held.forward(&link).expect("forward").rids;
assert_eq!(pushed.iter().collect::<Vec<_>>(), forward.iter().collect::<Vec<_>>());
assert_eq!(adjacency.reached(&held), pushed.len(), "counted without reading a row");
let wrong = Rids::from_sorted(parents + 1, vec![0]).expect("wrong");
assert!(adjacency.push(&wrong).is_err(), "a set over another table");
}
#[test]
fn it_reads_back_what_it_wrote_and_refuses_a_torn_body() {
let (parents_of, parents) = scattered();
let adjacency = Adjacency::build(&parents_of, parents).expect("build");
let mut bytes = Vec::new();
adjacency.write(&mut bytes).expect("write");
assert_eq!(bytes.len(), HEADER_BYTES + adjacency.bytes());
let read = Adjacency::read(&bytes).expect("read");
assert_eq!(read.edges(), adjacency.edges());
let held = Rids::from_sorted(parents, vec![5, 6, 7]).expect("held");
assert_eq!(read.push(&held).expect("push"), adjacency.push(&held).expect("push"));
assert!(Adjacency::read(&bytes[..bytes.len() - 1]).is_err(), "a short body");
bytes[25] = 9;
assert!(Adjacency::read(&bytes).is_err(), "a layout from elsewhere");
}
}