Search...
ctrl/
Light
Dark
System
Sign in

Constraints

Constraints give users fine-grained control to ensure data consistency. They can be defined on properties, links, object types, and custom scalars.

Gel includes a number of standard ready-to-use constraints:

exclusive

Enforce uniqueness (disallow duplicate values)

expression

Custom constraint expression (followed by keyword on)

one_of

A list of allowable values

max_value

Maximum value numerically/lexicographically

max_ex_value

Maximum value numerically/lexicographically (exclusive range)

min_value

Minimum value numerically/lexicographically

min_ex_value

Minimum value numerically/lexicographically (exclusive range)

max_len_value

Maximum length (str only)

min_len_value

Minimum length (str only)

regexp

Regex constraint (str only)

Example: enforce all User objects to have a unique username no longer than 25 characters:

Copy
type User {
  required username: str {
    # usernames must be unique
    constraint exclusive;

    # max length (built-in)
    constraint max_len_value(25);
  };
}

Constraints can be defined on object types. This is useful when the constraint logic must reference multiple links or properties.

Example: enforce that the magnitude of ConstrainedVector objects is no more than 5

Copy
type ConstrainedVector {
  required x: float64;
  required y: float64;

  constraint expression on (
    (.x ^ 2 + .y ^ 2) ^ 0.5 <= 5
    # or, long form: `(__subject__.x + __subject__.y) ^ 0.5 <= 5`
  );
}

The expression constraint is used here to define custom constraint logic. Inside constraints, the keyword __subject__ can be used to reference the value being constrained.

Note that inside an object type declaration, you can omit __subject__ and simply refer to properties with the leading dot notation (e.g. .property).

Also note that the constraint expression are fairly restricted. Due to how constraints are implemented, you can only reference single (non-multi) properties and links defined on the object type:

Copy
# Not valid!
type User {
  required username: str;
  multi friends: User;

  # ❌ constraints cannot contain paths with more than one hop
  constraint expression on ('bob' in .friends.username);
}

You can re-use constraints across multiple object types by declaring them as abstract constraints. Example:

Copy
abstract constraint min_value(min: anytype) {
    errmessage :=
      'Minimum allowed value for {__subject__} is {min}.';

    using (__subject__ >= min);
}

# use it like this:

scalar type posint64 extending int64 {
    constraint min_value(0);
}

# or like this:

type User {
  required age: int16 {
    constraint min_value(12);
  };
}

Constraints can be defined on computed properties:

Copy
type User {
  required username: str;
  required clean_username := str_trim(str_lower(.username));

  constraint exclusive on (.clean_username);
}

To define a composite constraint, create an exclusive constraint on a tuple of properties or links.

Copy
type User {
  username: str;
}

type BlogPost {
  title: str;

  author: User;

  constraint exclusive on ((.title, .author));
}

Constraints on object types can be made partial, so that they are not enforced when the specified except condition is met.

Copy
type User {
  required username: str;
  deleted: bool;

  # Usernames must be unique unless marked deleted
  constraint exclusive on (.username) except (.deleted);
}

Custom scalar types can be constrained.

Copy
scalar type username extending str {
  constraint regexp(r'^[A-Za-z0-9_]{4,20}$');
}

Note: you can't use exclusive constraints on custom scalar types, as the concept of exclusivity is only defined in the context of a given object type.

Use expression constraints to declare custom constraints using arbitrary EdgeQL expressions. The example below uses the built-in str_trim() function.

Copy
scalar type title extending str {
  constraint expression on (
    __subject__ = str_trim(__subject__)
  );
}

If you define a constraint on a type and then extend that type, the constraint will not be applied individually to each extending type. Instead, it will apply globally across all the types that inherited the constraint.

Copy
type User {
  required name: str {
    constraint exclusive;
  }
}
type Administrator extending User;
type Moderator extending User;
Copy
gel> 
.... 
.... 
insert Administrator {
 name := 'Jan'
};
{default::Administrator {id: 7aeaa146-f5a5-11ed-a598-53ddff476532}}
Copy
gel> 
.... 
.... 
insert Moderator {
 name := 'Jan'
};
gel error: ConstraintViolationError: name violates exclusivity constraint
  Detail: value of property 'name' of object type 'default::Moderator'
  violates exclusivity constraint
Copy
gel> 
.... 
.... 
insert User {
 name := 'Jan'
};
gel error: ConstraintViolationError: name violates exclusivity constraint
  Detail: value of property 'name' of object type 'default::User'
  violates exclusivity constraint

As this example demonstrates, if an object of one extending type has a value for a property that is exclusive, an object of a different extending type cannot have the same value.

If that's not what you want, you can instead delegate the constraint to the inheriting types by prepending the delegated keyword to the constraint. The constraint would then be applied just as if it were declared individually on each of the inheriting types.

Copy
type User {
  required name: str {
    delegated constraint exclusive;
  }
}
type Administrator extending User;
type Moderator extending User;
Copy
gel> 
.... 
.... 
insert Administrator {
 name := 'Jan'
};
{default::Administrator {id: 7aeaa146-f5a5-11ed-a598-53ddff476532}}
Copy
gel> 
.... 
.... 
insert User {
 name := 'Jan'
};
{default::User {id: a6e3fdaf-c44b-4080-b39f-6a07496de66b}}
Copy
gel> 
.... 
.... 
insert Moderator {
 name := 'Jan'
};
{default::Moderator {id: d3012a3f-0f16-40a8-8884-7203f393b63d}}
Copy
gel> 
.... 
.... 
insert Moderator {
 name := 'Jan'
};
gel error: ConstraintViolationError: name violates exclusivity constraint
  Detail: value of property 'name' of object type 'default::Moderator'
  violates exclusivity constraint

With the addition of delegated to the constraints, the inserts were successful for each of the types. We did not hit a constraint violation until we tried to insert a second Moderator object with the same name as the existing one.

This section describes the syntax to declare constraints in your schema.

[{abstract | delegated}] constraint name [ ( [argspec] [, ...] ) ]
    [ on ( subject-expr ) ]
    [ except ( except-expr ) ]
    [ extending base [, ...] ]
"{"
    [ using constr-expression ; ]
    [ errmessage := error-message ; ]
    [ annotation-declarations ]
    [ ... ]
"}" ;

where argspec is:

[ argname: ] {argtype | argvalue}

This declaration defines a new constraint with the following options:

abstract

If specified, the constraint will be abstract.

delegated

If specified, the constraint is defined as delegated, which means that it will not be enforced on the type it's declared on, and the enforcement will be delegated to the subtypes of this type. This is particularly useful for exclusive constraints in abstract types. This is only valid for concrete constraints.

name

The name (optionally module-qualified) of the new constraint.

argspec

An optional list of constraint arguments.

For an abstract constraint argname optionally specifies the argument name and argtype specifies the argument type.

For a concrete constraint argname optionally specifies the argument name and argvalue specifies the argument value. The argument value specification must match the parameter declaration of the abstract constraint.

on ( subject-expr )

An optional expression defining the subject of the constraint. If not specified, the subject is the value of the schema item on which the concrete constraint is defined.

The expression must refer to the original subject of the constraint as __subject__. The expression must be Immutable, but may refer to __subject__ and its properties and links.

Note also that <subject-expr> itself has to be parenthesized.

except ( exception-expr )

An optional expression defining a condition to create exceptions to the constraint. If <exception-expr> evaluates to true, the constraint is ignored for the current subject. If it evaluates to false or {}, the constraint applies normally.

except may only be declared on object constraints, and otherwise follows the same rules as on.

extending base [, ...]

If specified, declares the parent constraints for this abstract constraint.

The valid SDL sub-declarations are listed below:

using constr_expression

A boolean expression that returns true for valid data and false for invalid data. The expression may refer to the subject of the constraint as __subject__. This declaration is only valid for abstract constraints.

errmessage := error_message

An optional string literal defining the error message template that is raised when the constraint is violated. The template is a formatted string that may refer to constraint context variables in curly braces. The template may refer to the following:

  • $argname – the value of the specified constraint argument

  • __subject__ – the value of the title annotation of the scalar type, property or link on which the constraint is defined.

If the content of curly braces does not match any variables, the curly braces are emitted as-is. They can also be escaped by using double curly braces.

annotation-declarations

Set constraint annotation to a given value.

This section describes the low-level DDL commands for creating and dropping constraints and abstract constraints. You typically don't need to use these commands directly, but knowing about them is useful for reviewing migrations.

Define a new abstract constraint.

[ with [ module-alias := ] module module-name ]
create abstract constraint name [ ( [argspec] [, ...] ) ]
  [ on ( subject-expr ) ]
  [ extending base [, ...] ]
"{" subcommand; [...] "}" ;

where argspec is:

  [ argname: ] argtype

where subcommand is one of

  using constr-expression
  set errmessage := error-message
  create annotation annotation-name := value

The command create abstract constraint defines a new abstract constraint.

If name is qualified with a module name, then the constraint is created in that module, otherwise it is created in the current module. The constraint name must be distinct from that of any existing schema item in the module.

Most sub-commands and options of this command are identical to the SDL constraint declaration, with some additional features listed below:

[ module-alias := ] module module-name

An optional list of module alias declarations to be used in the migration definition. When module-alias is not specified, module-name becomes the effective current module and is used to resolve all unqualified names.

set errmessage := error_message

An optional string literal defining the error message template that is raised when the constraint is violated. Other than a slight syntactical difference this is the same as the corresponding SDL declaration.

create annotation annotation-name := value;

Set constraint annotation <annotation-name> to <value>. See create annotation for details.

Create an abstract constraint "uppercase" which checks if the subject is a string in upper case:

Copy
create abstract constraint uppercase {
  create annotation title := "Upper case constraint";

  using (str_upper(__subject__) = __subject__);

  set errmessage := "{__subject__} is not in upper case";
};

Alter the definition of an abstract constraint.

[ with [ module-alias := ] module module-name ]
alter abstract constraint name
"{" subcommand; [...] "}" ;

where subcommand is one of

  rename to newname
  using constr-expression
  set errmessage := error-message
  reset errmessage
  create annotation annotation-name := value
  alter annotation annotation-name := value
  drop annotation annotation-name

The command alter abstract constraint changes the definition of an abstract constraint item. name must be a name of an existing abstract constraint, optionally qualified with a module name.

[ module-alias := ] module module-name

An optional list of module alias declarations to be used in the migration definition. When module-alias is not specified, module-name becomes the effective current module and is used to resolve all unqualified names.

name

The name (optionally module-qualified) of the constraint to alter.

Subcommands allowed in the alter abstract constraint block:

rename to newname

Change the name of the constraint to newname. All concrete constraints inheriting from this constraint are also renamed.

alter annotation annotation-name := value

Alter constraint annotation <annotation-name>. See alter annotation for details.

drop annotation annotation-name

Remove annotation <annotation-name>. See drop annotation for details.

reset errmessage

Remove the error message from this abstract constraint. The error message specified in the base abstract constraint will be used instead.

All subcommands allowed in a create abstract constraint block are also valid here.

Rename the abstract constraint "uppercase" to "upper_case":

Copy
alter abstract constraint uppercase rename to upper_case;

Remove an abstract constraint from the schema.

[ with [ module-alias := ] module module-name ]
drop abstract constraint name ;

The command drop abstract constraint removes an existing abstract constraint item from the database schema. If any schema items depending on this constraint exist, the operation is refused.

[ module-alias := ] module module-name

An optional list of module alias declarations to be used in the migration definition.

name

The name (optionally module-qualified) of the constraint to remove.

Drop abstract constraint upper_case:

Copy
drop abstract constraint upper_case;

Define a concrete constraint on the specified schema item.

[ with [ module-alias := ] module module-name ]
create [ delegated ] constraint name
  [ ( [argspec] [, ...] ) ]
  [ on ( subject-expr ) ]
  [ except ( except-expr ) ]
"{" subcommand; [...] "}" ;

where argspec is:

  [ argname: ] argvalue

where subcommand is one of

  set errmessage := error-message
  create annotation annotation-name := value

The command create constraint defines a new concrete constraint. It can only be used in the context of create scalar, alter scalar, create property, alter property, create link, or alter link.

name must be a name (optionally module-qualified) of a previously defined abstract constraint.

Most sub-commands and options of this command are identical to the SDL constraint declaration, with some additional features listed below:

[ module-alias := ] module module-name

An optional list of module alias declarations to be used in the migration definition.

set errmessage := error_message

An optional string literal defining the error message template that is raised when the constraint is violated. Other than a slight syntactical difference, this is the same as the corresponding SDL declaration.

create annotation annotation-name := value;

An optional list of annotations for the constraint. See create annotation for details.

Create a "score" property on the "User" type with a minimum value constraint:

Copy
alter type User create property score: int64 {
  create constraint min_value(0)
};

Create a Vector with a maximum magnitude:

Copy
create type Vector {
  create required property x: float64;
  create required property y: float64;
  create constraint expression ON (
    __subject__.x^2 + __subject__.y^2 < 25
  );
}

Alter the definition of a concrete constraint on the specified schema item.

[ with [ module-alias := ] module module-name [, ...] ]
alter constraint name
  [ ( [argspec] [, ...] ) ]
  [ on ( subject-expr ) ]
  [ except ( except-expr ) ]
"{" subcommand; [ ... ] "}" ;

-- or --

[ with [ module-alias := ] module module-name [, ...] ]
alter constraint name
  [ ( [argspec] [, ...] ) ]
  [ on ( subject-expr ) ]
  subcommand ;

where subcommand is one of:

  set delegated
  set not delegated
  set errmessage := error-message
  reset errmessage
  create annotation annotation-name := value
  alter annotation annotation-name
  drop annotation annotation-name

The command alter constraint changes the definition of a concrete constraint. Both single- and multi-command forms are supported.

[ module-alias := ] module module-name

An optional list of module alias declarations for the migration.

name

The name (optionally module-qualified) of the concrete constraint that is being altered.

argspec

A list of constraint arguments as specified at the time of create constraint.

on ( subject-expr )

An expression defining the subject of the constraint as specified at the time of create constraint.

The following subcommands are allowed in the alter constraint block:

set delegated

Mark the constraint as delegated, which means it will not be enforced on the type it's declared on, and enforcement is delegated to subtypes. Useful for exclusive constraints.

set not delegated

Mark the constraint as not delegated, so it is enforced globally across the type and any extending types.

rename to newname

Change the name of the constraint to <newname>.

alter annotation annotation-name

Alter a constraint annotation.

drop annotation annotation-name

Remove a constraint annotation.

reset errmessage

Remove the error message from this constraint, reverting to that of the abstract constraint, if any.

All subcommands allowed in create constraint are also valid in alter constraint.

Change the error message on the minimum value constraint on the property "score" of the "User" type:

Copy
alter type User alter property score
  alter constraint min_value(0)
    set errmessage := 'Score cannot be negative';

Remove a concrete constraint from the specified schema item.

[ with [ module-alias := ] module module-name [, ...] ]
drop constraint name
  [ ( [argspec] [, ...] ) ]
  [ on ( subject-expr ) ]
  [ except ( except-expr ) ] ;

The command drop constraint removes the specified constraint from its containing schema item.

[ module-alias := ] module module-name

Optional module alias declarations for the migration definition.

name

The name (optionally module-qualified) of the concrete constraint to remove.

argspec

A list of constraint arguments as specified at the time of create constraint.

on ( subject-expr )

Expression defining the subject of the constraint as specified at the time of create constraint.

Remove constraint "min_value" from the property "score" of the "User" type:

Copy
alter type User alter property score
  drop constraint min_value(0);