Skip to main content

armour_typ/
graph.rs

1//! Declarative, serializable schema-graph model. No executable code here —
2//! the registry/validator live in `armdb::schema`; this model is what a
3//! future RPC `GetSchema` extension can ship to a GUI.
4
5use serde::Serialize;
6
7use crate::refs::PathSeg;
8use crate::typ::Typ;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
11pub enum CollectionKind {
12    /// Primary collection owning an entity (FK target for its brand).
13    Entity,
14    /// Derived collection synchronized from a source (hook/task).
15    SecondaryIndex,
16    /// Same entity duplicated for another access path.
17    Denormalized,
18    /// Recomputable counters/aggregates.
19    Counters,
20    /// Not related to other collections.
21    Standalone,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
25pub enum OnMissing {
26    /// Missing target — error finding.
27    Error,
28    /// Missing target — warning finding.
29    Warn,
30    /// Missing target is legal (e.g. reply to a deleted message).
31    AllowMissing,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
35pub enum RelationKind {
36    Fk {
37        /// Path of the ID field inside key/value ("k"/"v" prefix in `field_in`).
38        field_path: Vec<PathSeg>,
39        brand: String,
40        on_missing: OnMissing,
41        /// false — graph-only edge; validator reports it as UnvalidatedEdge
42        /// unless an explicit fk declaration covers it.
43        validated: bool,
44    },
45    /// `to` is a secondary index of `from`.
46    Index,
47    /// `to` is a denormalization of `from`.
48    Denorm,
49    /// Field `field` in `from` is recomputed from `to`.
50    Counter { field: String },
51}
52
53/// Value-model of a collection (how the value is serialized on disk).
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
55pub enum ValueModel {
56    /// Fixed-size, zerocopy-transmute (ZeroTree/ZeroMap).
57    Zero,
58    /// Codec value inline in the index (TypedTree/TypedMap).
59    Typed,
60    /// Codec value on disk + LRU cache, pointer in the index (VarTypedTree/VarTypedMap).
61    VarTyped,
62}
63
64/// Collection shape (access pattern).
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
66pub enum CollectionShape {
67    /// Ordered scan / range / prefix (SkipList).
68    Tree,
69    /// Point-lookup, no iteration (HashMap).
70    Map,
71}
72
73/// Durability backend.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
75pub enum StorageBackend {
76    /// Append-only log + compaction.
77    Bitcask,
78    /// In-place pwrite into fixed slots.
79    Fixed,
80}
81
82/// Physical storage class of a collection — fixed at `db.open_*`.
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
84pub struct StorageClass {
85    pub model: ValueModel,
86    pub shape: CollectionShape,
87    pub backend: StorageBackend,
88}
89
90#[derive(Debug, Clone, Serialize)]
91pub struct CollectionNode {
92    pub name: String,
93    pub kind: CollectionKind,
94    /// Value type descriptor (already serializable, reused as-is).
95    pub ty: Typ,
96    /// Key type descriptor (`S::K`). `None` for external nodes (no handle).
97    pub key_ty: Option<Typ>,
98    /// Brand of SelfId if the key is a branded ID.
99    pub self_brand: Option<String>,
100    /// Physical storage class. `None` for external nodes (no handle).
101    pub storage: Option<StorageClass>,
102}
103
104#[derive(Debug, Clone, Serialize)]
105pub struct RelationEdge {
106    pub from: String,
107    pub to: String,
108    pub kind: RelationKind,
109}
110
111#[derive(Debug, Clone, Serialize, Default)]
112pub struct SchemaGraph {
113    pub collections: Vec<CollectionNode>,
114    pub relations: Vec<RelationEdge>,
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use crate::scalar::ScalarTyp;
121
122    #[test]
123    fn graph_serializes_to_json() {
124        let g = SchemaGraph {
125            collections: vec![CollectionNode {
126                name: "users".into(),
127                kind: CollectionKind::Entity,
128                ty: Typ::Scalar(ScalarTyp::U64),
129                key_ty: None,
130                self_brand: Some("USR".into()),
131                storage: None,
132            }],
133            relations: vec![RelationEdge {
134                from: "messages".into(),
135                to: "users".into(),
136                kind: RelationKind::Fk {
137                    field_path: vec![PathSeg::Field("sender_id")],
138                    brand: "USR".into(),
139                    on_missing: OnMissing::Error,
140                    validated: true,
141                },
142            }],
143        };
144        let json = serde_json::to_string(&g).expect("serialize");
145        assert!(json.contains("\"users\""));
146        assert!(json.contains("\"sender_id\""));
147    }
148}