From 83f8175066f195d3afda5944ac41d6a18d5e0370 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Wed, 20 Mar 2024 15:22:49 -0700 Subject: [PATCH 01/11] add fix and update the pagination tests --- src/Core/Resolvers/SqlPaginationUtil.cs | 4 +++- .../GraphQLPaginationTests/GraphQLPaginationTestBase.cs | 9 ++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Core/Resolvers/SqlPaginationUtil.cs b/src/Core/Resolvers/SqlPaginationUtil.cs index 0e419e548e..677252d7ee 100644 --- a/src/Core/Resolvers/SqlPaginationUtil.cs +++ b/src/Core/Resolvers/SqlPaginationUtil.cs @@ -102,7 +102,9 @@ private static JsonObject CreatePaginationConnection(JsonElement root, Paginatio { // parse *Connection.endCursor if there are no elements // if no after is added, but it has been requested HotChocolate will report it as null - if (returnedElemNo > 0) + // Need to validate we have an extra element, because otherwise there is no next page + // and endCursor should be left as null. + if (returnedElemNo > 0 && hasExtraElement) { JsonElement lastElemInRoot = rootEnumerated.ElementAtOrDefault(returnedElemNo - 1); connection.Add(QueryBuilder.PAGINATION_TOKEN_FIELD_NAME, diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs index 84b34e4e84..ff315a4ac2 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs @@ -573,8 +573,8 @@ public async Task PaginateCompositePkTable() } }"; - after = SqlPaginationUtil.Base64Encode($"[{{\"EntityName\":\"Review\",\"FieldName\":\"book_id\",\"FieldValue\":1,\"Direction\":0}}," + - $"{{\"EntityName\":\"Review\",\"FieldName\":\"id\",\"FieldValue\":569,\"Direction\":0}}]"); + //after = SqlPaginationUtil.Base64Encode($"[{{\"EntityName\":\"Review\",\"FieldName\":\"book_id\",\"FieldValue\":1,\"Direction\":0}}," + + // $"{{\"EntityName\":\"Review\",\"FieldName\":\"id\",\"FieldValue\":569,\"Direction\":0}}]"); JsonElement actual = await ExecuteGraphQLRequestAsync(graphQLQuery, graphQLQueryName, isAuthenticated: false); string expected = @"{ ""items"": [ @@ -588,7 +588,7 @@ public async Task PaginateCompositePkTable() } ], ""hasNextPage"": false, - ""endCursor"": """ + after + @""" + ""endCursor"": null }"; SqlTestHelper.PerformTestEqualJsonStrings(expected, actual.ToString()); @@ -625,8 +625,7 @@ public async Task PaginationWithFilterArgument() ""publisher_id"": 2345 } ], - ""endCursor"": """ + - SqlPaginationUtil.Base64Encode($"[{{\"EntityName\":\"Book\",\"FieldName\":\"id\",\"FieldValue\":4,\"Direction\":0}}]") + @""", + ""endCursor"": null, ""hasNextPage"": false }"; From 09175626465e166b9234e62237c48d0962e1f3b2 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Wed, 20 Mar 2024 15:23:35 -0700 Subject: [PATCH 02/11] remove commented out code --- .../GraphQLPaginationTests/GraphQLPaginationTestBase.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs index ff315a4ac2..e3b57b0dce 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs @@ -573,8 +573,6 @@ public async Task PaginateCompositePkTable() } }"; - //after = SqlPaginationUtil.Base64Encode($"[{{\"EntityName\":\"Review\",\"FieldName\":\"book_id\",\"FieldValue\":1,\"Direction\":0}}," + - // $"{{\"EntityName\":\"Review\",\"FieldName\":\"id\",\"FieldValue\":569,\"Direction\":0}}]"); JsonElement actual = await ExecuteGraphQLRequestAsync(graphQLQuery, graphQLQueryName, isAuthenticated: false); string expected = @"{ ""items"": [ From eb04a2b8ebebb1e5d8b77614ed648a63421c65a7 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Thu, 21 Mar 2024 16:13:11 -0700 Subject: [PATCH 03/11] adjusting logic to handle endCursor only --- .../Sql Query Structures/SqlQueryStructure.cs | 2 +- src/Core/Resolvers/SqlPaginationUtil.cs | 28 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Core/Resolvers/Sql Query Structures/SqlQueryStructure.cs b/src/Core/Resolvers/Sql Query Structures/SqlQueryStructure.cs index dd82e0369a..d62595b581 100644 --- a/src/Core/Resolvers/Sql Query Structures/SqlQueryStructure.cs +++ b/src/Core/Resolvers/Sql Query Structures/SqlQueryStructure.cs @@ -392,7 +392,7 @@ private SqlQueryStructure( AddColumnsForEndCursor(); } - if (PaginationMetadata.RequestedHasNextPage) + if (PaginationMetadata.RequestedHasNextPage || PaginationMetadata.RequestedEndCursor) { _limit++; } diff --git a/src/Core/Resolvers/SqlPaginationUtil.cs b/src/Core/Resolvers/SqlPaginationUtil.cs index 677252d7ee..fccfb1057b 100644 --- a/src/Core/Resolvers/SqlPaginationUtil.cs +++ b/src/Core/Resolvers/SqlPaginationUtil.cs @@ -62,26 +62,32 @@ private static JsonObject CreatePaginationConnection(JsonElement root, Paginatio root = document.RootElement.Clone(); } - IEnumerable rootEnumerated = root.EnumerateArray(); + // If the request includes either hasNextPage or endCursor then to correctly return those + // values we need to determine the correct pagination logic + bool paginationScenario = paginationMetadata.RequestedHasNextPage || paginationMetadata.RequestedEndCursor; + IEnumerable rootEnumerated = root.EnumerateArray(); + int returnedElemNo = rootEnumerated.Count(); bool hasExtraElement = false; - if (paginationMetadata.RequestedHasNextPage) - { - // check if the number of elements requested is successfully returned - // structure.Limit() is first + 1 for paginated queries where hasNextPage is requested - hasExtraElement = rootEnumerated.Count() == paginationMetadata.Structure!.Limit(); - - // add hasNextPage to connection elements - connection.Add(QueryBuilder.HAS_NEXT_PAGE_FIELD_NAME, hasExtraElement); + if (paginationScenario) + { + // structure.Limit() is first + 1 for paginated queries where hasNextPage or endCursor is requested + hasExtraElement = returnedElemNo == paginationMetadata.Structure!.Limit(); if (hasExtraElement) { - // remove the last element + // In a pagination scenario where we have an extra element, this element + // must be removed since it was only used to determine if there are additional + // records after those requested. rootEnumerated = rootEnumerated.Take(rootEnumerated.Count() - 1); } } - int returnedElemNo = rootEnumerated.Count(); + if (paginationMetadata.RequestedHasNextPage) + { + // add hasNextPage to connection elements + connection.Add(QueryBuilder.HAS_NEXT_PAGE_FIELD_NAME, hasExtraElement); + } if (paginationMetadata.RequestedItems) { From 5a81aeaf180db55893bde9b0138f79aaf18b8dc4 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Thu, 21 Mar 2024 16:55:58 -0700 Subject: [PATCH 04/11] adjust more tests to match new patterns --- src/Core/Resolvers/SqlPaginationUtil.cs | 1 + .../GraphQLPaginationTests/GraphQLPaginationTestBase.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/Resolvers/SqlPaginationUtil.cs b/src/Core/Resolvers/SqlPaginationUtil.cs index fccfb1057b..dca19d8c3c 100644 --- a/src/Core/Resolvers/SqlPaginationUtil.cs +++ b/src/Core/Resolvers/SqlPaginationUtil.cs @@ -80,6 +80,7 @@ private static JsonObject CreatePaginationConnection(JsonElement root, Paginatio // must be removed since it was only used to determine if there are additional // records after those requested. rootEnumerated = rootEnumerated.Take(rootEnumerated.Count() - 1); + --returnedElemNo; } } diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs index e3b57b0dce..808d01defa 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs @@ -350,7 +350,7 @@ public async Task RequestNestedPaginationQueries() ""title"": ""US history in a nutshell"" } ], - ""endCursor"": """ + SqlPaginationUtil.Base64Encode($"[{{\"EntityName\":\"Book\",\"FieldName\":\"id\",\"FieldValue\":4,\"Direction\":0}}]") + @""", + ""endCursor"": null, ""hasNextPage"": false } } From b587d3822c48ade1a9f775795bb43f0d2eeaa92b Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Thu, 21 Mar 2024 19:55:25 -0700 Subject: [PATCH 05/11] adjusting more tests --- .../GraphQLPaginationTests/GraphQLPaginationTestBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs index 808d01defa..500f68804a 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs @@ -143,7 +143,7 @@ public async Task RequestNoParamFullConnection() ""title"": ""Before Sunset"" } ], - ""endCursor"": """ + SqlPaginationUtil.Base64Encode($"[{{\"EntityName\":\"Book\",\"FieldName\":\"id\",\"FieldValue\":14,\"Direction\":0}}]") + @""", + ""endCursor"": null, ""hasNextPage"": false }"; From e50813ab22ecfa3e6d1a6448b972d9b8ff166ce2 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 22 Mar 2024 14:19:59 -0700 Subject: [PATCH 06/11] add new cases for new behavior --- .../GraphQLPaginationTestBase.cs | 14 +++- .../MsSqlGraphQLPaginationTests.cs | 73 ++++++++++++++----- .../MySqlGraphQLPaginationTests.cs | 53 ++++++++++---- .../PostgreSqlGraphQLPaginationTests.cs | 52 +++++++++---- 4 files changed, 144 insertions(+), 48 deletions(-) diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs index 500f68804a..327b84abc6 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/GraphQLPaginationTestBase.cs @@ -196,7 +196,8 @@ public async virtual Task RequestAfterTokenOnly( object afterValue, object endCursorValue, object afterIdValue, - object endCursorIdValue) + object endCursorIdValue, + bool isLastPage) { string graphQLQueryName = "supportedTypes"; string after; @@ -212,14 +213,16 @@ public async virtual Task RequestAfterTokenOnly( } string graphQLQuery = @"{ - supportedTypes(first: 3," + $"after: \"{after}\" " + + supportedTypes(first: 2," + $"after: \"{after}\" " + $"orderBy: {{ {exposedFieldName} : ASC }} )" + @"{ endCursor } }"; JsonElement root = await ExecuteGraphQLRequestAsync(graphQLQuery, graphQLQueryName, isAuthenticated: false); - string actual = SqlPaginationUtil.Base64Decode(root.GetProperty(QueryBuilder.PAGINATION_TOKEN_FIELD_NAME).GetString()); + string actual = root.GetProperty(QueryBuilder.PAGINATION_TOKEN_FIELD_NAME).GetString(); + // Decode if not null + actual = string.IsNullOrEmpty(actual) ? "null" : SqlPaginationUtil.Base64Decode(root.GetProperty(QueryBuilder.PAGINATION_TOKEN_FIELD_NAME).GetString()); string expected; if ("typeid".Equals(exposedFieldName)) { @@ -231,6 +234,11 @@ public async virtual Task RequestAfterTokenOnly( $"{{\"EntityName\":\"SupportedType\",\"FieldName\":\"typeid\",\"FieldValue\":{endCursorIdValue},\"Direction\":0}}]"; } + if (isLastPage) + { + expected = "null"; + } + SqlTestHelper.PerformTestEqualJsonStrings(expected, actual.ToString()); } diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs index f66e6d9040..4a67d05ef8 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs @@ -25,55 +25,92 @@ public static async Task SetupAsync(TestContext context) /// [DataTestMethod] - [DataRow("typeid", 1, 4, "", "", + [DataRow("typeid", 1, 4, "", "", false, DisplayName = "Test after token for primary key with mapped name.")] - [DataRow("byte_types", 0, 255, 2, 4, DisplayName = "Test after token for byte values.")] - [DataRow("short_types", -32768, 32767, 3, 4, DisplayName = "Test after token for short values.")] - [DataRow("int_types", -2147483648, 2147483647, 3, 4, + [DataRow("typeid", 4, 6, "", "", true, + DisplayName = "Test after token for primary key with mapped name for last page.")] + [DataRow("byte_types", 0, 1, 2, 1, false, DisplayName = "Test after token for byte values.")] + [DataRow("byte_types", 1, "", 1, "", true, DisplayName = "Test after token for byte values for last page.")] + [DataRow("short_types", -32768, 1, 3, 1, false, DisplayName = "Test after token for short values.")] + [DataRow("short_types", 1, "", 1, "", true, DisplayName = "Test after token for short values for last page.")] + [DataRow("int_types", -2147483648, 1, 3, 1, false, DisplayName = "Test after token for int values.")] - [DataRow("long_types", -9223372036854775808, 9.223372036854776E+18, 3, 4, + [DataRow("int_types", 1, "", 1, "", true, + DisplayName = "Test after token for int values for last page.")] + [DataRow("long_types", -9223372036854775808, 1, 3, 1, false, DisplayName = "Test after token for long values.")] - [DataRow("string_types", "\"\"", "\"null\"", 1, 4, + [DataRow("long_types", 1, "", 1, "", true, + DisplayName = "Test after token for long values for last page.")] + [DataRow("string_types", "\"\"", "\"null\"", 1, 3, false, DisplayName = "Test after token for string values.")] - [DataRow("single_types", -3.39E38, 3.4E38, 3, 4, + [DataRow("string_types", "null", "", 3, "", true, + DisplayName = "Test after token for string values for last page.")] + [DataRow("single_types", -3.39E38, .33000001, 3, 1, false, DisplayName = "Test after token for single values.")] - [DataRow("float_types", -1.7E308, 1.7E308, 3, 4, + [DataRow("single_types", .33, "", 1, "", true, + DisplayName = "Test after token for single values for last page.")] + [DataRow("float_types", -1.7E308, .33, 3, 1, false, DisplayName = "Test after token for float values.")] - [DataRow("decimal_types", -9.292929, 0.333333, 2, 1, + [DataRow("float_types", .33, "", 1, "", true, + DisplayName = "Test after token for float values for last page.")] + [DataRow("decimal_types", -9.292929, 0.0000000000000292929, 2, 4, false, DisplayName = "Test after token for decimal values.")] - [DataRow("boolean_types", "false", "true", 2, 4, + [DataRow("decimal_types", 0.333333, "", 1, "", true, + DisplayName = "Test after token for decimal values for last page.")] + [DataRow("boolean_types", "false", "true", 2, 3, false, DisplayName = "Test after token for boolean values.")] + [DataRow("boolean_types", "true", "", 3, "", true, + DisplayName = "Test after token for boolean values for last page.")] [DataRow("date_types", "\"0001-01-01\"", - "\"9999-12-31\"", 3, 4, + "\"1999-01-08\"", 3, 2, false, DisplayName = "Test after token for date values.")] + [DataRow("date_types", "\"1999-01-08\"", + "", 2, "", true, + DisplayName = "Test after token for date values for last page.")] [DataRow("datetime_types", "\"1753-01-01T00:00:00.000\"", - "\"9999-12-31T23:59:59\"", 3, 4, + "\"1999-01-08T10:23:54\"", 3, 1, false, DisplayName = "Test after token for datetime values.")] + [DataRow("datetime_types", "\"9999-12-31T23:59:59\"", + "", 4, "", true, + DisplayName = "Test after token for datetime values for last page.")] [DataRow("datetime2_types", "\"0001-01-01 00:00:00.0000000\"", - "\"9999-12-31T23:59:59.9999999\"", 3, 4, + "\"1999-01-08T10:23:54.9999999\"", 3, 1, false, DisplayName = "Test after token for datetime2 values.")] + [DataRow("datetime2_types", "\"9999-12-31T23:59:59.9999999\"", + "", 4, "", true, + DisplayName = "Test after token for datetime2 values for last page.")] [DataRow("datetimeoffset_types", "\"0001-01-01 00:00:00.0000000+0:00\"", - "\"9999-12-31T23:59:59.9999999+14:00\"", 3, 4, + "\"1999-01-08T10:23:54.9999999-14:00\"", 3, 1, false, DisplayName = "Test after token for datetimeoffset values.")] + [DataRow("datetimeoffset_types", "\"9999-12-31T23:59:59.9999999+14:00\"", + "", 4, "", true, + DisplayName = "Test after token for datetimeoffset values for last page.")] [DataRow("smalldatetime_types", "\"1900-01-01 00:00:00\"", - "\"2079-06-06T00:00:00\"", 3, 4, + "\"1999-01-08T10:24:00\"", 3, 1, false, DisplayName = "Test after token for smalldate values.")] - [DataRow("bytearray_types", "\"AAAAAA==\"", "\"/////w==\"", 3, 4, + [DataRow("smalldatetime_types", "\"2079-06-06T00:00:00\"", + "", 4, "", true, + DisplayName = "Test after token for smalldate values for last page.")] + [DataRow("bytearray_types", "\"AAAAAA==\"", "\"q83vASM=\"", 3, 1, false, DisplayName = "Test after token for bytearray values.")] + [DataRow("bytearray_types", "\"q83vASM=\"", "", 1, "", true, + DisplayName = "Test after token for bytearray values for last page.")] [TestMethod] public override async Task RequestAfterTokenOnly( string exposedFieldName, object afterValue, object endCursorValue, object afterIdValue, - object endCursorIdValue) + object endCursorIdValue, + bool isLastPage) { await base.RequestAfterTokenOnly( exposedFieldName, afterValue, endCursorValue, afterIdValue, - endCursorIdValue); + endCursorIdValue, + isLastPage); } } } diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs index fb8ab11faa..e0bf92d3ec 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs @@ -25,43 +25,68 @@ public static async Task SetupAsync(TestContext context) /// [DataTestMethod] - [DataRow("typeid", 1, 4, "", "", + [DataRow("typeid", 1, 4, "", "", false, DisplayName = "Test after token for primary key with mapped name.")] - [DataRow("byte_types", 0, 255, 2, 4, DisplayName = "Test after token for byte values.")] - [DataRow("short_types", -32768, 32767, 3, 4, DisplayName = "Test after token for short values.")] - [DataRow("int_types", -2147483648, 2147483647, 3, 4, + [DataRow("typeid", 4, 6, "", "", true, + DisplayName = "Test after token for primary key with mapped name for last page.")] + [DataRow("byte_types", 0, 1, 2, 1, false, DisplayName = "Test after token for byte values.")] + [DataRow("byte_types", 1, "", 1, "", true, DisplayName = "Test after token for byte values for last page.")] + [DataRow("short_types", -32768, 1, 3, 1, false, DisplayName = "Test after token for short values.")] + [DataRow("short_types", 1, "", 1, "", true, DisplayName = "Test after token for short values for last page.")] + [DataRow("int_types", -2147483648, 1, 3, 1, false, DisplayName = "Test after token for int values.")] - [DataRow("long_types", -9223372036854775808, 9.223372036854776E+18, 3, 4, + [DataRow("int_types", 1, "", 1, "", true, + DisplayName = "Test after token for int values for last page.")] + [DataRow("long_types", -9223372036854775808, 1, 3, 1, false, DisplayName = "Test after token for long values.")] - [DataRow("string_types", "\"\"", "\"null\"", 1, 4, + [DataRow("long_types", 1, "", 1, "", true, + DisplayName = "Test after token for long values for last page.")] + [DataRow("string_types", "\"\"", "\"null\"", 1, 3, false, DisplayName = "Test after token for string values.")] - [DataRow("single_types", -3.39E38, 3.3999999521443642E+38, 3, 4, + [DataRow("string_types", "null", "", 3, "", true, + DisplayName = "Test after token for string values for last page.")] + [DataRow("single_types", -3.39E38, .33000001, 3, 1, false, DisplayName = "Test after token for single values.")] - [DataRow("float_types", -1.7E308, 1.7E308, 3, 4, + [DataRow("single_types", .33, "", 1, "", true, + DisplayName = "Test after token for single values for last page.")] + [DataRow("float_types", -1.7E308, .33, 3, 1, false, DisplayName = "Test after token for float values.")] - [DataRow("decimal_types", -9.292929, 0.333333, 2, 1, + [DataRow("float_types", .33, "", 1, "", true, + DisplayName = "Test after token for float values for last page.")] + [DataRow("decimal_types", -9.292929, 0.0000000000000292929, 2, 4, false, DisplayName = "Test after token for decimal values.")] - [DataRow("boolean_types", "false", "true", 2, 4, + [DataRow("decimal_types", 0.333333, "", 1, "", true, + DisplayName = "Test after token for decimal values for last page.")] + [DataRow("boolean_types", "false", "true", 2, 3, false, DisplayName = "Test after token for boolean values.")] + [DataRow("boolean_types", "true", "", 3, "", true, + DisplayName = "Test after token for boolean values for last page.")] [DataRow("datetime_types", "\"1753-01-01T00:00:00.000\"", - "\"9999-12-31 23:59:59.000000\"", 3, 4, + "\"1999-01-08T10:23:54\"", 3, 1, false, DisplayName = "Test after token for datetime values.")] - [DataRow("bytearray_types", "\"AAAAAA==\"", "\"/////w==\"", 3, 4, + [DataRow("datetime_types", "\"9999-12-31T23:59:59\"", + "", 4, "", true, + DisplayName = "Test after token for datetime values for last page.")] + [DataRow("bytearray_types", "\"AAAAAA==\"", "\"q83vASM=\"", 3, 1, false, DisplayName = "Test after token for bytearray values.")] + [DataRow("bytearray_types", "\"q83vASM=\"", "", 1, "", true, + DisplayName = "Test after token for bytearray values for last page.")] [TestMethod] public override async Task RequestAfterTokenOnly( string exposedFieldName, object afterValue, object endCursorValue, object afterIdValue, - object endCursorIdValue) + object endCursorIdValue, + bool isLastPage) { await base.RequestAfterTokenOnly( exposedFieldName, afterValue, endCursorValue, afterIdValue, - endCursorIdValue); + endCursorIdValue, + isLastPage); } } } diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs index e9f40ed1a5..2216464c82 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs @@ -25,42 +25,68 @@ public static async Task SetupAsync(TestContext context) /// [DataTestMethod] - [DataRow("typeid", 1, 4, "", "", + [DataRow("typeid", 1, 4, "", "", false, DisplayName = "Test after token for primary key with mapped name.")] - [DataRow("short_types", -32768, 32767, 3, 4, DisplayName = "Test after token for short values.")] - [DataRow("int_types", -2147483648, 2147483647, 3, 4, + [DataRow("typeid", 4, 6, "", "", true, + DisplayName = "Test after token for primary key with mapped name for last page.")] + [DataRow("byte_types", 0, 1, 2, 1, false, DisplayName = "Test after token for byte values.")] + [DataRow("byte_types", 1, "", 1, "", true, DisplayName = "Test after token for byte values for last page.")] + [DataRow("short_types", -32768, 1, 3, 1, false, DisplayName = "Test after token for short values.")] + [DataRow("short_types", 1, "", 1, "", true, DisplayName = "Test after token for short values for last page.")] + [DataRow("int_types", -2147483648, 1, 3, 1, false, DisplayName = "Test after token for int values.")] - [DataRow("long_types", -9223372036854775808, 9.223372036854776E+18, 3, 4, + [DataRow("int_types", 1, "", 1, "", true, + DisplayName = "Test after token for int values for last page.")] + [DataRow("long_types", -9223372036854775808, 1, 3, 1, false, DisplayName = "Test after token for long values.")] - [DataRow("string_types", "\"\"", "\"null\"", 1, 4, + [DataRow("long_types", 1, "", 1, "", true, + DisplayName = "Test after token for long values for last page.")] + [DataRow("string_types", "\"\"", "\"null\"", 1, 3, false, DisplayName = "Test after token for string values.")] - [DataRow("single_types", -3.39E38, 3.4E38, 3, 4, + [DataRow("string_types", "null", "", 3, "", true, + DisplayName = "Test after token for string values for last page.")] + [DataRow("single_types", -3.39E38, .33000001, 3, 1, false, DisplayName = "Test after token for single values.")] - [DataRow("float_types", -1.7E308, 1.7E308, 3, 4, + [DataRow("single_types", .33, "", 1, "", true, + DisplayName = "Test after token for single values for last page.")] + [DataRow("float_types", -1.7E308, .33, 3, 1, false, DisplayName = "Test after token for float values.")] - [DataRow("decimal_types", -9.292929, 0.333333, 2, 1, + [DataRow("float_types", .33, "", 1, "", true, + DisplayName = "Test after token for float values for last page.")] + [DataRow("decimal_types", -9.292929, 0.0000000000000292929, 2, 4, false, DisplayName = "Test after token for decimal values.")] - [DataRow("boolean_types", "false", "true", 2, 4, + [DataRow("decimal_types", 0.333333, "", 1, "", true, + DisplayName = "Test after token for decimal values for last page.")] + [DataRow("boolean_types", "false", "true", 2, 3, false, DisplayName = "Test after token for boolean values.")] + [DataRow("boolean_types", "true", "", 3, "", true, + DisplayName = "Test after token for boolean values for last page.")] [DataRow("datetime_types", "\"1753-01-01T00:00:00.000\"", - "\"9999-12-31T23:59:59.997\"", 3, 4, + "\"1999-01-08T10:23:54\"", 3, 1, false, DisplayName = "Test after token for datetime values.")] - [DataRow("bytearray_types", "\"AAAAAA==\"", "\"/////w==\"", 3, 4, + [DataRow("datetime_types", "\"9999-12-31T23:59:59\"", + "", 4, "", true, + DisplayName = "Test after token for datetime values for last page.")] + [DataRow("bytearray_types", "\"AAAAAA==\"", "\"q83vASM=\"", 3, 1, false, DisplayName = "Test after token for bytearray values.")] + [DataRow("bytearray_types", "\"q83vASM=\"", "", 1, "", true, + DisplayName = "Test after token for bytearray values for last page.")] [TestMethod] public override async Task RequestAfterTokenOnly( string exposedFieldName, object afterValue, object endCursorValue, object afterIdValue, - object endCursorIdValue) + object endCursorIdValue, + bool isLastPage) { await base.RequestAfterTokenOnly( exposedFieldName, afterValue, endCursorValue, afterIdValue, - endCursorIdValue); + endCursorIdValue, + isLastPage); } } } From 4487c42d5a3595ec9bdbf9cd90306dec018c4c0b Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 22 Mar 2024 14:25:22 -0700 Subject: [PATCH 07/11] better names --- src/Core/Resolvers/SqlPaginationUtil.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Core/Resolvers/SqlPaginationUtil.cs b/src/Core/Resolvers/SqlPaginationUtil.cs index dca19d8c3c..1125445019 100644 --- a/src/Core/Resolvers/SqlPaginationUtil.cs +++ b/src/Core/Resolvers/SqlPaginationUtil.cs @@ -64,23 +64,23 @@ private static JsonObject CreatePaginationConnection(JsonElement root, Paginatio // If the request includes either hasNextPage or endCursor then to correctly return those // values we need to determine the correct pagination logic - bool paginationScenario = paginationMetadata.RequestedHasNextPage || paginationMetadata.RequestedEndCursor; + bool isPaginationRequested = paginationMetadata.RequestedHasNextPage || paginationMetadata.RequestedEndCursor; IEnumerable rootEnumerated = root.EnumerateArray(); - int returnedElemNo = rootEnumerated.Count(); + int returnedElementCount = rootEnumerated.Count(); bool hasExtraElement = false; - if (paginationScenario) + if (isPaginationRequested) { // structure.Limit() is first + 1 for paginated queries where hasNextPage or endCursor is requested - hasExtraElement = returnedElemNo == paginationMetadata.Structure!.Limit(); + hasExtraElement = returnedElementCount == paginationMetadata.Structure!.Limit(); if (hasExtraElement) { // In a pagination scenario where we have an extra element, this element // must be removed since it was only used to determine if there are additional // records after those requested. rootEnumerated = rootEnumerated.Take(rootEnumerated.Count() - 1); - --returnedElemNo; + --returnedElementCount; } } @@ -107,13 +107,13 @@ private static JsonObject CreatePaginationConnection(JsonElement root, Paginatio if (paginationMetadata.RequestedEndCursor) { - // parse *Connection.endCursor if there are no elements - // if no after is added, but it has been requested HotChocolate will report it as null + // Note: if we do not add endCursor to the connection but it was in the request, its value will + // automatically be populated as null. // Need to validate we have an extra element, because otherwise there is no next page // and endCursor should be left as null. - if (returnedElemNo > 0 && hasExtraElement) + if (returnedElementCount > 0 && hasExtraElement) { - JsonElement lastElemInRoot = rootEnumerated.ElementAtOrDefault(returnedElemNo - 1); + JsonElement lastElemInRoot = rootEnumerated.ElementAtOrDefault(returnedElementCount - 1); connection.Add(QueryBuilder.PAGINATION_TOKEN_FIELD_NAME, MakeCursorFromJsonElement( lastElemInRoot, From df0a9bc052b873e4498267bdd20d097efc445aa8 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 22 Mar 2024 15:24:47 -0700 Subject: [PATCH 08/11] fixed Last(?) tests --- .../GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs | 2 +- .../GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs | 6 +++--- .../PostgreSqlGraphQLPaginationTests.cs | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs index 4a67d05ef8..be16a0e6bd 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs @@ -25,7 +25,7 @@ public static async Task SetupAsync(TestContext context) /// [DataTestMethod] - [DataRow("typeid", 1, 4, "", "", false, + [DataRow("typeid", 1, 3, "", "", false, DisplayName = "Test after token for primary key with mapped name.")] [DataRow("typeid", 4, 6, "", "", true, DisplayName = "Test after token for primary key with mapped name for last page.")] diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs index e0bf92d3ec..18a45e637e 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs @@ -25,7 +25,7 @@ public static async Task SetupAsync(TestContext context) /// [DataTestMethod] - [DataRow("typeid", 1, 4, "", "", false, + [DataRow("typeid", 1, 3, "", "", false, DisplayName = "Test after token for primary key with mapped name.")] [DataRow("typeid", 4, 6, "", "", true, DisplayName = "Test after token for primary key with mapped name for last page.")] @@ -41,11 +41,11 @@ public static async Task SetupAsync(TestContext context) DisplayName = "Test after token for long values.")] [DataRow("long_types", 1, "", 1, "", true, DisplayName = "Test after token for long values for last page.")] - [DataRow("string_types", "\"\"", "\"null\"", 1, 3, false, + [DataRow("string_types", "\"\"", "\"lksa;jdflasdf;alsdflksdfkldj\"", 1, 2, false, DisplayName = "Test after token for string values.")] [DataRow("string_types", "null", "", 3, "", true, DisplayName = "Test after token for string values for last page.")] - [DataRow("single_types", -3.39E38, .33000001, 3, 1, false, + [DataRow("single_types", -3.39E38, .33000001311302185, 3, 1, false, DisplayName = "Test after token for single values.")] [DataRow("single_types", .33, "", 1, "", true, DisplayName = "Test after token for single values for last page.")] diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs index 2216464c82..2d6602c298 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs @@ -25,12 +25,10 @@ public static async Task SetupAsync(TestContext context) /// [DataTestMethod] - [DataRow("typeid", 1, 4, "", "", false, + [DataRow("typeid", 1, 3, "", "", false, DisplayName = "Test after token for primary key with mapped name.")] [DataRow("typeid", 4, 6, "", "", true, DisplayName = "Test after token for primary key with mapped name for last page.")] - [DataRow("byte_types", 0, 1, 2, 1, false, DisplayName = "Test after token for byte values.")] - [DataRow("byte_types", 1, "", 1, "", true, DisplayName = "Test after token for byte values for last page.")] [DataRow("short_types", -32768, 1, 3, 1, false, DisplayName = "Test after token for short values.")] [DataRow("short_types", 1, "", 1, "", true, DisplayName = "Test after token for short values for last page.")] [DataRow("int_types", -2147483648, 1, 3, 1, false, @@ -41,11 +39,11 @@ public static async Task SetupAsync(TestContext context) DisplayName = "Test after token for long values.")] [DataRow("long_types", 1, "", 1, "", true, DisplayName = "Test after token for long values for last page.")] - [DataRow("string_types", "\"\"", "\"null\"", 1, 3, false, + [DataRow("string_types", "\"\"", "\"lksa;jdflasdf;alsdflksdfkldj\"", 1, 3, false, DisplayName = "Test after token for string values.")] [DataRow("string_types", "null", "", 3, "", true, DisplayName = "Test after token for string values for last page.")] - [DataRow("single_types", -3.39E38, .33000001, 3, 1, false, + [DataRow("single_types", -3.39E38, .33, 3, 1, false, DisplayName = "Test after token for single values.")] [DataRow("single_types", .33, "", 1, "", true, DisplayName = "Test after token for single values for last page.")] From f45e4c62c8d480cfa00c38424e56ebe6f77a28ff Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 22 Mar 2024 15:36:18 -0700 Subject: [PATCH 09/11] postgres test still had wrong typeID fvalue --- .../GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs | 2 +- src/Service.Tests/dab-config.MsSql.json | 2 +- src/Service.Tests/dab-config.PostgreSql.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs index 2d6602c298..9e92b901a3 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs @@ -39,7 +39,7 @@ public static async Task SetupAsync(TestContext context) DisplayName = "Test after token for long values.")] [DataRow("long_types", 1, "", 1, "", true, DisplayName = "Test after token for long values for last page.")] - [DataRow("string_types", "\"\"", "\"lksa;jdflasdf;alsdflksdfkldj\"", 1, 3, false, + [DataRow("string_types", "\"\"", "\"lksa;jdflasdf;alsdflksdfkldj\"", 1, 2, false, DisplayName = "Test after token for string values.")] [DataRow("string_types", "null", "", 3, "", true, DisplayName = "Test after token for string values for last page.")] diff --git a/src/Service.Tests/dab-config.MsSql.json b/src/Service.Tests/dab-config.MsSql.json index dbec0174ef..4c34ed0790 100644 --- a/src/Service.Tests/dab-config.MsSql.json +++ b/src/Service.Tests/dab-config.MsSql.json @@ -2,7 +2,7 @@ "$schema": "https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json", "data-source": { "database-type": "mssql", - "connection-string": "Server=tcp:127.0.0.1,1433;Persist Security Info=False;User ID=sa;Password=REPLACEME;MultipleActiveResultSets=False;Connection Timeout=5;", + "connection-string": "Server=(localdb)\\MSSQLLocalDB;Database=master;Integrated Security=true;Persist Security Info=False;User ID=sa;Password=SSMSark!97TRS!;MultipleActiveResultSets=False;Connection Timeout=5;Encrypt=False;", "options": { "set-session-context": true } diff --git a/src/Service.Tests/dab-config.PostgreSql.json b/src/Service.Tests/dab-config.PostgreSql.json index 8995a00b35..2c53107180 100644 --- a/src/Service.Tests/dab-config.PostgreSql.json +++ b/src/Service.Tests/dab-config.PostgreSql.json @@ -2,7 +2,7 @@ "$schema": "https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json", "data-source": { "database-type": "postgresql", - "connection-string": "Host=localhost;Database=datagatewaytest;username=REPLACEME;password=REPLACEME", + "connection-string": "Host=localhost;Database=Test;username=postgres;password=121983", "options": {} }, "runtime": { From 9be19557f08797e79f47d37a88aac80da08d0d63 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 22 Mar 2024 15:43:51 -0700 Subject: [PATCH 10/11] revert config,oops! --- src/Service.Tests/dab-config.PostgreSql.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/dab-config.PostgreSql.json b/src/Service.Tests/dab-config.PostgreSql.json index 2c53107180..8995a00b35 100644 --- a/src/Service.Tests/dab-config.PostgreSql.json +++ b/src/Service.Tests/dab-config.PostgreSql.json @@ -2,7 +2,7 @@ "$schema": "https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json", "data-source": { "database-type": "postgresql", - "connection-string": "Host=localhost;Database=Test;username=postgres;password=121983", + "connection-string": "Host=localhost;Database=datagatewaytest;username=REPLACEME;password=REPLACEME", "options": {} }, "runtime": { From cf385450254d923d3596d1393a63e6e48b4eb89c Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 22 Mar 2024 15:45:12 -0700 Subject: [PATCH 11/11] revert config, oops --- src/Service.Tests/dab-config.MsSql.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/dab-config.MsSql.json b/src/Service.Tests/dab-config.MsSql.json index 4c34ed0790..dbec0174ef 100644 --- a/src/Service.Tests/dab-config.MsSql.json +++ b/src/Service.Tests/dab-config.MsSql.json @@ -2,7 +2,7 @@ "$schema": "https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json", "data-source": { "database-type": "mssql", - "connection-string": "Server=(localdb)\\MSSQLLocalDB;Database=master;Integrated Security=true;Persist Security Info=False;User ID=sa;Password=SSMSark!97TRS!;MultipleActiveResultSets=False;Connection Timeout=5;Encrypt=False;", + "connection-string": "Server=tcp:127.0.0.1,1433;Persist Security Info=False;User ID=sa;Password=REPLACEME;MultipleActiveResultSets=False;Connection Timeout=5;", "options": { "set-session-context": true }