nodedb_lite/engine/columnar/store/
schema.rs1use super::lifecycle::{META_COLUMNAR_COLLECTIONS, META_COLUMNAR_SCHEMA_PREFIX};
6use super::segments::remove_segment_bytes;
7use super::state::{CollectionState, ColumnarEngine, SegmentMeta};
8use crate::error::LiteError;
9use crate::storage::engine::{StorageEngine, WriteOp};
10use nodedb_columnar::mutation::MutationEngine;
11use nodedb_types::Namespace;
12use nodedb_types::columnar::{ColumnarProfile, ColumnarSchema};
13use std::sync::{Arc, Mutex};
14
15impl<S: StorageEngine> ColumnarEngine<S> {
16 pub async fn create_collection(
18 &self,
19 name: &str,
20 schema: ColumnarSchema,
21 profile: ColumnarProfile,
22 bitemporal: bool,
23 ) -> Result<(), LiteError> {
24 let mut names: Vec<String> = {
26 let guard = self
27 .collections
28 .read()
29 .map_err(|_| LiteError::LockPoisoned)?;
30 if guard.contains_key(name) {
31 return Err(LiteError::BadRequest {
32 detail: format!("columnar collection '{name}' already exists"),
33 });
34 }
35 guard.keys().cloned().collect()
36 };
37 names.push(name.to_string());
38
39 #[derive(serde::Serialize, zerompk::ToMessagePack)]
41 struct StoredSchema<'a> {
42 schema: &'a ColumnarSchema,
43 profile: &'a ColumnarProfile,
44 bitemporal: bool,
45 }
46 let meta_key = format!("{META_COLUMNAR_SCHEMA_PREFIX}{name}");
47 let schema_bytes = zerompk::to_msgpack_vec(&StoredSchema {
48 schema: &schema,
49 profile: &profile,
50 bitemporal,
51 })
52 .map_err(|e| LiteError::Serialization {
53 detail: e.to_string(),
54 })?;
55
56 let names_bytes =
57 zerompk::to_msgpack_vec(&names).map_err(|e| LiteError::Serialization {
58 detail: e.to_string(),
59 })?;
60
61 self.storage
62 .batch_write(&[
63 WriteOp::Put {
64 ns: Namespace::Meta,
65 key: meta_key.into_bytes(),
66 value: schema_bytes,
67 },
68 WriteOp::Put {
69 ns: Namespace::Meta,
70 key: META_COLUMNAR_COLLECTIONS.to_vec(),
71 value: names_bytes,
72 },
73 ])
74 .await?;
75
76 let mutation = MutationEngine::new(name.to_string(), schema);
77 let state = CollectionState {
78 mutation,
79 profile,
80 bitemporal,
81 segments: Vec::new(),
82 next_segment_id: 1,
83 };
84
85 let mut guard = self
86 .collections
87 .write()
88 .map_err(|_| LiteError::LockPoisoned)?;
89 if guard.contains_key(name) {
90 return Err(LiteError::BadRequest {
91 detail: format!(
92 "columnar collection '{name}' was created concurrently by another writer"
93 ),
94 });
95 }
96 guard.insert(name.to_string(), Arc::new(Mutex::new(state)));
97
98 Ok(())
99 }
100
101 pub async fn drop_collection(&self, name: &str) -> Result<(), LiteError> {
103 let (segments, remaining_names): (Vec<SegmentMeta>, Vec<String>) = {
105 let mut guard = self
106 .collections
107 .write()
108 .map_err(|_| LiteError::LockPoisoned)?;
109 let state_arc = guard
110 .remove(name)
111 .ok_or_else(|| LiteError::collection_not_found("columnar", name))?;
112 let segments = {
113 let s = state_arc.lock().map_err(|_| LiteError::LockPoisoned)?;
114 s.segments.clone()
115 };
116 let names: Vec<String> = guard.keys().cloned().collect();
117 (segments, names)
118 };
119
120 for seg in &segments {
122 if seg.fully_deleted_at_ms.is_none() {
123 remove_segment_bytes(&*self.storage, name, seg.segment_id).await?;
124 }
125 }
126
127 let mut ops = Vec::new();
129 for seg in &segments {
130 ops.push(WriteOp::Delete {
131 ns: Namespace::Columnar,
132 key: format!("{name}:del:{}", seg.segment_id).into_bytes(),
133 });
134 }
135 ops.push(WriteOp::Delete {
136 ns: Namespace::Columnar,
137 key: format!("{name}:meta").into_bytes(),
138 });
139 ops.push(WriteOp::Delete {
140 ns: Namespace::Meta,
141 key: format!("{META_COLUMNAR_SCHEMA_PREFIX}{name}").into_bytes(),
142 });
143
144 let names_bytes =
145 zerompk::to_msgpack_vec(&remaining_names).map_err(|e| LiteError::Serialization {
146 detail: e.to_string(),
147 })?;
148 ops.push(WriteOp::Put {
149 ns: Namespace::Meta,
150 key: META_COLUMNAR_COLLECTIONS.to_vec(),
151 value: names_bytes,
152 });
153
154 self.storage.batch_write(&ops).await?;
155 Ok(())
156 }
157
158 pub async fn alter_add_column(
160 &self,
161 name: &str,
162 column: nodedb_types::columnar::ColumnDef,
163 ) -> Result<(), LiteError> {
164 if !column.nullable && column.default.is_none() {
165 return Err(LiteError::BadRequest {
166 detail: format!(
167 "ALTER ADD COLUMN '{}': non-nullable column must have a DEFAULT",
168 column.name
169 ),
170 });
171 }
172
173 let state_arc = self.lookup(name)?;
174
175 let (mut schema, profile) = {
177 let s = Self::lock_state(&state_arc)?;
178 if s.mutation
179 .schema()
180 .columns
181 .iter()
182 .any(|c| c.name == column.name)
183 {
184 return Err(LiteError::BadRequest {
185 detail: format!("column '{}' already exists in '{name}'", column.name),
186 });
187 }
188 (s.mutation.schema().clone(), s.profile.clone())
189 };
190
191 schema.columns.push(column);
192 schema.version = schema.version.saturating_add(1);
193
194 #[derive(serde::Serialize, zerompk::ToMessagePack)]
196 struct StoredSchema<'a> {
197 schema: &'a ColumnarSchema,
198 profile: &'a ColumnarProfile,
199 }
200 let meta_key = format!("{META_COLUMNAR_SCHEMA_PREFIX}{name}");
201 let schema_bytes = zerompk::to_msgpack_vec(&StoredSchema {
202 schema: &schema,
203 profile: &profile,
204 })
205 .map_err(|e| LiteError::Serialization {
206 detail: e.to_string(),
207 })?;
208
209 self.storage
210 .put(Namespace::Meta, meta_key.as_bytes(), &schema_bytes)
211 .await?;
212
213 let mut s = Self::lock_state(&state_arc)?;
215 s.mutation = MutationEngine::new(name.to_string(), schema);
216
217 Ok(())
218 }
219
220 pub fn schema(&self, name: &str) -> Option<ColumnarSchema> {
222 let guard = self.collections.read().ok()?;
223 let state_arc = guard.get(name)?;
224 let s = state_arc.lock().ok()?;
225 Some(s.mutation.schema().clone())
226 }
227
228 pub fn profile(&self, name: &str) -> Option<ColumnarProfile> {
230 let guard = self.collections.read().ok()?;
231 let state_arc = guard.get(name)?;
232 let s = state_arc.lock().ok()?;
233 Some(s.profile.clone())
234 }
235
236 pub fn collection_names(&self) -> Vec<String> {
238 self.collections
239 .read()
240 .map(|g| g.keys().cloned().collect())
241 .unwrap_or_default()
242 }
243}