Skip to content

[RFC] Executing multiple operations in a query #375

Open
@bbakerman

Description

@bbakerman

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.
  • 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 ExecutionResults 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    👻 Needs ChampionRFC Needs a champion to progress (See CONTRIBUTING.md)💭 Strawman (RFC 0)RFC Stage 0 (See CONTRIBUTING.md)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions