K3 · GraphPreview

Your agent has the rows. Give it the relationships.

An agent that can only SELECT sees a table at a time. The questions that decide things — who else does this supplier touch, what is five hops from this account, which of these are the same company — are traversals. Declare a graph over the tables you already have and your agent can walk them: no graph store to load, nothing to sync, writes still ordinary SQL with transactions.

Derived, not duplicated

Your tables stay the truth.

CREATE GRAPH builds a compact adjacency artifact pinned at a specific table version and serves traversals from it. It rebuilds itself when the edge table drains or compacts — and when you need the newest writes before that happens, a 'strong' read merges them into the traversal at query time.

  nodes/edges tables ──CREATE GRAPH──►  adjacency artifact @ version V
        │                                      │
        │  writes (SQL INSERT/DELETE)          ├── default read: artifact as-is
        ▼                                      │
  write-ahead log ────────'strong' read────────┴── artifact ∪ un-drained writes
Three doors, one engine

Reach it however you already work.

SQL
pg.uk-lon-1.dodil.io:5432
graph_* table functions, cypher(), and graph DDL
The full surface — DDL, traversals, analytics and freshness control.
Bolt
bolt+s://bolt.uk-lon-1.dodil.io:7687
The Cypher subset — neo4j drivers and cypher-shell
Bolt 5.0–5.4 / 4.4. Cypher only: no SQL, no analytics.
GraphQL
gql.uk-lon-1.dodil.io/graphql
khop / neighbors / shortestPath root fields, plus a nested traversal field per node type
Traversals only. Composes with relational and vector fields in one query.
One traversal, your spelling

Declare it once. Query it anywhere.

-- ordinary tables; the edge table needs its own PK
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);

-- traverse
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)

-- whole-graph analytics
SELECT * FROM graph_pagerank('social');              -- (node, rank)
SELECT * FROM graph_components('social');            -- (node, component)
The surface

Traversals and whole-graph analytics.

graph_khopk-hop expansion from a start node
graph_neighborsdirect neighbours, 'out' / 'in' / 'both'
graph_shortest_pathshortest path, unweighted BFS
graph_pagerankPageRank over the whole graph
graph_componentsweakly connected components
graph_bfsleveled breadth-first search
The part that pays for itself

Traverse in a subquery. Aggregate outside it.

Because the graph lives in the same engine as the rows, a traversal is just another relation. No round trip to a second database, no stitching two result sets together in application code.

-- total deal value across an account's 5-hop corporate family
SELECT SUM(amount) FROM deals
WHERE account_id IN (SELECT node FROM graph_khop('accts', 1, 5));
What Preview means here

The shapes below are stable — the surface is deliberately narrow, and we would rather you knew the edges before you build on them.

  • One node table and one edge table per graph.
  • Integer node keys only — string keys are rejected loudly.
  • Read-only Cypher: CREATE / MERGE / SET / DELETE point you back to SQL.
  • Unweighted shortest path; no weighted paths, no arbitrary-pattern matching.
  • Analytics run over SQL only — not over Bolt or GraphQL.
Full feature status
One bucket

Four dimensions over one copy of the data.

FAQ

Questions, answered.

Your rows already know each other.

One CREATE GRAPH and you can walk them.

Start free
Regions
UKLiveEULiveMiddle EastSoonAfricaSoon
Compliance
SOC 2In progressISO 27001In progressGDPR-readyData residencyEnforced
© 2026 Circle Technologies Pte Ltd. All rights reserved.