coven_merge/dependency.rs
1//! Original sources consumed by a row change (§9.5, D5).
2
3/// The changes one row change used, in role order: the generation source
4/// first, then each replaced setter in the authored column order, then the
5/// sources its operations used beyond those, then its foreign-key causes in
6/// execution order. A source that fills several roles appears once per role.
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct Provenance<C> {
9 /// The change that established the generation this change advanced.
10 pub generation: Option<C>,
11 /// The change that set each cell this change replaced.
12 pub setters: Vec<C>,
13 /// The generation source and replaced setters its operations used that
14 /// the net change does not already name (D5 `Execution`).
15 pub execution: Vec<C>,
16 /// The direct changes and intermediate actions in the same write whose
17 /// foreign-key actions produced this change (§9.6).
18 pub causes: Vec<C>,
19}
20
21impl<C> Provenance<C> {
22 /// Every source, in role order, keeping repeats.
23 pub fn sources(&self) -> impl Iterator<Item = &C> {
24 self.generation.iter().chain(&self.setters).chain(&self.execution).chain(&self.causes)
25 }
26}
27
28/// One row change's verdict under the shared rule.
29#[derive(Clone, Debug, PartialEq, Eq)]
30#[cfg(test)]
31pub(crate) enum DataVerdict<C> {
32 /// It applies.
33 Retained,
34 /// Its own admission dropped it: a removal cut-off, schema change or
35 /// reset. That policy decides whether it leaves a loss.
36 Dropped,
37 /// It was admitted but used data that does not survive. It becomes a
38 /// frozen loss naming these failed sources in role order, with repeats.
39 Dependent(Vec<C>),
40}
41
42#[cfg(test)]
43impl<C> DataVerdict<C> {
44 /// Whether this change survives as a source for later changes.
45 pub(crate) fn retained(&self) -> bool {
46 matches!(self, Self::Retained)
47 }
48}
49
50/// Classify a change from its own admission and its sources' verdicts.
51/// Sources were decided first: each was authored, and so consumed, earlier.
52/// `retained` answers for one source; an unavailable source is the caller's
53/// wait or refusal, never a guess here.
54#[cfg(test)]
55pub(crate) fn classify<C: Clone, E>(
56 admitted: bool,
57 provenance: &Provenance<C>,
58 mut retained: impl FnMut(&C) -> Result<bool, E>,
59) -> Result<DataVerdict<C>, E> {
60 if !admitted {
61 return Ok(DataVerdict::Dropped);
62 }
63 let mut failed = Vec::new();
64 for source in provenance.sources() {
65 if !retained(source)? {
66 failed.push(source.clone());
67 }
68 }
69 Ok(if failed.is_empty() { DataVerdict::Retained } else { DataVerdict::Dependent(failed) })
70}
71
72#[cfg(test)]
73#[path = "dependency_tests.rs"]
74mod tests;