GraphQL

Relata exposes a POST /graphql endpoint that translates a subset of GraphQL query syntax to Relata SQL and executes it through the same governed query path as /query (ACL, cell masking, tenant scoping, cluster fan-out). The translator is hand-rolled with no external GraphQL dependency — supply-chain safe by design.

Conformance: GraphQL query subset — field selection, limit, where (single equality), __schema/__type introspection. Mutations, subscriptions, fragments, unions, and interfaces are not supported. For the full query surface, use SQL.

Endpoint

MethodPathBody
POST/graphqlapplication/json{"query": "...", "variables": {...}}

There is no GET form; /graphql accepts POST only.

Authentication

Same as /query. When RELATA_BEARER_TOKEN is set, include it:

curl -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"query": "{ Person { id name } }"}' \
     http://localhost:9090/graphql

Unauthenticated requests receive HTTP 401 (application/problem+json).

Request body

FieldRequiredNotes
queryyesGraphQL query string. Empty → HTTP 400.
variablesnoJSON object of $var bindings, bound server-side (see Variables).

PURPOSE is read from the x-relata-purpose header (defaults to analytics) and prepended to the translated SQL, so every /graphql read is governed and audited exactly like a purpose-tagged SQL query.

Usage

Field selection

curl -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"query": "{ Person { id name email } }"}' \
     http://localhost:9090/graphql

Translates to SELECT id, name, email FROM Person LIMIT 100 (default limit 100).

limit and where

-d '{"query": "{ Person(limit: 5, where: {name: \"Alice\"}) { id name } }"}'

Translates to SELECT id, name FROM Person WHERE name = 'Alice' LIMIT 5. The where argument accepts a single field = value equality predicate. String values are escaped to prevent injection; field names are restricted to [A-Za-z0-9_].

Variables

$var references in limit and where are bound from the variables object (#3260). A variable-definitions block (query Q($n: String) { ... }) is accepted and ignored — values always come from variables.

-d '{
  "query": "query ByName($n: String, $lim: Int) { Person(limit: $lim, where: {name: $n}) { id name } }",
  "variables": {"n": "Alice", "lim": 5}
}'

Translates to SELECT id, name FROM Person WHERE name = 'Alice' LIMIT 5.

The whole where filter can also be a variable:

-d '{
  "query": "{ Person(where: $f) { id name } }",
  "variables": {"f": {"name": "Alice", "age": 30}}
}'

Object variables are conjoined with AND (name = 'Alice' AND age = 30).

Never silent. A $var that is referenced but missing from variables, or whose value is null or wrong-typed (e.g. a string where limit needs an integer), returns an error envelope — the filter is never silently dropped. String variable values are bound as quoted literals, so variable content cannot inject SQL.

Introspection

-d '{"query": "{ __schema { types { name } } }"}'

__schema / __type queries return the ontology's object type names:

{
  "data": {
    "__schema": {
      "types": [
        {"name": "Person"},
        {"name": "Organization"}
      ]
    }
  },
  "errors": []
}

Response format

Every response uses the standard GraphQL envelope:

{
  "data": [ {"id": "...", "name": "Alice"} ],
  "errors": []
}
  • On success, data is an array of row objects (capped by RELATA_MAX_RESULT_ROWS); errors is [].
  • On a translator or policy error, HTTP 400/500 with {"data": null, "errors": [{"message": "..."}]}.
  • Malformed JSON or a missing query field returns an RFC 7807 application/problem+json error.

Supported subset

ConstructStatus
Field selection { Type { f1 f2 } }Supported
query { ... } wrapper or bare { ... }Supported
limit: N argument (default 100)Supported
where: { field: "value" } (single equality)Supported
where: $objVar (whole-filter object variable, AND-joined)Supported
__schema / __type introspection sentinelSupported (type listing)
mutation { ... }400 — "mutations not yet supported"
Subscriptions, fragments, unions, interfacesNot supported (parse error)
Variable binding ($var in limit / where)Supported (#3260) — missing/mistyped variable is a hard error
Full GraphQL spec complianceDeferred — use /query for the full SQL surface

Cluster fan-out

When this node is a cluster coordinator with peers, the translated SQL is routed through the cluster-fan-out-aware path so results span all shards. A standalone node executes locally. The response envelope is identical either way.

SDK usage

The published SDKs expose the door directly — e.g. the Python SDK:

# Field selection.
rows = client.graphql("{ Person(limit: 5) { id name } }")
 
# Variables are bound server-side (#3260); a missing/mistyped variable raises.
rows = client.graphql(
    "query ByName($n: String) { Person(where: {name: $n}) { id name } }",
    variables={"n": "Alice"},
)

Configuration

None required. /graphql is available on every deployment profile (free, server, cluster). Auth is mandatory on server/cluster and optional on free, identical to /query.

See also

  • SQL Reference — the full dialect (use this beyond the subset above)
  • SPARQL — the other query-language door
  • Protocols — the full protocol-door catalogue