From fe24f2bfd5de1af07d26665f25600738b9355c6e Mon Sep 17 00:00:00 2001 From: Ludwig Date: Mon, 25 Nov 2024 10:23:46 +0100 Subject: [PATCH] feat: propagate operation name (#993) Co-authored-by: spetrunin --- pkg/astvisitor/visitor.go | 2 +- .../graphql_datasource/graphql_datasource.go | 28 + .../graphql_datasource_federation_test.go | 627 +++++++++++++++++- v2/pkg/engine/plan/configuration.go | 5 +- v2/pkg/engine/plan/configuration_visitor.go | 12 +- .../engine/plan/datasource_configuration.go | 9 +- v2/pkg/engine/plan/planner.go | 2 +- v2/pkg/engine/plan/planner_configuration.go | 7 + 8 files changed, 680 insertions(+), 12 deletions(-) diff --git a/pkg/astvisitor/visitor.go b/pkg/astvisitor/visitor.go index b9deef9da..86c05d28b 100644 --- a/pkg/astvisitor/visitor.go +++ b/pkg/astvisitor/visitor.go @@ -144,7 +144,7 @@ type ( EnterFragmentSpreadVisitor LeaveFragmentSpreadVisitor } - // EnterFragmentSpreadVisitor is the callback when the walker enters an inline framgnet + // EnterInlineFragmentVisitor is the callback when the walker enters an inline fragment EnterInlineFragmentVisitor interface { // EnterInlineFragment gets called when the walker enters an inline fragment // ref is the reference to the selection set on the AST diff --git a/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go b/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go index 3f20aaf02..e2436bfd4 100644 --- a/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go +++ b/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go @@ -8,6 +8,8 @@ import ( "net/http" "regexp" "slices" + "strconv" + "strings" "sync" "time" @@ -398,14 +400,40 @@ func (p *Planner[T]) ConfigureSubscription() plan.SubscriptionConfiguration { } } +func (p *Planner[T]) buildUpstreamOperationName(ref int) string { + operationName := p.visitor.Operation.OperationDefinitionNameBytes(ref) + if len(operationName) == 0 { + return "" + } + + fetchID := strconv.Itoa(p.dataSourcePlannerConfig.FetchID) + + builder := strings.Builder{} + builder.Grow(len(operationName) + len(p.dataSourceConfig.Name()) + len(fetchID) + 4) // 4 is for delimiters "__" + + builder.Write(operationName) + builder.WriteString("__" + p.dataSourceConfig.Name() + "__" + fetchID) + + return builder.String() +} + func (p *Planner[T]) EnterOperationDefinition(ref int) { operationType := p.visitor.Operation.OperationDefinitions[ref].OperationType if p.dataSourcePlannerConfig.IsNested { operationType = ast.OperationTypeQuery } + definition := p.upstreamOperation.AddOperationDefinitionToRootNodes(ast.OperationDefinition{ OperationType: operationType, }) + + if p.dataSourcePlannerConfig.Options.EnableOperationNamePropagation { + operation := p.buildUpstreamOperationName(ref) + if operation != "" { + p.upstreamOperation.OperationDefinitions[definition.Ref].Name = p.upstreamOperation.Input.AppendInputString(operation) + } + } + p.nodes = append(p.nodes, definition) } diff --git a/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go b/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go index 88c2e2f4d..4f40b7627 100644 --- a/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go +++ b/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go @@ -1592,6 +1592,373 @@ func TestGraphQLDataSourceFederation(t *testing.T) { )) }) + t.Run("query having a fetch after fetch with composite key and operation propagation", func(t *testing.T) { + t.Run("run", RunTest( + definition, + ` + query CompositeKey { + user { + foo + } + otherUser { + foo + } + }`, + "CompositeKey", + &plan.SynchronousResponsePlan{ + Response: &resolve.GraphQLResponse{ + Data: &resolve.Object{ + Fetches: []resolve.Fetch{ + &resolve.SingleFetch{ + FetchDependencies: resolve.FetchDependencies{ + FetchID: 0, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://service-a","body":{"query":"query CompositeKey__service-a__0 {user {__typename account {id} id} otherUser {__typename account {id} id}}"}}`, + DataSource: &Source{}, + PostProcessing: DefaultPostProcessingConfiguration, + }, + }, + }, + Fields: []*resolve.Field{ + { + Name: []byte("user"), + Value: &resolve.Object{ + Fetches: []resolve.Fetch{ + &resolve.SingleFetch{ + FetchDependencies: resolve.FetchDependencies{ + FetchID: 1, + DependsOnFetchIDs: []int{0}, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://service-b","body":{"query":"query CompositeKey__service-b__1($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename foo}}}","variables":{"representations":[$$0$$]}}}`, + Variables: resolve.NewVariables( + &resolve.ResolvableObjectVariable{ + Renderer: resolve.NewGraphQLVariableResolveRenderer(&resolve.Object{ + Nullable: true, + Fields: []*resolve.Field{ + { + Name: []byte("__typename"), + Value: &resolve.String{ + Path: []string{"__typename"}, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + { + Name: []byte("account"), + Value: &resolve.Object{ + Path: []string{"account"}, + Fields: []*resolve.Field{ + { + Name: []byte("id"), + Value: &resolve.Scalar{ + Path: []string{"id"}, + }, + }, + }, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + { + Name: []byte("id"), + Value: &resolve.Scalar{ + Path: []string{"id"}, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + }, + }), + }, + ), + DataSource: &Source{}, + RequiresEntityFetch: true, + PostProcessing: SingleEntityPostProcessingConfiguration, + SetTemplateOutputToNullOnVariableNull: true, + }, + }, + }, + Path: []string{"user"}, + Fields: []*resolve.Field{ + { + Name: []byte("foo"), + Value: &resolve.Boolean{ + Path: []string{"foo"}, + }, + }, + }, + }, + }, + { + Name: []byte("otherUser"), + Value: &resolve.Object{ + Fetches: []resolve.Fetch{ + &resolve.SingleFetch{ + FetchDependencies: resolve.FetchDependencies{ + FetchID: 2, + DependsOnFetchIDs: []int{0}, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://service-b","body":{"query":"query CompositeKey__service-b__2($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename foo}}}","variables":{"representations":[$$0$$]}}}`, + Variables: resolve.NewVariables( + &resolve.ResolvableObjectVariable{ + Renderer: resolve.NewGraphQLVariableResolveRenderer(&resolve.Object{ + Nullable: true, + Fields: []*resolve.Field{ + { + Name: []byte("__typename"), + Value: &resolve.String{ + Path: []string{"__typename"}, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + { + Name: []byte("account"), + Value: &resolve.Object{ + Path: []string{"account"}, + Fields: []*resolve.Field{ + { + Name: []byte("id"), + Value: &resolve.Scalar{ + Path: []string{"id"}, + }, + }, + }, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + { + Name: []byte("id"), + Value: &resolve.Scalar{ + Path: []string{"id"}, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + }, + }), + }, + ), + DataSource: &Source{}, + RequiresEntityFetch: true, + PostProcessing: SingleEntityPostProcessingConfiguration, + SetTemplateOutputToNullOnVariableNull: true, + }, + }, + }, + Path: []string{"otherUser"}, + Fields: []*resolve.Field{ + { + Name: []byte("foo"), + Value: &resolve.Boolean{ + Path: []string{"foo"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + plan.Configuration{ + Logger: nil, + DefaultFlushIntervalMillis: 0, + DataSources: []plan.DataSource{ + subgraphADatasourceConfiguration, + subgraphBDatasourceConfiguration, + }, + DisableResolveFieldPositions: true, + DisableIncludeInfo: true, + EnableOperationNamePropagation: true, + }, + )) + }) + t.Run("query with operation propagation but without operation name", func(t *testing.T) { + t.Run("run", RunTest( + definition, + ` + query { + user { + foo + } + otherUser { + foo + } + }`, + "", + &plan.SynchronousResponsePlan{ + Response: &resolve.GraphQLResponse{ + Data: &resolve.Object{ + Fetches: []resolve.Fetch{ + &resolve.SingleFetch{ + FetchDependencies: resolve.FetchDependencies{ + FetchID: 0, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://service-a","body":{"query":"{user {__typename account {id} id} otherUser {__typename account {id} id}}"}}`, + DataSource: &Source{}, + PostProcessing: DefaultPostProcessingConfiguration, + }, + }, + }, + Fields: []*resolve.Field{ + { + Name: []byte("user"), + Value: &resolve.Object{ + Fetches: []resolve.Fetch{ + &resolve.SingleFetch{ + FetchDependencies: resolve.FetchDependencies{ + FetchID: 1, + DependsOnFetchIDs: []int{0}, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://service-b","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename foo}}}","variables":{"representations":[$$0$$]}}}`, + Variables: resolve.NewVariables( + &resolve.ResolvableObjectVariable{ + Renderer: resolve.NewGraphQLVariableResolveRenderer(&resolve.Object{ + Nullable: true, + Fields: []*resolve.Field{ + { + Name: []byte("__typename"), + Value: &resolve.String{ + Path: []string{"__typename"}, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + { + Name: []byte("account"), + Value: &resolve.Object{ + Path: []string{"account"}, + Fields: []*resolve.Field{ + { + Name: []byte("id"), + Value: &resolve.Scalar{ + Path: []string{"id"}, + }, + }, + }, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + { + Name: []byte("id"), + Value: &resolve.Scalar{ + Path: []string{"id"}, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + }, + }), + }, + ), + DataSource: &Source{}, + RequiresEntityFetch: true, + PostProcessing: SingleEntityPostProcessingConfiguration, + SetTemplateOutputToNullOnVariableNull: true, + }, + }, + }, + Path: []string{"user"}, + Fields: []*resolve.Field{ + { + Name: []byte("foo"), + Value: &resolve.Boolean{ + Path: []string{"foo"}, + }, + }, + }, + }, + }, + { + Name: []byte("otherUser"), + Value: &resolve.Object{ + Fetches: []resolve.Fetch{ + &resolve.SingleFetch{ + FetchDependencies: resolve.FetchDependencies{ + FetchID: 2, + DependsOnFetchIDs: []int{0}, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://service-b","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename foo}}}","variables":{"representations":[$$0$$]}}}`, + Variables: resolve.NewVariables( + &resolve.ResolvableObjectVariable{ + Renderer: resolve.NewGraphQLVariableResolveRenderer(&resolve.Object{ + Nullable: true, + Fields: []*resolve.Field{ + { + Name: []byte("__typename"), + Value: &resolve.String{ + Path: []string{"__typename"}, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + { + Name: []byte("account"), + Value: &resolve.Object{ + Path: []string{"account"}, + Fields: []*resolve.Field{ + { + Name: []byte("id"), + Value: &resolve.Scalar{ + Path: []string{"id"}, + }, + }, + }, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + { + Name: []byte("id"), + Value: &resolve.Scalar{ + Path: []string{"id"}, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + }, + }), + }, + ), + DataSource: &Source{}, + RequiresEntityFetch: true, + PostProcessing: SingleEntityPostProcessingConfiguration, + SetTemplateOutputToNullOnVariableNull: true, + }, + }, + }, + Path: []string{"otherUser"}, + Fields: []*resolve.Field{ + { + Name: []byte("foo"), + Value: &resolve.Boolean{ + Path: []string{"foo"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + plan.Configuration{ + Logger: nil, + DefaultFlushIntervalMillis: 0, + DataSources: []plan.DataSource{ + subgraphADatasourceConfiguration, + subgraphBDatasourceConfiguration, + }, + DisableResolveFieldPositions: true, + DisableIncludeInfo: true, + EnableOperationNamePropagation: true, + }, + )) + }) + t.Run("query without nested key object fields", func(t *testing.T) { t.Run("run", RunTest( definition, @@ -8979,6 +9346,7 @@ func TestGraphQLDataSourceFederation(t *testing.T) { moderatorID: ID! subject: String! title: String! + address: Address! } union Account = User | Admin | Moderator @@ -8991,6 +9359,11 @@ func TestGraphQLDataSourceFederation(t *testing.T) { accounts: [Account!] nodes: [Node!] } + + type Address { + id: ID! + zip: String! + } ` firstSubgraphSDL := ` @@ -9019,6 +9392,11 @@ func TestGraphQLDataSourceFederation(t *testing.T) { accounts: [Account!] nodes: [Node!] } + + type Address @key(fields: "id") { + id: ID! + zip: String! + } ` firstDatasourceConfiguration := mustDataSourceConfiguration( @@ -9043,6 +9421,10 @@ func TestGraphQLDataSourceFederation(t *testing.T) { TypeName: "Moderator", FieldNames: []string{"moderatorID"}, }, + { + TypeName: "Address", + FieldNames: []string{"id", "zip"}, + }, }, ChildNodes: []plan.TypeField{ { @@ -9064,6 +9446,10 @@ func TestGraphQLDataSourceFederation(t *testing.T) { TypeName: "Moderator", SelectionSet: "moderatorID", }, + { + TypeName: "Address", + SelectionSet: "id", + }, }, }, }, @@ -9145,6 +9531,11 @@ func TestGraphQLDataSourceFederation(t *testing.T) { moderatorID: ID! subject: String! title: String! + address: Address! + } + + type Address @key(fields: "id") { + id: ID! } ` @@ -9156,7 +9547,11 @@ func TestGraphQLDataSourceFederation(t *testing.T) { RootNodes: []plan.TypeField{ { TypeName: "Moderator", - FieldNames: []string{"moderatorID", "subject", "title"}, + FieldNames: []string{"moderatorID", "subject", "title", "address"}, + }, + { + TypeName: "Address", + FieldNames: []string{"id"}, }, }, FederationMetaData: plan.FederationMetaData{ @@ -9165,6 +9560,10 @@ func TestGraphQLDataSourceFederation(t *testing.T) { TypeName: "Moderator", SelectionSet: "moderatorID", }, + { + TypeName: "Address", + SelectionSet: "id", + }, }, }, }, @@ -9356,6 +9755,232 @@ func TestGraphQLDataSourceFederation(t *testing.T) { ) }) + t.Run("test nested union query with propagated operation name", func(t *testing.T) { + RunWithPermutations( + t, + definition, + ` + query Accounts { + accounts { + ... on User { + name + } + ... on Admin { + adminName + } + ... on Moderator { + subject + address { + zip + } + } + } + } + `, + "Accounts", + &plan.SynchronousResponsePlan{ + Response: &resolve.GraphQLResponse{ + Fetches: resolve.Sequence( + resolve.Single(&resolve.SingleFetch{ + FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://first.service","body":{"query":"query Accounts__first-service__0 {accounts {__typename ... on User {__typename id} ... on Admin {__typename adminID} ... on Moderator {__typename moderatorID}}}"}}`, + PostProcessing: DefaultPostProcessingConfiguration, + DataSource: &Source{}, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + }), + resolve.SingleWithPath(&resolve.SingleFetch{ + FetchDependencies: resolve.FetchDependencies{ + FetchID: 1, + DependsOnFetchIDs: []int{0}, + }, FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://second.service","body":{"query":"query Accounts__second-service__1($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename name} ... on Admin {__typename adminName}}}","variables":{"representations":[$$0$$]}}}`, + DataSource: &Source{}, + SetTemplateOutputToNullOnVariableNull: true, + RequiresEntityBatchFetch: true, + PostProcessing: EntitiesPostProcessingConfiguration, + Variables: []resolve.Variable{ + &resolve.ResolvableObjectVariable{ + Renderer: resolve.NewGraphQLVariableResolveRenderer(&resolve.Object{ + Nullable: true, + Fields: []*resolve.Field{ + { + Name: []byte("__typename"), + Value: &resolve.String{ + Path: []string{"__typename"}, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + { + Name: []byte("id"), + Value: &resolve.String{ + Path: []string{"id"}, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + { + Name: []byte("__typename"), + Value: &resolve.String{ + Path: []string{"__typename"}, + }, + OnTypeNames: [][]byte{[]byte("Admin")}, + }, + { + Name: []byte("adminID"), + Value: &resolve.String{ + Path: []string{"adminID"}, + }, + OnTypeNames: [][]byte{[]byte("Admin")}, + }, + }, + }), + }, + }, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + }, "accounts", resolve.ArrayPath("accounts")), + resolve.SingleWithPath(&resolve.SingleFetch{ + FetchDependencies: resolve.FetchDependencies{ + FetchID: 2, + DependsOnFetchIDs: []int{0}, + }, + FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://third.service","body":{"query":"query Accounts__third-service__2($representations: [_Any!]!){_entities(representations: $representations){... on Moderator {__typename subject address {__typename id}}}}","variables":{"representations":[$$0$$]}}}`, + DataSource: &Source{}, + SetTemplateOutputToNullOnVariableNull: true, + RequiresEntityBatchFetch: true, + PostProcessing: EntitiesPostProcessingConfiguration, + Variables: []resolve.Variable{ + &resolve.ResolvableObjectVariable{ + Renderer: resolve.NewGraphQLVariableResolveRenderer(&resolve.Object{ + Nullable: true, + Fields: []*resolve.Field{ + { + Name: []byte("__typename"), + Value: &resolve.String{ + Path: []string{"__typename"}, + }, + OnTypeNames: [][]byte{[]byte("Moderator")}, + }, + { + Name: []byte("moderatorID"), + Value: &resolve.String{ + Path: []string{"moderatorID"}, + }, + OnTypeNames: [][]byte{[]byte("Moderator")}, + }, + }, + }), + }, + }, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + }, "accounts", resolve.ArrayPath("accounts")), + resolve.SingleWithPath(&resolve.SingleFetch{ + FetchDependencies: resolve.FetchDependencies{ + FetchID: 3, + DependsOnFetchIDs: []int{2}, + }, + FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://first.service","body":{"query":"query Accounts__first-service__3($representations: [_Any!]!){_entities(representations: $representations){... on Address {__typename zip}}}","variables":{"representations":[$$0$$]}}}`, + DataSource: &Source{}, + SetTemplateOutputToNullOnVariableNull: true, + RequiresEntityBatchFetch: true, + PostProcessing: EntitiesPostProcessingConfiguration, + Variables: []resolve.Variable{ + &resolve.ResolvableObjectVariable{ + Renderer: resolve.NewGraphQLVariableResolveRenderer(&resolve.Object{ + Nullable: true, + Fields: []*resolve.Field{ + { + Name: []byte("__typename"), + Value: &resolve.String{ + Path: []string{"__typename"}, + }, + OnTypeNames: [][]byte{[]byte("Address")}, + }, + { + Name: []byte("id"), + Value: &resolve.String{ + Path: []string{"id"}, + }, + OnTypeNames: [][]byte{[]byte("Address")}, + }, + }, + }), + }, + }, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + }, "accounts.@.address", resolve.ArrayPath("accounts"), resolve.ObjectPath("address")), + ), + Data: &resolve.Object{ + Fields: []*resolve.Field{ + { + Name: []byte("accounts"), + Value: &resolve.Array{ + Path: []string{"accounts"}, + Nullable: true, + Item: &resolve.Object{ + Nullable: false, + Fields: []*resolve.Field{ + { + Name: []byte("name"), + Value: &resolve.String{ + Path: []string{"name"}, + }, + OnTypeNames: [][]byte{[]byte("User")}, + }, + { + Name: []byte("adminName"), + Value: &resolve.String{ + Path: []string{"adminName"}, + }, + OnTypeNames: [][]byte{[]byte("Admin")}, + }, + { + Name: []byte("subject"), + Value: &resolve.String{ + Path: []string{"subject"}, + }, + OnTypeNames: [][]byte{[]byte("Moderator")}, + }, + { + Name: []byte("address"), + Value: &resolve.Object{ + Path: []string{"address"}, + Fields: []*resolve.Field{ + { + Name: []byte("zip"), + Value: &resolve.String{ + Path: []string{"zip"}, + }, + }, + }, + }, + OnTypeNames: [][]byte{[]byte("Moderator")}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + plan.Configuration{ + DataSources: []plan.DataSource{ + firstDatasourceConfiguration, + secondDatasourceConfiguration, + thirdDatasourceConfiguration, + }, + DisableResolveFieldPositions: true, + EnableOperationNamePropagation: true, + }, + WithDefaultPostProcessor(), + ) + }) + t.Run("interface query on array - with interface selection expanded to inline fragments", func(t *testing.T) { RunWithPermutations( t, diff --git a/v2/pkg/engine/plan/configuration.go b/v2/pkg/engine/plan/configuration.go index a727814dd..1f48fcbad 100644 --- a/v2/pkg/engine/plan/configuration.go +++ b/v2/pkg/engine/plan/configuration.go @@ -2,6 +2,7 @@ package plan import ( "github.com/jensneuse/abstractlogger" + "github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve" ) @@ -15,7 +16,9 @@ type Configuration struct { // This setting removes position information from all fields // In production, this should be set to false so that error messages are easier to understand DisableResolveFieldPositions bool - CustomResolveMap map[string]resolve.CustomResolve + // EnableOperationNamePropagation appends the operation name from nested operations + EnableOperationNamePropagation bool + CustomResolveMap map[string]resolve.CustomResolve // Debug - configure debug options Debug DebugConfiguration diff --git a/v2/pkg/engine/plan/configuration_visitor.go b/v2/pkg/engine/plan/configuration_visitor.go index fc79d1799..587832e8e 100644 --- a/v2/pkg/engine/plan/configuration_visitor.go +++ b/v2/pkg/engine/plan/configuration_visitor.go @@ -17,11 +17,11 @@ import ( // configurationVisitor - walks through the operation multiple times to collect plannings paths // to resolve fields from different datasources. // we are revisiting query when we have: -// - missing path, which was not planned on the previuos walks +// - missing path, which was not planned on the previous walks // - we have fields which are waiting for dependencies type configurationVisitor struct { logger abstractlogger.Logger - debug DebugConfiguration + plannerConfiguration Configuration suggestionsSelectionReasonsEnabled bool operationName string // graphql query name @@ -180,7 +180,7 @@ func (c *configurationVisitor) addPath(plannerIdx int, configuration pathConfigu } func (c *configurationVisitor) saveAddedPath(configuration pathConfiguration) { - if c.debug.ConfigurationVisitor { + if c.plannerConfiguration.Debug.ConfigurationVisitor { c.debugPrint("saveAddedPath", configuration.String()) } @@ -242,7 +242,7 @@ func (c *configurationVisitor) removeMissingPath(path string) { } func (c *configurationVisitor) debugPrint(args ...any) { - if !c.debug.ConfigurationVisitor { + if !c.plannerConfiguration.Debug.ConfigurationVisitor { return } @@ -832,7 +832,7 @@ func (c *configurationVisitor) addNewPlanner(fieldRef int, typeName, fieldName, paths, ) - plannerConfig := dsConfig.CreatePlannerConfiguration(c.logger, fetchConfiguration, plannerPathConfig) + plannerConfig := dsConfig.CreatePlannerConfiguration(c.logger, fetchConfiguration, plannerPathConfig, c.plannerConfiguration) c.planners = append(c.planners, plannerConfig) @@ -990,7 +990,7 @@ func (c *configurationVisitor) handleMissingPath(planned bool, typeName string, // __typename field on a union could not have a suggestion return } else { - if c.debug.PrintPlanningPaths { + if c.plannerConfiguration.Debug.PrintPlanningPaths { fmt.Println("Adding potentially missing path", currentPath) } diff --git a/v2/pkg/engine/plan/datasource_configuration.go b/v2/pkg/engine/plan/datasource_configuration.go index 20a5caff7..d9f6dc217 100644 --- a/v2/pkg/engine/plan/datasource_configuration.go +++ b/v2/pkg/engine/plan/datasource_configuration.go @@ -239,14 +239,14 @@ type DataSource interface { Name() string Hash() DSHash FederationConfiguration() FederationMetaData - CreatePlannerConfiguration(logger abstractlogger.Logger, fetchConfig *objectFetchConfiguration, pathConfig *plannerPathsConfiguration) PlannerConfiguration + CreatePlannerConfiguration(logger abstractlogger.Logger, fetchConfig *objectFetchConfiguration, pathConfig *plannerPathsConfiguration, configuration Configuration) PlannerConfiguration } func (d *dataSourceConfiguration[T]) CustomConfiguration() T { return d.custom } -func (d *dataSourceConfiguration[T]) CreatePlannerConfiguration(logger abstractlogger.Logger, fetchConfig *objectFetchConfiguration, pathConfig *plannerPathsConfiguration) PlannerConfiguration { +func (d *dataSourceConfiguration[T]) CreatePlannerConfiguration(logger abstractlogger.Logger, fetchConfig *objectFetchConfiguration, pathConfig *plannerPathsConfiguration, configuration Configuration) PlannerConfiguration { planner := d.factory.Planner(logger) fetchConfig.planner = planner @@ -256,6 +256,9 @@ func (d *dataSourceConfiguration[T]) CreatePlannerConfiguration(logger abstractl objectFetchConfiguration: fetchConfig, plannerPathsConfiguration: pathConfig, planner: planner, + options: plannerConfigurationOptions{ + EnableOperationNamePropagation: configuration.EnableOperationNamePropagation, + }, } return plannerConfig @@ -286,6 +289,8 @@ type DataSourcePlannerConfiguration struct { ParentPath string PathType PlannerPathType IsNested bool + Options plannerConfigurationOptions + FetchID int } type PlannerPathType int diff --git a/v2/pkg/engine/plan/planner.go b/v2/pkg/engine/plan/planner.go index 45e27b8f8..fe486b0c5 100644 --- a/v2/pkg/engine/plan/planner.go +++ b/v2/pkg/engine/plan/planner.go @@ -323,7 +323,7 @@ func (p *Planner) createPlanningPaths(operation, definition *ast.Document, repor p.debugMessage("Create planning paths") } - p.configurationVisitor.debug = p.config.Debug + p.configurationVisitor.plannerConfiguration = p.config // set initial suggestions and used data sources p.configurationVisitor.dataSources, p.configurationVisitor.nodeSuggestions = diff --git a/v2/pkg/engine/plan/planner_configuration.go b/v2/pkg/engine/plan/planner_configuration.go index d6660c32c..69e23e8bb 100644 --- a/v2/pkg/engine/plan/planner_configuration.go +++ b/v2/pkg/engine/plan/planner_configuration.go @@ -15,6 +15,11 @@ type plannerConfiguration[T any] struct { objectFetchConfiguration *objectFetchConfiguration requiredFields FederationFieldConfigurations + options plannerConfigurationOptions +} + +type plannerConfigurationOptions struct { + EnableOperationNamePropagation bool } type PlannerConfiguration interface { @@ -37,6 +42,8 @@ func (p *plannerConfiguration[T]) Register(visitor *Visitor) error { ParentPath: p.parentPath, PathType: p.parentPathType, IsNested: p.IsNestedPlanner(), + FetchID: p.objectFetchConfiguration.fetchID, + Options: p.options, } return p.planner.Register(visitor, p.dataSourceConfiguration, dataSourcePlannerConfig)