surformSurrealDB schemas, declared

Functions

Functions are stored SurrealQL procedures — callable from queries, other functions, events, and permission expressions. The keyword is fn; parameters keep their $; the body is real SurrealQL.

The shape

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

/// Whether the current user may edit this post.
fn can_edit($p: record<post>) -> bool {
  return $auth.id = $p.author or $auth.role = 'admin';
} {
  permissions: $auth.admin = true;
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE FUNCTION OVERWRITE fn::can_edit($p: record<post>) -> bool {
    RETURN $auth.id = $p.author OR $auth.role = 'admin'
}
    PERMISSIONS WHERE $auth.admin = true
    COMMENT 'Whether the current user may edit this post.';

Reading it: the signature fn name($param: type) -> return_type, then a brace block with the body, then — optionally — a second brace block with ordinary clauses (permissions:, GraphQL hints). It is the one statement with two blocks.

The body's last statement, without a trailing ;, is the return value:

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

fn tax_total($net: number, $rate: option<number>) {
  let $r = $rate ?? 0.15;
  let $tax = math::floor($net * $r);
  $net + $tax
};

fn ping() -> string {
  'pong'
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE FUNCTION OVERWRITE fn::tax_total($net: number, $rate: option<number>) {
    LET $r = $rate ?? 0.15f;
    LET $tax = math::floor($net * $r);
    $net + $tax;
};
DEFINE FUNCTION OVERWRITE fn::ping() -> string { 'pong' };

Zero-parameter functions are legal (fn ping() { … };), and the body is re-emitted in the engine's canonical form — the compiler hands your SurrealQL to the official parser and prints its result, so a stored body and your file agree after a round-trip.

Permissions

Functions default to permissions: full — anyone who can run queries can call them. Lock them down per function:

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

table log {
  schemafull;
  field at: datetime;
};

fn purge_logs($before: datetime) {
  delete log where at < $before;
} {
  permissions: none;
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE log TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE `at` ON log TYPE datetime;
DEFINE FUNCTION OVERWRITE fn::purge_logs($before: datetime) { DELETE log WHERE at < $before }
    PERMISSIONS NONE;

permissions: takes none, full, or an expression (which the compiler emits as PERMISSIONS WHERE …).

Optional parameters

A trailing option<T> parameter may be omitted at the call site — fn::last_opt(5) and fn::last_opt(5, 2) both work:

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

fn last_opt($req: number, $opt: option<number>) {
  $opt ?? $req
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE FUNCTION OVERWRITE fn::last_opt($req: number, $opt: option<number>) { $opt ?? $req };

Names with segments, GraphQL hints

Names may carry :: segments — fn util::slugify(…) defines fn::util::slugify. GraphQL hints ride the second block:

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

fn util::slugify($s: string) -> string {
  string::lowercase(string::replace($s, ' ', '-'))
} {
  graphql {
    alias: 'slugify';
  };
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE FUNCTION OVERWRITE fn::util::slugify($s: string) -> string {
    string::lowercase(string::replace($s, ' ', '-'))
}
    GRAPHQL_ALIAS 'slugify';

Where next