use std::collections::HashSet;
use rudb_catalog::{Catalog, ForeignKey, Key, QualifiedName, Table};
use rudb_common::{Error, Result, Value};
use rudb_vector::Chunk;
fn held(table: &Table, columns: &[usize]) -> Result<HashSet<Box<[u8]>>> {
let key = Key { columns: (0..columns.len()).collect(), primary: false };
let mut keys = HashSet::new();
for at in 0..table.rows().chunk_count() {
let chunk = table.rows().read(at, columns)?;
for row in 0..chunk.len() {
let values: Vec<Value> = chunk.row(row).collect();
if let Some(encoded) = key.of_row(&values) {
keys.insert(encoded);
}
}
}
Ok(keys)
}
fn named(names: impl Iterator<Item = String>, values: impl Iterator<Item = Value>) -> String {
names.zip(values).map(|(name, value)| format!("{name}: {value}")).collect::<Vec<_>>().join(", ")
}
pub(crate) fn missing(catalog: &Catalog, name: &QualifiedName, rows: &[Chunk]) -> Result<()> {
let table = catalog.table(name)?;
for foreign in table.foreign() {
let target = catalog.table(&foreign.table)?;
let keys = held(target, &foreign.referenced)?;
let key = Key { columns: foreign.columns.clone(), primary: false };
for chunk in rows {
for row in 0..chunk.len() {
let values: Vec<Value> = chunk.row(row).collect();
let Some(encoded) = key.of_row(&values) else { continue };
if keys.contains(&encoded) {
continue;
}
let names = foreign.referenced.iter().map(|&at| target.columns()[at].name.clone());
let values = foreign.columns.iter().map(|&at| values[at].clone());
return Err(Error::constraint(format!(
"Violates foreign key constraint because key \"{}\" does not exist in the \
referenced table",
named(names, values)
)));
}
}
}
Ok(())
}
pub(crate) fn lost(catalog: &Catalog, name: &QualifiedName, after: &[Chunk]) -> Result<()> {
let holders: Vec<(&Table, &ForeignKey)> = catalog
.tables()
.flat_map(|table| {
table.foreign().iter().filter(|foreign| &foreign.table == name).map(move |f| (table, f))
})
.collect();
if holders.is_empty() {
return Ok(());
}
let table = catalog.table(name)?;
for (holder, foreign) in holders {
let before = held(table, &foreign.referenced)?;
let key = Key { columns: foreign.referenced.clone(), primary: false };
let mut kept = HashSet::new();
for chunk in after {
for row in 0..chunk.len() {
let values: Vec<Value> = chunk.row(row).collect();
if let Some(encoded) = key.of_row(&values) {
kept.insert(encoded);
}
}
}
let gone: HashSet<&Box<[u8]>> = before.difference(&kept).collect();
if gone.is_empty() {
continue;
}
let key = Key { columns: (0..foreign.columns.len()).collect(), primary: false };
for at in 0..holder.rows().chunk_count() {
let chunk = holder.rows().read(at, &foreign.columns)?;
for row in 0..chunk.len() {
let values: Vec<Value> = chunk.row(row).collect();
let Some(encoded) = key.of_row(&values) else { continue };
if !gone.contains(&encoded) {
continue;
}
let names = foreign.columns.iter().map(|&at| holder.columns()[at].name.clone());
return Err(Error::constraint(format!(
"Violates foreign key constraint because key \"{}\" is still referenced by a \
foreign key in a different table. If this is an unexpected constraint \
violation, please refer to our foreign key limitations in the documentation",
named(names, values.into_iter())
)));
}
}
}
Ok(())
}