Skip to main content

icydb_schema/node/
store.rs

1use crate::node::{
2    validate_app_memory_id, validate_memory_id_in_range, validate_memory_id_not_reserved,
3    validate_stable_key, validate_stable_key_segment,
4};
5use crate::prelude::*;
6
7///
8/// Store
9///
10/// Schema node describing stable IC BTreeMap memories that store:
11/// - primary entity data
12/// - all index data for that entity
13/// - persisted schema metadata for that store
14///
15
16#[derive(Clone, Debug, Serialize)]
17pub struct Store {
18    def: Def,
19    ident: &'static str,
20    name: &'static str,
21    canister: &'static str,
22    data_memory_id: u8,
23    index_memory_id: u8,
24    schema_memory_id: u8,
25}
26
27impl Store {
28    #[must_use]
29    pub const fn new(
30        def: Def,
31        ident: &'static str,
32        store_name: &'static str,
33        canister: &'static str,
34        data_memory_id: u8,
35        index_memory_id: u8,
36        schema_memory_id: u8,
37    ) -> Self {
38        Self {
39            def,
40            ident,
41            name: store_name,
42            canister,
43            data_memory_id,
44            index_memory_id,
45            schema_memory_id,
46        }
47    }
48
49    #[must_use]
50    pub const fn def(&self) -> &Def {
51        &self.def
52    }
53
54    #[must_use]
55    pub const fn ident(&self) -> &'static str {
56        self.ident
57    }
58
59    #[must_use]
60    pub const fn store_name(&self) -> &'static str {
61        self.name
62    }
63
64    #[must_use]
65    pub const fn canister(&self) -> &'static str {
66        self.canister
67    }
68
69    #[must_use]
70    pub const fn data_memory_id(&self) -> u8 {
71        self.data_memory_id
72    }
73
74    #[must_use]
75    pub const fn index_memory_id(&self) -> u8 {
76        self.index_memory_id
77    }
78
79    #[must_use]
80    pub const fn schema_memory_id(&self) -> u8 {
81        self.schema_memory_id
82    }
83
84    #[must_use]
85    pub fn data_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
86        self.allocation(memory_namespace, StoreMemoryRole::Data)
87    }
88
89    #[must_use]
90    pub fn index_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
91        self.allocation(memory_namespace, StoreMemoryRole::Index)
92    }
93
94    #[must_use]
95    pub fn schema_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
96        self.allocation(memory_namespace, StoreMemoryRole::Schema)
97    }
98
99    #[must_use]
100    pub fn allocation(
101        &self,
102        memory_namespace: &str,
103        role: StoreMemoryRole,
104    ) -> StableMemoryAllocation {
105        let memory_id = match role {
106            StoreMemoryRole::Data => self.data_memory_id,
107            StoreMemoryRole::Index => self.index_memory_id,
108            StoreMemoryRole::Schema => self.schema_memory_id,
109        };
110
111        StableMemoryAllocation::new(
112            memory_id,
113            stable_memory_key(memory_namespace, self.store_name(), role.as_str()),
114            None,
115            None,
116        )
117    }
118}
119
120#[derive(Clone, Copy, Debug, Eq, PartialEq)]
121pub enum StoreMemoryRole {
122    Data,
123    Index,
124    Schema,
125}
126
127impl StoreMemoryRole {
128    #[must_use]
129    pub const fn as_str(self) -> &'static str {
130        match self {
131            Self::Data => "data",
132            Self::Index => "index",
133            Self::Schema => "schema",
134        }
135    }
136}
137
138#[derive(Clone, Debug, Eq, PartialEq)]
139pub struct StableMemoryAllocation {
140    memory_id: u8,
141    stable_key: String,
142    schema_version: Option<u32>,
143    schema_fingerprint: Option<String>,
144}
145
146impl StableMemoryAllocation {
147    #[must_use]
148    pub const fn new(
149        memory_id: u8,
150        stable_key: String,
151        schema_version: Option<u32>,
152        schema_fingerprint: Option<String>,
153    ) -> Self {
154        Self {
155            memory_id,
156            stable_key,
157            schema_version,
158            schema_fingerprint,
159        }
160    }
161
162    #[must_use]
163    pub const fn memory_id(&self) -> u8 {
164        self.memory_id
165    }
166
167    #[must_use]
168    pub const fn stable_key(&self) -> &str {
169        self.stable_key.as_str()
170    }
171
172    #[must_use]
173    pub const fn schema_version(&self) -> Option<u32> {
174        self.schema_version
175    }
176
177    #[must_use]
178    pub const fn schema_fingerprint(&self) -> Option<&str> {
179        match &self.schema_fingerprint {
180            Some(value) => Some(value.as_str()),
181            None => None,
182        }
183    }
184
185    #[must_use]
186    pub fn same_identity_as(&self, other: &Self) -> bool {
187        self.memory_id == other.memory_id && self.stable_key == other.stable_key
188    }
189}
190
191#[must_use]
192pub fn stable_memory_key(memory_namespace: &str, store_name: &str, role: &str) -> String {
193    format!("icydb.{memory_namespace}.{store_name}.{role}.v1")
194}
195
196impl MacroNode for Store {
197    fn as_any(&self) -> &dyn std::any::Any {
198        self
199    }
200}
201
202impl ValidateNode for Store {
203    fn validate(&self) -> Result<(), ErrorTree> {
204        let mut errs = ErrorTree::new();
205        let schema = schema_read();
206
207        match schema.cast_node::<Canister>(self.canister()) {
208            Ok(canister) => {
209                validate_stable_key_segment(&mut errs, "store store_name", self.store_name());
210
211                // Validate data memory ID
212                validate_memory_id_in_range(
213                    &mut errs,
214                    "data_memory_id",
215                    self.data_memory_id(),
216                    canister.memory_min(),
217                    canister.memory_max(),
218                );
219                validate_app_memory_id(&mut errs, "data_memory_id", self.data_memory_id());
220                validate_memory_id_not_reserved(&mut errs, "data_memory_id", self.data_memory_id());
221                validate_stable_key(
222                    &mut errs,
223                    "data stable key",
224                    self.data_allocation(canister.memory_namespace())
225                        .stable_key(),
226                );
227
228                // Validate index memory ID
229                validate_memory_id_in_range(
230                    &mut errs,
231                    "index_memory_id",
232                    self.index_memory_id(),
233                    canister.memory_min(),
234                    canister.memory_max(),
235                );
236                validate_app_memory_id(&mut errs, "index_memory_id", self.index_memory_id());
237                validate_memory_id_not_reserved(
238                    &mut errs,
239                    "index_memory_id",
240                    self.index_memory_id(),
241                );
242                validate_stable_key(
243                    &mut errs,
244                    "index stable key",
245                    self.index_allocation(canister.memory_namespace())
246                        .stable_key(),
247                );
248
249                // Validate schema memory ID
250                validate_memory_id_in_range(
251                    &mut errs,
252                    "schema_memory_id",
253                    self.schema_memory_id(),
254                    canister.memory_min(),
255                    canister.memory_max(),
256                );
257                validate_app_memory_id(&mut errs, "schema_memory_id", self.schema_memory_id());
258                validate_memory_id_not_reserved(
259                    &mut errs,
260                    "schema_memory_id",
261                    self.schema_memory_id(),
262                );
263                validate_stable_key(
264                    &mut errs,
265                    "schema stable key",
266                    self.schema_allocation(canister.memory_namespace())
267                        .stable_key(),
268                );
269
270                // Ensure the per-store memories are distinct.
271                if self.data_memory_id() == self.index_memory_id() {
272                    err!(
273                        errs,
274                        "data_memory_id and index_memory_id must differ (both are {})",
275                        self.data_memory_id(),
276                    );
277                }
278                if self.data_memory_id() == self.schema_memory_id() {
279                    err!(
280                        errs,
281                        "data_memory_id and schema_memory_id must differ (both are {})",
282                        self.data_memory_id(),
283                    );
284                }
285                if self.index_memory_id() == self.schema_memory_id() {
286                    err!(
287                        errs,
288                        "index_memory_id and schema_memory_id must differ (both are {})",
289                        self.index_memory_id(),
290                    );
291                }
292            }
293            Err(e) => errs.add(e),
294        }
295
296        errs.result()
297    }
298}
299
300impl VisitableNode for Store {
301    fn route_key(&self) -> String {
302        self.def().path()
303    }
304
305    fn drive<V: Visitor>(&self, v: &mut V) {
306        self.def().accept(v);
307    }
308}