Skip to content
Merged
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
38 changes: 38 additions & 0 deletions execution/engine/execution_engine_cost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,44 @@ func TestExecutionEngine_Cost(t *testing.T) {
computeCosts(),
))

// Regression test for the abstract field without __typename bug recordObjectTypeStats).
// When the subgraph resolves a single (non-list) abstract field and does NOT return __typename,
// we must still record one occurrence for that field's path, falling back to the declared
// abstract type name in actual costs.
t.Run("single abstract field without __typename takes into account implementing types", runWithoutError(
ExecutionEngineTestCase{
schema: graphql.StarwarsSchema(t),
operation: func(t *testing.T) graphql.Request {
return graphql.Request{
Query: `{ hero { ... on Human { height } } }`,
}
},
dataSources: []plan.DataSource{
mustGraphqlDataSourceConfiguration(t, "id",
mustFactory(t,
testNetHttpClient(t, roundTripperTestCase{
expectedHost: "example.com", expectedPath: "/", expectedBody: "",
// No __typename returned for the abstract hero field.
sendResponseBody: `{"data":{"hero":{"height":"12"}}}`,
sendStatusCode: 200,
}),
),
&plan.DataSourceMetadata{RootNodes: rootNodes, ChildNodes: childNodes, CostConfig: &plan.DataSourceCostConfig{
Weights: map[plan.FieldCoordinate]*plan.FieldCost{},
Types: map[string]int{
"Human": 13,
"Droid": 7,
},
}},
customConfig,
),
},
expectedEstimatedCost: intPtr(13), // Query.hero(13)
expectedActualCost: intPtr(13), // Query.hero(13)
},
computeCosts(),
))

t.Run("field returning a list of abstracts with partial fragments considers only actual type counts", runWithoutError(
ExecutionEngineTestCase{
schema: graphql.StarwarsSchema(t),
Expand Down
9 changes: 7 additions & 2 deletions v2/pkg/engine/resolve/resolvable.go
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,7 @@ func (r *Resolvable) walkArray(arr *Array, value *astjson.Value) bool {
// that resolves an abstract (interface/union) field.
func (r *Resolvable) recordObjectTypeStats(obj *Object, typeName []byte) {
// An array item Object has an empty Path
if len(obj.Path) == 0 || typeName == nil || !obj.isAbstract() {
if len(obj.Path) == 0 || !obj.isAbstract() {
return
}
pathKey := r.currentFieldPath()
Expand All @@ -1033,7 +1033,12 @@ func (r *Resolvable) recordObjectTypeStats(obj *Object, typeName []byte) {
if stats.TypeNames == nil {
stats.TypeNames = make(map[string]int, 1)
}
stats.TypeNames[string(typeName)]++
// Fall back to the declared abstract type name when the subgraph did not return __typename.
name := obj.TypeName
if typeName != nil {
name = string(typeName)
}
stats.TypeNames[name]++
r.typeNameStats[pathKey] = stats
}

Expand Down
Loading