Skip to content

Commit 8ce8ced

Browse files
authored
Fixed issue when projection on node fields. (#7551)
1 parent fe1f216 commit 8ce8ced

File tree

4 files changed

+103
-8
lines changed

4 files changed

+103
-8
lines changed

src/HotChocolate/Core/src/Execution/Extensions/HotChocolateExecutionSelectionExtensions.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ public static Expression<Func<TValue, TValue>> AsSelector<TValue>(
7474
return GetOrCreateExpression<TValue>(selection, builder);
7575
}
7676

77+
if ((flags & FieldFlags.NodesField) == FieldFlags.NodesField)
78+
{
79+
return GetOrCreateNodeExpression<TValue>(selection);
80+
}
81+
7782
return GetOrCreateExpression<TValue>(selection);
7883
}
7984

@@ -93,6 +98,13 @@ private static Expression<Func<TValue, TValue>> GetOrCreateExpression<TValue>(
9398
static (_, ctx) => ctx.builder.TryCompile<TValue>()!,
9499
(builder, selection));
95100

101+
private static Expression<Func<TValue, TValue>> GetOrCreateNodeExpression<TValue>(
102+
ISelection selection)
103+
=> selection.DeclaringOperation.GetOrAddState(
104+
CreateNodeExpressionKey<TValue>(selection.Id),
105+
static (_, ctx) => ctx._builder.BuildNodeExpression<TValue>(ctx.selection),
106+
(_builder, selection));
107+
96108
private static bool TryGetExpression<TValue>(
97109
ISelection selection,
98110
[NotNullWhen(true)] out Expression<Func<TValue, TValue>>? expression)
@@ -170,6 +182,30 @@ private static string CreateExpressionKey(int key)
170182
return Encoding.UTF8.GetString(span.Slice(0, written + keyPrefix.Length));
171183
}
172184

185+
private static string CreateNodeExpressionKey<TValue>(int key)
186+
{
187+
var typeName = typeof(TValue).FullName!;
188+
var typeNameLength = Encoding.UTF8.GetMaxByteCount(typeName.Length);
189+
var keyPrefix = GetKeyPrefix();
190+
var requiredBufferSize = EstimateIntLength(key) + keyPrefix.Length + typeNameLength;
191+
byte[]? rented = null;
192+
var span = requiredBufferSize <= 256
193+
? stackalloc byte[requiredBufferSize]
194+
: (rented = ArrayPool<byte>.Shared.Rent(requiredBufferSize));
195+
196+
keyPrefix.CopyTo(span);
197+
Utf8Formatter.TryFormat(key, span.Slice(keyPrefix.Length), out var written, 'D');
198+
var typeNameWritten = Encoding.UTF8.GetBytes(typeName, span.Slice(written + keyPrefix.Length));
199+
var keyString = Encoding.UTF8.GetString(span.Slice(0, written + keyPrefix.Length + typeNameWritten));
200+
201+
if (rented is not null)
202+
{
203+
ArrayPool<byte>.Shared.Return(rented);
204+
}
205+
206+
return keyString;
207+
}
208+
173209
private static ReadOnlySpan<byte> GetKeyPrefix()
174210
=> "hc-dataloader-expr-"u8;
175211

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

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,44 @@ public Expression<Func<TRoot, TRoot>> BuildExpression<TRoot>(ISelection selectio
3131
return Expression.Lambda<Func<TRoot, TRoot>>(selectionSetExpression, parameter);
3232
}
3333

34+
public Expression<Func<TRoot, TRoot>> BuildNodeExpression<TRoot>(ISelection selection)
35+
{
36+
var rootType = typeof(TRoot);
37+
var parameter = Expression.Parameter(rootType, "root");
38+
var requirements = selection.DeclaringOperation.Schema.Features.GetRequired<FieldRequirementsMetadata>();
39+
var context = new Context(parameter, rootType, requirements);
40+
var root = new TypeContainer();
41+
42+
var entityType = selection.DeclaringOperation.GetPossibleTypes(selection).FirstOrDefault(t => t.RuntimeType == typeof(TRoot));
43+
44+
if (entityType is null)
45+
{
46+
throw new InvalidOperationException(
47+
$"Unable to resolve the entity type from `{typeof(TRoot).FullName}`.");
48+
}
49+
50+
var typeNode = new TypeNode(entityType.RuntimeType);
51+
var selectionSet = selection.DeclaringOperation.GetSelectionSet(selection, entityType);
52+
CollectSelections(context, selectionSet, typeNode);
53+
root.TryAddNode(typeNode);
54+
55+
if (typeNode.Nodes.Count == 0)
56+
{
57+
TryAddAnyLeafField(selection, typeNode);
58+
}
59+
60+
CollectTypes(context, selection, root);
61+
62+
var selectionSetExpression = BuildTypeSwitchExpression(context, root);
63+
64+
if (selectionSetExpression is null)
65+
{
66+
throw new InvalidOperationException("The selection set is empty.");
67+
}
68+
69+
return Expression.Lambda<Func<TRoot, TRoot>>(selectionSetExpression, parameter);
70+
}
71+
3472
private void CollectTypes(Context context, ISelection selection, TypeContainer parent)
3573
{
3674
var namedType = selection.Type.NamedType();
@@ -49,11 +87,12 @@ private void CollectTypes(Context context, ISelection selection, TypeContainer p
4987
CollectSelections(context, possibleSelectionSet, possibleTypeNode);
5088
parent.TryAddNode(possibleTypeNode);
5189

52-
if(possibleTypeNode.Nodes.Count == 0)
90+
if (possibleTypeNode.Nodes.Count == 0)
5391
{
5492
TryAddAnyLeafField(selection, possibleTypeNode);
5593
}
5694
}
95+
5796
return;
5897
}
5998

@@ -63,7 +102,7 @@ private void CollectTypes(Context context, ISelection selection, TypeContainer p
63102
CollectSelections(context, selectionSet, typeNode);
64103
parent.TryAddNode(typeNode);
65104

66-
if(typeNode.Nodes.Count == 0)
105+
if (typeNode.Nodes.Count == 0)
67106
{
68107
TryAddAnyLeafField(selection, typeNode);
69108
}
@@ -80,7 +119,7 @@ private void CollectTypes(Context context, ISelection selection, TypeContainer p
80119
foreach (var typeNode in parent.Nodes)
81120
{
82121
var newParent = Expression.Convert(context.Parent, typeNode.Type);
83-
var newContext = context with { Parent = newParent, ParentType = typeNode.Type };
122+
var newContext = context with { Parent = newParent, ParentType = typeNode.Type };
84123
var typeCondition = Expression.TypeIs(context.Parent, typeNode.Type);
85124
var selectionSet = BuildSelectionSetExpression(newContext, typeNode);
86125

@@ -225,7 +264,7 @@ private void CollectSelections(
225264
return Expression.Bind(node.Property, propertyAccessor);
226265
}
227266

228-
if(node.IsArrayOrCollection)
267+
if (node.IsArrayOrCollection)
229268
{
230269
throw new NotSupportedException("List projections are not supported.");
231270
}

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
using HotChocolate.Execution.Processing;
66
using HotChocolate.Execution.TestContext;
77
using HotChocolate.Types;
8-
<<<<<<< Updated upstream
9-
=======
10-
using HotChocolate.Types.Relay;
11-
>>>>>>> Stashed changes
128
using Microsoft.EntityFrameworkCore;
139
using Microsoft.Extensions.DependencyInjection;
1410
using Squadron;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Brand_With_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+

0 commit comments

Comments
 (0)