Officially out now: The TypeDB 3.0 Roadmap

Lesson 3.4: Fetching schema types

Variablizing types

So far, we’ve seen that the output of Fetch queries includes a type field for every retrieved data instance and their attributes. However, there are also types involved in the query that are not returned. Consider this polymorphic query from Lesson 3.2, where we asked for books that a user had interacted with in some way on the web store.

match
$user isa user, has id "u0008";
$book isa book;
(executor: $user, action: $action) isa action-execution;
($book, $action) isa relation;
fetch
$book: isbn, title;
{
    "book": {
        "isbn": [
            { "value": "9780060929794", "value_type": "string", "type": { "label": "isbn-13", "root": "attribute" } },
            { "value": "0060929790", "value_type": "string", "type": { "label": "isbn-10", "root": "attribute" } }
        ],
        "title": [ { "value": "One Hundred Years of Solitude", "value_type": "string", "type": { "label": "title", "root": "attribute" } } ],
        "type": { "label": "paperback", "root": "entity" }
    }
}
{
    "book": {
        "isbn": [
            { "value": "9780008627843", "value_type": "string", "type": { "label": "isbn-13", "root": "attribute" } },
            { "value": "0008627843", "value_type": "string", "type": { "label": "isbn-10", "root": "attribute" } }
        ],
        "title": [ { "value": "The Hobbit", "value_type": "string", "type": { "label": "title", "root": "attribute" } } ],
        "type": { "label": "ebook", "root": "entity" }
    }
}
{
    "book": {
        "isbn": [
            { "value": "9780387881355", "value_type": "string", "type": { "label": "isbn-13", "root": "attribute" } },
            { "value": "0387881352", "value_type": "string", "type": { "label": "isbn-10", "root": "attribute" } }
        ],
        "title": [ { "value": "Electron Backscatter Diffraction in Materials Science", "value_type": "string", "type": { "label": "title", "root": "attribute" } } ],
        "type": { "label": "hardback", "root": "entity" }
    }
}
{
    "book": {
        "isbn": [
            { "value": "9780195153446", "value_type": "string", "type": { "label": "isbn-13", "root": "attribute" } },
            { "value": "0195153448", "value_type": "string", "type": { "label": "isbn-10", "root": "attribute" } }
        ],
        "title": [ { "value": "Classical Mythology", "value_type": "string", "type": { "label": "title", "root": "attribute" } } ],
        "type": { "label": "paperback", "root": "entity" }
    }
}
{
    "book": {
        "isbn": [
            { "value": "9780500026557", "value_type": "string", "type": { "label": "isbn-13", "root": "attribute" } },
            { "value": "0500026556", "value_type": "string", "type": { "label": "isbn-10", "root": "attribute" } }
        ],
        "title": [ { "value": "Hokusai's Fuji", "value_type": "string", "type": { "label": "title", "root": "attribute" } } ],
        "type": { "label": "paperback", "root": "entity" }
    }
}

While the results list the books, they do not tell us anything about the nature of the user’s interaction with them. We can easily retrieve this information by adding a couple of additional lines to the query.

match
$user isa user, has id "u0008";
$book isa book;
(executor: $user, action: $action) isa action-execution;
($book, $action) isa relation;
$action isa! $action-type;   # new line
fetch
$book: isbn, title;
$action-type;                # new line

studio run Run

If we run this new query, we now also retrieve the type of $action!

{
    "action-type": { "label": "order", "root": "entity" },
    "book": {
        "isbn": [
            { "value": "9780060929794", "value_type": "string", "type": { "label": "isbn-13", "root": "attribute" } },
            { "value": "0060929790", "value_type": "string", "type": { "label": "isbn-10", "root": "attribute" } }
        ],
        "title": [ { "value": "One Hundred Years of Solitude", "value_type": "string", "type": { "label": "title", "root": "attribute" } } ],
        "type": { "label": "paperback", "root": "entity" }
    }
}
{
    "action-type": { "label": "order", "root": "entity" },
    "book": {
        "isbn": [
            { "value": "9780008627843", "value_type": "string", "type": { "label": "isbn-13", "root": "attribute" } },
            { "value": "0008627843", "value_type": "string", "type": { "label": "isbn-10", "root": "attribute" } }
        ],
        "title": [ { "value": "The Hobbit", "value_type": "string", "type": { "label": "title", "root": "attribute" } } ],
        "type": { "label": "ebook", "root": "entity" }
    }
}
{
    "action-type": { "label": "order", "root": "entity" },
    "book": {
        "isbn": [
            { "value": "9780387881355", "value_type": "string", "type": { "label": "isbn-13", "root": "attribute" } },
            { "value": "0387881352", "value_type": "string", "type": { "label": "isbn-10", "root": "attribute" } }
        ],
        "title": [ { "value": "Electron Backscatter Diffraction in Materials Science", "value_type": "string", "type": { "label": "title", "root": "attribute" } } ],
        "type": { "label": "hardback", "root": "entity" }
    }
}
{
    "action-type": { "label": "review", "root": "entity" },
    "book": {
        "isbn": [
            { "value": "9780195153446", "value_type": "string", "type": { "label": "isbn-13", "root": "attribute" } },
            { "value": "0195153448", "value_type": "string", "type": { "label": "isbn-10", "root": "attribute" } }
        ],
        "title": [ { "value": "Classical Mythology", "value_type": "string", "type": { "label": "title", "root": "attribute" } } ],
        "type": { "label": "paperback", "root": "entity" }
    }
}
{
    "action-type": { "label": "review", "root": "entity" },
    "book": {
        "isbn": [
            { "value": "9780500026557", "value_type": "string", "type": { "label": "isbn-13", "root": "attribute" } },
            { "value": "0500026556", "value_type": "string", "type": { "label": "isbn-10", "root": "attribute" } }
        ],
        "title": [ { "value": "Hokusai's Fuji", "value_type": "string", "type": { "label": "title", "root": "attribute" } } ],
        "type": { "label": "paperback", "root": "entity" }
    }
}
{
    "action-type": { "label": "review", "root": "entity" },
    "book": {
        "isbn": [
            { "value": "9780008627843", "value_type": "string", "type": { "label": "isbn-13", "root": "attribute" } },
            { "value": "0008627843", "value_type": "string", "type": { "label": "isbn-10", "root": "attribute" } }
        ],
        "title": [ { "value": "The Hobbit", "value_type": "string", "type": { "label": "title", "root": "attribute" } } ],
        "type": { "label": "ebook", "root": "entity" }
    }
}

We have been able to retrieve this information by introducing a new $action-type variable in an additional statement.

$action isa! $action-type;

Here we’ve used the isa! keyword which, like the isa keyword, is used to specify the type of a variable. However, while the isa keyword simultaneously matches a type and all of its subtypes, the isa! keyword matches a type exactly, without using inheritance polymorphism. We will see more examples of this throughout this lesson and later in the course.

Previous isa statements we’ve used have used a variable as the subject of the statement and a type label as the object, but in this isa! statement we’ve also used a variable, $action-type, as the object. The isa and isa! keywords must always take a data instance as the subject and a type as the object, which means that the variable $action-type represents a type rather than a data instance! Here we’ve made use of type variablization, one of TypeQL’s most powerful features.

Exercise

Modify the above query to also retrieve the type of the relation between $book and $action.

Sample solution
match
$user isa user, has id "u0008";
$book isa book;
(executor: $user, action: $action) isa action-execution;
($book, $action) isa! $relation-type;
$action isa! $action-type;
fetch
$book: isbn, title;
$action-type;
$relation-type;

studio run Run

The ability to variablize types in queries arises from TypeQL’s core philosophy:

Querying type hierarchies

In addition to using type variablization to retrieve the types of data instances returned in our queries, we can also query the schema directly, without having to involve data instances at all. For example, the following query retrieves the subtypes of book.

match
$book-type sub book;
fetch
$book-type;

studio run Run

{ "book-type": { "label": "ebook", "root": "entity" } }
{ "book-type": { "label": "hardback", "root": "entity" } }
{ "book-type": { "label": "book", "root": "entity" } }
{ "book-type": { "label": "paperback", "root": "entity" } }

These are indeed the subtypes of book. We also retrieve the type book itself because it is trivially its own subtype. For this query, we’ve used the sub keyword, which matches subtypes of a given supertype. Unlike the isa keyword, both the subject and the object of sub statements must be types. In this case, we’ve variablized the subject of the sub statement to query subtypes, but we could also variablize the object to query supertypes. In the following example, we query the supertypes of publisher.

match
publisher sub $supertype;
fetch
$supertype;

studio run Run

{ "supertype": { "label": "thing", "root": "thing" } }
{ "supertype": { "label": "entity", "root": "entity" } }
{ "supertype": { "label": "publisher", "root": "entity" } }
{ "supertype": { "label": "company", "root": "entity" } }

In addition to company (which is the direct supertype of publisher) and publisher (which is trivially its own supertype) we also retrieve the root types entity (the supertype of all entities) and thing (the supertype of all types).

The thing type is deprecated and will be removed in TypeDB 3.0. For more information, see the TypeDB 3.0 roadmap.

If we would like to retrieve only direct subtypes or supertypes of a type, we can instead use the sub! keyword, similar to the isa! keyword!

match
publisher sub! $supertype;
fetch
$supertype;

studio run Run

{ "supertype": { "label": "company", "root": "entity" } }
Exercise

Write a query to retrieve the direct supertype of city.

Sample solution
match
city sub! $supertype;
fetch
$supertype;

studio run Run

Now write a query to determine the other types that share that direct supertype.

Hint

The query should retrieve the direct subtypes of place.

Sample solution
match
$place-type sub! place;
fetch
$place-type;

studio run Run

Finally, write a query to retrieve all entity types in the schema.

Hint

The query should retrieve the subtypes of the root type entity, directly and indirectly.

Sample solution
match
$entity-type sub entity;
fetch
$entity-type;

studio run Run

Querying ownership interfaces

In addition to querying the type hierarchies defined in the schema, we can also query the defined ownership and role interfaces. In the following query, we retrieve the list of attribute types that the entity type promotion owns.

match
promotion owns $attribute;
fetch
$attribute;

studio run Run

{ "attribute": { "label": "code", "root": "attribute" } }
{ "attribute": { "label": "start-timestamp", "root": "attribute" } }
{ "attribute": { "label": "end-timestamp", "root": "attribute" } }
{ "attribute": { "label": "name", "root": "attribute" } }

To query ownerships, we use the owns keyword. As with the sub keyword, we can invert the intent of the statement by variablizing the object instead of the subject. We do this in the next query to retrieve the list of types that own the attribute type isbn-13.

match
$type owns isbn-13;
fetch
$type;

studio run Run

{ "type": { "label": "ebook", "root": "entity" } }
{ "type": { "label": "hardback", "root": "entity" } }
{ "type": { "label": "book", "root": "entity" } }
{ "type": { "label": "paperback", "root": "entity" } }
Exercise

Write a query to retrieve the list of types that own name.

Sample solution
match
$type owns name;
fetch
$type;

studio run Run

Querying role interfaces

Ownerships of attribute types are queried using a single keyword owns, but roles in relations must be queried using two different keywords: relates and plays. We will see how they work in the next few examples. To begin with, we will use the relates keyword to retrieve the list of roles in the locating relation.

match
locating relates $role;
fetch
$role;

studio run Run

{ "role": { "label": "locating:located", "root": "relation:role" } }
{ "role": { "label": "locating:location", "root": "relation:role" } }

When not part of a relation tuple, roles are described by role labels, comprising two terms specified by a : delimiter. The first term is the name of the relation that the role is part of, and the second is the name of the role in the scope of the relation. This is because multiple relations are permitted to use the same role names. So, for instance, the label locating:located represents the located role of the locating relation type.

To query for types that play a role using the plays keyword, we must use the same notation.

match
$type plays locating:located;
fetch
$type;

studio run Run

{ "type": { "label": "place", "root": "entity" } }
{ "type": { "label": "publication", "root": "entity" } }
{ "type": { "label": "user", "root": "entity" } }
{ "type": { "label": "city", "root": "entity" } }
{ "type": { "label": "state", "root": "entity" } }
{ "type": { "label": "country", "root": "entity" } }
{ "type": { "label": "address", "root": "entity" } }

Here we have queried the types that play the located role in locating. As with the sub and owns keywords, we can also invert the intent of plays statements1. In the next query, we do so in order to retrieve the list of roles that book plays.

match
book plays $role;
fetch
$role;

studio run Run

{ "role": { "label": "rating:rated", "root": "relation:role" } }
{ "role": { "label": "publishing:published", "root": "relation:role" } }
{ "role": { "label": "recommendation:recommended", "root": "relation:role" } }
{ "role": { "label": "order-line:item", "root": "relation:role" } }
{ "role": { "label": "contribution:work", "root": "relation:role" } }
{ "role": { "label": "promotion-inclusion:item", "root": "relation:role" } }
Exercise

Write a query to retrieve the list of roles in the delivery relation.

Sample solution
match
delivery relates $role;
fetch
$role;

studio run Run

Now modify the query to also retrieve the types that play those roles.

Sample solution
match
delivery relates $role;
$type plays $role;
fetch
$role;
$type;

studio run Run

Footnotes

  1. ^ The astute reader will notice that we have used this "inversion" technique for sub, owns, and plays statements, but not for relates statements. TypeQL is flexible enough that we could certainly do so if we wanted, but it is not particularly useful to and there are certain practical considerations that make the syntax tricky to use correctly. While learning the basics of TypeQL, it is best to avoid relates statements of this kind.