Skip to main content

loonfs_core/commit/
materialize.rs

1//! Materializes a prepared commit into ordered WAL deltas plus per-op
2//! results.
3
4use super::{PreparedCommit, ResolvedBinding, ValidatedOp};
5use loonfs_api::wire::wal::WalDelta;
6use loonfs_api::{ContentRef, InodeId, InodeKind, RevisionNo};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct MaterializedCommitDelta {
11    pub semantic_op_index: u32,
12    pub delta_index: u32,
13    pub wal_delta: WalDelta,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub(crate) struct MaterializedCommit {
18    pub prepared: PreparedCommit,
19    /// Observational wall-clock stamp from the publishing request context,
20    /// carried into the durable WAL payload. Not part of the semantic
21    /// identity: two materializations of one prepared commit under
22    /// different clocks share a fingerprint.
23    pub committed_at_ms: u64,
24    pub deltas: Vec<MaterializedCommitDelta>,
25    pub results: Vec<CommitOpResult>,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(tag = "kind", rename_all = "snake_case")]
30pub enum CommitOpResult {
31    CreateDirectory {
32        op_index: u32,
33        inode_id: InodeId,
34    },
35    CreateFile {
36        op_index: u32,
37        inode_id: InodeId,
38        revision_no: RevisionNo,
39        content_ref: ContentRef,
40    },
41    ReplaceFile {
42        op_index: u32,
43        inode_id: InodeId,
44        revision_no: RevisionNo,
45        content_ref: ContentRef,
46    },
47    RestoreRevision {
48        op_index: u32,
49        inode_id: InodeId,
50        source_revision_no: RevisionNo,
51        revision_no: RevisionNo,
52        content_ref: ContentRef,
53    },
54    DeleteFile {
55        op_index: u32,
56        inode_id: InodeId,
57    },
58    Rename {
59        op_index: u32,
60        inode_id: InodeId,
61    },
62    DeleteSubtree {
63        op_index: u32,
64        root_inode_id: InodeId,
65    },
66    Undelete {
67        op_index: u32,
68        inode_id: InodeId,
69    },
70}
71
72pub(crate) fn materialize_commit(
73    prepared: PreparedCommit,
74    committed_at_ms: u64,
75) -> MaterializedCommit {
76    let mut deltas = Vec::new();
77    let mut results = Vec::with_capacity(prepared.plan.validated_ops.len());
78    for op in &prepared.plan.validated_ops {
79        let (mut op_deltas, result) = materialize_validated_op(op);
80        deltas.append(&mut op_deltas);
81        results.push(result);
82    }
83
84    MaterializedCommit {
85        prepared,
86        committed_at_ms,
87        deltas,
88        results,
89    }
90}
91
92pub(super) fn materialize_validated_op(
93    op: &ValidatedOp,
94) -> (Vec<MaterializedCommitDelta>, CommitOpResult) {
95    let mut deltas = Vec::new();
96    let result = match op {
97        ValidatedOp::CreateDir {
98            op_index,
99            parent_inode_id,
100            display_name,
101            name_key,
102            child_inode_id,
103            create_inode_delta_index,
104            bind_delta_index,
105        } => {
106            push_delta(
107                &mut deltas,
108                *op_index,
109                WalDelta::CreateInode {
110                    delta_index: *create_inode_delta_index,
111                    inode_id: *child_inode_id,
112                    inode_kind: InodeKind::Directory,
113                },
114            );
115            push_delta(
116                &mut deltas,
117                *op_index,
118                WalDelta::BindDirentry {
119                    delta_index: *bind_delta_index,
120                    parent_inode_id: *parent_inode_id,
121                    name_key: name_key.clone(),
122                    display_name: display_name.clone(),
123                    child_inode_id: *child_inode_id,
124                },
125            );
126            CommitOpResult::CreateDirectory {
127                op_index: *op_index,
128                inode_id: *child_inode_id,
129            }
130        }
131        ValidatedOp::CreateFile {
132            op_index,
133            parent_inode_id,
134            display_name,
135            name_key,
136            child_inode_id,
137            content_ref,
138            create_inode_delta_index,
139            bind_delta_index,
140            revision_delta_index,
141        } => {
142            push_delta(
143                &mut deltas,
144                *op_index,
145                WalDelta::CreateInode {
146                    delta_index: *create_inode_delta_index,
147                    inode_id: *child_inode_id,
148                    inode_kind: InodeKind::File,
149                },
150            );
151            push_delta(
152                &mut deltas,
153                *op_index,
154                WalDelta::BindDirentry {
155                    delta_index: *bind_delta_index,
156                    parent_inode_id: *parent_inode_id,
157                    name_key: name_key.clone(),
158                    display_name: display_name.clone(),
159                    child_inode_id: *child_inode_id,
160                },
161            );
162            push_delta(
163                &mut deltas,
164                *op_index,
165                WalDelta::AppendFileRevision {
166                    delta_index: *revision_delta_index,
167                    inode_id: *child_inode_id,
168                    revision_no: RevisionNo(1),
169                    content_ref: content_ref.clone(),
170                },
171            );
172            CommitOpResult::CreateFile {
173                op_index: *op_index,
174                inode_id: *child_inode_id,
175                revision_no: RevisionNo(1),
176                content_ref: content_ref.clone(),
177            }
178        }
179        ValidatedOp::ReplaceFile {
180            op_index,
181            inode_id,
182            revision_no,
183            content_ref,
184            revision_delta_index,
185        } => {
186            push_delta(
187                &mut deltas,
188                *op_index,
189                WalDelta::AppendFileRevision {
190                    delta_index: *revision_delta_index,
191                    inode_id: *inode_id,
192                    revision_no: *revision_no,
193                    content_ref: content_ref.clone(),
194                },
195            );
196            CommitOpResult::ReplaceFile {
197                op_index: *op_index,
198                inode_id: *inode_id,
199                revision_no: *revision_no,
200                content_ref: content_ref.clone(),
201            }
202        }
203        ValidatedOp::RestoreRevision {
204            op_index,
205            inode_id,
206            source_revision_no,
207            revision_no,
208            content_ref,
209            revision_delta_index,
210        } => {
211            push_delta(
212                &mut deltas,
213                *op_index,
214                WalDelta::AppendFileRevision {
215                    delta_index: *revision_delta_index,
216                    inode_id: *inode_id,
217                    revision_no: *revision_no,
218                    content_ref: content_ref.clone(),
219                },
220            );
221            CommitOpResult::RestoreRevision {
222                op_index: *op_index,
223                inode_id: *inode_id,
224                source_revision_no: *source_revision_no,
225                revision_no: *revision_no,
226                content_ref: content_ref.clone(),
227            }
228        }
229        ValidatedOp::DeleteFile {
230            op_index,
231            inode_id,
232            source_binding,
233            unbind_delta_index,
234            tombstone_delta_index,
235        } => {
236            push_unbind_delta(&mut deltas, *op_index, *unbind_delta_index, source_binding);
237            push_delta(
238                &mut deltas,
239                *op_index,
240                WalDelta::TombstoneSubtree {
241                    delta_index: *tombstone_delta_index,
242                    root_inode_id: *inode_id,
243                    parent_inode_id: Some(source_binding.parent_inode_id),
244                    name_key: Some(source_binding.name_key.clone()),
245                    display_name: Some(source_binding.display_name.clone()),
246                },
247            );
248            CommitOpResult::DeleteFile {
249                op_index: *op_index,
250                inode_id: *inode_id,
251            }
252        }
253        ValidatedOp::Rename {
254            op_index,
255            inode_id,
256            new_parent_inode_id,
257            new_display_name,
258            new_name_key,
259            source_binding,
260            unbind_delta_index,
261            bind_delta_index,
262        } => {
263            push_unbind_delta(&mut deltas, *op_index, *unbind_delta_index, source_binding);
264            push_delta(
265                &mut deltas,
266                *op_index,
267                WalDelta::BindDirentry {
268                    delta_index: *bind_delta_index,
269                    parent_inode_id: *new_parent_inode_id,
270                    name_key: new_name_key.clone(),
271                    display_name: new_display_name.clone(),
272                    child_inode_id: *inode_id,
273                },
274            );
275            CommitOpResult::Rename {
276                op_index: *op_index,
277                inode_id: *inode_id,
278            }
279        }
280        ValidatedOp::DeleteSubtree {
281            op_index,
282            root_inode_id,
283            source_binding,
284            unbind_delta_index,
285            tombstone_delta_index,
286        } => {
287            push_unbind_delta(&mut deltas, *op_index, *unbind_delta_index, source_binding);
288            push_delta(
289                &mut deltas,
290                *op_index,
291                WalDelta::TombstoneSubtree {
292                    delta_index: *tombstone_delta_index,
293                    root_inode_id: *root_inode_id,
294                    parent_inode_id: Some(source_binding.parent_inode_id),
295                    name_key: Some(source_binding.name_key.clone()),
296                    display_name: Some(source_binding.display_name.clone()),
297                },
298            );
299            CommitOpResult::DeleteSubtree {
300                op_index: *op_index,
301                root_inode_id: *root_inode_id,
302            }
303        }
304        ValidatedOp::Undelete {
305            op_index,
306            inode_id,
307            parent_inode_id,
308            display_name,
309            name_key,
310            target_seq,
311            target_delta_index,
312            revoke_tombstone_delta_index,
313            bind_delta_index,
314        } => {
315            // The mirror of delete's unbind-plus-tombstone: revoke the
316            // exact deletion generation validation resolved, then bind the
317            // recovered inode at its new home.
318            push_delta(
319                &mut deltas,
320                *op_index,
321                WalDelta::RevokeSubtreeTombstone {
322                    delta_index: *revoke_tombstone_delta_index,
323                    root_inode_id: *inode_id,
324                    target_seq: *target_seq,
325                    target_delta_index: *target_delta_index,
326                },
327            );
328            push_delta(
329                &mut deltas,
330                *op_index,
331                WalDelta::BindDirentry {
332                    delta_index: *bind_delta_index,
333                    parent_inode_id: *parent_inode_id,
334                    name_key: name_key.clone(),
335                    display_name: display_name.clone(),
336                    child_inode_id: *inode_id,
337                },
338            );
339            CommitOpResult::Undelete {
340                op_index: *op_index,
341                inode_id: *inode_id,
342            }
343        }
344    };
345
346    (deltas, result)
347}
348
349fn push_unbind_delta(
350    deltas: &mut Vec<MaterializedCommitDelta>,
351    semantic_op_index: u32,
352    delta_index: u32,
353    binding: &ResolvedBinding,
354) {
355    push_delta(
356        deltas,
357        semantic_op_index,
358        WalDelta::UnbindDirentry {
359            delta_index,
360            parent_inode_id: binding.parent_inode_id,
361            name_key: binding.name_key.clone(),
362            display_name: binding.display_name.clone(),
363            child_inode_id: binding.child_inode_id,
364            bind_seq: binding.bind_seq,
365            bind_delta_index: binding.bind_delta_index,
366        },
367    )
368}
369
370fn push_delta(
371    deltas: &mut Vec<MaterializedCommitDelta>,
372    semantic_op_index: u32,
373    wal_delta: WalDelta,
374) {
375    let delta_index = wal_delta_index(&wal_delta);
376    deltas.push(MaterializedCommitDelta {
377        semantic_op_index,
378        delta_index,
379        wal_delta,
380    });
381}
382
383fn wal_delta_index(wal_delta: &WalDelta) -> u32 {
384    match wal_delta {
385        WalDelta::CreateInode { delta_index, .. }
386        | WalDelta::BindDirentry { delta_index, .. }
387        | WalDelta::UnbindDirentry { delta_index, .. }
388        | WalDelta::AppendFileRevision { delta_index, .. }
389        | WalDelta::TombstoneSubtree { delta_index, .. }
390        | WalDelta::RevokeSubtreeTombstone { delta_index, .. } => *delta_index,
391    }
392}