Executing multiple queries concurrently
Multiple queries can be combined together, and executed as a single operation, reusing their state and their data.
This is different from query batching, in which the GraphQL server also executes multiple queries in a single request, but those queries are merely executed one after the other, independently from each other.
This feature improves performance. Instead of executing queries independenty in different requests (so that we first execute an operation against the GraphQL server, then wait for its response, and then use that result to perform another operation), we can execute them together, thus avoiding the latency from the several requests.
Multiple Query Execution also allow us to better organize our GraphQL queries, splitting them into logical units that depend on each other, and that are conditionally executed based on the result from a previous operation.
How to use multiple query execution
Let's suppose we want to search all posts which mention the name of the logged-in user. Normally, we would need two queries to accomplish this:
We first retrieve the user's name
:
...and then, having executed the first query, we can pass the retrieved user's name
as variable $search
to perform the search in a second query:
Multiple Query Execution simplifies this process, allowing us to retrieve all data and execute all required logic in a single request:
Multiple Query Execution is attained with the use of these special directives:
@depends
(operation directive): have an operation (whether aquery
ormutation
) indicate what other operations must be executed before@export
(field directive): export some field value from one operation, to inject it as an input to some field in another operation@deferredExport
(field directive): Similar to@export
but to be used with Multi-Field Directives.
In addition, directives @include
and @skip
are also made available as operation directives (they are normally only field directives), and these can be used to conditionally execute an operation if it satisfies some condition.
The GraphQL server will create the list of operations to load and execute, retrieving them from each @depends(on: ...)
, and will export the values from any field containing @export
as a dynamic variable (with name defined under argument as
) to be input in any subsequent operation.
Combining these directives, we are able to split any complex functionality into intermediate steps, alternating query
and mutation
operations, add their dependencies in the required order, and execute them all in a single request by defining the outermost operation in ?operationName=...
(in the example above, that will be ?operationName=GetPostsContainingString
).
@depends
Defining the operations to load and execute via
When the GraphQL document contains multiple operations, we indicate to the server which one to execute via URL param ?operationName=...
; otherwise, the last operation will be executed.
Starting from this initial operation, the server will collect all operations to execute, which are defined by adding directive depends(on: [...])
, and execute them in the corresponding order respecting the dependencies.
Directive arg operations
receives an array of operation names ([String]
), or we can also provide a single operation name (String
).
In this query, we pass ?operationName=Four
, and the executed operations (whether query
or mutation
) will be ["One", "Two", "Three", "Four"]
:
@export
Sharing data across queries via
Directive @export
exports the value of a field (or set of fields) into a dynamic variable, to be used as input in some field from another query.
For instance, in this query we export the logged-in user's name, and use this value to search for posts containing this string (please notice that variable $loggedInUserName
, because it is dynamic, does not need be defined in operation FindPosts
):
Dynamic variable outputs
@export
can produce 6 different outputs, based on a combination of:
- The value of the
type
argument (eitherSINGLE
,LIST
orDICTIONARY
) - If the directive is applied to a single field, or to multiple fields (via the Multi-Field Directives module)
The 6 possible outputs then are:
SINGLE
type:- Single field
- Multi-field
LIST
type:- Single field
- Multi-field
DICTIONARY
type:- Single field
- Multi-field
SINGLE
type / Single field
The output is a single value when passing param type: SINGLE
(which is set as the default value).
In this query:
...the dynamic variable $postTitle
will have value:
Please notice that if SINGLE
is applied over an array of entities, then the value for the last entity is the one that is exported.
In this query:
...the dynamic variable $postTitle
will have the value for post with ID 5
:
SINGLE
type / Multi-field
If @export
is applied on several fields (by adding param affectAdditionalFieldsUnderPos
provided by the Multi-Field Directives module), then the value that is set on the dynamic variable is a dictionary of { key: field alias, value: field value }
(of type JSONObject
).
This query:
...exports dynamic variable $postData
with value:
LIST
type / Single field
The dynamic variable will contain an array with the field value from all the queried entities (from the enclosing field), by passing param type: LIST
.
When running this query (in which queried entities are posts with ID 1
and 5
):
...the dynamic variable $postTitles
will have value:
LIST
type / Multi-field
We obtain an array of dictionaries (of type JSONObject
), each containing the values of the fields on which the directive is applied.
This query:
...exports dynamic variable $postsData
with value:
DICTIONARY
type / Single field
The dynamic variable will contain a dictionary (of type JSONObject
) with the ID from the queries entity as key, and the field values as value, by passing param type: DICTIONARY
.
This query:
...exports dynamic variable $postIDTitles
with value:
DICTIONARY
type / Multi-field
In this combination, we export a dictionary of dictionaries: { key: entity ID, value: { key: field alias, value: field value } }
(using a type JSONObject
that will contain entries of type JSONObject
).
This query:
...exports dynamic variable $postsIDProperties
with value:
Conditional execution of operations
When Multiple Query Execution is enabled, directives @include
and @skip
are also available as operation directives, and these can be use to conditionally execute an operation if it satisfies some condition.
For instance, in this query, operation CheckIfPostExists
exports a dynamic variable $postExists
and, only if its value is true
, will mutation ExecuteOnlyIfPostExists
be executed:
Exporting values when iterating an array or JSON object
@export
respects the cardinality from any encompassing meta-directive.
In particular, whenever @export
is nested below a meta-directive that iterates on array items or JSON object properties (i.e. @underEachArrayItem
and @underEachJSONObjectProperty
), then the exported value will be an array.
This query:
...produces $contentAttributes
with value:
In contrast, the same query that accesses a specific item in the array instead of iterating over all of them (by replacing @underEachArrayItem
with @underArrayItem(index: 0)
) will export a single value.
This query:
...produces $contentAttributes
with value:
Directive execution order
If there are other directives before @export
, the exported value will reflect the modifications by those previous directives.
For instance, in this query, depending on @export
taking place before or after @strUpperCase
, the result will be different:
Producing:
Multi-Field Directives
When the Multi-Field Directives feature is enabled and we export the value of multiple fields into a dictionary, use @deferredExport
instead of @export
to guarantee that all directives from every involved fields have been executed before exporting the field's value.
For instance, in this query, the first field has directive @strUpperCase
applied to it, and the second has @titleCase
. When executing @deferredExport
, the exported value will have these directives applied:
Producing:
GraphQL spec
This functionality is currently not part of the GraphQL spec, but it has been requested: