Skip to content

Commit efa4da5

Browse files
committed
Added pageInfo as connection flag (#8153)
1 parent f1b9da5 commit efa4da5

8 files changed

+170
-51
lines changed

src/HotChocolate/Core/src/Types/Resolvers/Expressions/Parameters/ConnectionFlagsParameterExpressionBuilder.cs

+1-20
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,5 @@ public T Execute<T>(IResolverContext context)
3434
=> (T)(object)Execute(context);
3535

3636
private static ConnectionFlags Execute(IResolverContext context)
37-
{
38-
var flags = ConnectionFlags.None;
39-
40-
if (context.IsSelected("totalCount"))
41-
{
42-
flags |= ConnectionFlags.TotalCount;
43-
}
44-
45-
if (context.IsSelected("edges"))
46-
{
47-
flags |= ConnectionFlags.Edges;
48-
}
49-
50-
if (context.IsSelected("nodes"))
51-
{
52-
flags |= ConnectionFlags.Nodes;
53-
}
54-
55-
return flags;
56-
}
37+
=> ConnectionFlagsHelper.GetConnectionFlags(context);
5738
}

src/HotChocolate/Core/src/Types/Types/Pagination/ConnectionFlags.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,17 @@ public enum ConnectionFlags
2727
TotalCount = 4,
2828

2929
/// <summary>
30-
/// The relative cursor field was requested by the user.
30+
/// The page info field was requested by the user.
3131
/// </summary>
32-
RelativeCursor = 8,
32+
PageInfo = 8,
3333

3434
/// <summary>
35-
/// The nodes or edges field was requested by the user.
35+
/// The relative cursor field was requested by the user.
3636
/// </summary>
37-
NodesOrEdges = Edges | Nodes,
37+
RelativeCursor = 16,
3838

3939
/// <summary>
4040
/// All fields were requested by the user.
4141
/// </summary>
42-
All = Edges | Nodes | TotalCount | RelativeCursor
42+
All = Edges | Nodes | TotalCount | PageInfo | RelativeCursor
4343
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Concurrent;
2+
using System.Collections.Immutable;
23
using HotChocolate.Language;
34
using HotChocolate.Resolvers;
45
using HotChocolate.Types.Descriptors.Definitions;
@@ -23,7 +24,7 @@ public static ConnectionFlags GetConnectionFlags(IResolverContext context)
2324
string.Format(_keyFormat, context.Selection.Id),
2425
static (_, ctx) =>
2526
{
26-
if(ctx.Selection.Field is ObjectField field
27+
if (ctx.Selection.Field is ObjectField field
2728
&& !field.Flags.HasFlag(FieldFlags.Connection))
2829
{
2930
return ConnectionFlags.None;
@@ -33,48 +34,42 @@ public static ConnectionFlags GetConnectionFlags(IResolverContext context)
3334

3435
var connectionFlags = ConnectionFlags.None;
3536

36-
if(ctx.IsSelected("edges"))
37+
if (ctx.IsSelected("edges"))
3738
{
3839
connectionFlags |= ConnectionFlags.Edges;
3940
}
4041

41-
if(ctx.IsSelected("nodes"))
42+
if (ctx.IsSelected("nodes"))
4243
{
4344
connectionFlags |= ConnectionFlags.Nodes;
4445
}
4546

46-
if(ctx.IsSelected("totalCount"))
47+
if (ctx.IsSelected("totalCount"))
4748
{
4849
connectionFlags |= ConnectionFlags.TotalCount;
4950
}
5051

51-
if ((options.EnableRelativeCursors ?? PagingDefaults.EnableRelativeCursors)
52-
&& options.RelativeCursorFields.Count > 0)
52+
if (options.PageInfoFields.Count > 0
53+
|| ((options.EnableRelativeCursors ?? PagingDefaults.EnableRelativeCursors)
54+
&& options.RelativeCursorFields.Count > 0))
5355
{
5456
var startSelections = ctx.Select();
5557
var selectionContext = new IsSelectedContext(ctx.Schema, startSelections);
5658

57-
foreach (var relativeCursor in options.RelativeCursorFields)
59+
if (options.PageInfoFields.Count > 0)
5860
{
59-
// we reset the state here so that each visitation starts fresh.
60-
selectionContext.AllSelected = true;
61-
selectionContext.Selections.Clear();
62-
selectionContext.Selections.Push(startSelections);
63-
64-
// we parse the selection pattern, we in essence use
65-
// a SelectionSetNode as a selection pattern.
66-
var selectionPattern = ParsePattern(relativeCursor);
67-
68-
// then we visit the selection and if one selection of the selection pattern
69-
// is not hit we break the loop and do not set the relative cursor flag.
70-
IsSelectedVisitor.Instance.Visit(selectionPattern, selectionContext);
71-
72-
// if however all selections of the selection pattern are
73-
// hit we set the relative cursor flag.
74-
if (selectionContext.AllSelected)
61+
if (ArePatternsMatched(startSelections, selectionContext, options.PageInfoFields))
62+
{
63+
connectionFlags |= ConnectionFlags.PageInfo;
64+
}
65+
}
66+
67+
if ((options.EnableRelativeCursors ?? PagingDefaults.EnableRelativeCursors)
68+
&& options.RelativeCursorFields.Count > 0)
69+
{
70+
if (ArePatternsMatched(startSelections, selectionContext, options.RelativeCursorFields))
7571
{
7672
connectionFlags |= ConnectionFlags.RelativeCursor;
77-
break;
7873
}
7974
}
8075
}
@@ -84,6 +79,37 @@ public static ConnectionFlags GetConnectionFlags(IResolverContext context)
8479
context);
8580
}
8681

82+
private static bool ArePatternsMatched(
83+
ISelectionCollection startSelections,
84+
IsSelectedContext selectionContext,
85+
ImmutableHashSet<string> patterns)
86+
{
87+
foreach (var fieldPattern in patterns)
88+
{
89+
// we reset the state here so that each visitation starts fresh.
90+
selectionContext.AllSelected = true;
91+
selectionContext.Selections.Clear();
92+
selectionContext.Selections.Push(startSelections);
93+
94+
// we parse the selection pattern, we in essence use
95+
// a SelectionSetNode as a selection pattern.
96+
var selectionPattern = ParsePattern(fieldPattern);
97+
98+
// then we visit the selection and if one selection of the selection pattern
99+
// is not hit we break the loop and signal that the pattern was not hit.
100+
IsSelectedVisitor.Instance.Visit(selectionPattern, selectionContext);
101+
102+
// if however all selections of the selection pattern are
103+
// hit we exit early and return true.
104+
if (selectionContext.AllSelected)
105+
{
106+
return true;
107+
}
108+
}
109+
110+
return false;
111+
}
112+
87113
private static SelectionSetNode ParsePattern(string selectionSet)
88114
=> _parsedSelectionSets.GetOrAdd(selectionSet, static s => ParseSelectionSet($"{{ {s} }}"));
89115
}

src/HotChocolate/Core/src/Types/Types/Pagination/PagingOptions.cs

+18-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,21 @@ public class PagingOptions
7070
/// Gets or sets the fields that represent relative cursors.
7171
/// </summary>
7272
public ImmutableHashSet<string> RelativeCursorFields { get; set; } =
73-
["pageInfo { forwardCursors }", "pageInfo { backwardCursors }"];
73+
[
74+
"pageInfo { forwardCursors }",
75+
"pageInfo { backwardCursors }"
76+
];
77+
78+
/// <summary>
79+
/// Gets or sets the fields that represent page infos like hasNextPage or startCursor.
80+
/// </summary>
81+
public ImmutableHashSet<string> PageInfoFields { get; set; } =
82+
[
83+
"pageInfo { startCursor }",
84+
"pageInfo { endCursor }" ,
85+
"pageInfo { hasNextPage }" ,
86+
"pageInfo { hasPreviousPage }"
87+
];
7488

7589
/// <summary>
7690
/// Merges the <paramref name="other"/> options into this options instance wherever
@@ -92,6 +106,7 @@ internal void Merge(PagingOptions other)
92106
IncludeNodesField ??= other.IncludeNodesField;
93107
EnableRelativeCursors ??= other.EnableRelativeCursors;
94108
RelativeCursorFields = RelativeCursorFields.Union(other.RelativeCursorFields);
109+
PageInfoFields = PageInfoFields.Union(other.PageInfoFields);
95110
}
96111

97112
/// <summary>
@@ -110,6 +125,7 @@ internal PagingOptions Copy()
110125
ProviderName = ProviderName,
111126
IncludeNodesField = IncludeNodesField,
112127
EnableRelativeCursors = EnableRelativeCursors,
113-
RelativeCursorFields = RelativeCursorFields
128+
RelativeCursorFields = RelativeCursorFields,
129+
PageInfoFields = PageInfoFields
114130
};
115131
}

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

+26
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,32 @@ public async Task Query_Brands_First_2_Products_First_2_ForwardCursors()
313313
MatchSnapshot(result, interceptor);
314314
}
315315

316+
[Fact]
317+
public async Task Verify_That_PageInfo_Flag_Is_Correctly_Inferred()
318+
{
319+
// arrange
320+
using var interceptor = new TestQueryInterceptor();
321+
322+
// act
323+
var result = await ExecuteAsync(
324+
"""
325+
{
326+
brands(first: 1) {
327+
nodes {
328+
products(first: 2) {
329+
pageInfo {
330+
endCursor
331+
}
332+
}
333+
}
334+
}
335+
}
336+
""");
337+
338+
// assert
339+
MatchSnapshot(result, interceptor);
340+
}
341+
316342
[Fact]
317343
public async Task Query_Products_Include_TotalCount()
318344
{

src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Types/Brands/BrandNode.cs

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ public static async Task<PageConnection<Product>> GetProductsAsync(
2222
ISelection selection,
2323
CancellationToken cancellationToken)
2424
{
25+
// we for test purposes only return an empty page if the connection flags are set to PageInfo
26+
if(connectionFlags == ConnectionFlags.PageInfo)
27+
{
28+
return new PageConnection<Product>(Page<Product>.Empty);
29+
}
30+
2531
var page = await productService.GetProductsByBrandAsync(brand.Id, pagingArgs, query, cancellationToken);
2632
return new PageConnection<Product>(page);
2733
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Verify_That_PageInfo_Flag_Is_Correctly_Inferred
2+
3+
## Result
4+
5+
```json
6+
{
7+
"data": {
8+
"brands": {
9+
"nodes": [
10+
{
11+
"products": {
12+
"pageInfo": {
13+
"endCursor": null
14+
}
15+
}
16+
}
17+
]
18+
}
19+
}
20+
}
21+
```
22+
23+
## Query 1
24+
25+
```sql
26+
-- @__p_0='2'
27+
SELECT b."Id", b."Name"
28+
FROM "Brands" AS b
29+
ORDER BY b."Name" DESC, b."Id"
30+
LIMIT @__p_0
31+
```
32+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Verify_That_PageInfo_Flag_Is_Correctly_Inferred
2+
3+
## Result
4+
5+
```json
6+
{
7+
"data": {
8+
"brands": {
9+
"nodes": [
10+
{
11+
"products": {
12+
"pageInfo": {
13+
"endCursor": null
14+
}
15+
}
16+
}
17+
]
18+
}
19+
}
20+
}
21+
```
22+
23+
## Query 1
24+
25+
```sql
26+
-- @__p_0='2'
27+
SELECT b."Id", b."Name"
28+
FROM "Brands" AS b
29+
ORDER BY b."Name" DESC, b."Id"
30+
LIMIT @__p_0
31+
```
32+

0 commit comments

Comments
 (0)