OriginChain docs

Schema for Cypher.

schema · cypher

Cypher reads the same rows as SQL. The difference is the pattern grammar: MATCH (a)-[:rel]->(b) walks a graph edge declared in [[relations]]. Without that block, only single-node MATCH works.

Engine surface: POST /v1/tenants/:t/cypher with { "cypher": "...", "default_schema": "..." } body. Translator: pragmatic Cypher subset → oc_query::Plan tree → executor (same plan cache as SQL).

Required schema fields.

Without these, this query surface doesn't function at all.

field effect
namespace + table + primary_key + [[columns]] Everything SQL needs — Cypher reads the same rows.
[[relations]] (only for traversal patterns) Required for any pattern that walks an edge like (a)-[:rel]->(b). Without it, only single-node MATCH (n) WHERE … works.

Optional fields — what each one unlocks.

Add only the fields whose effect you need. Each one buys a specific capability — speed up a predicate, guard a write, or unlock a new query shape.

field type default effect
[[relations]] bidirectional bool true Reverse edge written atomically so MATCH (p)<-[:r]-(o) reads via the same key. Default true.
default_schema (body field) string Body field on the /cypher request — anchors unanchored MATCH (n) WHERE … to a specific schema when the pattern doesn't pin one.
[[relations]].target.{namespace,table,pk} sub-table Target descriptor on each relation. Required, but goes inside the relation — engine resolves target rows by walking the back-edge key into this PK.

What's in the subset.

  • MATCH (n) WHERE n.col = literal — single-node
  • MATCH (n {pk_col: literal}) — node anchored by PK
  • MATCH (a)-[:rel]->(b) — single-hop traversal (forward)
  • MATCH (a)<-[:rel]-(b) — single-hop traversal (reverse, if bidirectional=true)
  • RETURN projections — bare columns, comma-separated, multi-column
  • CREATE (n {props…}) — single-node insert
  • UNWIND list AS var RETURN var

Refused shapes.

The engine returns a typed 400 with a hint instead of running these. Knowing them up front avoids a debugging round-trip.

shape why
Labels in patterns: (o:orders {...}) Schema id comes from default_schema or pk match — labels would be a second mechanism.
Multi-hop / variable-length: -[:r*1..3]-> Use the BFS / path / k-shortest graph endpoints instead.
MERGE Last-writer-wins puts; idempotent CREATE + match works for the same intent.
OPTIONAL MATCH v1 punt — rewrite as two queries client-side.
WITH clauses, CALL subqueries v1 punt — Plan tree composition handles the common cases.
Bound parameters ($name) Inline literals only — prepared statements are v1.
Comma-separated MATCH (multi-pattern) v1 punt — one pattern per query.

Abbreviation legend.

token meaning
PK Primary key — column(s) listed in primary_key = [...]
[[relations]] Schema TOML block declaring a graph edge between two tables
from_col The column on THIS table whose value is the relation's source — NOT 'column'
[relations.target] Sub-table inside [[relations]] giving the target's namespace, table, and pk column
bidirectional When true (default), engine writes a reverse edge atomically so traversal works in both directions
default_schema Body field on /cypher requests — anchors unanchored MATCH to a schema id
v0 / v1 Engine API generations — v0 today, v1 next release

Worked example.

Schema TOML — copy + register via POST /v1/tenants/:t/schemas with Content-Type: text/plain.

namespace   = "shop"
table       = "orders"
primary_key = ["id"]

[[columns]]
name = "id"          
ty = "str"  
required = true
[[columns]]
name = "customer_id" 
ty = "str"  
required = true
[[columns]]
name = "product_id"  
ty = "str"  
required = true
[[columns]]
name = "qty"         
ty = "i64"

# Graph edge orders → products
[[relations]]
name          = "bought_product"
from_col      = "product_id"
bidirectional = true

[relations.target]
namespace = "shop"
table     = "products"
pk        = "id"

# Graph edge orders → customers
[[relations]]
name          = "by_customer"
from_col      = "customer_id"
bidirectional = true

[relations.target]
namespace = "shop"
table     = "customers"
pk        = "id"

Queries it enables.

# MATCH + WHERE
curl -X POST $BASE/v1/tenants/$T/cypher -H "Authorization: Bearer $BEARER" \
  -d '{
    "cypher": "MATCH (o) WHERE o.customer_id = \"c001\" RETURN o.id, o.product_id, o.qty",
    "default_schema": "shop.orders"
  }'

# Single-hop traversal (forward)
curl -X POST $BASE/v1/tenants/$T/cypher -H "Authorization: Bearer $BEARER" \
  -d '{
    "cypher": "MATCH (o {id: \"o001\"})-[:bought_product]->(p) RETURN p.name, p.price_cents",
    "default_schema": "shop.orders"
  }'

# Reverse traversal (works because bidirectional=true)
curl -X POST $BASE/v1/tenants/$T/cypher -H "Authorization: Bearer $BEARER" \
  -d '{
    "cypher": "MATCH (o {id: \"o001\"})-[:by_customer]->(c) RETURN c.name, c.country",
    "default_schema": "shop.orders"
  }'

# CREATE (single-node insert; NO label syntax in v0)
curl -X POST $BASE/v1/tenants/$T/cypher -H "Authorization: Bearer $BEARER" \
  -d '{
    "cypher": "CREATE (o {id: \"o-new-1\", customer_id: \"c001\", product_id: \"p001\", qty: 1, total_cents: 14999, ts: 1718200000})",
    "default_schema": "shop.orders"
  }'

# UNWIND
curl -X POST $BASE/v1/tenants/$T/cypher -H "Authorization: Bearer $BEARER" \
  -d '{"cypher": "UNWIND [1,2,3,4,5] AS n RETURN n"}'