Fetch query
A Fetch query retrieves values from a TypeDB database and returns them as JSON objects.
Fetch queries do not modify data, so can be used in Read or Write transactions. In read transactions, Fetch queries can use rule-based inference.
Behavior
A Fetch query projects concepts matched from a database to values formatted as JSON objects.
Types, attributes, value-variables, or subqueries with aggregation are projected directly into JSON. Entities and relations don’t have values to project, so instead of projecting them directly, we can extract values of attributes owned by them.
A Fetch query returns a lazy stream/iterator of results.
Every returned result is a valid JSON object.
The number of query results returned is equal or
less than the number of matched results by a match
clause.
In particular, if there are no matches in the preceding match
clause, then there are no results returned.
Syntax
Fetch queries are written in TypeQL with the following syntax:
match <pattern>
fetch {
<variable> [as <label>]
| <variable> [as <label>] : (<attribute-type> [as <label>]), ...
| <subquery-label> : {<fetch_query> | <get_aggregate_query>}
}; ...
[<modifiers>]
Variables mentioned in a fetch
clause must be bound (set) in a match
clause of the same query.
Match clause
A match
clause is mandatory in a Fetch query.
You can use a declarative and composable TypeQL pattern in a match
clause and TypeDB will find data that matches
the pattern.
For more information on a match
clause, see the
Match clause description on the Queries page.
For more information on patterns used in a match
clause, see the
Pattern syntax section.
Fetch clause
A fetch
clause is used in a Fetch query
to specify a projection of values from matched database concepts to an output of JSON objects.
This clause is executed exactly once for every result matched by the preceding match
clause of the same query.
A fetch
clause produces a valid JSON object for every matched result,
but deduplicates the results.
Following the fetch
keyword are one or multiple variables or subqueries, separated by semicolons.
Every variable or subquery mentioned in a fetch
clause adds a top-level key to the resulting JSON objects.
Fetching values and types directly
Types, attributes, and value-variables can be projected directly into JSON
by using their variable in a fetch
clause.
fetch <variable>; ...
Every instance in the JSON output has its type, root type, and value (if applicable).
See the Fetch an attribute and the Fetch a type or a value variable examples below.
Fetching owned attributes
Not every type can have a value to fetch.
Fetch always needs a value to project into JSON.
Hence, entities and relations can be used as variables in a fetch
clause only by
extracting attributes that they have.
To extract owned attributes, we use a variable followed by a colon and a list of comma-separated attribute-types. Every attribute type mentioned in this way adds a JSON subsection with the list of instances of that attribute type owned by the matched concept from a variable.
fetch (<variable> : <attribute-type>, ... ;) ...
See the Fetch all attributes owned example below.
Customizing output by relabeling
A fetch
clause support relabeling of variables and attribute types.
You can assign a new label which will be used for JSON output.
To relabel a variable or an attribute type, add the as
keyword followed by a new label.
By surrounding a new label in double quotes, you can use whitespaces in it.
fetch (<variable> as <label> : <attribute-type> as <label>, ... ;) ...
See the Customize output example below.
Subqueries
A Fetch query can include a subquery in a fetch
clause: another Fetch query or a Get query with
aggregation (to return a value).
A subquery consists of label, colon, and a TypeQL query string surrounded by curly brackets.
The label determines the key that will be used to introduce results of the subquery into the JSON output.
fetch <subquery-label> : {(<fetch_query> | <get_aggregate_query>)};
A subquery shares variables with its parent query. At least one variable from a parent query must be used in a subquery.
The number of nested subqueries is not limited, but many nested levels can break technical limitations of gRPC messaging.
For an example of nested subqueries, see the Grouping results section below.
Number of results
A Fetch query can return fewer results, than it was matched by its match
clause.
Excluding the usage of modifiers, that can happen due to natural deduplication of results.
See an example below.
match
$p isa person, has name $n;
fetch $n;
In the above example match
clause matches all person
type instances that own a name
attribute.
The fetch
clause then returns values of those name
attributes.
What happens if two persons have the same name?
In TypeDB database that is stored, as two entities own the same attribute (instance of the name
attribute type).
The match
clause finds all pairs of a person entity and its owned attribute.
But fetch
clause returns only the values of attributes.
Since both persons own the very same attribute,
it will be returned only once, reducing the total number of results.
For more examples of filtering matched results, see the Get query page.
Modifiers
In a Fetch query, modifiers can change the number and order of results.
They can be used to add pagination for the query results.
Sort the results
sort <variable> [asc|desc] [,<variable> [asc|desc]];
Use the sort
keyword followed by a variable to sort the results.
A second argument is optional and determines the sorting order: asc
(ascending, by default) or desc
(descending).
match
$p isa person, has full-name $n;
fetch $n;
sort $n asc;
This query returns sorted values of all full-name
attributes owned by person
entities.
To sort by multiple variables, add additional variables with a comma separator.
Offset the results
offset <value>;
Use the offset
keyword followed by the number to offset the results.
This is commonly used with the limit
keyword to return a desired range of results for pagination.
Don’t forget to use sort the results to ensure more deterministic and predictable results.
match $p isa person, has full-name $n;
fetch $n;
sort $n;
offset 6;
limit 10;
The above example sorts the full-name
attributes of all person
entities in ascending order,
skips the first six results, and returns up to the next ten.
Limit the results
limit <value>;
Use the limit
keyword followed by a positive integer to limit the number of results (answers) returned.
match
$p isa person, has full-name $n;
fetch $n;
limit 1;
We recommend using the limit
with the sorting aggregation
to get more deterministic and predictable results.
Examples
The following examples use the IAM schema and IAM sample data.
Fetch an attribute
To fetch an attribute, use the following query:
match
$f isa file, has path $p;
fetch
$p;
The above query matches files with their path
attributes and then fetches the path
attribute.
{ "p": { "value": "README.md", "value_type": "string", "type": { "label": "path", "root": "attribute" } } }
Fetch a type or a value variable
You can fetch a value of a value variable or a type from a schema of a database:
match
$x has attribute "iopvu.java", has size-kb $s;
$x isa! $type;
?size = round( ( $s + 2222 ) / 1024 );
fetch
$type;
?size;
The above query matches any instance of data ($x
) that has name
of iopvu.java
and any size-kb
($s
).
The variable $type
is assigned to be equal to the type of $x
.
Finally, we bound the value variable ?size
to some value, based on the value of the $s
.
{
"size": { "value": 2, "value_type": "long" },
"type": { "label": "file", "root": "entity" }
}
Note, that the size
has no type because it is a value from a value variable and
the type
has no value because it is a type of a concept.
Fetch all attributes owned
To fetch all attributes, owned by any type, we can fetch a variable with a colon followed by the attribute
root type:
match
$f isa file;
fetch
$f: attribute;
The above query matches all files (even those that do not have any attributes) and then fetches all attributes for every file.
{
"f": {
"attribute": [
{ "value": 55, "value_type": "long", "type": { "label": "size-kb", "root": "attribute" } },
{ "value": "iopvu.java", "value_type": "string", "type": { "label": "path", "root": "attribute" } }
],
"type": { "label": "file", "root": "entity" }
}
}
Customize output
match
$f isa file;
fetch
$f as file: attribute as "all attributes";
The above query is equal to the previous one, but it uses relabeling to customize keys of the JSON objects.
{
"file": {
"all attributes": [
{ "value": 55, "value_type": "long", "type": { "label": "size-kb", "root": "attribute" } },
{ "value": "iopvu.java", "value_type": "string", "type": { "label": "path", "root": "attribute" } }
],
"type": { "label": "file", "root": "entity" }
}
}
Note that instead of f
we have file
key and instead of attribute
— all-attributes
.
Using Get queries as subqueries
You can use Get queries as subqueries as long as you use aggregation to get a value.
match
$p isa person, has full-name $n;
fetch
$n;
$p: attribute;
try-subquery: {match $p has email $e; $e contains "kevin"; get $e; count;};
In the above query, we use Get query with count aggregation to get the number of emails with "kevin" substring owned for every user. Hence, the value in "try-subquery" key has no type.
{
"n": { "value": "Kevin Morrison", "value_type": "string", "type": { "label": "full-name", "root": "attribute" } },
"p": {
"attribute": [
{ "value": "Kevin Morrison", "value_type": "string", "type": { "label": "full-name", "root": "attribute" } },
{ "value": "kevin.morrison@vaticle.com", "value_type": "string", "type": { "label": "email", "root": "attribute" } }
],
"type": { "label": "person", "root": "entity" }
},
"try-subquery": { "value": 1, "value_type": "long" }
}
Using inference
We can use Fetch query to infer new facts.
For example, we can use the add-view-permission
rule from the
IAM schema
to infer view_file
action access permissions.
match
$o isa object, has path $fp;
$pa($o, $va) isa access;
$va isa action, has name 'view_file';
fetch $fp;
Using the IAM sample data the above query shows any results only if inference is enabled.
{ "fp": { "value": "README.md", "value_type": "string", "type": { "label": "path", "root": "attribute" } } }
Try the same query with disabled inference to see no matched results.
Complex example
Let’s try a bigger example with a little bit of everything:
match
$u isa user;
$o isa object;
$va isa action, has name "view_file";
$pa($o, $va) isa access;
$p($u, $pa) isa permission;
fetch
$u as user: full-name, email;
$o as object: attribute as all-attributes;
$va as action: name as action-name;
convert-size: {
match
$o has size-kb $sk;
?sm = round( $sk / 1024 );
fetch
?sm as size-mb; };
The above query matches all users and all objects, that those users can access with view_file
action.
Then it fetches full-name
and email
attributes for users,
all attributes for objects and name
attributes for action.
Finally, for every matched result it runs a subquery to convert size-kb
to size-mb
.
The result is a stream/iterator of JSON objects. See an example of such JSON object below.
{
"action": {
"action-name": [ { "value": "view_file", "value_type": "string", "type": { "label": "name", "root": "attribute" } } ],
"type": { "label": "operation", "root": "entity" }
},
"convert-size": [ { "size-mb": { "value": 1, "value_type": "long" } } ],
"object": {
"all-attributes": [
{ "value": 758, "value_type": "long", "type": { "label": "size-kb", "root": "attribute" } },
{ "value": "budget_2022-05-01.xlsx", "value_type": "string", "type": { "label": "path", "root": "attribute" } }
],
"type": { "label": "file", "root": "entity" }
},
"user": {
"email": [ { "value": "pearle.goodman@vaticle.com", "value_type": "string", "type": { "label": "email", "root": "attribute" } } ],
"full-name": [ { "value": "Pearle Goodman", "value_type": "string", "type": { "label": "full-name", "root": "attribute" } } ],
"type": { "label": "person", "root": "entity" }
}
}
Note how we do not match any attributes, except for name
for action in the match
clause.
If we do match instances of a type with has <attribute-type>
statement that excludes all instances that do not own
any of such attribute type.
For example: $u isa user, has full-name $fn;
would exclude all users that do not have any
full-name
.
At the same time, mathing $u usa user;
and then fetching $u: full-name;
will return even those users
that do not own any full-name
attributes.
The corresponding value would contain an empty list of values in this case.
Grouping results
One might want to group query results to reduce the size of a response or simplify further processing. In a fetch query that can be achieved by utilizing subqueries. For example, see below how to group the results from previous example by user.
match
$u isa user;
fetch
$u as user: attribute as all-attributes;
permited-files:{
match
$o isa object;
$va isa action, has name "view_file";
$pa($o, $va) isa access;
$p($u, $pa) isa permission;
fetch
$o as object: attribute as all-attributes;
convert-size: {
match
$o has size-kb $sk;
?sm = round( $sk / 1024 );
fetch
?sm as size-mb; }; };
The above query matches all users at first.
Then it fetches all attributes for every user and runs a subquery for a subsection labeled as permitted-files
.
This subquery matches all objects that participate in an access
relation with action named view_file
that
play a role in a permission
relation with the user matched by the parent query.
Then it fetches all attributes for such objects and runs another subquery.
The second subquery matches all objects that have size-kb
attribute and converts its value into some new value
with arithmetic.
Then it fetches the converted value as size-mb
.
The resulting JSON objects have a predictable structure of keys, that was set by the fetch
clauses, using
relabeling:
-
user
-
all-attributes
-
-
permitted-files
-
object
-
all-attributes
-
-
convert-size
-
See an example of the partial output below.
{
"permited-files": [
{
"convert-size": [ { "size-mb": { "value": 1, "value_type": "long" } } ],
"object": {
"all-attributes": [
{ "value": 758, "value_type": "long", "type": { "label": "size-kb", "root": "attribute" } },
{ "value": "budget_2022-05-01.xlsx", "value_type": "string", "type": { "label": "path", "root": "attribute" } }
],
"type": { "label": "file", "root": "entity" }
}
},
...
],
"user": {
"all-attributes": [
{ "value": "Kevin Morrison", "value_type": "string", "type": { "label": "full-name", "root": "attribute" } },
{ "value": "kevin.morrison@vaticle.com", "value_type": "string", "type": { "label": "email", "root": "attribute" } }
],
"type": { "label": "person", "root": "entity" }
}
}
Note that the order of key/value pairs in JSON is not guaranteed.