1use crate::db::index::IndexState;
7use candid::CandidType;
8use serde::Deserialize;
9
10#[cfg_attr(doc, doc = "StorageReport\n\nLive storage snapshot payload.")]
11#[derive(CandidType, Clone, Debug, Default, Deserialize)]
12pub struct StorageReport {
13 pub(crate) storage_data: Vec<DataStoreSnapshot>,
14 pub(crate) storage_index: Vec<IndexStoreSnapshot>,
15 pub(crate) schema_storage: Vec<SchemaStoreSnapshot>,
16 pub(crate) entity_storage: Vec<EntitySnapshot>,
17 pub(crate) corrupted_keys: u64,
18 pub(crate) corrupted_entries: u64,
19}
20
21#[cfg_attr(
22 doc,
23 doc = "IntegrityTotals\n\nAggregated integrity-scan counters across all stores."
24)]
25#[derive(CandidType, Clone, Debug, Default, Deserialize)]
26pub struct IntegrityTotals {
27 pub(crate) data_rows_scanned: u64,
28 pub(crate) index_entries_scanned: u64,
29 pub(crate) corrupted_data_keys: u64,
30 pub(crate) corrupted_data_rows: u64,
31 pub(crate) corrupted_index_keys: u64,
32 pub(crate) corrupted_index_entries: u64,
33 pub(crate) missing_index_entries: u64,
34 pub(crate) divergent_index_entries: u64,
35 pub(crate) orphan_index_references: u64,
36 pub(crate) misuse_findings: u64,
37}
38
39impl IntegrityTotals {
40 pub(super) const fn add_store_snapshot(&mut self, store: &IntegrityStoreSnapshot) {
41 self.data_rows_scanned = self
42 .data_rows_scanned
43 .saturating_add(store.data_rows_scanned);
44 self.index_entries_scanned = self
45 .index_entries_scanned
46 .saturating_add(store.index_entries_scanned);
47 self.corrupted_data_keys = self
48 .corrupted_data_keys
49 .saturating_add(store.corrupted_data_keys);
50 self.corrupted_data_rows = self
51 .corrupted_data_rows
52 .saturating_add(store.corrupted_data_rows);
53 self.corrupted_index_keys = self
54 .corrupted_index_keys
55 .saturating_add(store.corrupted_index_keys);
56 self.corrupted_index_entries = self
57 .corrupted_index_entries
58 .saturating_add(store.corrupted_index_entries);
59 self.missing_index_entries = self
60 .missing_index_entries
61 .saturating_add(store.missing_index_entries);
62 self.divergent_index_entries = self
63 .divergent_index_entries
64 .saturating_add(store.divergent_index_entries);
65 self.orphan_index_references = self
66 .orphan_index_references
67 .saturating_add(store.orphan_index_references);
68 self.misuse_findings = self.misuse_findings.saturating_add(store.misuse_findings);
69 }
70
71 #[must_use]
73 pub const fn data_rows_scanned(&self) -> u64 {
74 self.data_rows_scanned
75 }
76
77 #[must_use]
79 pub const fn index_entries_scanned(&self) -> u64 {
80 self.index_entries_scanned
81 }
82
83 #[must_use]
85 pub const fn corrupted_data_keys(&self) -> u64 {
86 self.corrupted_data_keys
87 }
88
89 #[must_use]
91 pub const fn corrupted_data_rows(&self) -> u64 {
92 self.corrupted_data_rows
93 }
94
95 #[must_use]
97 pub const fn corrupted_index_keys(&self) -> u64 {
98 self.corrupted_index_keys
99 }
100
101 #[must_use]
103 pub const fn corrupted_index_entries(&self) -> u64 {
104 self.corrupted_index_entries
105 }
106
107 #[must_use]
109 pub const fn missing_index_entries(&self) -> u64 {
110 self.missing_index_entries
111 }
112
113 #[must_use]
115 pub const fn divergent_index_entries(&self) -> u64 {
116 self.divergent_index_entries
117 }
118
119 #[must_use]
121 pub const fn orphan_index_references(&self) -> u64 {
122 self.orphan_index_references
123 }
124
125 #[must_use]
127 pub const fn misuse_findings(&self) -> u64 {
128 self.misuse_findings
129 }
130}
131
132#[cfg_attr(
133 doc,
134 doc = "IntegrityStoreSnapshot\n\nPer-store integrity findings and scan counters."
135)]
136#[derive(CandidType, Clone, Debug, Default, Deserialize)]
137pub struct IntegrityStoreSnapshot {
138 pub(crate) path: String,
139 pub(crate) data_rows_scanned: u64,
140 pub(crate) index_entries_scanned: u64,
141 pub(crate) corrupted_data_keys: u64,
142 pub(crate) corrupted_data_rows: u64,
143 pub(crate) corrupted_index_keys: u64,
144 pub(crate) corrupted_index_entries: u64,
145 pub(crate) missing_index_entries: u64,
146 pub(crate) divergent_index_entries: u64,
147 pub(crate) orphan_index_references: u64,
148 pub(crate) misuse_findings: u64,
149}
150
151impl IntegrityStoreSnapshot {
152 #[must_use]
154 pub(crate) fn new(path: String) -> Self {
155 Self {
156 path,
157 ..Self::default()
158 }
159 }
160
161 #[must_use]
163 pub const fn path(&self) -> &str {
164 self.path.as_str()
165 }
166
167 #[must_use]
169 pub const fn data_rows_scanned(&self) -> u64 {
170 self.data_rows_scanned
171 }
172
173 #[must_use]
175 pub const fn index_entries_scanned(&self) -> u64 {
176 self.index_entries_scanned
177 }
178
179 #[must_use]
181 pub const fn corrupted_data_keys(&self) -> u64 {
182 self.corrupted_data_keys
183 }
184
185 #[must_use]
187 pub const fn corrupted_data_rows(&self) -> u64 {
188 self.corrupted_data_rows
189 }
190
191 #[must_use]
193 pub const fn corrupted_index_keys(&self) -> u64 {
194 self.corrupted_index_keys
195 }
196
197 #[must_use]
199 pub const fn corrupted_index_entries(&self) -> u64 {
200 self.corrupted_index_entries
201 }
202
203 #[must_use]
205 pub const fn missing_index_entries(&self) -> u64 {
206 self.missing_index_entries
207 }
208
209 #[must_use]
211 pub const fn divergent_index_entries(&self) -> u64 {
212 self.divergent_index_entries
213 }
214
215 #[must_use]
217 pub const fn orphan_index_references(&self) -> u64 {
218 self.orphan_index_references
219 }
220
221 #[must_use]
223 pub const fn misuse_findings(&self) -> u64 {
224 self.misuse_findings
225 }
226}
227
228#[cfg_attr(
229 doc,
230 doc = "IntegrityReport\n\nFull integrity-scan output across all registered stores."
231)]
232#[derive(CandidType, Clone, Debug, Default, Deserialize)]
233pub struct IntegrityReport {
234 pub(crate) stores: Vec<IntegrityStoreSnapshot>,
235 pub(crate) totals: IntegrityTotals,
236}
237
238impl IntegrityReport {
239 #[must_use]
241 pub(crate) const fn new(stores: Vec<IntegrityStoreSnapshot>, totals: IntegrityTotals) -> Self {
242 Self { stores, totals }
243 }
244
245 #[must_use]
247 pub const fn stores(&self) -> &[IntegrityStoreSnapshot] {
248 self.stores.as_slice()
249 }
250
251 #[must_use]
253 pub const fn totals(&self) -> &IntegrityTotals {
254 &self.totals
255 }
256}
257
258impl StorageReport {
259 #[must_use]
261 pub(crate) const fn new(
262 storage_data: Vec<DataStoreSnapshot>,
263 storage_index: Vec<IndexStoreSnapshot>,
264 schema_storage: Vec<SchemaStoreSnapshot>,
265 entity_storage: Vec<EntitySnapshot>,
266 corrupted_keys: u64,
267 corrupted_entries: u64,
268 ) -> Self {
269 Self {
270 storage_data,
271 storage_index,
272 schema_storage,
273 entity_storage,
274 corrupted_keys,
275 corrupted_entries,
276 }
277 }
278
279 #[must_use]
281 pub const fn storage_data(&self) -> &[DataStoreSnapshot] {
282 self.storage_data.as_slice()
283 }
284
285 #[must_use]
287 pub const fn storage_index(&self) -> &[IndexStoreSnapshot] {
288 self.storage_index.as_slice()
289 }
290
291 #[must_use]
293 pub const fn schema_storage(&self) -> &[SchemaStoreSnapshot] {
294 self.schema_storage.as_slice()
295 }
296
297 #[must_use]
299 pub const fn entity_storage(&self) -> &[EntitySnapshot] {
300 self.entity_storage.as_slice()
301 }
302
303 #[must_use]
305 pub const fn corrupted_keys(&self) -> u64 {
306 self.corrupted_keys
307 }
308
309 #[must_use]
311 pub const fn corrupted_entries(&self) -> u64 {
312 self.corrupted_entries
313 }
314}
315
316#[cfg_attr(doc, doc = "SchemaStoreSnapshot\n\nSchema-store diagnostic row.")]
317#[derive(CandidType, Clone, Debug, Default, Deserialize)]
318pub struct SchemaStoreSnapshot {
319 pub(crate) path: String,
320 pub(crate) storage: StoreSnapshotStorageMode,
321 pub(crate) memory_id: Option<u8>,
322 pub(crate) stable_key: Option<String>,
323 pub(crate) schema_version: Option<u32>,
324 pub(crate) schema_fingerprint: Option<String>,
325 pub(crate) entity_count: u64,
326}
327
328#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
333pub enum StoreSnapshotStorageMode {
334 #[default]
335 Stable,
336 Heap,
337}
338
339impl StoreSnapshotStorageMode {
340 #[must_use]
342 pub const fn as_str(self) -> &'static str {
343 match self {
344 Self::Stable => "stable",
345 Self::Heap => "heap",
346 }
347 }
348}
349
350#[derive(Clone, Debug, Eq, PartialEq)]
351pub(crate) struct StoreSnapshotAllocationIdentity {
352 memory_id: u8,
353 stable_key: String,
354}
355
356impl StoreSnapshotAllocationIdentity {
357 pub(crate) const fn new(memory_id: u8, stable_key: String) -> Self {
358 Self {
359 memory_id,
360 stable_key,
361 }
362 }
363
364 const fn memory_id(&self) -> u8 {
365 self.memory_id
366 }
367}
368
369#[derive(Clone, Debug, Default, Eq, PartialEq)]
370pub(crate) struct StoreSnapshotSchemaMetadata {
371 schema_version: Option<u32>,
372 schema_fingerprint: Option<String>,
373}
374
375impl StoreSnapshotSchemaMetadata {
376 pub(crate) const fn absent() -> Self {
377 Self {
378 schema_version: None,
379 schema_fingerprint: None,
380 }
381 }
382
383 pub(crate) const fn new(schema_version: u32, schema_fingerprint: String) -> Self {
384 Self {
385 schema_version: Some(schema_version),
386 schema_fingerprint: Some(schema_fingerprint),
387 }
388 }
389
390 const fn schema_version(&self) -> Option<u32> {
391 self.schema_version
392 }
393
394 fn schema_fingerprint(&self) -> Option<String> {
395 self.schema_fingerprint.clone()
396 }
397}
398
399#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
400pub(crate) struct IndexStoreSnapshotStats {
401 entries: u64,
402 user_entries: u64,
403 system_entries: u64,
404 memory_bytes: u64,
405 state: IndexState,
406}
407
408impl IndexStoreSnapshotStats {
409 pub(crate) const fn new(
410 entries: u64,
411 user_entries: u64,
412 system_entries: u64,
413 memory_bytes: u64,
414 state: IndexState,
415 ) -> Self {
416 Self {
417 entries,
418 user_entries,
419 system_entries,
420 memory_bytes,
421 state,
422 }
423 }
424}
425
426impl SchemaStoreSnapshot {
427 #[must_use]
429 pub(crate) fn new(
430 path: String,
431 storage: StoreSnapshotStorageMode,
432 allocation: Option<StoreSnapshotAllocationIdentity>,
433 schema_metadata: StoreSnapshotSchemaMetadata,
434 entity_count: u64,
435 ) -> Self {
436 let memory_id = allocation
437 .as_ref()
438 .map(StoreSnapshotAllocationIdentity::memory_id);
439 let stable_key = match allocation {
440 Some(allocation) => Some(allocation.stable_key),
441 None => None,
442 };
443 Self {
444 path,
445 storage,
446 memory_id,
447 stable_key,
448 schema_version: schema_metadata.schema_version(),
449 schema_fingerprint: schema_metadata.schema_fingerprint(),
450 entity_count,
451 }
452 }
453
454 #[must_use]
456 pub const fn path(&self) -> &str {
457 self.path.as_str()
458 }
459
460 #[must_use]
462 pub const fn storage(&self) -> StoreSnapshotStorageMode {
463 self.storage
464 }
465
466 #[must_use]
468 pub const fn memory_id(&self) -> Option<u8> {
469 self.memory_id
470 }
471
472 #[must_use]
474 pub const fn stable_key(&self) -> Option<&str> {
475 match &self.stable_key {
476 Some(value) => Some(value.as_str()),
477 None => None,
478 }
479 }
480
481 #[must_use]
483 pub const fn schema_version(&self) -> Option<u32> {
484 self.schema_version
485 }
486
487 #[must_use]
489 pub const fn schema_fingerprint(&self) -> Option<&str> {
490 match &self.schema_fingerprint {
491 Some(value) => Some(value.as_str()),
492 None => None,
493 }
494 }
495
496 #[must_use]
498 pub const fn entity_count(&self) -> u64 {
499 self.entity_count
500 }
501}
502
503#[cfg_attr(doc, doc = "DataStoreSnapshot\n\nData-store snapshot row.")]
504#[derive(CandidType, Clone, Debug, Default, Deserialize)]
505pub struct DataStoreSnapshot {
506 pub(crate) path: String,
507 pub(crate) storage: StoreSnapshotStorageMode,
508 pub(crate) memory_id: Option<u8>,
509 pub(crate) stable_key: Option<String>,
510 pub(crate) schema_version: Option<u32>,
511 pub(crate) schema_fingerprint: Option<String>,
512 pub(crate) entries: u64,
513 pub(crate) memory_bytes: u64,
514}
515
516impl DataStoreSnapshot {
517 #[must_use]
519 pub(crate) fn new(
520 path: String,
521 storage: StoreSnapshotStorageMode,
522 allocation: Option<StoreSnapshotAllocationIdentity>,
523 schema_metadata: StoreSnapshotSchemaMetadata,
524 entries: u64,
525 memory_bytes: u64,
526 ) -> Self {
527 let memory_id = allocation
528 .as_ref()
529 .map(StoreSnapshotAllocationIdentity::memory_id);
530 let stable_key = match allocation {
531 Some(allocation) => Some(allocation.stable_key),
532 None => None,
533 };
534 Self {
535 path,
536 storage,
537 memory_id,
538 stable_key,
539 schema_version: schema_metadata.schema_version(),
540 schema_fingerprint: schema_metadata.schema_fingerprint(),
541 entries,
542 memory_bytes,
543 }
544 }
545
546 #[must_use]
548 pub const fn path(&self) -> &str {
549 self.path.as_str()
550 }
551
552 #[must_use]
554 pub const fn storage(&self) -> StoreSnapshotStorageMode {
555 self.storage
556 }
557
558 #[must_use]
560 pub const fn memory_id(&self) -> Option<u8> {
561 self.memory_id
562 }
563
564 #[must_use]
566 pub const fn stable_key(&self) -> Option<&str> {
567 match &self.stable_key {
568 Some(value) => Some(value.as_str()),
569 None => None,
570 }
571 }
572
573 #[must_use]
575 pub const fn schema_version(&self) -> Option<u32> {
576 self.schema_version
577 }
578
579 #[must_use]
581 pub const fn schema_fingerprint(&self) -> Option<&str> {
582 match &self.schema_fingerprint {
583 Some(value) => Some(value.as_str()),
584 None => None,
585 }
586 }
587
588 #[must_use]
590 pub const fn entries(&self) -> u64 {
591 self.entries
592 }
593
594 #[must_use]
596 pub const fn memory_bytes(&self) -> u64 {
597 self.memory_bytes
598 }
599}
600
601#[cfg_attr(doc, doc = "IndexStoreSnapshot\n\nIndex-store snapshot row.")]
602#[derive(CandidType, Clone, Debug, Default, Deserialize)]
603pub struct IndexStoreSnapshot {
604 pub(crate) path: String,
605 pub(crate) storage: StoreSnapshotStorageMode,
606 pub(crate) memory_id: Option<u8>,
607 pub(crate) stable_key: Option<String>,
608 pub(crate) schema_version: Option<u32>,
609 pub(crate) schema_fingerprint: Option<String>,
610 pub(crate) entries: u64,
611 pub(crate) user_entries: u64,
612 pub(crate) system_entries: u64,
613 pub(crate) memory_bytes: u64,
614 pub(crate) state: IndexState,
615}
616
617impl IndexStoreSnapshot {
618 #[must_use]
620 pub(crate) fn new(
621 path: String,
622 storage: StoreSnapshotStorageMode,
623 allocation: Option<StoreSnapshotAllocationIdentity>,
624 schema_metadata: StoreSnapshotSchemaMetadata,
625 stats: IndexStoreSnapshotStats,
626 ) -> Self {
627 let memory_id = allocation
628 .as_ref()
629 .map(StoreSnapshotAllocationIdentity::memory_id);
630 let stable_key = match allocation {
631 Some(allocation) => Some(allocation.stable_key),
632 None => None,
633 };
634 Self {
635 path,
636 storage,
637 memory_id,
638 stable_key,
639 schema_version: schema_metadata.schema_version(),
640 schema_fingerprint: schema_metadata.schema_fingerprint(),
641 entries: stats.entries,
642 user_entries: stats.user_entries,
643 system_entries: stats.system_entries,
644 memory_bytes: stats.memory_bytes,
645 state: stats.state,
646 }
647 }
648
649 #[must_use]
651 pub const fn path(&self) -> &str {
652 self.path.as_str()
653 }
654
655 #[must_use]
657 pub const fn storage(&self) -> StoreSnapshotStorageMode {
658 self.storage
659 }
660
661 #[must_use]
663 pub const fn memory_id(&self) -> Option<u8> {
664 self.memory_id
665 }
666
667 #[must_use]
669 pub const fn stable_key(&self) -> Option<&str> {
670 match &self.stable_key {
671 Some(value) => Some(value.as_str()),
672 None => None,
673 }
674 }
675
676 #[must_use]
678 pub const fn schema_version(&self) -> Option<u32> {
679 self.schema_version
680 }
681
682 #[must_use]
684 pub const fn schema_fingerprint(&self) -> Option<&str> {
685 match &self.schema_fingerprint {
686 Some(value) => Some(value.as_str()),
687 None => None,
688 }
689 }
690
691 #[must_use]
693 pub const fn entries(&self) -> u64 {
694 self.entries
695 }
696
697 #[must_use]
699 pub const fn user_entries(&self) -> u64 {
700 self.user_entries
701 }
702
703 #[must_use]
705 pub const fn system_entries(&self) -> u64 {
706 self.system_entries
707 }
708
709 #[must_use]
711 pub const fn memory_bytes(&self) -> u64 {
712 self.memory_bytes
713 }
714
715 #[must_use]
718 pub const fn state(&self) -> IndexState {
719 self.state
720 }
721}
722
723#[cfg_attr(doc, doc = "EntitySnapshot\n\nPer-entity storage snapshot row.")]
724#[derive(CandidType, Clone, Debug, Default, Deserialize)]
725pub struct EntitySnapshot {
726 pub(crate) store: String,
727
728 pub(crate) path: String,
729
730 pub(crate) entries: u64,
731
732 pub(crate) memory_bytes: u64,
733}
734
735impl EntitySnapshot {
736 #[must_use]
738 pub(crate) const fn new(store: String, path: String, entries: u64, memory_bytes: u64) -> Self {
739 Self {
740 store,
741 path,
742 entries,
743 memory_bytes,
744 }
745 }
746
747 #[must_use]
749 pub const fn store(&self) -> &str {
750 self.store.as_str()
751 }
752
753 #[must_use]
755 pub const fn path(&self) -> &str {
756 self.path.as_str()
757 }
758
759 #[must_use]
761 pub const fn entries(&self) -> u64 {
762 self.entries
763 }
764
765 #[must_use]
767 pub const fn memory_bytes(&self) -> u64 {
768 self.memory_bytes
769 }
770}