Properties
Properties are used to associate primitive data with an object type or link.
type Player {
property email: str;
points: int64;
is_online: bool;
}
Properties are associated with a name (e.g. email
) and a primitive
type (e.g. str
).
The term primitive type is an umbrella term that
encompasses scalar types like str
,
arrays and tuples,
and more.
Properties can be declared using the property
keyword if that improves
readability, or it can be ommitted.
Required properties
Properties can be either optional
(the default) or required
.
E.g. here we have a User
type that's guaranteed to have an email
,
but name
is optional and can be empty:
type User {
required email: str;
optional name: str;
}
Since optional
keyword is the default, we can omit it:
type User {
required email: str;
name: str;
}
Cardinality
Properties have a cardinality:
-
prop: type
, short forsingle prop: type
, can either hold zero or one value (that's the default). -
multi prop: type
can hold an unordered set of values, which can be zero, one, or more values of typetype
.
For example:
type User {
# "single" keyword isn't necessary here:
# properties are single by default
single name: str;
# an unordered set of strings
multi nicknames: str;
# an unordered set of string arrays
multi set_of_arrays: array<str>;
}
multi vs. arrays
multi
properties are stored differently than arrays under the hood.
Essentially they are stored in a separate table (owner_id, value)
.
Pros of multi properties vs. arrays
-
multi
properties allow efficient search and mutation of large sets. Arrays are much slower for those operations. -
multi
properties can have indexes and constraints appied to individual elements; arrays, in general, cannot. -
It's easier to aggregate sets and operate on them than on arrays. In many cases arrays would require unpacking them into a set first.
Cons of multi properties vs. arrays
-
On small sets, arrays are faster to retrieve.
-
It's easier to retain the original order in arrays. Arrays are ordered, but sets are not.
Default values
Properties can have a default value. This default can be a static value or an arbitrary EdgeQL expression, which will be evaluated upon insertion.
type Player {
required points: int64 {
default := 0;
}
required latitude: float64 {
default := (360 * random() - 180);
}
}
Readonly properties
Properties can be marked as readonly
. In the example below, the
User.external_id
property can be set at the time of creation but not
modified thereafter.
type User {
required external_id: uuid {
readonly := true;
}
}
Constraints
Properties can be augmented wth constraints. The example below showcases a subset of Gel's built-in constraints.
type BlogPost {
title: str {
constraint exclusive; # all post titles must be unique
constraint min_len_value(8);
constraint max_len_value(30);
constraint regexp(r'^[A-Za-z0-9 ]+$');
}
status: str {
constraint one_of('Draft', 'InReview', 'Published');
}
upvotes: int64 {
constraint min_value(0);
constraint max_value(9999);
}
}
You can constrain properties with arbitrary EdgeQL expressions
returning bool
. To reference the value of the property, use the special scope
keyword __subject__
.
type BlogPost {
title: str {
constraint expression on (
__subject__ = str_trim(__subject__)
);
}
}
The constraint above guarantees that BlogPost.title
doesn't contain any
leading or trailing whitespace by checking that the raw string is equal to the
trimmed version. It uses the built-in str_trim()
function.
For a full reference of built-in constraints, see the Constraints reference.
Annotations
Properties can contain annotations, small human-readable notes. The built-in
annotations are title
, description
, and deprecated
. You may also
declare custom annotation types.
type User {
email: str {
annotation title := 'Email address';
}
}
Abstract properties
Properties can be concrete (the default) or abstract. Abstract properties
are declared independent of a source or target, can contain annotations, constraints, indexes, and can be marked as
readonly
.
abstract property email_prop {
annotation title := 'An email address';
readonly := true;
}
type Student {
# inherits annotations and "readonly := true"
email: str {
extending email_prop;
};
}
Overloading properties
Any time we want to amend an inherited property (e.g. to add a constraint),
the overloaded
keyword must be used. This is to prevent unintentional
overloading due to a name clash:
abstract type Named {
optional name: str;
}
type User extending Named {
# make "name" required
overloaded required name: str;
}
Declaring properties
Syntax
This section describes the syntax to declare properties in your schema.
Concrete property form used inside type declaration:
[ overloaded ] [{required | optional}] [{single | multi}]
[ property ] name : type
[ "{"
[ extending base [, ...] ; ]
[ default := expression ; ]
[ readonly := {true | false} ; ]
[ annotation-declarations ]
[ constraint-declarations ]
...
"}" ]
Computed property form used inside type declaration:
[{required | optional}] [{single | multi}]
[ property ] name := expression;
Computed property form used inside type declaration (extended):
[ overloaded ] [{required | optional}] [{single | multi}]
property name [: type]
[ "{"
using (expression) ;
[ extending base [, ...] ; ]
[ annotation-declarations ]
[ constraint-declarations ]
...
"}" ]
Abstract property form:
abstract property [module::]name
[ "{"
[extending base [, ...] ; ]
[ readonly := {true | false} ; ]
[ annotation-declarations ]
...
"}" ]
Description
There are several forms of property
declaration, as shown in the
syntax synopsis above. The first form is the canonical definition
form, the second and third forms are used for defining a
computed property, and the last
one is a form to define an abstract property
.
The abstract form allows declaring the property directly inside a module.
Concrete property forms are always used as sub-declarations for an object type or a link.
The following options are available:
- overloaded
-
If specified, indicates that the property is inherited and that some feature of it may be altered in the current object type. It is an error to declare a property as overloaded if it is not inherited.
- required
-
If specified, the property is considered required for the parent object type. It is an error for an object to have a required property resolve to an empty value. Child properties always inherit the required attribute, i.e it is not possible to make a required property non-required by extending it.
- optional
-
This is the default qualifier assumed when no qualifier is specified, but it can also be specified explicitly. The property is considered optional for the parent object type, i.e. it is possible for the property to resolve to an empty value.
- multi
-
Specifies that there may be more than one instance of this property in an object, in other words,
Object.property
may resolve to a set of a size greater than one. - single
-
Specifies that there may be at most one instance of this property in an object, in other words,
Object.property
may resolve to a set of a size not greater than one.single
is assumed if nethermulti
norsingle
qualifier is specified. - extending base [, ...]
-
Optional clause specifying the parents of the new property item.
Use of
extending
creates a persistent schema relationship between the new property and its parents. Schema modifications to the parent(s) propagate to the child. - type
-
The type must be a valid type expression denoting a non-abstract scalar or a container type.
The valid SDL sub-declarations are listed below:
- default := expression
-
Specifies the default value for the property as an EdgeQL expression. The default value is used in an
insert
statement if an explicit value for this property is not specified.The expression must be Stable.
- readonly := {true | false}
-
If
true
, the property is considered read-only. Modifications of this property are prohibited once an object is created. All of the derived properties must preserve the original read-only value. - annotation-declarations
-
Set property annotation to a given value.
- constraint-declarations
-
Define a concrete constraint on the property.
DDL commands
This section describes the low-level DDL commands for creating, altering, and dropping properties. You typically don't need to use these commands directly, but knowing about them is useful for reviewing migrations.
Create property
Define a new property.
[ with with-item [, ...] ]
{create|alter} {type|link} SourceName "{"
[ ... ]
create [{required | optional}] [{single | multi}]
property name
[ extending base [, ...] ] : type
[ "{" subcommand; [...] "}" ] ;
[ ... ]
"}"
Computed property form:
[ with with-item [, ...] ]
{create|alter} {type|link} SourceName "{"
[ ... ]
create [{required | optional}] [{single | multi}]
property name := expression;
[ ... ]
"}"
Abstract property form:
[ with with-item [, ...] ]
create abstract property [module::]name [extending base [, ...]]
[ "{" subcommand; [...] "}" ]
where subcommand is one of
set default := expression
set readonly := {true | false}
create annotation annotation-name := value
create constraint constraint-name ...
Parameters
Most sub-commands and options of this command are identical to the
SDL property declaration. The
following subcommands are allowed in the create property
block:
- set default := expression
-
Specifies the default value for the property as an EdgeQL expression. Other than a slight syntactical difference this is the same as the corresponding SDL declaration.
- set readonly := {true | false}
-
Specifies whether the property is considered read-only. Other than a slight syntactical difference this is the same as the corresponding SDL declaration.
- create annotation annotation-name := value
-
Set property annotation-name to value.
See
create annotation
for details. - create constraint
-
Define a concrete constraint on the property. See
create constraint
for details.
Examples
Define a new link address
on the User
object type:
alter type User {
create property address: str
};
Define a new computed property
number_of_connections
on the User
object type counting the
number of interests:
alter type User {
create property number_of_connections :=
count(.interests)
};
Define a new abstract link orderable
with weight
property:
create abstract link orderable {
create property weight: std::int64
};
Alter property
Change the definition of a property.
[ with with-item [, ...] ]
{create | alter} {type | link} source "{"
[ ... ]
alter property name
[ "{" ] subcommand; [...] [ "}" ];
[ ... ]
"}"
[ with with-item [, ...] ]
alter abstract property [module::]name
[ "{" ] subcommand; [...] [ "}" ];
where subcommand is one of
set default := expression
reset default
set readonly := {true | false}
reset readonly
rename to newname
extending ...
set required [using (<conversion-expr)]
set optional
reset optionality
set single [using (<conversion-expr)]
set multi
reset cardinality [using (<conversion-expr)]
set type typename [using (<conversion-expr)]
reset type
using (computed-expr)
create annotation annotation-name := value
alter annotation annotation-name := value
drop annotation annotation-name
create constraint constraint-name ...
alter constraint constraint-name ...
drop constraint constraint-name ...
Parameters
- source
-
The name of an object type or link on which the property is defined. May be optionally qualified with module.
- name
-
The unqualified name of the property to modify.
- module
-
Optional name of the module to create or alter the abstract property in. If not specified, the current module is used.
The following subcommands are allowed in the alter link
block:
- rename to newname
-
Change the name of the property to newname. All concrete properties inheriting from this property are also renamed.
- extending ...
-
Alter the property parent list. The full syntax of this subcommand is:
extending name [, ...] [ first | last | before parent | after parent ]
This subcommand makes the property a child of the specified list of parent property items. The requirements for the parent-child relationship are the same as when creating a property.
It is possible to specify the position in the parent list using the following optional keywords:
-
first
– insert parent(s) at the beginning of the parent list, -
last
– insert parent(s) at the end of the parent list, -
before <parent>
– insert parent(s) before an existing parent, -
after <parent>
– insert parent(s) after an existing parent.
-
- set required [using (<conversion-expr)]
-
Make the property required.
- set optional
-
Make the property no longer required (i.e. make it optional).
- reset optionality
-
Reset the optionality of the property to the default value (
optional
), or, if the property is inherited, to the value inherited from properties in supertypes. - set single [using (<conversion-expr)]
-
Change the maximum cardinality of the property set to one. Only valid for concrete properties.
- set multi
-
Change the maximum cardinality of the property set to greater than one. Only valid for concrete properties.
- reset cardinality [using (<conversion-expr)]
-
Reset the maximum cardinality of the property to the default value (
single
), or, if the property is inherited, to the value inherited from properties in supertypes. - set type typename [using (<conversion-expr)]
-
Change the type of the property to the specified typename. The optional
using
clause specifies a conversion expression that computes the new property value from the old. The conversion expression must return a singleton set and is evaluated on each element ofmulti
properties. Ausing
clause must be provided if there is no implicit or assignment cast from old to new type. - reset type
-
Reset the type of the property to the type inherited from properties of the same name in supertypes. It is an error to
reset type
on a property that is not inherited. - using (computed-expr)
-
Change the expression of a computed property. Only valid for concrete properties.
- alter annotation annotation-name;
-
Alter property annotation annotation-name. See
alter annotation
for details. - drop annotation annotation-name;
-
Remove property annotation annotation-name. See
drop annotation
for details. - alter constraint constraint-name ...
-
Alter the definition of a constraint for this property. See
alter constraint
for details. - drop constraint constraint-name;
-
Remove a constraint from this property. See
drop constraint
for details. - reset default
-
Remove the default value from this property, or reset it to the value inherited from a supertype, if the property is inherited.
- reset readonly
-
Set property writability to the default value (writable), or, if the property is inherited, to the value inherited from properties in supertypes.
All the subcommands allowed in the create property
block are also
valid subcommands for alter property
block.
Examples
Set the title
annotation of property address
of object type
User
to "Home address"
:
alter type User {
alter property address
create annotation title := "Home address";
};
Add a maximum-length constraint to property address
of object type
User
:
alter type User {
alter property address {
create constraint max_len_value(500);
};
};
Rename the property weight
of link orderable
to sort_by
:
alter abstract link orderable {
alter property weight rename to sort_by;
};
Redefine the computed property
number_of_connections
to be the number of friends:
alter type User {
alter property number_of_connections using (
count(.friends)
)
};