pub mod node;
pub mod relationship;
pub use node::Node;
pub use relationship::Relationship;
use crate::error::{GraphError, Result};
use crate::storage::Storage;
use serde_json::Value;
use std::collections::HashMap;
pub type Id = u64;
pub struct Graph {
pub storage: Storage,
next_node_id: Id,
next_relationship_id: Id,
}
impl Graph {
pub fn new() -> Result<Self> {
Ok(Graph {
storage: Storage::new()?,
next_node_id: 1,
next_relationship_id: 1,
})
}
pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
Ok(Graph {
storage: Storage::open(path)?,
next_node_id: 1,
next_relationship_id: 1,
})
}
pub fn create_node(&mut self, properties: HashMap<String, Value>) -> Result<Id> {
let id = self.next_node_id;
self.next_node_id += 1;
let node = Node::new(id, properties);
self.storage.store_node(node)?;
Ok(id)
}
pub fn get_node(&self, id: Id) -> Result<Option<Node>> {
self.storage.get_node(id)
}
pub fn create_relationship(
&mut self,
from_id: Id,
to_id: Id,
rel_type: String,
properties: HashMap<String, Value>,
) -> Result<Id> {
if self.get_node(from_id)?.is_none() {
return Err(GraphError::NotFound(format!("Node {from_id} not found")));
}
if self.get_node(to_id)?.is_none() {
return Err(GraphError::NotFound(format!("Node {to_id} not found")));
}
let id = self.next_relationship_id;
self.next_relationship_id += 1;
let relationship = Relationship::new(id, from_id, to_id, rel_type, properties);
self.storage.store_relationship(relationship)?;
Ok(id)
}
pub fn get_relationship(&self, id: Id) -> Result<Option<Relationship>> {
self.storage.get_relationship(id)
}
pub fn get_relationships_for_node(&self, node_id: Id) -> Result<Vec<Relationship>> {
self.storage.get_relationships_for_node(node_id)
}
pub fn create_property_index(&mut self, property_key: String) {
self.storage.create_property_index(property_key);
}
pub fn create_range_index(&mut self, property_key: String) {
self.storage.create_range_index(property_key);
}
pub fn create_composite_index(&mut self, property_keys: Vec<String>) {
self.storage.create_composite_index(property_keys);
}
pub fn find_nodes_by_property(
&self,
property_key: &str,
property_value: &Value,
) -> Result<Vec<Node>> {
let node_ids = self.storage.find_by_property(property_key, property_value);
let mut nodes = Vec::new();
for node_id in node_ids {
if let Some(node) = self.get_node(node_id)? {
nodes.push(node);
}
}
Ok(nodes)
}
pub fn find_nodes_in_range(
&self,
property_key: &str,
min_value: &Value,
max_value: &Value,
) -> Result<Vec<Node>> {
let node_ids = self
.storage
.find_in_range(property_key, min_value, max_value);
let mut nodes = Vec::new();
for node_id in node_ids {
if let Some(node) = self.get_node(node_id)? {
nodes.push(node);
}
}
Ok(nodes)
}
pub fn find_relationships_by_type(&self, rel_type: &str) -> Result<Vec<Relationship>> {
let rel_ids = self.storage.find_relationships_by_type(rel_type);
let mut relationships = Vec::new();
for rel_id in rel_ids {
if let Some(relationship) = self.get_relationship(rel_id)? {
relationships.push(relationship);
}
}
Ok(relationships)
}
pub fn find_outgoing_relationships(
&self,
from_id: Id,
rel_type: &str,
) -> Result<Vec<Relationship>> {
let rel_ids = self.storage.find_outgoing_relationships(from_id, rel_type);
let mut relationships = Vec::new();
for rel_id in rel_ids {
if let Some(relationship) = self.get_relationship(rel_id)? {
relationships.push(relationship);
}
}
Ok(relationships)
}
pub fn find_incoming_relationships(
&self,
to_id: Id,
rel_type: &str,
) -> Result<Vec<Relationship>> {
let rel_ids = self.storage.find_incoming_relationships(to_id, rel_type);
let mut relationships = Vec::new();
for rel_id in rel_ids {
if let Some(relationship) = self.get_relationship(rel_id)? {
relationships.push(relationship);
}
}
Ok(relationships)
}
pub fn get_index_stats(&self) -> crate::index::IndexStats {
self.storage.get_index_stats()
}
pub fn flush(&self) -> Result<()> {
self.storage.flush()
}
pub fn delete_node(&mut self, id: Id) -> Result<Option<Node>> {
if self.storage.node_has_relationships(id) {
return Err(crate::error::GraphError::Storage(
"Cannot delete node with existing relationships".into(),
));
}
self.storage.delete_node(id)
}
pub fn detach_delete_node(&mut self, id: Id) -> Result<Option<Node>> {
let rel_ids: Vec<Id> = self
.storage
.get_relationships_for_node(id)?
.iter()
.map(|r| r.id)
.collect();
for rel_id in rel_ids {
self.storage.delete_relationship(rel_id)?;
}
self.storage.delete_node(id)
}
pub fn delete_relationship(&mut self, id: Id) -> Result<Option<Relationship>> {
self.storage.delete_relationship(id)
}
pub fn update_node_properties(
&mut self,
id: Id,
properties: HashMap<String, Value>,
) -> Result<Option<Node>> {
if let Some(mut node) = self.storage.get_node(id)? {
for (key, value) in properties {
node.properties.insert(key, value);
}
self.storage.store_node(node.clone())?;
Ok(Some(node))
} else {
Ok(None)
}
}
pub fn update_relationship_properties(
&mut self,
id: Id,
properties: HashMap<String, Value>,
) -> Result<Option<Relationship>> {
if let Some(mut rel) = self.storage.get_relationship(id)? {
for (key, value) in properties {
rel.properties.insert(key, value);
}
self.storage.store_relationship(rel.clone())?;
Ok(Some(rel))
} else {
Ok(None)
}
}
pub fn remove_node_properties(
&mut self,
id: Id,
property_names: &[&str],
) -> Result<Option<Node>> {
if let Some(mut node) = self.storage.get_node(id)? {
for name in property_names {
node.properties.remove(*name);
}
self.storage.store_node(node.clone())?;
Ok(Some(node))
} else {
Ok(None)
}
}
pub fn remove_relationship_properties(
&mut self,
id: Id,
property_names: &[&str],
) -> Result<Option<Relationship>> {
if let Some(mut rel) = self.storage.get_relationship(id)? {
for name in property_names {
rel.properties.remove(*name);
}
self.storage.store_relationship(rel.clone())?;
Ok(Some(rel))
} else {
Ok(None)
}
}
}
impl Default for Graph {
fn default() -> Self {
Self::new().expect("Failed to create default graph")
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_create_node() {
let mut graph = Graph::new().unwrap();
let properties = [("name".to_string(), json!("Alice"))].into();
let node_id = graph.create_node(properties).unwrap();
assert_eq!(node_id, 1);
}
#[test]
fn test_create_relationship() {
let mut graph = Graph::new().unwrap();
let node1_id = graph.create_node(HashMap::new()).unwrap();
let node2_id = graph.create_node(HashMap::new()).unwrap();
let rel_id = graph
.create_relationship(node1_id, node2_id, "KNOWS".to_string(), HashMap::new())
.unwrap();
assert_eq!(rel_id, 1);
}
#[test]
fn test_graph_indexing() {
let mut graph = Graph::new().unwrap();
graph.create_property_index("name".to_string());
graph.create_range_index("age".to_string());
let mut props1 = HashMap::new();
props1.insert("name".to_string(), json!("Alice"));
props1.insert("age".to_string(), json!(25));
let node1_id = graph.create_node(props1).unwrap();
let mut props2 = HashMap::new();
props2.insert("name".to_string(), json!("Bob"));
props2.insert("age".to_string(), json!(30));
let node2_id = graph.create_node(props2).unwrap();
let mut props3 = HashMap::new();
props3.insert("name".to_string(), json!("Alice"));
props3.insert("age".to_string(), json!(35));
let _node3_id = graph.create_node(props3).unwrap();
let alice_nodes = graph
.find_nodes_by_property("name", &json!("Alice"))
.unwrap();
assert_eq!(alice_nodes.len(), 2);
let age_range_nodes = graph
.find_nodes_in_range("age", &json!(28), &json!(32))
.unwrap();
assert_eq!(age_range_nodes.len(), 1);
assert_eq!(age_range_nodes[0].id, node2_id);
let rel_id = graph
.create_relationship(node1_id, node2_id, "KNOWS".to_string(), HashMap::new())
.unwrap();
let knows_rels = graph.find_relationships_by_type("KNOWS").unwrap();
assert_eq!(knows_rels.len(), 1);
assert_eq!(knows_rels[0].id, rel_id);
let outgoing_rels = graph
.find_outgoing_relationships(node1_id, "KNOWS")
.unwrap();
assert_eq!(outgoing_rels.len(), 1);
assert_eq!(outgoing_rels[0].id, rel_id);
let stats = graph.get_index_stats();
assert!(stats.property_index_count > 0);
assert!(stats.range_index_count > 0);
}
}