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