Ontology & OWL 2
In Relata the ontology is the schema. You do not CREATE TABLE — types are
declared in a git-branched ontology and auto-registered on ingest. The class
hierarchy is a first-class citizen the planner reasons over with the OWL 2 QL
profile.
Schema-as-code, git-branched
The ontology (relata-ontology) is versioned like source: it has a HEAD
pointer and branches, so a schema change is a commit you can review, diff, and
roll back — not an irreversible ALTER TABLE.
Two ways to declare a type:
| Path | Use it for |
|---|---|
POST /ontology/migrate | SHACL-shaped schema migrations (the durable, reviewable path) |
POST /types | Runtime type registration (quick, ad-hoc) |
There is no CREATE TYPE / CREATE TABLE / CREATE INDEX in the SQL
surface — those parse-then-501 on the pgwire door and point you here. This is
deliberate: schema lives in the ontology so it carries provenance and branches.
# Declare a Person type and an EMPLOYED_BY link via the ontology API
curl -X POST http://localhost:9090/ontology/migrate \
-H "Authorization: Bearer $RELATA_BEARER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"types": [
{ "name": "Person", "properties": { "name": "text", "email": "email" } },
{ "name": "Organization", "properties": { "name": "text" } }
],
"links": [
{ "name": "EMPLOYED_BY", "subject": "Person", "object": "Organization" }
]
}'Once declared, rows ingest against the type and every read is governed and bi-temporal — see Bi-Temporal and Governance.
OWL 2 QL reasoning
Relata implements the OWL 2 QL profile (ADR-153) for reasoning over the type
hierarchy. QL was chosen over EL for a concrete engineering reason: QL queries
rewrite to SQL (recursive CTEs), which maps directly onto the executor’s
WITH RECURSIVE support and keeps data complexity polynomial (LOGSPACE) — in
line with Relata’s latency commitments.
The primary axiom is subClassOf transitive closure. TBox axioms compile to a
fixed-point datalog program run as a recursive CTE over the internal
relata_subclass_of table:
-- "All ancestor classes of a given type" — the subClassOf transitive closure
WITH RECURSIVE ancestors(parent) AS (
SELECT parent FROM relata_subclass_of WHERE child = 'Employee'
UNION
SELECT s.parent
FROM relata_subclass_of s
JOIN ancestors a ON s.child = a.parent
)
SELECT DISTINCT parent FROM ancestors;A query for Person therefore also matches its subclasses (Employee,
Contractor, …) without you enumerating them — the hierarchy does the work.
Consistency checks (e.g. detecting a cyclic subClassOf) run the same way.
Honest limits
Relata favours honesty over marketing (see the trade-offs on every concept page). For the ontology:
- OWL 2 QL, not EL. EL adds role chains and concrete domains but needs a
dedicated reasoner; no production Rust OWL 2 EL reasoner exists, so it is out
of scope. QL covers the
subClassOftransitive closure that is the primary type-hierarchy use case (ADR-153). - Reasoner verbs are wired; inferred-edge auto-materialisation is not. Reasoning is available on the read path; automatically persisting inferred edges at insert time needs a provenance-source design decision and is tracked (#1059).
- Schema DDL is API-only.
CREATE TYPE/ALTER TABLEare not SQL operations — usePOST /ontology/migrateorPOST /types.
See also
- SQL Reference — DDL & the ontology
- SPARQL — query the
KnowledgeTriplegraph - Identity — canonical types and resolution