Who acted with whom
CypherA two-hop pattern with an aggregate and a collected list.
MATCH (k:Person {name: 'Keanu Reeves'})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(co)
RETURN co.name AS coActor, count(m) AS movies, collect(m.title) AS titles
ORDER BY movies DESC, coActor;
+--------------------+--------+---------------------------------------+
| coActor | movies | titles |
+--------------------+--------+---------------------------------------+
| "Carrie-Anne Moss" | 2 | ["The Matrix", "The Matrix Reloaded"] |
| "Hugo Weaving" | 1 | ["The Matrix"] |
+--------------------+--------+---------------------------------------+
2 rows
Shortest path between two people
CypherA bidirectional breadth-first search, then a list comprehension over the path.
MATCH p = shortestPath((h:Person {name: 'Hugo Weaving'})-[*]-(l:Person {name: 'Lana Wachowski'}))
RETURN [n IN nodes(p) | coalesce(n.name, n.title)] AS path;
+--------------------------------------------------+
| path |
+--------------------------------------------------+
| ["Hugo Weaving", "The Matrix", "Lana Wachowski"] |
+--------------------------------------------------+
1 row
Everyone within four hops
CypherVariable-length patterns in any direction, deduplicated.
MATCH (h:Person {name: 'Hugo Weaving'})-[*1..4]-(other:Person)
WHERE other <> h
RETURN DISTINCT other.name AS reachable ORDER BY reachable;
+--------------------+
| reachable |
+--------------------+
| "Carrie-Anne Moss" |
| "Keanu Reeves" |
| "Lana Wachowski" |
+--------------------+
3 rows
Upsert with a uniqueness constraint
CypherMERGE creates the node once; the constraint keeps names unique; temporal values are first-class.
CREATE CONSTRAINT person_name IF NOT EXISTS FOR (p:Person) REQUIRE p.name IS UNIQUE;
MERGE (p:Person {name: 'Laurence Fishburne'})
ON CREATE SET p.born = 1961, p.created = date('2026-11-13')
ON MATCH SET p.seen = true
RETURN p.name, p.born, p.created;
1 indexes added, 1 constraints added
+----------------------+--------+------------+
| p.name | p.born | p.created |
+----------------------+--------+------------+
| "Laurence Fishburne" | 1961 | 2026-11-13 |
+----------------------+--------+------------+
1 row; 1 nodes created, 3 properties set, 1 labels added
Counting subqueries and durations
CypherCOUNT { } inline, and date arithmetic with duration.between.
MATCH (m:Movie)
RETURN m.title AS title,
COUNT { (m)<-[:ACTED_IN]-() } AS cast,
duration.between(date({year: m.released}), date('2026-01-01')).years AS yearsAgo
ORDER BY m.released;
+-----------------------+------+----------+
| title | cast | yearsAgo |
+-----------------------+------+----------+
| "The Matrix" | 3 | 27 |
| "The Matrix Reloaded" | 2 | 23 |
| "John Wick" | 1 | 12 |
+-----------------------+------+----------+
3 rows
Distance between two places
CypherWGS-84 points and haversine distance in metres.
WITH point({longitude: 14.42, latitude: 50.09}) AS prague,
point({longitude: 16.61, latitude: 49.20}) AS brno
RETURN round(point.distance(prague, brno) / 1000) AS km;
+-------+
| km |
+-------+
| 186.0 |
+-------+
1 row
Breadth-first search from a node
AlgorithmsHop distances from Keanu Reeves, ignoring direction.
MATCH (k:Person {name: 'Keanu Reeves'})
CALL algo.bfs(k, {orientation: 'UNDIRECTED'}) YIELD node, depth
RETURN coalesce(node.name, node.title) AS reached, depth ORDER BY depth, reached;
+-----------------------+-------+
| reached | depth |
+-----------------------+-------+
| "Keanu Reeves" | 0 |
| "John Wick" | 1 |
| "The Matrix" | 1 |
| "The Matrix Reloaded" | 1 |
| "Carrie-Anne Moss" | 2 |
| "Hugo Weaving" | 2 |
| "Lana Wachowski" | 2 |
+-----------------------+-------+
7 rows
Money that goes round in circles
AlgorithmsA small transfer graph: connected components group the accounts, and a three-hop cycle shows money returning to where it started.
UNWIND [
{from: 'alice', to: 'bob', amount: 120},
{from: 'bob', to: 'carol', amount: 80},
{from: 'carol', to: 'alice', amount: 45},
{from: 'dave', to: 'erin', amount: 300}
] AS t
MERGE (a:Account {id: t.from})
MERGE (b:Account {id: t.to})
CREATE (a)-[:SENT {amount: t.amount}]->(b);
CALL algo.wcc({relationshipTypes: 'SENT'}) YIELD node, componentId
RETURN componentId, collect(node.id) AS accounts ORDER BY componentId;
MATCH p = (a:Account)-[:SENT*3]->(a)
RETURN [n IN nodes(p) | n.id] AS cycle,
reduce(total = 0, r IN relationships(p) | total + r.amount) AS moved
LIMIT 1;
5 nodes created, 4 relationships created, 9 properties set, 5 labels added
+-------------+---------------------------+
| componentId | accounts |
+-------------+---------------------------+
| 0 | ["alice", "bob", "carol"] |
| 3 | ["dave", "erin"] |
+-------------+---------------------------+
2 rows
+------------------------------------+-------+
| cycle | moved |
+------------------------------------+-------+
| ["alice", "bob", "carol", "alice"] | 245 |
+------------------------------------+-------+
1 row
The official neo4j package, unchanged. Start the server with --bolt.
from neo4j import GraphDatabase
with GraphDatabase.driver("bolt://127.0.0.1:7687", auth=("neo4j", "s3cret")) as driver:
records, summary, keys = driver.execute_query(
"MATCH (p:Person) WHERE p.born < $year RETURN p.name AS name",
year=1965,
)
print([r["name"] for r in records])
Any language, over HTTP
DriversOne JSON request per query. Real responses from graphbuff-server, shortened.
curl -s http://127.0.0.1:7475/query -H 'Content-Type: application/json' \
-d '{"query": "CREATE (:Person {name: 'Ann'})-[:KNOWS]->(:Person {name: 'Bob'})"}'
curl -s http://127.0.0.1:7475/query -H 'Content-Type: application/json' \
-d '{"query": "MATCH (p:Person) RETURN p.name AS name ORDER BY name"}'
{"columns":[],"rows":[],"stats":{"nodesCreated":2,"relationshipsCreated":1,"propertiesSet":2,"labelsAdded":2,...,"containsUpdates":true}}
{"columns":["name"],"rows":[["Ann"],["Bob"]],"stats":{...,"containsUpdates":false}}
Java, with no dependencies
Driversexamples/java/GraphbuffClient.java is a single-file HTTP client for Java 17+. Copy it into a project and drop main.
var db = new GraphbuffClient("http://127.0.0.1:7475", token);
db.query("CREATE DATABASE javademo IF NOT EXISTS", null);
Result r = db.query("javademo",
"MATCH (p:Person) WHERE p.age > $min RETURN p.name AS name, p.age AS age ORDER BY age",
Map.of("min", 30));
for (int i = 0; i < r.rows().size(); i++) {
System.out.println(r.get(i, "name") + " is " + r.get(i, "age"));
}
Embedded in Rust, with a transaction
DriversNo server at all: the database runs in your process. Readers keep reading while the transaction is open.
use graphbuff::{Database, StorageMode, params};
let db = Database::open(StorageMode::hybrid("data/bank"))?;
let mut tx = db.begin()?;
tx.execute_with_params(
"MATCH (a:Account {id: $from}), (b:Account {id: $to})
SET a.balance = a.balance - $amount, b.balance = b.balance + $amount",
¶ms! { "from" => 1, "to" => 2, "amount" => 10 },
)?;
tx.commit()?;
See what the planner did
OperationsPROFILE runs the query and reports rows and time per operator (times left out here).
PROFILE MATCH (p:Person)-[:ACTED_IN]->(m:Movie {title: 'The Matrix'})
RETURN p.name ORDER BY p.name;
Select [3 rows]
Sort p.name [3 rows]
Project p.name [3 rows]
Expand(All) (m)<-[:ACTED_IN]-(p:Person) (~1.5 rows) [3 rows]
NodeByLabelScan (m:Movie {…}) (~3.0 rows) [1 row]
Start [1 row]
+--------------------+
| p.name |
+--------------------+
| "Carrie-Anne Moss" |
| "Hugo Weaving" |
| "Keanu Reeves" |
+--------------------+
3 rows
Bulk import from CSV
OperationsThe neo4j-admin import header format, written straight into a persistent store.
graphbuff import --data data --database social \
--nodes Person=people_header.csv,people1.csv,people2.csv \
--relationships FOLLOWS=follows.csv \
--index 'Person(id)'
# people_header.csv
id:ID(Person),name,born:int,tags:string[],:LABEL
# follows.csv
:START_ID(Person),:END_ID(Person),since:date
Several databases, one server
OperationsEach database picks its own storage engine.
CREATE DATABASE movies;
CREATE DATABASE scratch IF NOT EXISTS OPTIONS {storage: 'memory'};
CREATE OR REPLACE DATABASE events
OPTIONS {storage: 'hybrid', sync: 'os', checkpointAfterBytes: 16777216};
SHOW DATABASES;