Cypher: describe a shape

Cypher lets you draw the shape you want as ASCII-art and the engine compiles it into a traversal — picking a bound anchor and expanding along the pattern with index-free adjacency — so unlike SQL there is no join planner for expansion, because the pattern already IS the pointer walk.

Previously

Raw pointer-walks don't scale to humans. Cypher fixes that: you draw the shape — Alice's friends' friends — and the engine turns the pattern straight into a traversal, no join planner for the expand. But that pattern has two ends, and the engine had to CHOOSE which one to start from. Pick wrong and even index-free adjacency can't save you.

Scene 07

Cypher: describe a shape

  1. Watch
  2. Try it
  3. Predict
  4. Capture
CYPHERAliceBobCarolDaveErinFrankGraceHeidiIvanJudyThe RockThe MatrixInceptionGotham(a:User {name:'Alice'})-[:FRIEND]->(b)-[:FRIEND]->(c)SPINElocal 1 · global 14Cypher summons the small lit …
the bound anchor (a value: name='Alice') → ringed
What to watch for

Nobody hand-writes pointer-walks. In Cypher you DRAW the shape you want, in ASCII-art: (a:User {name:'Alice'})-[:FRIEND]->(b)-[:FRIEND]->(c) — read it left to right: a User named Alice, an arrow to a friend b, another arrow to b's friend c. Watch what the engine does with it. First it binds the anchor — the node with a concrete value, Alice — and rings her. Then it walks the pattern: each -[:FRIEND]-> is one pointer follow from the node it's standing on, and the matched subgraph lights up hop by hop. The shape you typed and the walk that runs are the SAME thing.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Planner.compileExpand
bind the anchor once, then one pointer follow per pattern arrow
def run(pattern):
# the ONLY index lookup: find the bound anchor by value
start = index_seek(pattern.anchor) # Alice, by name
frontier = {start}
for arrow in pattern.arrows: # each -[:FRIEND]->
next = set()
for node in frontier:
# EXPAND: follow relationship pointers, no join
next |= follow(node, arrow.relType)
frontier = next
return frontier # the matched subgraph
Planner.bindAnchor
the pattern's one bound value (name:'Alice') becomes the start — found once, with an index
def bind_anchor(pattern):
# the bound end has a concrete value; the other ends are free
bound = [p for p in pattern.nodes if p.has_value]
anchor = most_selective(bound) # here: name='Alice'
# the ONE index lookup the whole expand needs
start = index_seek(anchor) # O(log N), once
return start # ringed in the diagram
Store.followArrow
one pattern arrow = walk the node's relationship chain — pointer follows, no index
def follow(node, rel_type): # one -[:FRIEND]-> arrow
out = set()
rel = node.first_rel # pointer on the node record
while rel is not None: # the doubly-linked chain
if rel.type == rel_type:
out.add(rel.other_end(node)) # O(1) per edge
rel = rel.next_rel # next pointer, not a seek
return out # the hop's reached nodes

Where this sits in Build a graph database (Neo4j / Dgraph-style)

Scene 07 of 16, in the The language act — Cypher is a shape; the planner picks an anchor.. A Cypher MATCH (a:User {name:'Alice'})-[:FRIEND]->(b)-[:FRIEND]->(c) is a declarative description of a pattern, and for expansion it maps directly onto pointer-chasing — no join planner needed.

Up next. Cypher compiled our pattern into a walk — but it quietly made a decision we didn't: which node to anchor on. Start from Alice (a few friends) versus start from the movie 1M people rated, and the same pattern costs a thousand times more. The planner's real job isn't joins — it's anchor selection and guessing how many nodes each end pulls in.

All 16 scenes in Build a graph database (Neo4j / Dgraph-style) · Every curriculum

Built with Arqly
Every scene in Build a graph database (Neo4j / Dgraph-style) builds on the one before it.All 16 Build a graph database (Neo4j / Dgraph-style) scenes