SimpleGraph
SimpleGraph
is the reference implementation for the Graph API, designed primarily to showcase and test the full
capabilities of the API, including comprehensive indexing support for property graphs. While functional, it's not
optimized for high performance.
Installation
Add SimpleGraph to your project dependencies:
[dependencies]
graph-api-lib = "0.2.0"
graph-api-derive = "0.1.4" # For derive macros
graph-api-simplegraph = "0.2.1"
Overview
SimpleGraph
is a custom in-memory graph implementation built specifically to demonstrate and validate all Graph API
features, including every type of index. It serves as a clear example for developers implementing the Graph API traits
and is invaluable for testing Graph API consumers against a fully compliant backend. It handles property graph use cases
where elements have labels (enum variants) and indexed properties.
#![allow(unused)] fn main() { use graph_api_simplegraph::SimpleGraph; use graph_api_derive::{VertexExt, EdgeExt}; use graph_api_lib::Graph; // Define vertex and edge types #[derive(Debug, Clone, VertexExt)] pub enum Vertex { Person { #[index(hash)] name: String, #[index(range)] age: u64, }, Project { name: String }, } #[derive(Debug, Clone, EdgeExt)] pub enum Edge { Knows { since: i32 }, Created, } // Create a new graph let mut graph = SimpleGraph::new(); // Use the graph let alice = graph.add_vertex(Vertex::Person { name: "Alice".to_string(), age: 30 }); let project = graph.add_vertex(Vertex::Project { name: "Graph API".to_string() }); graph.add_edge(alice, project, Edge::Created); }
Architecture
SimpleGraph
uses a custom data structure designed specifically for property graphs:
-
Vertex Storage: Vertices are stored in collections grouped by label (enum variant), allowing for efficient label-based filtering.
-
Edge Storage: Edges are stored with both head and tail connections, enabling fast traversal in both directions.
-
Indexes: Multiple index types are implemented to support different query patterns:
- Label indexes for finding vertices/edges by label
- Hash indexes for exact property matching
- Range indexes for numeric and string range queries
- Full-text indexes for text search
-
Adjacency Lists: Each vertex maintains an adjacency list for fast edge traversal.
Features
SimpleGraph
supports all Graph API features:
- ✅ Vertex label indexes
- ✅ Edge label indexes
- ✅ Vertex hash indexes
- ❌ Edge hash indexes
- ✅ Vertex range indexes
- ❌ Edge range indexes
- ✅ Vertex full-text indexes
- ✅ Edge adjacent label indexes (this isn't fully supported in graph-api-lib yet)
- ✅ Graph clearing
Performance Characteristics
SimpleGraph
is primarily designed for feature completeness and ease of understanding, not for high performance:
- Correctness over Speed: Implementation prioritizes demonstrating API features correctly.
- Basic Optimizations: Includes fundamental optimizations like adjacency lists but lacks advanced performance tuning.
Benchmarks
Benchmarks exist primarily to validate the functionality of the API features rather than to showcase raw speed. Expect:
- Functional insertion and removal.
- Correct index lookups (hash, range, full-text) but potentially slower than highly optimized alternatives.
- Basic traversal efficiency.
Use Cases
SimpleGraph
is ideal for:
- Testing: Verifying code that uses the Graph API against a fully compliant implementation.
- Reference: Understanding how to implement the Graph API traits and features.
- Learning: Exploring the capabilities of the Graph API in a controlled environment.
- Prototyping: Quickly building graph-based applications where performance is not the primary concern initially.
Implementation Notes
SimpleGraph
is implemented using several key components:
Core Data Structures
LabelledVertices
: Stores vertices by label, with efficient access by IDLabelledEdges
: Stores edges by labelVertexStorage
: Maintains vertex data and adjacency informationVertexIndexStorage
: Handles different index types (hash, range, full-text)
Indexes
SimpleGraph
implements several index types:
HashIndex
: Maps property values to sets of vertex/edge IDsRangeIndex
: Uses ordered maps for range queriesFullTextIndex
: Implements inverted indexes for text search
Mutation Handling
To maintain index consistency, SimpleGraph
uses a mutation listener system:
#![allow(unused)] fn main() { // When a vertex property changes, the index is automatically updated graph.vertex_mut(vertex_id) .unwrap() .project_mut::<PersonMut<_ > > () .unwrap() .set_name("New Name"); }
Limitations
While SimpleGraph
is a robust implementation, it has some limitations:
- In-memory only: All graph data must fit in memory
- Single-threaded: No built-in support for concurrent access
- No persistence: No built-in support for saving/loading graphs
Source Code
The source code for SimpleGraph
is available in
the graph-api-simplegraph crate.
Example Usage
Here's a complete example demonstrating key features of SimpleGraph
:
#![allow(unused)] fn main() { use graph_api_simplegraph::SimpleGraph; use graph_api_derive::{VertexExt, EdgeExt}; use graph_api_lib::{Graph, VertexSearch, EdgeSearch, Direction}; // Define vertex and edge types #[derive(Debug, Clone, VertexExt)] pub enum Vertex { Person { #[index(hash)] name: String, #[index(range)] age: u64, #[index(full_text)] bio: String, }, Project { name: String }, } #[derive(Debug, Clone, EdgeExt)] pub enum Edge { Knows { since: i32 }, Created, } // Create a new graph let mut graph = SimpleGraph::new(); // Add vertices let alice = graph.add_vertex(Vertex::Person { name: "Alice".to_string(), age: 30, bio: "Graph enthusiast".to_string(), }); let bob = graph.add_vertex(Vertex::Person { name: "Bob".to_string(), age: 25, bio: "Software developer".to_string(), }); let project = graph.add_vertex(Vertex::Project { name: "Graph API".to_string() }); // Add edges graph.add_edge(alice, bob, Edge::Knows { since: 2020 }); graph.add_edge(alice, project, Edge::Created); graph.add_edge(bob, project, Edge::Created); // Query by label let people = graph.walk() .vertices(Vertex::person_all()) .collect::<Vec<_ > > (); assert_eq!(people.len(), 2); // Query by property (hash index) let alice_found = graph.walk() .vertices(Vertex::person_by_name("Alice")) .collect::<Vec<_ > > (); assert_eq!(alice_found.len(), 1); // Query by range (range index) let young_people = graph.walk() .vertices(Vertex::person_by_age(20..30)) .collect::<Vec<_ > > (); assert_eq!(young_people.len(), 1); // Text search (full-text index) let developers = graph.walk() .vertices(Vertex::person_by_bio("developer")) .collect::<Vec<_ > > (); assert_eq!(developers.len(), 1); // Traversal let alices_creations = graph.walk() .vertices(Vertex::person_by_name("Alice")) .edges(Edge::created().direction(Direction::Outgoing)) .head() .collect::<Vec<_ > > (); assert_eq!(alices_creations.len(), 1); }
Advanced Features
SimpleGraph
supports several advanced features:
Custom Indexing
You can define custom indexes on vertex and edge properties:
#![allow(unused)] fn main() { #[derive(Debug, Clone, VertexExt)] pub enum Vertex { Person { #[index(hash)] id: u64, #[index(range)] score: f64, #[index(full_text)] description: String, }, } }
Mutation with Index Updates
When properties are modified, indexes are automatically updated:
#![allow(unused)] fn main() { // Update an indexed property graph.vertex_mut(vertex_id) .unwrap() .project_mut::<PersonMut<_ > > () .unwrap() .set_age(31); // The age index is automatically updated, so this query now works let people = graph.walk() .vertices(Vertex::person_by_age(31..32)) .collect::<Vec<_ > > (); assert_eq!(people.len(), 1); }