use std::collections::{HashMap, HashSet};
use crate::error::{DbError, DbResult};
use super::registry::{BuiltRegistry, Decl, IndexTargetDecl};
pub(super) type Owner = (String, String);
pub(super) enum BuildIssue {
Collision {
index: &'static str,
key: Vec<u8>,
owners: [Owner; 2],
},
Projection {
source: &'static str,
error: DbError,
},
}
pub(super) struct SkippedSource {
pub name: &'static str,
pub rows: u64,
pub targets: Vec<&'static str>,
}
pub(super) type TargetRows = HashMap<Vec<u8>, (Vec<u8>, Owner)>;
#[derive(Default)]
pub(super) struct ExpectedRows {
by_target: HashMap<&'static str, TargetRows>,
}
impl ExpectedRows {
pub(super) fn rows(&self, target: &str) -> Option<&TargetRows> {
self.by_target.get(target)
}
pub(super) fn contains(&self, target: &str, key: &[u8]) -> bool {
self.by_target
.get(target)
.is_some_and(|m| m.contains_key(key))
}
}
pub(super) fn declared_targets(reg: &BuiltRegistry) -> HashSet<&'static str> {
reg.reg
.decls
.iter()
.filter_map(|d| match d {
Decl::Index { targets, .. } => Some(targets.as_slice()),
_ => None,
})
.flat_map(|ts| ts.iter().map(|t| t.name))
.collect()
}
pub(super) fn target_decl<'r>(reg: &'r BuiltRegistry, name: &str) -> Option<&'r IndexTargetDecl> {
reg.reg
.decls
.iter()
.filter_map(|d| match d {
Decl::Index { targets, .. } => Some(targets),
_ => None,
})
.flat_map(|ts| ts.iter())
.find(|t| t.name == name)
}
pub(super) fn build(
reg: &BuiltRegistry,
on_issue: &mut dyn FnMut(BuildIssue) -> DbResult<()>,
) -> DbResult<(ExpectedRows, Vec<SkippedSource>)> {
let mut expected = ExpectedRows::default();
let mut skipped: Vec<SkippedSource> = Vec::new();
for d in ®.reg.decls {
let Decl::Index {
source,
targets,
project,
} = d
else {
continue;
};
let Some(src) = reg.reg.entries.iter().find(|e| e.name == *source) else {
continue;
};
let mut abort: Option<DbError> = None;
let mut issue = |i: BuildIssue, abort: &mut Option<DbError>| {
if let Err(e) = on_issue(i) {
*abort = Some(e);
}
};
let limits = src.projection_limits;
(src.scan_any)(&mut |src_kb, k, v| {
if abort.is_some() {
return;
}
let mut rows = Vec::new();
if let Err(e) = project(k, v, limits, &mut rows) {
issue(BuildIssue::Projection { source, error: e }, &mut abort);
return;
}
let owner = (source.to_string(), super::validate::hex(src_kb));
for (target_idx, key, value) in rows {
let Some(t) = targets.get(target_idx) else {
issue(
BuildIssue::Projection {
source,
error: DbError::Internal("index decl: target index out of range"),
},
&mut abort,
);
continue;
};
let rows = expected.by_target.entry(t.name).or_default();
if let Some((_, existing)) = rows.get(&key)
&& existing != &owner
{
let mut owners = [existing.clone(), owner.clone()];
owners.sort();
issue(
BuildIssue::Collision {
index: t.name,
key: key.clone(),
owners,
},
&mut abort,
);
continue;
}
rows.insert(key, (value, owner.clone()));
}
});
if let Some(e) = abort {
return Err(e);
}
let rows = (src.undecodable_rows)();
if rows > 0 {
let names: Vec<&'static str> = targets.iter().map(|t| t.name).collect();
tracing::warn!(
source = source,
rows,
targets = ?names,
"schema: source rows could not be read; the expected index set is incomplete"
);
skipped.push(SkippedSource {
name: source,
rows,
targets: names,
});
}
}
Ok((expected, skipped))
}