haematite_cli/serve/storage/embed_inherit.rs
1//! F1 (ruled 5 Oct 08:55 and 09:03): a fork inherits its parent's companion,
2//! so its first walk lists only the records that differ (F-1 to F-3).
3//!
4//! The parent comes from the fork's own provenance row. Nothing is carried
5//! while any position in the parent's companion has a walk open: an open
6//! walk's target is the parent's own version (F2b), and carried it would name
7//! that version as the fork's. The answer is then `Waiting`, naming the
8//! field.
9//!
10//! Step one publishes the fork's companion as a namespace record on the
11//! parent's companion's shard roots, sharing its nodes, positions carried as
12//! they are: each names the branch whose version its `through` is. Step two,
13//! one commit, removes the position of every field the fork does not declare
14//! at its head; that field's vectors stay for the pruning box (F-7), and with
15//! no position nothing holds the parent's version for them. Both steps are
16//! safe to repeat: a call finding the companion there does step two if any
17//! such position is left, and answers `Already` only when none is. A
18//! companion already there, inherited or of the fork's own making, is never
19//! replaced.
20//!
21//! The parent's model and the fork's cannot differ: both read the same
22//! declaration, and a declaration naming another model while the field has
23//! vectors is refused (`embed_model_change_not_available`, Q7), so no check
24//! is made here.
25//!
26//! Durable commits: one ref creation for step one, and at most one commit for
27//! step two, only when the fork leaves a field undeclared.
28
29use super::branches::provenance;
30use super::embedded::{self, Field};
31use super::{
32 BranchKind, Captured, Failure, Storage, branch_name, create_branch_with_kind,
33 current_timestamp, identifier, reads, version,
34};
35
36/// What one call did.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum Inherited {
39 /// The companion was made, or its positions for undeclared fields were
40 /// removed.
41 Done,
42 /// The fork has its companion and nothing is left to do.
43 Already,
44 /// The branch was not forked, or its parent has no companion.
45 Nothing,
46 /// The parent's walk on this field is open; nothing was written.
47 Waiting { collection: String, field: String },
48}
49
50impl Storage {
51 /// Gives `fork` a companion made from its parent's (F1).
52 ///
53 /// # Errors
54 /// `branch_missing`; whatever reading the provenance, a position or a
55 /// declaration, making the companion or the commit answers.
56 pub fn embed_inherit(&mut self, fork: &str) -> Result<Inherited, Failure> {
57 identifier(fork)?;
58 let record = self
59 .refs
60 .get(&branch_name(fork))
61 .ok_or_else(|| Failure::new("branch_missing", fork))?;
62 let head = Captured {
63 branch: fork.to_owned(),
64 version: version(record),
65 shards: record.shards.clone(),
66 };
67 let companion = embedded::companion_name(fork);
68 if self.refs.get(&companion).is_none() {
69 let Some(origin) = provenance(&self.reader, &head)? else {
70 return Ok(Inherited::Nothing);
71 };
72 let Some(parent) = self.refs.get(&embedded::companion_name(&origin.branch)) else {
73 return Ok(Inherited::Nothing);
74 };
75 for ((collection, field), position) in self.companion_positions(parent)? {
76 if position.toward.is_some() {
77 return Ok(Inherited::Waiting { collection, field });
78 }
79 }
80 let roots: Vec<_> = parent
81 .shards
82 .iter()
83 .map(|shard| (shard.shard_id, shard.head))
84 .collect();
85 create_branch_with_kind(
86 &companion,
87 roots,
88 BranchKind::Namespace,
89 &mut self.refs,
90 &self.registry,
91 current_timestamp(),
92 )
93 .map_err(Failure::branch_commit)?;
94 self.publish_record(&companion)?;
95 self.drop_undeclared(&head, &companion)?;
96 return Ok(Inherited::Done);
97 }
98 if self.drop_undeclared(&head, &companion)? {
99 return Ok(Inherited::Done);
100 }
101 Ok(Inherited::Already)
102 }
103
104 /// Removes, in one commit, the position of every field `head` does not
105 /// declare embedded; whether there was any.
106 fn drop_undeclared(&mut self, head: &Captured, companion: &str) -> Result<bool, Failure> {
107 let record = self
108 .refs
109 .get(companion)
110 .ok_or_else(|| Failure::new("reference_missing", companion))?;
111 let mut removals = Vec::new();
112 for ((collection, field), _position) in self.companion_positions(record)? {
113 let key = super::embed_job::field_key(&collection, &field);
114 let declared = match reads::get_bytes(&self.reader, head, &key)? {
115 Some(bytes) => matches!(embedded::read_field(&bytes)?, Field::Embedded(_)),
116 None => false,
117 };
118 if !declared {
119 removals.push((embedded::position_key(&collection, &field), None));
120 }
121 }
122 if removals.is_empty() {
123 return Ok(false);
124 }
125 self.commit_namespace(companion, &removals)?;
126 Ok(true)
127 }
128}
129
130#[cfg(test)]
131#[path = "embed_inherit_tests.rs"]
132mod tests;