Skip to main content

sim_lib_expr_tree/
runtime.rs

1mod calculation;
2mod creation;
3mod inspection;
4mod path;
5
6use std::collections::BTreeMap;
7
8use sim_expr_tree_calc::{CodecPolicyPatch, ExprTreeCalc};
9use sim_expr_tree_core::{
10    BackendKind, CellCreate, CellId, ControlEntry, DirId, ExprTreeStores, GeneratedNameKind,
11    MountDescriptor, MountEpoch, MountResource, Namespace, NamespaceName, NodeKind, SourceEntry,
12    WriterLane,
13};
14use sim_kernel::Expr;
15use sim_table_core::TablePath;
16
17use crate::runtime_support::{debug_error, source_projection};
18use path::{child_path, path_within, resolve_path, split_path};
19
20/// Hard ceiling on authored namespace nodes in one live tree.
21pub const MAX_TREE_NODES: usize = 4_096;
22/// Hard ceiling on entries returned by one list operation.
23pub const MAX_LIST_ITEMS: usize = 1_024;
24
25#[derive(Clone, Debug)]
26enum EntryIdentity {
27    Dir(DirId),
28    Cell(CellId),
29}
30
31#[derive(Clone, Debug)]
32struct RuntimeCell {
33    id: CellId,
34    source: Expr,
35    revision: u64,
36}
37
38pub(crate) struct TreeState {
39    storage_name: String,
40    namespace: Namespace,
41    stores: ExprTreeStores,
42    calc: ExprTreeCalc,
43    entries: BTreeMap<String, EntryIdentity>,
44    cells: BTreeMap<String, RuntimeCell>,
45    next_cell_id: u64,
46    next_dir_id: u64,
47    source_revision: u64,
48}
49
50impl TreeState {
51    pub(crate) fn new_cell(
52        &mut self,
53        parent: &str,
54        name: Option<&str>,
55        source: Expr,
56    ) -> std::result::Result<String, String> {
57        self.ensure_room()?;
58        let parent_path = resolve_path(parent, &TablePath::root())?;
59        self.ensure_writable(&parent_path)?;
60        let parent_id = self.dir_id(&parent_path)?;
61        self.next_cell_id = self.next_cell_id.saturating_add(1);
62        let cell_id = CellId::new(format!(
63            "cell:{}:{}",
64            self.namespace.tree_id(),
65            self.next_cell_id
66        ))
67        .map_err(debug_error)?;
68        let reserved_name = self.with_writer(|namespace, lane| {
69            let reserved =
70                reserve_name(namespace, lane, &parent_id, name, GeneratedNameKind::Cell)?;
71            let create = CellCreate::new(
72                cell_id.clone(),
73                parent_id.clone(),
74                reserved.clone(),
75                NodeKind::Source,
76            );
77            namespace.create_cell(lane, create)?;
78            Ok(reserved)
79        })?;
80        let path = child_path(&parent_path, reserved_name.as_str())?;
81        let source_projection = source_projection(&source)?;
82        self.source_revision = self.source_revision.saturating_add(1);
83        let revision = self.source_revision;
84        let key = path.to_absolute_reference();
85        self.entries
86            .insert(key.clone(), EntryIdentity::Cell(cell_id.clone()));
87        self.cells.insert(
88            key.clone(),
89            RuntimeCell {
90                id: cell_id.clone(),
91                source: source.clone(),
92                revision,
93            },
94        );
95        let mut pending = ExprTreeStores::prepare_source_control_commit(
96            BTreeMap::from([(cell_id, SourceEntry::new(source_projection))]),
97            BTreeMap::from([(
98                format!("source-revision:{key}"),
99                ControlEntry::Counter(revision),
100            )]),
101        );
102        self.stores.commit_source(&mut pending);
103        self.stores.commit_control(&mut pending);
104        self.calc.set_cell(path, source);
105        self.run_ready_automatic();
106        Ok(key)
107    }
108
109    pub(crate) fn new_dir(
110        &mut self,
111        parent: &str,
112        name: Option<&str>,
113    ) -> std::result::Result<String, String> {
114        let parent_path = resolve_path(parent, &TablePath::root())?;
115        self.create_dir(&parent_path, name, false)
116    }
117
118    fn create_dir(
119        &mut self,
120        parent_path: &TablePath,
121        name: Option<&str>,
122        mounting: bool,
123    ) -> std::result::Result<String, String> {
124        self.ensure_room()?;
125        self.ensure_writable(parent_path)?;
126        if !mounting && self.table_mount_contains(parent_path) {
127            return Err(format!(
128                "new-dir rejected: Table mount {parent_path} is a leaf"
129            ));
130        }
131        let parent_id = self.dir_id(parent_path)?;
132        self.next_dir_id = self.next_dir_id.saturating_add(1);
133        let dir_id = DirId::new(format!(
134            "dir:{}:{}",
135            self.namespace.tree_id(),
136            self.next_dir_id
137        ))
138        .map_err(debug_error)?;
139        let reserved_name = self.with_writer(|namespace, lane| {
140            let reserved = reserve_name(namespace, lane, &parent_id, name, GeneratedNameKind::Dir)?;
141            namespace.create_dir(
142                lane,
143                dir_id.clone(),
144                &parent_id,
145                reserved.clone(),
146                CodecPolicyPatch::empty(),
147            )?;
148            Ok(reserved)
149        })?;
150        let path = child_path(parent_path, reserved_name.as_str())?;
151        let key = path.to_absolute_reference();
152        self.entries.insert(key.clone(), EntryIdentity::Dir(dir_id));
153        Ok(key)
154    }
155
156    pub(crate) fn mount(
157        &mut self,
158        path: &str,
159        backend: BackendKind,
160        resource: MountResource,
161        epoch: MountEpoch,
162    ) -> std::result::Result<String, String> {
163        let target = resolve_path(path, &TablePath::root())?;
164        if target.is_root() {
165            return Err("mount rejected: root is the required root Dir".to_owned());
166        }
167        let (parent, name) = split_path(&target)?;
168        let created = self.create_dir(&parent, Some(&name), true)?;
169        let descriptor = match resource {
170            MountResource::Table => MountDescriptor::table(target.clone(), backend, epoch),
171            MountResource::Dir => MountDescriptor::dir(target.clone(), backend, epoch),
172        };
173        if let Err(error) = self.stores.mount(descriptor) {
174            let _ = self.delete_empty_dir(&target);
175            return Err(debug_error(error));
176        }
177        self.calc.mount(target, resource, backend, epoch);
178        Ok(created)
179    }
180
181    pub(crate) fn unmount(&mut self, path: &str) -> std::result::Result<bool, String> {
182        let target = resolve_path(path, &TablePath::root())?;
183        self.ensure_empty_dir(&target)?;
184        self.stores.unmount(&target).map_err(debug_error)?;
185        self.calc.unmount(&target);
186        self.delete_empty_dir(&target)?;
187        Ok(true)
188    }
189
190    pub(crate) fn move_entry(
191        &mut self,
192        from: &str,
193        to: &str,
194    ) -> std::result::Result<String, String> {
195        let from = resolve_path(from, &TablePath::root())?;
196        let to = resolve_path(to, &TablePath::root())?;
197        if from.is_root() || to.is_root() {
198            return Err("move rejected: root cannot move or be replaced".to_owned());
199        }
200        self.ensure_writable(&from)?;
201        let (to_parent, to_name) = split_path(&to)?;
202        self.ensure_writable(&to_parent)?;
203        let parent_id = self.dir_id(&to_parent)?;
204        let new_name = NamespaceName::new(to_name).map_err(debug_error)?;
205        let from_key = from.to_absolute_reference();
206        let identity = self
207            .entries
208            .get(&from_key)
209            .cloned()
210            .ok_or_else(|| format!("missing namespace entry {from_key}"))?;
211        if self
212            .stores
213            .mounts()
214            .any(|mount| path_within(&from, mount.path()))
215        {
216            return Err(format!(
217                "mounted subtree {from} must be unmounted before move"
218            ));
219        }
220        if self.entries.contains_key(&to.to_absolute_reference()) {
221            return Err(format!("target path already exists: {to}"));
222        }
223        match &identity {
224            EntryIdentity::Cell(id) => {
225                let id = id.clone();
226                self.with_writer(|namespace, lane| {
227                    namespace.move_cell(lane, &id, &parent_id, new_name)
228                })?;
229                self.calc.move_cell(&from, to.clone());
230            }
231            EntryIdentity::Dir(id) => {
232                let id = id.clone();
233                self.with_writer(|namespace, lane| {
234                    namespace.move_dir(lane, &id, &parent_id, new_name)
235                })?;
236            }
237        }
238        self.rekey_subtree(&from, &to)?;
239        Ok(to.to_absolute_reference())
240    }
241
242    pub(crate) fn rename_entry(
243        &mut self,
244        path: &str,
245        name: &str,
246    ) -> std::result::Result<String, String> {
247        let path = resolve_path(path, &TablePath::root())?;
248        let (parent, _) = split_path(&path)?;
249        let target = child_path(&parent, name)?;
250        self.move_entry(
251            &path.to_absolute_reference(),
252            &target.to_absolute_reference(),
253        )
254    }
255
256    pub(crate) fn delete(&mut self, path: &str) -> std::result::Result<bool, String> {
257        let path = resolve_path(path, &TablePath::root())?;
258        let key = path.to_absolute_reference();
259        let identity = self
260            .entries
261            .get(&key)
262            .cloned()
263            .ok_or_else(|| format!("missing namespace entry {key}"))?;
264        self.ensure_writable(&path)?;
265        match identity {
266            EntryIdentity::Cell(id) => {
267                self.with_writer(|namespace, lane| namespace.delete_cell(lane, &id))?;
268                self.calc.remove_cell(&path);
269                self.stores.remove_source(&id);
270                self.entries.remove(&key);
271                self.cells.remove(&key);
272            }
273            EntryIdentity::Dir(_) => {
274                self.ensure_empty_dir(&path)?;
275                if self.mount_at(&path).is_some() {
276                    return Err(format!("mounted path {path} must be unmounted first"));
277                }
278                self.delete_empty_dir(&path)?;
279            }
280        }
281        Ok(true)
282    }
283
284    pub(crate) fn set_expr(
285        &mut self,
286        path: &str,
287        source: Expr,
288    ) -> std::result::Result<String, String> {
289        let path = resolve_path(path, &TablePath::root())?;
290        let key = path.to_absolute_reference();
291        self.ensure_writable(&path)?;
292        let projection = source_projection(&source)?;
293        let cell = self
294            .cells
295            .get_mut(&key)
296            .ok_or_else(|| format!("not a cell: {key}"))?;
297        self.source_revision = self.source_revision.saturating_add(1);
298        cell.source = source.clone();
299        cell.revision = self.source_revision;
300        let mut pending = ExprTreeStores::prepare_source_control_commit(
301            BTreeMap::from([(cell.id.clone(), SourceEntry::new(projection))]),
302            BTreeMap::from([(
303                format!("source-revision:{key}"),
304                ControlEntry::Counter(cell.revision),
305            )]),
306        );
307        self.stores.commit_source(&mut pending);
308        self.stores.commit_control(&mut pending);
309        self.calc.set_cell(path, source);
310        self.run_ready_automatic();
311        Ok(key)
312    }
313
314    pub(crate) fn list(
315        &self,
316        path: &str,
317    ) -> std::result::Result<Vec<(String, &'static str)>, String> {
318        let path = resolve_path(path, &TablePath::root())?;
319        self.dir_id(&path)?;
320        let mut rows = self
321            .entries
322            .iter()
323            .filter_map(|(key, identity)| {
324                let candidate = TablePath::parse_absolute(key).ok()?;
325                let (parent, _) = split_path(&candidate).ok()?;
326                if parent != path {
327                    return None;
328                }
329                let kind = match identity {
330                    EntryIdentity::Cell(_) => "cell",
331                    EntryIdentity::Dir(_) if self.mount_at(&candidate).is_some() => "mount",
332                    EntryIdentity::Dir(_) => "dir",
333                };
334                Some((key.clone(), kind))
335            })
336            .collect::<Vec<_>>();
337        rows.sort();
338        if rows.len() > MAX_LIST_ITEMS {
339            return Err(format!(
340                "list exceeds hard item limit {MAX_LIST_ITEMS} at {path}"
341            ));
342        }
343        Ok(rows)
344    }
345
346    fn ensure_room(&self) -> std::result::Result<(), String> {
347        if self.entries.len() >= MAX_TREE_NODES {
348            Err(format!("tree node limit {MAX_TREE_NODES} reached"))
349        } else {
350            Ok(())
351        }
352    }
353
354    fn entry(&self, path: &TablePath) -> std::result::Result<EntryIdentity, String> {
355        self.entries
356            .get(&path.to_absolute_reference())
357            .cloned()
358            .ok_or_else(|| format!("missing namespace entry {path}"))
359    }
360
361    fn dir_id(&self, path: &TablePath) -> std::result::Result<DirId, String> {
362        match self.entry(path)? {
363            EntryIdentity::Dir(id) => Ok(id),
364            EntryIdentity::Cell(_) => Err(format!("not a directory: {path}")),
365        }
366    }
367
368    fn cell(&self, path: &TablePath) -> std::result::Result<&RuntimeCell, String> {
369        self.cells
370            .get(&path.to_absolute_reference())
371            .ok_or_else(|| format!("not a cell: {path}"))
372    }
373
374    fn with_writer<T>(
375        &mut self,
376        action: impl FnOnce(
377            &mut Namespace,
378            WriterLane,
379        ) -> std::result::Result<T, sim_expr_tree_core::NamespaceError>,
380    ) -> std::result::Result<T, String> {
381        let lane = self.namespace.acquire_writer().map_err(debug_error)?;
382        let value = action(&mut self.namespace, lane);
383        let released = self.namespace.release_writer(lane);
384        match (value, released) {
385            (Ok(value), Ok(())) => Ok(value),
386            (Err(error), _) | (_, Err(error)) => Err(debug_error(error)),
387        }
388    }
389
390    fn delete_empty_dir(&mut self, path: &TablePath) -> std::result::Result<(), String> {
391        let key = path.to_absolute_reference();
392        let id = self.dir_id(path)?;
393        self.with_writer(|namespace, lane| namespace.delete_dir(lane, &id))?;
394        self.entries.remove(&key);
395        Ok(())
396    }
397
398    fn ensure_empty_dir(&self, path: &TablePath) -> std::result::Result<(), String> {
399        let prefix = format!("{}/", path.to_absolute_reference().trim_end_matches('/'));
400        if self
401            .entries
402            .keys()
403            .any(|entry| entry != &path.to_absolute_reference() && entry.starts_with(&prefix))
404        {
405            Err(format!("directory is not empty: {path}"))
406        } else {
407            Ok(())
408        }
409    }
410
411    fn rekey_subtree(
412        &mut self,
413        from: &TablePath,
414        to: &TablePath,
415    ) -> std::result::Result<(), String> {
416        let from_key = from.to_absolute_reference();
417        let to_key = to.to_absolute_reference();
418        let descendants = self
419            .entries
420            .keys()
421            .filter(|key| {
422                *key == &from_key
423                    || key
424                        .strip_prefix(&from_key)
425                        .is_some_and(|tail| tail.starts_with('/'))
426            })
427            .cloned()
428            .collect::<Vec<_>>();
429        if descendants.len() > MAX_TREE_NODES {
430            return Err("move subtree exceeds tree node limit".to_owned());
431        }
432        for old in descendants {
433            let suffix = old.strip_prefix(&from_key).expect("selected prefix");
434            let new = format!("{to_key}{suffix}");
435            let identity = self.entries.remove(&old).expect("selected entry");
436            if let Some(cell) = self.cells.remove(&old) {
437                let old_path = TablePath::parse_absolute(&old).map_err(debug_error)?;
438                let new_path = TablePath::parse_absolute(&new).map_err(debug_error)?;
439                if old != from_key {
440                    self.calc.move_cell(&old_path, new_path);
441                }
442                self.cells.insert(new.clone(), cell);
443            }
444            self.entries.insert(new, identity);
445        }
446        Ok(())
447    }
448
449    fn ensure_writable(&self, path: &TablePath) -> std::result::Result<(), String> {
450        for mount in self.stores.mounts() {
451            if path_within(mount.path(), path) && mount.backend() == BackendKind::ReadOnly {
452                return Err(format!("mounted backend is read-only at {}", mount.path()));
453            }
454        }
455        Ok(())
456    }
457
458    fn table_mount_contains(&self, path: &TablePath) -> bool {
459        self.stores.mounts().any(|mount| {
460            mount.resource() == MountResource::Table && path_within(mount.path(), path)
461        })
462    }
463
464    fn mount_at(&self, path: &TablePath) -> Option<&MountDescriptor> {
465        self.stores.mounts().find(|mount| mount.path() == path)
466    }
467}
468
469fn reserve_name(
470    namespace: &mut Namespace,
471    lane: WriterLane,
472    parent: &DirId,
473    name: Option<&str>,
474    generated: GeneratedNameKind,
475) -> std::result::Result<NamespaceName, sim_expr_tree_core::NamespaceError> {
476    match name {
477        Some(name) => namespace.reserve_name(lane, parent, NamespaceName::new(name)?),
478        None => namespace.reserve_generated_name(lane, parent, generated),
479    }
480}