Skip to content

Commit 82886c5

Browse files
committed
Fixed issue where projections fallback was not possible. (#8139)
1 parent 16095a7 commit 82886c5

16 files changed

+269
-423
lines changed

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

+42
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,32 @@ namespace HotChocolate.Execution.Projections;
1111

1212
internal sealed class SelectionExpressionBuilder
1313
{
14+
private static readonly NullabilityInfoContext _nullabilityInfoContext = new();
15+
private static readonly HashSet<Type> _runtimeLeafTypes =
16+
[
17+
typeof(string),
18+
typeof(byte),
19+
typeof(short),
20+
typeof(int),
21+
typeof(long),
22+
typeof(float),
23+
typeof(byte),
24+
typeof(decimal),
25+
typeof(Guid),
26+
typeof(bool),
27+
typeof(char),
28+
typeof(byte?),
29+
typeof(short?),
30+
typeof(int?),
31+
typeof(long?),
32+
typeof(float?),
33+
typeof(byte?),
34+
typeof(decimal?),
35+
typeof(Guid?),
36+
typeof(bool?),
37+
typeof(char?)
38+
];
39+
1440
public Expression<Func<TRoot, TRoot>> BuildExpression<TRoot>(ISelection selection)
1541
{
1642
var rootType = typeof(TRoot);
@@ -214,11 +240,27 @@ private static void TryAddAnyLeafField(
214240
}
215241
else
216242
{
243+
// if id does not exist we will try to select any leaf field from the type.
217244
var anyProperty = selectionType.Fields.FirstOrDefault(t => t.Type.IsLeafType() && t.Member is PropertyInfo);
245+
218246
if (anyProperty?.Member is PropertyInfo anyPropertyInfo)
219247
{
220248
parent.AddOrGetNode(anyPropertyInfo);
221249
}
250+
else
251+
{
252+
// if we still have not found any leaf we will inspect the runtime type and
253+
// try to select any leaf property.
254+
var properties = selectionType.RuntimeType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
255+
foreach (var property in properties)
256+
{
257+
if (_runtimeLeafTypes.Contains(property.PropertyType))
258+
{
259+
parent.AddOrGetNode(property);
260+
break;
261+
}
262+
}
263+
}
222264
}
223265
}
224266

src/HotChocolate/Core/src/Execution/RequestExecutorResolver.cs

+23-16
Original file line numberDiff line numberDiff line change
@@ -107,30 +107,37 @@ private async ValueTask ConsumeExecutorEvictionsAsync(
107107
ChannelReader<string> reader,
108108
CancellationToken cancellationToken)
109109
{
110-
while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false))
110+
try
111111
{
112-
while (reader.TryRead(out var schemaName))
112+
while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false))
113113
{
114-
var semaphore = GetSemaphoreForSchema(schemaName);
115-
await semaphore.WaitAsync(cancellationToken);
116-
117-
try
114+
while (reader.TryRead(out var schemaName))
118115
{
119-
if (_executors.TryGetValue(schemaName, out var previousExecutor))
116+
var semaphore = GetSemaphoreForSchema(schemaName);
117+
await semaphore.WaitAsync(cancellationToken);
118+
119+
try
120120
{
121-
await UpdateRequestExecutorAsync(schemaName, previousExecutor);
121+
if (_executors.TryGetValue(schemaName, out var previousExecutor))
122+
{
123+
await UpdateRequestExecutorAsync(schemaName, previousExecutor);
124+
}
125+
}
126+
catch
127+
{
128+
// Ignore
129+
}
130+
finally
131+
{
132+
semaphore.Release();
122133
}
123-
}
124-
catch
125-
{
126-
// Ignore
127-
}
128-
finally
129-
{
130-
semaphore.Release();
131134
}
132135
}
133136
}
137+
catch (OperationCanceledException)
138+
{
139+
// ignore
140+
}
134141
}
135142

136143
private SemaphoreSlim GetSemaphoreForSchema(string schemaName)

src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Data/CatalogContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public class CatalogContext(DbContextOptions<CatalogContext> options) : DbContex
1212

1313
public DbSet<Brand> Brands => Set<Brand>();
1414

15+
public DbSet<SingleProperty> SingleProperties => Set<SingleProperty>();
16+
1517
protected override void OnModelCreating(ModelBuilder builder)
1618
{
1719
builder.ApplyConfiguration(new BrandEntityTypeConfiguration());

src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs

+42
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,48 @@ public async Task Query_Products_Exclude_TotalCount()
360360
MatchSnapshot(result, interceptor);
361361
}
362362

363+
[Fact]
364+
public async Task Ensure_That_Self_Requirement_Is_Honored()
365+
{
366+
// arrange
367+
using var interceptor = new TestQueryInterceptor();
368+
369+
// act
370+
var result = await ExecuteAsync(
371+
"""
372+
{
373+
singleProperties {
374+
id
375+
}
376+
}
377+
378+
""");
379+
380+
// assert
381+
MatchSnapshot(result, interceptor);
382+
}
383+
384+
[Fact]
385+
public async Task Fallback_To_Runtime_Properties_When_No_Field_Is_Bindable()
386+
{
387+
// arrange
388+
using var interceptor = new TestQueryInterceptor();
389+
390+
// act
391+
var result = await ExecuteAsync(
392+
"""
393+
{
394+
singleProperties {
395+
__typename
396+
}
397+
}
398+
399+
""");
400+
401+
// assert
402+
MatchSnapshot(result, interceptor);
403+
}
404+
363405
private static ServiceProvider CreateServer(string connectionString)
364406
{
365407
var services = new ServiceCollection();

src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Migrations/20240317214259_Initial.Designer.cs

-148
This file was deleted.

0 commit comments

Comments
 (0)