Skip to main content

loonfs_core/metadata/
queries.rs

1//! Seq-gated reads over [`MetadataState`]: record lookups, visibility
2//! checks, and path resolution.
3//!
4//! Every seq-parameterized query routes to its `*_at_head` twin once
5//! `base_seq` reaches [`MetadataState::indexed_seq`], and to a historical
6//! row scan below it. The composite visibility decisions themselves live in
7//! [`super::visibility`]; this module only chooses which storage arm
8//! (historical scan or at-head index) answers the primitive lookups.
9
10use super::visibility::{self, resolve_in_memory_read, unbind_matches_binding};
11use super::{DirentryBindRecord, InodeRecord, MetadataState, SubtreeTombstoneRecord};
12use loonfs_api::{AbsolutePath, ChangeSeq, InodeId, InodeKind, NameKey};
13use serde::{Deserialize, Serialize};
14use std::future::Future;
15use thiserror::Error;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub struct ResolvedVisiblePath {
19    pub absolute_path: String,
20    pub inode_id: InodeId,
21    pub inode_kind: InodeKind,
22    pub parent_inode_id: Option<InodeId>,
23    pub display_name: String,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)]
27pub enum VisiblePathError {
28    #[error("canonical root inode is missing")]
29    RootMissing,
30    #[error("visible path not found: `{absolute_path}`")]
31    PathNotFound { absolute_path: String },
32    #[error(
33        "path component traversal expected directory at `{absolute_path}` but found inode `{inode_id}` kind `{inode_kind}`"
34    )]
35    PathComponentNotDirectory {
36        absolute_path: String,
37        inode_id: InodeId,
38        inode_kind: InodeKind,
39    },
40}
41
42impl MetadataState {
43    pub fn inode_at_seq(&self, inode_id: InodeId, base_seq: ChangeSeq) -> Option<InodeRecord> {
44        if base_seq >= self.indexed_seq() {
45            return self.inode_at_head(inode_id);
46        }
47        self.inode_at_seq_scan(inode_id, base_seq)
48    }
49
50    pub(crate) fn inode_at_head(&self, inode_id: InodeId) -> Option<InodeRecord> {
51        self.indexes.inode(inode_id)
52    }
53
54    fn inode_at_seq_scan(&self, inode_id: InodeId, base_seq: ChangeSeq) -> Option<InodeRecord> {
55        self.inodes
56            .iter()
57            .find(|inode| inode.inode_id == inode_id && inode.created_seq <= base_seq)
58            .cloned()
59    }
60
61    /// Latest bind for `(parent, name)` at or before `base_seq`, regardless
62    /// of whether it has since been unbound.
63    pub(crate) fn bound_child_at_seq(
64        &self,
65        parent_inode_id: InodeId,
66        name_key: &NameKey,
67        base_seq: ChangeSeq,
68    ) -> Option<DirentryBindRecord> {
69        if base_seq >= self.indexed_seq() {
70            return self.indexes.latest_bind(parent_inode_id, name_key);
71        }
72        self.bound_child_at_seq_scan(parent_inode_id, name_key, base_seq)
73    }
74
75    fn bound_child_at_seq_scan(
76        &self,
77        parent_inode_id: InodeId,
78        name_key: &NameKey,
79        base_seq: ChangeSeq,
80    ) -> Option<DirentryBindRecord> {
81        self.direntry_binds
82            .iter()
83            .filter(|direntry| {
84                direntry.parent_inode_id == parent_inode_id
85                    && direntry.name_key == *name_key
86                    && direntry.bind_seq <= base_seq
87            })
88            .max_by_key(|direntry| (direntry.bind_seq, direntry.bind_delta_index))
89            .cloned()
90    }
91
92    pub fn current_parent_binding_for_child(
93        &self,
94        child_inode_id: InodeId,
95        base_seq: ChangeSeq,
96    ) -> Option<DirentryBindRecord> {
97        if base_seq >= self.indexed_seq() {
98            return self.current_parent_binding_for_child_at_head(child_inode_id);
99        }
100        read_now(visibility::current_parent_binding_for_child(
101            &mut self.reads_at_seq(base_seq),
102            child_inode_id,
103        ))
104    }
105
106    pub(crate) fn current_parent_binding_for_child_at_head(
107        &self,
108        child_inode_id: InodeId,
109    ) -> Option<DirentryBindRecord> {
110        self.indexes.active_parent_for_child(child_inode_id)
111    }
112
113    pub fn active_subtree_tombstone(
114        &self,
115        root_inode_id: InodeId,
116        base_seq: ChangeSeq,
117    ) -> Option<SubtreeTombstoneRecord> {
118        if base_seq >= self.indexed_seq() {
119            return self.active_subtree_tombstone_at_head(root_inode_id);
120        }
121        self.active_subtree_tombstone_scan(root_inode_id, base_seq)
122    }
123
124    pub(crate) fn active_subtree_tombstone_at_head(
125        &self,
126        root_inode_id: InodeId,
127    ) -> Option<SubtreeTombstoneRecord> {
128        self.indexes.active_tombstone(root_inode_id)
129    }
130
131    fn active_subtree_tombstone_scan(
132        &self,
133        root_inode_id: InodeId,
134        base_seq: ChangeSeq,
135    ) -> Option<SubtreeTombstoneRecord> {
136        super::rows::active_tombstone_from_records(
137            self.subtree_tombstones
138                .iter()
139                .filter(|tombstone| tombstone.root_inode_id == root_inode_id)
140                .cloned(),
141            base_seq,
142        )
143    }
144
145    pub fn covering_subtree_tombstone(
146        &self,
147        inode_id: InodeId,
148        base_seq: ChangeSeq,
149    ) -> Option<SubtreeTombstoneRecord> {
150        if base_seq >= self.indexed_seq() {
151            return self.covering_subtree_tombstone_at_head(inode_id);
152        }
153        read_now(visibility::covering_subtree_tombstone(
154            &mut self.reads_at_seq(base_seq),
155            inode_id,
156        ))
157    }
158
159    pub(crate) fn covering_subtree_tombstone_at_head(
160        &self,
161        inode_id: InodeId,
162    ) -> Option<SubtreeTombstoneRecord> {
163        read_now(visibility::covering_subtree_tombstone(
164            &mut self.reads_at_head(),
165            inode_id,
166        ))
167    }
168
169    pub fn visible_inode(&self, inode_id: InodeId, base_seq: ChangeSeq) -> Option<InodeRecord> {
170        if base_seq >= self.indexed_seq() {
171            return self.visible_inode_at_head(inode_id);
172        }
173        read_now(visibility::visible_inode(
174            &mut self.reads_at_seq(base_seq),
175            inode_id,
176        ))
177    }
178
179    pub(crate) fn visible_inode_at_head(&self, inode_id: InodeId) -> Option<InodeRecord> {
180        read_now(visibility::visible_inode(
181            &mut self.reads_at_head(),
182            inode_id,
183        ))
184    }
185
186    pub fn visible_child(
187        &self,
188        parent_inode_id: InodeId,
189        name_key: &NameKey,
190        base_seq: ChangeSeq,
191    ) -> Option<DirentryBindRecord> {
192        if base_seq >= self.indexed_seq() {
193            return self.visible_child_at_head(parent_inode_id, name_key);
194        }
195        read_now(visibility::visible_child(
196            &mut self.reads_at_seq(base_seq),
197            parent_inode_id,
198            name_key,
199        ))
200    }
201
202    pub(crate) fn visible_child_at_head(
203        &self,
204        parent_inode_id: InodeId,
205        name_key: &NameKey,
206    ) -> Option<DirentryBindRecord> {
207        read_now(visibility::visible_child(
208            &mut self.reads_at_head(),
209            parent_inode_id,
210            name_key,
211        ))
212    }
213
214    pub fn resolve_visible_path(
215        &self,
216        absolute_path: &AbsolutePath,
217        base_seq: ChangeSeq,
218    ) -> Result<ResolvedVisiblePath, VisiblePathError> {
219        resolve_in_memory_read(visibility::resolve_visible_path(
220            &mut self.reads_at_seq(base_seq),
221            absolute_path,
222        ))
223    }
224
225    pub(super) fn latest_parent_binding_for_child_at_seq(
226        &self,
227        child_inode_id: InodeId,
228        base_seq: ChangeSeq,
229    ) -> Option<DirentryBindRecord> {
230        self.direntry_binds
231            .iter()
232            .filter(|direntry| {
233                direntry.child_inode_id == child_inode_id && direntry.bind_seq <= base_seq
234            })
235            .max_by_key(|direntry| (direntry.bind_seq, direntry.bind_delta_index))
236            .cloned()
237    }
238
239    pub(crate) fn is_direntry_unbound_at_seq(
240        &self,
241        direntry: &DirentryBindRecord,
242        base_seq: ChangeSeq,
243    ) -> bool {
244        if base_seq >= self.indexed_seq() {
245            return self.is_direntry_unbound_at_head(direntry);
246        }
247        self.is_direntry_unbound_at_seq_scan(direntry, base_seq)
248    }
249
250    pub(crate) fn is_direntry_unbound_at_head(&self, direntry: &DirentryBindRecord) -> bool {
251        self.indexes.is_unbound(direntry)
252    }
253
254    fn is_direntry_unbound_at_seq_scan(
255        &self,
256        direntry: &DirentryBindRecord,
257        base_seq: ChangeSeq,
258    ) -> bool {
259        self.direntry_unbinds
260            .iter()
261            .any(|unbind| unbind.unbind_seq <= base_seq && unbind_matches_binding(unbind, direntry))
262    }
263
264    pub fn would_create_directory_cycle(
265        &self,
266        inode_id: InodeId,
267        new_parent_inode_id: InodeId,
268        base_seq: ChangeSeq,
269    ) -> bool {
270        if base_seq >= self.indexed_seq() {
271            return self.would_create_directory_cycle_at_head(inode_id, new_parent_inode_id);
272        }
273        read_now(visibility::would_create_directory_cycle(
274            &mut self.reads_at_seq(base_seq),
275            inode_id,
276            new_parent_inode_id,
277        ))
278    }
279
280    pub(crate) fn would_create_directory_cycle_at_head(
281        &self,
282        inode_id: InodeId,
283        new_parent_inode_id: InodeId,
284    ) -> bool {
285        read_now(visibility::would_create_directory_cycle(
286            &mut self.reads_at_head(),
287            inode_id,
288            new_parent_inode_id,
289        ))
290    }
291}
292
293/// Drives an in-memory visibility read and unwraps its uninhabited error
294/// arm: of the [`super::visibility`] rules only `resolve_visible_path`
295/// constructs a [`VisiblePathError`], and it is not routed through here.
296fn read_now<T>(future: impl Future<Output = Result<T, VisiblePathError>>) -> T {
297    resolve_in_memory_read(future).expect("seq-scoped metadata state reads should be infallible")
298}