use nodedb_types::Namespace;
use nodedb_types::document::Document;
use nodedb_types::id::{EdgeId, NodeId};
use crate::engine::crdt::engine::CrdtBatchOp;
use crate::engine::graph::edge::{edge_crdt_fields, edge_id_for};
use crate::error::LiteError;
use crate::nodedb::LockExt;
use crate::nodedb::convert::loro_value_to_document;
use crate::nodedb::core::types::NodeDbLite;
use crate::storage::engine::StorageEngine;
const META_LEGACY_EDGE_KEYS_MIGRATED: &[u8] = b"meta:legacy_edge_keys_migrated";
struct LegacyEdgeRewrite {
collection: String,
old_key: String,
new_key: String,
fields: Vec<(String, loro::LoroValue)>,
}
impl<S: StorageEngine> NodeDbLite<S> {
pub(crate) async fn migrate_legacy_edge_keys(&self) -> Result<(), LiteError> {
let already_done = self
.storage
.get(Namespace::Meta, META_LEGACY_EDGE_KEYS_MIGRATED)
.await?
.is_some();
if already_done {
return Ok(());
}
let mut rewrites: Vec<LegacyEdgeRewrite> = Vec::new();
let mut saw_edge_collection = false;
{
let crdt = self.crdt.lock_or_recover();
let collections = crdt.collection_names();
for crdt_coll in &collections {
if !crdt_coll.starts_with("__edges__") {
continue;
}
saw_edge_collection = true;
for id in crdt.list_ids(crdt_coll) {
if id.parse::<EdgeId>().is_ok() {
continue;
}
let Some(loro_val) = crdt.read(crdt_coll, &id) else {
continue;
};
let doc = loro_value_to_document(&id, &loro_val);
let (Some(src), Some(dst), Some(label)) = (
doc.get_str("src").map(str::to_owned),
doc.get_str("dst").map(str::to_owned),
doc.get_str("label").map(str::to_owned),
) else {
continue;
};
let from = NodeId::from_validated(src);
let to = NodeId::from_validated(dst);
let Ok(edge_id) = edge_id_for(&from, &to, &label) else {
continue;
};
let new_key = format!("{edge_id}");
if new_key == id {
continue;
}
let mut props = Document::new(new_key.clone());
for (k, v) in doc.fields {
if k != "src" && k != "dst" && k != "label" {
props.fields.insert(k, v);
}
}
let properties = if props.fields.is_empty() {
None
} else {
Some(props)
};
let fields: Vec<(String, loro::LoroValue)> =
edge_crdt_fields(&from, &to, &label, &properties)
.into_iter()
.map(|(k, v)| (k.to_owned(), v))
.collect();
rewrites.push(LegacyEdgeRewrite {
collection: crdt_coll.clone(),
old_key: id,
new_key,
fields,
});
}
}
}
if rewrites.is_empty() {
if saw_edge_collection {
self.storage
.put(Namespace::Meta, META_LEGACY_EDGE_KEYS_MIGRATED, &[1u8])
.await?;
}
return Ok(());
}
{
let mut crdt = self.crdt.lock_or_recover();
let field_refs: Vec<Vec<(&str, loro::LoroValue)>> = rewrites
.iter()
.map(|r| {
r.fields
.iter()
.map(|(k, v)| (k.as_str(), v.clone()))
.collect()
})
.collect();
let ops: Vec<CrdtBatchOp<'_>> = rewrites
.iter()
.zip(field_refs.iter())
.map(|(r, fields)| (r.collection.as_str(), r.new_key.as_str(), fields.as_slice()))
.collect();
crdt.batch_upsert(&ops)?;
}
{
let mut crdt = self.crdt.lock_or_recover();
for r in &rewrites {
crdt.delete(&r.collection, &r.old_key)?;
}
}
if saw_edge_collection {
self.storage
.put(Namespace::Meta, META_LEGACY_EDGE_KEYS_MIGRATED, &[1u8])
.await?;
}
Ok(())
}
}