Skip to Content
We are live but in Staging 🎉
Data EnginesGraphOverview

Graph

Preview — the graph engine is v1 and the surface is deliberately narrow: one node table and one edge table per graph, integer node keys only, and a read-only Cypher subset. The shapes documented here are stable, but the follow-ups called out on each page (multi-table graphs, string keys, weighted paths) are not shipped. See Feature status.

The fourth dimension of a bucket. A graph is declared over tables you already have — no separate graph store to load. Node rows come from an ordinary table, edge rows from an ordinary table with src/dst columns; writes ride the normal SQL write path, transactions included.

Graphs are Tables-pillar objects: there is no separate graph service to enable. CREATE GRAPH / DROP GRAPH / SHOW GRAPHS go through Tables.Execute like any other SQL, and the wire endpoints + credentials come from the same Connect story as every other engine.

Two-plane model

The graph engine is a derived artifact over table truth:

  • Base truth — the node and edge tables. Every insert/update/delete is an ordinary SQL write.
  • Serve planeCREATE GRAPH builds a compact CSR adjacency pinned at a specific table version and serves traversals from it. Table writes after the pin are not in the artifact until it rebuilds (which happens automatically when the edge table drains/compacts) — unless you ask for a 'strong' read, which merges the not-yet-compacted writes into the traversal at query time.
nodes/edges tables ──CREATE GRAPH──► CSR artifact @ version V │ │ │ writes (SQL INSERT/DELETE) ├── eventual read: artifact as-is ▼ │ write-ahead log ────────'strong' read────────┴── artifact ∪ un-drained writes

See Graph DDL for the exact lifecycle and freshness rules.

Quick tour

-- ordinary tables (edge tables need their own PK; the build reads only SRC/DST) CREATE TABLE person (id INT PRIMARY KEY, name VARCHAR); CREATE TABLE knows (edge_id INT PRIMARY KEY, src INT, dst INT); -- declare the graph over them CREATE GRAPH social NODES (person KEY id) EDGES (knows SRC src DST dst); SHOW GRAPHS; -- traverse: native table functions… SELECT * FROM graph_khop('social', 1, 2); -- (node, hop_distance) SELECT * FROM graph_neighbors('social', 2, 'both'); -- (neighbor) SELECT * FROM graph_shortest_path('social', 1, 7); -- (step, node) -- …or the Cypher spelling (same engine, same rows) SELECT * FROM cypher('social', 'MATCH (a)-[:knows*1..2]->(b) WHERE id(a) = 1 RETURN b'); -- whole-graph analytics SELECT * FROM graph_pagerank('social'); -- (node, rank) SELECT * FROM graph_components('social'); -- (node, component) SELECT * FROM graph_bfs('social', 1); -- (node, level) -- graph + SQL in one statement: traverse in a subquery, aggregate outside. -- e.g. total deal value across an account's 5-hop network SELECT SUM(amount) FROM deals WHERE account_id IN (SELECT node FROM graph_khop('accts', 1, 5));

All of the above runs over the Postgres wire, Execute, or dodil data sql -b <bucket> "…".

Three doors to one engine

DoorWhat it speaksNotes
SQLpg.uk-lon-1.dodil.io:5432, Execute RPC, dodil data sqlgraph_* table functions + cypher() + graph DDLThe full surface — DDL, traversals, analytics, freshness control
Boltbolt+s://bolt.uk-lon-1.dodil.io:7687, neo4j drivers, cypher-shellThe Cypher subset — Cypher only, no SQL, no analyticsBolt 5.0–5.4 / 4.4. Use bolt+s:// — TLS on connect, verified against public roots. neo4j+s:// and neo4j:// are both refused as unrouted (a routing-protocol limit, not a TLS one). Autocommit RUN only, no query parameters; the Bolt database is the bucket name, required on every session. See the Bolt notes for the v1 graph-selection gap
GraphQLgql.uk-lon-1.dodil.io/graphql{graph}_khop / {graph}_neighbors / {graph}_shortestPath root fields, plus a nested traversal field on each node-table type named after that graph’s edge table (with a depth argument)Traversals only — no analytics. Composes with relational and vector fields in one query — see Converging engines

The same traversal, spelled three ways:

# -d is the DATABASE = your bucket name. Scheme is bolt+s:// (TLS, public # roots). neo4j+s:// and neo4j:// both fail — they ask for routing, which # this adapter refuses. cypher-shell \ -a bolt+s://bolt.uk-lon-1.dodil.io:7687 \ -u "$DK_KEY_ID" -p "$DK_SECRET" \ -d kb-prod \ "MATCH (a)-[:knows*1..2]->(b) WHERE id(a) = 1 RETURN b"

What the engine is (and isn’t)

  • Directed multigraph over integer keys. Node keys are the node table’s integer KEY column (v1: string keys are rejected loudly). Every edge-table row is an edge — parallel edges are kept, self-loops allowed.
  • Read-only query surface. There is no write Cypher: CREATE/MERGE/ SET/DELETE are rejected with a pointer to SQL — edges are ordinary rows, so you INSERT INTO knows … instead (and get transactions for free).
  • Single-start traversals + whole-graph analytics. k-hop expansion, neighbors, shortest path (unweighted BFS), PageRank, weak connected components, leveled BFS. No weighted paths, no arbitrary-pattern matching.
  • Traversal direction is a query-time choice'out' (default), 'in', 'both' — because the artifact stores both forward and reverse adjacency.
  • Results are key rows, not node objects. Traversals return BIGINT node keys (plus hop/step/level columns) — no Neo4j Node/Relationship structs. Rehydrate properties by JOINing the node table in the same statement (the traversal is served plane-side and spliced in as an inline relation — see Composing traversals with SQL), or let GraphQL’s nested fields do that join for you.

Where to next

  • Graph DDLCREATE/DROP/SHOW GRAPHS exact grammar, build lifecycle, how table edits reflect, residency
  • Cypher & Traversals — the supported subset, anchoring rules, the honest unsupported list, Bolt specifics
  • Analyticsgraph_pagerank, graph_components, graph_bfs: parameters and output shapes
  • Converging engines — KNN + graph + SQL + GraphQL over one bucket
  • Connect & wire adapters — endpoints and credentials