edges

Graphs

Native edges and traversals — multi-hop without a side-car.

What it is

Graphs on RedDB.

First-class graph primitives on the same engine that holds your tables and documents. No ETL into a separate Neo4j cluster, no adjacency-list workaround, no synchronised snapshots.

Edges connect any two entities in the system — table rows, documents, vector items, or other edges. Traversals run as SQL extensions: SEARCH CONTEXT 'node-id' DEPTH 2 returns the neighbourhood already serialised for application use.

Pairs naturally with vectors — RAG ASK uses the graph to expand context around semantically retrieved nodes.

Code

Write it. Read it. Same engine.

Insert

INSERT INTO identity EDGE (label, from, to) VALUES ('OWNS', 'alice', 'passport:AB1234567');

Query

SEARCH CONTEXT 'passport:AB1234567' DEPTH 2;

Use cases

Where graphs earn their place.

  • Identity + access

    Users own assets own resources. Multi-hop access checks resolve in one query.

  • Knowledge graphs

    Wikipedia-style entity-and-edge models powering ASK across an organisation.

  • Recommendations

    Customer → bought → product, expanded by similar-customers / similar-products edges in the same hop.

  • Fraud detection

    Account → device → IP rings discovered by traversing the edge graph at write time.

Build it

End-to-end walkthrough.

Four steps from empty database to a real graphs workload — each step shows the exact code and explains what the engine is doing.

  1. Step 1

    1. Declare the graph

    An EDGE collection is a typed adjacency list. from and to are entity references — they don't have to point to the same table. label is the edge type (OWNS, FOLLOWS, BLOCKED_BY, etc).

    CREATE EDGE identity (
      label   TEXT NOT NULL,
      weight  REAL DEFAULT 1.0
    );
    
    -- Edges connect any two entities in the engine — table rows,
    -- document IDs, vector item IDs.
  2. Step 2

    2. Insert edges as the relational data lands

    Edges are written in the same transaction as the rows they connect. There is no eventual consistency between "the row exists" and "the edge points at it" — both land or neither does.

    BEGIN;
      INSERT INTO users (id, name) VALUES (42, 'Alice');
      INSERT INTO assets (id, kind) VALUES ('passport:AB1234567', 'passport');
      INSERT INTO identity EDGE (label, from, to)
      VALUES ('OWNS', 'user:42', 'passport:AB1234567');
    COMMIT;
  3. Step 3

    3. Multi-hop traversal

    Two grammars for two shapes: SEARCH CONTEXT returns the neighbourhood (good for RAG context expansion); PATH FROM ... TO returns one path (good for "is X connected to Y", "show me the trail"). Both use index-aware traversal — depth 4 is not a recursive CTE explosion.

    SEARCH CONTEXT 'passport:AB1234567' DEPTH 2;
    -- returns the document/table rows reachable in 2 hops,
    -- with edges labelled and weighted.
    
    PATH FROM 'user:42' TO 'transaction:txn_999' MAX DEPTH 4;
    -- shortest path in <= 4 hops, returns the full edge sequence.
  4. Step 4

    4. Combine with vector search

    The vector search returns the 5 nearest events; the engine then expands each result by 2 hops in the graph and ships the combined neighbourhood back. This is what powers RAG over your relational data — no Neo4j sync, no Pinecone glue, one round trip.

    -- Find suspicious activity, then expand to its graph context.
    SEARCH SIMILAR TEXT 'login from new country'
      COLLECTION events LIMIT 5
      WITH CONTEXT DEPTH 2;

Engine features

What RedDB adds on top of graphs.

The engine knobs you would otherwise wire up yourself — TTL, encryption, indexes, atomic ops, eventual consistency — applied to this data model.

  • Bidirectional indexing

    Every edge is indexed both from→to and to→from. WHERE from = ? and WHERE to = ? both hit a seek; you do not have to maintain a reverse adjacency table.

  • Edge properties

    Edges are typed rows — declare arbitrary columns (label, weight, created_at, custom attributes). Filter / sort traversals on these in the same query: EDGE LABEL IN ('OWNS') WHERE weight > 0.5.

  • EdgeRef typed pointers

    A column with type EdgeRef constrains values to point at real edges. Useful for "log this audit entry against the edge that authorised it" patterns — the broken-pointer class of bug surfaces at insert.

  • Depth-bounded traversal

    SEARCH CONTEXT … DEPTH N and PATH … MAX DEPTH N are mandatory bounds. The planner enforces them; runaway recursion is impossible. Combined with the edge index, depth 4–5 stays usable on millions of edges.

  • ACID atomic across nodes + edges

    A BEGIN; INSERT row; INSERT edge; COMMIT; either fully lands or fully rolls back. No "the row exists but the edge doesn't yet" eventual consistency window.

  • Vector + graph hybrid (RAG context)

    SEARCH SIMILAR ... WITH CONTEXT DEPTH N runs a vector search and expands each hit by N graph hops in one round trip. This is the canonical RAG retrieval shape, native instead of glued.

  • Algorithm catalog

    Native operators for shortest path (PATH FROM ... TO ... — A* / Dijkstra weighted), centrality (PageRank, Personalized PageRank, HITS, Betweenness), community detection (Louvain, Label Propagation), connected components, topological sort, and cycle detection. No external Spark / GraphX cluster.

  • Edge-level policy enforcement

    Policies can scope by edge label or property. A viewer role denied edges with label = SENSITIVE_RELATIONSHIP will see traversals stop at the boundary — no leakage through the graph.

Showcases

What graphs can do once you bring the rest of the engine in.

Cross-model correlation, algorithm flex, multi-model patterns — things that take a stack of services elsewhere.

A* shortest-path with edge weights

Weighted shortest path between two nodes, bounded depth, in pure SQL.

A logistics graph has routes edges with cost and duration weights. Find the cheapest path from warehouse:NYC to customer:9912, max 5 hops. No external graph DB, no Dijkstra implementation in your service.

sql

PATH FROM 'warehouse:NYC' TO 'customer:9912'
  EDGE LABEL = 'ROUTE'
  WEIGHT BY (e) -> e.cost
  ALGORITHM 'astar'
  MAX DEPTH 5;
-- Returns the ordered edge sequence + total cost.
-- Switch ALGORITHM to 'dijkstra' for unweighted heuristic-free.

PageRank for influence ranking

Compute PageRank over a follower graph — find the most-influential authors.

A follows edge collection tracks who follows whom. The native GRAPH PAGERANK operator returns scores per node — used directly for "trending authors" or as a feature in a ranking pipeline.

sql

GRAPH PAGERANK
  EDGE COLLECTION follows
  DAMPING 0.85
  ITERATIONS 30
  TOP 100;
-- Returns: { node_id, score }, ordered by score DESC.

-- Personalized: bias toward a seed set (recommendations).
GRAPH PERSONALIZED_PAGERANK
  EDGE COLLECTION follows
  SEED ['user:42']
  DAMPING 0.85;

Community detection for fraud rings

Louvain on a device-sharing graph surfaces clusters of accounts behaving as one.

Account → device → IP edges form a graph where fraud rings appear as tightly-connected components. Run Louvain community detection, then flag every account in clusters above a size threshold for manual review.

sql

GRAPH COMMUNITIES
  EDGE COLLECTION shares_device
  ALGORITHM 'louvain'
  WHERE created_at > now() - INTERVAL '30 days';
-- Returns: { node_id, community_id }.

-- Pipe to the alerting table.
INSERT INTO fraud_review (user_id, community, detected_at)
SELECT node_id, community_id, now()
FROM GRAPH_COMMUNITIES_RESULT
WHERE community_size >= 5;

Migrate from

Bring graphs over from your current tool.

From

Neo4j

Export nodes + relationships as CSV via APOC, then import each side into RedDB — table for entities, edge collection for relationships.

# Neo4j side
apoc.export.csv.all('graph.csv', {})

# RedDB side
red import --table entities  entities.csv
red import --edges identity   relationships.csv \
  --from-col=:START_ID --to-col=:END_ID --label-col=:TYPE

From

Postgres adjacency tables

A (parent_id, child_id, kind) join table is already shaped like an edge collection. Lift-and-shift via SELECT INTO.

INSERT INTO identity EDGE (label, from, to)
SELECT kind, parent_id::text, child_id::text
FROM postgres_export.parent_child_relations;

Recipes

Common patterns, real driver code.

sql

Multi-hop access control

A user can read a resource if a chain of OWN/SHARES edges connects them, depth ≤ 4.

PATH FROM 'user:42' TO 'resource:doc-99'
  EDGE LABEL IN ('OWNS', 'SHARES', 'MEMBER_OF')
  MAX DEPTH 4;
-- non-empty result = access granted.

sql

Recommendation context

Pick a similar item via vector search, then expand its graph context for a richer recommendation set.

SEARCH SIMILAR TEXT $query
  COLLECTION products LIMIT 5
  WITH CONTEXT DEPTH 1
  EDGE LABEL IN ('CO_PURCHASED', 'CATEGORY_OF');

sql

Fraud ring detection

Find accounts sharing a device or IP within 2 hops — common on signup-abuse + payment-fraud workflows.

SELECT a.id AS suspect, b.id AS connected_via,
       p.path
FROM users a
JOIN PATH FROM a.id TO ANY MAX DEPTH 2
     EDGE LABEL IN ('USED_DEVICE', 'USED_IP') p
JOIN users b ON p.terminal = b.id
WHERE a.signup_at > now() - INTERVAL '24 hours'
  AND b.id <> a.id;

Limits

What it does. What it costs.

Operational caps and durability semantics, no hand-waving.

Max edges per node
Unlimited (indexed both directions)
Practical traversal depth
4–5 on millions of edges; use index-aware planning
Edge property columns
Same as a regular table — typed columns + indexes
Bidirectional traversal
Native — `from→to` and `to→from` indexed
Cycle detection
Built-in (depth bound prevents infinite walks)

When NOT to use

Don't pick graphs for these.

Honest constraints — when another model fits better.

  • Don't model strict trees as graphs

    If every node has exactly one parent, a parent_id column on a row is enough. Graphs earn their keep when N parents / multi-label edges show up.

  • Avoid graphs for high-write append-only event streams

    Time-series with tags is a better fit. Edges become hot to update; the index thrash dominates.

  • Don't try to walk a 6+ hop traversal at request time

    Use a materialised reachability table for cases that need depth 6 — pre-compute the closure offline. Real-time deep traversal is a query-pattern smell.

vs

How RedDB graphs compare.

Neo4j

Cypher-flavour graph queries on an engine that also speaks SQL, JSON, vectors, and time-series — one source of truth.

Postgres recursive CTE

Native traversal grammar with index-aware planning instead of recursive joins that explode at depth 3.

FAQ

Questions teams ask before they pick graphs.

Do edges have properties?

Yes. Each edge is a row in the edge collection, so you can declare arbitrary columns (weight, created_at, label, custom attributes) and query / filter on them.

How deep can a traversal go without falling over?

Depth is bounded per query (DEPTH N or MAX DEPTH N). The planner enforces the bound and uses the edge index to keep each hop cheap. Depth 4–5 on millions of edges is the typical usable envelope.

Is this Cypher?

Cypher-flavoured but not Cypher. The grammar reads like SQL extensions (SEARCH CONTEXT, PATH FROM ... TO) so existing SQL tooling parses it and your team does not have to learn a second language.

Can I query the graph + the relational facts in one statement?

Yes — SEARCH CONTEXT returns rows, so you can join its output to a regular table in the same SELECT. This is the headline feature: one query for "users who own this asset, plus their recent activity".

When should I NOT use the graph model?

When everything is a tree of strict ownership (a parent column on a row is enough), or when the relationships fit happily in a join table. Graphs earn their keep when traversal depth varies and edge types are more than two.

Try graphs on RedDB.

Free nano database, no credit card. AGPL self-host or managed Cloud — same engine, same file format.