//! Index structures for fast node and edge lookups //! //! Provides label indexes, property indexes, and edge type indexes for efficient querying use crate::edge::Edge; use crate::hyperedge::{Hyperedge, HyperedgeId}; use crate::node::Node; use crate::types::{EdgeId, NodeId, PropertyValue}; use dashmap::DashMap; use std::collections::HashSet; use std::sync::Arc; /// Label index for nodes (maps labels to node IDs) #[derive(Debug, Clone)] pub struct LabelIndex { /// Label -> Set of node IDs index: Arc>>, } impl LabelIndex { /// Create a new label index pub fn new() -> Self { Self { index: Arc::new(DashMap::new()), } } /// Add a node to the index pub fn add_node(&self, node: &Node) { for label in &node.labels { self.index .entry(label.name.clone()) .or_insert_with(HashSet::new) .insert(node.id.clone()); } } /// Remove a node from the index pub fn remove_node(&self, node: &Node) { for label in &node.labels { if let Some(mut set) = self.index.get_mut(&label.name) { set.remove(&node.id); } } } /// Get all nodes with a specific label pub fn get_nodes_by_label(&self, label: &str) -> Vec { self.index .get(label) .map(|set| set.iter().cloned().collect()) .unwrap_or_default() } /// Get all labels in the index pub fn all_labels(&self) -> Vec { self.index.iter().map(|entry| entry.key().clone()).collect() } /// Count nodes with a specific label pub fn count_by_label(&self, label: &str) -> usize { self.index.get(label).map(|set| set.len()).unwrap_or(0) } /// Clear the index pub fn clear(&self) { self.index.clear(); } } impl Default for LabelIndex { fn default() -> Self { Self::new() } } /// Property index for nodes (maps property keys to values to node IDs) #[derive(Debug, Clone)] pub struct PropertyIndex { /// Property key -> Property value -> Set of node IDs index: Arc>>>, } impl PropertyIndex { /// Create a new property index pub fn new() -> Self { Self { index: Arc::new(DashMap::new()), } } /// Add a node to the index pub fn add_node(&self, node: &Node) { for (key, value) in &node.properties { let value_str = self.property_value_to_string(value); self.index .entry(key.clone()) .or_insert_with(DashMap::new) .entry(value_str) .or_insert_with(HashSet::new) .insert(node.id.clone()); } } /// Remove a node from the index pub fn remove_node(&self, node: &Node) { for (key, value) in &node.properties { let value_str = self.property_value_to_string(value); if let Some(value_map) = self.index.get(key) { if let Some(mut set) = value_map.get_mut(&value_str) { set.remove(&node.id); } } } } /// Get nodes by property key-value pair pub fn get_nodes_by_property(&self, key: &str, value: &PropertyValue) -> Vec { let value_str = self.property_value_to_string(value); self.index .get(key) .and_then(|value_map| { value_map .get(&value_str) .map(|set| set.iter().cloned().collect()) }) .unwrap_or_default() } /// Get all nodes that have a specific property key (regardless of value) pub fn get_nodes_with_property(&self, key: &str) -> Vec { self.index .get(key) .map(|value_map| { let mut result = HashSet::new(); for entry in value_map.iter() { for id in entry.value().iter() { result.insert(id.clone()); } } result.into_iter().collect() }) .unwrap_or_default() } /// Get all property keys in the index pub fn all_property_keys(&self) -> Vec { self.index.iter().map(|entry| entry.key().clone()).collect() } /// Clear the index pub fn clear(&self) { self.index.clear(); } /// Convert property value to string for indexing fn property_value_to_string(&self, value: &PropertyValue) -> String { match value { PropertyValue::Null => "null".to_string(), PropertyValue::Boolean(b) => b.to_string(), PropertyValue::Integer(i) => i.to_string(), PropertyValue::Float(f) => f.to_string(), PropertyValue::String(s) => s.clone(), PropertyValue::Array(_) | PropertyValue::List(_) => format!("{:?}", value), PropertyValue::Map(_) => format!("{:?}", value), } } } impl Default for PropertyIndex { fn default() -> Self { Self::new() } } /// Edge type index (maps edge types to edge IDs) #[derive(Debug, Clone)] pub struct EdgeTypeIndex { /// Edge type -> Set of edge IDs index: Arc>>, } impl EdgeTypeIndex { /// Create a new edge type index pub fn new() -> Self { Self { index: Arc::new(DashMap::new()), } } /// Add an edge to the index pub fn add_edge(&self, edge: &Edge) { self.index .entry(edge.edge_type.clone()) .or_insert_with(HashSet::new) .insert(edge.id.clone()); } /// Remove an edge from the index pub fn remove_edge(&self, edge: &Edge) { if let Some(mut set) = self.index.get_mut(&edge.edge_type) { set.remove(&edge.id); } } /// Get all edges of a specific type pub fn get_edges_by_type(&self, edge_type: &str) -> Vec { self.index .get(edge_type) .map(|set| set.iter().cloned().collect()) .unwrap_or_default() } /// Get all edge types pub fn all_edge_types(&self) -> Vec { self.index.iter().map(|entry| entry.key().clone()).collect() } /// Count edges of a specific type pub fn count_by_type(&self, edge_type: &str) -> usize { self.index.get(edge_type).map(|set| set.len()).unwrap_or(0) } /// Clear the index pub fn clear(&self) { self.index.clear(); } } impl Default for EdgeTypeIndex { fn default() -> Self { Self::new() } } /// Adjacency index for fast neighbor lookups #[derive(Debug, Clone)] pub struct AdjacencyIndex { /// Node ID -> Set of outgoing edge IDs outgoing: Arc>>, /// Node ID -> Set of incoming edge IDs incoming: Arc>>, } impl AdjacencyIndex { /// Create a new adjacency index pub fn new() -> Self { Self { outgoing: Arc::new(DashMap::new()), incoming: Arc::new(DashMap::new()), } } /// Add an edge to the index pub fn add_edge(&self, edge: &Edge) { self.outgoing .entry(edge.from.clone()) .or_insert_with(HashSet::new) .insert(edge.id.clone()); self.incoming .entry(edge.to.clone()) .or_insert_with(HashSet::new) .insert(edge.id.clone()); } /// Remove an edge from the index pub fn remove_edge(&self, edge: &Edge) { if let Some(mut set) = self.outgoing.get_mut(&edge.from) { set.remove(&edge.id); } if let Some(mut set) = self.incoming.get_mut(&edge.to) { set.remove(&edge.id); } } /// Get all outgoing edges from a node pub fn get_outgoing_edges(&self, node_id: &NodeId) -> Vec { self.outgoing .get(node_id) .map(|set| set.iter().cloned().collect()) .unwrap_or_default() } /// Get all incoming edges to a node pub fn get_incoming_edges(&self, node_id: &NodeId) -> Vec { self.incoming .get(node_id) .map(|set| set.iter().cloned().collect()) .unwrap_or_default() } /// Get all edges connected to a node (both incoming and outgoing) pub fn get_all_edges(&self, node_id: &NodeId) -> Vec { let mut edges = self.get_outgoing_edges(node_id); edges.extend(self.get_incoming_edges(node_id)); edges } /// Get degree (number of outgoing edges) pub fn out_degree(&self, node_id: &NodeId) -> usize { self.outgoing.get(node_id).map(|set| set.len()).unwrap_or(0) } /// Get in-degree (number of incoming edges) pub fn in_degree(&self, node_id: &NodeId) -> usize { self.incoming.get(node_id).map(|set| set.len()).unwrap_or(0) } /// Clear the index pub fn clear(&self) { self.outgoing.clear(); self.incoming.clear(); } } impl Default for AdjacencyIndex { fn default() -> Self { Self::new() } } /// Hyperedge node index (maps nodes to hyperedges they participate in) #[derive(Debug, Clone)] pub struct HyperedgeNodeIndex { /// Node ID -> Set of hyperedge IDs index: Arc>>, } impl HyperedgeNodeIndex { /// Create a new hyperedge node index pub fn new() -> Self { Self { index: Arc::new(DashMap::new()), } } /// Add a hyperedge to the index pub fn add_hyperedge(&self, hyperedge: &Hyperedge) { for node_id in &hyperedge.nodes { self.index .entry(node_id.clone()) .or_insert_with(HashSet::new) .insert(hyperedge.id.clone()); } } /// Remove a hyperedge from the index pub fn remove_hyperedge(&self, hyperedge: &Hyperedge) { for node_id in &hyperedge.nodes { if let Some(mut set) = self.index.get_mut(node_id) { set.remove(&hyperedge.id); } } } /// Get all hyperedges containing a node pub fn get_hyperedges_by_node(&self, node_id: &NodeId) -> Vec { self.index .get(node_id) .map(|set| set.iter().cloned().collect()) .unwrap_or_default() } /// Clear the index pub fn clear(&self) { self.index.clear(); } } impl Default for HyperedgeNodeIndex { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; use crate::node::NodeBuilder; #[test] fn test_label_index() { let index = LabelIndex::new(); let node1 = NodeBuilder::new().label("Person").label("User").build(); let node2 = NodeBuilder::new().label("Person").build(); index.add_node(&node1); index.add_node(&node2); let people = index.get_nodes_by_label("Person"); assert_eq!(people.len(), 2); let users = index.get_nodes_by_label("User"); assert_eq!(users.len(), 1); assert_eq!(index.count_by_label("Person"), 2); } #[test] fn test_property_index() { let index = PropertyIndex::new(); let node1 = NodeBuilder::new() .property("name", "Alice") .property("age", 30i64) .build(); let node2 = NodeBuilder::new() .property("name", "Bob") .property("age", 30i64) .build(); index.add_node(&node1); index.add_node(&node2); let alice = index.get_nodes_by_property("name", &PropertyValue::String("Alice".to_string())); assert_eq!(alice.len(), 1); let age_30 = index.get_nodes_by_property("age", &PropertyValue::Integer(30)); assert_eq!(age_30.len(), 2); let with_age = index.get_nodes_with_property("age"); assert_eq!(with_age.len(), 2); } #[test] fn test_edge_type_index() { let index = EdgeTypeIndex::new(); let edge1 = Edge::create("n1".to_string(), "n2".to_string(), "KNOWS"); let edge2 = Edge::create("n2".to_string(), "n3".to_string(), "KNOWS"); let edge3 = Edge::create("n1".to_string(), "n3".to_string(), "WORKS_WITH"); index.add_edge(&edge1); index.add_edge(&edge2); index.add_edge(&edge3); let knows_edges = index.get_edges_by_type("KNOWS"); assert_eq!(knows_edges.len(), 2); let works_with_edges = index.get_edges_by_type("WORKS_WITH"); assert_eq!(works_with_edges.len(), 1); assert_eq!(index.all_edge_types().len(), 2); } #[test] fn test_adjacency_index() { let index = AdjacencyIndex::new(); let edge1 = Edge::create("n1".to_string(), "n2".to_string(), "KNOWS"); let edge2 = Edge::create("n1".to_string(), "n3".to_string(), "KNOWS"); let edge3 = Edge::create("n2".to_string(), "n1".to_string(), "KNOWS"); index.add_edge(&edge1); index.add_edge(&edge2); index.add_edge(&edge3); assert_eq!(index.out_degree(&"n1".to_string()), 2); assert_eq!(index.in_degree(&"n1".to_string()), 1); let outgoing = index.get_outgoing_edges(&"n1".to_string()); assert_eq!(outgoing.len(), 2); let incoming = index.get_incoming_edges(&"n1".to_string()); assert_eq!(incoming.len(), 1); } }