diff --git a/execution/Makefile b/execution/Makefile index d6af5a4902..8d258d0379 100644 --- a/execution/Makefile +++ b/execution/Makefile @@ -13,12 +13,13 @@ test-quick: update-fixtures: GOLDIE_UPDATE=1 go test -count=1 ./engine/... +# golangci-lint will find the config file in the root dir of the repo. .PHONY: format format: - golangci-lint fmt --config ../.golangci.yml + golangci-lint fmt lint: - golangci-lint run --config ../.golangci.yml . + golangci-lint run .PHONY: prepare-merge prepare-merge: format test diff --git a/execution/engine/execution_engine_test.go b/execution/engine/execution_engine_test.go index 03fad5af4c..a054fd5957 100644 --- a/execution/engine/execution_engine_test.go +++ b/execution/engine/execution_engine_test.go @@ -231,6 +231,12 @@ func withValueCompletion() executionTestOptions { } } +func withFetchReasons() executionTestOptions { + return func(options *_executionTestOptions) { + options.propagateFetchReasons = true + } +} + func TestExecutionEngine_Execute(t *testing.T) { run := func(testCase ExecutionEngineTestCase, withError bool, expectedErrorMessage string, options ...executionTestOptions) func(t *testing.T) { t.Helper() @@ -262,6 +268,7 @@ func TestExecutionEngine_Execute(t *testing.T) { for _, option := range options { option(&opts) } + engineConf.plannerConfig.BuildFetchReasons = opts.propagateFetchReasons engine, err := NewExecutionEngine(ctx, abstractlogger.Noop{}, engineConf, resolve.ResolverOptions{ MaxConcurrency: 1024, ResolvableOptions: opts.resolvableOptions, @@ -827,7 +834,7 @@ func TestExecutionEngine_Execute(t *testing.T) { }, )) - t.Run("execute simple hero operation with propagating to subgraphs reason for fields being requested", runWithoutError( + t.Run("execute simple hero operation with propagating to subgraphs fetch reasons", runWithoutError( ExecutionEngineTestCase{ schema: graphql.StarwarsSchema(t), operation: graphql.LoadStarWarsQuery(starwars.FileSimpleHeroQuery, nil), @@ -875,9 +882,7 @@ func TestExecutionEngine_Execute(t *testing.T) { fields: []plan.FieldConfiguration{}, expectedResponse: `{"data":{"hero":{"name":"Luke Skywalker"}}}`, }, - func(eto *_executionTestOptions) { - eto.propagateFetchReasons = true - }, + withFetchReasons(), )) t.Run("execute simple hero operation with graphql data source and empty errors list", runWithoutError( @@ -4539,9 +4544,7 @@ func TestExecutionEngine_Execute(t *testing.T) { dataSources: makeDataSource(t, true), expectedResponse: `{"data":{"accounts":[{"some":{"title":"User1"}},{"some":{"__typename":"User","id":"2"}},{"some":{"title":"User3"}}]}}`, }, - func(eto *_executionTestOptions) { - eto.propagateFetchReasons = true - }, + withFetchReasons(), )) }) } diff --git a/go.work b/go.work index b3c4280860..18a4f29462 100644 --- a/go.work +++ b/go.work @@ -1,6 +1,6 @@ go 1.25 -toolchain go1.25 +toolchain go1.25.1 use ( // v1 diff --git a/v2/Makefile b/v2/Makefile index 14c12adcf5..e916a2f3e5 100644 --- a/v2/Makefile +++ b/v2/Makefile @@ -13,13 +13,14 @@ test-quick: update-fixtures: GOLDIE_UPDATE=1 go test -count=1 ./pkg/... +# golangci-lint will find the config file in the root dir of the repo. .PHONY: format format: - golangci-lint fmt --config ../.golangci.yml + golangci-lint fmt .PHONY: lint lint: - golangci-lint run --config ../.golangci.yml . + golangci-lint run cd ../execution && make lint .PHONY: prepare-merge 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 2e881ca07d..abb8021dae 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 @@ -3086,6 +3086,9 @@ func TestGraphQLDataSourceFederation(t *testing.T) { expectedPlan := func() *plan.SynchronousResponsePlan { return &plan.SynchronousResponsePlan{ Response: &resolve.GraphQLResponse{ + Info: &resolve.GraphQLResponseInfo{ + OperationType: ast.OperationTypeQuery, + }, Fetches: resolve.Sequence( resolve.Single(&resolve.SingleFetch{ FetchDependencies: resolve.FetchDependencies{ @@ -3096,7 +3099,48 @@ func TestGraphQLDataSourceFederation(t *testing.T) { Input: `{"method":"POST","url":"http://user.service","body":{"query":"{user {account {address {line1 line2 __typename id}}}}"}}`, DataSource: &Source{}, PostProcessing: DefaultPostProcessingConfiguration, - FieldFetchReasons: []resolve.FetchReason{ + }, + Info: &resolve.FetchInfo{ + DataSourceID: "user.service", + DataSourceName: "user.service", + RootFields: []resolve.GraphCoordinate{ + { + TypeName: "Query", + FieldName: "user", + HasAuthorizationRule: false, + }, + }, + OperationType: ast.OperationTypeQuery, + PropagatedFetchReasons: []resolve.FetchReason{ + { + TypeName: "Address", + FieldName: "id", + BySubgraphs: []string{ + "account.service", + "address-enricher.service", + "address.service", + }, + IsKey: true, + }, + { + TypeName: "Address", + FieldName: "line1", + BySubgraphs: []string{"account.service"}, + IsRequires: true, + }, + { + TypeName: "Address", + FieldName: "line2", + BySubgraphs: []string{"account.service"}, + IsRequires: true, + }, + }, + FetchReasons: []resolve.FetchReason{ + { + TypeName: "Account", + FieldName: "address", + ByUser: true, + }, { TypeName: "Address", FieldName: "id", @@ -3119,6 +3163,16 @@ func TestGraphQLDataSourceFederation(t *testing.T) { BySubgraphs: []string{"account.service"}, IsRequires: true, }, + { + TypeName: "Query", + FieldName: "user", + ByUser: true, + }, + { + TypeName: "User", + FieldName: "account", + ByUser: true, + }, }, }, }), @@ -3129,8 +3183,51 @@ func TestGraphQLDataSourceFederation(t *testing.T) { }, DataSourceIdentifier: []byte("graphql_datasource.Source"), FetchConfiguration: resolve.FetchConfiguration{ - Input: `{"method":"POST","url":"http://address-enricher.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on Address {__typename country city}}}","variables":{"representations":[$$0$$]}}}`, - DataSource: &Source{}, + Input: `{"method":"POST","url":"http://address-enricher.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on Address {__typename country city}}}","variables":{"representations":[$$0$$]}}}`, + DataSource: &Source{}, + PostProcessing: SingleEntityPostProcessingConfiguration, + RequiresEntityFetch: true, + 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.Scalar{ + Path: []string{"id"}, + }, + OnTypeNames: [][]byte{[]byte("Address")}, + }, + }, + }), + }, + }, + SetTemplateOutputToNullOnVariableNull: true, + }, + Info: &resolve.FetchInfo{ + DataSourceID: "address-enricher.service", + DataSourceName: "address-enricher.service", + RootFields: []resolve.GraphCoordinate{ + { + TypeName: "Address", + FieldName: "country", + HasAuthorizationRule: false, + }, + { + TypeName: "Address", + FieldName: "city", + HasAuthorizationRule: false, + }, + }, + OperationType: ast.OperationTypeQuery, CoordinateDependencies: []resolve.FetchDependency{ { Coordinate: resolve.GraphCoordinate{ @@ -3167,6 +3264,36 @@ func TestGraphQLDataSourceFederation(t *testing.T) { }, }, }, + FetchReasons: []resolve.FetchReason{ + { + TypeName: "Account", + FieldName: "address", + ByUser: true, + }, + { + TypeName: "Address", + FieldName: "city", + BySubgraphs: []string{"address.service"}, + IsRequires: true, + }, + { + TypeName: "Address", + FieldName: "country", + BySubgraphs: []string{"address.service"}, + IsRequires: true, + }, + }, + }, + }, "user.account.address", resolve.ObjectPath("user"), resolve.ObjectPath("account"), resolve.ObjectPath("address")), + resolve.SingleWithPath(&resolve.SingleFetch{ + FetchDependencies: resolve.FetchDependencies{ + FetchID: 2, + DependsOnFetchIDs: []int{0, 1}, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://address.service","body":{"query":"query($representations: [_Any!]!, $a: String!){_entities(representations: $representations){... on Address {__typename line3(test: $a) zip}}}","variables":{"a":"BOOM","representations":[$$0$$]}}}`, + DataSource: &Source{}, PostProcessing: SingleEntityPostProcessingConfiguration, RequiresEntityFetch: true, Variables: []resolve.Variable{ @@ -3188,24 +3315,42 @@ func TestGraphQLDataSourceFederation(t *testing.T) { }, OnTypeNames: [][]byte{[]byte("Address")}, }, + { + Name: []byte("country"), + Value: &resolve.String{ + Path: []string{"country"}, + }, + OnTypeNames: [][]byte{[]byte("Address")}, + }, + { + Name: []byte("city"), + Value: &resolve.String{ + Path: []string{"city"}, + }, + OnTypeNames: [][]byte{[]byte("Address")}, + }, }, }), }, }, SetTemplateOutputToNullOnVariableNull: true, }, - }, "user.account.address", resolve.ObjectPath("user"), resolve.ObjectPath("account"), resolve.ObjectPath("address")), - resolve.SingleWithPath(&resolve.SingleFetch{ - FetchDependencies: resolve.FetchDependencies{ - FetchID: 2, - DependsOnFetchIDs: []int{0, 1}, - }, - DataSourceIdentifier: []byte("graphql_datasource.Source"), - FetchConfiguration: resolve.FetchConfiguration{ - Input: `{"method":"POST","url":"http://address.service","body":{"query":"query($representations: [_Any!]!, $a: String!){_entities(representations: $representations){... on Address {__typename line3(test: $a) zip}}}","variables":{"a":"BOOM","representations":[$$0$$]}}}`, - DataSource: &Source{}, - PostProcessing: SingleEntityPostProcessingConfiguration, - RequiresEntityFetch: true, + Info: &resolve.FetchInfo{ + DataSourceID: "address.service", + DataSourceName: "address.service", + RootFields: []resolve.GraphCoordinate{ + { + TypeName: "Address", + FieldName: "line3", + HasAuthorizationRule: false, + }, + { + TypeName: "Address", + FieldName: "zip", + HasAuthorizationRule: false, + }, + }, + OperationType: ast.OperationTypeQuery, CoordinateDependencies: []resolve.FetchDependency{ { Coordinate: resolve.GraphCoordinate{ @@ -3260,7 +3405,26 @@ func TestGraphQLDataSourceFederation(t *testing.T) { }, }, }, - FieldFetchReasons: []resolve.FetchReason{ + PropagatedFetchReasons: []resolve.FetchReason{ + { + TypeName: "Address", + FieldName: "line3", + BySubgraphs: []string{"account.service"}, + IsRequires: true, + }, + { + TypeName: "Address", + FieldName: "zip", + BySubgraphs: []string{"account.service"}, + IsRequires: true, + }, + }, + FetchReasons: []resolve.FetchReason{ + { + TypeName: "Account", + FieldName: "address", + ByUser: true, + }, { TypeName: "Address", FieldName: "line3", @@ -3274,6 +3438,19 @@ func TestGraphQLDataSourceFederation(t *testing.T) { IsRequires: true, }, }, + }, + }, "user.account.address", resolve.ObjectPath("user"), resolve.ObjectPath("account"), resolve.ObjectPath("address")), + resolve.SingleWithPath(&resolve.SingleFetch{ + FetchDependencies: resolve.FetchDependencies{ + FetchID: 3, + DependsOnFetchIDs: []int{0, 2}, + }, + DataSourceIdentifier: []byte("graphql_datasource.Source"), + FetchConfiguration: resolve.FetchConfiguration{ + Input: `{"method":"POST","url":"http://account.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on Address {__typename fullAddress}}}","variables":{"representations":[$$0$$]}}}`, + DataSource: &Source{}, + PostProcessing: SingleEntityPostProcessingConfiguration, + RequiresEntityFetch: true, Variables: []resolve.Variable{ &resolve.ResolvableObjectVariable{ Renderer: resolve.NewGraphQLVariableResolveRenderer(&resolve.Object{ @@ -3287,23 +3464,37 @@ func TestGraphQLDataSourceFederation(t *testing.T) { OnTypeNames: [][]byte{[]byte("Address")}, }, { - Name: []byte("id"), - Value: &resolve.Scalar{ - Path: []string{"id"}, + Name: []byte("line1"), + Value: &resolve.String{ + Path: []string{"line1"}, }, OnTypeNames: [][]byte{[]byte("Address")}, }, { - Name: []byte("country"), + Name: []byte("line2"), Value: &resolve.String{ - Path: []string{"country"}, + Path: []string{"line2"}, }, OnTypeNames: [][]byte{[]byte("Address")}, }, { - Name: []byte("city"), + Name: []byte("line3"), Value: &resolve.String{ - Path: []string{"city"}, + Path: []string{"line3"}, + }, + OnTypeNames: [][]byte{[]byte("Address")}, + }, + { + Name: []byte("zip"), + Value: &resolve.String{ + Path: []string{"zip"}, + }, + OnTypeNames: [][]byte{[]byte("Address")}, + }, + { + Name: []byte("id"), + Value: &resolve.Scalar{ + Path: []string{"id"}, }, OnTypeNames: [][]byte{[]byte("Address")}, }, @@ -3313,17 +3504,17 @@ func TestGraphQLDataSourceFederation(t *testing.T) { }, SetTemplateOutputToNullOnVariableNull: true, }, - }, "user.account.address", resolve.ObjectPath("user"), resolve.ObjectPath("account"), resolve.ObjectPath("address")), - resolve.SingleWithPath(&resolve.SingleFetch{ - FetchDependencies: resolve.FetchDependencies{ - FetchID: 3, - DependsOnFetchIDs: []int{0, 2}, - }, - DataSourceIdentifier: []byte("graphql_datasource.Source"), - FetchConfiguration: resolve.FetchConfiguration{ - Input: `{"method":"POST","url":"http://account.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on Address {__typename fullAddress}}}","variables":{"representations":[$$0$$]}}}`, - DataSource: &Source{}, - PostProcessing: SingleEntityPostProcessingConfiguration, + Info: &resolve.FetchInfo{ + DataSourceID: "account.service", + DataSourceName: "account.service", + RootFields: []resolve.GraphCoordinate{ + { + TypeName: "Address", + FieldName: "fullAddress", + HasAuthorizationRule: false, + }, + }, + OperationType: ast.OperationTypeQuery, CoordinateDependencies: []resolve.FetchDependency{ { Coordinate: resolve.GraphCoordinate{ @@ -3380,59 +3571,18 @@ func TestGraphQLDataSourceFederation(t *testing.T) { }, }, }, - RequiresEntityFetch: true, - 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("line1"), - Value: &resolve.String{ - Path: []string{"line1"}, - }, - OnTypeNames: [][]byte{[]byte("Address")}, - }, - { - Name: []byte("line2"), - Value: &resolve.String{ - Path: []string{"line2"}, - }, - OnTypeNames: [][]byte{[]byte("Address")}, - }, - { - Name: []byte("line3"), - Value: &resolve.String{ - Path: []string{"line3"}, - }, - OnTypeNames: [][]byte{[]byte("Address")}, - }, - { - Name: []byte("zip"), - Value: &resolve.String{ - Path: []string{"zip"}, - }, - OnTypeNames: [][]byte{[]byte("Address")}, - }, - { - Name: []byte("id"), - Value: &resolve.Scalar{ - Path: []string{"id"}, - }, - OnTypeNames: [][]byte{[]byte("Address")}, - }, - }, - }), + FetchReasons: []resolve.FetchReason{ + { + TypeName: "Account", + FieldName: "address", + ByUser: true, + }, + { + TypeName: "Address", + FieldName: "fullAddress", + ByUser: true, }, }, - SetTemplateOutputToNullOnVariableNull: true, }, }, "user.account.address", resolve.ObjectPath("user"), resolve.ObjectPath("account"), resolve.ObjectPath("address")), ), @@ -3440,39 +3590,82 @@ func TestGraphQLDataSourceFederation(t *testing.T) { Fields: []*resolve.Field{ { Name: []byte("user"), + Info: &resolve.FieldInfo{ + Name: "user", + ExactParentTypeName: "Query", + ParentTypeNames: []string{"Query"}, + NamedType: "User", + Source: resolve.TypeFieldSource{ + IDs: []string{"user.service"}, + Names: []string{"user.service"}, + }, + }, Value: &resolve.Object{ Path: []string{"user"}, Nullable: true, PossibleTypes: map[string]struct{}{ "User": {}, }, - TypeName: "User", + SourceName: "user.service", + TypeName: "User", Fields: []*resolve.Field{ { Name: []byte("account"), + Info: &resolve.FieldInfo{ + Name: "account", + ExactParentTypeName: "User", + ParentTypeNames: []string{"User"}, + NamedType: "Account", + Source: resolve.TypeFieldSource{ + IDs: []string{"user.service"}, + Names: []string{"user.service"}, + }, + }, Value: &resolve.Object{ Path: []string{"account"}, Nullable: true, PossibleTypes: map[string]struct{}{ "Account": {}, }, - TypeName: "Account", + SourceName: "user.service", + TypeName: "Account", Fields: []*resolve.Field{ { Name: []byte("address"), + Info: &resolve.FieldInfo{ + Name: "address", + ExactParentTypeName: "Account", + ParentTypeNames: []string{"Account"}, + NamedType: "Address", + Source: resolve.TypeFieldSource{ + IDs: []string{"user.service"}, + Names: []string{"user.service"}, + }, + }, Value: &resolve.Object{ Path: []string{"address"}, Nullable: true, PossibleTypes: map[string]struct{}{ "Address": {}, }, - TypeName: "Address", + SourceName: "user.service", + TypeName: "Address", Fields: []*resolve.Field{ { Name: []byte("fullAddress"), Value: &resolve.String{ Path: []string{"fullAddress"}, }, + Info: &resolve.FieldInfo{ + Name: "fullAddress", + ExactParentTypeName: "Address", + ParentTypeNames: []string{"Address"}, + NamedType: "String", + Source: resolve.TypeFieldSource{ + IDs: []string{"account.service"}, + Names: []string{"account.service"}, + }, + }, }, }, }, @@ -3520,7 +3713,7 @@ func TestGraphQLDataSourceFederation(t *testing.T) { }, }, WithDefaultPostProcessor(), - WithFieldDependencies(), + WithFetchReasons(), ) }) @@ -16067,7 +16260,7 @@ func TestGraphQLDataSourceFederation(t *testing.T) { SelectionSet: "id", Conditions: []plan.KeyCondition{ { - Coordinates: []plan.KeyConditionCoordinate{ + Coordinates: []plan.FieldCoordinate{ { TypeName: "User", FieldName: "hostedImageWithProvides", @@ -17008,7 +17201,7 @@ func TestGraphQLDataSourceFederation(t *testing.T) { SelectionSet: "id", Conditions: []plan.KeyCondition{ { - Coordinates: []plan.KeyConditionCoordinate{ + Coordinates: []plan.FieldCoordinate{ { TypeName: "Host", FieldName: "image", diff --git a/v2/pkg/engine/datasourcetesting/datasourcetesting.go b/v2/pkg/engine/datasourcetesting/datasourcetesting.go index 1b1a622bee..5987666447 100644 --- a/v2/pkg/engine/datasourcetesting/datasourcetesting.go +++ b/v2/pkg/engine/datasourcetesting/datasourcetesting.go @@ -33,6 +33,7 @@ type testOptions struct { withFieldInfo bool withPrintPlan bool withFieldDependencies bool + withFetchReasons bool } func WithPostProcessors(postProcessors ...*postprocess.Processor) func(*testOptions) { @@ -70,10 +71,19 @@ func WithPrintPlan() func(*testOptions) { func WithFieldDependencies() func(*testOptions) { return func(o *testOptions) { + o.withFieldInfo = true o.withFieldDependencies = true } } +func WithFetchReasons() func(*testOptions) { + return func(o *testOptions) { + o.withFieldInfo = true + o.withFieldDependencies = true + o.withFetchReasons = true + } +} + func RunWithPermutations(t *testing.T, definition, operation, operationName string, expectedPlan plan.Plan, config plan.Configuration, options ...func(*testOptions)) { t.Helper() @@ -147,6 +157,10 @@ func RunTestWithVariables(definition, operation, operationName, variables string config.DisableIncludeFieldDependencies = false } + if opts.withFetchReasons { + config.BuildFetchReasons = true + } + if opts.skipReason != "" { t.Skip(opts.skipReason) } diff --git a/v2/pkg/engine/plan/configuration.go b/v2/pkg/engine/plan/configuration.go index a5216da629..215bbbcbd3 100644 --- a/v2/pkg/engine/plan/configuration.go +++ b/v2/pkg/engine/plan/configuration.go @@ -31,8 +31,14 @@ type Configuration struct { DisableIncludeInfo bool // DisableIncludeFieldDependencies controls whether the planner generates - // field dependency structures (useful in tests). + // field dependency structures. + // It requires DisableIncludeInfo set to false. DisableIncludeFieldDependencies bool + + // BuildFetchReasons allows generating the FetchReasons structure for all the fields. + // It may be enabled by some other components of the engine. + // It requires DisableIncludeInfo and DisableIncludeFieldDependencies set to false. + BuildFetchReasons bool } type DebugConfiguration struct { diff --git a/v2/pkg/engine/plan/datasource_configuration.go b/v2/pkg/engine/plan/datasource_configuration.go index 2601a43cc6..2adac86e79 100644 --- a/v2/pkg/engine/plan/datasource_configuration.go +++ b/v2/pkg/engine/plan/datasource_configuration.go @@ -53,6 +53,9 @@ type DataSourceMetadata struct { rootNodesIndex map[string]fieldsIndex // maps TypeName to fieldsIndex childNodesIndex map[string]fieldsIndex // maps TypeName to fieldsIndex + + // requireFetchReasons provides a lookup map for fields marked with corresponding directive. + requireFetchReasons map[FieldCoordinate]struct{} } type DirectivesConfigurations interface { @@ -71,13 +74,12 @@ type NodesInfo interface { HasChildNode(typeName, fieldName string) bool HasExternalChildNode(typeName, fieldName string) bool HasChildNodeWithTypename(typeName string) bool - RequiresFetchReason(typeName, fieldName string) bool + RequireFetchReasons() map[FieldCoordinate]struct{} } type fieldsIndex struct { - fields map[string]struct{} - externalFields map[string]struct{} - fetchReasonFields map[string]struct{} + fields map[string]struct{} + externalFields map[string]struct{} } func (d *DataSourceMetadata) Init() error { @@ -102,14 +104,14 @@ func (d *DataSourceMetadata) InitNodesIndex() { d.rootNodesIndex = make(map[string]fieldsIndex, len(d.RootNodes)) d.childNodesIndex = make(map[string]fieldsIndex, len(d.ChildNodes)) + d.requireFetchReasons = make(map[FieldCoordinate]struct{}) for i := range d.RootNodes { typeName := d.RootNodes[i].TypeName if _, ok := d.rootNodesIndex[typeName]; !ok { d.rootNodesIndex[typeName] = fieldsIndex{ - fields: make(map[string]struct{}, len(d.RootNodes[i].FieldNames)), - externalFields: make(map[string]struct{}, len(d.RootNodes[i].ExternalFieldNames)), - fetchReasonFields: make(map[string]struct{}, len(d.RootNodes[i].FetchReasonFields)), + fields: make(map[string]struct{}, len(d.RootNodes[i].FieldNames)), + externalFields: make(map[string]struct{}, len(d.RootNodes[i].ExternalFieldNames)), } } for _, name := range d.RootNodes[i].FieldNames { @@ -119,7 +121,7 @@ func (d *DataSourceMetadata) InitNodesIndex() { d.rootNodesIndex[typeName].externalFields[name] = struct{}{} } for _, name := range d.RootNodes[i].FetchReasonFields { - d.rootNodesIndex[typeName].fetchReasonFields[name] = struct{}{} + d.requireFetchReasons[FieldCoordinate{typeName, name}] = struct{}{} } } @@ -127,9 +129,8 @@ func (d *DataSourceMetadata) InitNodesIndex() { typeName := d.ChildNodes[i].TypeName if _, ok := d.childNodesIndex[typeName]; !ok { d.childNodesIndex[typeName] = fieldsIndex{ - fields: make(map[string]struct{}), - externalFields: make(map[string]struct{}), - fetchReasonFields: make(map[string]struct{}), + fields: make(map[string]struct{}), + externalFields: make(map[string]struct{}), } } for _, name := range d.ChildNodes[i].FieldNames { @@ -139,7 +140,7 @@ func (d *DataSourceMetadata) InitNodesIndex() { d.childNodesIndex[typeName].externalFields[name] = struct{}{} } for _, name := range d.ChildNodes[i].FetchReasonFields { - d.childNodesIndex[typeName].fetchReasonFields[name] = struct{}{} + d.requireFetchReasons[FieldCoordinate{typeName, name}] = struct{}{} } } } @@ -207,32 +208,8 @@ func (d *DataSourceMetadata) HasExternalChildNode(typeName, fieldName string) bo return ok } -func (d *DataSourceMetadata) RequiresFetchReason(typeName, fieldName string) bool { - return d.hasFetchReasonRootNode(typeName, fieldName) || d.hasFetchReasonChildNode(typeName, fieldName) -} - -func (d *DataSourceMetadata) hasFetchReasonRootNode(typeName, fieldName string) bool { - if d.rootNodesIndex == nil { - return false - } - index, ok := d.rootNodesIndex[typeName] - if !ok { - return false - } - _, ok = index.fetchReasonFields[fieldName] - return ok -} - -func (d *DataSourceMetadata) hasFetchReasonChildNode(typeName, fieldName string) bool { - if d.childNodesIndex == nil { - return false - } - index, ok := d.childNodesIndex[typeName] - if !ok { - return false - } - _, ok = index.fetchReasonFields[fieldName] - return ok +func (d *DataSourceMetadata) RequireFetchReasons() map[FieldCoordinate]struct{} { + return d.requireFetchReasons } func (d *DataSourceMetadata) HasChildNodeWithTypename(typeName string) bool { diff --git a/v2/pkg/engine/plan/federation_metadata.go b/v2/pkg/engine/plan/federation_metadata.go index b4479a4891..994ea9539f 100644 --- a/v2/pkg/engine/plan/federation_metadata.go +++ b/v2/pkg/engine/plan/federation_metadata.go @@ -82,11 +82,11 @@ type FederationFieldConfiguration struct { } type KeyCondition struct { - Coordinates []KeyConditionCoordinate `json:"coordinates"` - FieldPath []string `json:"field_path"` + Coordinates []FieldCoordinate `json:"coordinates"` + FieldPath []string `json:"field_path"` } -type KeyConditionCoordinate struct { +type FieldCoordinate struct { TypeName string `json:"type_name"` FieldName string `json:"field_name"` } diff --git a/v2/pkg/engine/plan/node_selection_visitor.go b/v2/pkg/engine/plan/node_selection_visitor.go index 6a1259a457..5f7a71c1cd 100644 --- a/v2/pkg/engine/plan/node_selection_visitor.go +++ b/v2/pkg/engine/plan/node_selection_visitor.go @@ -45,8 +45,7 @@ type nodeSelectionVisitor struct { hasNewFields bool // hasNewFields is used to determine if we need to run the planner again. It will be true in case required fields were added hasUnresolvedFields bool // hasUnresolvedFields is used to determine if we need to run the planner again. We should set it to true in case we have unresolved fields - fieldPathCoordinates []KeyConditionCoordinate // currentFieldPathCoordinates is a stack of field path coordinates // TODO: remove me - rewrittenFieldRefs []int + rewrittenFieldRefs []int } type fieldDependencyKey struct { @@ -127,12 +126,6 @@ func (c *nodeSelectionVisitor) EnterDocument(operation, definition *ast.Document c.selectionSetRefs = c.selectionSetRefs[:0] } - if c.fieldPathCoordinates == nil { - c.fieldPathCoordinates = make([]KeyConditionCoordinate, 0, 8) - } else { - c.fieldPathCoordinates = c.fieldPathCoordinates[:0] - } - if c.secondaryRun { return } @@ -218,17 +211,9 @@ func (c *nodeSelectionVisitor) EnterField(fieldRef int) { // check if field selections are abstract and needs rewrites c.rewriteSelectionSetHavingAbstractFragments(fieldRef, ds) } - - c.fieldPathCoordinates = append(c.fieldPathCoordinates, KeyConditionCoordinate{ - FieldName: fieldName, - TypeName: typeName, - }) } func (c *nodeSelectionVisitor) LeaveField(ref int) { - if len(c.fieldPathCoordinates) > 0 { - c.fieldPathCoordinates = c.fieldPathCoordinates[:len(c.fieldPathCoordinates)-1] - } } func (c *nodeSelectionVisitor) handleFieldRequiredByRequires(fieldRef int, parentPath, typeName, fieldName, currentPath string, dsConfig DataSource) { diff --git a/v2/pkg/engine/plan/visitor.go b/v2/pkg/engine/plan/visitor.go index 382be28074..e67af37c20 100644 --- a/v2/pkg/engine/plan/visitor.go +++ b/v2/pkg/engine/plan/visitor.go @@ -1325,21 +1325,40 @@ func (v *Visitor) configureFetch(internal *objectFetchConfiguration, external re DataSourceIdentifier: []byte(dataSourceType), } - if !v.Config.DisableIncludeInfo { - singleFetch.Info = &resolve.FetchInfo{ - DataSourceID: internal.sourceID, - DataSourceName: internal.sourceName, - RootFields: internal.rootFields, - OperationType: internal.operationType, - QueryPlan: external.QueryPlan, - } + if v.Config.DisableIncludeInfo { + return singleFetch + } + singleFetch.Info = &resolve.FetchInfo{ + DataSourceID: internal.sourceID, + DataSourceName: internal.sourceName, + RootFields: internal.rootFields, + OperationType: internal.operationType, + QueryPlan: external.QueryPlan, } - if !v.Config.DisableIncludeFieldDependencies { - singleFetch.CoordinateDependencies = v.resolveFetchDependencies(internal.fetchID) - singleFetch.FieldFetchReasons = v.buildFetchReasons(internal.fetchID) + if v.Config.DisableIncludeFieldDependencies { + return singleFetch + } + singleFetch.Info.CoordinateDependencies = v.resolveFetchDependencies(internal.fetchID) + + if !v.Config.BuildFetchReasons { + return singleFetch + } + singleFetch.Info.FetchReasons = v.buildFetchReasons(internal.fetchID) + if len(singleFetch.Info.FetchReasons) == 0 { + return singleFetch } + dsConfig := v.planners[internal.fetchID].DataSourceConfiguration() + lookup := dsConfig.RequireFetchReasons() + propagated := make([]resolve.FetchReason, 0, len(lookup)) + for _, fr := range singleFetch.Info.FetchReasons { + field := FieldCoordinate{fr.TypeName, fr.FieldName} + if _, ok := lookup[field]; ok { + propagated = append(propagated, fr) + } + } + singleFetch.Info.PropagatedFetchReasons = propagated return singleFetch } @@ -1403,14 +1422,9 @@ func (v *Visitor) buildFetchReasons(fetchID int) []resolve.FetchReason { if !ok { return nil } - dsConfig := v.planners[fetchID].DataSourceConfiguration() - type typedField struct { - typeName string - field string - } reasons := make([]resolve.FetchReason, 0, len(fields)) - index := make(map[typedField]int, len(fields)) + index := make(map[FieldCoordinate]int, len(fields)) for _, fieldRef := range fields { fieldName := v.Operation.FieldNameString(fieldRef) @@ -1418,17 +1432,13 @@ func (v *Visitor) buildFetchReasons(fetchID int) []resolve.FetchReason { continue } typeName := v.fieldEnclosingTypeNames[fieldRef] - if !dsConfig.RequiresFetchReason(typeName, fieldName) { - continue - } byUser := !v.skipField(fieldRef) - dependants, ok := v.fieldRefDependants[fieldRef] var subgraphs []string var isKey, isRequires bool - if ok { + if dependants, ok := v.fieldRefDependants[fieldRef]; ok { subgraphs = make([]string, 0, len(dependants)) for _, reqByRef := range dependants { plannerIDs, ok := v.fieldPlanners[reqByRef] @@ -1460,7 +1470,7 @@ func (v *Visitor) buildFetchReasons(fetchID int) []resolve.FetchReason { // Deduplicate using the index and merge with existing entries. if byUser || len(subgraphs) > 0 { - key := typedField{typeName: typeName, field: fieldName} + key := FieldCoordinate{TypeName: typeName, FieldName: fieldName} var i int if i, ok = index[key]; ok { // True should overwrite false. diff --git a/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go b/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go index 3d6a00b356..f5d0b2ae26 100644 --- a/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go +++ b/v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go @@ -75,10 +75,8 @@ func (d *createConcreteSingleFetchTypes) createEntityBatchFetch(fetch *resolve.S } return &resolve.BatchEntityFetch{ - FetchDependencies: fetch.FetchDependencies, - CoordinateDependencies: fetch.CoordinateDependencies, - FieldFetchReasons: fetch.FieldFetchReasons, - Info: fetch.Info, + FetchDependencies: fetch.FetchDependencies, + Info: fetch.Info, Input: resolve.BatchInput{ Header: resolve.InputTemplate{ Segments: fetch.InputTemplate.Segments[:representationsVariableIndex], @@ -122,10 +120,8 @@ func (d *createConcreteSingleFetchTypes) createEntityFetch(fetch *resolve.Single } return &resolve.EntityFetch{ - FetchDependencies: fetch.FetchDependencies, - CoordinateDependencies: fetch.CoordinateDependencies, - FieldFetchReasons: fetch.FieldFetchReasons, - Info: fetch.Info, + FetchDependencies: fetch.FetchDependencies, + Info: fetch.Info, Input: resolve.EntityInput{ Header: resolve.InputTemplate{ Segments: fetch.InputTemplate.Segments[:representationsVariableIndex], diff --git a/v2/pkg/engine/postprocess/deduplicate_single_fetches.go b/v2/pkg/engine/postprocess/deduplicate_single_fetches.go index c7864fc32c..bdb6bee576 100644 --- a/v2/pkg/engine/postprocess/deduplicate_single_fetches.go +++ b/v2/pkg/engine/postprocess/deduplicate_single_fetches.go @@ -51,10 +51,14 @@ func (d *deduplicateSingleFetches) replaceDependsOnFetchId(root *resolve.FetchTr continue } - for j := range root.ChildNodes[i].Item.Fetch.DependenciesCoordinates() { - for k := range root.ChildNodes[i].Item.Fetch.DependenciesCoordinates()[j].DependsOn { - if root.ChildNodes[i].Item.Fetch.DependenciesCoordinates()[j].DependsOn[k].FetchID == oldId { - root.ChildNodes[i].Item.Fetch.DependenciesCoordinates()[j].DependsOn[k].FetchID = newId + info := root.ChildNodes[i].Item.Fetch.FetchInfo() + if info == nil { + continue + } + for j := range info.CoordinateDependencies { + for k := range info.CoordinateDependencies[j].DependsOn { + if info.CoordinateDependencies[j].DependsOn[k].FetchID == oldId { + info.CoordinateDependencies[j].DependsOn[k].FetchID = newId } } } diff --git a/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go b/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go index 9a0121ba20..d3da3718ca 100644 --- a/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go +++ b/v2/pkg/engine/postprocess/deduplicate_single_fetches_test.go @@ -99,6 +99,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) { }, FetchConfiguration: resolve.FetchConfiguration{ Input: "a", + }, + Info: &resolve.FetchInfo{ CoordinateDependencies: []resolve.FetchDependency{ { DependsOn: []resolve.FetchDependencyOrigin{ @@ -123,6 +125,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) { }, FetchConfiguration: resolve.FetchConfiguration{ Input: "a", + }, + Info: &resolve.FetchInfo{ CoordinateDependencies: []resolve.FetchDependency{ { DependsOn: []resolve.FetchDependencyOrigin{ @@ -147,6 +151,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) { }, FetchConfiguration: resolve.FetchConfiguration{ Input: "b", + }, + Info: &resolve.FetchInfo{ CoordinateDependencies: []resolve.FetchDependency{ { DependsOn: []resolve.FetchDependencyOrigin{ @@ -174,6 +180,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) { }, FetchConfiguration: resolve.FetchConfiguration{ Input: "b", + }, + Info: &resolve.FetchInfo{ CoordinateDependencies: []resolve.FetchDependency{ { DependsOn: []resolve.FetchDependencyOrigin{ @@ -219,6 +227,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) { }, FetchConfiguration: resolve.FetchConfiguration{ Input: "a", + }, + Info: &resolve.FetchInfo{ CoordinateDependencies: []resolve.FetchDependency{ { DependsOn: []resolve.FetchDependencyOrigin{ @@ -243,6 +253,8 @@ func TestDeduplicateSingleFetches_ProcessFetchTree(t *testing.T) { }, FetchConfiguration: resolve.FetchConfiguration{ Input: "b", + }, + Info: &resolve.FetchInfo{ CoordinateDependencies: []resolve.FetchDependency{ { DependsOn: []resolve.FetchDependencyOrigin{ diff --git a/v2/pkg/engine/postprocess/postprocess.go b/v2/pkg/engine/postprocess/postprocess.go index 9a4fadf56d..f92627fbca 100644 --- a/v2/pkg/engine/postprocess/postprocess.go +++ b/v2/pkg/engine/postprocess/postprocess.go @@ -180,9 +180,15 @@ func (p *Processor) createFetchTree(res *resolve.GraphQLResponse) { if p.collectDataSourceInfo { var list = make([]resolve.DataSourceInfo, 0, len(fetches)) for _, fetch := range fetches { - dsInfo := fetch.Fetch.DataSourceInfo() - if !slices.Contains(list, dsInfo) { - list = append(list, dsInfo) + info := fetch.Fetch.FetchInfo() + if info != nil { + dsInfo := resolve.DataSourceInfo{ + ID: info.DataSourceID, + Name: info.DataSourceName, + } + if !slices.Contains(list, dsInfo) { + list = append(list, dsInfo) + } } } res.DataSources = list diff --git a/v2/pkg/engine/resolve/fetch.go b/v2/pkg/engine/resolve/fetch.go index 137670bcd2..26e20c743c 100644 --- a/v2/pkg/engine/resolve/fetch.go +++ b/v2/pkg/engine/resolve/fetch.go @@ -20,9 +20,10 @@ const ( type Fetch interface { FetchKind() FetchKind Dependencies() *FetchDependencies - DataSourceInfo() DataSourceInfo - DependenciesCoordinates() []FetchDependency - FetchReasons() []FetchReason + + // FetchInfo returns additional fetch-related information. + // Callers must treat FetchInfo as read-only after planning; it may be nil when disabled by planner options. + FetchInfo() *FetchInfo } type FetchItem struct { @@ -101,19 +102,8 @@ func (s *SingleFetch) Dependencies() *FetchDependencies { return &s.FetchDependencies } -func (s *SingleFetch) DependenciesCoordinates() []FetchDependency { - return s.CoordinateDependencies -} - -func (s *SingleFetch) FetchReasons() []FetchReason { - return s.FieldFetchReasons -} - -func (s *SingleFetch) DataSourceInfo() DataSourceInfo { - return DataSourceInfo{ - ID: s.Info.DataSourceID, - Name: s.Info.DataSourceName, - } +func (s *SingleFetch) FetchInfo() *FetchInfo { + return s.Info } // FetchDependencies holding current fetch id and ids of fetches that current fetch depends on @@ -170,33 +160,20 @@ func (*SingleFetch) FetchKind() FetchKind { type BatchEntityFetch struct { FetchDependencies - Input BatchInput - DataSource DataSource - PostProcessing PostProcessingConfiguration - DataSourceIdentifier []byte - Trace *DataSourceLoadTrace - Info *FetchInfo - CoordinateDependencies []FetchDependency - FieldFetchReasons []FetchReason + Input BatchInput + DataSource DataSource + PostProcessing PostProcessingConfiguration + DataSourceIdentifier []byte + Trace *DataSourceLoadTrace + Info *FetchInfo } func (b *BatchEntityFetch) Dependencies() *FetchDependencies { return &b.FetchDependencies } -func (b *BatchEntityFetch) DependenciesCoordinates() []FetchDependency { - return b.CoordinateDependencies -} - -func (b *BatchEntityFetch) FetchReasons() []FetchReason { - return b.FieldFetchReasons -} - -func (b *BatchEntityFetch) DataSourceInfo() DataSourceInfo { - return DataSourceInfo{ - ID: b.Info.DataSourceID, - Name: b.Info.DataSourceName, - } +func (b *BatchEntityFetch) FetchInfo() *FetchInfo { + return b.Info } type BatchInput struct { @@ -223,33 +200,20 @@ func (*BatchEntityFetch) FetchKind() FetchKind { type EntityFetch struct { FetchDependencies - Input EntityInput - DataSource DataSource - PostProcessing PostProcessingConfiguration - DataSourceIdentifier []byte - Trace *DataSourceLoadTrace - Info *FetchInfo - CoordinateDependencies []FetchDependency - FieldFetchReasons []FetchReason + Input EntityInput + DataSource DataSource + PostProcessing PostProcessingConfiguration + DataSourceIdentifier []byte + Trace *DataSourceLoadTrace + Info *FetchInfo } func (e *EntityFetch) Dependencies() *FetchDependencies { return &e.FetchDependencies } -func (e *EntityFetch) DependenciesCoordinates() []FetchDependency { - return e.CoordinateDependencies -} - -func (e *EntityFetch) FetchReasons() []FetchReason { - return e.FieldFetchReasons -} - -func (e *EntityFetch) DataSourceInfo() DataSourceInfo { - return DataSourceInfo{ - ID: e.Info.DataSourceID, - Name: e.Info.DataSourceName, - } +func (e *EntityFetch) FetchInfo() *FetchInfo { + return e.Info } type EntityInput struct { @@ -276,22 +240,14 @@ func (p *ParallelListItemFetch) Dependencies() *FetchDependencies { return &p.Fetch.FetchDependencies } -func (p *ParallelListItemFetch) DependenciesCoordinates() []FetchDependency { - return p.Fetch.CoordinateDependencies -} - -func (p *ParallelListItemFetch) FetchReasons() []FetchReason { - return p.Fetch.FieldFetchReasons +func (p *ParallelListItemFetch) FetchInfo() *FetchInfo { + return p.Fetch.Info } func (*ParallelListItemFetch) FetchKind() FetchKind { return FetchKindParallelListItem } -func (p *ParallelListItemFetch) DataSourceInfo() DataSourceInfo { - return p.Fetch.DataSourceInfo() -} - type QueryPlan struct { DependsOnFields []Representation Query string @@ -341,17 +297,6 @@ type FetchConfiguration struct { QueryPlan *QueryPlan - // CoordinateDependencies contain a list of GraphCoordinates (typeName+fieldName) - // and which fields from other fetches they depend on. - // This information is useful to understand why a fetch depends on other fetches, - // and how multiple dependencies lead to a chain of fetches - CoordinateDependencies []FetchDependency - - // FieldFetchReasons contains provenance for fields that require fetch reason to be propagated - // to their subgraph. It is optional propagation via request extensions; - // it does not affect execution. - FieldFetchReasons []FetchReason - // OperationName is non-empty when the operation name is propagated to the upstream subgraph fetch. OperationName string } @@ -367,10 +312,6 @@ func (fc *FetchConfiguration) Equals(other *FetchConfiguration) bool { } // Note: we do not compare datasources, as they will always be a different instance. - // Note: we do not compare CoordinateDependencies, as they contain more detailed - // dependencies information that is already present in the FetchDependencies on the fetch itself. - // Note: we do not compare FieldFetchReasons, as it is derived data for an extension - // and does not affect fetch execution semantics. if fc.RequiresParallelListItemFetch != other.RequiresParallelListItemFetch { return false @@ -429,12 +370,29 @@ type FetchReason struct { IsRequires bool `json:"is_requires,omitempty"` } +// FetchInfo contains additional (derived) information about the fetch. +// Some fields may not be generated depending on planner flags. type FetchInfo struct { DataSourceID string DataSourceName string RootFields []GraphCoordinate OperationType ast.OperationType QueryPlan *QueryPlan + + // CoordinateDependencies contain a list of GraphCoordinates (typeName+fieldName) + // and which fields from other fetches they depend on. + // This information is useful to understand why a fetch depends on other fetches, + // and how multiple dependencies lead to a chain of fetches + CoordinateDependencies []FetchDependency + + // FetchReasons contains provenance for reasons why particular fields were fetched. + // If this structure is built, then all the fields are processed. + FetchReasons []FetchReason + + // PropagatedFetchReasons holds those FetchReasons that will be propagated + // with the request to the subgraph as part of the "fetch_reason" extension. + // Specifically, it is created only for fields stored in the DataSource.RequireFetchReasons(). + PropagatedFetchReasons []FetchReason } type GraphCoordinate struct { @@ -540,3 +498,11 @@ type WroteRequestStats struct { DurationSinceStartPretty string `json:"duration_since_start_pretty"` Err string `json:"err,omitempty"` } + +// Compile-time interface assertions to catch regressions. +var ( + _ Fetch = (*SingleFetch)(nil) + _ Fetch = (*BatchEntityFetch)(nil) + _ Fetch = (*EntityFetch)(nil) + _ Fetch = (*ParallelListItemFetch)(nil) +) diff --git a/v2/pkg/engine/resolve/fetchtree.go b/v2/pkg/engine/resolve/fetchtree.go index ad1eb2fcb8..f4fd987cea 100644 --- a/v2/pkg/engine/resolve/fetchtree.go +++ b/v2/pkg/engine/resolve/fetchtree.go @@ -216,7 +216,7 @@ func (n *FetchTreeNode) queryPlan() *FetchTreeQueryPlanNode { SubgraphName: f.Info.DataSourceName, SubgraphID: f.Info.DataSourceID, Path: n.Item.ResponsePath, - Dependencies: f.FetchConfiguration.CoordinateDependencies, + Dependencies: f.Info.CoordinateDependencies, } if f.Info.QueryPlan != nil { @@ -231,7 +231,7 @@ func (n *FetchTreeNode) queryPlan() *FetchTreeQueryPlanNode { SubgraphName: f.Info.DataSourceName, SubgraphID: f.Info.DataSourceID, Path: n.Item.ResponsePath, - Dependencies: f.CoordinateDependencies, + Dependencies: f.Info.CoordinateDependencies, } if f.Info.QueryPlan != nil { @@ -246,7 +246,7 @@ func (n *FetchTreeNode) queryPlan() *FetchTreeQueryPlanNode { SubgraphName: f.Info.DataSourceName, SubgraphID: f.Info.DataSourceID, Path: n.Item.ResponsePath, - Dependencies: f.CoordinateDependencies, + Dependencies: f.Info.CoordinateDependencies, } if f.Info.QueryPlan != nil { diff --git a/v2/pkg/engine/resolve/loader.go b/v2/pkg/engine/resolve/loader.go index 685383ca4e..1e9f993ec7 100644 --- a/v2/pkg/engine/resolve/loader.go +++ b/v2/pkg/engine/resolve/loader.go @@ -1599,10 +1599,10 @@ func (l *Loader) executeSourceLoad(ctx context.Context, fetchItem *FetchItem, so } } if l.propagateFetchReasons && !IsIntrospectionDataSource(res.ds.ID) { - fetchReasons := fetchItem.Fetch.FetchReasons() - if len(fetchReasons) > 0 { + info := fetchItem.Fetch.FetchInfo() + if info != nil && len(info.PropagatedFetchReasons) > 0 { var encoded []byte - encoded, res.err = json.Marshal(fetchReasons) + encoded, res.err = json.Marshal(info.PropagatedFetchReasons) if res.err != nil { res.err = errors.WithStack(res.err) return