Lesson 5.3: Defining data constraints

Cardinality

The most important type of data constraint is configuring cardinality. Cardinality indicates how many times a connection is allowed between instances of a type.

For example:

define
entity book,
  owns title @card(1),
  owns genre @card(0..);

→ Commit

Indicates that every book instance must have exactly 1 title, and 0 or more genres. You can configure any cardinality range you like, such as @card(10..20), which allows between 10 and 20 (inclusive of both) connections.

Cardinalities can be set on plays and relates as well:

define
entity book,
  plays publishing:published @card(1);

relation publishing,
  relates publisher @card(0..),
  relates published @card(1),
  relates publication @card(0..);

→ Don’t commit, we don’t want to keep these

This configures that every book has must be in 1 publishing relationship, and every publishing relationship connect to exactly 1 published role player, and 0 or more of either publisher or publication.

By default the following cardinalities are set:

  • owns has @card(0..1) - there are 0 or 1 ownerships from an owner to the attribute type

  • relates has @card(0..1) - there are 0 or 1 players of a role in a relation type

  • plays has @card(0..) - any number of relations may be participated in

Constraining values

Strings

The values of string attribute types can be constrained with the @values or the @regex annotation, which specifies a Rust regex pattern validated when instantiating the attribute or on ownership.

define
attribute status, value string @values("paid", "dispatched", "delivered", "returned", "canceled");
attribute isbn-10, value string @regex(".{10}");

entity book, owns isbn-10;
entity order, owns status;

→ Commit

Numerical values

For numerical value types, you can provide allowed value ranges. You can use also @values to also restrict numerical value types to a specific set:

define
attribute page-count, value integer @range(0..);
attribute discount, value double @values(0.1, 0.2, 0.3);

→ Commit

Attribute ownership constraints and uniqueness

Value constraint annotations also by applied only to owns declarations.

Additionally, TypeQL has syntax for defining key and unique constraints on attribute ownerships via annotations, to enforce uniqueness.

define
attribute id, value string;
entity book,
  owns isbn-13 @key,
  owns isbn-10 @unique;
entity user, owns id @key;
entity order, owns id @key;
entity review, owns id @key;

→ Commit

When applied to an ownership statement in a Define query, the @key and @unique annotations apply a key or unique constraint respectively to that ownership.

A key constraint annotation requires that instances of the owner type own exactly one instance of the attribute type, and that it must be uniquely owned by the owner type. In the example above, every instance of book must own exactly one instance of isbn-13, and no two instances of book can own the same instance of isbn-13.

A unique constraint annotation requires only that instances of the attribute type are uniquely owned by the owner type. In the example above, no two instances of book can own the same instance of isbn-10, but an instance of book can own any number of instances of isbn-10 (including none).

Ownership annotations apply to the specific ownerships they are defined on rather than to the attribute in general. In the example above, key constraints are applied to the ownership of id by user, order, and review. This means that, while two instances of user (or two of order, or two of review) could not own the same instance of id, it is perfectly permissible for an instance of user and an instance of order to own the same instance of id.