Skip to main content

reddb_server/
physical.rs

1//! Physical storage design primitives for RedDB's deterministic on-disk layout.
2
3use std::collections::BTreeMap;
4use std::fs;
5use std::io;
6use std::path::{Path, PathBuf};
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use crate::api::{
10    CatalogSnapshot, CollectionStats, RedDBOptions, SchemaManifest, StorageMode,
11    REDDB_FORMAT_VERSION,
12};
13use crate::index::IndexKind;
14use crate::json::{from_slice, parse_json, to_vec};
15use crate::serde_json::{Map, Value as JsonValue};
16
17pub const DEFAULT_GRID_BLOCK_SIZE: usize = 512 * 1024;
18pub const DEFAULT_PAGE_SIZE: usize = 4096;
19pub const DEFAULT_SUPERBLOCK_COPIES: u8 = 4;
20pub const PHYSICAL_METADATA_PROTOCOL_VERSION: &str = "reddb-physical-v1";
21pub const PHYSICAL_METADATA_BINARY_EXTENSION: &str = "meta.rdbx";
22pub const DEFAULT_MANIFEST_EVENT_HISTORY: usize = 256;
23/// Retention applied when the seq-N catalog journal is enabled at the `Max`
24/// tier. See [`seqn_journal_retention`].
25pub const DEFAULT_METADATA_JOURNAL_RETENTION: usize = 32;
26/// Retention applied when the seq-N catalog journal is opt-in enabled outside
27/// of the `Max` tier — keeps forensics surface minimal on lower tiers.
28pub const OPT_IN_METADATA_JOURNAL_RETENTION: usize = 4;
29
30use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
31
32// JSON sidecar policy. 0 = unset (consult env, default off), 1 = enabled,
33// 2 = disabled. Threaded as a process-global because the metadata save path
34// is reached from many call sites that do not currently carry a layout
35// handle. Tier wiring (#469/#471/#472) flips this on at startup for `Max`;
36// minimal/standard/performance leave it off and emit only the binary
37// `<data>.meta.rdbx` + journal entries.
38static META_JSON_SIDECAR_POLICY: AtomicU8 = AtomicU8::new(0);
39
40/// Process-wide opt-in for the legacy `<data>.meta.json` sidecar.
41/// Call once at startup after resolving the active [`StorageLayout`].
42pub fn set_meta_json_sidecar_enabled(enabled: bool) {
43    META_JSON_SIDECAR_POLICY.store(if enabled { 1 } else { 2 }, Ordering::Relaxed);
44}
45
46/// Whether new metadata writes should additionally emit the JSON sidecar.
47/// Defaults to `false`; opt-in via [`set_meta_json_sidecar_enabled`] or the
48/// `REDDB_META_JSON_SIDECAR=1` env var (escape hatch for ad-hoc debugging
49/// of a non-Max instance). Reads always tolerate either JSON or binary.
50pub fn meta_json_sidecar_enabled() -> bool {
51    match META_JSON_SIDECAR_POLICY.load(Ordering::Relaxed) {
52        1 => true,
53        2 => false,
54        _ => std::env::var("REDDB_META_JSON_SIDECAR")
55            .ok()
56            .map(|v| matches!(v.as_str(), "1" | "true" | "TRUE" | "yes" | "on"))
57            .unwrap_or(false),
58    }
59}
60
61// Seq-N catalog journal policy. 0 = unset (consult env, default off), 1 =
62// enabled, 2 = disabled. Mirrors the meta-json sidecar toggle but governs the
63// `<data>.meta.rdbx.seq-{N}` forensic trail emitted on every metadata save.
64// Tier wiring (deferred) flips this on for `Max` with retention 32; opt-in
65// elsewhere lands with retention 4. See `seqn_journal_retention`.
66static SEQN_JOURNAL_POLICY: AtomicU8 = AtomicU8::new(0);
67// Retention override. 0 = unset (consult env, default off-tier retention).
68static SEQN_JOURNAL_RETENTION: AtomicUsize = AtomicUsize::new(0);
69
70/// Process-wide opt-in for the seq-N catalog journal (`<data>.meta.rdbx.seq-N`
71/// snapshot trail). Defaults off so non-`Max` tiers don't accumulate forensic
72/// artifacts. Tier wiring should call this with `true` for `Max`. Escape
73/// hatch: `REDDB_SEQN_JOURNAL=1`.
74pub fn set_seqn_journal_enabled(enabled: bool) {
75    SEQN_JOURNAL_POLICY.store(if enabled { 1 } else { 2 }, Ordering::Relaxed);
76}
77
78/// Whether new metadata saves should also emit a seq-N journal entry.
79pub fn seqn_journal_enabled() -> bool {
80    match SEQN_JOURNAL_POLICY.load(Ordering::Relaxed) {
81        1 => true,
82        2 => false,
83        _ => std::env::var("REDDB_SEQN_JOURNAL")
84            .ok()
85            .map(|v| matches!(v.as_str(), "1" | "true" | "TRUE" | "yes" | "on"))
86            .unwrap_or(false),
87    }
88}
89
90// Pager-meta sidecar policy (#477). 0 = unset (consult env, default off — keep
91// `<data>-meta` shadow), 1 = enabled (fold meta into page 1 + overflow chain;
92// no `-meta` sidecar), 2 = disabled (current behavior). Tier wiring (deferred)
93// flips this on for tiers that prefer a single datafile artifact. Escape hatch:
94// `REDDB_FOLD_PAGER_META=1`.
95static FOLD_PAGER_META_POLICY: AtomicU8 = AtomicU8::new(0);
96
97/// Process-wide opt-in for folding pager metadata (page 1) into the datafile
98/// without an adjacent `<data>-meta` shadow. When enabled, the corruption-
99/// recovery shadow at `<data>-meta` is not written; readers trust page 1
100/// (plus its overflow chain) as the single source of truth. Defaults off.
101pub fn set_fold_pager_meta_enabled(enabled: bool) {
102    FOLD_PAGER_META_POLICY.store(if enabled { 1 } else { 2 }, Ordering::Relaxed);
103}
104
105/// Whether the pager should fold metadata into page 1 only and skip the
106/// `<data>-meta` sidecar shadow. Reads still tolerate the sidecar so existing
107/// databases keep working through the flag flip.
108pub fn fold_pager_meta_enabled() -> bool {
109    match FOLD_PAGER_META_POLICY.load(Ordering::Relaxed) {
110        1 => true,
111        2 => false,
112        _ => std::env::var("REDDB_FOLD_PAGER_META")
113            .ok()
114            .map(|v| matches!(v.as_str(), "1" | "true" | "TRUE" | "yes" | "on"))
115            .unwrap_or(false),
116    }
117}
118
119// Fold-DWB-into-WAL policy (#478). 0 = unset (consult env, default off — keep
120// `-dwb` sidecar), 1 = enabled (emit FullPageImage WAL records before first
121// page modification per checkpoint cycle; no `-dwb` sidecar), 2 = disabled.
122// Tier wiring (deferred) flips this on for tiers that prefer a single WAL-
123// rooted recovery path. Escape hatch: `REDDB_FOLD_DWB_INTO_WAL=1`.
124static FOLD_DWB_INTO_WAL_POLICY: AtomicU8 = AtomicU8::new(0);
125
126/// Process-wide opt-in for folding the double-write buffer into the WAL via
127/// full-page-image (FPI) records. When enabled, the pager does not open or
128/// write `<data>-dwb`; recovery rebuilds torn pages from FPI records replayed
129/// before normal redo. Defaults off.
130pub fn set_fold_dwb_into_wal_enabled(enabled: bool) {
131    FOLD_DWB_INTO_WAL_POLICY.store(if enabled { 1 } else { 2 }, Ordering::Relaxed);
132}
133
134/// Whether the pager should fold DWB into WAL (no `<data>-dwb` sidecar).
135/// Reads still tolerate the legacy sidecar so existing databases keep
136/// working through the flag flip.
137pub fn fold_dwb_into_wal_enabled() -> bool {
138    match FOLD_DWB_INTO_WAL_POLICY.load(Ordering::Relaxed) {
139        1 => true,
140        2 => false,
141        _ => std::env::var("REDDB_FOLD_DWB_INTO_WAL")
142            .ok()
143            .map(|v| matches!(v.as_str(), "1" | "true" | "TRUE" | "yes" | "on"))
144            .unwrap_or(false),
145    }
146}
147
148/// Process-wide retention for the seq-N catalog journal. Tier wiring should
149/// call this with `DEFAULT_METADATA_JOURNAL_RETENTION` (32) for `Max` and
150/// `OPT_IN_METADATA_JOURNAL_RETENTION` (4) for opt-in non-`Max` use.
151/// `0` resets to defaults (env or off-tier baseline).
152pub fn set_seqn_journal_retention(retention: usize) {
153    SEQN_JOURNAL_RETENTION.store(retention, Ordering::Relaxed);
154}
155
156/// Resolved retention bound for the seq-N journal. Falls back to env
157/// `REDDB_SEQN_JOURNAL_RETENTION`, then to `OPT_IN_METADATA_JOURNAL_RETENTION`
158/// (4) — the conservative off-tier baseline.
159pub fn seqn_journal_retention() -> usize {
160    let stored = SEQN_JOURNAL_RETENTION.load(Ordering::Relaxed);
161    if stored > 0 {
162        return stored;
163    }
164    std::env::var("REDDB_SEQN_JOURNAL_RETENTION")
165        .ok()
166        .and_then(|v| v.parse::<usize>().ok())
167        .filter(|v| *v > 0)
168        .unwrap_or(OPT_IN_METADATA_JOURNAL_RETENTION)
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum PhysicalMetadataSource {
173    Binary,
174    BinaryJournal,
175    Json,
176}
177
178impl PhysicalMetadataSource {
179    pub fn as_str(self) -> &'static str {
180        match self {
181            Self::Binary => "binary",
182            Self::BinaryJournal => "binary_journal",
183            Self::Json => "json",
184        }
185    }
186}
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
188pub struct BlockReference {
189    pub index: u64,
190    pub checksum: u128,
191}
192
193#[derive(Debug, Clone, Default)]
194pub struct ManifestPointers {
195    pub oldest: BlockReference,
196    pub newest: BlockReference,
197}
198
199#[derive(Debug, Clone)]
200pub struct SuperblockHeader {
201    pub format_version: u32,
202    pub sequence: u64,
203    pub copies: u8,
204    pub manifest: ManifestPointers,
205    pub free_set: BlockReference,
206    pub collection_roots: BTreeMap<String, u64>,
207}
208
209impl Default for SuperblockHeader {
210    fn default() -> Self {
211        Self {
212            format_version: crate::api::REDDB_FORMAT_VERSION,
213            sequence: 0,
214            copies: DEFAULT_SUPERBLOCK_COPIES,
215            manifest: ManifestPointers::default(),
216            free_set: BlockReference::default(),
217            collection_roots: BTreeMap::new(),
218        }
219    }
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub enum ManifestEventKind {
224    Insert,
225    Update,
226    Remove,
227    Checkpoint,
228}
229
230#[derive(Debug, Clone)]
231pub struct ManifestEvent {
232    pub collection: String,
233    pub object_key: String,
234    pub kind: ManifestEventKind,
235    pub block: BlockReference,
236    pub snapshot_min: u64,
237    pub snapshot_max: Option<u64>,
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
241pub enum CompactionPolicy {
242    Incremental,
243    Manual,
244}
245
246#[derive(Debug, Clone)]
247pub struct WalPolicy {
248    pub auto_checkpoint_pages: u32,
249    pub fsync_on_commit: bool,
250    pub ring_buffer_bytes: u64,
251}
252
253impl Default for WalPolicy {
254    fn default() -> Self {
255        Self {
256            auto_checkpoint_pages: 1000,
257            fsync_on_commit: true,
258            ring_buffer_bytes: 64 * 1024 * 1024,
259        }
260    }
261}
262
263#[derive(Debug, Clone)]
264pub struct GridLayout {
265    pub block_size: usize,
266    pub page_size: usize,
267    pub superblock_copies: u8,
268}
269
270impl Default for GridLayout {
271    fn default() -> Self {
272        Self {
273            block_size: DEFAULT_GRID_BLOCK_SIZE,
274            page_size: DEFAULT_PAGE_SIZE,
275            superblock_copies: DEFAULT_SUPERBLOCK_COPIES,
276        }
277    }
278}
279
280#[derive(Debug, Clone)]
281pub struct PhysicalLayout {
282    pub mode: StorageMode,
283    pub grid: GridLayout,
284    pub wal: WalPolicy,
285    pub compaction: CompactionPolicy,
286}
287
288impl PhysicalLayout {
289    pub fn from_options(options: &RedDBOptions) -> Self {
290        Self {
291            mode: options.mode,
292            grid: GridLayout::default(),
293            wal: WalPolicy {
294                auto_checkpoint_pages: options.auto_checkpoint_pages,
295                ..WalPolicy::default()
296            },
297            compaction: CompactionPolicy::Incremental,
298        }
299    }
300
301    pub fn is_persistent(&self) -> bool {
302        self.mode == StorageMode::Persistent
303    }
304}
305
306#[derive(Debug, Clone, Default)]
307pub struct SnapshotDescriptor {
308    pub snapshot_id: u64,
309    pub created_at_unix_ms: u128,
310    pub superblock_sequence: u64,
311    pub collection_count: usize,
312    pub total_entities: usize,
313}
314
315#[derive(Debug, Clone, Copy, PartialEq, Eq)]
316pub enum ContractOrigin {
317    Explicit,
318    Implicit,
319    Migrated,
320}
321
322impl ContractOrigin {
323    pub fn as_str(self) -> &'static str {
324        match self {
325            Self::Explicit => "explicit",
326            Self::Implicit => "implicit",
327            Self::Migrated => "migrated",
328        }
329    }
330}
331
332#[derive(Debug, Clone)]
333pub struct DeclaredColumnContract {
334    pub name: String,
335    pub data_type: String,
336    pub sql_type: Option<crate::storage::schema::SqlTypeName>,
337    pub not_null: bool,
338    pub default: Option<String>,
339    pub compress: Option<u8>,
340    pub unique: bool,
341    pub primary_key: bool,
342    pub enum_variants: Vec<String>,
343    pub array_element: Option<String>,
344    pub decimal_precision: Option<u8>,
345}
346
347#[derive(Debug, Clone)]
348pub struct CollectionContract {
349    pub name: String,
350    pub declared_model: crate::catalog::CollectionModel,
351    pub schema_mode: crate::catalog::SchemaMode,
352    pub origin: ContractOrigin,
353    pub version: u32,
354    pub created_at_unix_ms: u128,
355    pub updated_at_unix_ms: u128,
356    pub default_ttl_ms: Option<u64>,
357    pub vector_dimension: Option<usize>,
358    pub vector_metric: Option<crate::storage::engine::distance::DistanceMetric>,
359    pub context_index_fields: Vec<String>,
360    pub declared_columns: Vec<DeclaredColumnContract>,
361    pub table_def: Option<crate::storage::schema::TableDef>,
362    /// Enabled by `CREATE TABLE ... WITH timestamps = true`. When true,
363    /// the runtime auto-populates two user-visible columns
364    /// `created_at` + `updated_at` (BIGINT unix-ms) sourced from the
365    /// `UnifiedEntity::created_at/updated_at` fields. `created_at` is
366    /// immutable after insert; `updated_at` is bumped on every mutation.
367    pub timestamps_enabled: bool,
368    /// Enabled by `CREATE TABLE ... WITH context_index = true` (or by
369    /// naming specific `context_index_fields`). When true, every INSERT
370    /// tokenises the row's text fields and populates the global context
371    /// index that backs `SEARCH CONTEXT` / `SEARCH SIMILAR TEXT` / `ASK`
372    /// (RAG). When false (default), inserts skip the tokenisation +
373    /// 3-way RwLock write storm entirely — ~800 ns faster per insert,
374    /// and SEARCH returns empty for this collection.
375    ///
376    /// Opt-in by design: pure OLTP tables (accounts, orders, events)
377    /// pay zero indexing tax; search-oriented tables (articles, docs)
378    /// flip the switch at CREATE time.
379    pub context_index_enabled: bool,
380    /// Metrics collections are backed by time-series storage but carry a
381    /// metrics-specific raw sample retention contract.
382    pub metrics_raw_retention_ms: Option<u64>,
383    /// Metrics rollup tiers declared by `CREATE METRICS ... DOWNSAMPLE`.
384    pub metrics_rollup_policies: Vec<String>,
385    /// Metrics tenant identity source. Defaults to current tenant context and
386    /// can be declared as a stable identity path for future ingestion slices.
387    pub metrics_tenant_identity: Option<String>,
388    /// Metrics namespace identity. v0 starts with a default namespace so
389    /// series identity is namespace-aware before Prometheus ingestion exists.
390    pub metrics_namespace: Option<String>,
391    /// Enabled by `CREATE TABLE ... APPEND ONLY` or `WITH
392    /// (append_only = true)`. When true, the runtime rejects
393    /// `UPDATE` and `DELETE` against this collection at parse time
394    /// with a clear error — the operator's immutability intent
395    /// becomes a first-class catalog fact rather than an RLS-shaped
396    /// approximation. Default `false` so legacy DDL keeps its
397    /// mutable semantics.
398    pub append_only: bool,
399    /// Declarative subscriptions created by `WITH EVENTS`. This is
400    /// metadata only in #291; event emission is wired by the outbox slice.
401    pub subscriptions: Vec<crate::catalog::SubscriptionDescriptor>,
402    /// `CREATE TIMESERIES ... WITH SESSION_KEY <col>` — the column the
403    /// `SESSIONIZE` operator partitions by when no key is supplied at
404    /// query-time. `None` for non-timeseries collections and for
405    /// timeseries created without the clause. Issue #576 slice 1.
406    pub session_key: Option<String>,
407    /// `CREATE TIMESERIES ... SESSION_GAP <duration>` — the default
408    /// inactivity gap (milliseconds) the `SESSIONIZE` operator uses to
409    /// close a session when no gap is supplied at query-time. `None`
410    /// for non-timeseries collections and for timeseries created
411    /// without the clause. Issue #576 slice 1.
412    pub session_gap_ms: Option<u64>,
413    /// `ALTER COLLECTION ... SET RETENTION <duration>` — declarative
414    /// retention policy in milliseconds. `None` means retention is
415    /// not enforced. Reads filter out rows older than `now -
416    /// retention_duration_ms` by the collection's timestamp column.
417    /// Issue #580 — DeclarativeRetention slice 1.
418    pub retention_duration_ms: Option<u64>,
419}
420
421/// Canonical artifact lifecycle states.
422///
423/// State machine transitions:
424/// ```text
425///   Declared ──► Building ──► Ready ──► Stale ──► RequiresRebuild
426///       │            │          │                       │
427///       │            ▼          ▼                       │
428///       │         Failed    Disabled                    │
429///       │            │                                  │
430///       └────────────┴──────────────────────────────────┘
431///                    (rebuild restarts from Building)
432/// ```
433#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
434pub enum ArtifactState {
435    /// Index declared but never materialized.
436    Declared,
437    /// Artifact is being built or rebuilt.
438    Building,
439    /// Artifact is materialized and queryable.
440    Ready,
441    /// Artifact is explicitly disabled by the operator.
442    Disabled,
443    /// Underlying data changed; artifact is out of date.
444    Stale,
445    /// Build or warmup failed; manual intervention may be needed.
446    Failed,
447    /// Artifact must be rebuilt before it can serve reads.
448    RequiresRebuild,
449}
450
451impl ArtifactState {
452    /// Parse from the legacy string representation stored in physical metadata.
453    pub fn from_build_state(s: &str, enabled: bool) -> Self {
454        if !enabled {
455            return Self::Disabled;
456        }
457        match s {
458            "ready" => Self::Ready,
459            "building" | "catalog-derived" | "metadata-only" | "artifact-published"
460            | "registry-loaded" => Self::Building,
461            "stale" => Self::Stale,
462            "failed" => Self::Failed,
463            "requires_rebuild" | "requires-rebuild" => Self::RequiresRebuild,
464            _ => Self::Declared,
465        }
466    }
467
468    /// Canonical string representation for storage and API surfaces.
469    pub fn as_str(&self) -> &'static str {
470        match self {
471            Self::Declared => "declared",
472            Self::Building => "building",
473            Self::Ready => "ready",
474            Self::Disabled => "disabled",
475            Self::Stale => "stale",
476            Self::Failed => "failed",
477            Self::RequiresRebuild => "requires_rebuild",
478        }
479    }
480
481    /// Whether this artifact is safe for query reads.
482    pub fn is_queryable(&self) -> bool {
483        matches!(self, Self::Ready)
484    }
485
486    /// Whether a rebuild operation is valid from this state.
487    pub fn can_rebuild(&self) -> bool {
488        matches!(
489            self,
490            Self::Declared | Self::Stale | Self::Failed | Self::RequiresRebuild
491        )
492    }
493
494    /// Whether this state indicates the artifact needs attention.
495    pub fn needs_attention(&self) -> bool {
496        matches!(self, Self::Failed | Self::RequiresRebuild | Self::Stale)
497    }
498}
499
500impl std::fmt::Display for ArtifactState {
501    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
502        f.write_str(self.as_str())
503    }
504}
505
506#[derive(Debug, Clone)]
507pub struct PhysicalIndexState {
508    pub name: String,
509    pub kind: IndexKind,
510    pub collection: Option<String>,
511    pub enabled: bool,
512    pub entries: usize,
513    pub estimated_memory_bytes: u64,
514    pub last_refresh_ms: Option<u128>,
515    pub backend: String,
516    pub artifact_kind: Option<String>,
517    pub artifact_root_page: Option<u32>,
518    pub artifact_checksum: Option<u64>,
519    pub build_state: String,
520}
521
522impl PhysicalIndexState {
523    /// Canonical artifact lifecycle state derived from physical state.
524    pub fn artifact_state(&self) -> ArtifactState {
525        ArtifactState::from_build_state(&self.build_state, self.enabled)
526    }
527}
528
529#[derive(Debug, Clone)]
530pub struct ExportDescriptor {
531    pub name: String,
532    pub created_at_unix_ms: u128,
533    pub snapshot_id: Option<u64>,
534    pub superblock_sequence: u64,
535    pub data_path: String,
536    pub metadata_path: String,
537    pub collection_count: usize,
538    pub total_entities: usize,
539}
540
541#[derive(Debug, Clone)]
542pub struct PhysicalGraphProjection {
543    pub name: String,
544    pub created_at_unix_ms: u128,
545    pub updated_at_unix_ms: u128,
546    pub state: String,
547    pub source: String,
548    pub node_labels: Vec<String>,
549    pub node_types: Vec<String>,
550    pub edge_labels: Vec<String>,
551    pub last_materialized_sequence: Option<u64>,
552}
553
554#[derive(Debug, Clone)]
555pub struct PhysicalAnalyticsJob {
556    pub id: String,
557    pub kind: String,
558    pub state: String,
559    pub projection: Option<String>,
560    pub created_at_unix_ms: u128,
561    pub updated_at_unix_ms: u128,
562    pub last_run_sequence: Option<u64>,
563    pub metadata: BTreeMap<String, String>,
564}
565
566#[derive(Debug, Clone)]
567pub struct PhysicalTreeDefinition {
568    pub collection: String,
569    pub name: String,
570    pub root_id: u64,
571    pub default_max_children: usize,
572    pub ordered_children: bool,
573    pub ownership: String,
574    pub auto_fix_mode: String,
575    pub created_at_unix_ms: u128,
576    pub updated_at_unix_ms: u128,
577}
578
579#[derive(Debug, Clone)]
580pub struct PhysicalMetadataFile {
581    pub protocol_version: String,
582    pub generated_at_unix_ms: u128,
583    pub last_loaded_from: Option<String>,
584    pub last_healed_at_unix_ms: Option<u128>,
585    pub manifest: SchemaManifest,
586    pub catalog: CatalogSnapshot,
587    pub manifest_events: Vec<ManifestEvent>,
588    pub indexes: Vec<PhysicalIndexState>,
589    pub graph_projections: Vec<PhysicalGraphProjection>,
590    pub analytics_jobs: Vec<PhysicalAnalyticsJob>,
591    pub tree_definitions: Vec<PhysicalTreeDefinition>,
592    pub collection_ttl_defaults_ms: BTreeMap<String, u64>,
593    pub collection_contracts: Vec<CollectionContract>,
594    pub exports: Vec<ExportDescriptor>,
595    pub superblock: SuperblockHeader,
596    pub snapshots: Vec<SnapshotDescriptor>,
597}
598
599mod helpers;
600mod json_codec;
601mod metadata_file;
602pub mod shm;
603
604pub use self::shm::{
605    provision_shm, read_shm_header, set_shm_provisioning_enabled, shm_path_for,
606    shm_provisioning_enabled, ShmHandle, ShmHeader, ShmProvisionState, SHM_FILE_SIZE,
607    SHM_HEADER_SIZE, SHM_MAGIC, SHM_VERSION,
608};
609
610use self::helpers::*;
611use self::json_codec::*;