Skip to content
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

Expose query plan #322

Open
dnerdy opened this issue Jan 21, 2022 · 10 comments
Open

Expose query plan #322

dnerdy opened this issue Jan 21, 2022 · 10 comments
Labels
internally-reviewed Internally reviewed

Comments

@dnerdy
Copy link
Contributor

dnerdy commented Jan 21, 2022

We have a use case that requires access to the query plan. We use the query plan for side-by-side testing responses when migrating fields between services.

Would y'all be open to adding a method to the V2 engine to return a simplified version of the query plan?

The returned plan would have its own dedicated types so the internals of the resolve package aren't exposed. If desired, the plan could be wired up to the Query Plan tab of the Apollo-maintained graphql-playground (the playground bundled with graphql-go-tools would need to be updated). That way this feature could benefit all graphql-go-tools users.

For reference, this is what the query plan looks like in the Apollo playground:
query-plan

And here's a more detailed plan:

QueryPlan {
  Sequence {
    Fetch(service: "users") {
      {
        user {
          __typename
          id
        }
      }
    },
    Flatten(path: "user") {
      Fetch(service: "classroom") {
        {
          ... on User {
            __typename
            id
          }
        } =>
        {
          ... on User {
            students {
              id
            }    
          }
        }
      },
    },
  },
}

The query plan need not match this format exactly. Aside from side-by-side testing, we have found it useful to have readable query plans during development.

Note that the plan string (in the examples above) is formatted like a GraphQL query document, though in Go it would probably make sense to expose a struct like:

type QueryPlan struct {
    RootNode PlanNode
}

type PlanNode interface {
    PlanNodeKind()
}

type SequenceNode struct {
    Nodes []PlanNode
}

type ParallelNode struct {
    Nodes []PlanNode
}

type FetchNode struct {
    ServiceName: string  // Or this could be the data source URL if we didn't want to support naming data sources
    Operation: string  // e.g. { fieldOne fieldTwo { fieldThree } }
}

type FlattenNode struct {
    Path []string
    Node PlanNode
}

Thoughts?

@jensneuse
Copy link
Member

  1. I have plans to make generated execution plans JSON encodable and decodable.
  2. I also had the idea of having a "pretty" version of the plan, e.g. to show it in the Playground. So if you're also into this, we could work on a pretty printer for the plan package.

@csilvers
Copy link
Contributor

Hi @jensneuse thanks for your quick response. Do you have a timeline for (1), and/or estimates for how long (1) and (2) would take? We're happy to work with you on this in whatever way makes sense, but a timeline would help us know if we need to look into workaround in the meanwhile.

@csilvers
Copy link
Contributor

(One thing (2) will require is threading through the service name, since right now that information is not available in any data that the query plan has access to, that I'm aware of. I don't know if you had thoughts on how you'd like to see that done, as well.)

@jensneuse
Copy link
Member

(One thing (2) will require is threading through the service name, since right now that information is not available in any data that the query plan has access to, that I'm aware of. I don't know if you had thoughts on how you'd like to see that done, as well.)

what do you mean by service name?

@dnerdy
Copy link
Contributor Author

dnerdy commented Jan 21, 2022

In Apollo, data sources have an identifier (which they call "name"). In the query plan we want to know which data source is responsible for which fields. The data source URL could be used to identify a data source, but it would be more convenient if we could associate an identifier with a data source directly.

@dnerdy
Copy link
Contributor Author

dnerdy commented Jan 21, 2022

(This is, for example, service: "users" in the query plans above.)

@jensneuse
Copy link
Member

I think we could give each datasouce a name, which could default to an auto generated value. It would be interesting if you could describe the final "outcome" you'd like to achieve. This way, we could work our way backwards. An idea could be that you create a PR with a markdown or other format to describe the desired outcome. We can iterate on this and implement a solution when we're happy.

@csilvers
Copy link
Contributor

It would be interesting if you could describe the final "outcome" you'd like to achieve.

From a product standpoint, what we need is something that is functionally equivalent to the apollo query plan; the thing that looks like

QueryPlan {
  Sequence {
    Fetch(service: "users") {
      {
        user {
          __typename
          id
        }
      }
    },
    Flatten(path: "user") {
      Fetch(service: "classroom") {
        {
          ... on User {
            __typename
            id
          }
        } =>
        {
          ... on User {
            students {
              id
            }    
          }
        }
      },
    },
  },
}

It's easiest for us if this is presented using Go data structures, so we don't have to write a parser for it, but even if just the string form were provided it would be good enough.

We do need all the information in it though, in particular the "path" and "service" annotations.

@csilvers
Copy link
Contributor

@dnerdy wrote up a proof of concept of what it could look like to pass a "Name" through to the query planner, so it could fill out the service: <name> directive. Note this is just for the graphql datasource.

diff --git a/pkg/engine/datasource/graphql_datasource/graphql_datasource.go b/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
index 1b307604..4d711d2b 100644
--- a/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
+++ b/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
@@ -207,8 +207,9 @@ func ConfigJson(config Configuration) json.RawMessage {
 }
 
 type FederationConfiguration struct {
-	Enabled    bool
-	ServiceSDL string
+	Enabled     bool
+	ServiceSDL  string
+	ServiceName string // For pretty printed query plan info
 }
 
 type SubscriptionConfiguration struct {
@@ -275,7 +276,8 @@ func (p *Planner) ConfigureFetch() plan.FetchConfiguration {
 	return plan.FetchConfiguration{
 		Input: string(input),
 		DataSource: &Source{
-			httpClient: p.fetchClient,
+			httpClient:  p.fetchClient,
+			ServiceName: p.config.Federation.ServiceName,
 		},
 		Variables:            p.variables,
 		DisallowSingleFlight: p.disallowSingleFlight,
@@ -283,7 +285,7 @@ func (p *Planner) ConfigureFetch() plan.FetchConfiguration {
 			ExtractGraphqlResponse:    true,
 			ExtractFederationEntities: p.extractEntities,
 		},
-		BatchConfig:      batchConfig,
+		BatchConfig: batchConfig,
 	}
 }
 
@@ -1145,7 +1147,8 @@ func (f *Factory) Planner(ctx context.Context) plan.DataSourcePlanner {
 }
 
 type Source struct {
-	httpClient *http.Client
+	httpClient  *http.Client
+	ServiceName string
 }
 
 func (s *Source) Load(ctx context.Context, input []byte, writer io.Writer) (err error) {

And when creating the datasource, the client could specify the ServiceName field.

@dnerdy
Copy link
Contributor Author

dnerdy commented Jan 26, 2022

I'd be happy to continue driving this forward. @jensneuse do you like the format of the Apollo query plan? A benefit of the format is that it's very similar to an operation document (aside from the => separating the representation keys sent to an upstream and the fields selected from the upstream). Also, the playground supports syntax highlighting for the format.

I'm not sure what the ideal format would be for other data source types.

I'd be happy to start up a doc to continue the conversation, as you suggested. Or, we could toss around ideas in this thread. Either works for me.

@StarpTech StarpTech added the internally-reviewed Internally reviewed label Mar 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
internally-reviewed Internally reviewed
Projects
None yet
Development

No branches or pull requests

4 participants