Skip to content

Commit 998bd3f

Browse files
authored
Fixed node field projections when nothing is selected. (#7554)
1 parent a41690e commit 998bd3f

File tree

5 files changed

+169
-10
lines changed

5 files changed

+169
-10
lines changed

src/HotChocolate/Core/src/Execution/Projections/SelectionExpressionBuilder.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public Expression<Func<TRoot, TRoot>> BuildNodeExpression<TRoot>(ISelection sele
5454

5555
if (typeNode.Nodes.Count == 0)
5656
{
57-
TryAddAnyLeafField(selection, typeNode);
57+
TryAddAnyLeafField(typeNode, entityType);
5858
}
5959

6060
CollectTypes(context, selection, root);
@@ -89,7 +89,7 @@ private void CollectTypes(Context context, ISelection selection, TypeContainer p
8989

9090
if (possibleTypeNode.Nodes.Count == 0)
9191
{
92-
TryAddAnyLeafField(selection, possibleTypeNode);
92+
TryAddAnyLeafField(possibleTypeNode, possibleType);
9393
}
9494
}
9595

@@ -104,7 +104,7 @@ private void CollectTypes(Context context, ISelection selection, TypeContainer p
104104

105105
if (typeNode.Nodes.Count == 0)
106106
{
107-
TryAddAnyLeafField(selection, typeNode);
107+
TryAddAnyLeafField(typeNode, objectType);
108108
}
109109
}
110110

@@ -200,22 +200,21 @@ private void CollectSelection(
200200
}
201201

202202
private static void TryAddAnyLeafField(
203-
ISelection selection,
204-
TypeNode parent)
203+
TypeNode parent,
204+
IObjectType selectionType)
205205
{
206206
// if we could not collect anything it means that either all fields
207207
// are skipped or that __typename is the only field that is selected.
208208
// in this case we will try to select the id field or if that does
209209
// not exist we will look for a leaf field that we can select.
210-
var type = (ObjectType)selection.Type.NamedType();
211-
if (type.Fields.TryGetField("id", out var idField)
210+
if (selectionType.Fields.TryGetField("id", out var idField)
212211
&& idField.Member is PropertyInfo idProperty)
213212
{
214213
parent.AddOrGetNode(idProperty);
215214
}
216215
else
217216
{
218-
var anyProperty = type.Fields.FirstOrDefault(t => t.Type.IsLeafType() && t.Member is PropertyInfo);
217+
var anyProperty = selectionType.Fields.FirstOrDefault(t => t.Type.IsLeafType() && t.Member is PropertyInfo);
219218
if (anyProperty?.Member is PropertyInfo anyPropertyInfo)
220219
{
221220
parent.AddOrGetNode(anyPropertyInfo);

src/HotChocolate/Core/test/Execution.Tests/Projections/ProjectableDataLoaderTests.cs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using HotChocolate.Execution.Processing;
66
using HotChocolate.Execution.TestContext;
77
using HotChocolate.Types;
8+
using HotChocolate.Types.Relay;
89
using Microsoft.EntityFrameworkCore;
910
using Microsoft.Extensions.DependencyInjection;
1011
using Squadron;
@@ -595,6 +596,108 @@ public async Task Brand_Only_TypeName()
595596
.MatchMarkdownSnapshot();
596597
}
597598

599+
600+
[Fact]
601+
public async Task Brand_With_Id_And_Name_Over_Node()
602+
{
603+
// Arrange
604+
var queries = new List<string>();
605+
var connectionString = CreateConnectionString();
606+
await CatalogContext.SeedAsync(connectionString);
607+
608+
// Act
609+
var result = await new ServiceCollection()
610+
.AddScoped(_ => queries)
611+
.AddTransient(_ => new CatalogContext(connectionString))
612+
.AddGraphQL()
613+
.AddQueryType<NodeQuery>()
614+
.AddGlobalObjectIdentification()
615+
.AddPagingArguments()
616+
.ModifyRequestOptions(o => o.IncludeExceptionDetails = true)
617+
.ExecuteRequestAsync(
618+
"""
619+
{
620+
node(id: "QnJhbmQ6MQ==") {
621+
id
622+
... on Brand {
623+
name
624+
}
625+
}
626+
}
627+
""");
628+
629+
Snapshot.Create()
630+
.AddSql(queries)
631+
.AddResult(result)
632+
.MatchMarkdownSnapshot();
633+
}
634+
635+
[Fact]
636+
public async Task Brand_With_Name_Over_Node()
637+
{
638+
// Arrange
639+
var queries = new List<string>();
640+
var connectionString = CreateConnectionString();
641+
await CatalogContext.SeedAsync(connectionString);
642+
643+
// Act
644+
var result = await new ServiceCollection()
645+
.AddScoped(_ => queries)
646+
.AddTransient(_ => new CatalogContext(connectionString))
647+
.AddGraphQL()
648+
.AddQueryType<NodeQuery>()
649+
.AddGlobalObjectIdentification()
650+
.AddPagingArguments()
651+
.ModifyRequestOptions(o => o.IncludeExceptionDetails = true)
652+
.ExecuteRequestAsync(
653+
"""
654+
{
655+
node(id: "QnJhbmQ6MQ==") {
656+
... on Brand {
657+
name
658+
}
659+
}
660+
}
661+
""");
662+
663+
Snapshot.Create()
664+
.AddSql(queries)
665+
.AddResult(result)
666+
.MatchMarkdownSnapshot();
667+
}
668+
669+
[Fact]
670+
public async Task Brand_With_Default_Field_Over_Node()
671+
{
672+
// Arrange
673+
var queries = new List<string>();
674+
var connectionString = CreateConnectionString();
675+
await CatalogContext.SeedAsync(connectionString);
676+
677+
// Act
678+
var result = await new ServiceCollection()
679+
.AddScoped(_ => queries)
680+
.AddTransient(_ => new CatalogContext(connectionString))
681+
.AddGraphQL()
682+
.AddQueryType<NodeQuery>()
683+
.AddGlobalObjectIdentification()
684+
.AddPagingArguments()
685+
.ModifyRequestOptions(o => o.IncludeExceptionDetails = true)
686+
.ExecuteRequestAsync(
687+
"""
688+
{
689+
node(id: "QnJhbmQ6MQ==") {
690+
__typename
691+
}
692+
}
693+
""");
694+
695+
Snapshot.Create()
696+
.AddSql(queries)
697+
.AddResult(result)
698+
.MatchMarkdownSnapshot();
699+
}
700+
598701
public class Query
599702
{
600703
public async Task<Brand?> GetBrandByIdAsync(
@@ -639,6 +742,17 @@ public class Query
639742
=> await brandById.Select(default(Expression<Func<Brand, Brand>>)).LoadAsync(id, cancellationToken);
640743
}
641744

745+
public class NodeQuery
746+
{
747+
[NodeResolver]
748+
public async Task<Brand?> GetBrandByIdAsync(
749+
int id,
750+
ISelection selection,
751+
BrandByIdDataLoader brandById,
752+
CancellationToken cancellationToken)
753+
=> await brandById.Select(selection).LoadAsync(id, cancellationToken);
754+
}
755+
642756
[ExtendObjectType<Brand>]
643757
public class BrandExtensions
644758
{
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Brand_With_Default_Field_Over_Node
2+
3+
## SQL
4+
5+
```text
6+
-- @__keys_0={ '1' } (DbType = Object)
7+
SELECT b."Id"
8+
FROM "Brands" AS b
9+
WHERE b."Id" = ANY (@__keys_0)
10+
```
11+
12+
## Result
13+
14+
```json
15+
{
16+
"data": {
17+
"node": {
18+
"__typename": "Brand"
19+
}
20+
}
21+
}
22+
```
23+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Brand_With_Id_And_Name_Over_Node
2+
3+
## SQL
4+
5+
```text
6+
-- @__keys_0={ '1' } (DbType = Object)
7+
SELECT b."Id", b."Name"
8+
FROM "Brands" AS b
9+
WHERE b."Id" = ANY (@__keys_0)
10+
```
11+
12+
## Result
13+
14+
```json
15+
{
16+
"data": {
17+
"node": {
18+
"id": "QnJhbmQ6MQ==",
19+
"name": "Brand0"
20+
}
21+
}
22+
}
23+
```
24+

src/HotChocolate/Core/test/Execution.Tests/Projections/__snapshots__/ProjectableDataLoaderTests.Brand_With_Name_Over_Node.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
```text
66
-- @__keys_0={ '1' } (DbType = Object)
7-
SELECT b."Id", b."Name"
7+
SELECT b."Name", b."Id"
88
FROM "Brands" AS b
99
WHERE b."Id" = ANY (@__keys_0)
1010
```
@@ -15,7 +15,6 @@ WHERE b."Id" = ANY (@__keys_0)
1515
{
1616
"data": {
1717
"node": {
18-
"id": "QnJhbmQ6MQ==",
1918
"name": "Brand0"
2019
}
2120
}

0 commit comments

Comments
 (0)