OriginChainDB docs
schema · cypher

Cypher.

Cypher reads the same rows SQL does. What it adds is a pattern grammar: instead of joining tables on keys, you draw the shape you are looking for - (a)-[:rel]->(b) - and the engine walks it.

Reach for it when the question is "what is this row connected to, and what is that connected to". A three-hop question is three arrows in Cypher and three joins in SQL, and the arrows stay readable. For aggregation, string matching, arithmetic or anything with a GROUP BY in it, use SQL - this dialect deliberately does not compete there.

one route

POST /v1/tenants/:t/cypher with a body of { cypher, default_schema?, params? }. Reads and writes both go here. No SDK wraps this route yet, so every tab on this page builds the request by hand - which is three lines of boilerplate and then plain query strings.

Before you start: the graph shape.

A relationship is not a table. It is a column on the row that holds another row's primary key, plus a [[relations]] block naming it. Declare the block and every write to that table starts maintaining the edge for you. Graph walks through the modelling in depth; here is the shape these examples use.

Register shop.customers first - a relation's target table must already exist when the pointing table is registered. It refers to itself through referred_by, which is what makes a variable-length referral chain legal later on.

schemas/customers.toml
namespace   = "shop"
table       = "customers"
primary_key = ["id"]

[[columns]]
name = "id"
ty   = "str"
required = true

[[columns]]
name = "name"
ty   = "str"

[[columns]]
name = "country"
ty   = "str"

[[columns]]
name = "referred_by"
ty   = "str"        # another customer's id - or absent

# A customer points at the customer who referred them. Source and target
# are the SAME table, which is what makes variable-length paths legal.
[[relations]]
name          = "referrer"
from_col      = "referred_by"
target        = { namespace = "shop", table = "customers", pk = "id" }
bidirectional = true

# REQUIRED for MATCH (c:customers {id: '...'}) to resolve.
[[indexes]]
name    = "by_id"
columns = ["id"]
schemas/orders.toml
namespace   = "shop"
table       = "orders"
primary_key = ["id"]

[[columns]]
name = "id"
ty   = "str"
required = true

[[columns]]
name = "customer"
ty   = "str"        # a shop.customers id

[[columns]]
name = "amount_cents"
ty   = "i64"

[[columns]]
name = "status"
ty   = "str"

[[columns]]
name = "notes"
ty   = "str"

[[columns]]
name = "placed_ms"
ty   = "u64"

# The edge: an order points at the customer who placed it.
[[relations]]
name          = "placed_by"
from_col      = "customer"
target        = { namespace = "shop", table = "customers", pk = "id" }
bidirectional = true

[[indexes]]
name    = "by_id"
columns = ["id"]
the by_id index is not optional

MATCH (o:orders {id: "..."}) resolves through an index named by_<primary key column>. Without that [[indexes]] block the point match does not resolve, and since every traversal has to start from a pinned node, nothing on this page will work. Declare it on any table you intend to query with Cypher.

Related.