Skip to main content

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;