| Crates.io | graphqlite |
| lib.rs | graphqlite |
| version | 0.3.0 |
| created_at | 2025-12-24 04:10:21.486315+00 |
| updated_at | 2026-01-10 14:43:54.356444+00 |
| description | SQLite extension for graph queries using Cypher |
| homepage | |
| repository | https://github.com/colliery-io/graphqlite |
| max_upload_size | |
| id | 2002730 |
| size | 4,035,415 |
Rust bindings for GraphQLite, a SQLite extension that adds graph database capabilities using Cypher.
Add to your Cargo.toml:
[dependencies]
graphqlite = "0.1.0-beta"
The Graph struct provides an ergonomic interface for common graph operations:
use graphqlite::Graph;
fn main() -> graphqlite::Result<()> {
let g = Graph::open(":memory:")?;
// Add nodes
g.upsert_node("alice", [("name", "Alice"), ("age", "30")], "Person")?;
g.upsert_node("bob", [("name", "Bob"), ("age", "25")], "Person")?;
// Add edge
g.upsert_edge("alice", "bob", [("since", "2020")], "KNOWS")?;
// Query
println!("{:?}", g.stats()?); // GraphStats { nodes: 2, edges: 1 }
println!("{:?}", g.get_neighbors("alice")?);
// Graph algorithms
let ranks = g.pagerank(0.85, 20)?;
let communities = g.community_detection(10)?;
Ok(())
}
For complex queries or when you need full control:
use graphqlite::Connection;
fn main() -> graphqlite::Result<()> {
let conn = Connection::open("graph.db")?;
conn.cypher("CREATE (a:Person {name: 'Alice', age: 30})")?;
conn.cypher("CREATE (b:Person {name: 'Bob', age: 25})")?;
conn.cypher(r#"
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
CREATE (a)-[:KNOWS]->(b)
"#)?;
let results = conn.cypher("MATCH (a:Person)-[:KNOWS]->(b) RETURN a.name, b.name")?;
for row in &results {
let a: String = row.get("a.name")?;
let b: String = row.get("b.name")?;
println!("{} knows {}", a, b);
}
Ok(())
}
use graphqlite::{Graph, graph};
// Constructor
let g = Graph::open("graph.db")?;
let g = Graph::open(":memory:")?;
let g = Graph::open_in_memory()?;
// Or use the factory function
let g = graph(":memory:")?;
| Method | Description |
|---|---|
upsert_node(id, props, label) |
Create or update a node |
get_node(id) |
Get node by ID |
has_node(id) |
Check if node exists |
delete_node(id) |
Delete node and its edges |
get_all_nodes(label) |
Get all nodes, optionally by label |
| Method | Description |
|---|---|
upsert_edge(src, dst, props, type) |
Create edge between nodes |
get_edge(src, dst) |
Get edge properties |
has_edge(src, dst) |
Check if edge exists |
delete_edge(src, dst) |
Delete edge |
get_all_edges() |
Get all edges |
| Method | Description |
|---|---|
node_degree(id) |
Count edges connected to node |
get_neighbors(id) |
Get adjacent nodes |
stats() |
Get node/edge counts |
query(cypher) |
Execute raw Cypher query |
Centrality
| Method | Description |
|---|---|
pagerank(damping, iterations) |
PageRank importance scores |
degree_centrality() |
In/out/total degree for each node |
betweenness_centrality() |
Betweenness centrality scores |
closeness_centrality() |
Closeness centrality scores |
eigenvector_centrality(iterations) |
Eigenvector centrality scores |
Community Detection
| Method | Description |
|---|---|
community_detection(iterations) |
Label propagation communities |
louvain(resolution) |
Louvain modularity optimization |
Connected Components
| Method | Description |
|---|---|
wcc() |
Weakly connected components |
scc() |
Strongly connected components |
Path Finding
| Method | Description |
|---|---|
shortest_path(src, dst, weight) |
Dijkstra's shortest path |
astar(src, dst, lat, lon) |
A* with optional heuristic |
apsp() |
All-pairs shortest paths (Floyd-Warshall) |
Traversal
| Method | Description |
|---|---|
bfs(start, max_depth) |
Breadth-first search |
dfs(start, max_depth) |
Depth-first search |
Similarity
| Method | Description |
|---|---|
node_similarity(n1, n2, threshold, k) |
Jaccard similarity |
knn(node, k) |
K-nearest neighbors |
triangle_count() |
Triangle counts and clustering coefficients |
// Batch insert nodes (upsert semantics)
g.upsert_nodes_batch([
("n1", [("name", "Node1")], "Type"),
("n2", [("name", "Node2")], "Type"),
])?;
// Batch insert edges (upsert semantics)
g.upsert_edges_batch([
("n1", "n2", [("weight", "1.0")], "CONNECTS"),
])?;
For maximum throughput when building graphs from external data, use the bulk insert methods. These bypass Cypher parsing entirely and use direct SQL, achieving 100-500x faster insert rates.
// Bulk insert nodes - returns HashMap<external_id, internal_rowid>
let id_map = g.insert_nodes_bulk([
("alice", vec![("name", "Alice"), ("age", "30")], "Person"),
("bob", vec![("name", "Bob"), ("age", "25")], "Person"),
("charlie", vec![("name", "Charlie")], "Person"),
])?;
// Bulk insert edges using the ID map - no MATCH queries needed!
let edges_inserted = g.insert_edges_bulk(
[
("alice", "bob", vec![("since", "2020")], "KNOWS"),
("bob", "charlie", vec![("since", "2021")], "KNOWS"),
],
&id_map,
)?;
// Or use the convenience method for both
let result = g.insert_graph_bulk(nodes, edges)?;
println!("Inserted {} nodes, {} edges", result.nodes_inserted, result.edges_inserted);
// Resolve existing node IDs (for edges to pre-existing nodes)
let resolved = g.resolve_node_ids(["alice", "bob"])?;
| Method | Description |
|---|---|
insert_nodes_bulk(nodes) |
Insert nodes, returns ID mapping |
insert_edges_bulk(edges, id_map) |
Insert edges using ID map |
insert_graph_bulk(nodes, edges) |
Insert both, returns BulkInsertResult |
resolve_node_ids(ids) |
Resolve external IDs to internal rowids |
use graphqlite::Connection;
let conn = Connection::open("graph.db")?;
let conn = Connection::open(":memory:")?;
let conn = Connection::open_in_memory()?;
// Wrap existing rusqlite connection
let sqlite_conn = rusqlite::Connection::open_in_memory()?;
let conn = Connection::from_rusqlite(sqlite_conn)?;
| Method | Description |
|---|---|
cypher(query) |
Execute Cypher query, return results |
execute(sql) |
Execute raw SQL |
sqlite_connection() |
Access underlying rusqlite connection |
let results = conn.cypher("MATCH (n) RETURN n.name")?;
results.len(); // Number of rows
results.is_empty(); // Check if empty
results.columns(); // Column names
results[0]; // First row
for row in &results {
let name: String = row.get("n.name")?;
}
Type-safe value extraction:
let name: String = row.get("name")?;
let age: i64 = row.get("age")?;
let score: f64 = row.get("score")?;
let active: bool = row.get("active")?;
let maybe: Option<String> = row.get("optional")?;
use graphqlite::{escape_string, sanitize_rel_type, CYPHER_RESERVED};
// Escape strings for Cypher queries
let safe = escape_string("it's"); // "it\\'s"
// Sanitize relationship types
let rel = sanitize_rel_type("has-items"); // "has_items"
let rel = sanitize_rel_type("CREATE"); // "REL_CREATE"
// Check reserved keywords
if CYPHER_RESERVED.contains(&"MATCH") {
println!("MATCH is reserved");
}
The extension is located automatically. To specify a custom path:
let conn = Connection::open_with_extension("graph.db", "/path/to/graphqlite.dylib")?;
Or set the GRAPHQLITE_EXTENSION_PATH environment variable.
MIT