A tracked feature matrix for Fluree’s openCypher
surface, against openCypher 9 (the Cypher 9 language reference the
openCypher TCK exercises). For
syntax and semantics see Cypher (concept); for recipes
see the Cypher cookbook.
Partial — a common subset works; specific forms are deferred (noted).
⟂
Divergent by design — intentionally different because Fluree is multi-modal graph. Rejected-or-adapted, never silently wrong.
⏳
Deferred — not yet implemented; rejected with a clear error.
Guiding invariant: an unsupported construct produces a clear error, never a
silently wrong result. Divergences (⟂) are where openCypher’s LPG assumptions
meet Fluree’s RDF model.
Nodes are IRIs. A node is an RDF subject (an IRI/blank node), not an opaque
LPG node. labels(n) are rdf:type assertions; node identity is the IRI.
Relationships are edge annotations. A relationship is the base triple
(s, p, o); binding -[r:T]-> reifies it into an f:reifies* annotation node
(the LPG edge identity). Fluree does not implement RDF-star triple terms —
see Edge annotations.
id(n) / elementId(n) return the node/relationship IRI string — there
is no integer element id.
No implicit per-statement transaction id semantics; immutability/time-travel
replace it (f:t, history queries).
Projection boundary; WHERE→HAVING when it references aggregates; DISTINCT/ORDER BY/SKIP/LIMIT; collect() carries forward as a list. Before a write clause only the pass-through / rename / computed-alias / filter subset is allowed (no aggregation / DISTINCT / ORDER BY / SKIP / LIMIT); WITH before DELETE deferred.
UNWIND
✅
Inline lists and runtime list expressions; $param lists via API substitution.
RETURN
✅
*, aliases, DISTINCT, ORDER BY/SKIP/LIMIT. SKIP/LIMIT must be literal integers.
Find-or-create with ON CREATE SET. ON MATCH SET deferred (for all MERGE forms — node and relationship).
MERGE (relationship)
◑
Standalone + bound-endpoint forms; ON CREATE SET. Deferred: property-bearing rel MERGE (-[:T {p:v}]->), multi-hop / multi-part MERGE, multiple MERGE clauses, and MERGE combined with another write clause in one statement.
SET / REMOVE
✅
Properties, += map merge, labels.
DELETE / DETACH DELETE
✅
FOREACH
⏳
CALL proc(...) YIELD
⏳
Stored/builtin procedures (distinct from CALL { … }).
LOAD CSV
⏳
Bulk CSV import exists via the CLI, not the LOAD CSV clause.
Rejected — a node must be constrained by a label, property, or relationship (no whole-graph scan).
Directed typed relationship -[:T]->, <-[:T]-
✅
Type alternation -[:A|B]->
✅
Union of concrete predicates.
Undirected -[:T]-
✅
Forward ∪ reverse Union.
Untyped relationship -[r]->
✅
Variable predicate; system facts hidden.
Bounded var-length -[:T*m..n]->
✅
Enumerates trails (one row per path, relationship-uniqueness).
Unbounded var-length -[:T*]->
⟂
Reachability (one row per reachable endpoint), not path enumeration.
Untyped var-length -[*m..n]->
⟂
Wildcard reachability over node→node edges; excludes rdf:type/f:reifies*. A direction is required (-[*]- deferred); an unbounded lower bound above 1 (-[*2..]->) deferred — give an upper bound or name a type.
Bounded var-length binding-[r:T*m..n]-> / p = …
✅
r = rel list, p = path; via per-branch construction.
Unbounded var-length binding
⏳
Needs a path-enumeration operator.
shortestPath / allShortestPaths
✅
Anchored, single typed predicate; All emits one row per minimal path.
Prefer ⟂ over ⏳ when the divergence is an intentional RDF-model choice —
and record why in the Notes column.
A future step is to wire a subset of the openCypher TCK .feature scenarios as
executable tests and generate the supported/deferred columns from their pass/fail
results, replacing the hand-maintained status marks.