Skip to content

Define 'execution' as in 'before execution begins' #1174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions spec/Section 3 -- Type System.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ GraphQL supports two abstract types: interfaces and unions.
An `Interface` defines a list of fields; `Object` types and other Interface
types which implement this Interface are guaranteed to implement those fields.
Whenever a field claims it will return an Interface type, it will return a valid
implementing Object type during execution.
implementing Object type during _execution_.

A `Union` defines a list of possible types; similar to interfaces, whenever the
type system claims a union will be returned, one of the possible types will be
Expand Down Expand Up @@ -505,7 +505,7 @@ information on the serialization of scalars in common JSON and other formats.
If a GraphQL service expects a scalar type as input to an argument, coercion is
observable and the rules must be well defined. If an input value does not match
a coercion rule, a _request error_ must be raised (input values are validated
before execution begins).
before _execution_ begins).

GraphQL has different constant literals to represent integer and floating-point
input values, and coercion rules may apply differently depending on which type
Expand Down Expand Up @@ -810,7 +810,7 @@ And will yield the subset of each object type queried:
**Field Ordering**

When querying an Object, the resulting mapping of fields are conceptually
ordered in the same order in which they were encountered during execution,
ordered in the same order in which they were encountered during _execution_,
excluding fragments for which the type does not apply and fields or fragments
that are skipped via `@skip` or `@include` directives. This ordering is
correctly produced when using the {CollectFields()} algorithm.
Expand Down Expand Up @@ -2058,8 +2058,8 @@ directive @example on

Directives can also be used to annotate the type system definition language as
well, which can be a useful tool for supplying additional metadata in order to
generate GraphQL execution services, produce client generated runtime code, or
many other useful extensions of the GraphQL semantics.
generate GraphQL services, produce client generated runtime code, or many other
useful extensions of the GraphQL semantics.

In this example, the directive `@example` annotates field and argument
definitions:
Expand Down Expand Up @@ -2122,7 +2122,7 @@ directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
```

The `@skip` _built-in directive_ may be provided for fields, fragment spreads,
and inline fragments, and allows for conditional exclusion during execution as
and inline fragments, and allows for conditional exclusion during _execution_ as
described by the `if` argument.

In this example `experimentalField` will only be queried if the variable
Expand All @@ -2142,7 +2142,7 @@ directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

The `@include` _built-in directive_ may be provided for fields, fragment
spreads, and inline fragments, and allows for conditional inclusion during
execution as described by the `if` argument.
_execution_ as described by the `if` argument.

In this example `experimentalField` will only be queried if the variable
`$someTest` has the value `true`
Expand Down
2 changes: 1 addition & 1 deletion spec/Section 4 -- Introspection.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ GraphQL supports type name introspection within any _selection set_ in an
operation, with the single exception of selections at the root of a subscription
operation. Type name introspection is accomplished via the meta-field
`__typename: String!` on any Object, Interface, or Union. It returns the name of
the concrete Object type at that point during execution.
the concrete Object type at that point during _execution_.

This is most often used when querying against Interface or Union types to
identify which actual Object type of the possible types has been returned.
Expand Down
7 changes: 4 additions & 3 deletions spec/Section 5 -- Validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ given GraphQL schema.
An invalid request is still technically executable, and will always produce a
stable result as defined by the algorithms in the Execution section, however
that result may be ambiguous, surprising, or unexpected relative to a request
containing validation errors, so execution should only occur for valid requests.
containing validation errors, so _execution_ should only occur for valid
requests.

Typically validation is performed in the context of a request immediately before
execution, however a GraphQL service may execute a request without explicitly
Expand Down Expand Up @@ -108,7 +109,7 @@ input FindDogInput {

GraphQL execution will only consider the executable definitions Operation and
Fragment. Type system definitions and extensions are not executable, and are not
considered during execution.
considered during _execution_.

To avoid ambiguity, a document containing {TypeSystemDefinitionOrExtension} is
invalid for execution.
Expand Down Expand Up @@ -579,7 +580,7 @@ type that is either an Object, Interface or Union type.
**Explanatory Text**

If multiple field selections with the same _response name_ are encountered
during execution, the field and arguments to execute and the resulting value
during _execution_, the field and arguments to execute and the resulting value
should be unambiguous. Therefore any two field selections which might both be
encountered for the same object are only valid if they are equivalent.

Expand Down
118 changes: 70 additions & 48 deletions spec/Section 6 -- Execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ A GraphQL service generates a response from a request via execution.
- {extensions} (optional): A map reserved for implementation-specific additional
information.

Given this information, the result of {ExecuteRequest(schema, document,
operationName, variableValues, initialValue)} produces the response, to be
formatted according to the Response section below.
Given this information, the result of {Request(schema, document, operationName,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets think of how we can keep the algorithm names with action verbs. Ideally this has an "execute" or "process" or something here.

Considering the prose above describes this as "request via execution" then "execute request" does seem like what a reader would expect

variableValues, initialValue)} produces the response, to be formatted according
to the Response section below.

Implementations should not add additional properties to a _request_, which may
conflict with future editions of the GraphQL specification. Instead,
Expand All @@ -39,27 +39,44 @@ and have no effect on the observable execution, validation, or response of a
GraphQL document. Descriptions and comments on executable documents MAY be used
for non-observable purposes, such as logging and other developer tools.

## Executing Requests
## Processing Requests
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the intermixing of "execute" and "process" is ultimately more confusing than helpful, despite it being technically more accurate. I think it's the result of the topline section being "Execution" and then within we define execution as a sub-step of "Processing". It's a much larger change to retitle the entire section.

I wonder if instead we could define phases of execution?


To execute a request, the executor must have a parsed {Document} and a selected
<a name="#sec-Executing-Requests">
<!-- Legacy link, this section was previously titled "Executing Requests" -->
</a>

To process a request, the executor must have a parsed {Document} and a selected
operation name to run if the document defines multiple operations, otherwise the
document is expected to only contain a single operation. The result of the
request is determined by the result of executing this operation according to the
"Executing Operations” section below.
request is determined by the result of performing this operation according to
the "Performing Operations” section below.

The {Request()} algorithm contains the preamble for _execution_, handling
concerns such as determining the operation and coercing the inputs, before
passing the request on to the relevant algorithm for the operation's type which
then performs any other necessary preliminary steps (for example establishing
the source event stream for subscription operations) and then initiates
_execution_.

Note: An error raised before _execution_ begins will typically be a _request
error_, and once _execution_ begins will typically be an _execution error_.

:: We define _execution_ as the process of executing the operation's _root
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here is where we could define "during execution" with something more specific to avoid the overloaded term.

These names might not be ideal, but it seems like we have two phases to execution.

  • First there is a pre-execution preparation step where we coerce variables and ensure we have a valid type and object to begin execution with

  • Then there is the actual collection and execution of fields (though even here it's a bit arbitrary that "execution" starts as we begin collecting fields not when we start the first ExecuteCollectedFields. Perhaps we could call this phase "field execution" or "selection execution"?

I appreciate what we're trying to do here is more clearly define what scenarios lead to an error result with no data entry vs one with a partial data entry. We started with "field error" -- which while unclear for other reasons -- at least evaded this issue. They were clearly errors which occurred at a per-field level. Maybe we just need to do a half-step back to that?

selection set_ through {ExecuteRootSelectionSet()}, and hence _execution_ begins
when {ExecuteRootSelectionSet()} is called for the first time in a request.

ExecuteRequest(schema, document, operationName, variableValues, initialValue):
Request(schema, document, operationName, variableValues, initialValue):

- Let {operation} be the result of {GetOperation(document, operationName)}.
- Let {coercedVariableValues} be the result of {CoerceVariableValues(schema,
operation, variableValues)}.
- If {operation} is a query operation:
- Return {ExecuteQuery(operation, schema, coercedVariableValues,
initialValue)}.
- Return {Query(operation, schema, coercedVariableValues, initialValue)}.
- Otherwise if {operation} is a mutation operation:
- Return {ExecuteMutation(operation, schema, coercedVariableValues,
initialValue)}.
- Return {Mutation(operation, schema, coercedVariableValues, initialValue)}.
- Otherwise if {operation} is a subscription operation:
- Return {Subscribe(operation, schema, coercedVariableValues, initialValue)}.
- Return {Subscription(operation, schema, coercedVariableValues,
initialValue)}.

GetOperation(document, operationName):

Expand All @@ -74,27 +91,28 @@ GetOperation(document, operationName):

### Validating Requests

As explained in the Validation section, only requests which pass all validation
rules should be executed. If validation errors are known, they should be
reported in the list of "errors" in the response and the request must fail
without execution.
As explained in the Validation section, only operations from documents which
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice cleanup here

pass all validation rules should be executed. If validation errors are known,
they should be reported in the list of "errors" in the response and the request
must fail without execution.

Typically validation is performed in the context of a request immediately before
execution, however a GraphQL service may execute a request without immediately
validating it if that exact same request is known to have been validated before.
A GraphQL service should only execute requests which _at some point_ were known
to be free of any validation errors, and have since not changed.
calling {Request()}, however a GraphQL service may process a request without
immediately validating the document if that exact same document is known to have
been validated before. A GraphQL service should only execute operations which
_at some point_ were known to be free of any validation errors, and have since
not changed.

For example: the request may be validated during development, provided it does
not later change, or a service may validate a request once and memoize the
result to avoid validating the same request again in the future.
For example: the document may be validated during development, provided it does
not later change, or a service may validate a document once and memoize the
result to avoid validating the same document again in the future.

### Coercing Variable Values

If the operation has defined any variables, then the values for those variables
need to be coerced using the input coercion rules of variable's declared type.
If a _request error_ is encountered during input coercion of variable values,
then the operation fails without execution.
then the request fails without _execution_.

CoerceVariableValues(schema, operation, variableValues):

Expand Down Expand Up @@ -131,7 +149,11 @@ CoerceVariableValues(schema, operation, variableValues):

Note: This algorithm is very similar to {CoerceArgumentValues()}.

## Executing Operations
## Performing Operations

<a name="#sec-Executing-Operations">
<!-- Legacy link, this section was previously titled "Executing Operations" -->
</a>

The type system, as described in the "Type System" section of the spec, must
provide a query root operation type. If mutations or subscriptions are
Expand All @@ -144,9 +166,9 @@ If the operation is a query, the result of the operation is the result of
executing the operation’s _root selection set_ with the query root operation
type.

An initial value may be provided when executing a query operation.
An initial value may be provided when performing a query operation.

ExecuteQuery(query, schema, variableValues, initialValue):
Query(query, schema, variableValues, initialValue):

- Let {queryType} be the root Query type in {schema}.
- Assert: {queryType} is an Object type.
Expand All @@ -164,7 +186,7 @@ It is expected that the top level fields in a mutation operation perform
side-effects on the underlying data system. Serial execution of the provided
mutations ensures against race conditions during these side-effects.

ExecuteMutation(mutation, schema, variableValues, initialValue):
Mutation(mutation, schema, variableValues, initialValue):

- Let {mutationType} be the root Mutation type in {schema}.
- Assert: {mutationType} is an Object type.
Expand All @@ -176,12 +198,13 @@ ExecuteMutation(mutation, schema, variableValues, initialValue):

If the operation is a subscription, the result is an _event stream_ called the
_response stream_ where each event in the event stream is the result of
executing the operation for each new event on an underlying _source stream_.
executing the operation’s _root selection set_ for each new event on an
underlying _source stream_.

Executing a subscription operation creates a persistent function on the service
Performing a subscription operation creates a persistent function on the service
that maps an underlying _source stream_ to a returned _response stream_.

Subscribe(subscription, schema, variableValues, initialValue):
Subscription(subscription, schema, variableValues, initialValue):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is admittedly the weirdest. Regardless of how we net-out on naming, we should rethink the terms used for the phases of subscription


- Let {sourceStream} be the result of running
{CreateSourceEventStream(subscription, schema, variableValues, initialValue)}.
Expand All @@ -190,9 +213,9 @@ Subscribe(subscription, schema, variableValues, initialValue):
variableValues)}.
- Return {responseStream}.

Note: In a large-scale subscription system, the {Subscribe()} and
{ExecuteSubscriptionEvent()} algorithms may be run on separate services to
maintain predictable scaling properties. See the section below on Supporting
Note: In a large-scale subscription system, the {Subscription()} and
{SubscriptionEvent()} algorithms may be run on separate services to maintain
predictable scaling properties. See the section below on Supporting
Subscriptions at Scale.

As an example, consider a chat application. To subscribe to new messages posted
Expand Down Expand Up @@ -313,8 +336,7 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues):
- Let {responseStream} be a new _event stream_.
- When {sourceStream} emits {sourceValue}:
- Let {executionResult} be the result of running
{ExecuteSubscriptionEvent(subscription, schema, variableValues,
sourceValue)}.
{SubscriptionEvent(subscription, schema, variableValues, sourceValue)}.
- If internal {error} was raised:
- Cancel {sourceStream}.
- Complete {responseStream} with {error}.
Expand All @@ -328,21 +350,21 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues):
- Complete {responseStream} normally.
- Return {responseStream}.

Note: Since {ExecuteSubscriptionEvent()} handles all _execution error_, and
_request error_ only occur during {CreateSourceEventStream()}, the only
remaining error condition handled from {ExecuteSubscriptionEvent()} are internal
exceptional errors not described by this specification.
Note: Since {SubscriptionEvent()} handles all _execution error_, and _request
error_ only occur during {CreateSourceEventStream()}, the only remaining error
condition handled from {SubscriptionEvent()} are internal exceptional errors not
described by this specification.

ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue):
SubscriptionEvent(subscription, schema, variableValues, initialValue):

- Let {subscriptionType} be the root Subscription type in {schema}.
- Assert: {subscriptionType} is an Object type.
- Let {rootSelectionSet} be the _root selection set_ in {subscription}.
- Return {ExecuteRootSelectionSet(variableValues, initialValue,
subscriptionType, rootSelectionSet, "normal")}.

Note: The {ExecuteSubscriptionEvent()} algorithm is intentionally similar to
{ExecuteQuery()} since this is how each event result is produced.
Note: The {SubscriptionEvent()} algorithm is intentionally similar to {Query()}
since this is how each event result is produced.

#### Unsubscribe

Expand Down Expand Up @@ -638,7 +660,7 @@ A valid GraphQL executor can resolve the four fields in whatever order it chose
(however of course `birthday` must be resolved before `month`, and `address`
before `street`).

When executing a mutation, the selections in the top most selection set will be
When performing a mutation, the selections in the top most selection set will be
executed in serial order, starting with the first appearing field textually.

When executing a collected fields map serially, the executor must consider each
Expand Down Expand Up @@ -788,9 +810,9 @@ CoerceArgumentValues(objectType, field, variableValues):
Any _request error_ raised as a result of input coercion during
{CoerceArgumentValues()} should be treated instead as an _execution error_.

Note: Variable values are not coerced because they are expected to be coerced
before executing the operation in {CoerceVariableValues()}, and valid operations
must only allow usage of variables of appropriate types.
Note: Variable values are not coerced because they are expected to be coerced by
{CoerceVariableValues()} before _execution_ begins, and valid operations must
only allow usage of variables of appropriate types.

Note: Implementations are encouraged to optimize the coercion of an argument's
default value by doing so only once and caching the resulting coerced value.
Expand Down
Loading