From 256dd0701018457bb6b06dff34520e07397c2d71 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 30 Sep 2024 15:23:52 +0200 Subject: [PATCH] Fixed issue when required data was present in query. (#7536) --- .../Projections/IPropertyNodeProvider.cs | 12 +++ .../src/Execution/Projections/PropertyNode.cs | 76 ------------------ .../Projections/PropertyNodeContainer.cs | 79 +++++++++++++++++++ .../Projections/SelectionExpressionBuilder.cs | 2 +- .../Projections/ProjectableDataLoaderTests.cs | 33 ++++++++ ...sts.Brand_Details_Requires_Brand_Name_2.md | 24 ++++++ 6 files changed, 149 insertions(+), 77 deletions(-) create mode 100644 src/HotChocolate/Core/src/Execution/Projections/IPropertyNodeProvider.cs create mode 100644 src/HotChocolate/Core/src/Execution/Projections/PropertyNodeContainer.cs create mode 100644 src/HotChocolate/Core/test/Execution.Tests/Projections/__snapshots__/ProjectableDataLoaderTests.Brand_Details_Requires_Brand_Name_2.md diff --git a/src/HotChocolate/Core/src/Execution/Projections/IPropertyNodeProvider.cs b/src/HotChocolate/Core/src/Execution/Projections/IPropertyNodeProvider.cs new file mode 100644 index 00000000000..9cb965c81a1 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Projections/IPropertyNodeProvider.cs @@ -0,0 +1,12 @@ +using System.Reflection; + +namespace HotChocolate.Execution.Projections; + +internal interface IPropertyNodeProvider +{ + IReadOnlyList Nodes { get; } + + PropertyNode AddOrGetNode(PropertyInfo property); + + void TryAddNode(PropertyNode newNode); +} diff --git a/src/HotChocolate/Core/src/Execution/Projections/PropertyNode.cs b/src/HotChocolate/Core/src/Execution/Projections/PropertyNode.cs index 39c793de8ae..e7484a076cd 100644 --- a/src/HotChocolate/Core/src/Execution/Projections/PropertyNode.cs +++ b/src/HotChocolate/Core/src/Execution/Projections/PropertyNode.cs @@ -90,79 +90,3 @@ public PropertyNode Clone() return null; } } - -internal class PropertyNodeContainer( - List? nodes = null) - : IPropertyNodeProvider -{ - private static readonly IReadOnlyList _emptyNodes = Array.Empty(); - private List? _nodes = nodes; - private bool _sealed; - - public IReadOnlyList Nodes - => _nodes ?? _emptyNodes; - - public PropertyNode AddOrGetNode(PropertyInfo property) - { - if (_sealed) - { - throw new InvalidOperationException("The property node container is sealed."); - } - - _nodes ??= new(); - - foreach (var node in Nodes) - { - if (node.Property.Name.Equals(property.Name)) - { - return node; - } - } - - var newNode = new PropertyNode(property); - _nodes.Add(newNode); - return newNode; - } - - public void AddNode(PropertyNode newNode) - { - if (_sealed) - { - throw new InvalidOperationException("The property node container is sealed."); - } - - _nodes ??= new(); - - foreach (var node in Nodes) - { - if (node.Property.Name.Equals(node.Property.Name)) - { - throw new InvalidOperationException("Duplicate property."); - } - } - - _nodes.Add(newNode); - } - - public void Seal() - { - if (!_sealed) - { - foreach (var node in Nodes) - { - node.Seal(); - } - - _sealed = true; - } - } -} - -internal interface IPropertyNodeProvider -{ - IReadOnlyList Nodes { get; } - - PropertyNode AddOrGetNode(PropertyInfo property); - - void AddNode(PropertyNode newNode); -} diff --git a/src/HotChocolate/Core/src/Execution/Projections/PropertyNodeContainer.cs b/src/HotChocolate/Core/src/Execution/Projections/PropertyNodeContainer.cs new file mode 100644 index 00000000000..b2135a09e2e --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Projections/PropertyNodeContainer.cs @@ -0,0 +1,79 @@ +using System.Reflection; + +namespace HotChocolate.Execution.Projections; + +internal class PropertyNodeContainer( + List? nodes = null) + : IPropertyNodeProvider +{ + private static readonly IReadOnlyList _emptyNodes = Array.Empty(); + private List? _nodes = nodes; + private bool _sealed; + + public IReadOnlyList Nodes + => _nodes ?? _emptyNodes; + + public PropertyNode AddOrGetNode(PropertyInfo property) + { + if (_sealed) + { + throw new InvalidOperationException("The property node container is sealed."); + } + + _nodes ??= new(); + + foreach (var node in Nodes) + { + if (node.Property.Name.Equals(property.Name)) + { + return node; + } + } + + var newNode = new PropertyNode(property); + _nodes.Add(newNode); + return newNode; + } + + public void TryAddNode(PropertyNode newNode) + { + if (_sealed) + { + throw new InvalidOperationException("The property node container is sealed."); + } + + _nodes ??= new(); + + foreach (var node in _nodes) + { + if (node.Property.Name.Equals(newNode.Property.Name)) + { + if (!node.Property.MetadataToken.Equals(newNode.Property.MetadataToken)) + { + throw new InvalidOperationException("Duplicate property name."); + } + + // we add the child nodes that are not already present + foreach (var newChild in newNode.Nodes) + { + node.TryAddNode(newChild); + } + } + } + + _nodes.Add(newNode); + } + + public void Seal() + { + if (!_sealed) + { + foreach (var node in Nodes) + { + node.Seal(); + } + + _sealed = true; + } + } +} diff --git a/src/HotChocolate/Core/src/Execution/Projections/SelectionExpressionBuilder.cs b/src/HotChocolate/Core/src/Execution/Projections/SelectionExpressionBuilder.cs index 172f03731a1..5cd638d08d5 100644 --- a/src/HotChocolate/Core/src/Execution/Projections/SelectionExpressionBuilder.cs +++ b/src/HotChocolate/Core/src/Execution/Projections/SelectionExpressionBuilder.cs @@ -142,7 +142,7 @@ private void CollectSelections( { foreach (var requirement in requirements) { - parent.AddNode(requirement.Clone()); + parent.TryAddNode(requirement.Clone()); } } diff --git a/src/HotChocolate/Core/test/Execution.Tests/Projections/ProjectableDataLoaderTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Projections/ProjectableDataLoaderTests.cs index a8164f5f3a1..fc17af18edd 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Projections/ProjectableDataLoaderTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Projections/ProjectableDataLoaderTests.cs @@ -399,6 +399,39 @@ public async Task Brand_Details_Requires_Brand_Name() .MatchMarkdownSnapshot(); } + [Fact] + public async Task Brand_Details_Requires_Brand_Name_2() + { + // Arrange + var queries = new List(); + var connectionString = CreateConnectionString(); + await CatalogContext.SeedAsync(connectionString); + + // Act + var result = await new ServiceCollection() + .AddScoped(_ => queries) + .AddTransient(_ => new CatalogContext(connectionString)) + .AddGraphQL() + .AddQueryType() + .AddTypeExtension() + .AddPagingArguments() + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .ExecuteRequestAsync( + """ + { + brandById(id: 1) { + name + details + } + } + """); + + Snapshot.Create() + .AddSql(queries) + .AddResult(result) + .MatchMarkdownSnapshot(); + } + [Fact] public async Task Brand_Products_TypeName() { diff --git a/src/HotChocolate/Core/test/Execution.Tests/Projections/__snapshots__/ProjectableDataLoaderTests.Brand_Details_Requires_Brand_Name_2.md b/src/HotChocolate/Core/test/Execution.Tests/Projections/__snapshots__/ProjectableDataLoaderTests.Brand_Details_Requires_Brand_Name_2.md new file mode 100644 index 00000000000..238f99d2aee --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Projections/__snapshots__/ProjectableDataLoaderTests.Brand_Details_Requires_Brand_Name_2.md @@ -0,0 +1,24 @@ +# Brand_Details_Requires_Brand_Name_2 + +## SQL + +```text +-- @__keys_0={ '1' } (DbType = Object) +SELECT b."Name", b."Id" +FROM "Brands" AS b +WHERE b."Id" = ANY (@__keys_0) +``` + +## Result + +```json +{ + "data": { + "brandById": { + "name": "Brand0", + "details": "Brand Name:Brand0" + } + } +} +``` +