diff --git a/src/HotChocolate/Core/src/Types.CursorPagination.Extensions/PageConnection.cs b/src/HotChocolate/Core/src/Types.CursorPagination.Extensions/PageConnection.cs index d1dd52f0826..d9bc6a95404 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination.Extensions/PageConnection.cs +++ b/src/HotChocolate/Core/src/Types.CursorPagination.Extensions/PageConnection.cs @@ -76,5 +76,5 @@ public override IReadOnlyList>? Edges /// Identifies the total count of items in the connection. /// [GraphQLDescription("Identifies the total count of items in the connection.")] - public int TotalCount => _page.TotalCount ?? -1; + public int TotalCount => _page.TotalCount ?? 0; } diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs index 885f6bbcde4..4077fa54ed7 100644 --- a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs +++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs @@ -536,6 +536,39 @@ ... on Brand { """); } + [Fact] + public async Task Query_ProductTypes_TotalCount_Zero_When_No_Results_Due_To_FilterCondition() + { + // arrange + using var interceptor = new TestQueryInterceptor(); + + // act + var result = await ExecuteAsync( + """ + { + productTypes(first: 100, order: { name: ASC } where: { name: { eq: "Not Existing" } }) { + totalCount + nodes { + name + } + } + } + """); + + // assert + result.MatchInlineSnapshot( + """ + { + "data": { + "productTypes": { + "totalCount": 0, + "nodes": [] + } + } + } + """); + } + private static ServiceProvider CreateServer(string connectionString) { var services = new ServiceCollection(); diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/Products/ProductTypeQueries.cs b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/Products/ProductTypeQueries.cs new file mode 100644 index 00000000000..1644b1f707b --- /dev/null +++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/Products/ProductTypeQueries.cs @@ -0,0 +1,26 @@ +using GreenDonut.Data; +using HotChocolate.Data.Data; +using HotChocolate.Data.Models; +using HotChocolate.Types; +using HotChocolate.Types.Pagination; + +namespace HotChocolate.Data.Types.Products; + +[QueryType] +public static partial class ProductTypeQueries +{ + [UseConnection(IncludeTotalCount = true)] + [UseFiltering] + [UseSorting] + public static async ValueTask> GetProductTypesAsync( + PagingArguments pagingArguments, + QueryContext queryContext, + CatalogContext context, + CancellationToken cancellationToken) + { + return new PageConnection( + await context.ProductTypes + .With(queryContext) + .ToPageAsync(pagingArguments, cancellationToken)); + } +} diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql index c7b9d31034a..c46323aa4b4 100644 --- a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql +++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql @@ -123,6 +123,25 @@ type ProductType { products: [Product!]! } +"A connection to a list of items." +type ProductTypeConnection @shareable { + "A list of edges." + edges: [ProductTypeEdge!] + "A flattened list of the nodes" + nodes: [ProductType!] + "Information to aid in pagination." + pageInfo: PageInfo! + "Identifies the total count of items in the connection." + totalCount: Int! @cost(weight: "10") +} + +type ProductTypeEdge @shareable { + "The item at the end of the edge." + node: ProductType! + "A cursor for use in pagination." + cursor: String! +} + type Query { "Fetches an object given its ID." node("ID of the object." id: ID!): Node @lookup @shareable @cost(weight: "10") @@ -134,6 +153,7 @@ type Query { products("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String where: ProductFilterInput @cost(weight: "10") order: [ProductSortInput!] @cost(weight: "10")): ProductConnection! @listSize(assumedSize: 50, slicingArguments: [ "first", "last" ], slicingArgumentDefaultValue: 10, sizedFields: [ "edges", "nodes" ], requireOneSlicingArgument: false) @cost(weight: "10") productById(id: ID!): Product @lookup @internal @cost(weight: "10") productsNonRelative("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String where: ProductFilterInput @cost(weight: "10") order: [ProductSortInput!] @cost(weight: "10")): ProductConnection! @listSize(assumedSize: 50, slicingArguments: [ "first", "last" ], slicingArgumentDefaultValue: 10, sizedFields: [ "edges", "nodes" ], requireOneSlicingArgument: false) @cost(weight: "10") + productTypes("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String where: ProductTypeFilterInput @cost(weight: "10") order: [ProductTypeSortInput!] @cost(weight: "10")): ProductTypeConnection! @listSize(assumedSize: 50, slicingArguments: [ "first", "last" ], slicingArgumentDefaultValue: 10, sizedFields: [ "edges", "nodes" ], requireOneSlicingArgument: false) @cost(weight: "10") singleProperties: [SingleProperty!]! @cost(weight: "10") } @@ -226,6 +246,11 @@ input ProductTypeFilterInput { products: ListFilterInputTypeOfProductFilterInput } +input ProductTypeSortInput { + id: SortEnumType @cost(weight: "10") + name: SortEnumType @cost(weight: "10") +} + input StringOperationFilterInput { and: [StringOperationFilterInput!] or: [StringOperationFilterInput!] @@ -278,8 +303,8 @@ composite schema. type User @internal { - id: ID! - name: String! +id: ID! +name: String! } directive @internal on OBJECT | FIELD_DEFINITION