use rudb_catalog::{Catalog, Rows, Table};
use rudb_common::{Result, Session, Value};
use rudb_functions::link_fields;
use rudb_graph::{Cardinality, Degrees, Relationship, Side, parse_links};
use rudb_native::graph::Edge;
use rudb_plan::{Plan, Slice};
use crate::metadata::{Metadata, text};
pub(crate) fn links(
session: &Session,
catalog: &Catalog,
plan: &Plan,
index: u32,
columns: Slice,
) -> Result<Metadata> {
let declared = parse_links(session.links()).unwrap_or_default();
let mut rows = Vec::with_capacity(declared.len());
for link in &declared {
rows.push(row(catalog, link));
}
Metadata::new("rudb_links", &link_fields(), &rows, plan, index, columns)
}
fn row(catalog: &Catalog, link: &Relationship) -> Vec<Value> {
let stored = key_map_of(catalog, &link.parent);
let held = forward_link_of(catalog, link);
let shape = degrees_of(catalog, link);
let map_bytes = stored
.as_ref()
.map(|map| map.bytes as u64)
.or_else(|| refused_key_map_of(catalog, &link.parent));
let link_bytes =
held.as_ref().map(|held| held.bytes() as u64).or_else(|| refused_link_of(catalog, link));
let (cardinality, note) = verdict(
catalog,
link,
stored.as_ref(),
held.as_ref(),
map_bytes.is_some(),
link_bytes.is_some(),
);
vec![
text(&link.name()),
text(&link.child.table),
text(&link.child.columns.join(", ")),
text(&link.parent.table),
text(&link.parent.columns.join(", ")),
text(cardinality),
stored.as_ref().map_or(Value::Null, |map| text(map.form.label())),
map_bytes.map_or(Value::Null, |bytes| Value::BigInt(clamp(bytes))),
held.as_ref().map_or(Value::Null, |held| text(held.form().label())),
link_bytes.map_or(Value::Null, |bytes| Value::BigInt(clamp(bytes))),
shape.as_ref().map_or(Value::Null, |shape| Value::Double(shape.mean())),
shape.as_ref().map_or(Value::Null, |shape| Value::BigInt(clamp(shape.highest()))),
shape.as_ref().map_or(Value::Null, |shape| Value::BigInt(clamp(shape.percentile(0.99)))),
shape.as_ref().and_then(Degrees::locality).map_or(Value::Null, Value::Double),
shape.as_ref().map_or(Value::Null, |shape| Value::Boolean(shape.unique())),
shape.as_ref().map_or(Value::Null, |shape| Value::Boolean(shape.total())),
note.map_or(Value::Null, text),
]
}
fn clamp(count: u64) -> i64 {
i64::try_from(count).unwrap_or(i64::MAX)
}
struct Stored {
form: rudb_graph::Form,
bytes: usize,
distinct: bool,
}
fn key_map_of(catalog: &Catalog, parent: &Side) -> Option<Stored> {
if parent.columns.len() != 1 {
return None;
}
let table = table_named(catalog, &parent.table)?;
let column = table.column_index(&parent.columns[0])?;
let Rows::Native(reader) = table.rows() else { return None };
let map = rudb_native::graph::key_map(reader, column)?;
Some(Stored { form: map.form(), bytes: map.bytes(), distinct: map.observed().distinct })
}
fn refused_key_map_of(catalog: &Catalog, parent: &Side) -> Option<u64> {
if parent.columns.len() != 1 {
return None;
}
let table = table_named(catalog, &parent.table)?;
let column = table.column_index(&parent.columns[0])?;
let Rows::Native(reader) = table.rows() else { return None };
rudb_native::graph::refused_key_map(reader, column).map(|(_, bytes)| bytes)
}
fn refused_link_of(catalog: &Catalog, link: &Relationship) -> Option<u64> {
let child = table_named(catalog, &link.child.table)?;
let Rows::Native(rows) = child.rows() else { return None };
rudb_native::graph::refused_link(rows, key_in(child, &link.child.columns)?)
.map(|(_, bytes)| bytes)
}
fn forward_link_of(catalog: &Catalog, link: &Relationship) -> Option<rudb_graph::Link> {
let child = table_named(catalog, &link.child.table)?;
let parent = table_named(catalog, &link.parent.table)?;
let (Rows::Native(child_rows), Rows::Native(parent_rows)) = (child.rows(), parent.rows())
else {
return None;
};
let edge = Edge {
child: child.name().table.clone(),
child_column: key_in(child, &link.child.columns)?,
parent: parent.name().table.clone(),
parent_column: key_in(parent, &link.parent.columns)?,
};
rudb_native::graph::stored_link(child_rows, parent_rows, &edge)
}
fn degrees_of(catalog: &Catalog, link: &Relationship) -> Option<Degrees> {
let child = table_named(catalog, &link.child.table)?;
let Rows::Native(rows) = child.rows() else { return None };
rudb_native::graph::stored_degrees(rows, key_in(child, &link.child.columns)?)
}
fn key_in(table: &Table, columns: &[String]) -> Option<usize> {
let at = columns.iter().map(|column| table.column_index(column)).collect::<Option<Vec<_>>>()?;
rudb_native::graph::key_of(&at)
}
fn table_named<'a>(catalog: &'a Catalog, name: &str) -> Option<&'a Table> {
catalog
.databases()
.iter()
.flat_map(|database| database.schemas().iter().flat_map(rudb_catalog::Schema::tables))
.find(|table| table.name().table.eq_ignore_ascii_case(name))
}
fn verdict(
catalog: &Catalog,
link: &Relationship,
stored: Option<&Stored>,
held: Option<&rudb_graph::Link>,
measured_map: bool,
measured_link: bool,
) -> (&'static str, Option<&'static str>) {
let Some(stored) = stored else {
if table_named(catalog, &link.parent.table).is_none() {
return (Cardinality::Unverified.label(), Some("no table of that name"));
}
if link.parent.columns.len() == 2 {
return match held {
Some(_) => linked(held, measured_link),
None if measured_link => (
Cardinality::Unverified.label(),
Some("the link was measured and not kept, so link_bytes is what it would cost"),
),
None => (Cardinality::Unverified.label(), Some("no link is stored")),
};
}
if link.parent.columns.len() != 1 {
return (
Cardinality::Unverified.label(),
Some("no link is built over a key this wide"),
);
}
if measured_map {
return (
Cardinality::Unverified.label(),
Some(
"the key map was measured and not kept, so key_map_bytes is what it would cost",
),
);
}
return (Cardinality::Unverified.label(), Some("no key map is stored"));
};
if !stored.distinct {
return (
Cardinality::Unverified.label(),
Some("the parent key repeats, so this is not a many to one relationship"),
);
}
linked(held, measured_link)
}
fn linked(
held: Option<&rudb_graph::Link>,
measured_link: bool,
) -> (&'static str, Option<&'static str>) {
match held {
Some(held) if held.linked() == held.children() => (Cardinality::ExactlyOne.label(), None),
Some(_) => (
Cardinality::AtMostOne.label(),
Some("some child rows have no parent, so this is not exactly one"),
),
None if measured_link => (
Cardinality::AtMostOne.label(),
Some("the link was measured and not kept, so link_bytes is what it would cost"),
),
None => (Cardinality::AtMostOne.label(), Some("no link is stored")),
}
}