Fundamentals
All queries to a TypeDB database are written in TypeQL.
TypeQL is a declarative query language. Rather than writing an algorithm of how the data should be retrieved, we declare requirements, and the TypeDB query processor will take care of finding an optimal way to retrieve and process the data.
TypeQL is both a Data Definition Language (DDL) and Data Manipulation Language (DML). |
As a DDL, it lets us define a schema of a database.
As a DML, it lets us query (read&write) data of a database.
The complete set of the TypeQL grammar can be seen in the ANTLR v.4 specification file: TypeQL.g4.
Query structure
A TypeQL query consists of clauses (one, two or three). There are the following types of clauses:
-
define
-
undefine
-
match
-
fetch
-
get
-
insert
-
delete
All clauses start with the keyword, the same as the clause’s name, followed by:
-
A list of variables and optional modifiers at the end (for a
get
clause); -
A list of variables or subqueries (for a
fetch
clause); -
A TypeQL pattern (for all other clauses).
A pattern consists of one or more statements.
-
Query
-
Clause
-
Pattern
-
Statement
-
-
-
See a simple query example
A string or a file with multiple TypeQL queries is a script.
See a TypeQL script example
insert
$p isa person,
has full-name "Masako Holley",
has email "masako.holley@vaticle.com";
insert
$p isa person,
has full-name "Pearle Goodman",
has email "pearle.goodman@vaticle.com";
insert
$p isa person,
has full-name "Kevin Morrison",
has email "kevin.morrison@vaticle.com";
The above example contains three insert queries without a match clause.
To rewrite it as a single query we need to remove the second and third insert
keyword and change variables, so that
they are not overlapping (otherwise, we will insert a single person entity with three full-names and three emails):
insert
$p1 isa person,
has full-name "Masako Holley",
has email "masako.holley@vaticle.com";
$p2 isa person,
has full-name "Pearle Goodman",
has email "pearle.goodman@vaticle.com";
$p3 isa person,
has full-name "Kevin Morrison",
has email "kevin.morrison@vaticle.com";
For more information on patterns, see the Pattern matching essentials and the Advanced patterns & queries pages.
Schema
TypeDB schema is like a blueprint of a database.
Every instance of data inserted in a TypeDB database must be assigned a type and be validated against constraints.
Database schemas written in TypeQL resemble logical data models of relational databases. TypeDB removes the need to create a separate physical data model and allows developers to create schemas that mirror their object model, simplifying design and development. As a result, TypeDB schemas look like entity-relationship (ER) diagrams with entities, relations, and attributes connected by roles of relations and ownerships of attributes, but also subtyping (see inheritance below). |
Types
A type represents a set of constraints on the interpretation of data.
A type for a data instance is like a class for an object in OOP.
A definition of a type in a schema of a database sets:
-
type hierarchy — every user-defined type has only one parent or supertype.
-
constraints — what a type can do: what role to play, what attributes it can own, what value type it has (attribute types only), and what roles it relates (relation types only).
A type can be addressed by its label (name). A type label is unique in a schema of a database.
We can define a new type only as a subtype of an existing one.
A new empty database has a set of built-in types. These built-in types are called root types because all user-defined types will be subtypes (direct or non-direct) of those root types.
Root type label | A subtype of the root type | An instance of data of a subtype |
---|---|---|
|
Entity type |
Instance of entity type |
|
Relation type |
Instance of relation type |
|
Attribute type |
Instance of attribute type |
All root types are abstract types. That’s why when we say entity type, we usually mean a subtype of the |
To avoid ambiguity when using type labels, we should try to provide context or specify what exactly we mean:
|
Inheritance
A type can subtype another type. As a result, the subtype inherits all the attributes owned and roles played by its supertype.
Roles can be inherited and even overridden as a part of relation inheritance. |
Type can only have a single supertype.
Types can be subtypes of other subtypes, resulting in a type hierarchy.
See simple example
For example, business-unit
subtypes user-group
, which subtypes subject
, which subtypes entity
root type.
Also, person
subtypes user
, that subtypes subject
:
-
entity
-
subject
-
user group
-
business unit
-
-
user
-
person
-
-
-
In TypeQL we can define these types with the following query:
define
subject sub entity;
user-group sub subject;
business-unit sub user-group;
user sub subject;
person sub user;
There is a strict hierarchy of types, so the whole typing system of a TypeDB database can always be represented by three independent trees with one of the root types at the top of each tree.
See hierarchy trees example
For example, a schema with the following types:
-
entity
-
person
-
vehicle
-
car
-
motorcycle
-
bicycle
-
-
-
relation
-
owning
-
using
-
driving
-
traveling
-
-
-
attribute
-
model
-
name
-
full-name
-
nickname
-
-
can be visualized as the following type hierarchy:

Abstract types
An abstract type can’t be instantiated (we can’t insert an instance of data of this type). All we can do with an abstract type is to subtype it.
All root types are abstract types.
The opposite of an abstract type is a concrete type.
All user-defined types are concrete types by default.
Thing type
An internal type called thing
can be used to address all types (both built-in and user-defined) or instances of all
types (effectively — all data).
All types are subtypes of the thing
, including the root types.

The Consider using For example: Data retrieval example
|
Entity types
Entity types (or subtypes of the entity
root type) represent the classification of independent objects in the
data model of our business domain.
Instance of data of an entity type represents a standalone object that exists in our data model independently.
Instance of an entity type doesn’t have a value. It is usually addressed by its ownership over attribute instances and/or roles played in relation instances.
An object modeled with an entity type might practically require other entities to exist, such as a car that cannot exist without its parts, but can be conceptualized without reference to those other entities: a car can be imagined without considering its parts.
See example
Given the schema:
define
name sub attribute, value string;
person sub entity,
owns name,
plays marriage:spouse;
marriage sub relation, relates spouse;
An instance of the person
type can be inserted in a database:
-
without owning any instances of the
name
attribute type, nor playing any roles, -
owning exactly one instance of the
name
attribute type with some value and playing a role of a spouse in a marriage relation, -
owning multiple names and/or playing the role
spouse
in multiple instances of themarriage
relation type.
To define a new entity type, we need to set its label and what type it’s a subtype of.
To set a property of an entity (like a name of a person), we need to define ownership by this entity of an instance
of an attribute
type with the required value.
To define a relationship between an entity and some other user-defined types, we need to define a relation with roles and the ability of the involved types to play those roles.
Entity types and instances example
For example, there could be entity types like company
and person
.
Given the company
entity type defined in a database schema, we can insert instances of data of this type in such
a database. Every instance of the company
type inserted into the database will represent a company, that can be
addressed by whatever attributes it has (e.g., name, registration number), or by roles played in relations (e.g.,
employer
for the particular instance of person
entity type in an employment
relation type).

On the above image two instances of company
type are called Company #1
and Company #2
, while in real life
scenario in a TypeDB database there is almost no way to differentiate between those two instances if they have no
attributes and do not participate in any relations. The only information we can get from existence of two instances
is that there are two distinct objects. But its really hard to tell which one is which without any additional
related data inserted.
For more information on how to define an entity
type, see the
Define entity types section on the
Define types page.
Relation types
Relation types (or subtypes of the relation
root type) represent relationships between types.
Relation types have roles.
Other types can play roles in relations if it’s mentioned in their definition. An instance of another type can be a role player for a role in the instance of a relation.
An instance of a relation can be uniquely addressed by a combination of its type, owned attributes, and role players.
A relation
type must specify at least one role. A relation cannot be conceptualized without at least some of its
role players.
See the group-membership example
For example, given the schema:
define
group-membership sub relation,
relates user-group,
relates group-member;
user-group sub entity, plays group-membership:user-group;
subject sub entity, plays group-membership:group-member;
user sub subject;
group-membership
is a relation
type that defines user-group
and group-member
roles.
The user-group
role is to be played by a user-group
entity type whereas the group-member
role is to be played by
a subject
type and all its subtypes entities.
Roles
Roles are special internal types used by relations. We can’t create an instance of a role in a database. But we can set an instance of another type (role player) to play a role in a particular instance of a relation type.
Roles allow a schema to enforce logical constraints on types of role players.
See the example of a role player’s type constraint
For example, given the schema below, a file
type entity can’t play any role in a group-membership
relation,
and a user
type entity can play the group-member
role of a group-membership
relation because it inherits it
from the subject
type.
define
group-membership sub relation,
relates user-group,
relates group-member;
user-group sub entity, plays group-membership:user-group;
subject sub entity, plays group-membership:group-member;
user sub subject;
file sub entity;
Abstract roles
Since TypeDB v. |
See the example of a concrete role in abstract type
Given the following schema:
define
membership sub relation,
abstract,
relates member;
group-membership sub membership;
The concrete relation type group-membership
inherits the member
role from the abstract relation
type membership
.
Before version 2.22.0
of TypeDB, roles of abstract relations are also abstract.
It would make the role member
abstract.
We have to override
an abstract role with a new concrete role to use it for data.
Since version 2.22.0
of TypeDB, all roles are concrete.
We can use a role inherited from an abstract relation directly without the need to override it with a new role:
insert
(member: $x) isa group-membership;
Databases created in versions of TypeDB prior to The best way to switch to the new behavior is to install the TypeDB v. See the alternative way to manually upgrade database schemaThe alternative way to migrate to the new behaviour is to reset (unset and set) the abstractness of each abstract relation type via the Driver API methods:
Pseudocode example
|
For more
information on how to define a relation
subtype, see the
Define relation types section on the
Define types page.
Attribute types
Attribute types (or subtypes of the attribute
root type) represent properties that other types can own.
Attribute types have a value type, and instances of attribute types have a value. This value is fixed and unique for every given instance of the attribute type.
Other types can own an attribute type. That means that instances of these other types can own an instance of this attribute type. This usually means that an object in our domain has a property with the matching value.
An instance of an attribute type can be uniquely addressed by its type and value.
There can’t be a second instance of the same type with the same value.
Multiple types can own the same attribute type — and different instances of the same type or different types can share ownership of the same attribute instance.
For more information on the types of values, attributes can have: see the list of value types on the Define types page.
See example
Given the schema:
define
name sub attribute, value string;
person sub entity, owns name;
An instance of an attribute type name
with a value "Bob" can be owned by:
-
no one (no instance of the
person
type owns the instance of thename
type with value "Bob"), -
one particular person (there is one person with such a name),
-
or multiple people (there are multiple people with the name
Bob
. All of thepesron
type instances have ownership of the same instance of thename
type with the value "Bob").
The feature of an attribute type owning another attribute type will be deprecated in one of the upcoming versions and deleted in TypeDB version 3.0. |
For more information on how to define an attribute
subtype, see the
Define attribute types section on the
Define types page.
Rules
Rules are a part of a schema and define embedded logic.
The reasoning engine uses rules as a set of logic to infer new data.
Rules can dramatically shorten complex queries, perform explainable knowledge discovery, and implement business logic at the database level.
A rule consists of a condition and a conclusion.
A condition is a pattern to look for in data.
A conclusion is a pattern to insert virtual (inferred) data for every result matched with a pattern from the condition.
Inference can only be used in a read transaction. Rules can’t change persisted data in a database. All reasoning is done within a dataset of a transaction. |
The rules syntax uses when
and then
keywords for condition and conclusion, respectively.
rule rule-label:
when {
## The condition
} then {
## The conclusion
};
The conclusion can be used to create a single virtual instance of data: a relation or ownership of an attribute.
Queries use rules for Inferring new data only in read transactions and only if the inference option is enabled.
See an example of a rule
rule add-view-permission: when {
$modify isa action, has name "modify_file";
$view isa action, has name "view_file";
$ac_modify (object: $obj, action: $modify) isa access;
$ac_view (object: $obj, action: $view) isa access;
(subject: $subj, access: $ac_modify) isa permission;
} then {
(subject: $subj, access: $ac_view) isa permission;
};
See the explanation.
We can use computation operations and functions in the condition pattern. And we can use value variables in the conclusion of a rule.
It is possible to create a recursive logic in the line of |
For more information on how to create rules in a schema, see the Define rules page.
Data
Every piece of data stored in a TypeDB database must be an instance of a type defined in the schema of the database.
In other words, to insert data into a TypeDB database, we must use the types defined in the schema of the database.
The schema defines the vocabulary we use to query our data. See the example below.
For more information about reading and writing data, see the following pages:
-
Writing data:
-
Reading data:
Example
Given the following schema:
define
person sub entity,
owns name,
owns age,
owns certified-fortune-teller;
name sub attribute, value string;
age sub attribute, value long;
certified-fortune-teller sub attribute, value boolean;
The simplest Insert query can look like this:
insert $p isa person;
By inserting information, that some unbound variable isa
<type> we create an instance of the type. The person
is
the type label in this example.
But what is the use of an instance of an entity type without any attributes owned or roles played in relations?
A more useful example of an insert query can look as the following:
insert
$p isa person,
has name "Bob",
has age 31,
has certified-fortune-teller false;
That creates an entity and assigns an ownership of three attributes of different types to the newly created entity.
If any of the attributes with the given value didn’t exist prior to this query — TypeDB will create an instance of an attribute with the required value automatically.
Now, to read that data, we can use the Get query:
match $p isa person;
get;
This should return to us all instances of the person
type. But yet again — what is the use for entities without their
properties? Let’s query for all attributes owned by the person
type instances:
match
$p isa person, has $a;
The response should consist of pairs of the person
type instance with an instance of an attribute type owned by it,
including our Bob
with the age
of 31
.
The result of the above query will not return us the first instance of the |
Let’s query specifically for Bob
, age 31
, and retrieve the value of the boolean attribute
certified-fortune-teller
:
match
$p isa person, has name `Bob`, has age 31, has certified-fortune-teller $cft;
get $p, $cft;
The above query will find every person
entity that has ownership over the instance of the attribute type name
with
the value of Bob
, ownership of the age
with the value of 31
and the ownership of the certified-fortune-teller
attribute with any value.
With the get
clause, we filter the results to get the person
instances and the corresponding
certified-fortune-teller
attribute (represented by the $cft
variable in the pattern) for every matched result
in a database.
For more information why the get
clause in the above example needs $p
see the
Get query page or the
example explanation.
Formatting
Indentation
Any indentation in TypeQL will be ignored and serves are only for visual aid.
See an example indentation
The following three queries are identical in their result:
insert $p isa person, has full-name "Masako Holley", has email "masako.holley@vaticle.com";
insert
$p isa person,
has full-name "Masako Holley",
has email "masako.holley@vaticle.com";
insert
$p
isa
person
,
has
full-name
"Masako Holley"
,
has
email
"masako.holley@vaticle.com"
;
Comments
TypeQL uses in-line comments only. To make a comment use the #
symbol.
See a comment example
# This is the first query
insert # this is the insert query without a match clause
$p isa person; # this line inserts a person entity
$p has full-name "Bob"; # we add a constraint to the $p variable used in a previous line
# This is the second query
insert
$p isa person, has full-name "Alex"; # In this query we insert another person
Learn more
Learn more essential information about different queries in TypeQL with the Queries page.
Or skip it and go straight to the Schema and Data sections of this documentation for more in-depth look on every query type.
Use the TypeQL grammar reference page for factual information about syntax of TypeQL.