Skip to main content

Rust SDK

The Rust SDK (graphmind-sdk) provides both embedded and remote access to Graphmind. In embedded mode the database runs in-process with zero network overhead. In remote mode it connects to a running server over HTTP.

Installation

Add to your Cargo.toml:

[dependencies]
graphmind-sdk = "0.6.4"
tokio = { version = "1", features = ["full"] }

Quick Start -- Embedded Mode

No server required. The graph lives in your process memory.

use graphmind_sdk::{EmbeddedClient, GraphmindClient};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = EmbeddedClient::new();

// Create nodes and edges (semicolons separate statements)
client.query("default", r#"
CREATE (a:Person {name: "Alice", age: 30});
CREATE (b:Person {name: "Bob", age: 25});
MATCH (a:Person {name: "Alice"}), (b:Person {name: "Bob"})
CREATE (a)-[:KNOWS {since: 2020}]->(b)
"#).await?;

// Query
let result = client.query_readonly("default",
"MATCH (p:Person) RETURN p.name, p.age ORDER BY p.age"
).await?;

for record in &result.records {
println!("{:?}", record);
}

Ok(())
}

Quick Start -- Remote Mode

Connect to a running Graphmind server (RESP on :6379, HTTP on :8080).

use graphmind_sdk::{RemoteClient, GraphmindClient};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = RemoteClient::new("http://localhost:8080");

let result = client.query("default",
"MATCH (p:Person)-[:KNOWS]->(f) RETURN p.name, f.name"
).await?;

println!("Columns: {:?}", result.columns);
println!("Found {} records", result.records.len());

Ok(())
}

The GraphmindClient Trait

Both EmbeddedClient and RemoteClient implement the GraphmindClient trait, so you can write generic code:

use graphmind_sdk::{GraphmindClient, GraphmindResult, QueryResult};

async fn count_nodes(client: &dyn GraphmindClient) -> GraphmindResult<u64> {
let result = client.query_readonly("default",
"MATCH (n) RETURN count(n) AS total"
).await?;
// Extract count from first record
let count = result.records[0][0].as_u64().unwrap_or(0);
Ok(count)
}

Trait Methods

MethodDescription
query(graph, cypher)Execute a read/write Cypher query
query_readonly(graph, cypher)Execute a read-only query
explain(graph, cypher)Return the EXPLAIN plan without executing
profile(graph, cypher)Execute with PROFILE instrumentation
schema(graph)Return a schema summary string
status()Server health, version, node/edge counts
ping()Connectivity check (returns "PONG")
list_graphs()List all graph namespaces
delete_graph(graph)Delete all data in a graph

CRUD Operations

CREATE

// Nodes with properties
client.query("default", r#"
CREATE (p:Person {name: "Carol", age: 28, active: true})
"#).await?;

// Edges with properties
client.query("default", r#"
MATCH (a:Person {name: "Alice"}), (c:Person {name: "Carol"})
CREATE (a)-[:WORKS_WITH {project: "GraphDB", since: 2023}]->(c)
"#).await?;

MATCH with WHERE

let result = client.query_readonly("default", r#"
MATCH (p:Person)
WHERE p.age > 25 AND p.active = true
RETURN p.name, p.age
ORDER BY p.age DESC
LIMIT 10
"#).await?;

SET (update properties)

client.query("default", r#"
MATCH (p:Person {name: "Alice"})
SET p.age = 31, p.title = "Engineer"
"#).await?;

DELETE

// Delete specific nodes and their edges
client.query("default", r#"
MATCH (p:Person {name: "Bob"})
DELETE p
"#).await?;

MERGE (upsert)

client.query("default", r#"
MERGE (p:Person {name: "Dave"})
SET p.age = 35
"#).await?;

Aggregations

let result = client.query_readonly("default", r#"
MATCH (p:Person)
RETURN count(p) AS total,
avg(p.age) AS avg_age,
min(p.age) AS youngest,
max(p.age) AS oldest,
collect(p.name) AS names
"#).await?;

GROUP BY

let result = client.query_readonly("default", r#"
MATCH (p:Person)-[:WORKS_AT]->(c:Company)
RETURN c.name, count(p) AS employees, avg(p.age) AS avg_age
ORDER BY employees DESC
"#).await?;

Traversals

Multi-hop Patterns

let result = client.query_readonly("default", r#"
MATCH (a:Person {name: "Alice"})-[:KNOWS]->(b)-[:KNOWS]->(c)
WHERE a <> c
RETURN DISTINCT c.name AS friend_of_friend
"#).await?;

Variable-length Paths

let result = client.query_readonly("default", r#"
MATCH (a:Person {name: "Alice"})-[:KNOWS*1..3]->(b:Person)
RETURN DISTINCT b.name, length(b) AS distance
"#).await?;

Schema Introspection

let schema = client.schema("default").await?;
println!("{}", schema);
// Output:
// Node labels: Person (42), Company (5), City (8)
// Edge types: KNOWS (120), WORKS_AT (42), LIVES_IN (42)

EXPLAIN and PROFILE

// EXPLAIN -- show the plan without executing
let plan = client.explain("default",
"MATCH (p:Person)-[:KNOWS]->(f) WHERE p.age > 25 RETURN f.name"
).await?;
for record in &plan.records {
println!("{:?}", record);
}

// PROFILE -- execute and show operator-level timing/row counts
let profile = client.profile("default",
"MATCH (p:Person)-[:KNOWS]->(f) RETURN f.name"
).await?;
for record in &profile.records {
println!("{:?}", record);
}

Multi-tenancy

Use different graph names to isolate data:

// Each graph name is a separate namespace
client.query("tenant_acme", r#"CREATE (n:User {name: "Acme User"})"#).await?;
client.query("tenant_globex", r#"CREATE (n:User {name: "Globex User"})"#).await?;

// Queries are scoped to their graph
let acme = client.query_readonly("tenant_acme", "MATCH (n) RETURN count(n)").await?;
let globex = client.query_readonly("tenant_globex", "MATCH (n) RETURN count(n)").await?;

// List all graphs
let graphs = client.list_graphs().await?;

// Delete a tenant's graph
client.delete_graph("tenant_acme").await?;

TenantStoreManager (Embedded Only)

For direct access to isolated graph stores in embedded mode, use TenantStoreManager:

use graphmind::TenantStoreManager;

let mgr = TenantStoreManager::new();

// Create isolated graph stores (created on first access)
let production = mgr.get_store("production").await;
let staging = mgr.get_store("staging").await;

// Each has its own data
{
let mut store = production.write().await;
engine.execute_mut("CREATE (n:User {name: 'Alice'})", &mut store, "production")?;
}

// List all tenants
let graphs = mgr.list_graphs().await; // ["default", "production", "staging"]

Extension: Graph Algorithms (Embedded Only)

The AlgorithmClient trait adds algorithm methods to EmbeddedClient:

use graphmind_sdk::{EmbeddedClient, AlgorithmClient, PageRankConfig};

let client = EmbeddedClient::new();
// ... populate graph ...

// PageRank
let config = PageRankConfig { damping_factor: 0.85, iterations: 20, ..Default::default() };
let scores = client.page_rank(config, Some("Person"), Some("KNOWS")).await;
for (node_id, score) in &scores {
println!("Node {} -> {:.4}", node_id, score);
}

// Shortest path (BFS)
let path = client.bfs(source_id, target_id, None, None).await;
if let Some(p) = path {
println!("Path: {:?}, cost: {}", p.path, p.cost);
}

// Dijkstra (weighted)
let path = client.dijkstra(src, dst, None, Some("ROAD"), Some("distance")).await;

// Weakly connected components
let wcc = client.weakly_connected_components(None, None).await;
println!("{} components found", wcc.components.len());

// Strongly connected components
let scc = client.strongly_connected_components(None, None).await;

Extension: Vector Search (Embedded Only)

The VectorClient trait adds HNSW vector index operations:

use graphmind_sdk::{EmbeddedClient, VectorClient, DistanceMetric};

let client = EmbeddedClient::new();

// Create a vector index
client.create_vector_index("Document", "embedding", 384, DistanceMetric::Cosine).await?;

// Add vectors
client.add_vector("Document", "embedding", node_id, &embedding_vec).await?;

// k-NN search
let results = client.vector_search("Document", "embedding", &query_vec, 10).await?;
for (node_id, distance) in results {
println!("Node {:?} at distance {:.4}", node_id, distance);
}

Error Handling

All SDK methods return GraphmindResult<T>, which wraps GraphmindError:

use graphmind_sdk::{GraphmindError, GraphmindResult};

match client.query("default", "INVALID CYPHER").await {
Ok(result) => println!("Success: {} records", result.records.len()),
Err(GraphmindError::QueryError(msg)) => eprintln!("Bad query: {}", msg),
Err(GraphmindError::ConnectionError(msg)) => eprintln!("Network: {}", msg),
Err(e) => eprintln!("Other error: {}", e),
}

Thread Safety

EmbeddedClient uses Arc<RwLock<GraphStore>> internally, so it is safe to clone and share across threads:

let client = EmbeddedClient::new();
let c1 = client.clone();
let c2 = client.clone();

let h1 = tokio::spawn(async move {
c1.query_readonly("default", "MATCH (n) RETURN count(n)").await
});
let h2 = tokio::spawn(async move {
c2.query_readonly("default", "MATCH (n) RETURN count(n)").await
});

let (r1, r2) = tokio::join!(h1, h2);

Working with Query Results

The QueryResult struct contains:

FieldTypeDescription
columnsVec<String>Column names from RETURN clause
recordsVec<Vec<serde_json::Value>>Row data as JSON values
nodesVec<SdkNode>Graph nodes touched by the query
edgesVec<SdkEdge>Graph edges touched by the query
let result = client.query_readonly("default",
"MATCH (p:Person) RETURN p.name, p.age"
).await?;

// Iterate records
for row in &result.records {
let name = row[0].as_str().unwrap_or("?");
let age = row[1].as_i64().unwrap_or(0);
println!("{} is {} years old", name, age);
}

// Inspect returned nodes
for node in &result.nodes {
println!("Node {} labels={:?} props={:?}", node.id, node.labels, node.properties);
}

Re-exported Types

The SDK re-exports key types from the core crate so you do not need to depend on graphmind directly:

  • Graph types: GraphStore, Node, Edge, NodeId, EdgeId, PropertyValue, Label, EdgeType
  • Query types: QueryEngine, RecordBatch, CacheStats
  • Algorithm types: PageRankConfig, PathResult, WccResult, SccResult, FlowResult, MSTResult
  • Vector types: VectorIndex, VectorIndexManager, DistanceMetric
  • Persistence types: PersistenceManager, PersistentStorage, Wal
  • NLQ types: NLQPipeline, NLQConfig, LLMProvider
  • Optimization types: PSOSolver, GASolver, DESolver, SASolver, NSGA2Solver, and more