DEFINE FIELD
statementThe DEFINE FIELD
statement allows you to instantiate a named field on a table, enabling you to set the field’s data type, set a default value, apply assertions to protect data consistency, and set permissions specifying what operations can be performed on the field.
DEFINE FIELD
statement.DEFINE FIELD
statement.
SurrealQL SyntaxDEFINE FIELD [ OVERWRITE | IF NOT EXISTS ] @name ON [ TABLE ] @table [ [ FLEXIBLE ] TYPE @type ] [ DEFAULT @expression ] [ READONLY ] [ VALUE @expression ] [ ASSERT @expression ] [ PERMISSIONS [ NONE | FULL | FOR select @expression | FOR create @expression | FOR update @expression ] ] [ COMMENT @string ]
The following expression shows the simplest way to use the DEFINE FIELD
statement.
-- Declare the name of a field. DEFINE FIELD email ON TABLE user;
The DEFINE FIELD
statement allows you to set the data type of a field. For a full list of supported data types, see Data types.
-- Set a field to have the string data type DEFINE FIELD email ON TABLE user TYPE string; -- Set a field to have the datetime data type DEFINE FIELD created ON TABLE user TYPE datetime; -- Set a field to have the bool data type DEFINE FIELD locked ON TABLE user TYPE bool; -- Set a field to have the number data type DEFINE FIELD login_attempts ON TABLE user TYPE number;
A |
vertical bar can be used to allow a field to be one of a set of types. The following example shows a field that can be a UUID
or an int
, perhaps for user
records that have varying data due to two diffent legacy ID types.
-- Set a field to have either the uuid or int type DEFINE FIELD user_id ON TABLE user TYPE uuid|int;
You can also set a field to have the array data type. The array data type can be used to store a list of values. You can also set the data type of the array’s contents.
-- Set a field to have the array data type DEFINE FIELD roles ON TABLE user TYPE array<string>; -- Set a field to have the array data type, equivalent to `array<any>` DEFINE FIELD posts ON TABLE user TYPE array; -- Set a field to have the array object data type DEFINE FIELD emails ON TABLE user TYPE array<object>;
You can make a field optional by wrapping the inner type in an option
, which allows you to store NONE
values in the field.
-- A user may enter a biography, but it is not required. -- By using the option type you also allow for NONE values. DEFINE FIELD biography ON TABLE user TYPE option<string>;
The example below shows how to define a field user
on a POST
table. The field is of type record. This means that the field can store a record<user>
or NONE
.
DEFINE FIELD user ON TABLE POST TYPE option<record<user>>;
Flexible types allow you to have SCHEMALESS
functionality on a SCHEMAFULL
table. This is necessary for working with nested object
types, which by nature do not have defined fields.
DEFINE TABLE user SCHEMAFULL; DEFINE FIELD name ON TABLE user TYPE string; DEFINE FIELD metadata ON TABLE user FLEXIBLE TYPE object;
Taking the following CREATE
statement:
CREATE user SET name = "User1", metadata = { country_code: "ee", time_zone: "EEST", age: 25 };
Without FLEXIBLE
, the database will recognize that an object has been inserted, but has no way of knowing whether the fields inside conform to the strict schema. As a result, metadata
will show up as an empty object. Note the difference between this and the name
field. The CREATE
statement will simply err if a string for name
is not provided, while metadata
will not err because some object has been passed in.
[ { "id": "user:6nv0vymyutpvajzrfjuw", "metadata": {}, "name": "User1" } ]
With FLEXIBLE
, the output will be as expected as the schema now flexibly allows any sort of object to be a field on the user
table.
Response[ { "id": "user:4a9amtnwzrvbya6p9l8x", "metadata": { "age": 25, "country_code": "ee", "time_zone": "EEST" }, "name": "User1" } ]
The fields of an object can be defined individually using the .
operator.
-- Define nested object property types DEFINE FIELD emails.address ON TABLE user TYPE string; DEFINE FIELD emails.primary ON TABLE user TYPE bool;
DEFAULT
clause to set a default valueYou can set a default value for a field using the DEFAULT
clause. The default value will be used if no value is provided for the field.
-- A user is not locked by default. DEFINE FIELD locked ON TABLE user TYPE bool -- Set a default value if empty DEFAULT false;
VALUE
clause to set a field’s valueThe VALUE
clause differs from DEFAULT
in that a default value is calculated if no other is indicated, otherwise accepting the value given in a query.
DEFINE FIELD updated ON TABLE user DEFAULT time::now(); -- Set `updated` to the year 1900 CREATE user SET updated = d"1900-01-01"; -- Then set to the year 1910 UPDATE user SET updated = d"1910-01-01";
A VALUE
clause, on the other hand, will ignore attempts to set the field to any other value.
DEFINE FIELD updated ON TABLE user VALUE time::now(); -- Ignores 1900 date, sets `updated` to current time CREATE user SET updated = d"1900-01-01"; -- Ignores again, updates to current time UPDATE user SET updated = d"1900-01-01";
As the example above shows, a VALUE
clause sets the value every time a record is modified (created or updated). However, the value will not be recalculated in a SELECT
statement, which simply accesses the current set value.
DEFINE FIELD updated ON TABLE user VALUE time::now(); CREATE user:one; SELECT * FROM ONLY user:one; -- Sleep for one second SLEEP 1s; -- `updated` is still the same SELECT * FROM ONLY user:one;
To create a field that is calculated each time it is accessed, a future
can be used.
DEFINE FIELD accessed_at ON TABLE user VALUE <future> { time::now() }; CREATE user:one; SELECT * FROM ONLY user:one; -- Sleep for one second SLEEP 1s; -- `accessed_at` is a different value now SELECT * FROM ONLY user:one;
You can alter a passed value using the VALUE
clause. This is useful for altering the value of a field before it is stored in the database.
In the example below, the VALUE
clause is used to ensure that the email address is always stored in lowercase characters by using the string::lowercase
function.
-- Ensure that an email address is always stored in lowercase characters DEFINE FIELD email ON TABLE user TYPE string VALUE string::lowercase($value);
You can take your field definitions even further by using asserts. Assert can be used to ensure that your data remains consistent. For example you can use asserts to ensure that a field is always a valid email address, or that a number is always positive.
-- Give the user table an email field. Store it in a string DEFINE FIELD email ON TABLE user TYPE string -- Check if the value is a properly formatted email address ASSERT string::is::email($value);
READONLY
Available since: v1.2.0
The READONLY
clause can be used to prevent any updates to a field. This is useful for fields that are automatically updated by the system. To make a field READONLY
, add the READONLY
clause to the DEFINE FIELD
statement. As seen in the example below, the created
field is set to READONLY
.
DEFINE FIELD created ON resource VALUE time::now() READONLY;
IF NOT EXISTS
clauseAvailable since: v1.3.0
The IF NOT EXISTS
clause can be used to define a field only if it does not already exist. You should use the IF NOT EXISTS
clause when defining a field in SurrealDB if you want to ensure that the field is only created if it does not already exist. If the field already exists, the DEFINE FIELD
statement will return an error.
It’s particularly useful when you want to safely attempt to define a field without manually checking its existence first.
On the other hand, you should not use the IF NOT EXISTS
clause when you want to ensure that the field definition is updated regardless of whether it already exists. In such cases, you might prefer using the OVERWRITE
clause, which allows you to define a field and overwrite an existing one if it already exists, ensuring that the latest version of the definition is always in use
-- Create a field if it does not already exist DEFINE FIELD IF NOT EXISTS email ON TABLE user TYPE string;
OVERWRITE
clauseAvailable since: v2.0.0
The OVERWRITE
clause can be used to define a field and overwrite an existing one if it already exists. You should use the OVERWRITE
clause when you want to modify an existing field definition. If the field already exists, the DEFINE FIELD
statement will overwrite the existing definition with the new one.
-- Create an FIELD and overwrite if it already exists DEFINE FIELD OVERWRITE example ON TABLE user TYPE string;
By default, the permissions on a field will be set to FULL unless otherwise specified.
DEFINE FIELD info ON TABLE some_table TYPE string; INFO FOR TABLE some_table;
Response{ events: {}, fields: { info: 'DEFINE FIELD info ON some_table TYPE string PERMISSIONS FULL' }, indexes: {}, lives: {}, tables: {} }
You can set permissions on fields to control who can perform operations on them using the PERMISSIONS
clause. The PERMISSIONS
clause can be used to set permissions for SELECT
, CREATE
, and UPDATE
operations. The DELETE
operation only relates to records and, as such, is not available for fields.
-- Set permissions for the email field DEFINE FIELD email ON TABLE user PERMISSIONS FOR select WHERE published=true OR user=$auth.id FOR update WHERE user=$auth.id OR $auth.role="admin";
By using an Access Control List as an example we can show how we can restrict what values can be stored in an array. In this example we are using an array to store the permissions for a user on a resource. The permissions are restricted to a specific set of values.
-- An ACL can be applied to any kind of resource (record) DEFINE FIELD resource ON TABLE acl TYPE record; -- We associate the acl with a user using record<user> DEFINE FIELD user ON TABLE acl TYPE record<user>; -- The permissions for the user+resource will be stored in an array. DEFINE FIELD permissions ON TABLE acl TYPE array -- The array must not be empty because at least one permission is required to make a valid ACL -- The items in the array must also be restricted to specific permissions ASSERT array::len($value) > 0 AND $value ALLINSIDE ["create", "read", "write", "delete"]; -- SEE IT IN ACTION -- 1: Add users CREATE user:tobie SET firstName = 'Tobie', lastName = 'Hitchcock', email = 'Tobie.Hitchcock@surrealdb.com'; CREATE user:abc SET firstName = 'A', lastName = 'B', email = 'c@d.com'; CREATE user:efg SET firstName = 'E', lastName = 'F', email = 'g@h.com'; -- 2: Create a resource CREATE document:SurrealDB_whitepaper SET name = "some messaging queue"; -- 3: Associate with ACL CREATE acl SET user = user:tobie, resource = document:SurrealDB_whitepaper, permissions = ["create", "write", "read"]; CREATE acl SET user = user:abc, resource = document:SurrealDB_whitepaper, permissions = ["read", "delete"]; -- Test Asserts using failure examples -- A: Create ACL without permissions field CREATE acl SET user = user:efg, permissions = [], # FAIL - permissions must not be empty resource = document:SurrealDB_whitepaper; -- B: Create acl with invalid permisson CREATE acl SET user = user:efg, permissions = ["all"], # FAIL - This value is not allowed in the array resource = document:SurrealDB_whitepaper;
You can use the ASSERT
clause to apply a regular expression to a field to ensure that it matches a specific pattern. In the example below, the ASSERT
clause is used to ensure that the countrycode
field is always a valid ISO-3166 country code.
-- Specify a field on the user table DEFINE FIELD countrycode ON user TYPE string -- Ensure country code is ISO-3166 ASSERT $value = /[A-Z]{3}/ -- Set a default value if empty VALUE $value OR $before OR 'GBR' ;
While a DEFINE TABLE
statement represents a template for any subsequent records to be created, a DEFINE FIELD
statement pertains to concrete field data of a record. As such, a DEFINE FIELD
statement gives access to the record’s other fields through their names, as well as the current field through the $value
parameter.
DEFINE TABLE person SCHEMAFULL; DEFINE FIELD first_name ON TABLE person TYPE string VALUE string::lowercase($value); DEFINE FIELD last_name ON TABLE person TYPE string VALUE string::lowercase($value); DEFINE FIELD name ON TABLE person VALUE first_name + ' ' + last_name; // Creates a `person` with the name "bob bobson" CREATE person SET first_name = "BOB", last_name = "BOBSON";
The $this
parameter gives access to the entire record on which a field is defined.
DEFINE FIELD extra_self ON TABLE person VALUE $this; CREATE person:one SET name = "Little person", age = 6;
Output[ { age: 6, extra_self: { age: 6, id: person:one, name: 'Person' }, id: person:one, name: 'Person' } ]
In practice, using $this
to access the full record is useful when a field is defined as an expression, especially a future which is computed every time the field is accessed.
DEFINE FIELD followers ON TABLE person VALUE <future> { (SELECT VALUE <-follows<-person.id FROM ONLY $this) }; CREATE person:one, person:two, person:three; RELATE person:one->follows->person:three; SELECT * FROM person:three; RELATE person:two->follows->person:three; SELECT * FROM person:three;
Output of SELECT statements-------- Query -------- [ { followers: [ person:one ], id: person:three } ] -------- Query -------- [ { followers: [ person:one, person:two ], id: person:three } ]
As DEFINE FIELD
statements are computed in alphabetical order, be sure to keep this in mind when using fields that rely on the values of others.
The following example is identical to the above except that full_name
has been chosen for the previous field name
. The full_name
field will be calculated after first_name
, but before last_name
.
DEFINE TABLE person SCHEMAFULL; DEFINE FIELD first_name ON TABLE person TYPE string VALUE string::lowercase($value); DEFINE FIELD last_name ON TABLE person TYPE string VALUE string::lowercase($value); DEFINE FIELD full_name ON TABLE person VALUE first_name + ' ' + last_name; // Creates a `person` with `full_name` of "bob BOBSON", not "bob bobson" CREATE person SET first_name = "Bob", last_name = "Bobson";
A good rule of thumb is to organize your DEFINE FIELD
statements in alphabetical order.
Available since: v2.0.0
A field can also be defined as a literal type, by specifying one or more possible values and/or permitted types.
DEFINE FIELD coffee ON TABLE order TYPE "regular" | "large" | { special_order: string }; CREATE order SET coffee = { special_order: "Venti Quadruple Ristretto Half-Decaf Soy Latte with 4 pumps of sugar-free vanilla syrup" }; CREATE order SET coffee = "small";
Response-------- Query -------- [ { coffee: { special_order: 'Venti Quadruple Ristretto Half-Decaf Soy Latte with 4 pumps of sugar-free vanilla syrup' }, id: order:ga3m9qxtv8m02wdgoe73 } ] -------- Query -------- "Found 'small' for field `coffee`, with record `order:juq3twfic1s6gxw9ljgj`, but expected a 'regular' | 'large' | { special_order: string }"
One more example of a literal containing settings for a full text search filter:
DEFINE FIELD filter ON TABLE search_settings TYPE "None" | { type: "Ascii" } | { type: "EdgeNgram", from: int, to: int } | { type: "Lowercase" } | { type: "Ngram", from: int, to: int } | { type: "Snowball", language: string } | { type: "Uppercase" };