Skip to content

Create single planning phase #53

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
Horusiath opened this issue Jul 7, 2016 · 1 comment
Closed

Create single planning phase #53

Horusiath opened this issue Jul 7, 2016 · 1 comment

Comments

@Horusiath
Copy link
Contributor

Horusiath commented Jul 7, 2016

The major problem we have right now, is that query execution model is not aware of the whole context of execute operation. This is particularly irritating in scenarios such as i.e. constructing SQL queries based on incoming GraphQL query.

Right now going deep into nested relations will either require using lazy collections all the way down (like IQueryable, which is not the case for integration with Relay.js connection data structures) or will cause N+1 selects in resulting SQL queries.

One of the ideas here is to introduce query planning (as presented in graphql/graphql-js#304 ) before starting execution phase.

Why?

Having explicit execution plan before starting the value resolution process gives us some advantages:

  • This could help us, as we have the whole plan prepared up-front and we can make some contextual decisions (such as construction of SQL queries) based on that data.
  • It would be useful in pub/sub scenarios as we could potentially cache execution plans instead of queries and execute them directly, once new value needs to be published. This way we don't need to repeatedly reevaluate whole document AST into result set.
  • Potentially since we've got raw execution tree, we could optimize it.

Why not Document AST?

Well, there are some problems with document AST:

  • It's doesn't preserve any relations to actually executed tree. So you'll need to map it onto schema anyway using tree traversal.
  • It can contain fragments - fragments are not represented in schema. Moreover there are multiple tricks that can be done over over them, that should be optimized in resulting set, ie.:
query {
    users {
        ... userName
        ... userAge
    }
}

fragment userName on User { name }
fragment userName on User { age }

should not iterate over users twice, instead it should be optimized to:

query {
    users {
        name
        age
    }
}

How

We need to introduce an execution phase, in a very raw form the proposal would be:

type Reduce = FieldResolveContext -> obj -> obj
type ExecutionPlan =
    // reducer for leaf types, such as scalars and enums
    | ReduceLeaf * Reduce
    // reducer for selection sets materialized over output object type
    | ReduceSelection of (string * ExecutionPlan) []
    // reducer for list types
    | ReduceCollection of ExecutionPlan
    // reducer for union and interface types
    | ReduceAbstraction of Map<string, ExecutionPlan>

Execution phase itself should be splitted into two: 1) preparation of execution plan 2) evaluating execution plan. Additionally it should be able to extract execution plan separatelly (as mentioned in first two points of "Why?" paragraph.

Notes:

  • How to resolve condintioanlly included/excluded fields without introducing too much dynamism in result set (right now we have pretty good object allocation story thanks to reducing allocations of dynamic data structures like lists in favor of static arrays).
@Horusiath
Copy link
Contributor Author

Closed by #56

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant