use std::path::PathBuf;
use log::debug;
use crate::graph::unified::concurrent::CodeGraph;
use crate::graph::unified::edge::kind::EdgeKind;
use crate::graph::unified::file::id::FileId;
use crate::graph::unified::node::id::NodeId;
use crate::graph::unified::storage::segment::FileSegment;
#[derive(Debug, Default, Clone)]
pub struct ReindexStats {
pub files_reindexed: usize,
pub nodes_tombstoned: usize,
pub edges_tombstoned: usize,
pub nodes_committed: usize,
pub edges_inserted: usize,
pub files_skipped: usize,
}
pub fn reindex_files(graph: &mut CodeGraph, changed_paths: &[PathBuf]) -> ReindexStats {
let mut stats = ReindexStats::default();
for path in changed_paths {
let file_id = match graph.files().get(path) {
Some(fid) => fid,
None => {
debug!(
"reindex: skipping {} — not in file registry",
path.display()
);
stats.files_skipped += 1;
continue;
}
};
let old_segment = match graph.file_segments().get(file_id).copied() {
Some(seg) => seg,
None => {
debug!(
"reindex: skipping {} (FileId {:?}) — no segment",
path.display(),
file_id
);
stats.files_skipped += 1;
continue;
}
};
let edges_tombstoned = tombstone_old_edges(graph, &old_segment);
let nodes_tombstoned = tombstone_old_nodes(graph, &old_segment);
graph.file_segments_mut().remove(file_id);
let _ = graph.files_mut().take_nodes(file_id);
stats.files_reindexed += 1;
stats.nodes_tombstoned += nodes_tombstoned;
stats.edges_tombstoned += edges_tombstoned;
}
stats
}
fn tombstone_old_edges(graph: &mut CodeGraph, segment: &FileSegment) -> usize {
let start = segment.start_slot;
let end = segment.end_slot();
let mut count = 0usize;
let mut edges_to_remove: Vec<(NodeId, NodeId, EdgeKind, FileId)> = Vec::new();
for idx in start..end {
if let Some(slot) = graph.nodes().slot(idx)
&& slot.is_occupied()
{
let node_id = NodeId::new(idx, slot.generation());
for edge_ref in &graph.edges().edges_from(node_id) {
edges_to_remove.push((
edge_ref.source,
edge_ref.target,
edge_ref.kind.clone(),
edge_ref.file,
));
}
for edge_ref in &graph.edges().edges_to(node_id) {
edges_to_remove.push((
edge_ref.source,
edge_ref.target,
edge_ref.kind.clone(),
edge_ref.file,
));
}
}
}
edges_to_remove.sort_by(|a, b| {
a.0.index()
.cmp(&b.0.index())
.then_with(|| a.1.index().cmp(&b.1.index()))
});
edges_to_remove.dedup_by(|a, b| {
a.0.index() == b.0.index()
&& a.1.index() == b.1.index()
&& std::mem::discriminant(&a.2) == std::mem::discriminant(&b.2)
});
for (source, target, kind, file) in edges_to_remove {
graph.edges_mut().remove_edge(source, target, kind, file);
count += 1;
}
count
}
fn tombstone_old_nodes(graph: &mut CodeGraph, segment: &FileSegment) -> usize {
let start = segment.start_slot;
let end = segment.end_slot();
let mut count = 0usize;
for idx in start..end {
if let Some(slot) = graph.nodes().slot(idx)
&& slot.is_occupied()
{
let generation = slot.generation();
let node_id = NodeId::new(idx, generation);
graph.nodes_mut().remove(node_id);
count += 1;
}
}
count
}
pub fn allocate_new_segment(
graph: &mut CodeGraph,
file_id: FileId,
node_count: u32,
) -> Result<u32, crate::graph::unified::storage::arena::ArenaError> {
let placeholder = crate::graph::unified::storage::NodeEntry::new(
crate::graph::unified::node::NodeKind::Other,
crate::graph::unified::string::id::StringId::new(0),
file_id,
);
let start = graph.nodes_mut().alloc_range(node_count, &placeholder)?;
graph
.file_segments_mut()
.record_range(file_id, start, node_count);
Ok(start)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reindex_stats_default() {
let stats = ReindexStats::default();
assert_eq!(stats.files_reindexed, 0);
assert_eq!(stats.nodes_tombstoned, 0);
assert_eq!(stats.edges_tombstoned, 0);
assert_eq!(stats.nodes_committed, 0);
assert_eq!(stats.edges_inserted, 0);
assert_eq!(stats.files_skipped, 0);
}
}