Cypher Reference¶
GraphForge implements the full openCypher language. This guide covers every supported clause, pattern, expression, and function.
Compliance: 100% of the openCypher TCK (3,885 scenarios) as of v0.3.9.
Table of Contents¶
- Reading Data
- Writing Data
- Updating Data
- Query Chaining
- Patterns
- Expressions and Operators
- Functions
- Temporal Types
- Parameters
Reading Data¶
MATCH¶
Find nodes and relationships matching a pattern.
-- All nodes
MATCH (n) RETURN n
-- Nodes with label
MATCH (p:Person) RETURN p
-- Nodes with multiple labels
MATCH (e:Person:Employee) RETURN e
-- Nodes with property filter
MATCH (p:Person {name: 'Alice'}) RETURN p
-- Directed relationship
MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a, b
-- Undirected relationship
MATCH (a:Person)-[:KNOWS]-(b:Person) RETURN a, b
-- Multiple relationship types
MATCH (a)-[r:KNOWS|LIKES]->(b) RETURN a, r, b
-- Variable-length paths
MATCH (a:Person)-[:KNOWS*1..3]->(b:Person) RETURN a, b
-- Unbounded variable-length
MATCH (a)-[*]->(b) RETURN a, b
-- Named path
MATCH p = (a:Person)-[:KNOWS*]->(b:Person) RETURN p
-- Multi-hop
MATCH (a:Person)-[:KNOWS]->(b:Person)-[:WORKS_AT]->(c:Company)
RETURN a.name, b.name, c.name
OPTIONAL MATCH¶
Left-outer-join semantics: returns NULL for missing matches.
MATCH (p:Person)
OPTIONAL MATCH (p)-[:KNOWS]->(friend:Person)
RETURN p.name AS person, friend.name AS friend -- friend.name is NULL if no match
-- Multiple optional matches
MATCH (p:Person)
OPTIONAL MATCH (p)-[:WORKS_AT]->(company:Company)
OPTIONAL MATCH (p)-[:LIVES_IN]->(city:City)
RETURN p.name, company.name, city.name
WHERE¶
Filter by any boolean expression.
-- Comparison
MATCH (p:Person) WHERE p.age > 25 RETURN p
-- NULL check
MATCH (p:Person) WHERE p.email IS NULL RETURN p
MATCH (p:Person) WHERE p.email IS NOT NULL RETURN p
-- Boolean operators
MATCH (p:Person) WHERE p.age > 25 AND p.city = 'NYC' RETURN p
MATCH (p:Person) WHERE p.age < 20 OR p.age > 60 RETURN p
MATCH (p:Person) WHERE NOT p.active RETURN p
-- Label predicate
MATCH (n) WHERE n:Person RETURN n
-- String predicates
MATCH (p:Person) WHERE p.name STARTS WITH 'Al' RETURN p
MATCH (p:Person) WHERE p.name ENDS WITH 'ice' RETURN p
MATCH (p:Person) WHERE p.name CONTAINS 'lic' RETURN p
-- Regex
MATCH (p:Person) WHERE p.name =~ 'A.*' RETURN p
-- List membership
MATCH (p:Person) WHERE p.city IN ['NYC', 'Boston', 'London'] RETURN p
-- Pattern predicate (not returning path)
MATCH (p:Person) WHERE (p)-[:KNOWS]->(:Person {name: 'Alice'}) RETURN p
MATCH (p:Person) WHERE NOT (p)-[:KNOWS]->() RETURN p
-- EXISTS subquery
MATCH (p:Person)
WHERE EXISTS { MATCH (p)-[:KNOWS]->(:Person) }
RETURN p.name
-- Property existence (short form)
MATCH (p:Person) WHERE exists(p.email) RETURN p
RETURN¶
Project variables, properties, expressions, and aggregations.
-- Return variable
MATCH (p:Person) RETURN p
-- Return property
MATCH (p:Person) RETURN p.name, p.age
-- Alias
MATCH (p:Person) RETURN p.name AS person, p.age AS age
-- DISTINCT
MATCH (p:Person) RETURN DISTINCT p.city
-- Aggregation (implicit GROUP BY on non-aggregated columns)
MATCH (p:Person) RETURN p.city AS city, count(*) AS total
-- All properties (map)
MATCH (p:Person) RETURN p {.*}
-- Arithmetic in return
MATCH (p:Person) RETURN p.name, p.salary * 1.1 AS after_raise
-- CASE expression
MATCH (p:Person)
RETURN p.name,
CASE
WHEN p.age < 18 THEN 'Minor'
WHEN p.age < 65 THEN 'Adult'
ELSE 'Senior'
END AS category
-- Return * (all variables in scope)
MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN *
ORDER BY, LIMIT, SKIP¶
-- Sort ascending (default)
MATCH (p:Person) RETURN p.name ORDER BY p.age
-- Sort descending
MATCH (p:Person) RETURN p.name ORDER BY p.age DESC
-- Multiple sort keys
MATCH (p:Person) RETURN p.name ORDER BY p.city ASC, p.age DESC
-- Limit results
MATCH (p:Person) RETURN p.name ORDER BY p.age LIMIT 10
-- Pagination
MATCH (p:Person) RETURN p.name ORDER BY p.name SKIP 20 LIMIT 10
Writing Data¶
CREATE¶
Create nodes and relationships.
-- Single node
CREATE (p:Person {name: 'Alice', age: 30})
-- Multiple labels
CREATE (e:Person:Employee {name: 'Charlie'})
-- Multiple nodes in one clause
CREATE (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
-- Relationship between existing nodes
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
CREATE (a)-[:KNOWS {since: 2020}]->(b)
-- Create inline
CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})
-- CREATE ... RETURN
CREATE (p:Person {name: 'Alice'})
RETURN p.name AS name, id(p) AS node_id
MERGE¶
Find or create — safe for idempotent upserts.
-- Merge node (create if not exists, match if exists)
MERGE (p:Person {email: 'alice@example.com'})
-- ON CREATE and ON MATCH callbacks
MERGE (p:Person {email: 'alice@example.com'})
ON CREATE SET p.created = 2024, p.name = 'Alice'
ON MATCH SET p.last_seen = 2024
-- Merge relationship (both endpoints must already exist)
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
MERGE (a)-[:KNOWS]->(b)
-- Merge with RETURN
MERGE (p:Person {email: 'alice@example.com'})
RETURN p
UNWIND¶
Expand a list into rows.
UNWIND [1, 2, 3] AS x RETURN x * 2 AS doubled
-- Common pattern: batch-create from list
UNWIND ['Alice', 'Bob', 'Charlie'] AS name
CREATE (:Person {name: name})
-- Expand collected list
MATCH (p:Person)
WITH collect(p.name) AS names
UNWIND names AS name
RETURN name
Updating Data¶
SET¶
Add or update properties and labels.
-- Set property
MATCH (p:Person {name: 'Alice'}) SET p.age = 31
-- Multiple properties
MATCH (p:Person {name: 'Alice'})
SET p.age = 31, p.city = 'Boston', p.active = true
-- Set from map
MATCH (p:Person {name: 'Alice'})
SET p += {age: 31, city: 'Boston'}
-- Overwrite all properties
MATCH (p:Person {name: 'Alice'})
SET p = {name: 'Alice', age: 31}
-- Add label
MATCH (p:Person {name: 'Alice'}) SET p:Manager
-- SET ... RETURN
MATCH (p:Person {name: 'Alice'})
SET p.age = 31
RETURN p.name AS name, p.age AS new_age
REMOVE¶
Remove properties and labels.
-- Remove property
MATCH (p:Person {name: 'Alice'}) REMOVE p.temp
-- Remove multiple
MATCH (p:Person {name: 'Alice'}) REMOVE p.temp, p.draft
-- Remove label
MATCH (p:Person {name: 'Alice'}) REMOVE p:Manager
DELETE / DETACH DELETE¶
-- Delete a relationship
MATCH (a)-[r:KNOWS]->(b) WHERE a.name = 'Alice' DELETE r
-- Delete a node (no relationships may exist)
MATCH (p:Person {name: 'Alice'}) DELETE p
-- DETACH DELETE: remove node and all its relationships
MATCH (p:Person {name: 'Alice'}) DETACH DELETE p
-- Delete everything
MATCH (n) DETACH DELETE n
Query Chaining¶
WITH¶
Pass results between query stages, filter aggregations, limit scope.
-- Chain match with filter
MATCH (p:Person)-[:KNOWS]->(friend)
WITH p, count(friend) AS friends
WHERE friends > 5
RETURN p.name AS person, friends
-- Rename and continue
MATCH (p:Person)
WITH p.name AS name, p.age AS age
WHERE age > 25
RETURN name, age
-- Aggregate then match
MATCH (p:Person)
WITH p.city AS city, count(*) AS pop
WHERE pop > 100
MATCH (p2:Person {city: city})
RETURN p2.name, city, pop
ORDER BY pop DESC
-- WITH DISTINCT
MATCH (p:Person)-[:KNOWS]->(friend:Person)
WITH DISTINCT friend
RETURN friend.name AS mutual_friend
UNION / UNION ALL¶
-- UNION (removes duplicates)
MATCH (p:Person) RETURN p.name AS name
UNION
MATCH (c:Company) RETURN c.name AS name
-- UNION ALL (keeps duplicates)
MATCH (p:Person) RETURN p.name AS name
UNION ALL
MATCH (p2:Person) RETURN p2.name AS name
Patterns¶
Node Patterns¶
| Pattern | Meaning |
|---|---|
(n) |
Any node |
(n:Person) |
Node with label Person |
(n:Person:Employee) |
Node with both labels |
(n:Person {age: 30}) |
Node with label and property |
(:Person) |
Anonymous node with label |
({name: 'Alice'}) |
Anonymous node with property |
Relationship Patterns¶
| Pattern | Meaning |
|---|---|
-[r]-> |
Any directed relationship |
-[:KNOWS]-> |
Specific type |
-[r:KNOWS {since: 2020}]-> |
Type with property |
-[:KNOWS\|LIKES]-> |
Multiple types (OR) |
-[*]-> |
Variable-length (any) |
-[*2]-> |
Exactly 2 hops |
-[*1..3]-> |
1 to 3 hops |
-[*..5]-> |
Up to 5 hops |
-[*3..]-> |
3 or more hops |
Path Variables¶
-- Bind path to variable
MATCH p = (a:Person)-[:KNOWS*]->(b:Person)
RETURN length(p) AS hops, nodes(p) AS path_nodes
-- Shortest path
MATCH p = shortestPath((a:Person {name: 'Alice'})-[:KNOWS*]->(b:Person {name: 'Bob'}))
RETURN p
Expressions and Operators¶
Arithmetic¶
RETURN 5 + 3 -- 8
RETURN 5 - 3 -- 2
RETURN 5 * 3 -- 15
RETURN 5 / 2 -- 2.5
RETURN 5 % 2 -- 1
RETURN 2 ^ 10 -- 1024.0
Comparison¶
RETURN 5 = 5 -- true
RETURN 5 <> 6 -- true
RETURN 5 < 6 -- true
RETURN 5 <= 5 -- true
RETURN 6 > 5 -- true
RETURN 6 >= 6 -- true
Boolean¶
RETURN true AND false -- false
RETURN true OR false -- true
RETURN NOT true -- false
RETURN true XOR true -- false
-- NULL propagation
RETURN null AND true -- null
RETURN null OR true -- true
RETURN NOT null -- null
String¶
RETURN 'hello' + ' world' -- 'hello world'
RETURN 'Alice' STARTS WITH 'Al' -- true
RETURN 'Alice' ENDS WITH 'ice' -- true
RETURN 'Alice' CONTAINS 'lic' -- true
RETURN 'Alice' =~ 'A.*' -- true (regex)
List Operators¶
RETURN [1, 2, 3] + [4, 5] -- [1, 2, 3, 4, 5]
RETURN 3 IN [1, 2, 3] -- true
RETURN [1, 2, 3][0] -- 1
RETURN [1, 2, 3][1..3] -- [2, 3]
NULL¶
-- NULL propagates through most operations
RETURN null + 5 -- null
RETURN null = null -- null (use IS NULL instead)
-- Safe operators
MATCH (p:Person) WHERE p.age IS NULL RETURN p
MATCH (p:Person) WHERE p.age IS NOT NULL RETURN p
CASE Expression¶
-- Simple form
RETURN CASE p.status
WHEN 'active' THEN 'Active User'
WHEN 'inactive' THEN 'Inactive User'
ELSE 'Unknown'
END
-- Generic form
RETURN CASE
WHEN p.age < 18 THEN 'Minor'
WHEN p.age < 65 THEN 'Adult'
ELSE 'Senior'
END
Functions¶
String Functions¶
| Function | Example | Result |
|---|---|---|
toLower(s) |
toLower('HELLO') |
'hello' |
toUpper(s) |
toUpper('hello') |
'HELLO' |
trim(s) |
trim(' hi ') |
'hi' |
ltrim(s) |
ltrim(' hi') |
'hi' |
rtrim(s) |
rtrim('hi ') |
'hi' |
replace(s, f, r) |
replace('aaa', 'a', 'b') |
'bbb' |
substring(s, start, len) |
substring('hello', 1, 3) |
'ell' |
left(s, n) |
left('hello', 2) |
'he' |
right(s, n) |
right('hello', 2) |
'lo' |
split(s, delim) |
split('a,b,c', ',') |
['a','b','c'] |
reverse(s) |
reverse('hello') |
'olleh' |
size(s) |
size('hello') |
5 |
toString(x) |
toString(42) |
'42' |
Math Functions¶
| Function | Description |
|---|---|
abs(n) |
Absolute value |
ceil(n) |
Round up |
floor(n) |
Round down |
round(n) |
Round to nearest |
sqrt(n) |
Square root |
pow(base, exp) |
Power |
exp(n) |
e^n |
log(n) |
Natural log |
log10(n) |
Base-10 log |
sin(n), cos(n), tan(n) |
Trig (radians) |
asin(n), acos(n), atan(n), atan2(y,x) |
Inverse trig |
pi() |
π (3.14159…) |
e() |
e (2.71828…) |
rand() |
Random 0.0–1.0 |
sign(n) |
-1, 0, or 1 |
List Functions¶
| Function | Example | Result |
|---|---|---|
head(list) |
head([1,2,3]) |
1 |
tail(list) |
tail([1,2,3]) |
[2,3] |
last(list) |
last([1,2,3]) |
3 |
size(list) |
size([1,2,3]) |
3 |
range(start, end) |
range(1,5) |
[1,2,3,4,5] |
range(start, end, step) |
range(0,10,2) |
[0,2,4,6,8,10] |
reverse(list) |
reverse([1,2,3]) |
[3,2,1] |
sort(list) |
sort([3,1,2]) |
[1,2,3] |
keys(map) |
keys({a:1,b:2}) |
['a','b'] |
List Comprehension and Predicates¶
-- Filter a list
RETURN [x IN range(1,10) WHERE x % 2 = 0] -- [2,4,6,8,10]
-- Transform a list
RETURN [x IN range(1,5) | x * x] -- [1,4,9,16,25]
-- Filter + transform
RETURN [x IN range(1,10) WHERE x > 5 | x * 2] -- [12,14,16,18,20]
-- all() — every element satisfies predicate
RETURN all(x IN [2,4,6] WHERE x % 2 = 0) -- true
-- any() — at least one element satisfies predicate
RETURN any(x IN [1,2,3] WHERE x > 2) -- true
-- none() — no element satisfies predicate
RETURN none(x IN [1,2,3] WHERE x > 5) -- true
-- single() — exactly one element satisfies predicate
RETURN single(x IN [1,2,3] WHERE x = 2) -- true
-- reduce() — fold list to single value
RETURN reduce(acc = 0, x IN [1,2,3,4,5] | acc + x) -- 15
Aggregation Functions¶
-- Count rows
MATCH (p:Person) RETURN count(*) AS total
-- Count non-null
MATCH (p:Person) RETURN count(p.age) AS with_age
-- Count distinct
MATCH (p:Person) RETURN count(DISTINCT p.city) AS cities
-- Numeric aggregations
MATCH (p:Person) RETURN sum(p.salary), avg(p.age), min(p.age), max(p.age)
-- Collect into list
MATCH (p:Person) RETURN collect(p.name) AS names
-- Collect distinct
MATCH (p:Person) RETURN collect(DISTINCT p.city) AS cities
-- Standard deviation
MATCH (p:Person) RETURN stDev(p.age), stDevP(p.age)
-- Percentile
MATCH (p:Person) RETURN percentileDisc(p.age, 0.5) AS median
Graph Functions¶
-- Node identity
RETURN id(n)
-- Labels
RETURN labels(n) -- list of labels
RETURN 'Person' IN labels(n) -- true/false
-- Relationship type
RETURN type(r)
-- Properties (as map)
RETURN properties(n)
-- Keys (property names)
RETURN keys(n)
-- Path functions
MATCH p = (a)-[*]->(b)
RETURN nodes(p), relationships(p), length(p)
-- Endpoints
MATCH (a)-[r]->(b)
RETURN startNode(r), endNode(r)
-- Degree
MATCH (n:Person)
RETURN size((n)-[:KNOWS]->()) AS out_degree
Predicate Functions¶
-- exists() — property exists
MATCH (p:Person) WHERE exists(p.email) RETURN p
-- isEmpty()
RETURN isEmpty([]) -- true
RETURN isEmpty('') -- true
RETURN isEmpty(null) -- true
-- Null coalescing
RETURN coalesce(null, null, 'default') -- 'default'
RETURN coalesce(p.nickname, p.name) -- first non-null
Conversion Functions¶
RETURN toInteger('42') -- 42
RETURN toInteger(3.7) -- 3
RETURN toFloat('3.14') -- 3.14
RETURN toString(42) -- '42'
RETURN toBoolean('true') -- true
RETURN toBoolean(0) -- false
Temporal Types¶
GraphForge implements full openCypher temporal precision including nanoseconds and IANA timezone names.
Construction¶
RETURN date('2024-01-15')
RETURN date({year: 2024, month: 1, day: 15})
RETURN date() -- current date
RETURN time('14:30:00.000000789') -- with nanoseconds
RETURN time({hour: 14, minute: 30, second: 0, nanosecond: 789})
RETURN datetime('2024-01-15T14:30:00[Europe/London]') -- IANA timezone
RETURN datetime('2024-01-15T14:30:00+01:00') -- offset
RETURN datetime() -- current datetime
RETURN localDatetime('2024-01-15T14:30:00')
RETURN duration('P1Y2M3DT4H5M6.789S')
RETURN duration({years: 1, months: 2, days: 3})
Accessors¶
RETURN date('2024-01-15').year -- 2024
RETURN date('2024-01-15').month -- 1
RETURN date('2024-01-15').day -- 15
RETURN date('2024-01-15').weekday -- 1 (Monday)
RETURN date('2024-01-15').week -- 3 (ISO week)
RETURN date('2024-01-15').ordinalDay -- 15
RETURN time('14:30:00.000000789').hour -- 14
RETURN time('14:30:00.000000789').nanosecond -- 789
RETURN datetime('2024-01-15T14:30:00[Europe/Stockholm]').timezone
-- 'Europe/Stockholm'
RETURN duration('P1Y2M3DT4H5M6S').years -- 1
RETURN duration('P1Y2M3DT4H5M6S').months -- 2
RETURN duration('PT0.000000789S').nanoseconds -- 789
Arithmetic¶
RETURN date('2024-01-01') + duration('P1M') -- 2024-02-01
RETURN date('2024-01-01') - duration('P1Y') -- 2023-01-01
RETURN duration('P1Y') + duration('P6M') -- P1Y6M
-- Between
RETURN duration.between(date('2020-01-01'), date('2024-01-01'))
RETURN duration.inMonths(date('2020-01-01'), date('2024-01-01'))
RETURN duration.inDays(datetime('2020-01-01T00:00'), datetime('2020-01-15T12:00'))
RETURN duration.inSeconds(time('08:00'), time('16:30'))
Extreme Years¶
GraphForge supports years outside Python's native range (1–9999):
RETURN localdatetime('+999999999-12-31T23:59:59').year -- 999999999
RETURN localdatetime('-000001-01-01T00:00').year -- -1
Truncation¶
RETURN date.truncate('month', date('2024-07-15')) -- 2024-07-01
RETURN datetime.truncate('day', datetime()) -- current day at midnight
RETURN time.truncate('hour', time('14:37:00')) -- 14:00:00
Parameters¶
Bind values as parameters to avoid string interpolation:
# Python
results = db.execute(
"MATCH (p:Person {name: $name}) WHERE p.age > $min_age RETURN p",
{'name': 'Alice', 'min_age': 25}
)
-- In Cypher, $name and $min_age are the parameter syntax
MATCH (p:Person {name: $name})
WHERE p.age > $min_age
RETURN p.name, p.age
Parameters work with all value types: strings, integers, floats, booleans, null, lists, maps.
Common Patterns¶
Find Friends-of-Friends¶
MATCH (me:Person {name: 'Alice'})-[:KNOWS]->(friend)-[:KNOWS]->(foaf:Person)
WHERE NOT (me)-[:KNOWS]->(foaf) AND me <> foaf
RETURN DISTINCT foaf.name AS recommendation
Most Connected Nodes¶
MATCH (p:Person)-[:KNOWS]->(friend)
RETURN p.name AS person, count(friend) AS connections
ORDER BY connections DESC
LIMIT 10
Upsert Pattern¶
MERGE (p:Person {email: 'alice@example.com'})
ON CREATE SET p.name = 'Alice', p.created = 2024
ON MATCH SET p.last_seen = 2024
RETURN p
Batch Create from List¶
UNWIND [
{name: 'Alice', age: 30},
{name: 'Bob', age: 25},
{name: 'Carol', age: 35}
] AS row
CREATE (:Person {name: row.name, age: row.age})
Conditional Return¶
MATCH (p:Person)
RETURN p.name,
CASE WHEN p.age IS NULL THEN 'unknown'
WHEN p.age < 18 THEN 'minor'
ELSE 'adult'
END AS category
EXISTS Subquery¶
-- Nodes that have at least one outgoing relationship
MATCH (p:Person)
WHERE EXISTS { MATCH (p)-[:KNOWS]->() }
RETURN p.name
-- Nodes that have no relationships
MATCH (p:Person)
WHERE NOT EXISTS { MATCH (p)-[]-() }
RETURN p.name AS isolated
See Also¶
- Graph Construction — Python API for building graphs
- Quick Start — five-minute walkthrough
- OpenCypher Compatibility — feature matrix
- TCK Compliance — 3,885/3,885 passing