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
20pub const MAX_TREE_NODES: usize = 4_096;
22pub 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}