Skip to Content
We are live but in Staging 🎉

Graph Analytics

Preview — three whole-graph algorithms ship on the SQL surface today (graph_pagerank, graph_components, graph_bfs). Signatures and output shapes below are the stable contract; the algorithm set is still growing.

Beyond single-start traversals, the engine ships whole-graph algorithms as SQL table functions. Each returns typed rows and composes into a larger statement exactly like the traversals — see Composing traversals with SQL.

All analytics are eventual-only: they run over the pinned artifact (there is no 'strong' freshness argument — a read-your-writes overlay isn’t meaningful for a full-graph sweep). Rebuilds on table drain keep them fresh; see Graph DDL.

graph_pagerank

SELECT * FROM graph_pagerank('<graph>' [, <damping> [, <max_iters>]]);
argtypedefaultconstraint
dampingfloat0.85finite, 0.0 – 1.0 (out of range → loud error)
max_itersint100non-negative

Output: (node BIGINT, rank DOUBLE) — one row per node, ranks form a probability distribution (Σ ≈ 1.0). Convergence tolerance is fixed at 1e-9 (L1 delta between sweeps); iteration stops at the tolerance or max_iters, whichever first.

Semantics (canonical PageRank, matching Neo4j GDS / NetworkX):

  • Standard power iteration with uniform dangling-mass redistribution — a node with no out-edges donates its rank to all nodes, so mass never leaks.
  • Parallel edges count: a u→v edge stored twice sends v two shares of u’s rank. Self-loops count as ordinary edges.
  • Deterministic: identical input ⇒ bit-identical output.
SELECT * FROM graph_pagerank('social'); -- defaults SELECT * FROM graph_pagerank('social', 0.5, 50); -- tuned

graph_components

SELECT * FROM graph_components('<graph>');

Output: (node BIGINT, component INT) — one row per node, including isolated nodes (each its own component).

  • Components are weak (edges treated as undirected — the graph-DB default; 1→2 with no back-edge is still one component). Strongly-connected components are not offered.
  • Component ids are dense 0..k and deterministic: numbered ascending by each component’s smallest node key — stable across rebuilds and edge reordering.
-- fixture: web {1..7} + disconnected pair 99→98 SELECT * FROM graph_components('social'); -- {1..7} → component 0 (smallest key 1), {98,99} → component 1

graph_bfs

SELECT * FROM graph_bfs('<graph>', <start> [, '<direction>']);

direction'out' (default) · 'in' · 'both'.

Output: (node BIGINT, level INT) — every node reachable from start under the direction, once, at its minimum hop level. The start itself is the first row at level 0.

  • Unreachable nodes are absent — no level-∞ rows.
  • Unknown start key → zero rows (not an error).
  • This is graph_khop run to exhaustion plus the level-0 start row — same minimum-distance semantics.
SELECT * FROM graph_bfs('social', 1); -- node | level (fixture 1→2→3→4→5, 2→6→7) -- 1 | 0 -- 2 | 1 -- 3 | 2 6 | 2 -- 4 | 3 7 | 3 -- 5 | 4 -- 98/99 absent (disconnected) SELECT * FROM graph_bfs('social', 7, 'both'); -- walk back up the chain

Error behavior

Malformed analytics calls are loud invalid_argument errors, never silent fall-throughs — wrong arity (graph_components('g', 3)), out-of-range damping (graph_pagerank('g', 1.5)), missing start (graph_bfs('g')) all reject with a message naming the expected signature.

In the engine but not (yet) on the SQL surface

The engine core also implements per-node degree and a whole-graph degree_distribution histogram, plus capped/filtered expansion variants. These have no graph_* SQL function today — don’t look for graph_degree(…). Degree is easy to get from what is exposed:

-- out-degree of one node = row count of its neighbors SELECT count(*) AS out_degree FROM graph_neighbors('social', 2);

Elsewhere on the wire

SQL is the full analytics surface. The other doors are narrower:

DoorAnalytics?
SQL — Postgres wire, Execute, dodil data sqlall three functions, composable into a larger statement
HTTP — POST /v1/graph/:name/analyticsall three, one per request: the JSON body carries exactly one of pagerank ({iters, damping}), bfs ({startId}), or connectedComponents ({}) — supplying zero or more than one is a 400
gRPC — TablesGraph.RunGraphAnalyticsall three, as a oneof algo with the same three arms. Zero-valued PageRank knobs take the engine defaults above
Bolt / Cyphernone — the Cypher subset has no analytics spelling
GraphQLnone — it exposes the traversal root fields only
-- the portable spelling SELECT * FROM graph_pagerank('social', 0.85, 100);

See also