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
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.
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;
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;
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;
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 |
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;
Run
{ "supertype": { "label": "company", "root": "entity" } }
Write a query to retrieve the direct supertype of city
.
Sample solution
match
city sub! $supertype;
fetch
$supertype;
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;
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;
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;
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;
Run
{ "type": { "label": "ebook", "root": "entity" } }
{ "type": { "label": "hardback", "root": "entity" } }
{ "type": { "label": "book", "root": "entity" } }
{ "type": { "label": "paperback", "root": "entity" } }
Write a query to retrieve the list of types that own name
.
Sample solution
match
$type owns name;
fetch
$type;
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;
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;
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;
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" } }
Write a query to retrieve the list of roles in the delivery
relation.
Sample solution
match
delivery relates $role;
fetch
$role;
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;
Run
Footnotes
-
^ The astute reader will notice that we have used this "inversion" technique for
sub
,owns
, andplays
statements, but not forrelates
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 avoidrelates
statements of this kind.