surformSurrealDB schemas, declared

Relations

SurrealDB's graph model lives in relation tables: tables whose records are edges, each connecting an in record to an out record. Surform spells relations in the header, in RELATE's own order.

The arrow form

applied to SurrealDB 3.3.0
schema.surdl
sdl
namespace acme;
database shop;

table user->wrote->post {
  enforced;
  at: datetime {
    default: time::now();
  };
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE wrote TYPE RELATION IN `user` OUT `post` ENFORCED SCHEMALESS;
DEFINE FIELD OVERWRITE `at` ON wrote TYPE datetime DEFAULT time::now();

from -> edge -> to. The edge table's own fields (at above) nest inside, exactly like a normal table's. What the header adds:

  • the sides become typed — in must be a user record, out a post record;
  • enforced; adds the existence check: writing an edge whose endpoints don't exist is rejected. Without it, dangling endpoints are allowed.

Sides can be lists (with |, SurrealQL's separator) and either side can be omitted to leave it unconstrained:

applied to SurrealDB 3.3.0
schema.surdl
sdl
namespace acme;
database shop;

table user | org->wrote->post | comment {
  enforced;
};

table follower->watched->;  // IN-only: out is unconstrained
table->tagged->post;        // OUT-only: in is unconstrained
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE wrote TYPE RELATION IN `user` | org OUT `post` | `comment` ENFORCED SCHEMALESS;
DEFINE TABLE OVERWRITE watched TYPE RELATION IN follower SCHEMALESS;
DEFINE TABLE OVERWRITE tagged TYPE RELATION OUT `post` SCHEMALESS;

The unconstrained relation

An edge with no named sides at all — the generic join:

applied to SurrealDB 3.3.0
schema.surdl
sdl
namespace acme;
database shop;

table relation generic_edge;
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE generic_edge TYPE RELATION SCHEMALESS;

table -> generic_edge ->; means the same thing; the relation word is the canonical spelling.

Lightweight edges (SurrealDB 3.3)

A lightweight relation stores no edge records at all — an edge is just the canonical pair [in, out]. RELATE becomes idempotent per pair, nothing beyond the endpoints can be mutated, and the endpoints must be named:

applied to SurrealDB 3.3.0
schema.surdl
sdl
namespace acme;
database shop;

table person->likes->post lightweight;

table person->watched->post lightweight {
  permissions: full;
  inline edges: 8;
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE likes TYPE RELATION IN person OUT `post` ENFORCED LIGHTWEIGHT SCHEMALESS;
DEFINE TABLE OVERWRITE watched TYPE RELATION IN person OUT `post` ENFORCED LIGHTWEIGHT SCHEMALESS
    PERMISSIONS FULL
    INLINE EDGES 8;

lightweight implies enforced — the engine always prints the pair together, and so does Surform. Because a lightweight edge has no records, the record-oriented clauses are rejected: drop;, changefeed, schemafull, and any nested field, index, or event:

real Surform diagnostics
this schema is wrong on purpose
sdl
namespace acme;
database shop;

table person->likes->post lightweight {
  schemafull;
};
what Surform reports
text
SDL-E1044 (spec/table.md)

  x clause does not apply to this table kind: a LIGHTWEIGHT relation cannot be declared SCHEMAFULL: its edges store no records
   ,-[4:27]
 3 | 
 4 | table person->likes->post lightweight {
   :                           ^^^^^|^^^^^
   :                                `-- not this table kind's clause
 5 |   schemafull;
   `----
  help: each `table` kind defines its own clauses — check the header kind and the clause list in the linked spec

A lightweight table can grow into a full relation later (while it holds no edges) by dropping the word.

The in/out fields you never write

Every relation table carries hidden in and out fields — the endpoints. You never declare them; the engine installs them from the header, the reader folds them back, and rewriting them by hand is a lint (SDL-L0010 — a record-typed in/out field would redefine the endpoints behind the table's back).

Relate-ability for normal tables

RELATE requires a relation table — with one exception: a table declared any accepts edges too. If you have a schemaless table that must take RELATE today and become a real relation someday, say table any thing.

Where next

  • Fields — inline; payloads on edge fields, and reference on plain record fields.
  • The linter — the un-enforced relation warning (SDL-L0006).
  • Indexes — indexing edge fields for fast traversals.