use nodedb_types::error::{NodeDbError, NodeDbResult};
use super::super::{LockExt, NodeDbLite};
use crate::storage::engine::StorageEngine;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CollectionMeta {
pub name: String,
pub collection_type: String,
pub created_at_ms: u64,
pub fields: Vec<(String, String)>,
#[serde(default)]
pub config_json: Option<String>,
#[serde(default)]
pub descriptor_json: Option<String>,
#[serde(default)]
pub bitemporal: bool,
#[serde(default)]
pub crdt: bool,
}
impl<S: StorageEngine> NodeDbLite<S> {
pub async fn create_collection(
&self,
name: &str,
fields: &[(String, String)],
) -> NodeDbResult<()> {
let guard = self.fts_state.admit_mutation().await;
let result = self
.create_collection_admitted(guard.permit(), name, fields)
.await;
guard.finish(result)
}
pub(crate) async fn create_collection_admitted(
&self,
_permit: &crate::engine::fts::coordinator::TextMutationPermit,
name: &str,
fields: &[(String, String)],
) -> NodeDbResult<()> {
let meta = CollectionMeta {
name: name.to_string(),
collection_type: "document".to_string(),
created_at_ms: crate::runtime::now_millis(),
fields: fields.to_vec(),
config_json: None,
descriptor_json: None,
bitemporal: false,
crdt: false,
};
let key = format!("collection:{name}");
let bytes = sonic_rs::to_vec(&meta).map_err(|e| NodeDbError::storage(e.to_string()))?;
self.storage
.put(nodedb_types::Namespace::Meta, key.as_bytes(), &bytes)
.await?;
Ok(())
}
pub async fn create_kv_collection(
&self,
name: &str,
config: &nodedb_types::KvConfig,
) -> NodeDbResult<()> {
let guard = self.fts_state.admit_mutation().await;
let result = self
.create_kv_collection_admitted(guard.permit(), name, config)
.await;
guard.finish(result)
}
pub(crate) async fn create_kv_collection_admitted(
&self,
_permit: &crate::engine::fts::coordinator::TextMutationPermit,
name: &str,
config: &nodedb_types::KvConfig,
) -> NodeDbResult<()> {
let fields: Vec<(String, String)> = config
.schema
.columns
.iter()
.map(|c| (c.name.clone(), c.column_type.to_string()))
.collect();
let config_json =
sonic_rs::to_string(config).map_err(|e| NodeDbError::storage(e.to_string()))?;
let meta = CollectionMeta {
name: name.to_string(),
collection_type: "kv".to_string(),
created_at_ms: crate::runtime::now_millis(),
fields,
config_json: Some(config_json),
descriptor_json: None,
bitemporal: false,
crdt: false,
};
let key = format!("collection:{name}");
let bytes = sonic_rs::to_vec(&meta).map_err(|e| NodeDbError::storage(e.to_string()))?;
self.storage
.put(nodedb_types::Namespace::Meta, key.as_bytes(), &bytes)
.await?;
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn implicit_collection_meta(&self, name: &str) -> Option<CollectionMeta> {
if name.starts_with("__") {
return None;
}
let crdt = self.crdt.lock_or_recover();
if !crdt.collection_names().iter().any(|n| n == name) {
return None;
}
Some(CollectionMeta {
name: name.to_string(),
collection_type: "document".to_string(),
created_at_ms: 0,
fields: Vec::new(),
config_json: None,
descriptor_json: None,
bitemporal: false,
crdt: false,
})
}
pub async fn drop_collection(&self, name: &str) -> NodeDbResult<()> {
let guard = self.fts_state.admit_mutation().await;
let result = async {
self.fts_state
.persist_collection_tombstone(&*self.storage, name, guard.permit())
.await
.map_err(NodeDbError::from)?;
{
let mut crdt = self.crdt.lock_or_recover();
crdt.clear_collection(name).map_err(NodeDbError::storage)?;
}
crate::query::kv_ops::indexes::kv_drop_collection(&self.query_engine, name)
.await
.map_err(NodeDbError::storage)?;
{
let mut fts = self.fts_state.manager.lock_or_recover();
fts.drop_collection(name);
}
let key = format!("collection:{name}");
self.storage
.delete(nodedb_types::Namespace::Meta, key.as_bytes())
.await?;
Ok(())
}
.await;
guard.finish(result)
}
pub(crate) async fn collection_known(&self, name: &str) -> NodeDbResult<bool> {
if self
.crdt
.lock_or_recover()
.collection_names()
.iter()
.any(|n| n == name)
|| self.strict.schema(name).is_some()
|| self.columnar.schema(name).is_some()
{
return Ok(true);
}
let key = format!("collection:{name}");
Ok(self
.storage
.get(nodedb_types::Namespace::Meta, key.as_bytes())
.await?
.is_some())
}
pub async fn list_collections(&self) -> NodeDbResult<Vec<CollectionMeta>> {
let pairs = self
.storage
.scan_prefix(nodedb_types::Namespace::Meta, b"collection:")
.await?;
let mut result = Vec::new();
for (_, value) in &pairs {
if let Ok(meta) = sonic_rs::from_slice::<CollectionMeta>(value) {
result.push(meta);
}
}
let crdt = self.crdt.lock_or_recover();
let crdt_names = crdt.collection_names();
let explicit: std::collections::HashSet<String> =
result.iter().map(|m| m.name.clone()).collect();
for name in crdt_names {
if !name.starts_with("__") && !explicit.contains(&name) {
result.push(CollectionMeta {
name,
collection_type: "document".to_string(),
created_at_ms: 0,
fields: Vec::new(),
config_json: None,
descriptor_json: None,
bitemporal: false,
crdt: false,
});
}
}
Ok(result)
}
}
pub(crate) async fn load_persisted_collection_metas<S: StorageEngine>(
storage: &S,
) -> NodeDbResult<std::collections::HashMap<String, CollectionMeta>> {
let pairs = storage
.scan_prefix(nodedb_types::Namespace::Meta, b"collection:")
.await?;
let mut map = std::collections::HashMap::with_capacity(pairs.len());
for (_, value) in &pairs {
if let Ok(meta) = sonic_rs::from_slice::<CollectionMeta>(value) {
map.insert(meta.name.clone(), meta);
}
}
Ok(map)
}