Officially out now: The TypeDB 3.0 Roadmap

Fetch query

A Fetch query retrieves values from a TypeDB database and returns them as JSON objects. For a practical guide on how to send a Fetch query to TypeDB, see the Fetch query page of the TypeDB Manual.

Syntax

A Fetch query consists of a match and a fetch clauses:

match <pattern>
fetch
  {  <variable> [as <label>]
   | <variable> [as <label>] : (<attribute-type> [as <label>]), ...
   | <subquery-label>        : "{" { <fetch_query> | <get_aggregate_query>} "}"
  }; ...

Behavior

A Fetch query retrieves concepts from a database and projects values and types to a JSON object.

A match clause is used to match data by a pattern, while a fetch clause is used to format the returned JSON.

The output of a Fetch query is a lazy stream/iterator of JSON objects. The number of query results can be less than the number of solutions of its match clause due to set semantics if not all query variables are projected into results.

You can use sorting and pagination with a Fetch query.

Fetch queries can use rule-based inference.

Match clause

A match clause in a Fetch query matches existing concepts in a database to project their values or values of attributes they own. You can use a declarative and composable TypeQL pattern in a match clause and TypeDB will find data that matches specified pattern.

A match clause is mandatory in a Fetch query.

For more information on patterns and statements used in a match clause, see the Patterns and Statements sections.

Fetch clause

A fetch clause is used in a Fetch query to specify the format of the returned JSON objects and projection of values (and types) from matched database concepts to the JSON.

In a fetch clause you specify top level projections to JSON, separating them with a semicolon. Projected variables and attribute types are used as keys in the resulted JSON, but you can customize that with output customization.

There are three types of projections that you can use in a Fetch query:

  • Direct projection — use a concept variable with either a type object or attribute.

  • Ownership projection — use a concept variable, colon, and attribute types that can be owned by an object in the variable.

  • Subquery — use another Fetch query or aggregated Get query.

A fetch clause is executed exactly once for every result matched by the preceding match clause of the same query. But it can return fewer results, than matched by the match clause if not all variables are projected.

Fetch variables

Types and values can be projected directly into a JSON.

Fetch a value

For this example, use a database with the IAM schema and sample data loaded.

To fetch a value, you can either fetch a concept variable with an attribute, or fetch a value variable.

For example, let’s match all files that own a path attribute and fetch all path attributes:

Fetching a value
match
$f isa file, has path $p;
fetch
$p;
Output example (partial)
{ "p": { "value": "psukg.java", "type": { "label": "path", "root": "attribute", "value_type": "string" } } }
{ "p": { "value": "budget_2021-08-01.xlsx", "type": { "label": "path", "root": "attribute", "value_type": "string" } } }
{ "p": { "value": "README.md", "type": { "label": "path", "root": "attribute", "value_type": "string" } } }

Fetch a type

For this example, use a database with the IAM schema and sample data loaded.

To fetch a type from a database, use a concept variable that is matched to a type by an isa or sub statement. Make sure to use an exclamation mark (isa!/sub!) to get only the exact type, and no supertypes via type inference.

Fetching type example
match
$x has attribute "iopvu.java";
$x isa! $type;
fetch
$type;

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.

Output example
{ "type": { "label": "file", "root": "entity" } }

Fetch owned attributes

For this example, use a database with the IAM schema and sample data loaded.

Entities and relations can be used in a fetch clause only by extracting values of attributes they own.

You can project all attributes owned by a data instance, filtered by an attribute type. To do that, use the concept variable, matched in a preceding match clause, in a fetch clause followed by a colon and attribute type label. You can use multiple types by listing them with a comma as a separator.

Let’s see how to fetch all attributes owned by every person entity of type full-name and email. First, we match every person entity, then we use the concept variable we matched them with in a fetch clause and specify attribute types to fetch:

Fetching owned attributes
match
$p isa person;
fetch
$p: full-name, email;

The above query example should produce a set of JSON objects with a similar format: the p as top-level key, with full-name, email, and type keys inside.

Output example (partial)
{
    "p": {
        "email": [ { "value": "kevin.morrison@typedb.com", "type": { "label": "email", "root": "attribute", "value_type": "string" } } ],
        "full-name": [ { "value": "Kevin Morrison", "type": { "label": "full-name", "root": "attribute", "value_type": "string" } } ],
        "type": { "label": "person", "root": "entity" }
    }
}

Fetching owned attributes by their type returns a list of owned attributes. This list can contain any number of attributes. Even if there are no attributes of selected type, the empty list is returned for the respective key.

To fetch all attributes owned by a data instance, use the attribute root type for projection. As a common supertype, it will project all possible attribute types.

See example
Fetching all attributes
match
$f isa file;
fetch
$f: attribute;

The above query example matches all files (even those that do not have any attributes) and then fetches all attributes for every file.

Output example (partial)
{
    "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" }
    }
}

Fetch with custom labels

For this example, use a database with the IAM schema and sample data loaded.

A Fetch query supports output customization (or relabeling) of the top level keys in the resulting JSON. Subqueries always use labels set in the query for their output. To customize a key for a direct projection or ownership projection, use the as keyword after the concept variable in a fetch clause. By surrounding a new label in double quotes, you can use whitespaces in it.

For example, if we want to fetch all attributes of entities matched by the variable $f, relabeling f to file:

Direct projection output customization example
match
$f isa file;
fetch
$f as file: attribute;
Output example (partial)
{
    "file": {
        "attribute": [
            { "value": 758, "type": { "label": "size-kb", "root": "attribute", "value_type": "long" } },
            { "value": "budget_2022-05-01.xlsx", "type": { "label": "path", "root": "attribute", "value_type": "string" } }
        ],
        "type": { "label": "file", "root": "entity" }
    }
}
{
    "file": {
        "attribute": [ { "value": "LICENSE", "type": { "label": "path", "root": "attribute", "value_type": "string" } } ],
        "type": { "label": "file", "root": "entity" }
    }
}

Ownership projection customization

For this example, use a database with the IAM schema and sample data loaded.

You can also customize (relabel) keys for attribute types used in ownership projection by using the same keyword as after the attribute type.

For example, if we want to fetch all attributes of entities matched by the variable $f, relabeling the f key to file, and the attribute key to the all attributes string:

Relabeling attribute type example
match
$f isa file;
fetch
$f as file: attribute as "all attributes";

The above query is equal to the previous one, but it uses the all attribute string for the key of attributes.

Output example (partial)
{
    "file": {
        "all attributes": [
            { "value": 758, "type": { "label": "size-kb", "root": "attribute", "value_type": "long" } },
            { "value": "budget_2022-05-01.xlsx", "type": { "label": "path", "root": "attribute", "value_type": "string" } }
        ],
        "type": { "label": "file", "root": "entity" }
    }
}
{
    "file": {
        "all attributes": [ { "value": "LICENSE", "type": { "label": "path", "root": "attribute", "value_type": "string" } } ],
        "type": { "label": "file", "root": "entity" }
    }
}

Fetch with subqueries

For this example, use a database with the IAM schema and sample data loaded.

A Fetch query can include a subquery in a fetch clause. A subquery is another Fetch query that returns a JSON for the key or a Get query with aggregation that returns 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.

For example, let’s match every file, and use a subquery to find its size in megabytes:

Subquery example (partial)
match
$f isa file;
fetch
$f as file: path as filename, size-kb;
file-size:{
    match
    $f has size-kb $sk;
    ?sm = round( $sk / 1024 );
    fetch
    ?sm as size-mb;
};

Some files don’t have a size-kb attribute. Without a subquery, matching this attribute in the top level match clause to calculate size in megabytes, would result in skipping the files without size, as they would not match a pattern. By adding a subquery, we can return all files. If a file doesn’t have a size-kb attribute, the subquery returns an empty list:

Output example
{
    "file": {
        "filename": [ { "value": "budget_2022-05-01.xlsx", "type": { "label": "path", "root": "attribute", "value_type": "string" } } ],
        "size-kb": [ { "value": 758, "type": { "label": "size-kb", "root": "attribute", "value_type": "long" } } ],
        "type": { "label": "file", "root": "entity" }
    },
    "file-size": [ { "size-mb": { "value": 1, "value_type": "long" } } ]
}
{
    "file": {
        "filename": [ { "value": "LICENSE", "type": { "label": "path", "root": "attribute", "value_type": "string" } } ],
        "size-kb": [  ],
        "type": { "label": "file", "root": "entity" }
    },
    "file-size": [  ]
}

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 using a big number of nested levels can break technical limitations of gRPC messaging.

Grouping results

For this example, use a database with the IAM schema and sample data loaded.

Subqueries can be used to group query results, reduce the size of a response, or simplify further processing. For example, let’s find all files that a user has a permission to access with the modify_file action. To group the results, let’s iterate through users on the parent query and use subquery to find files:

Grouping by user example
match
$u isa user;
fetch
$u as user: attribute as "User attributes";
permited-files:{
    match
    $f isa file;
    $va isa action, has name "modify_file";
    $pa($f, $va) isa access;
    $p($u, $pa) isa permission;
    fetch
    $f as file: attribute as "File attributes";
};

The above query matches all users at first. Then it fetches all attributes for every user and runs the subquery for the subsection labeled as permitted-files.

This subquery matches all files that participate in an access relation with action named modify_file that play a role in a permission relation with the user matched by the parent query. Then it fetches all attributes for such files.

The resulting JSON objects have a predictable structure of keys, that was set by the fetch clauses, using relabeling:

  • user

    • User attributes

  • permitted-files

    • file

      • File attributes

Output example (partial)
{
    "permited-files": [  ],
    "user": {
        "User attributes": [
            { "value": "masako.holley@typedb.com", "type": { "label": "email", "root": "attribute", "value_type": "string" } },
            { "value": "Masako Holley", "type": { "label": "full-name", "root": "attribute", "value_type": "string" } }
        ],
        "type": { "label": "person", "root": "entity" }
    }
}
{
    "permited-files": [
        {
            "file": {
                "File attributes": [
                    { "value": 70, "type": { "label": "size-kb", "root": "attribute", "value_type": "long" } },
                    { "value": "lzfkn.java", "type": { "label": "path", "root": "attribute", "value_type": "string" } }
                ],
                "type": { "label": "file", "root": "entity" }
            }
        },
        ...
        {
            "file": {
                "File attributes": [
                    { "value": 55, "type": { "label": "size-kb", "root": "attribute", "value_type": "long" } },
                    { "value": "iopvu.java", "type": { "label": "path", "root": "attribute", "value_type": "string" } }
                ],
                "type": { "label": "file", "root": "entity" }
            }
        }
    ],
    "user": {
        "User attributes": [
            { "value": "kevin.morrison@typedb.com", "type": { "label": "email", "root": "attribute", "value_type": "string" } },
            { "value": "Kevin Morrison", "type": { "label": "full-name", "root": "attribute", "value_type": "string" } }
        ],
        "type": { "label": "person", "root": "entity" }
    }
}
{
    "permited-files": [  ],
    "user": {
        "User attributes": [
            { "value": "pearle.goodman@typedb.com", "type": { "label": "email", "root": "attribute", "value_type": "string" } },
            { "value": "Pearle Goodman", "type": { "label": "full-name", "root": "attribute", "value_type": "string" } }
        ],
        "type": { "label": "person", "root": "entity" }
    }
}

The order of key/value pairs in a JSON is not guaranteed.

Aggregated Get subquery

To add an aggregated value for the Fetch output, use a Get query with an aggregation modifier.

The returned value is added to the resulted JSON with a subquery label as a key. If there is an empty response (no answer) for the Get subquery, then the value in JSON will be null.

Aggregated Get subquery example
match
$person isa person;
fetch
$person: full-name;
average-file-size: {
    match
    ($person,$acc) isa permission;
    $acc($file) isa access;
    $file isa file, has size-kb $size;
    get $size;
    mean $size;
};
Output example
{
    "average-file-size": null,
    "person": {
        "full-name": [ { "value": "Masako Holley", "type": { "label": "full-name", "root": "attribute", "value_type": "string" } } ],
        "type": { "label": "person", "root": "entity" }
    }
}
{
    "average-file-size": { "value": 444.85714285714283, "value_type": "double" },
    "person": {
        "full-name": [ { "value": "Kevin Morrison", "type": { "label": "full-name", "root": "attribute", "value_type": "string" } } ],
        "type": { "label": "person", "root": "entity" }
    }
}
{
    "average-file-size": { "value": 1231.5, "value_type": "double" },
    "person": {
        "full-name": [ { "value": "Pearle Goodman", "type": { "label": "full-name", "root": "attribute", "value_type": "string" } } ],
        "type": { "label": "person", "root": "entity" }
    }
}

Number of results

For this example, use a database with the IAM schema and sample data loaded.

A Fetch query can return fewer results, than it was matched by its match clause. Due to set semantics, if fetch clause projects not every variable from the match clause, some results might lose their uniqueness and made redundant.

Let’s try to match all entities of the person type that have an full-name attribute, and then project the full-name attribute only:

Number of results example
match
$p isa person, has full-name $n;
fetch $n;
Output example
{ "n": { "value": "Masako Holley", "type": { "label": "full-name", "root": "attribute", "value_type": "string" } } }
{ "n": { "value": "Kevin Morrison", "type": { "label": "full-name", "root": "attribute", "value_type": "string" } } }
{ "n": { "value": "Pearle Goodman", "type": { "label": "full-name", "root": "attribute", "value_type": "string" } } }

The match clause returns pairs of person and its full-name. The fetch clause then returns only the full-name attributes projected into a JSON.

If multiple entities have the same attribute, that is represented in a database as ownership of the very same single attribute. Since we filter the results to have only the $n variable, the results include such a name, but only once. Regardless of how many entities $p own it.

To prevent such deduplication, in this example, you can extend the results to include the $p variable into the projection, since every entity is unique. The results then will include pairs of $p and $n.

Learn more

Learn more about query patterns in TypeDB Academy.

See how to send a Fetch query to TypeDB.