1use crate::application::entity::json_to_metadata_value;
4use crate::application::ports::RuntimeEntityPort;
5use crate::storage::unified::{Metadata, MetadataValue};
6use reddb_rql::ast::{CreateTreeQuery, DropTreeQuery, TreeCommand, TreeNodeSpec, TreePosition};
7
8use super::*;
9
10const TREE_CHILD_EDGE_LABEL: &str = "TREE_CHILD";
11const TREE_METADATA_PREFIX: &str = "red.tree.";
12const TREE_METADATA_NAME: &str = "red.tree.name";
13const TREE_METADATA_MAX_CHILDREN: &str = "red.tree.max_children";
14const TREE_METADATA_CHILD_INDEX: &str = "red.tree.child_index";
15const TREE_OWNERSHIP_OWNED: &str = "owned";
16const TREE_AUTO_FIX_CONSERVATIVE: &str = "conservative";
17
18#[derive(Debug, Clone)]
19struct TreeIssue {
20 code: &'static str,
21 message: String,
22 entity_id: Option<EntityId>,
23}
24
25#[derive(Debug, Clone)]
26struct TreeNodeState {
27 metadata: Metadata,
28 max_children_override: Option<usize>,
29}
30
31#[derive(Debug, Clone)]
32struct TreeChildLink {
33 edge_id: EntityId,
34 parent_id: EntityId,
35 child_id: EntityId,
36 child_index: usize,
37}
38
39#[derive(Debug, Clone)]
40struct TreeState {
41 nodes: BTreeMap<EntityId, TreeNodeState>,
42 structural_edges: BTreeMap<EntityId, TreeChildLink>,
43 children_by_parent: BTreeMap<EntityId, Vec<TreeChildLink>>,
44 parent_by_child: BTreeMap<EntityId, TreeChildLink>,
45}
46
47#[derive(Debug, Clone)]
48struct TreeValidation {
49 issues: Vec<TreeIssue>,
50 depths: BTreeMap<EntityId, usize>,
51}
52
53#[derive(Debug, Clone)]
54struct TreeRebalancePlanRow {
55 node_id: EntityId,
56 old_parent_id: EntityId,
57 new_parent_id: EntityId,
58 old_index: usize,
59 new_index: usize,
60 old_depth: usize,
61 new_depth: usize,
62 changed: bool,
63}
64
65impl RedDBRuntime {
66 pub fn execute_create_tree(
67 &self,
68 raw_query: &str,
69 query: &CreateTreeQuery,
70 ) -> RedDBResult<RuntimeQueryResult> {
71 self.check_write(crate::runtime::write_gate::WriteKind::Ddl)?;
72 if query.default_max_children == 0 {
73 return Err(RedDBError::Query(
74 "tree default max children must be positive".to_string(),
75 ));
76 }
77
78 if self
79 .inner
80 .db
81 .tree_definition(&query.collection, &query.name)
82 .is_some()
83 {
84 if query.if_not_exists {
85 return Ok(RuntimeQueryResult::ok_message(
86 raw_query.to_string(),
87 &format!("tree '{}.{}' already exists", query.collection, query.name),
88 "create",
89 ));
90 }
91 return Err(RedDBError::Query(format!(
92 "tree '{}.{}' already exists",
93 query.collection, query.name
94 )));
95 }
96
97 let root = self.create_node_unchecked(self.build_tree_create_node_input(
98 &query.collection,
99 &query.name,
100 &query.root,
101 false,
102 )?)?;
103 self.ensure_tree_root_metadata(
104 &query.collection,
105 root.id,
106 &query.name,
107 query.root.max_children,
108 )?;
109 let now = current_tree_unix_ms();
110 let definition = crate::physical::PhysicalTreeDefinition {
111 collection: query.collection.clone(),
112 name: query.name.clone(),
113 root_id: root.id.raw(),
114 default_max_children: query.default_max_children,
115 ordered_children: true,
116 ownership: TREE_OWNERSHIP_OWNED.to_string(),
117 auto_fix_mode: TREE_AUTO_FIX_CONSERVATIVE.to_string(),
118 created_at_unix_ms: now,
119 updated_at_unix_ms: now,
120 };
121 self.inner
122 .db
123 .save_tree_definition(definition)
124 .map_err(|err| RedDBError::Internal(err.to_string()))?;
125 self.inner
126 .db
127 .persist_metadata()
128 .map_err(|err| RedDBError::Internal(err.to_string()))?;
129 self.note_table_write(&query.collection);
130 self.schema_vocabulary_apply(crate::runtime::schema_vocabulary::DdlEvent::CreateIndex {
134 collection: query.collection.clone(),
135 index: query.name.clone(),
136 columns: Vec::new(),
137 });
138
139 Ok(RuntimeQueryResult::ok_records(
140 raw_query.to_string(),
141 vec![
142 "collection".to_string(),
143 "tree_name".to_string(),
144 "root_id".to_string(),
145 "default_max_children".to_string(),
146 ],
147 vec![vec![
148 (
149 "collection".to_string(),
150 Value::text(query.collection.clone()),
151 ),
152 ("tree_name".to_string(), Value::text(query.name.clone())),
153 ("root_id".to_string(), Value::UnsignedInteger(root.id.raw())),
154 (
155 "default_max_children".to_string(),
156 Value::UnsignedInteger(query.default_max_children as u64),
157 ),
158 ]],
159 "create",
160 ))
161 }
162
163 pub fn execute_drop_tree(
164 &self,
165 raw_query: &str,
166 query: &DropTreeQuery,
167 ) -> RedDBResult<RuntimeQueryResult> {
168 self.check_write(crate::runtime::write_gate::WriteKind::Ddl)?;
169 let Some(definition) = self
170 .inner
171 .db
172 .tree_definition(&query.collection, &query.name)
173 else {
174 if query.if_exists {
175 return Ok(RuntimeQueryResult::ok_message(
176 raw_query.to_string(),
177 &format!("tree '{}.{}' does not exist", query.collection, query.name),
178 "drop",
179 ));
180 }
181 return Err(RedDBError::NotFound(format!(
182 "tree '{}.{}' not found",
183 query.collection, query.name
184 )));
185 };
186
187 let state = self.load_tree_state(&definition)?;
188 let node_ids: BTreeSet<EntityId> = state.nodes.keys().copied().collect();
189 let edge_ids = self.graph_edges_touching_nodes(&definition.collection, &node_ids)?;
190
191 for edge_id in edge_ids {
192 let _ = self.delete_entity_internal(&definition.collection, edge_id)?;
193 }
194
195 let mut ordered_nodes: Vec<EntityId> = node_ids.into_iter().collect();
196 ordered_nodes.sort_by(|left, right| right.cmp(left));
197 for node_id in ordered_nodes {
198 let _ = self.delete_entity_internal(&definition.collection, node_id)?;
199 }
200
201 self.inner
202 .db
203 .remove_tree_definition(&query.collection, &query.name)
204 .map_err(|err| RedDBError::Internal(err.to_string()))?;
205 self.inner
206 .db
207 .persist_metadata()
208 .map_err(|err| RedDBError::Internal(err.to_string()))?;
209 self.note_table_write(&query.collection);
210 self.schema_vocabulary_apply(crate::runtime::schema_vocabulary::DdlEvent::DropIndex {
212 collection: query.collection.clone(),
213 index: query.name.clone(),
214 });
215
216 Ok(RuntimeQueryResult::ok_message(
217 raw_query.to_string(),
218 &format!("tree '{}.{}' dropped", query.collection, query.name),
219 "drop",
220 ))
221 }
222
223 pub fn execute_tree_command(
224 &self,
225 raw_query: &str,
226 command: &TreeCommand,
227 ) -> RedDBResult<RuntimeQueryResult> {
228 self.check_write(crate::runtime::write_gate::WriteKind::Dml)?;
229 match command {
230 TreeCommand::Insert {
231 collection,
232 tree_name,
233 parent_id,
234 node,
235 position,
236 } => self.execute_tree_insert(
237 raw_query,
238 collection,
239 tree_name,
240 EntityId::new(*parent_id),
241 node,
242 *position,
243 ),
244 TreeCommand::Move {
245 collection,
246 tree_name,
247 node_id,
248 parent_id,
249 position,
250 } => self.execute_tree_move(
251 raw_query,
252 collection,
253 tree_name,
254 EntityId::new(*node_id),
255 EntityId::new(*parent_id),
256 *position,
257 ),
258 TreeCommand::Delete {
259 collection,
260 tree_name,
261 node_id,
262 } => {
263 self.execute_tree_delete(raw_query, collection, tree_name, EntityId::new(*node_id))
264 }
265 TreeCommand::Validate {
266 collection,
267 tree_name,
268 } => self.execute_tree_validate(raw_query, collection, tree_name),
269 TreeCommand::Rebalance {
270 collection,
271 tree_name,
272 dry_run,
273 } => self.execute_tree_rebalance(raw_query, collection, tree_name, *dry_run),
274 }
275 }
276
277 fn execute_tree_insert(
278 &self,
279 raw_query: &str,
280 collection: &str,
281 tree_name: &str,
282 parent_id: EntityId,
283 node: &TreeNodeSpec,
284 position: TreePosition,
285 ) -> RedDBResult<RuntimeQueryResult> {
286 let definition = self.require_tree_definition(collection, tree_name)?;
287 let state = self.load_tree_state(&definition)?;
288 self.ensure_tree_operable(&definition, &state)?;
289
290 if !state.nodes.contains_key(&parent_id) {
291 return Err(RedDBError::NotFound(format!(
292 "parent node '{}' was not found in tree '{}.{}'",
293 parent_id.raw(),
294 collection,
295 tree_name
296 )));
297 }
298
299 let mut children = child_id_list(&state.children_by_parent, parent_id);
300 let insert_index = resolve_tree_insert_position(position, children.len())?;
301 let parent_limit = effective_max_children(&definition, &state, parent_id)?;
302 if children.len() >= parent_limit {
303 return Err(RedDBError::Query(format!(
304 "parent node '{}' is at capacity ({}) in tree '{}.{}'",
305 parent_id.raw(),
306 parent_limit,
307 collection,
308 tree_name
309 )));
310 }
311
312 let created = self.create_node_unchecked(
313 self.build_tree_create_node_input(collection, tree_name, node, true)?,
314 )?;
315 children.insert(insert_index, created.id);
316 self.rewrite_parent_children(collection, tree_name, parent_id, &children, &state)?;
317 self.touch_tree_definition_timestamp(&definition)?;
318 self.note_table_write(collection);
319
320 Ok(RuntimeQueryResult::ok_records(
321 raw_query.to_string(),
322 vec![
323 "collection".to_string(),
324 "tree_name".to_string(),
325 "node_id".to_string(),
326 "parent_id".to_string(),
327 "child_index".to_string(),
328 ],
329 vec![vec![
330 (
331 "collection".to_string(),
332 Value::text(collection.to_string()),
333 ),
334 ("tree_name".to_string(), Value::text(tree_name.to_string())),
335 (
336 "node_id".to_string(),
337 Value::UnsignedInteger(created.id.raw()),
338 ),
339 (
340 "parent_id".to_string(),
341 Value::UnsignedInteger(parent_id.raw()),
342 ),
343 (
344 "child_index".to_string(),
345 Value::UnsignedInteger(insert_index as u64),
346 ),
347 ]],
348 "insert",
349 ))
350 }
351
352 fn execute_tree_move(
353 &self,
354 raw_query: &str,
355 collection: &str,
356 tree_name: &str,
357 node_id: EntityId,
358 new_parent_id: EntityId,
359 position: TreePosition,
360 ) -> RedDBResult<RuntimeQueryResult> {
361 let definition = self.require_tree_definition(collection, tree_name)?;
362 let state = self.load_tree_state(&definition)?;
363 self.ensure_tree_operable(&definition, &state)?;
364
365 if node_id.raw() == definition.root_id {
366 return Err(RedDBError::Query("cannot move the tree root".to_string()));
367 }
368 if !state.nodes.contains_key(&node_id) {
369 return Err(RedDBError::NotFound(format!(
370 "node '{}' was not found in tree '{}.{}'",
371 node_id.raw(),
372 collection,
373 tree_name
374 )));
375 }
376 if !state.nodes.contains_key(&new_parent_id) {
377 return Err(RedDBError::NotFound(format!(
378 "parent node '{}' was not found in tree '{}.{}'",
379 new_parent_id.raw(),
380 collection,
381 tree_name
382 )));
383 }
384 if node_id == new_parent_id {
385 return Err(RedDBError::Query(
386 "node cannot be moved under itself".to_string(),
387 ));
388 }
389 if subtree_ids(&state, node_id).contains(&new_parent_id) {
390 return Err(RedDBError::Query(
391 "node cannot be moved under one of its descendants".to_string(),
392 ));
393 }
394
395 let current_parent = state
396 .parent_by_child
397 .get(&node_id)
398 .map(|link| link.parent_id)
399 .ok_or_else(|| {
400 RedDBError::Query(format!(
401 "node '{}' does not have a structural parent in tree '{}.{}'",
402 node_id.raw(),
403 collection,
404 tree_name
405 ))
406 })?;
407
408 let mut old_parent_children = child_id_list(&state.children_by_parent, current_parent);
409 old_parent_children.retain(|child| *child != node_id);
410
411 let mut new_parent_children = if current_parent == new_parent_id {
412 old_parent_children.clone()
413 } else {
414 child_id_list(&state.children_by_parent, new_parent_id)
415 };
416
417 let insert_index = resolve_tree_insert_position(position, new_parent_children.len())?;
418 let new_parent_limit = effective_max_children(&definition, &state, new_parent_id)?;
419 if current_parent != new_parent_id && new_parent_children.len() >= new_parent_limit {
420 return Err(RedDBError::Query(format!(
421 "parent node '{}' is at capacity ({}) in tree '{}.{}'",
422 new_parent_id.raw(),
423 new_parent_limit,
424 collection,
425 tree_name
426 )));
427 }
428
429 new_parent_children.insert(insert_index, node_id);
430
431 if current_parent == new_parent_id {
432 self.rewrite_parent_children(
433 collection,
434 tree_name,
435 current_parent,
436 &new_parent_children,
437 &state,
438 )?;
439 } else {
440 self.rewrite_parent_children(
441 collection,
442 tree_name,
443 current_parent,
444 &old_parent_children,
445 &state,
446 )?;
447 self.rewrite_parent_children(
448 collection,
449 tree_name,
450 new_parent_id,
451 &new_parent_children,
452 &state,
453 )?;
454 }
455
456 self.touch_tree_definition_timestamp(&definition)?;
457 self.note_table_write(collection);
458
459 Ok(RuntimeQueryResult::ok_records(
460 raw_query.to_string(),
461 vec![
462 "collection".to_string(),
463 "tree_name".to_string(),
464 "node_id".to_string(),
465 "old_parent_id".to_string(),
466 "new_parent_id".to_string(),
467 "child_index".to_string(),
468 ],
469 vec![vec![
470 (
471 "collection".to_string(),
472 Value::text(collection.to_string()),
473 ),
474 ("tree_name".to_string(), Value::text(tree_name.to_string())),
475 ("node_id".to_string(), Value::UnsignedInteger(node_id.raw())),
476 (
477 "old_parent_id".to_string(),
478 Value::UnsignedInteger(current_parent.raw()),
479 ),
480 (
481 "new_parent_id".to_string(),
482 Value::UnsignedInteger(new_parent_id.raw()),
483 ),
484 (
485 "child_index".to_string(),
486 Value::UnsignedInteger(insert_index as u64),
487 ),
488 ]],
489 "update",
490 ))
491 }
492
493 fn execute_tree_delete(
494 &self,
495 raw_query: &str,
496 collection: &str,
497 tree_name: &str,
498 node_id: EntityId,
499 ) -> RedDBResult<RuntimeQueryResult> {
500 let definition = self.require_tree_definition(collection, tree_name)?;
501 let state = self.load_tree_state(&definition)?;
502 self.ensure_tree_operable(&definition, &state)?;
503
504 if !state.nodes.contains_key(&node_id) {
505 return Err(RedDBError::NotFound(format!(
506 "node '{}' was not found in tree '{}.{}'",
507 node_id.raw(),
508 collection,
509 tree_name
510 )));
511 }
512
513 if node_id.raw() == definition.root_id {
514 return self.execute_drop_tree(
515 raw_query,
516 &DropTreeQuery {
517 collection: collection.to_string(),
518 name: tree_name.to_string(),
519 if_exists: false,
520 },
521 );
522 }
523
524 let subtree = subtree_ids(&state, node_id);
525 let parent_id = state
526 .parent_by_child
527 .get(&node_id)
528 .map(|link| link.parent_id)
529 .ok_or_else(|| {
530 RedDBError::Query(format!(
531 "node '{}' does not have a structural parent in tree '{}.{}'",
532 node_id.raw(),
533 collection,
534 tree_name
535 ))
536 })?;
537 let surviving_children: Vec<EntityId> = child_id_list(&state.children_by_parent, parent_id)
538 .into_iter()
539 .filter(|child| *child != node_id)
540 .collect();
541
542 let edge_ids = self.graph_edges_touching_nodes(collection, &subtree)?;
543 for edge_id in edge_ids {
544 let _ = self.delete_entity_internal(collection, edge_id)?;
545 }
546
547 let mut ordered_nodes: Vec<EntityId> = subtree.iter().copied().collect();
548 ordered_nodes.sort_by(|left, right| right.cmp(left));
549 for subtree_node in ordered_nodes {
550 let _ = self.delete_entity_internal(collection, subtree_node)?;
551 }
552
553 self.rewrite_parent_children(
554 collection,
555 tree_name,
556 parent_id,
557 &surviving_children,
558 &state,
559 )?;
560 self.touch_tree_definition_timestamp(&definition)?;
561 self.note_table_write(collection);
562
563 Ok(RuntimeQueryResult::ok_records(
564 raw_query.to_string(),
565 vec![
566 "collection".to_string(),
567 "tree_name".to_string(),
568 "deleted_root_id".to_string(),
569 "deleted_node_count".to_string(),
570 ],
571 vec![vec![
572 (
573 "collection".to_string(),
574 Value::text(collection.to_string()),
575 ),
576 ("tree_name".to_string(), Value::text(tree_name.to_string())),
577 (
578 "deleted_root_id".to_string(),
579 Value::UnsignedInteger(node_id.raw()),
580 ),
581 (
582 "deleted_node_count".to_string(),
583 Value::UnsignedInteger(subtree.len() as u64),
584 ),
585 ]],
586 "delete",
587 ))
588 }
589
590 fn execute_tree_validate(
591 &self,
592 raw_query: &str,
593 collection: &str,
594 tree_name: &str,
595 ) -> RedDBResult<RuntimeQueryResult> {
596 let definition = self.require_tree_definition(collection, tree_name)?;
597 let state = self.load_tree_state(&definition)?;
598 let validation = self.validate_tree_state(&definition, &state);
599
600 if validation.issues.is_empty() {
601 return Ok(RuntimeQueryResult::ok_records(
602 raw_query.to_string(),
603 vec![
604 "ok".to_string(),
605 "code".to_string(),
606 "message".to_string(),
607 "entity_id".to_string(),
608 ],
609 vec![vec![
610 ("ok".to_string(), Value::Boolean(true)),
611 ("code".to_string(), Value::text("ok".to_string())),
612 (
613 "message".to_string(),
614 Value::text("tree is valid".to_string()),
615 ),
616 ("entity_id".to_string(), Value::Null),
617 ]],
618 "select",
619 ));
620 }
621
622 let rows = validation
623 .issues
624 .iter()
625 .map(|issue| {
626 vec![
627 ("ok".to_string(), Value::Boolean(false)),
628 ("code".to_string(), Value::text(issue.code.to_string())),
629 ("message".to_string(), Value::text(issue.message.clone())),
630 (
631 "entity_id".to_string(),
632 issue
633 .entity_id
634 .map(|entity_id| Value::UnsignedInteger(entity_id.raw()))
635 .unwrap_or(Value::Null),
636 ),
637 ]
638 })
639 .collect();
640 Ok(RuntimeQueryResult::ok_records(
641 raw_query.to_string(),
642 vec![
643 "ok".to_string(),
644 "code".to_string(),
645 "message".to_string(),
646 "entity_id".to_string(),
647 ],
648 rows,
649 "select",
650 ))
651 }
652
653 fn execute_tree_rebalance(
654 &self,
655 raw_query: &str,
656 collection: &str,
657 tree_name: &str,
658 dry_run: bool,
659 ) -> RedDBResult<RuntimeQueryResult> {
660 let definition = self.require_tree_definition(collection, tree_name)?;
661 let state = self.load_tree_state(&definition)?;
662 self.ensure_tree_operable(&definition, &state)?;
663 let plan = self.plan_tree_rebalance(&definition, &state)?;
664
665 if !dry_run {
666 let existing_edges: Vec<EntityId> = state.structural_edges.keys().copied().collect();
667 for edge_id in existing_edges {
668 let _ = self.delete_entity_internal(collection, edge_id)?;
669 }
670
671 for row in &plan {
672 self.create_edge_unchecked(self.build_tree_structural_edge_input(
673 collection,
674 tree_name,
675 row.new_parent_id,
676 row.node_id,
677 row.new_index,
678 ))?;
679 }
680
681 self.touch_tree_definition_timestamp(&definition)?;
682 self.note_table_write(collection);
683 }
684
685 let rows = plan
686 .iter()
687 .map(|row| {
688 vec![
689 (
690 "node_id".to_string(),
691 Value::UnsignedInteger(row.node_id.raw()),
692 ),
693 (
694 "old_parent_id".to_string(),
695 Value::UnsignedInteger(row.old_parent_id.raw()),
696 ),
697 (
698 "new_parent_id".to_string(),
699 Value::UnsignedInteger(row.new_parent_id.raw()),
700 ),
701 (
702 "old_index".to_string(),
703 Value::UnsignedInteger(row.old_index as u64),
704 ),
705 (
706 "new_index".to_string(),
707 Value::UnsignedInteger(row.new_index as u64),
708 ),
709 (
710 "old_depth".to_string(),
711 Value::UnsignedInteger(row.old_depth as u64),
712 ),
713 (
714 "new_depth".to_string(),
715 Value::UnsignedInteger(row.new_depth as u64),
716 ),
717 ("changed".to_string(), Value::Boolean(row.changed)),
718 ]
719 })
720 .collect();
721
722 Ok(RuntimeQueryResult::ok_records(
723 raw_query.to_string(),
724 vec![
725 "node_id".to_string(),
726 "old_parent_id".to_string(),
727 "new_parent_id".to_string(),
728 "old_index".to_string(),
729 "new_index".to_string(),
730 "old_depth".to_string(),
731 "new_depth".to_string(),
732 "changed".to_string(),
733 ],
734 rows,
735 "select",
736 ))
737 }
738
739 fn build_tree_create_node_input(
740 &self,
741 collection: &str,
742 tree_name: &str,
743 node: &TreeNodeSpec,
744 allow_max_children: bool,
745 ) -> RedDBResult<crate::application::CreateNodeInput> {
746 if allow_max_children && node.max_children == Some(0) {
747 return Err(RedDBError::Query(
748 "node max children must be positive".to_string(),
749 ));
750 }
751 self.ensure_tree_spec_is_user_safe(node)?;
752 let mut metadata = build_tree_node_metadata(tree_name, &node.metadata)?;
753 if allow_max_children {
754 if let Some(max_children) = node.max_children {
755 metadata.push((
756 TREE_METADATA_MAX_CHILDREN.to_string(),
757 MetadataValue::Int(max_children as i64),
758 ));
759 }
760 }
761 Ok(crate::application::CreateNodeInput {
762 collection: collection.to_string(),
763 label: node.label.clone(),
764 node_type: node.node_type.clone(),
765 properties: node.properties.clone(),
766 metadata,
767 embeddings: Vec::new(),
768 table_links: Vec::new(),
769 node_links: Vec::new(),
770 })
771 }
772
773 fn build_tree_structural_edge_input(
774 &self,
775 collection: &str,
776 tree_name: &str,
777 parent_id: EntityId,
778 child_id: EntityId,
779 child_index: usize,
780 ) -> crate::application::CreateEdgeInput {
781 crate::application::CreateEdgeInput {
782 collection: collection.to_string(),
783 label: TREE_CHILD_EDGE_LABEL.to_string(),
784 from: parent_id,
785 to: child_id,
786 weight: Some(1.0),
787 properties: Vec::new(),
788 metadata: vec![
789 (
790 TREE_METADATA_NAME.to_string(),
791 MetadataValue::String(tree_name.to_string()),
792 ),
793 (
794 TREE_METADATA_CHILD_INDEX.to_string(),
795 MetadataValue::Int(child_index as i64),
796 ),
797 ],
798 }
799 }
800
801 fn ensure_tree_spec_is_user_safe(&self, node: &TreeNodeSpec) -> RedDBResult<()> {
802 for (key, _) in &node.metadata {
803 if key.starts_with(TREE_METADATA_PREFIX) {
804 return Err(RedDBError::Query(format!(
805 "metadata key '{}' is reserved for managed trees",
806 key
807 )));
808 }
809 }
810 Ok(())
811 }
812
813 fn require_tree_definition(
814 &self,
815 collection: &str,
816 tree_name: &str,
817 ) -> RedDBResult<crate::physical::PhysicalTreeDefinition> {
818 self.inner
819 .db
820 .tree_definition(collection, tree_name)
821 .ok_or_else(|| {
822 RedDBError::NotFound(format!("tree '{}.{}' not found", collection, tree_name))
823 })
824 }
825
826 fn touch_tree_definition_timestamp(
827 &self,
828 definition: &crate::physical::PhysicalTreeDefinition,
829 ) -> RedDBResult<()> {
830 let mut updated = definition.clone();
831 updated.updated_at_unix_ms = current_tree_unix_ms();
832 self.inner
833 .db
834 .save_tree_definition(updated)
835 .map_err(|err| RedDBError::Internal(err.to_string()))?;
836 self.inner
837 .db
838 .persist_metadata()
839 .map_err(|err| RedDBError::Internal(err.to_string()))?;
840 Ok(())
841 }
842
843 fn ensure_tree_root_metadata(
844 &self,
845 collection: &str,
846 root_id: EntityId,
847 tree_name: &str,
848 max_children_override: Option<usize>,
849 ) -> RedDBResult<()> {
850 let store = self.inner.db.store();
851 let mut metadata = store.get_metadata(collection, root_id).unwrap_or_default();
852 metadata.set(
853 TREE_METADATA_NAME.to_string(),
854 MetadataValue::String(tree_name.to_string()),
855 );
856 match max_children_override {
857 Some(value) => metadata.set(
858 TREE_METADATA_MAX_CHILDREN.to_string(),
859 MetadataValue::Int(value as i64),
860 ),
861 None => {
862 metadata.remove(TREE_METADATA_MAX_CHILDREN);
863 }
864 }
865 store
866 .set_metadata(collection, root_id, metadata)
867 .map_err(|err| RedDBError::Internal(err.to_string()))
868 }
869
870 fn load_tree_state(
871 &self,
872 definition: &crate::physical::PhysicalTreeDefinition,
873 ) -> RedDBResult<TreeState> {
874 let store = self.inner.db.store();
875 let Some(manager) = store.get_collection(&definition.collection) else {
876 return Ok(TreeState {
877 nodes: BTreeMap::new(),
878 structural_edges: BTreeMap::new(),
879 children_by_parent: BTreeMap::new(),
880 parent_by_child: BTreeMap::new(),
881 });
882 };
883
884 let mut all_nodes = BTreeMap::new();
885 let mut candidate_links = Vec::new();
886 let mut member_node_ids = BTreeSet::new();
887 let root_id = EntityId::new(definition.root_id);
888
889 for entity in manager.query_all(|entity| {
890 matches!(
891 entity.kind,
892 EntityKind::GraphNode(_) | EntityKind::GraphEdge(_)
893 )
894 }) {
895 let metadata = store
896 .get_metadata(&definition.collection, entity.id)
897 .unwrap_or_default();
898 match &entity.kind {
899 EntityKind::GraphNode(_) => {
900 if metadata_tree_name(&metadata) == Some(definition.name.as_str()) {
901 member_node_ids.insert(entity.id);
902 }
903 all_nodes.insert(
904 entity.id,
905 TreeNodeState {
906 max_children_override: metadata_usize(
907 &metadata,
908 TREE_METADATA_MAX_CHILDREN,
909 ),
910 metadata,
911 },
912 );
913 }
914 EntityKind::GraphEdge(edge_kind) if edge_kind.label == TREE_CHILD_EDGE_LABEL => {
915 let Some(parent_id) = parse_tree_entity_id_text(&edge_kind.from_node) else {
916 continue;
917 };
918 let Some(child_id) = parse_tree_entity_id_text(&edge_kind.to_node) else {
919 continue;
920 };
921 if metadata_tree_name(&metadata) == Some(definition.name.as_str()) {
922 member_node_ids.insert(parent_id);
923 member_node_ids.insert(child_id);
924 }
925 let child_index =
926 metadata_usize(&metadata, TREE_METADATA_CHILD_INDEX).unwrap_or(usize::MAX);
927 candidate_links.push(TreeChildLink {
928 edge_id: entity.id,
929 parent_id,
930 child_id,
931 child_index,
932 });
933 }
934 _ => {}
935 }
936 }
937
938 let mut candidate_children_by_parent: BTreeMap<EntityId, Vec<TreeChildLink>> =
939 BTreeMap::new();
940 for link in &candidate_links {
941 candidate_children_by_parent
942 .entry(link.parent_id)
943 .or_default()
944 .push(link.clone());
945 }
946
947 for links in candidate_children_by_parent.values_mut() {
948 links.sort_by(|left, right| {
949 left.child_index
950 .cmp(&right.child_index)
951 .then_with(|| left.edge_id.cmp(&right.edge_id))
952 .then_with(|| left.child_id.cmp(&right.child_id))
953 });
954 for (derived_index, link) in links.iter_mut().enumerate() {
955 link.child_index = derived_index;
956 }
957 }
958
959 let mut queue = VecDeque::new();
960 queue.push_back(root_id);
961 while let Some(node_id) = queue.pop_front() {
962 if !member_node_ids.insert(node_id) {
963 continue;
964 }
965 if let Some(children) = candidate_children_by_parent.get(&node_id) {
966 for child in children {
967 member_node_ids.insert(child.parent_id);
968 member_node_ids.insert(child.child_id);
969 queue.push_back(child.child_id);
970 }
971 }
972 }
973
974 let mut nodes = BTreeMap::new();
975 for node_id in &member_node_ids {
976 if let Some(node) = all_nodes.get(node_id) {
977 nodes.insert(*node_id, node.clone());
978 }
979 }
980
981 let mut structural_edges = BTreeMap::new();
982 let mut children_by_parent: BTreeMap<EntityId, Vec<TreeChildLink>> = BTreeMap::new();
983 let mut parent_by_child = BTreeMap::new();
984 for links in candidate_children_by_parent.values() {
985 for link in links {
986 if !(member_node_ids.contains(&link.parent_id)
987 && member_node_ids.contains(&link.child_id))
988 {
989 continue;
990 }
991 structural_edges.insert(link.edge_id, link.clone());
992 children_by_parent
993 .entry(link.parent_id)
994 .or_default()
995 .push(link.clone());
996 parent_by_child.insert(link.child_id, link.clone());
997 }
998 }
999
1000 for links in children_by_parent.values_mut() {
1001 links.sort_by(|left, right| {
1002 left.child_index
1003 .cmp(&right.child_index)
1004 .then_with(|| left.child_id.cmp(&right.child_id))
1005 });
1006 }
1007
1008 Ok(TreeState {
1009 nodes,
1010 structural_edges,
1011 children_by_parent,
1012 parent_by_child,
1013 })
1014 }
1015
1016 fn validate_tree_state(
1017 &self,
1018 definition: &crate::physical::PhysicalTreeDefinition,
1019 state: &TreeState,
1020 ) -> TreeValidation {
1021 let mut issues = Vec::new();
1022 let root_id = EntityId::new(definition.root_id);
1023 if !state.nodes.contains_key(&root_id) {
1024 issues.push(TreeIssue {
1025 code: "missing_root",
1026 message: format!(
1027 "root node '{}' is missing from tree '{}.{}'",
1028 definition.root_id, definition.collection, definition.name
1029 ),
1030 entity_id: Some(root_id),
1031 });
1032 }
1033
1034 for (node_id, node) in &state.nodes {
1035 match metadata_tree_name(&node.metadata) {
1036 Some(name) if name == definition.name => {}
1037 Some(_) => issues.push(TreeIssue {
1038 code: "node_tree_name_mismatch",
1039 message: format!(
1040 "node '{}' is part of tree '{}.{}' but is missing '{}' metadata",
1041 node_id.raw(),
1042 definition.collection,
1043 definition.name,
1044 TREE_METADATA_NAME
1045 ),
1046 entity_id: Some(*node_id),
1047 }),
1048 None => {}
1049 }
1050 if metadata_invalid_max_children(&node.metadata) {
1051 issues.push(TreeIssue {
1052 code: "invalid_max_children",
1053 message: format!(
1054 "node '{}' has invalid '{}' metadata",
1055 node_id.raw(),
1056 TREE_METADATA_MAX_CHILDREN
1057 ),
1058 entity_id: Some(*node_id),
1059 });
1060 }
1061 }
1062
1063 let mut parent_counts: BTreeMap<EntityId, usize> = BTreeMap::new();
1064 for link in state.structural_edges.values() {
1065 parent_counts
1066 .entry(link.child_id)
1067 .and_modify(|count| *count += 1)
1068 .or_insert(1);
1069 if !state.nodes.contains_key(&link.parent_id) {
1070 issues.push(TreeIssue {
1071 code: "missing_parent_node",
1072 message: format!(
1073 "structural edge '{}' references missing parent node '{}'",
1074 link.edge_id.raw(),
1075 link.parent_id.raw()
1076 ),
1077 entity_id: Some(link.edge_id),
1078 });
1079 }
1080 if !state.nodes.contains_key(&link.child_id) {
1081 issues.push(TreeIssue {
1082 code: "missing_child_node",
1083 message: format!(
1084 "structural edge '{}' references missing child node '{}'",
1085 link.edge_id.raw(),
1086 link.child_id.raw()
1087 ),
1088 entity_id: Some(link.edge_id),
1089 });
1090 }
1091 }
1092
1093 for (child_id, count) in parent_counts {
1094 if count > 1 {
1095 issues.push(TreeIssue {
1096 code: "multiple_parents",
1097 message: format!("node '{}' has multiple structural parents", child_id.raw()),
1098 entity_id: Some(child_id),
1099 });
1100 }
1101 }
1102
1103 if let Some(link) = state.parent_by_child.get(&root_id) {
1104 issues.push(TreeIssue {
1105 code: "root_has_parent",
1106 message: format!(
1107 "root node '{}' has a structural parent '{}'",
1108 root_id.raw(),
1109 link.parent_id.raw()
1110 ),
1111 entity_id: Some(root_id),
1112 });
1113 }
1114
1115 for node_id in state.nodes.keys() {
1116 if *node_id == root_id {
1117 continue;
1118 }
1119 if !state.parent_by_child.contains_key(node_id) {
1120 issues.push(TreeIssue {
1121 code: "missing_parent",
1122 message: format!(
1123 "node '{}' is disconnected from root '{}'",
1124 node_id.raw(),
1125 root_id.raw()
1126 ),
1127 entity_id: Some(*node_id),
1128 });
1129 }
1130 }
1131
1132 for (parent_id, links) in &state.children_by_parent {
1133 let mut seen = BTreeSet::new();
1134 for (expected_index, link) in links.iter().enumerate() {
1135 if !seen.insert(link.child_index) {
1136 issues.push(TreeIssue {
1137 code: "duplicate_child_index",
1138 message: format!(
1139 "parent '{}' has duplicate child index '{}'",
1140 parent_id.raw(),
1141 link.child_index
1142 ),
1143 entity_id: Some(link.edge_id),
1144 });
1145 }
1146 if link.child_index != expected_index {
1147 issues.push(TreeIssue {
1148 code: "non_compact_child_index",
1149 message: format!(
1150 "parent '{}' children must use compact indexes starting at zero",
1151 parent_id.raw()
1152 ),
1153 entity_id: Some(link.edge_id),
1154 });
1155 break;
1156 }
1157 }
1158
1159 if let Ok(limit) = effective_max_children(definition, state, *parent_id) {
1160 if links.len() > limit {
1161 issues.push(TreeIssue {
1162 code: "max_children_exceeded",
1163 message: format!(
1164 "parent '{}' has {} children but limit is {}",
1165 parent_id.raw(),
1166 links.len(),
1167 limit
1168 ),
1169 entity_id: Some(*parent_id),
1170 });
1171 }
1172 }
1173 }
1174
1175 let mut depths = BTreeMap::new();
1176 let mut visited = BTreeSet::new();
1177 let mut stack = BTreeSet::new();
1178 if state.nodes.contains_key(&root_id) {
1179 validate_tree_cycles(
1180 root_id,
1181 0,
1182 state,
1183 &mut visited,
1184 &mut stack,
1185 &mut depths,
1186 &mut issues,
1187 );
1188 }
1189
1190 for node_id in state.nodes.keys() {
1191 if !depths.contains_key(node_id) {
1192 issues.push(TreeIssue {
1193 code: "unreachable_node",
1194 message: format!(
1195 "node '{}' is not reachable from root '{}'",
1196 node_id.raw(),
1197 root_id.raw()
1198 ),
1199 entity_id: Some(*node_id),
1200 });
1201 }
1202 }
1203
1204 TreeValidation { issues, depths }
1205 }
1206
1207 fn ensure_tree_operable(
1208 &self,
1209 definition: &crate::physical::PhysicalTreeDefinition,
1210 state: &TreeState,
1211 ) -> RedDBResult<()> {
1212 let validation = self.validate_tree_state(definition, state);
1213 let blocking_issue = validation.issues.into_iter().find(|issue| {
1214 !matches!(
1215 issue.code,
1216 "duplicate_child_index" | "non_compact_child_index"
1217 )
1218 });
1219 if let Some(issue) = blocking_issue {
1220 return Err(RedDBError::Query(format!(
1221 "tree '{}.{}' is not operable: {}",
1222 definition.collection, definition.name, issue.message
1223 )));
1224 }
1225 Ok(())
1226 }
1227
1228 fn rewrite_parent_children(
1229 &self,
1230 collection: &str,
1231 tree_name: &str,
1232 parent_id: EntityId,
1233 child_ids: &[EntityId],
1234 current_state: &TreeState,
1235 ) -> RedDBResult<()> {
1236 if let Some(existing_links) = current_state.children_by_parent.get(&parent_id) {
1237 for link in existing_links {
1238 let _ = self.delete_entity_internal(collection, link.edge_id)?;
1239 }
1240 }
1241 for (index, child_id) in child_ids.iter().enumerate() {
1242 self.create_edge_unchecked(self.build_tree_structural_edge_input(
1243 collection, tree_name, parent_id, *child_id, index,
1244 ))?;
1245 }
1246 Ok(())
1247 }
1248
1249 fn graph_edges_touching_nodes(
1250 &self,
1251 collection: &str,
1252 nodes: &BTreeSet<EntityId>,
1253 ) -> RedDBResult<BTreeSet<EntityId>> {
1254 let store = self.inner.db.store();
1255 let Some(manager) = store.get_collection(collection) else {
1256 return Ok(BTreeSet::new());
1257 };
1258 let mut edge_ids = BTreeSet::new();
1259 for entity in manager.query_all(|entity| matches!(entity.kind, EntityKind::GraphEdge(_))) {
1260 let EntityKind::GraphEdge(edge_kind) = &entity.kind else {
1261 continue;
1262 };
1263 let Some(from_id) = parse_tree_entity_id_text(&edge_kind.from_node) else {
1264 continue;
1265 };
1266 let Some(to_id) = parse_tree_entity_id_text(&edge_kind.to_node) else {
1267 continue;
1268 };
1269 if nodes.contains(&from_id) || nodes.contains(&to_id) {
1270 edge_ids.insert(entity.id);
1271 }
1272 }
1273 Ok(edge_ids)
1274 }
1275
1276 fn delete_entity_internal(&self, collection: &str, id: EntityId) -> RedDBResult<bool> {
1277 let store = self.inner.db.store();
1278 let deleted = store
1279 .delete(collection, id)
1280 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1281 if deleted {
1282 store.context_index().remove_entity(id);
1283 self.cdc_emit(
1284 crate::replication::cdc::ChangeOperation::Delete,
1285 collection,
1286 id.raw(),
1287 "entity",
1288 );
1289 }
1290 Ok(deleted)
1291 }
1292
1293 fn plan_tree_rebalance(
1294 &self,
1295 definition: &crate::physical::PhysicalTreeDefinition,
1296 state: &TreeState,
1297 ) -> RedDBResult<Vec<TreeRebalancePlanRow>> {
1298 let validation = self.validate_tree_state(definition, state);
1299 let root_id = EntityId::new(definition.root_id);
1300 if !validation.depths.contains_key(&root_id) {
1301 return Err(RedDBError::Query(format!(
1302 "tree '{}.{}' is missing its root '{}'",
1303 definition.collection, definition.name, definition.root_id
1304 )));
1305 }
1306
1307 let ordered = flatten_tree_preorder(state, root_id);
1308 let mut assignment_queue = VecDeque::new();
1309 assignment_queue.push_back(root_id);
1310 let mut assigned_children_count: BTreeMap<EntityId, usize> = BTreeMap::new();
1311 let mut new_depths = BTreeMap::new();
1312 new_depths.insert(root_id, 0usize);
1313
1314 let mut plan = Vec::with_capacity(ordered.len());
1315 for node_id in ordered {
1316 loop {
1317 let Some(candidate_parent) = assignment_queue.front().copied() else {
1318 return Err(RedDBError::Query(format!(
1319 "tree '{}.{}' does not have enough aggregate capacity to rebalance",
1320 definition.collection, definition.name
1321 )));
1322 };
1323 let assigned = assigned_children_count
1324 .get(&candidate_parent)
1325 .copied()
1326 .unwrap_or(0);
1327 let limit = effective_max_children(definition, state, candidate_parent)?;
1328 if assigned < limit {
1329 break;
1330 }
1331 assignment_queue.pop_front();
1332 }
1333
1334 let parent_id = assignment_queue.front().copied().ok_or_else(|| {
1335 RedDBError::Query(format!(
1336 "tree '{}.{}' does not have enough aggregate capacity to rebalance",
1337 definition.collection, definition.name
1338 ))
1339 })?;
1340 let entry = assigned_children_count.entry(parent_id).or_insert(0);
1341 let new_index = *entry;
1342 *entry += 1;
1343 let parent_depth = new_depths.get(&parent_id).copied().unwrap_or(0);
1344 new_depths.insert(node_id, parent_depth + 1);
1345 assignment_queue.push_back(node_id);
1346
1347 let old_link = state.parent_by_child.get(&node_id).ok_or_else(|| {
1348 RedDBError::Query(format!(
1349 "node '{}' is missing a structural parent in tree '{}.{}'",
1350 node_id.raw(),
1351 definition.collection,
1352 definition.name
1353 ))
1354 })?;
1355 let old_depth = validation.depths.get(&node_id).copied().unwrap_or(0);
1356 let new_depth = new_depths.get(&node_id).copied().unwrap_or(0);
1357 let changed = old_link.parent_id != parent_id
1358 || old_link.child_index != new_index
1359 || old_depth != new_depth;
1360
1361 plan.push(TreeRebalancePlanRow {
1362 node_id,
1363 old_parent_id: old_link.parent_id,
1364 new_parent_id: parent_id,
1365 old_index: old_link.child_index,
1366 new_index,
1367 old_depth,
1368 new_depth,
1369 changed,
1370 });
1371 }
1372
1373 Ok(plan)
1374 }
1375}
1376
1377fn current_tree_unix_ms() -> u128 {
1378 SystemTime::now()
1379 .duration_since(UNIX_EPOCH)
1380 .unwrap_or_default()
1381 .as_millis()
1382}
1383
1384fn build_tree_node_metadata(
1385 tree_name: &str,
1386 metadata_entries: &[(String, Value)],
1387) -> RedDBResult<Vec<(String, MetadataValue)>> {
1388 let mut metadata = Vec::with_capacity(metadata_entries.len() + 1);
1389 metadata.push((
1390 TREE_METADATA_NAME.to_string(),
1391 MetadataValue::String(tree_name.to_string()),
1392 ));
1393 for (key, value) in metadata_entries {
1394 metadata.push((key.clone(), storage_value_to_tree_metadata(value)?));
1395 }
1396 Ok(metadata)
1397}
1398
1399fn storage_value_to_tree_metadata(value: &Value) -> RedDBResult<MetadataValue> {
1400 Ok(match value {
1401 Value::Null => MetadataValue::Null,
1402 Value::Boolean(value) => MetadataValue::Bool(*value),
1403 Value::Integer(value) => MetadataValue::Int(*value),
1404 Value::UnsignedInteger(value) => {
1405 if *value <= i64::MAX as u64 {
1406 MetadataValue::Int(*value as i64)
1407 } else {
1408 MetadataValue::Timestamp(*value)
1409 }
1410 }
1411 Value::Float(value) => MetadataValue::Float(*value),
1412 Value::Text(value) => MetadataValue::String(value.to_string()),
1413 Value::Blob(value) => MetadataValue::Bytes(value.clone()),
1414 Value::Timestamp(value) => {
1415 if *value < 0 {
1416 return Err(RedDBError::Query(
1417 "negative timestamps are not supported in tree metadata".to_string(),
1418 ));
1419 }
1420 MetadataValue::Timestamp(*value as u64)
1421 }
1422 Value::Json(bytes) => {
1423 let json = crate::json::from_slice::<crate::json::Value>(bytes).map_err(|err| {
1424 RedDBError::Query(format!("failed to decode JSON metadata value: {err}"))
1425 })?;
1426 json_to_metadata_value(&json)?
1427 }
1428 Value::Vector(values) => MetadataValue::Array(
1429 values
1430 .iter()
1431 .map(|value| MetadataValue::Float(*value as f64))
1432 .collect(),
1433 ),
1434 Value::Array(values) => MetadataValue::Array(
1435 values
1436 .iter()
1437 .map(storage_value_to_tree_metadata)
1438 .collect::<RedDBResult<Vec<_>>>()?,
1439 ),
1440 other => MetadataValue::String(format!("{other:?}")),
1441 })
1442}
1443
1444fn resolve_tree_insert_position(position: TreePosition, len: usize) -> RedDBResult<usize> {
1445 match position {
1446 TreePosition::First => Ok(0),
1447 TreePosition::Last => Ok(len),
1448 TreePosition::Index(index) if index <= len => Ok(index),
1449 TreePosition::Index(index) => Err(RedDBError::Query(format!(
1450 "tree child position {} is out of bounds for {} children",
1451 index, len
1452 ))),
1453 }
1454}
1455
1456fn child_id_list(
1457 children_by_parent: &BTreeMap<EntityId, Vec<TreeChildLink>>,
1458 parent_id: EntityId,
1459) -> Vec<EntityId> {
1460 children_by_parent
1461 .get(&parent_id)
1462 .map(|links| links.iter().map(|link| link.child_id).collect())
1463 .unwrap_or_default()
1464}
1465
1466fn subtree_ids(state: &TreeState, root_id: EntityId) -> BTreeSet<EntityId> {
1467 let mut ids = BTreeSet::new();
1468 let mut queue = VecDeque::new();
1469 queue.push_back(root_id);
1470 while let Some(node_id) = queue.pop_front() {
1471 if !ids.insert(node_id) {
1472 continue;
1473 }
1474 if let Some(children) = state.children_by_parent.get(&node_id) {
1475 for child in children {
1476 queue.push_back(child.child_id);
1477 }
1478 }
1479 }
1480 ids
1481}
1482
1483fn flatten_tree_preorder(state: &TreeState, root_id: EntityId) -> Vec<EntityId> {
1484 let mut ordered = Vec::new();
1485 flatten_tree_preorder_inner(state, root_id, &mut ordered);
1486 ordered
1487}
1488
1489fn flatten_tree_preorder_inner(state: &TreeState, current: EntityId, ordered: &mut Vec<EntityId>) {
1490 if let Some(children) = state.children_by_parent.get(¤t) {
1491 for child in children {
1492 ordered.push(child.child_id);
1493 flatten_tree_preorder_inner(state, child.child_id, ordered);
1494 }
1495 }
1496}
1497
1498fn validate_tree_cycles(
1499 node_id: EntityId,
1500 depth: usize,
1501 state: &TreeState,
1502 visited: &mut BTreeSet<EntityId>,
1503 stack: &mut BTreeSet<EntityId>,
1504 depths: &mut BTreeMap<EntityId, usize>,
1505 issues: &mut Vec<TreeIssue>,
1506) {
1507 if stack.contains(&node_id) {
1508 issues.push(TreeIssue {
1509 code: "cycle_detected",
1510 message: format!("cycle detected at node '{}'", node_id.raw()),
1511 entity_id: Some(node_id),
1512 });
1513 return;
1514 }
1515 if !visited.insert(node_id) {
1516 return;
1517 }
1518 stack.insert(node_id);
1519 depths.insert(node_id, depth);
1520 if let Some(children) = state.children_by_parent.get(&node_id) {
1521 for child in children {
1522 validate_tree_cycles(
1523 child.child_id,
1524 depth + 1,
1525 state,
1526 visited,
1527 stack,
1528 depths,
1529 issues,
1530 );
1531 }
1532 }
1533 stack.remove(&node_id);
1534}
1535
1536fn effective_max_children(
1537 definition: &crate::physical::PhysicalTreeDefinition,
1538 state: &TreeState,
1539 node_id: EntityId,
1540) -> RedDBResult<usize> {
1541 let Some(node) = state.nodes.get(&node_id) else {
1542 return Err(RedDBError::NotFound(format!(
1543 "node '{}' was not found in tree '{}.{}'",
1544 node_id.raw(),
1545 definition.collection,
1546 definition.name
1547 )));
1548 };
1549 if let Some(value) = node.max_children_override {
1550 if value == 0 {
1551 return Err(RedDBError::Query(format!(
1552 "node '{}' has non-positive max children override",
1553 node_id.raw()
1554 )));
1555 }
1556 return Ok(value);
1557 }
1558 Ok(definition.default_max_children)
1559}
1560
1561fn metadata_tree_name(metadata: &Metadata) -> Option<&str> {
1562 match metadata.get(TREE_METADATA_NAME) {
1563 Some(MetadataValue::String(value)) => Some(value.as_str()),
1564 _ => None,
1565 }
1566}
1567
1568fn metadata_usize(metadata: &Metadata, key: &str) -> Option<usize> {
1569 match metadata.get(key) {
1570 Some(MetadataValue::Int(value)) if *value >= 0 => Some(*value as usize),
1571 Some(MetadataValue::Timestamp(value)) => (*value).try_into().ok(),
1572 _ => None,
1573 }
1574}
1575
1576fn metadata_invalid_max_children(metadata: &Metadata) -> bool {
1577 match metadata.get(TREE_METADATA_MAX_CHILDREN) {
1578 None => false,
1579 Some(MetadataValue::Int(value)) => *value <= 0,
1580 Some(MetadataValue::Timestamp(value)) => *value == 0,
1581 _ => true,
1582 }
1583}
1584
1585fn parse_tree_entity_id_text(value: &str) -> Option<EntityId> {
1586 value
1587 .strip_prefix('e')
1588 .unwrap_or(value)
1589 .parse::<u64>()
1590 .ok()
1591 .filter(|value| *value > 0)
1592 .map(EntityId::new)
1593}