diff --git a/src/HotChocolate/Core/src/Execution/Processing/OperationCompiler.cs b/src/HotChocolate/Core/src/Execution/Processing/OperationCompiler.cs index d9a13d0d7a8..6062bc98d07 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/OperationCompiler.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/OperationCompiler.cs @@ -111,7 +111,7 @@ public IOperation Compile( // collect root fields var rootPath = SelectionPath.Root; - var id = GetOrCreateSelectionSetRefId(operationDefinition.SelectionSet, rootPath); + var id = GetOrCreateSelectionSetRefId(operationDefinition.SelectionSet, operationType.Name, rootPath); var variants = GetOrCreateSelectionVariants(id); SelectionSetInfo[] infos = [new(operationDefinition.SelectionSet, 0),]; @@ -356,7 +356,7 @@ private void CompleteSelectionSet(CompilerContext context) } var selectionPath = context.Path.Append(selection.ResponseName); - selectionSetId = GetOrCreateSelectionSetRefId(selection.SelectionSet, selectionPath); + selectionSetId = GetOrCreateSelectionSetRefId(selection.SelectionSet, fieldType.Name, selectionPath); var possibleTypes = context.Schema.GetPossibleTypes(fieldType); if (_enqueuedSelectionSets.Add(selectionSetId)) @@ -598,7 +598,8 @@ private void ResolveFragment( ifConditionFlags = GetSelectionIncludeCondition(ifCondition, includeCondition); } - var id = GetOrCreateSelectionSetRefId(selectionSet, context.Path); + var typeName = typeCondition?.Name.Value ?? context.Type.Name; + var id = GetOrCreateSelectionSetRefId(selectionSet, typeName, context.Path); var variants = GetOrCreateSelectionVariants(id); var infos = new SelectionSetInfo[] { new(selectionSet, includeCondition), }; @@ -683,9 +684,12 @@ private FragmentDefinitionNode GetFragmentDefinition( private int GetNextFragmentId() => _nextFragmentId++; - private int GetOrCreateSelectionSetRefId(SelectionSetNode selectionSet, SelectionPath path) + private int GetOrCreateSelectionSetRefId( + SelectionSetNode selectionSet, + string selectionSetType, + SelectionPath path) { - var selectionSetRef = new SelectionSetRef(selectionSet, path); + var selectionSetRef = new SelectionSetRef(selectionSet, selectionSetType, path); if (!_selectionSetIdLookup.TryGetValue(selectionSetRef, out var selectionSetId)) { @@ -849,6 +853,7 @@ internal void RegisterNewSelection(Selection newSelection) private readonly struct SelectionSetRef( SelectionSetNode selectionSet, + string selectionSetTypeName, SelectionPath path) : IEquatable { @@ -856,9 +861,12 @@ private readonly struct SelectionSetRef( public SelectionPath Path { get; } = path; + public string SelectionSetTypeName { get; } = selectionSetTypeName; + public bool Equals(SelectionSetRef other) => SyntaxComparer.BySyntax.Equals(SelectionSet, other.SelectionSet) - && Path.Equals(other.Path); + && Path.Equals(other.Path) + && Ordinal.Equals(SelectionSetTypeName, other.SelectionSetTypeName); public override bool Equals(object? obj) => obj is SelectionSetRef other && Equals(other); @@ -866,6 +874,7 @@ public override bool Equals(object? obj) public override int GetHashCode() => HashCode.Combine( SyntaxComparer.BySyntax.GetHashCode(SelectionSet), - Path.GetHashCode()); + Path.GetHashCode(), + Ordinal.GetHashCode(SelectionSetTypeName)); } } diff --git a/src/HotChocolate/Core/test/Execution.Tests/Processing/OperationCompilerTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Processing/OperationCompilerTests.cs index 034cfe24d78..884e4685e98 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Processing/OperationCompilerTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Processing/OperationCompilerTests.cs @@ -1,12 +1,11 @@ using System.Text; -using ChilliCream.Testing; using HotChocolate.Language; using HotChocolate.StarWars; using HotChocolate.Types; using HotChocolate.Utilities; using Microsoft.Extensions.DependencyInjection; using Moq; -using Snapshooter.Xunit; +using CookieCrumble; namespace HotChocolate.Execution.Processing; @@ -1149,6 +1148,89 @@ public async Task Crypto_List_Test() MatchSnapshot(document, operation); } + [Fact] + public async Task Resolve_Concrete_Types_From_Unions() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .AddType() + .AddType() + .UseField(next => next) + .BuildSchemaAsync(); + + var document = Utf8GraphQLParser.Parse( + """ + query QueryName { + oneOrTwo { + ...TypeOneParts + ...TypeTwoParts + } + } + + fragment TypeOneParts on TypeOne { + field1 { name } + } + + fragment TypeTwoParts on TypeTwo { + field1 { name } + } + """); + + var operationDefinition = document.Definitions.OfType().Single(); + + // act + var compiler = new OperationCompiler(new InputParser()); + var operation = compiler.Compile( + "opid", + operationDefinition, + schema.QueryType, + document, + schema); + + // assert + MatchSnapshot(document, operation); + } + + [Fact] + public async Task Resolve_Concrete_Types_From_Unions_Execute() + { + // arrange + var executor = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .AddType() + .AddType() + .BuildRequestExecutorAsync(); + + var document = Utf8GraphQLParser.Parse( + """ + query QueryName { + oneOrTwo { + ...TypeOneParts + ...TypeTwoParts + } + } + + fragment TypeOneParts on TypeOne { + field1 { name } + } + + fragment TypeTwoParts on TypeTwo { + field1 { name } + } + """); + + // act + var result = await executor.ExecuteAsync(builder => builder.SetQuery(document)); + + // assert + result.MatchSnapshot(); + } + private static void MatchSnapshot(DocumentNode original, IOperation compiled) { var sb = new StringBuilder(); @@ -1201,4 +1283,32 @@ public void OptimizeSelectionSet(SelectionSetOptimizerContext context) } } } + + public class UnionQuery + { + public IOneOrTwo OneOrTwo() => new TypeOne(); + } + + public class TypeOne : IOneOrTwo + { + public FieldOne1 Field1 => new(); + } + + public class TypeTwo : IOneOrTwo + { + public FieldTwo1 Field1 => new(); + } + + [UnionType] + public interface IOneOrTwo; + + public class FieldOne1 + { + public string Name => "Name"; + } + + public class FieldTwo1 + { + public string Name => "Name"; + } } diff --git a/src/HotChocolate/Core/test/Execution.Tests/Processing/__snapshots__/OperationCompilerTests.Resolve_Concrete_Types_From_Unions.snap b/src/HotChocolate/Core/test/Execution.Tests/Processing/__snapshots__/OperationCompilerTests.Resolve_Concrete_Types_From_Unions.snap new file mode 100644 index 00000000000..996bc466df8 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Processing/__snapshots__/OperationCompilerTests.Resolve_Concrete_Types_From_Unions.snap @@ -0,0 +1,41 @@ +query QueryName { + oneOrTwo { + ... TypeOneParts + ... TypeTwoParts + } +} + +fragment TypeOneParts on TypeOne { + field1 { + name + } +} + +fragment TypeTwoParts on TypeTwo { + field1 { + name + } +} + +--------------------------------------------------------- + +query QueryName { + ... on UnionQuery { + oneOrTwo @__execute(id: 0, kind: DEFAULT, type: COMPOSITE) { + ... on TypeOne { + field1 @__execute(id: 1, kind: DEFAULT, type: COMPOSITE) { + ... on FieldOne1 { + name @__execute(id: 2, kind: DEFAULT, type: LEAF) + } + } + } + ... on TypeTwo { + field1 @__execute(id: 3, kind: DEFAULT, type: COMPOSITE) { + ... on FieldTwo1 { + name @__execute(id: 4, kind: DEFAULT, type: LEAF) + } + } + } + } + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Processing/__snapshots__/OperationCompilerTests.Resolve_Concrete_Types_From_Unions_Execute.snap b/src/HotChocolate/Core/test/Execution.Tests/Processing/__snapshots__/OperationCompilerTests.Resolve_Concrete_Types_From_Unions_Execute.snap new file mode 100644 index 00000000000..91e2182d4aa --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Processing/__snapshots__/OperationCompilerTests.Resolve_Concrete_Types_From_Unions_Execute.snap @@ -0,0 +1,9 @@ +{ + "data": { + "oneOrTwo": { + "field1": { + "name": "Name" + } + } + } +} \ No newline at end of file