Skip to content

Commit 9595102

Browse files
[Fusion] Fix __typename not being properly selected (#8680)
1 parent 4128aaa commit 9595102

13 files changed

+156
-39
lines changed

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/OperationPlanner.cs

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -857,11 +857,13 @@ private void PlanNodeLookup(
857857
backlog = backlog.Push(unresolvable, current);
858858
backlog = backlog.Push(fieldsWithRequirements, stepId);
859859

860-
var selectionSetNode = resolvable
861-
.WithSelections([
862-
new FieldNode(IntrospectionFieldNames.TypeName),
863-
..resolvable.Selections
864-
]);
860+
var resolvableSelections = resolvable.Selections;
861+
if (!resolvableSelections.Any(IsTypeNameSelection))
862+
{
863+
resolvableSelections = [new FieldNode(IntrospectionFieldNames.TypeName), ..resolvableSelections];
864+
}
865+
866+
var selectionSetNode = resolvable.WithSelections(resolvableSelections);
865867

866868
var indexBuilder = index.ToBuilder();
867869
indexBuilder.Register(workItem.SelectionSet.Id, selectionSetNode);
@@ -956,11 +958,13 @@ private void PlanNode(
956958

957959
(var sharedSelectionSet, var selectionSetsByType, index) = _selectionSetByTypePartitioner.Partition(input);
958960

959-
var nodeFieldSelectionSet =
960-
new SelectionSetNode([
961-
new FieldNode(IntrospectionFieldNames.TypeName),
962-
..sharedSelectionSet?.Selections ?? []
963-
]);
961+
var sharedSelections = sharedSelectionSet?.Selections ?? [];
962+
if (sharedSelections.Count < 1 || !sharedSelections.Any(IsTypeNameSelection))
963+
{
964+
sharedSelections = [new FieldNode(IntrospectionFieldNames.TypeName), ..sharedSelections];
965+
}
966+
967+
var nodeFieldSelectionSet = new SelectionSetNode(sharedSelections);
964968
var nodeFieldWithSelectionSet = nodeField.WithSelectionSet(nodeFieldSelectionSet);
965969
var fallbackQuerySelectionSet = new SelectionSetNode([nodeFieldWithSelectionSet]);
966970

@@ -1376,14 +1380,14 @@ private OperationDefinitionNode AddTypeNameToAbstractSelections(
13761380
var rewriter = SyntaxRewriter.Create<Stack<ITypeDefinition>>(
13771381
(node, path) =>
13781382
{
1379-
if (node is not FieldNode fieldNode || fieldNode.SelectionSet is null)
1383+
if (node is not FieldNode { SelectionSet: {} selectionSet } fieldNode)
13801384
{
13811385
return node;
13821386
}
13831387

13841388
var type = path.Peek();
13851389

1386-
if (type.IsAbstractType())
1390+
if (type.IsAbstractType() && !selectionSet.Selections.Any(IsTypeNameSelection))
13871391
{
13881392
// we add the __typename field to all selection sets that have
13891393
// an abstract type context as we need the type context for
@@ -1393,8 +1397,9 @@ private OperationDefinitionNode AddTypeNameToAbstractSelections(
13931397
// required __typename and a runtime required type information.
13941398
var typenameNode = new FieldNode(IntrospectionFieldNames.TypeName)
13951399
.WithDirectives([new DirectiveNode("fusion__requirement")]);
1400+
13961401
return fieldNode.WithSelectionSet(new SelectionSetNode([
1397-
typenameNode, ..fieldNode.SelectionSet.Selections
1402+
typenameNode, ..selectionSet.Selections
13981403
]));
13991404
}
14001405

@@ -1430,6 +1435,17 @@ private OperationDefinitionNode AddTypeNameToAbstractSelections(
14301435
return (OperationDefinitionNode)rewriter.Rewrite(operation, context)!;
14311436
}
14321437

1438+
private static bool IsTypeNameSelection(ISelectionNode selection)
1439+
{
1440+
if (selection is FieldNode field)
1441+
{
1442+
return field.Name.Value.Equals(IntrospectionFieldNames.TypeName)
1443+
&& field.Alias is null;
1444+
}
1445+
1446+
return false;
1447+
}
1448+
14331449
private readonly record struct PlanResult(
14341450
OperationDefinitionNode InternalOperationDefinition,
14351451
ImmutableList<PlanStep> Steps,

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/Partitioners/SelectionSetPartitioner.cs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,22 @@ public SelectionSetPartitionerResult Partition(
102102

103103
context.Nodes.Pop();
104104

105-
if (resolvableSelections is null && unresolvableSelections is null)
105+
var isAbstractType = type.NamedType().IsAbstractType();
106+
107+
if (resolvableSelections is null && unresolvableSelections is null && !isAbstractType)
106108
{
107109
return (selectionSetNode, null);
108110
}
109111

110112
if (unresolvableSelections is not null)
111113
{
114+
if (isAbstractType && !unresolvableSelections.Any(IsTypeNameSelection))
115+
{
116+
unresolvableSelections = [
117+
new FieldNode(IntrospectionFieldNames.TypeName),
118+
..unresolvableSelections];
119+
}
120+
112121
var unresolvableSelectionSet = new SelectionSetNode(unresolvableSelections);
113122
context.Register(selectionSetNode, unresolvableSelectionSet);
114123

@@ -121,17 +130,19 @@ public SelectionSetPartitionerResult Partition(
121130
unresolvableSelections = null;
122131
}
123132

124-
var resolvableSelections2 = resolvableSelections ?? selectionSetNode.Selections;
133+
resolvableSelections ??= [..selectionSetNode.Selections];
125134

126-
if (type.NamedType().IsAbstractType())
135+
if (isAbstractType && !resolvableSelections.Any(IsTypeNameSelection))
127136
{
128-
resolvableSelections2 = [new FieldNode(IntrospectionFieldNames.TypeName), ..resolvableSelections2];
137+
resolvableSelections = [
138+
new FieldNode(IntrospectionFieldNames.TypeName),
139+
..resolvableSelections];
129140
}
130141

131142
var result =
132143
(
133144
Resolvable:
134-
selectionSetNode.WithSelections(resolvableSelections2),
145+
selectionSetNode.WithSelections(resolvableSelections),
135146
Unresolvable: unresolvableSelections is not null
136147
? selectionSetNode.WithSelections(unresolvableSelections)
137148
: null
@@ -192,6 +203,17 @@ void CompleteSelection<T>(T original, T? resolvable, T? unresolvable, int index)
192203
// todo match correct inline fragment
193204
return providedSelectionSetNode;
194205
}
206+
207+
static bool IsTypeNameSelection(ISelectionNode selection)
208+
{
209+
if (selection is FieldNode field)
210+
{
211+
return field.Name.Value.Equals(IntrospectionFieldNames.TypeName)
212+
&& field.Alias is null;
213+
}
214+
215+
return false;
216+
}
195217
}
196218

197219
private (FieldNode?, FieldNode?) RewriteFieldNode(

src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/AbstractTypeTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,47 @@ ... on Author {
5050
response.MatchSnapshot();
5151
}
5252

53+
[Fact]
54+
public async Task Abstract_Type_Direct_Source_Schema_Call()
55+
{
56+
// arrange
57+
using var server1 = CreateSourceSchema(
58+
"A",
59+
b => b
60+
.AddQueryType<SourceSchema1.Query>()
61+
.AddType<SourceSchema1.Discussion>());
62+
63+
// act
64+
using var gateway = await CreateCompositeSchemaAsync(
65+
[
66+
("A", server1)
67+
]);
68+
69+
// assert
70+
using var client = GraphQLHttpClient.Create(gateway.CreateClient());
71+
72+
using var result = await client.PostAsync(
73+
"""
74+
query {
75+
interfaceConnection(first: 2) {
76+
edges {
77+
node {
78+
id
79+
}
80+
}
81+
pageInfo {
82+
hasNextPage
83+
}
84+
}
85+
}
86+
""",
87+
new Uri("http://localhost:5000/graphql"));
88+
89+
// act
90+
using var response = await result.ReadAsResultAsync();
91+
response.MatchSnapshot();
92+
}
93+
5394
[Fact]
5495
public async Task Abstract_Type_With_Concrete_Lookup()
5596
{
@@ -240,6 +281,10 @@ public class Query
240281

241282
public List<SharedType> GetAbstractTypes() => [new Discussion(1), new Author(2), new Product(3)];
242283

284+
[UsePaging]
285+
public IEnumerable<SharedType> InterfaceConnection()
286+
=> [new Discussion(1)];
287+
243288
[Lookup]
244289
public Author GetAuthorById(int id) => new Author(id);
245290
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"data": {
3+
"interfaceConnection": {
4+
"edges": [
5+
{
6+
"node": {
7+
"id": 1
8+
}
9+
}
10+
],
11+
"pageInfo": {
12+
"hasNextPage": false
13+
}
14+
}
15+
},
16+
"extensions": {
17+
"fusion": {
18+
"operationPlan": {
19+
"id": "d16d3ad290a364c80b9616ef4cd80259628a504bddc7d1ef55a260758959adbe",
20+
"operation": {
21+
"kind": "Query",
22+
"document": "{\n interfaceConnection(first: 2) {\n edges {\n node {\n __typename @fusion__requirement\n id\n }\n }\n pageInfo {\n hasNextPage\n }\n }\n}",
23+
"id": "45eb4c09cd33c661467dd26f8f97b5ad",
24+
"hash": "45eb4c09cd33c661467dd26f8f97b5ad",
25+
"shortHash": "45eb4c09"
26+
},
27+
"searchSpace": 1,
28+
"nodes": [
29+
{
30+
"id": 1,
31+
"type": "Operation",
32+
"schema": "A",
33+
"operation": {
34+
"name": "Op_45eb4c09_1",
35+
"kind": "Query",
36+
"document": "query Op_45eb4c09_1 {\n interfaceConnection(first: 2) {\n edges {\n node {\n __typename\n id\n }\n }\n pageInfo {\n hasNextPage\n }\n }\n}",
37+
"hash": "ec5a97cdb5e116835c35d228dad9aa98e67468e436de260c0f03ae7d44044e78",
38+
"shortHash": "ec5a97cd"
39+
},
40+
"responseNames": [
41+
"interfaceConnection"
42+
]
43+
}
44+
]
45+
}
46+
}
47+
}
48+
}

src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/GlobalObjectIdentificationTests.Only_TypeName_Selected_On_Concrete_Type.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"extensions": {
88
"fusion": {
99
"operationPlan": {
10-
"id": "17f3bedec7fd1e8ea47f31347103804992c87b23a6005bde9286dcc52e88c060",
10+
"id": "bcdc83b0403a3cc4a375d56494927687654063caf35a6fdfe03f6f4146b12b8a",
1111
"operation": {
1212
"kind": "Query",
1313
"document": "{\n node(id: \"RGlzY3Vzc2lvbjox\") {\n __typename @fusion__requirement\n ... on Discussion {\n __typename\n }\n }\n}",
@@ -51,9 +51,9 @@
5151
"operation": {
5252
"name": "Op_72167d7d_3",
5353
"kind": "Query",
54-
"document": "query Op_72167d7d_3 {\n node: discussionById(discussionId: \"RGlzY3Vzc2lvbjox\") {\n __typename\n __typename\n }\n}",
55-
"hash": "1a577bbb61a345b7eb4307da4f0a5fe835b9b58331bc96c0685594381b0c31df",
56-
"shortHash": "1a577bbb"
54+
"document": "query Op_72167d7d_3 {\n node: discussionById(discussionId: \"RGlzY3Vzc2lvbjox\") {\n __typename\n }\n}",
55+
"hash": "cf9d08901253a456552f6be60517863eba31e024544cf744d8139eb93355ac5b",
56+
"shortHash": "cf9d0890"
5757
},
5858
"responseNames": [
5959
"node"

src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Planning/__snapshots__/GlobalObjectIdentificationTests.Node_FIeld_Selections_On_Interface_And_Concrete_Type_Both_Have_Different_Dependencies.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ operation:
44
$id: ID!
55
) {
66
node(id: $id) {
7-
__typename @fusion__requirement
87
__typename
98
id
109
... on ProductList {
@@ -41,7 +40,6 @@ nodes:
4140
$id: ID!
4241
) {
4342
node(id: $id) {
44-
__typename
4543
__typename
4644
id
4745
}
@@ -58,7 +56,6 @@ nodes:
5856
node(id: $id) {
5957
__typename
6058
... on Item2 {
61-
__typename
6259
__typename
6360
id
6461
products {
@@ -126,7 +123,6 @@ nodes:
126123
node(id: $id) {
127124
__typename
128125
... on Item1 {
129-
__typename
130126
__typename
131127
id
132128
products {

src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Planning/__snapshots__/GlobalObjectIdentificationTests.Node_Field_Alongside_Regular_Root_Selections.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ operation:
77
username
88
}
99
node(id: $id) {
10-
__typename @fusion__requirement
1110
__typename
1211
... on Discussion {
1312
title
@@ -33,7 +32,6 @@ nodes:
3332
) {
3433
node(id: $id) {
3534
__typename
36-
__typename
3735
}
3836
}
3937
dependencies:
@@ -48,7 +46,6 @@ nodes:
4846
node(id: $id) {
4947
__typename
5048
... on Discussion {
51-
__typename
5249
__typename
5350
title
5451
}

src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Planning/__snapshots__/GlobalObjectIdentificationTests.Node_Field_Just_Id_And_Typename_Selected.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ operation:
44
$id: ID!
55
) {
66
node(id: $id) {
7-
__typename @fusion__requirement
8-
__typename
97
id
8+
__typename
109
}
1110
}
1211
name: testQuery
@@ -26,7 +25,6 @@ nodes:
2625
$id: ID!
2726
) {
2827
node(id: $id) {
29-
__typename
3028
id
3129
__typename
3230
}

src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Planning/__snapshots__/GlobalObjectIdentificationTests.Node_Field_Just_Typename_Selected.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ operation:
44
$id: ID!
55
) {
66
node(id: $id) {
7-
__typename @fusion__requirement
87
__typename
98
}
109
}
@@ -26,7 +25,6 @@ nodes:
2625
) {
2726
node(id: $id) {
2827
__typename
29-
__typename
3028
}
3129
}
3230
dependencies:

0 commit comments

Comments
 (0)