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