Skip to main content

nodedb_types/columnar/
profile.rs

1//! ColumnarProfile and DocumentMode — collection storage specializations.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7use super::schema::StrictSchema;
8
9/// Specialization profile for columnar collections.
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(tag = "profile")]
12pub enum ColumnarProfile {
13    /// General analytics. No special constraints.
14    Plain,
15    /// Time-partitioned append-only storage for metrics and logs.
16    Timeseries { time_key: String, interval: String },
17    /// Geometry-optimized storage with automatic spatial indexing.
18    Spatial {
19        geometry_column: String,
20        auto_rtree: bool,
21        auto_geohash: bool,
22    },
23}
24
25impl ColumnarProfile {
26    pub fn as_str(&self) -> &'static str {
27        match self {
28            Self::Plain => "plain",
29            Self::Timeseries { .. } => "timeseries",
30            Self::Spatial { .. } => "spatial",
31        }
32    }
33
34    pub fn is_timeseries(&self) -> bool {
35        matches!(self, Self::Timeseries { .. })
36    }
37
38    pub fn is_spatial(&self) -> bool {
39        matches!(self, Self::Spatial { .. })
40    }
41}
42
43impl fmt::Display for ColumnarProfile {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.write_str(self.as_str())
46    }
47}
48
49/// Storage mode for document collections.
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
51#[serde(tag = "mode")]
52pub enum DocumentMode {
53    /// Schemaless MessagePack documents. Default. CRDT-friendly.
54    #[default]
55    Schemaless,
56    /// Schema-enforced Binary Tuple documents.
57    Strict(StrictSchema),
58}
59
60impl DocumentMode {
61    pub fn is_schemaless(&self) -> bool {
62        matches!(self, Self::Schemaless)
63    }
64
65    pub fn is_strict(&self) -> bool {
66        matches!(self, Self::Strict(_))
67    }
68
69    pub fn as_str(&self) -> &'static str {
70        match self {
71            Self::Schemaless => "schemaless",
72            Self::Strict(_) => "strict",
73        }
74    }
75
76    pub fn schema(&self) -> Option<&StrictSchema> {
77        match self {
78            Self::Strict(s) => Some(s),
79            Self::Schemaless => None,
80        }
81    }
82}
83
84impl fmt::Display for DocumentMode {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        f.write_str(self.as_str())
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::columnar::{ColumnDef, ColumnType};
94
95    #[test]
96    fn columnar_profile_serde_roundtrip() {
97        let profiles = vec![
98            ColumnarProfile::Plain,
99            ColumnarProfile::Timeseries {
100                time_key: "time".into(),
101                interval: "1h".into(),
102            },
103            ColumnarProfile::Spatial {
104                geometry_column: "geom".into(),
105                auto_rtree: true,
106                auto_geohash: true,
107            },
108        ];
109        for p in profiles {
110            let json = serde_json::to_string(&p).unwrap();
111            let back: ColumnarProfile = serde_json::from_str(&json).unwrap();
112            assert_eq!(back, p);
113        }
114    }
115
116    #[test]
117    fn document_mode_default_is_schemaless() {
118        assert!(DocumentMode::default().is_schemaless());
119    }
120
121    #[test]
122    fn document_mode_strict_has_schema() {
123        let schema = StrictSchema::new(vec![ColumnDef::required("id", ColumnType::Int64)]).unwrap();
124        let mode = DocumentMode::Strict(schema.clone());
125        assert!(mode.is_strict());
126        assert_eq!(mode.schema().unwrap(), &schema);
127    }
128
129    #[test]
130    fn document_mode_serde_roundtrip() {
131        let modes = vec![
132            DocumentMode::Schemaless,
133            DocumentMode::Strict(
134                StrictSchema::new(vec![
135                    ColumnDef::required("id", ColumnType::Int64).with_primary_key(),
136                    ColumnDef::nullable("name", ColumnType::String),
137                ])
138                .unwrap(),
139            ),
140        ];
141        for m in modes {
142            let json = serde_json::to_string(&m).unwrap();
143            let back: DocumentMode = serde_json::from_str(&json).unwrap();
144            assert_eq!(back, m);
145        }
146    }
147}