use rudb_common::{Error, Result};
use crate::NodeRef;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Reducer {
pub leaves: Vec<Leaf>,
pub classes: u32,
pub extremes: Vec<Extreme>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Leaf {
pub input: NodeRef,
pub keys: Vec<Key>,
pub parent: Option<Edge>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Key {
pub class: u32,
pub column: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Edge {
pub leaf: u32,
pub class: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Extreme {
pub leaf: u32,
pub column: u32,
pub max: bool,
}
impl Reducer {
pub fn children(&self, leaf: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
self.leaves.iter().enumerate().filter_map(move |(child, held)| match held.parent {
Some(edge) if edge.leaf == leaf => Some((position(child), edge.class)),
_ => None,
})
}
#[must_use]
pub fn key(&self, leaf: u32, class: u32) -> Option<u32> {
self.leaves[leaf as usize].keys.iter().find(|key| key.class == class).map(|key| key.column)
}
#[must_use]
pub fn wanted(&self, leaf: u32) -> bool {
self.extremes.iter().any(|extreme| self.under(extreme.leaf, leaf))
}
#[must_use]
pub fn held(&self, leaf: u32) -> bool {
self.leaves[leaf as usize].parent.is_some() && self.wanted(leaf)
}
fn under(&self, leaf: u32, ancestor: u32) -> bool {
let mut at = Some(leaf);
while let Some(here) = at {
if here == ancestor {
return true;
}
at = self.leaves[here as usize].parent.map(|edge| edge.leaf);
}
false
}
pub fn validate(&self) -> Result<()> {
let count = self.leaves.len();
for (at, leaf) in self.leaves.iter().enumerate() {
let fail = |what: &str| Err(Error::internal(format!("relation {at} {what}")));
if leaf.keys.iter().any(|key| key.class >= self.classes) {
return fail("has a key in a class that is not there");
}
if let Some(edge) = leaf.parent {
if edge.leaf as usize <= at || edge.leaf as usize >= count {
return fail("hangs under a relation that is not after it");
}
if self.key(position(at), edge.class).is_none()
|| self.key(edge.leaf, edge.class).is_none()
{
return fail(
"shares a class with its parent that one of the two has no column in",
);
}
}
}
if self.extremes.iter().any(|extreme| extreme.leaf as usize >= count) {
return Err(Error::internal("an extreme is read from a relation that is not there"));
}
Ok(())
}
}
fn position(at: usize) -> u32 {
u32::try_from(at).expect("a plan has fewer than u32::MAX relations")
}
#[cfg(test)]
mod tests {
use super::{Edge, Extreme, Key, Leaf, Reducer};
fn line() -> Reducer {
Reducer {
leaves: vec![
Leaf {
input: 0,
keys: vec![Key { class: 0, column: 0 }],
parent: Some(Edge { leaf: 1, class: 0 }),
},
Leaf {
input: 1,
keys: vec![Key { class: 0, column: 0 }, Key { class: 1, column: 1 }],
parent: Some(Edge { leaf: 2, class: 1 }),
},
Leaf { input: 2, keys: vec![Key { class: 1, column: 0 }], parent: None },
],
classes: 2,
extremes: vec![Extreme { leaf: 0, column: 1, max: false }],
}
}
#[test]
fn the_children_are_the_relations_that_name_it_as_their_parent() {
let reducer = line();
assert_eq!(reducer.children(2).collect::<Vec<_>>(), [(1, 1)]);
assert_eq!(reducer.children(1).collect::<Vec<_>>(), [(0, 0)]);
assert_eq!(reducer.children(0).count(), 0);
}
#[test]
fn only_the_path_down_to_an_extreme_is_held() {
let reducer = line();
assert!(reducer.held(0), "the extreme is read here");
assert!(reducer.held(1), "and the path to it passes through here");
assert!(!reducer.held(2), "the root is reduced as it is scanned");
let mut rooted = reducer;
rooted.extremes = vec![Extreme { leaf: 2, column: 0, max: true }];
assert!(!rooted.held(0) && !rooted.held(1), "nothing below the root is read again");
}
#[test]
fn a_parent_before_its_child_is_refused() {
let mut reducer = line();
assert!(reducer.validate().is_ok());
reducer.leaves[2].parent = Some(Edge { leaf: 0, class: 0 });
assert!(reducer.validate().is_err());
}
#[test]
fn an_edge_on_a_class_one_end_has_no_column_in_is_refused() {
let mut reducer = line();
reducer.leaves[0].parent = Some(Edge { leaf: 1, class: 1 });
assert!(reducer.validate().is_err());
}
}