Cypher.
Define the tables and relations needed to match graph patterns with supported Cypher syntax.
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.
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.
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"] 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"] 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.