← Back to homepage

Cypher Reference

Ferrosa's native graph query layer. Run Cypher queries against your CQL tables — no separate graph database, no data duplication, no ETL pipelines.

Beta: Ferrosa's Cypher support is in beta. The query language subset and HTTP API are stabilizing but may change between releases.
Ferrosa Extension: Cypher support is Ferrosa's native graph query layer built on top of your existing CQL data model. Mark any CQL table as a vertex or edge type, and query it with Cypher via a standard HTTP/JSON API on port 7474.

On this page

Overview

Ferrosa lets you run graph queries against your existing CQL tables without a separate graph database. The workflow is:

  1. Create your tables with standard CQL (CREATE TABLE, INSERT, etc.)
  2. Annotate tables as vertices or edges using ALTER TABLE ... WITH extensions
  3. Query the graph with Cypher via the HTTP API on port 7474

Ferrosa automatically maintains an adjacency index for efficient multi-hop traversals. Your CQL tables remain the source of truth — the graph layer is a query interface, not a separate data store.

HTTP API

Endpoints

MethodPathDescription
POST/graph/queryExecute a Cypher query
POST/graph/explainShow query execution plan
GET/graph/schemaList vertex and edge labels
GET/graph/healthHealth check (no auth required)

Authentication

All endpoints except /graph/health require HTTP Basic authentication using the same credentials as CQL (default: cassandra:cassandra). Disable with FERROSA_AUTH_DISABLED=true for development.

Request format

Queries are sent as JSON with a query string and an optional keyspace:

// POST /graph/query
{
  "query": "MATCH (n:Person) RETURN n",
  "keyspace": "social"
}

Response format

Responses are JSON with a columns array and a rows array:

{
  "columns": ["n.name", "n.email"],
  "rows": [
    ["Alice", "alice@example.com"],
    ["Bob", "bob@example.com"]
  ]
}

Default port

The graph HTTP API listens on port 7474 by default. Configure with FERROSA_GRAPH_PORT.

Schema Setup

Ferrosa's graph layer works on top of existing CQL tables. You annotate tables as vertex or edge types using the extensions table property:

Vertex tables

-- Mark a table as a graph vertex type
ALTER TABLE social.users
  WITH extensions = {'graph.type': 'vertex', 'graph.label': 'Person'};

The graph.label becomes the node label used in Cypher patterns (e.g., (:Person)). The table's primary key columns become the vertex identifier.

Edge tables

-- Mark a table as a graph edge type
ALTER TABLE social.follows
  WITH extensions = {
    'graph.type': 'edge',
    'graph.label': 'FOLLOWS',
    'graph.source': 'Person',
    'graph.target': 'Person'
  };

Edge tables require graph.source and graph.target to specify which vertex labels the edge connects. Source and target can be different labels (e.g., Person to Company).

Full example

-- Create the keyspace and tables
CREATE KEYSPACE social WITH replication = {
  'class': 'SimpleStrategy',
  'replication_factor': 3
};

CREATE TABLE social.users (
  user_id uuid,
  name text,
  email text,
  age int,
  PRIMARY KEY (user_id)
);

CREATE TABLE social.follows (
  follower_id uuid,
  followed_id uuid,
  since timestamp,
  PRIMARY KEY (follower_id, followed_id)
);

-- Annotate as graph types
ALTER TABLE social.users
  WITH extensions = {'graph.type': 'vertex', 'graph.label': 'Person'};

ALTER TABLE social.follows
  WITH extensions = {
    'graph.type': 'edge',
    'graph.label': 'FOLLOWS',
    'graph.source': 'Person',
    'graph.target': 'Person'
  };

MATCH

MATCH is the primary read statement in Cypher. It describes a pattern to find in the graph and returns matching results.

-- Simple node match
MATCH (n:Person) RETURN n

-- Traversal with filter
MATCH (a:Person)-[:KNOWS]->(b:Person)
WHERE a.age > 30
RETURN a.name, b.name
ORDER BY a.age DESC LIMIT 10

-- Multi-hop traversal
MATCH (a:Person)-[:FOLLOWS]->(b:Person)-[:FOLLOWS]->(c:Person)
WHERE a.name = 'Alice'
RETURN c.name

Patterns are compiled into a query plan that resolves vertex lookups via the CQL tables and edge traversals via the adjacency index.

Node Patterns

Nodes are enclosed in parentheses. A node pattern can include a variable name, a label, and inline property filters:

PatternDescription
(n)Any node, bound to variable n
(n:Person)Node with label Person
(:Person)Node with label, no variable binding
(n:Person {name: 'Alice'})Node with inline property filter

Inline property filters

Properties specified inside {} are equality checks applied during pattern matching:

-- Find a specific person and their followers
MATCH (n:Person {name: 'Alice'})-[:FOLLOWS]->(f:Person)
RETURN f.name, f.email

Relationship Patterns

Relationships (edges) connect two nodes with an arrow indicating direction:

PatternDescription
(a)-[r]->(b)Directed relationship from a to b
(a)<-[r]-(b)Directed relationship from b to a
(a)-[r]-(b)Undirected — matches either direction
(a)-[:FOLLOWS]->(b)Relationship with type FOLLOWS
(a)-[r:FOLLOWS]->(b)Typed relationship bound to variable r

Examples

-- Directed: who does Alice follow?
MATCH (a:Person {name: 'Alice'})-[:FOLLOWS]->(b:Person)
RETURN b.name

-- Reverse direction: who follows Alice?
MATCH (a:Person {name: 'Alice'})<-[:FOLLOWS]-(b:Person)
RETURN b.name

-- Undirected: all connections regardless of direction
MATCH (a:Person)-[:WORKS_AT]-(b:Company)
RETURN a, b

WHERE Clause

WHERE filters the results of a MATCH pattern. It supports comparison operators, boolean logic, and null checks.

Operators

OperatorDescription
=Equals
<>Not equals
<Less than
>Greater than
<=Less than or equal
>=Greater than or equal
ANDLogical AND
ORLogical OR
NOTLogical negation
IS NULLCheck for null value
IS NOT NULLCheck for non-null value

Examples

-- Comparison operators
MATCH (n:Person)
WHERE n.age >= 21 AND n.age < 65
RETURN n.name, n.age

-- Boolean logic
MATCH (n:Person)
WHERE n.city = 'NYC' OR n.city = 'SF'
RETURN n.name

-- NOT and null checks
MATCH (n:Person)
WHERE NOT n.email IS NULL
RETURN n.name, n.email

RETURN Clause

RETURN specifies which variables and properties to include in the result set. It supports aliases, DISTINCT, ORDER BY, and LIMIT.

Syntax

-- Return specific properties
MATCH (n:Person) RETURN n.name, n.email

-- Aliases with AS
MATCH (n:Person) RETURN n.name AS person_name

-- DISTINCT to remove duplicates
MATCH (a:Person)-[:FOLLOWS]->(b:Person)
RETURN DISTINCT b.name

-- ORDER BY and LIMIT
MATCH (n:Person)
RETURN n.name, n.age
ORDER BY n.age DESC
LIMIT 25

-- Return entire node
MATCH (n:Person) RETURN n

CREATE, SET, DELETE

Coming Soon: CREATE, SET, and DELETE statements are fully parsed by the Cypher parser. Execution is coming in Phase 2. For now, use CQL INSERT/UPDATE/DELETE to mutate data — the graph layer will reflect the changes automatically via the adjacency index.

CREATE

-- Create a vertex
CREATE (n:Person {name: 'Alice', age: 30})

-- Create a relationship
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
CREATE (a)-[:FOLLOWS]->(b)

SET

-- Update a property
MATCH (n:Person {name: 'Alice'})
SET n.age = 31

DELETE

-- Delete a node (must have no relationships)
MATCH (n) WHERE n.status = 'inactive'
DELETE n

-- Delete a node and all its relationships
MATCH (n) WHERE n.status = 'inactive'
DETACH DELETE n

SUBSCRIBE

Ferrosa Extension: SUBSCRIBE extends Cypher with real-time streaming capabilities. Subscribe to any MATCH pattern and receive updates as the graph changes.

SUBSCRIBE supports two modes for real-time graph change streaming:

ModeSyntaxBehavior
EVERYEVERY 5sRe-executes the graph query at the given interval and pushes results
DELTADELTAPush-on-write — only sends changes as they occur via the commit log

Examples

-- Poll for changes every 5 seconds
SUBSCRIBE MATCH (n:Person) RETURN n EVERY 5s

-- Push-on-write for relationship changes
SUBSCRIBE MATCH (a)-[:FOLLOWS]->(b) RETURN a, b DELTA

-- Subscribe to a filtered traversal
SUBSCRIBE MATCH (a:Person {name: 'Alice'})-[:FOLLOWS]->(b:Person)
RETURN b.name, b.email
EVERY 10s

Resource Limits

Ferrosa enforces resource limits on graph queries to prevent unbounded traversals from impacting cluster stability:

LimitDefaultDescription
Query timeout30 secondsMaximum wall-clock time for a single query
Max result rows10,000Maximum rows returned in a single response
Max fan-out per hop10,000Maximum edges traversed at each hop in a multi-hop pattern

These defaults are tunable per query or globally via configuration. When a limit is reached, the query returns a partial result set with a truncated: true flag in the response.

Adjacency Index

When you annotate an edge table with graph.type: 'edge', Ferrosa automatically creates and maintains a system adjacency table for efficient graph traversals:

-- Automatically created by Ferrosa:
-- system_graph_<keyspace>.adjacency
--
-- Schema (internal):
--   source_label text
--   source_id    blob
--   direction    text   (OUT or IN)
--   edge_label   text
--   target_label text
--   target_id    blob

How it works

This design means that MATCH traversals resolve edge lookups via efficient partition-key reads on the adjacency table, rather than scanning the edge table.

Drivers & Integration

Ferrosa's graph endpoint is a standard HTTP/JSON API — any language with an HTTP client works. No special driver required.

curl

curl -X POST http://localhost:7474/graph/query \
  -u cassandra:cassandra \
  -H 'Content-Type: application/json' \
  -d '{"query": "MATCH (n:Person) RETURN n", "keyspace": "social"}'

Python

import requests

r = requests.post('http://localhost:7474/graph/query',
    auth=('cassandra', 'cassandra'),
    json={'query': 'MATCH (n:Person) RETURN n', 'keyspace': 'social'})

data = r.json()
for row in data['rows']:
    print(row)

JavaScript (Node.js / fetch)

const resp = await fetch('http://localhost:7474/graph/query', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Basic ' + btoa('cassandra:cassandra')
  },
  body: JSON.stringify({
    query: 'MATCH (n:Person) RETURN n',
    keyspace: 'social'
  })
});

const data = await resp.json();

Query plan inspection

curl -X POST http://localhost:7474/graph/explain \
  -u cassandra:cassandra \
  -H 'Content-Type: application/json' \
  -d '{"query": "MATCH (a:Person)-[:FOLLOWS]->(b:Person) RETURN b.name", "keyspace": "social"}'

Not Yet Supported

The following Cypher features are planned for future releases:

FeatureStatus
Variable-length paths (-[*]->)Planned
Aggregation functions (COUNT, SUM, AVG, MIN, MAX)Planned
UNIONPlanned
WITH (query chaining)Planned
CASE expressionsPlanned
Full property retrieval on RETURN nPlanned — Phase 1 returns vertex IDs
SubqueriesPlanned
MERGE (upsert)Planned
OPTIONAL MATCHPlanned
Path expressionsPlanned
Note: Even without these features, Ferrosa's Cypher support covers the most common graph query patterns: node lookups, directed and undirected traversals, multi-hop paths, property filters, sorting, and pagination. For mutations, use CQL directly — the graph layer reflects changes automatically.