Description
RFC - Executing multiple operations in a query
Today you can define multiple operations in the one query document but the specification states you can only execute one of them at a time. So given :
query A {
hero
{
id
name
friends
{
name
}
}
}
query
droid (id : $droidId ) {
name
}
}
You must indicate which operation to run. See http://facebook.github.io/graphql/October2016/#sec-Executing-Requests
This RFC proposes that we add the ability to run multiple operations at the one time.
Naming the operation
We propose that a list of operations be able to be given to the execution
ExecuteRequest(schema, document, operationNames, variableValues, initialValue)
- Let operations be the result of GetOperations(document, operationNames).
- Let coercedVariableValues be the result of CoerceVariableValues(schema, operation, variableValues).
- For each operation in operations
- If operation is a query operation:
- Return ExecuteQuery(operations, schema, coercedVariableValues, initialValue).
- Otherwise if operation is a mutation operation:
- Return ExecuteMutation(operations, schema, coercedVariableValues, initialValue).
GetOperations(document, operationNames)
- If operationName is null or empty:
- If document contains exactly one operation.
- Return the Operation contained in the document.
- Otherwise produce a query error requiring operationName.
- If document contains exactly one operation.
- Otherwise:
- Let operations be the Operations named operationNames in document.
- If any of the operations are not found, produce a query error.
- Return operations.
Executing each operation
The list of operations will be executed in the order specified. A list of ExecutionResult
s will be produced that represent each operation.
If an operation is a mutation, then the serial execution will be applied to it as per a single operation.
The finaly result will be a single ExecutionResult
that contains the list of ExecutionResult
for each operation executed. This preserves the current expected signature from an graphql execution.
However like subsciptions, the top level data iterm is a proscibed shape, that is list of ExecutionResult
var executionResult = ExecuteRequest(schema,document,["A,"B"], variables)
var listOfResults = executionResult.data
for (result in listOfResults) {
// process each result
}
Operation dependencies
Directive capabilities can create dependencies between the execution of operations.
For example B might depend on A executing first. If the graphql implementation detects such a dependency then it will wait for dependee operation (say A) to complete before executing the dependent operation (say B)
If not dependencies are detected, or the graphql implementation does not allow dependencies, then the operations can be executed in parralel, assuming they are query operations.
Variables given to each operation
The variables that are specified on the original execution will be given to each operation.
The graphql implementation is free to augment those variables with new values as it executes.
New variables will be subject to the same validation rules as the initial variables.
Capabilities unlocked because of multiple operations
Multiple operations allows for more efficient graphql queries. Multiple queries can be sent over slow mobile networks (which preparsed queries make even more efficient) and results can be sent back as soon as thye are obtained
One you have the ability to have multiple operations, you can introduce new capabilities like variable export.
This RFC is not concerned with defining exactly how this happens but having multiple operations is a pre-cursor to such capabilities.
For examaple imagine a query like
query A {
hero
{
id @export(as:"droidId")
name
friends @export(as:"r2d2Friends")
{
name @export(as:"friendNames")
}
}
}
query B($droidId : String!) {
droid (id : $droidId ) {
name
}
}
Output values from operation A would be exported as the variables named "droidId", "r2d2Friends", and "friendNames"
The operation B would be detected to be dependent on operation A and would be executed after operation A completes. It would receive the value of "droidId" as a variable from operation A
Open Questions
The return shape of the results is an open question. Should it be a list (as proposed) or be async iterator in line with subscription operations.
When the returned results are Promise
based then the distinction between list of Promise and async iterator of Promise
is very small and we prefer the simple list.
There is a symmetry between a list of operations as input and a list of results as output.
Current implementations
Sangria has this capability today sangria-graphql.org/learn/#batch-executor and graphql-java has a PR exploring the idea graphql-java/graphql-java#808