From 58b0947625e6c7de53e87bec3d7428a4d908e995 Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Mon, 13 Apr 2026 21:22:52 -0400 Subject: [PATCH] admin: convert fsck and dedup reads to use index - fsck: use for_each_relation for dangling edge detection (pruning deferred - needs delete_edge operation) - dedup: use for_each_relation for edge counting Remaining Vec uses in dedup mutation section need new index ops: - redirect_edge: change source/target UUID - delete_edge_by_uuid: tombstone by UUID Co-Authored-By: Kent Overstreet --- src/cli/admin.rs | 66 ++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/cli/admin.rs b/src/cli/admin.rs index 5e06cc3..de3c987 100644 --- a/src/cli/admin.rs +++ b/src/cli/admin.rs @@ -75,42 +75,35 @@ pub async fn cmd_fsck() -> Result<()> { } } - // Check edge endpoints + // Check edge endpoints using index + use crate::hippocampus::store::StoreView; let mut dangling = 0; - for rel in &store.relations { - if rel.deleted { continue; } - if !store.contains_key(&rel.source_key).unwrap_or(false) { - eprintln!("DANGLING: edge source '{}'", rel.source_key); + let mut orphan_edges: Vec<(String, String)> = Vec::new(); + store.for_each_relation(|source, target, _, _| { + let s_missing = !store.contains_key(source).unwrap_or(false); + let t_missing = !store.contains_key(target).unwrap_or(false); + if s_missing { + eprintln!("DANGLING: edge source '{}'", source); dangling += 1; } - if !store.contains_key(&rel.target_key).unwrap_or(false) { - eprintln!("DANGLING: edge target '{}'", rel.target_key); + if t_missing { + eprintln!("DANGLING: edge target '{}'", target); dangling += 1; } - } + if s_missing || t_missing { + orphan_edges.push((source.to_string(), target.to_string())); + } + }); // Prune orphan edges - let mut to_tombstone = Vec::new(); - for rel in &store.relations { - if rel.deleted { continue; } - if !store.contains_key(&rel.source_key).unwrap_or(false) - || !store.contains_key(&rel.target_key).unwrap_or(false) { - let mut tombstone = rel.clone(); - tombstone.deleted = true; - tombstone.version += 1; - to_tombstone.push(tombstone); + if !orphan_edges.is_empty() { + let count = orphan_edges.len(); + for (source, target) in &orphan_edges { + // set_link_strength with 0 would delete, but we don't have that + // For now just report - full cleanup requires more work + eprintln!("Would prune: {} → {}", source, target); } - } - if !to_tombstone.is_empty() { - let count = to_tombstone.len(); - store.append_relations(&to_tombstone)?; - for t in &to_tombstone { - if let Some(r) = store.relations.iter_mut().find(|r| r.uuid == t.uuid) { - r.deleted = true; - r.version = t.version; - } - } - eprintln!("Pruned {} orphan edges", count); + eprintln!("Found {} orphan edges (prune not yet implemented for index)", count); } let g = store.build_graph(); @@ -131,12 +124,19 @@ pub async fn cmd_dedup(apply: bool) -> Result<()> { return Ok(()); } - // Count edges per UUID + // Count edges per key (we'll map to UUID later) + use crate::hippocampus::store::StoreView; + let mut edges_by_key: HashMap = HashMap::new(); + store.for_each_relation(|source, target, _, _| { + *edges_by_key.entry(source.to_string()).or_default() += 1; + *edges_by_key.entry(target.to_string()).or_default() += 1; + }); + // Convert to edges_by_uuid for compatibility let mut edges_by_uuid: HashMap<[u8; 16], usize> = HashMap::new(); - for rel in &store.relations { - if rel.deleted { continue; } - *edges_by_uuid.entry(rel.source).or_default() += 1; - *edges_by_uuid.entry(rel.target).or_default() += 1; + for (key, count) in &edges_by_key { + if let Ok(Some(node)) = store.get_node(key) { + edges_by_uuid.insert(node.uuid, *count); + } } let mut identical_groups = Vec::new();