TypeDB Fundamentals Lecture Series: from Nov 16 to Jan 9

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

A Fetch query consists of a match and a fetch clauses, followed by optional modifiers.

Fetch queries are written in TypeQL with the following syntax:

Fetch query 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.

Fetching values directly syntax
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.

Fetching values of owned attributes syntax
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.

Relabeling syntax
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.

Subquery syntax
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.

Example of a fetch clause reducing the number of results
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.

The following modifiers can be used at the end of a Fetch query: sort, offset, and limit.

They can be used to add pagination for the query results.

Sort the results

Sort modifier syntax
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).

Sort example
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 modifier syntax
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.

Offset example
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 modifier syntax
limit <value>;

Use the limit keyword followed by a positive integer to limit the number of results (answers) returned.

Limit example
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:

Example of fetching an attribute
match
$f isa file, has path $p;
fetch
$p;

The above query matches files with their path attributes and then fetches the path attribute.

Result example
{ "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:

Example of fetching value variable and type
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.

Result example
{
    "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:

Example of fetching all attributes of every file
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.

Result example
{
    "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

Example of output customization
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.

Result example
{
    "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.

Example of using a Get subquery
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.

Result example
{
    "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.

Example of using inference
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.

Result example with inference 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:

Complex example
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.

Result example
{
    "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.

Grouping by user example
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.

Result example
{
    "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.

Provide Feedback