nodedb_lite/nodedb/collection/
ddl.rs1use nodedb_types::error::{NodeDbError, NodeDbResult};
4
5use super::super::{LockExt, NodeDbLite};
6use crate::storage::engine::StorageEngine;
7
8#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
10pub struct CollectionMeta {
11 pub name: String,
12 pub collection_type: String,
13 pub created_at_ms: u64,
14 pub fields: Vec<(String, String)>,
15 #[serde(default)]
18 pub config_json: Option<String>,
19 #[serde(default)]
26 pub descriptor_json: Option<String>,
27 #[serde(default)]
29 pub bitemporal: bool,
30 #[serde(default)]
37 pub crdt: bool,
38}
39
40impl<S: StorageEngine> NodeDbLite<S> {
41 pub async fn create_collection(
46 &self,
47 name: &str,
48 fields: &[(String, String)],
49 ) -> NodeDbResult<()> {
50 let guard = self.fts_state.admit_mutation().await;
51 let result = self
52 .create_collection_admitted(guard.permit(), name, fields)
53 .await;
54 guard.finish(result)
55 }
56
57 pub(crate) async fn create_collection_admitted(
58 &self,
59 _permit: &crate::engine::fts::coordinator::TextMutationPermit,
60 name: &str,
61 fields: &[(String, String)],
62 ) -> NodeDbResult<()> {
63 let meta = CollectionMeta {
64 name: name.to_string(),
65 collection_type: "document".to_string(),
66 created_at_ms: crate::runtime::now_millis(),
67 fields: fields.to_vec(),
68 config_json: None,
69 descriptor_json: None,
70 bitemporal: false,
71 crdt: false,
72 };
73 let key = format!("collection:{name}");
74 let bytes = sonic_rs::to_vec(&meta).map_err(|e| NodeDbError::storage(e.to_string()))?;
75 self.storage
76 .put(nodedb_types::Namespace::Meta, key.as_bytes(), &bytes)
77 .await?;
78 Ok(())
79 }
80
81 pub async fn create_kv_collection(
86 &self,
87 name: &str,
88 config: &nodedb_types::KvConfig,
89 ) -> NodeDbResult<()> {
90 let guard = self.fts_state.admit_mutation().await;
91 let result = self
92 .create_kv_collection_admitted(guard.permit(), name, config)
93 .await;
94 guard.finish(result)
95 }
96
97 pub(crate) async fn create_kv_collection_admitted(
98 &self,
99 _permit: &crate::engine::fts::coordinator::TextMutationPermit,
100 name: &str,
101 config: &nodedb_types::KvConfig,
102 ) -> NodeDbResult<()> {
103 let fields: Vec<(String, String)> = config
104 .schema
105 .columns
106 .iter()
107 .map(|c| (c.name.clone(), c.column_type.to_string()))
108 .collect();
109
110 let config_json =
111 sonic_rs::to_string(config).map_err(|e| NodeDbError::storage(e.to_string()))?;
112
113 let meta = CollectionMeta {
114 name: name.to_string(),
115 collection_type: "kv".to_string(),
116 created_at_ms: crate::runtime::now_millis(),
117 fields,
118 config_json: Some(config_json),
119 descriptor_json: None,
120 bitemporal: false,
121 crdt: false,
122 };
123 let key = format!("collection:{name}");
124 let bytes = sonic_rs::to_vec(&meta).map_err(|e| NodeDbError::storage(e.to_string()))?;
125 self.storage
126 .put(nodedb_types::Namespace::Meta, key.as_bytes(), &bytes)
127 .await?;
128 Ok(())
129 }
130
131 #[cfg(not(target_arch = "wasm32"))]
144 pub(crate) fn implicit_collection_meta(&self, name: &str) -> Option<CollectionMeta> {
145 if name.starts_with("__") {
146 return None;
147 }
148 let crdt = self.crdt.lock_or_recover();
149 if !crdt.collection_names().iter().any(|n| n == name) {
150 return None;
151 }
152 Some(CollectionMeta {
153 name: name.to_string(),
154 collection_type: "document".to_string(),
155 created_at_ms: 0,
156 fields: Vec::new(),
157 config_json: None,
158 descriptor_json: None,
159 bitemporal: false,
160 crdt: false,
161 })
162 }
163
164 pub async fn drop_collection(&self, name: &str) -> NodeDbResult<()> {
170 let guard = self.fts_state.admit_mutation().await;
171 let result = async {
172 self.fts_state
173 .persist_collection_tombstone(&*self.storage, name, guard.permit())
174 .await
175 .map_err(NodeDbError::from)?;
176 {
178 let mut crdt = self.crdt.lock_or_recover();
179 crdt.clear_collection(name).map_err(NodeDbError::storage)?;
180 }
181 crate::query::kv_ops::indexes::kv_drop_collection(&self.query_engine, name)
182 .await
183 .map_err(NodeDbError::storage)?;
184
185 {
187 let mut fts = self.fts_state.manager.lock_or_recover();
188 fts.drop_collection(name);
189 }
190
191 let key = format!("collection:{name}");
193 self.storage
194 .delete(nodedb_types::Namespace::Meta, key.as_bytes())
195 .await?;
196 Ok(())
197 }
198 .await;
199 guard.finish(result)
200 }
201
202 pub(crate) async fn collection_known(&self, name: &str) -> NodeDbResult<bool> {
206 if self
207 .crdt
208 .lock_or_recover()
209 .collection_names()
210 .iter()
211 .any(|n| n == name)
212 || self.strict.schema(name).is_some()
213 || self.columnar.schema(name).is_some()
214 {
215 return Ok(true);
216 }
217 let key = format!("collection:{name}");
218 Ok(self
219 .storage
220 .get(nodedb_types::Namespace::Meta, key.as_bytes())
221 .await?
222 .is_some())
223 }
224
225 pub async fn list_collections(&self) -> NodeDbResult<Vec<CollectionMeta>> {
227 let pairs = self
228 .storage
229 .scan_prefix(nodedb_types::Namespace::Meta, b"collection:")
230 .await?;
231 let mut result = Vec::new();
232 for (_, value) in &pairs {
233 if let Ok(meta) = sonic_rs::from_slice::<CollectionMeta>(value) {
234 result.push(meta);
235 }
236 }
237 let crdt = self.crdt.lock_or_recover();
239 let crdt_names = crdt.collection_names();
240 let explicit: std::collections::HashSet<String> =
241 result.iter().map(|m| m.name.clone()).collect();
242 for name in crdt_names {
243 if !name.starts_with("__") && !explicit.contains(&name) {
244 result.push(CollectionMeta {
245 name,
246 collection_type: "document".to_string(),
247 created_at_ms: 0,
248 fields: Vec::new(),
249 config_json: None,
250 descriptor_json: None,
251 bitemporal: false,
252 crdt: false,
253 });
254 }
255 }
256 Ok(result)
257 }
258}
259
260pub(crate) async fn load_persisted_collection_metas<S: StorageEngine>(
271 storage: &S,
272) -> NodeDbResult<std::collections::HashMap<String, CollectionMeta>> {
273 let pairs = storage
274 .scan_prefix(nodedb_types::Namespace::Meta, b"collection:")
275 .await?;
276 let mut map = std::collections::HashMap::with_capacity(pairs.len());
277 for (_, value) in &pairs {
278 if let Ok(meta) = sonic_rs::from_slice::<CollectionMeta>(value) {
279 map.insert(meta.name.clone(), meta);
280 }
281 }
282 Ok(map)
283}