Skip to Content
ConceptsOntology & OWL 2

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:

PathUse it for
POST /ontology/migrateSHACL-shaped schema migrations (the durable, reviewable path)
POST /typesRuntime 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 subClassOf transitive 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 TABLE are not SQL operations — use POST /ontology/migrate or POST /types.

See also

Last updated on