TypeDB 3.0 is live! Get started for free.

Defining annotations

This page explains how to use define queries to define annotations for types and type traits.

Overview

Annotations define constraints and specialized behavior for types and their traits, enabling more precise control over how data must behave in your database.

Example schema

As an example, consider the following schema

define
  entity content owns id;
  entity page sub content,
    owns page-id,
    owns name;
  entity profile sub page,
    owns username;
  entity user sub profile,
    owns email,
    owns phone,
    owns language,
    owns karma;

  attribute id value string;
  attribute page-id sub id;
  attribute username sub page-id;
  attribute name value string;
  attribute email value string;
  attribute phone value string;
  attribute language value string;
  attribute karma value double;

In the schema above we may have the following:

  • We may have general content data objects side by side with more specialized page s, profile s, and user s.

  • id s can be duplicated, i.e. need not be unique per content.

  • There can be any number of name s for a page and, thus, a user.

  • phone and email values do not adhere to specific formats.

  • …​

None of these behaviors may be desirable. TypeDB addresses the issue through a unified annotation mechanism. Annotation, prefixed with @, can be added to schema definition statements like the ones above. The following list summarizes some of the most common annotations:

  • @key ensures attributes act as keys to data. E.g.

    content owns id @key;
  • @regex enforces string values to adhere to specific regular expressions. E.g.,

    user owns phone @regex("^\d{8,15}$");
  • @unique enforce attributes uniquely identify their owner. E.g.,

    user owns phone @unique;
  • @card restricts cardinality, i.e., the number of data instances allowed in a specific context. E.g.,

    profile owns username @card(1..1);
  • @abstract disallows inserting data instances into types. E.g.,

    content @abstract;
  • @independent allows storing attributes without owners. E.g.

    attribute language @independent;

See the TypeQL Annotation reference for a full list of available annotations.

Annotation semantics

Default behaviors

Schema definitions may have default behavior if no annotation is specified.

  • Role player references of relation types default to @card(0..1) behavior (a relation may link 0 or 1 role player).

  • Role player traits of other types default to @card(0..) behavior (an object may play a specific role in 0 or more relations).

  • Attribute ownership traits of default to @card(0..1) behavior (an object may own a specific attribute 0 or 1 times).

See @card annotation for more details.

Redefining constraints

A single declaration cannot have multiple different annotations of the same kind. For example:

  • <statement> @card(X) @card(X) @card(X) will always result in type @card(X).

  • <statement> @card(X) @card(Y) is an invalid definition.

A defined annotations whose value should be changed can be redefined.

Constraints and annotations can also be combined as a result of subtyping and specialization.

Combining annotations

Constraints in TypeDB are cumulative, meaning that every constraint must be respected simultaneously. Constraints defined through annotations do not relax or interact with one another in a way that reduces their enforcement.

Subtyping

Subtyping is a powerful instrument in TypeDB and becomes even more powerful with annotations. As a general rule, subtypes inherit the behavior of their supertypes, but they may further specialize this behavior (e.g., through additional annotations).

See the annotation reference for further details.

Intricacies

Default behaviors are implicitly set for defined traits whenever there is no explicitly defined constraint

E.g., the following schema requires @card(0..) specification for page owns name, because the default cardinality would not allow page s and, thus, profile s to have 2 or 3 name s.

At the same time, user s will only have two cardinality constraints for their names: 0.. and 0..3 as the ownership is inherited. However, if you were to set a user owns name again, a default cardinality constraint would be generated for this ownership, and user would need to comply to three cardinality constraints for name s.

define
  entity page sub content, owns name @card(0..);
  entity profile sub page, owns name @card(0..3);
  entity user sub profile;