nodedb_types/columnar/
profile.rs1use std::fmt;
6
7use serde::{Deserialize, Serialize};
8
9use super::schema::StrictSchema;
10
11#[derive(
13 Debug,
14 Clone,
15 PartialEq,
16 Eq,
17 Serialize,
18 Deserialize,
19 zerompk::ToMessagePack,
20 zerompk::FromMessagePack,
21)]
22#[serde(tag = "profile")]
23pub enum ColumnarProfile {
24 Plain,
26 Timeseries { time_key: String, interval: String },
28 Spatial {
30 geometry_column: String,
31 auto_rtree: bool,
32 auto_geohash: bool,
33 },
34}
35
36impl ColumnarProfile {
37 pub fn as_str(&self) -> &'static str {
38 match self {
39 Self::Plain => "plain",
40 Self::Timeseries { .. } => "timeseries",
41 Self::Spatial { .. } => "spatial",
42 }
43 }
44
45 pub fn is_timeseries(&self) -> bool {
46 matches!(self, Self::Timeseries { .. })
47 }
48
49 pub fn is_spatial(&self) -> bool {
50 matches!(self, Self::Spatial { .. })
51 }
52}
53
54impl fmt::Display for ColumnarProfile {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 f.write_str(self.as_str())
57 }
58}
59
60#[derive(
62 Debug,
63 Clone,
64 PartialEq,
65 Eq,
66 Serialize,
67 Deserialize,
68 Default,
69 zerompk::ToMessagePack,
70 zerompk::FromMessagePack,
71)]
72#[serde(tag = "mode")]
73pub enum DocumentMode {
74 #[default]
76 Schemaless,
77 Strict(StrictSchema),
79}
80
81impl DocumentMode {
82 pub fn is_schemaless(&self) -> bool {
83 matches!(self, Self::Schemaless)
84 }
85
86 pub fn is_strict(&self) -> bool {
87 matches!(self, Self::Strict(_))
88 }
89
90 pub fn as_str(&self) -> &'static str {
91 match self {
92 Self::Schemaless => "schemaless",
93 Self::Strict(_) => "strict",
94 }
95 }
96
97 pub fn schema(&self) -> Option<&StrictSchema> {
98 match self {
99 Self::Strict(s) => Some(s),
100 Self::Schemaless => None,
101 }
102 }
103}
104
105impl fmt::Display for DocumentMode {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 f.write_str(self.as_str())
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::columnar::{ColumnDef, ColumnType};
115
116 #[test]
117 fn columnar_profile_serde_roundtrip() {
118 let profiles = vec![
119 ColumnarProfile::Plain,
120 ColumnarProfile::Timeseries {
121 time_key: "time".into(),
122 interval: "1h".into(),
123 },
124 ColumnarProfile::Spatial {
125 geometry_column: "geom".into(),
126 auto_rtree: true,
127 auto_geohash: true,
128 },
129 ];
130 for p in profiles {
131 let json = sonic_rs::to_string(&p).unwrap();
132 let back: ColumnarProfile = sonic_rs::from_str(&json).unwrap();
133 assert_eq!(back, p);
134 }
135 }
136
137 #[test]
138 fn document_mode_default_is_schemaless() {
139 assert!(DocumentMode::default().is_schemaless());
140 }
141
142 #[test]
143 fn document_mode_strict_has_schema() {
144 let schema = StrictSchema::new(vec![ColumnDef::required("id", ColumnType::Int64)]).unwrap();
145 let mode = DocumentMode::Strict(schema.clone());
146 assert!(mode.is_strict());
147 assert_eq!(mode.schema().unwrap(), &schema);
148 }
149
150 #[test]
151 fn document_mode_serde_roundtrip() {
152 let modes = vec![
153 DocumentMode::Schemaless,
154 DocumentMode::Strict(
155 StrictSchema::new(vec![
156 ColumnDef::required("id", ColumnType::Int64).with_primary_key(),
157 ColumnDef::nullable("name", ColumnType::String),
158 ])
159 .unwrap(),
160 ),
161 ];
162 for m in modes {
163 let json = sonic_rs::to_string(&m).unwrap();
164 let back: DocumentMode = sonic_rs::from_str(&json).unwrap();
165 assert_eq!(back, m);
166 }
167 }
168}