use super::super::edge::EdgeKind;
use super::super::file::FileId;
use super::super::node::NodeId;
use super::super::storage::{AuxiliaryIndices, NodeArena};
use super::identity::IdentityIndex;
use super::pass3_intra::PendingEdge;
#[derive(Debug, Clone, Default)]
pub struct IncrementalStats {
pub nodes_removed: usize,
pub edges_removed: usize,
pub edges_added: usize,
pub identity_entries_removed: usize,
}
#[derive(Debug)]
pub struct FileRemovalResult {
pub stats: IncrementalStats,
pub removed_nodes: Vec<NodeId>,
}
pub fn remove_file_nodes(
file_id: FileId,
identity_index: &mut IdentityIndex,
arena: &mut NodeArena,
indices: &mut AuxiliaryIndices,
) -> FileRemovalResult {
let mut stats = IncrementalStats::default();
let removed_entries = identity_index.remove_file(file_id);
stats.identity_entries_removed = removed_entries.len();
let removed_nodes: Vec<NodeId> = removed_entries.iter().map(|(_, id)| *id).collect();
stats.nodes_removed = removed_nodes.len();
let node_metadata: Vec<_> = removed_nodes
.iter()
.filter_map(|&node_id| {
arena
.get(node_id)
.map(|entry| (node_id, entry.kind, entry.name, entry.qualified_name))
})
.collect();
indices.remove_file_with_info(file_id, node_metadata);
for &node_id in &removed_nodes {
let _ = arena.remove(node_id);
}
FileRemovalResult {
stats,
removed_nodes,
}
}
#[must_use]
pub fn add_edge_incremental(
source: NodeId,
target: NodeId,
kind: EdgeKind,
file: FileId,
) -> (IncrementalStats, PendingEdge) {
let stats = IncrementalStats {
edges_added: 1,
..Default::default()
};
let edge = PendingEdge {
source,
target,
kind,
file,
spans: vec![], };
(stats, edge)
}
#[must_use]
pub fn add_edges_incremental(edges: &[PendingEdge]) -> IncrementalStats {
IncrementalStats {
edges_added: edges.len(),
..Default::default()
}
}
pub fn remove_node(
node_id: NodeId,
identity_index: &mut IdentityIndex,
arena: &mut NodeArena,
) -> IncrementalStats {
let mut stats = IncrementalStats::default();
if identity_index.remove_node_id(node_id).is_some() {
stats.identity_entries_removed = 1;
}
if arena.remove(node_id).is_some() {
stats.nodes_removed = 1;
}
stats
}
pub fn remove_nodes_batch(
node_ids: &[NodeId],
identity_index: &mut IdentityIndex,
arena: &mut NodeArena,
) -> IncrementalStats {
let mut stats = IncrementalStats::default();
for &node_id in node_ids {
if identity_index.remove_node_id(node_id).is_some() {
stats.identity_entries_removed += 1;
}
if arena.remove(node_id).is_some() {
stats.nodes_removed += 1;
}
}
stats
}
#[derive(Debug, Clone)]
pub struct EdgeToRemove {
pub source: NodeId,
pub target: NodeId,
pub kind: EdgeKind,
pub file: FileId,
}
#[cfg(test)]
mod tests {
use super::super::identity::IdentityKey;
use super::*;
use crate::graph::unified::StringId;
use crate::graph::unified::node::NodeKind;
use crate::graph::unified::storage::NodeEntry;
fn create_test_entry(name_id: StringId, file_id: FileId) -> NodeEntry {
NodeEntry::new(NodeKind::Function, name_id, file_id)
}
#[test]
fn test_remove_file_nodes() {
let mut arena = NodeArena::new();
let mut identity_index = IdentityIndex::new();
let mut indices = AuxiliaryIndices::new();
let file_id = FileId::new(5);
let name_id = StringId::new(1);
let entry1 = create_test_entry(name_id, file_id);
let entry2 = create_test_entry(name_id, file_id);
let node1 = arena.alloc(entry1).unwrap();
let node2 = arena.alloc(entry2).unwrap();
let key1 = IdentityKey::new(StringId::new(1), file_id, StringId::new(10));
let key2 = IdentityKey::new(StringId::new(1), file_id, StringId::new(11));
identity_index.insert(key1, node1);
identity_index.insert(key2, node2);
let result = remove_file_nodes(file_id, &mut identity_index, &mut arena, &mut indices);
assert_eq!(result.stats.nodes_removed, 2);
assert_eq!(result.stats.identity_entries_removed, 2);
assert_eq!(result.removed_nodes.len(), 2);
assert!(arena.get(node1).is_none());
assert!(arena.get(node2).is_none());
}
#[test]
fn test_add_edge_incremental() {
let source = NodeId::new(0, 1);
let target = NodeId::new(1, 1);
let file_id = FileId::new(0);
let (stats, edge) = add_edge_incremental(
source,
target,
EdgeKind::Calls {
argument_count: 0,
is_async: false,
},
file_id,
);
assert_eq!(stats.edges_added, 1);
assert_eq!(edge.source, source);
assert_eq!(edge.target, target);
assert!(matches!(
edge.kind,
EdgeKind::Calls {
argument_count: 0,
is_async: false
}
));
}
#[test]
fn test_add_edges_incremental_batch() {
let file_id = FileId::new(0);
let edges = vec![
PendingEdge {
source: NodeId::new(0, 1),
target: NodeId::new(1, 1),
kind: EdgeKind::Calls {
argument_count: 0,
is_async: false,
},
file: file_id,
spans: vec![],
},
PendingEdge {
source: NodeId::new(1, 1),
target: NodeId::new(2, 1),
kind: EdgeKind::References,
file: file_id,
spans: vec![],
},
PendingEdge {
source: NodeId::new(2, 1),
target: NodeId::new(0, 1),
kind: EdgeKind::Calls {
argument_count: 0,
is_async: false,
},
file: file_id,
spans: vec![],
},
];
let stats = add_edges_incremental(&edges);
assert_eq!(stats.edges_added, 3);
}
#[test]
fn test_remove_node() {
let mut arena = NodeArena::new();
let mut identity_index = IdentityIndex::new();
let file_id = FileId::new(0);
let name_id = StringId::new(1);
let entry = create_test_entry(name_id, file_id);
let node_id = arena.alloc(entry).unwrap();
let key = IdentityKey::new(StringId::new(1), file_id, StringId::new(10));
identity_index.insert(key, node_id);
let stats = remove_node(node_id, &mut identity_index, &mut arena);
assert_eq!(stats.nodes_removed, 1);
assert_eq!(stats.identity_entries_removed, 1);
assert!(arena.get(node_id).is_none());
}
#[test]
fn test_remove_nodes_batch() {
let mut arena = NodeArena::new();
let mut identity_index = IdentityIndex::new();
let file_id = FileId::new(0);
let name_id = StringId::new(1);
let node1 = arena.alloc(create_test_entry(name_id, file_id)).unwrap();
let node2 = arena.alloc(create_test_entry(name_id, file_id)).unwrap();
let node3 = arena.alloc(create_test_entry(name_id, file_id)).unwrap();
identity_index.insert(
IdentityKey::new(StringId::new(1), file_id, StringId::new(10)),
node1,
);
identity_index.insert(
IdentityKey::new(StringId::new(1), file_id, StringId::new(11)),
node2,
);
let stats = remove_nodes_batch(&[node1, node2, node3], &mut identity_index, &mut arena);
assert_eq!(stats.nodes_removed, 3);
assert_eq!(stats.identity_entries_removed, 2);
assert!(arena.get(node1).is_none());
assert!(arena.get(node2).is_none());
assert!(arena.get(node3).is_none());
}
#[test]
fn test_remove_nonexistent_node() {
let mut arena = NodeArena::new();
let mut identity_index = IdentityIndex::new();
let fake_id = NodeId::new(999, 1);
let stats = remove_node(fake_id, &mut identity_index, &mut arena);
assert_eq!(stats.nodes_removed, 0);
assert_eq!(stats.identity_entries_removed, 0);
}
#[test]
fn test_incremental_stats_default() {
let stats = IncrementalStats::default();
assert_eq!(stats.nodes_removed, 0);
assert_eq!(stats.edges_removed, 0);
assert_eq!(stats.edges_added, 0);
assert_eq!(stats.identity_entries_removed, 0);
}
}