Skip to main content
Version: 0.3.36

Graph Algorithms

Run graph algorithms from SQL to rank nodes, detect communities and measure how nodes are connected.

Overview

The algorithm functions run inside GRAPH_TABLE. RaisinDB builds an in-memory graph from the stored relations that match the pattern's relation types, runs the algorithm once per query, and returns each node's value as a column. There is nothing to project or materialise first.

Use them when you need to:

  • Rank nodes by importance (pageRank, closeness, betweenness)
  • Detect communities (louvain, cdlp)
  • Measure connectivity (bfs, sssp, wcc)
  • Describe structure (lcc, triangle_count, degree)

Quick start

Create a few people, let them follow each other, and ask for PageRank:

INSERT INTO 'social' (path, node_type, name, properties) VALUES
('/alice', 'social:Person', 'alice', '{"name":"Alice"}'::jsonb),
('/bob', 'social:Person', 'bob', '{"name":"Bob"}'::jsonb),
('/carol', 'social:Person', 'carol', '{"name":"Carol"}'::jsonb),
('/dave', 'social:Person', 'dave', '{"name":"Dave"}'::jsonb);

RELATE FROM path='/bob' IN WORKSPACE 'social' TO path='/alice' IN WORKSPACE 'social' TYPE 'follows';
RELATE FROM path='/carol' IN WORKSPACE 'social' TO path='/alice' IN WORKSPACE 'social' TYPE 'follows';
RELATE FROM path='/dave' IN WORKSPACE 'social' TO path='/alice' IN WORKSPACE 'social' TYPE 'follows';
RELATE FROM path='/dave' IN WORKSPACE 'social' TO path='/bob' IN WORKSPACE 'social' TYPE 'follows' WEIGHT 2.5;

SELECT * FROM GRAPH_TABLE(
MATCH (n:Person)
COLUMNS (n.name AS name, pageRank(n) AS rank)
)
ORDER BY rank DESC;
namerank
alice0.274
bob0.112
dave0.079
carol0.079

ORDER BY and LIMIT belong to the outer SELECT, not inside GRAPH_TABLE.

Available algorithms

AlgorithmFunctionReturnsDescription
PageRankpageRank(n)FloatImportance from incoming links
BFSbfs(n, source_id)IntegerHop count from a source node
SSSPsssp(n, source_id)FloatWeighted shortest-path distance
WCCwcc(n)IntegerWeakly connected component id
CDLPcdlp(n)IntegerCommunity label (label propagation)
LCClcc(n)FloatLocal clustering coefficient
Triangle counttriangle_count(n)IntegerTriangles the node is part of
Louvainlouvain(n)IntegerCommunity id (modularity)
Degreedegree(n)IntegerIncoming plus outgoing edges
In-degreein_degree(n)IntegerIncoming edges
Out-degreeout_degree(n)IntegerOutgoing edges
Closenesscloseness(n)FloatReachability centrality
Betweennessbetweenness(n)FloatBridge score
Component countcomponent_count()IntegerNumber of connected components
Community countcommunity_count()IntegerNumber of detected communities

Function names are case-insensitive, and most have aliases (page_rank, betweenness_centrality, closeness_centrality, component_id, shortest_path_distance); the reference lists them.

Ad-hoc queries

Any number of algorithm functions can appear in one COLUMNS list. They share the graph built for the query, so adding a second function is cheap:

SELECT * FROM GRAPH_TABLE(
MATCH (n:Person)
COLUMNS (
n.id AS user_id,
n.name AS name,
pageRank(n) AS influence,
louvain(n) AS community,
degree(n) AS connections
)
)
ORDER BY influence DESC;

The graph contains the relation types named in the pattern; a bare (n:Person) with no edge pattern uses every relation type. Results can be filtered, joined and aggregated like any SQL result:

-- average PageRank per community
SELECT community, COUNT(*) AS members, AVG(influence) AS avg_rank
FROM GRAPH_TABLE(
MATCH (n:Person)
COLUMNS (pageRank(n) AS influence, louvain(n) AS community)
) AS g
GROUP BY community
ORDER BY avg_rank DESC;

bfs and sssp take the id of the source node as their second argument (a path is not resolved):

SELECT * FROM GRAPH_TABLE(
MATCH (n:Person)
COLUMNS (n.name AS name, bfs(n, 'ce3a9b41-eb0f-4104-95f5-edfa16a32766') AS hops)
);
-- alice 0, everyone else NULL: the follows edges point at alice, not away from her

Background precomputation

Algorithms can also run in the background on a schedule, with results stored per branch. A background job wakes up every 60 seconds, computes every enabled config whose results are missing or stale, and keeps the stored graph projection for the next run.

note

Stored results are consumed by the platform (the console shows them, and relates_cache feeds row-level security). The GRAPH_TABLE functions above compute on the fly and do not read stored results, so a config does not speed up ad-hoc queries.

A config is a node of type raisin:GraphAlgorithmConfig in the raisin:access_control workspace, under /graph-config/. Create it with SQL, in a package, or from the Admin Console.

With SQL

INSERT INTO "raisin:access_control" (path, name, node_type, properties) VALUES (
'/graph-config/social-pagerank',
'social-pagerank',
'raisin:GraphAlgorithmConfig',
'{"algorithm": "pagerank", "enabled": true,
"target": {"mode": "branch", "branches": ["main"]},
"scope": {"workspaces": ["social"], "relation_types": ["follows"]},
"config": {"damping_factor": 0.85, "max_iterations": 100},
"refresh": {"on_relation_change": true, "ttl_seconds": 300}
}'::jsonb
);

-- list configs
SELECT name, properties->>'algorithm' AS algorithm, properties->>'enabled' AS enabled
FROM "raisin:access_control"
WHERE node_type = 'raisin:GraphAlgorithmConfig';

-- disable, delete
UPDATE "raisin:access_control" SET properties = properties || '{"enabled": false}'::jsonb
WHERE path = '/graph-config/social-pagerank';
DELETE FROM "raisin:access_control" WHERE path = '/graph-config/social-pagerank';

In a package

# content/raisin:access_control/graph-config/social-pagerank/.node.yaml
node_type: raisin:GraphAlgorithmConfig
properties:
algorithm: pagerank
enabled: true
target:
mode: branch
branches: [main]
scope:
workspaces: [social]
relation_types: [follows]
config:
damping_factor: 0.85
refresh:
on_relation_change: true
ttl_seconds: 300

Checking status

The management API reports each config's state, when it last ran and when the next tick is due:

curl -s localhost:8090/management/graph-cache/docs-graph/status -H "Authorization: Bearer $TOKEN"
{"success":true,"data":{"configs":[{"id":"social-pagerank","algorithm":"pagerank",
"enabled":true,"status":"pending","last_computed_at":null,"next_scheduled_at":null,
"node_count":null,"error":null,"config":{}}],
"next_tick_at":1788720348015,"tick_interval_seconds":60}}

POST /management/graph-cache/{repo}/{config_id}/recompute runs a config now; POST …/mark-stale marks it for the next tick.

Config reference

FieldTypeRequiredDescription
algorithmstringyespagerank, louvain, connected_components, betweenness_centrality, closeness_centrality, triangle_count, bfs, sssp, cdlp, lcc, relates_cache
enabledbooleanyesWhether the config is active
target.modestringyesbranch, all_branches, revision or branch_pattern
target.branchesstring[]noBranch names for mode: branch
target.revisionsstring[]noRevisions for mode: revision
target.branch_patternstringnoGlob for mode: branch_pattern
scope.node_typesstring[]noLimit to these node types
scope.relation_typesstring[]noLimit to these relation types
scope.workspacesstring[]noLimit to these workspaces
scope.pathsstring[]noLimit to these path globs
config.damping_factornumbernoPageRank damping (default 0.85)
config.max_iterationsnumbernoPageRank default 100; Louvain and CDLP default 10
config.convergence_thresholdnumbernoPageRank convergence (default 1e-6)
config.resolutionnumbernoLouvain resolution (default 1.0)
config.source_nodestringnoSource node id; required for bfs and sssp
refresh.ttl_secondsnumbernoRecompute after this many seconds
refresh.on_branch_changebooleannoRecompute when the branch HEAD moves
refresh.on_relation_changebooleannoRecompute when relations change
refresh.cronstringnoCron schedule, e.g. "0 */6 * * *"

When refresh is omitted every trigger is off: the config is computed once and stays as it is until you mark it stale or recompute it.

Examples

Influence and communities

SELECT * FROM GRAPH_TABLE(
MATCH (n:Person)
COLUMNS (
n.name AS name,
pageRank(n) AS influence,
cdlp(n) AS community,
in_degree(n) AS followers,
out_degree(n) AS following
)
)
ORDER BY influence DESC
LIMIT 20;

Bridge nodes

SELECT * FROM GRAPH_TABLE(
MATCH (n:Person)
COLUMNS (n.name AS name, betweenness(n) AS bridge_score, louvain(n) AS community)
) AS g
WHERE bridge_score > 0
ORDER BY bridge_score DESC
LIMIT 10;

Distance from a node

SELECT * FROM GRAPH_TABLE(
MATCH (n:Topic)
COLUMNS (n.name AS name, bfs(n, '<id of machine-learning>') AS distance)
) AS g
WHERE distance IS NOT NULL
ORDER BY distance;

Connected components

SELECT * FROM GRAPH_TABLE(
MATCH (n:Topic)
COLUMNS (n.name AS name, wcc(n) AS component, component_count() AS total)
)
ORDER BY component, name;

Clustering

SELECT community, COUNT(*) AS articles, AVG(clustering) AS avg_clustering
FROM GRAPH_TABLE(
MATCH (n:Article)
COLUMNS (louvain(n) AS community, lcc(n) AS clustering)
) AS g
GROUP BY community
ORDER BY avg_clustering DESC;

Next steps