1use crate::{Audience, ClaimOrder, MergeError, Parent, RowId, Slot, Timestamp};
2use std::collections::{BTreeMap, BTreeSet, VecDeque};
3
4#[derive(Clone, Debug, PartialEq, Eq)]
6pub enum OnDelete {
7 Cascade,
9 Restrict,
11 NoAction,
13 SetNull {
15 permitted: bool,
17 },
18}
19
20#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct Reference {
23 pub parent: Parent,
25 pub on_delete: OnDelete,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
32pub enum ReferenceValue {
33 Original {
35 parent: Parent,
37 stale: bool,
39 },
40 Null,
42}
43
44impl ReferenceValue {
45 fn dependency(&self) -> Option<(&RowId, bool)> {
46 match self {
47 Self::Original { parent, stale } => Some((&parent.row, *stale)),
48 Self::Null => None,
49 }
50 }
51}
52
53pub fn resolve_reference(
57 child: &RowId,
58 reference: &Reference,
59 parent_generation: u64,
60) -> Result<ReferenceValue, MergeError> {
61 reference.parent.validate_written(child)?;
62 let stale = parent_generation != reference.parent.generation;
63 if stale && matches!(reference.on_delete, OnDelete::SetNull { permitted: true }) {
64 return Ok(ReferenceValue::Null);
65 }
66 Ok(ReferenceValue::Original { parent: reference.parent.clone(), stale })
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
71pub enum RemovalRow {
72 Absent {
74 generation: u64,
76 },
77 Present {
79 generation: u64,
81 started: Timestamp,
83 references: BTreeMap<crate::ForeignKey, Reference>,
85 deleted_circle: Option<Slot>,
87 },
88}
89
90impl RemovalRow {
91 pub fn generation(&self) -> u64 {
93 match self {
94 Self::Absent { generation } | Self::Present { generation, .. } => *generation,
95 }
96 }
97 pub fn present(&self) -> bool {
99 matches!(self, Self::Present { .. })
100 }
101}
102
103#[derive(Clone, Debug, PartialEq, Eq)]
105pub struct UniqueClaim {
106 pub value: Vec<u8>,
109 pub order: ClaimOrder,
111}
112
113#[derive(Clone, Debug, PartialEq, Eq, Default)]
115pub struct Constraints {
116 pub failed_checks: BTreeSet<String>,
118 pub unique: BTreeMap<crate::UniqueConstraint, UniqueClaim>,
120}
121
122#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
124pub enum Group {
125 Claim {
127 table: String,
129 audience: Audience,
131 constraint: crate::UniqueConstraint,
133 value: Vec<u8>,
135 },
136 Key {
138 table: String,
140 key: Vec<u8>,
142 },
143}
144
145pub trait RemovalView {
149 type Error: From<MergeError>;
151 fn row(&self, row: &RowId) -> Result<RemovalRow, Self::Error>;
153 fn constraints(
157 &self,
158 row: &RowId,
159 references: &BTreeMap<crate::ForeignKey, ReferenceValue>,
160 ) -> Result<Constraints, Self::Error>;
161 fn children(&self, row: &RowId) -> Result<BTreeSet<RowId>, Self::Error>;
166 fn groups(&self, row: &RowId) -> Result<BTreeSet<Group>, Self::Error>;
170 fn members(&self, group: &Group) -> Result<BTreeSet<RowId>, Self::Error>;
173}
174
175#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
178pub enum Rule {
179 ForeignKey(crate::ForeignKey),
181 Check(String),
183 DeletedCircle(Slot),
185 OtherAudience,
187 Unique(crate::UniqueConstraint),
189}
190
191#[derive(Clone, Debug, PartialEq, Eq, Default)]
195pub struct RemovalResult {
196 pub region: BTreeSet<RowId>,
198 pub removed: BTreeMap<RowId, BTreeSet<Rule>>,
200 pub references: BTreeMap<RowId, BTreeMap<crate::ForeignKey, ReferenceValue>>,
203}
204
205#[cfg(any(test, feature = "test-utils"))]
207pub trait FullRemovalView: RemovalView {
208 fn rows(&self) -> Result<Vec<RowId>, Self::Error>;
210}
211
212#[cfg(any(test, feature = "test-utils"))]
215pub fn removals<V: FullRemovalView>(view: &V) -> Result<RemovalResult, V::Error> {
216 let rows = view.rows()?;
217 let mut seen = BTreeSet::new();
218 for row in &rows {
219 if !seen.insert(row.clone()) {
220 return Err(MergeError::DuplicateRow(row.clone()).into());
221 }
222 }
223 let region = inputs(view, &mut BTreeSet::new(), seen.clone())?;
224 let mut order = rows;
225 order.extend(region.keys().filter(|r| !seen.contains(*r)).cloned());
226 evaluate(view, region, &order)
227}
228
229pub fn recompute<E: From<MergeError>>(
242 before: &impl RemovalView<Error = E>,
243 after: &impl RemovalView<Error = E>,
244 touched: impl IntoIterator<Item = RowId>,
245) -> Result<RemovalResult, E> {
246 let mut before_groups = BTreeSet::new();
247 let mut after_groups = BTreeSet::new();
248 let mut changed: BTreeSet<RowId> = touched.into_iter().collect();
249 let mut pending: Vec<_> = changed.iter().cloned().collect();
250 while let Some(row) = pending.pop() {
251 let mut dependents = BTreeSet::new();
252 for (view, groups) in [
253 (before as &dyn RemovalView<Error = E>, &mut before_groups),
254 (after as &dyn RemovalView<Error = E>, &mut after_groups),
255 ] {
256 dependents.extend(view.children(&row)?);
257 dependents.extend(rivals(view, groups, &row)?);
258 }
259 for dependent in dependents {
260 if changed.insert(dependent.clone()) {
261 pending.push(dependent);
262 }
263 }
264 }
265 let region = inputs(after, &mut after_groups, changed)?;
266 let order: Vec<_> = region.keys().cloned().collect();
267 evaluate(after, region, &order)
268}
269
270fn rivals<E: From<MergeError>>(
272 view: &dyn RemovalView<Error = E>,
273 expanded: &mut BTreeSet<Group>,
274 row: &RowId,
275) -> Result<BTreeSet<RowId>, E> {
276 let mut rivals = BTreeSet::new();
277 for group in view.groups(row)? {
278 if expanded.insert(group.clone()) {
279 rivals.extend(view.members(&group)?);
280 }
281 }
282 Ok(rivals)
283}
284
285fn inputs<E: From<MergeError>>(
287 view: &dyn RemovalView<Error = E>,
288 expanded: &mut BTreeSet<Group>,
289 rows: BTreeSet<RowId>,
290) -> Result<BTreeMap<RowId, RemovalRow>, E> {
291 let mut region = BTreeMap::new();
292 let mut pending: Vec<_> = rows.into_iter().collect();
293 while let Some(row) = pending.pop() {
294 if region.contains_key(&row) {
295 continue;
296 }
297 let facts = view.row(&row)?;
298 if facts.present() != crate::generation_present(facts.generation()) {
299 return Err(MergeError::GenerationParity(facts.generation()).into());
300 }
301 if let RemovalRow::Present { references, .. } = &facts {
302 pending.extend(references.values().map(|r| r.parent.row.clone()));
303 }
304 pending.extend(rivals(view, expanded, &row)?);
305 region.insert(row, facts);
306 }
307 Ok(region)
308}
309
310struct Facts {
311 row: RemovalRow,
312 constraints: Constraints,
313 references: BTreeMap<crate::ForeignKey, ReferenceValue>,
314}
315
316fn evaluate<E: From<MergeError>>(
317 view: &dyn RemovalView<Error = E>,
318 raw: BTreeMap<RowId, RemovalRow>,
319 order: &[RowId],
320) -> Result<RemovalResult, E> {
321 let mut facts = BTreeMap::new();
322 for (row, data) in &raw {
323 let mut resolved = BTreeMap::new();
324 if let RemovalRow::Present { references, .. } = data {
325 for (name, reference) in references {
326 let parent_gen = raw[&reference.parent.row].generation();
327 resolved.insert(name.clone(), resolve_reference(row, reference, parent_gen)?);
328 }
329 }
330 let constraints =
331 if data.present() { view.constraints(row, &resolved)? } else { Constraints::default() };
332 facts.insert(row.clone(), Facts { row: data.clone(), constraints, references: resolved });
333 }
334 let mut out: BTreeSet<_> =
335 facts.iter().filter(|(_, f)| !f.row.present()).map(|(r, _)| r.clone()).collect();
336 let mut children = BTreeMap::<&RowId, Vec<&RowId>>::new();
340 for row in order {
341 for reference in facts[row].references.values() {
342 if let Some((parent, _)) = reference.dependency() {
343 children.entry(parent).or_default().push(row);
344 }
345 }
346 }
347 close(&facts, &children, order, &mut out);
348 let judged = judge_groups(&facts, &out);
349 out.extend(judged.keys().cloned());
350 close(&facts, &children, order, &mut out);
351 let mut result =
352 RemovalResult { region: raw.into_keys().collect(), ..RemovalResult::default() };
353 for (row, fact) in facts {
354 if fact.row.present() {
355 if out.contains(&row) {
356 let mut rules = monotone_rules(&fact, &out);
357 if let Some(unique) = judged.get(&row) {
358 rules.extend(unique.iter().cloned());
359 }
360 result.removed.insert(row.clone(), rules);
361 }
362 result.references.insert(row, fact.references);
363 }
364 }
365 Ok(result)
366}
367
368fn close(
369 facts: &BTreeMap<RowId, Facts>,
370 children: &BTreeMap<&RowId, Vec<&RowId>>,
371 order: &[RowId],
372 out: &mut BTreeSet<RowId>,
373) {
374 let mut pending = VecDeque::new();
375 for row in order {
376 if out.contains(row) || !monotone_rules(&facts[row], out).is_empty() {
377 out.insert(row.clone());
378 pending.push_back(row);
379 }
380 }
381 while let Some(parent) = pending.pop_front() {
382 if let Some(children) = children.get(parent) {
383 for child in children {
384 if out.insert((*child).clone()) {
387 pending.push_back(child);
388 }
389 }
390 }
391 }
392}
393
394fn monotone_rules(fact: &Facts, out: &BTreeSet<RowId>) -> BTreeSet<Rule> {
395 let mut rules = BTreeSet::new();
396 for (name, reference) in &fact.references {
397 if let Some((parent, stale)) = reference.dependency() {
398 if stale || out.contains(parent) {
399 rules.insert(Rule::ForeignKey(name.clone()));
400 }
401 }
402 }
403 rules.extend(fact.constraints.failed_checks.iter().cloned().map(Rule::Check));
404 if let RemovalRow::Present { deleted_circle: Some(entry), .. } = fact.row {
405 rules.insert(Rule::DeletedCircle(entry));
406 }
407 rules
408}
409
410fn judge_groups(
411 facts: &BTreeMap<RowId, Facts>,
412 out: &BTreeSet<RowId>,
413) -> BTreeMap<RowId, BTreeSet<Rule>> {
414 let mut claims = BTreeMap::<Group, Vec<(&RowId, ClaimOrder)>>::new();
415 let mut keys = BTreeMap::<(&str, &[u8]), Vec<(&RowId, Timestamp)>>::new();
416 for (row, fact) in facts {
417 if out.contains(row) {
418 continue;
419 }
420 for (name, claim) in &fact.constraints.unique {
421 claims
422 .entry(Group::Claim {
423 table: row.table.clone(),
424 audience: row.audience.clone(),
425 constraint: name.clone(),
426 value: claim.value.clone(),
427 })
428 .or_default()
429 .push((row, claim.order));
430 }
431 if let RemovalRow::Present { started, .. } = fact.row {
432 keys.entry((&row.table, &row.key)).or_default().push((row, started));
433 }
434 }
435 let mut judged = BTreeMap::<RowId, BTreeSet<Rule>>::new();
436 for (group, members) in claims {
437 let Group::Claim { constraint, .. } = group else { unreachable!() };
438 let &(winner, _) = members
439 .iter()
440 .min_by_key(|(row, order)| (*order, &row.key))
441 .expect("a competition group contains a row");
442 for (row, _) in members {
443 if row != winner {
444 judged.entry(row.clone()).or_default().insert(Rule::Unique(constraint.clone()));
445 }
446 }
447 }
448 for members in keys.values() {
449 let &(winner, earliest) = members
450 .iter()
451 .min_by_key(|(row, stamp)| (row.audience != Audience::Store, *stamp))
452 .expect("a key group contains a row");
453 for (row, started) in members {
454 if matches!(row.audience, Audience::Circle(_))
455 && (winner.audience == Audience::Store || earliest < *started)
456 {
457 judged.entry((*row).clone()).or_default().insert(Rule::OtherAudience);
458 }
459 }
460 }
461 judged
462}
463
464#[cfg(test)]
465#[path = "removal_tests.rs"]
466mod tests;