Skip to content

Commit d3cb1e2

Browse files
committed
Reduce totalCount requests when using relative cursors. (#8150)
1 parent 4be35be commit d3cb1e2

File tree

36 files changed

+315
-285
lines changed

36 files changed

+315
-285
lines changed

src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExpressionHelpers.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public static BatchExpression<TK, TV> BuildBatchExpression<TK, TV>(
220220
requestedCount = arguments.Last.Value;
221221
}
222222

223-
if (arguments.EnableRelativeCursors && cursor?.IsRelative == true)
223+
if (cursor?.IsRelative == true)
224224
{
225225
if ((arguments.Last is not null && cursor.Offset > 0) || (arguments.First is not null && cursor.Offset < 0))
226226
{

src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ public static async ValueTask<Page<T>> ToPageAsync<T>(
100100
arguments = arguments with { First = 10 };
101101
}
102102

103+
// if relative cursors are enabled and no cursor is provided
104+
// we must do an initial count of the dataset.
103105
if (arguments.EnableRelativeCursors
104106
&& string.IsNullOrEmpty(arguments.After)
105107
&& string.IsNullOrEmpty(arguments.Before))
@@ -153,9 +155,10 @@ public static async ValueTask<Page<T>> ToPageAsync<T>(
153155
}
154156
}
155157

156-
if (arguments.EnableRelativeCursors && cursor?.IsRelative == true)
158+
if (cursor?.IsRelative == true)
157159
{
158-
if ((arguments.Last is not null && cursor.Offset > 0) || (arguments.First is not null && cursor.Offset < 0))
160+
if ((arguments.Last is not null && cursor.Offset > 0)
161+
|| (arguments.First is not null && cursor.Offset < 0))
159162
{
160163
throw new ArgumentException(
161164
"Positive offsets are not allowed with `last`, and negative offsets are not allowed with `first`.",

src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs

+14-51
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,10 @@ private void WriteResolverArguments(Resolver resolver, IMethodSymbol resolverMet
880880
"var args{0}_options = global::{1}.GetPagingOptions(context.Schema, context.Selection.Field);",
881881
i,
882882
WellKnownTypes.PagingHelper);
883+
Writer.WriteIndentedLine(
884+
"var args{0}_flags = global::{1}.GetConnectionFlags(context);",
885+
i,
886+
WellKnownTypes.ConnectionFlagsHelper);
883887
Writer.WriteIndentedLine("var args{0}_first = context.ArgumentValue<int?>(\"first\");", i);
884888
Writer.WriteIndentedLine("var args{0}_after = context.ArgumentValue<string?>(\"after\");", i);
885889
Writer.WriteIndentedLine("int? args{0}_last = null;", i);
@@ -920,7 +924,10 @@ private void WriteResolverArguments(Resolver resolver, IMethodSymbol resolverMet
920924
Writer.WriteIndentedLine("{");
921925
using (Writer.IncreaseIndent())
922926
{
923-
Writer.WriteIndentedLine("args{0}_includeTotalCount = context.IsSelected(\"totalCount\");", i);
927+
Writer.WriteIndentedLine(
928+
"args{0}_includeTotalCount = args{0}_flags.HasFlag(global::{1}.TotalCount);",
929+
i,
930+
WellKnownTypes.ConnectionFlags);
924931
}
925932

926933
Writer.WriteIndentedLine("}");
@@ -940,14 +947,9 @@ private void WriteResolverArguments(Resolver resolver, IMethodSymbol resolverMet
940947
using (Writer.IncreaseIndent())
941948
{
942949
Writer.WriteIndentedLine(
943-
"EnableRelativeCursors = args{0}_options.EnableRelativeCursors",
944-
i);
945-
using (Writer.IncreaseIndent())
946-
{
947-
Writer.WriteIndentedLine(
948-
"?? global::{0}.EnableRelativeCursors",
949-
WellKnownTypes.PagingDefaults);
950-
}
950+
"EnableRelativeCursors = args{0}_flags.HasFlag(global::{1}.RelativeCursor)",
951+
i,
952+
WellKnownTypes.ConnectionFlags);
951953
}
952954

953955
Writer.WriteIndentedLine("};");
@@ -956,48 +958,9 @@ private void WriteResolverArguments(Resolver resolver, IMethodSymbol resolverMet
956958

957959
case ResolverParameterKind.ConnectionFlags:
958960
Writer.WriteIndentedLine(
959-
"var args{0} = global::{1}.Nothing;",
961+
"var args{0} = global::{1}.GetConnectionFlags(context);",
960962
i,
961-
WellKnownTypes.ConnectionFlags);
962-
Writer.WriteLine();
963-
964-
Writer.WriteIndentedLine(
965-
"if(context.IsSelected(\"edges\"))");
966-
Writer.WriteIndentedLine("{");
967-
using (Writer.IncreaseIndent())
968-
{
969-
Writer.WriteIndentedLine(
970-
"args{0} |= global::{1}.Edges;",
971-
i,
972-
WellKnownTypes.ConnectionFlags);
973-
}
974-
Writer.WriteIndentedLine("}");
975-
Writer.WriteLine();
976-
977-
Writer.WriteIndentedLine(
978-
"if(context.IsSelected(\"nodes\"))");
979-
Writer.WriteIndentedLine("{");
980-
using (Writer.IncreaseIndent())
981-
{
982-
Writer.WriteIndentedLine(
983-
"args{0} |= global::{1}.Nodes;",
984-
i,
985-
WellKnownTypes.ConnectionFlags);
986-
}
987-
Writer.WriteIndentedLine("}");
988-
Writer.WriteLine();
989-
990-
Writer.WriteIndentedLine(
991-
"if(context.IsSelected(\"totalCount\"))");
992-
Writer.WriteIndentedLine("{");
993-
using (Writer.IncreaseIndent())
994-
{
995-
Writer.WriteIndentedLine(
996-
"args{0} |= global::{1}.TotalCount;",
997-
i,
998-
WellKnownTypes.ConnectionFlags);
999-
}
1000-
Writer.WriteIndentedLine("}");
963+
WellKnownTypes.ConnectionFlagsHelper);
1001964
break;
1002965

1003966
case ResolverParameterKind.Unknown:
@@ -1007,7 +970,7 @@ private void WriteResolverArguments(Resolver resolver, IMethodSymbol resolverMet
1007970
resolver.Member.Name,
1008971
bindingIndex++,
1009972
ToFullyQualifiedString(parameter.Type, resolverMethod, typeLookup));
1010-
break;
973+
break;
1011974

1012975
default:
1013976
throw new ArgumentOutOfRangeException();

src/HotChocolate/Core/src/Types.Analyzers/WellKnownTypes.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ public static class WellKnownTypes
9393
public const string ObjectTypeDefinition = "HotChocolate.Types.Descriptors.Definitions.ObjectTypeDefinition";
9494
public const string NonNullType = "HotChocolate.Types.NonNullType";
9595
public const string ListType = "HotChocolate.Types.ListType";
96-
public const string ConnectionFlags = "HotChocolate.Types.Paging.ConnectionFlags";
96+
public const string ConnectionFlags = "HotChocolate.Types.Pagination.ConnectionFlags";
97+
public const string ConnectionFlagsHelper = "HotChocolate.Types.Pagination.ConnectionFlagsHelper";
9798

9899
public static HashSet<string> TypeClass { get; } =
99100
[

src/HotChocolate/Core/src/Types.CursorPagination/Extensions/UseConnectionAttribute.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public sealed class UseConnectionAttribute : DescriptorAttribute
2020
private bool? _allowBackwardPagination;
2121
private bool? _requirePagingBoundaries;
2222
private bool? _inferConnectionNameFromField;
23-
private bool? _EnableRelativeCursors;
23+
private bool? _enableRelativeCursors;
2424

2525
/// <summary>
2626
/// Overrides the global paging options for the annotated field.
@@ -94,8 +94,8 @@ public bool InferConnectionNameFromField
9494
/// </summary>
9595
public bool EnableRelativeCursors
9696
{
97-
get => _EnableRelativeCursors ?? PagingDefaults.EnableRelativeCursors;
98-
set => _EnableRelativeCursors = value;
97+
get => _enableRelativeCursors ?? PagingDefaults.EnableRelativeCursors;
98+
set => _enableRelativeCursors = value;
9999
}
100100

101101
public string? Name { get; set; }
@@ -119,7 +119,7 @@ protected internal override void TryConfigure(
119119
RequirePagingBoundaries = _requirePagingBoundaries,
120120
InferConnectionNameFromField = _inferConnectionNameFromField,
121121
ProviderName = Name,
122-
EnableRelativeCursors = _EnableRelativeCursors,
122+
EnableRelativeCursors = _enableRelativeCursors
123123
};
124124

125125
if (descriptor is IObjectFieldDescriptor fieldDesc)

src/HotChocolate/Core/src/Types/Resolvers/DefaultParameterBindingResolver.cs

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public DefaultParameterBindingResolver(
1717
{
1818
var serviceInspector = applicationServices.GetService<IServiceProviderIsService>();
1919

20+
// explicit internal expression builders will be added first.
2021
var bindingFactories = new List<IParameterBindingFactory>
2122
{
2223
new ParentParameterExpressionBuilder(),
@@ -31,6 +32,8 @@ public DefaultParameterBindingResolver(
3132

3233
if (customBindingFactories is not null)
3334
{
35+
// then we will add custom parameter expression builder and
36+
// give the user a chance to override our implicit expression builder.
3437
bindingFactories.AddRange(
3538
customBindingFactories
3639
.Where(t => !t.IsDefaultHandler)

src/HotChocolate/Core/src/Types/Resolvers/DefaultResolverCompiler.cs

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public DefaultResolverCompiler(
9191
expressionBuilders.Add(new FieldParameterExpressionBuilder());
9292
expressionBuilders.Add(new ClaimsPrincipalParameterExpressionBuilder());
9393
expressionBuilders.Add(new PathParameterExpressionBuilder());
94+
expressionBuilders.Add(new ConnectionFlagsParameterExpressionBuilder());
9495

9596
if (serviceInspector is not null)
9697
{

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public T Execute<T>(IResolverContext context)
3535

3636
private static ConnectionFlags Execute(IResolverContext context)
3737
{
38-
var flags = ConnectionFlags.Nothing;
38+
var flags = ConnectionFlags.None;
3939

4040
if (context.IsSelected("totalCount"))
4141
{

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

+12-84
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
using System.Linq.Expressions;
44
using System.Reflection;
55
using HotChocolate.Internal;
6-
using HotChocolate.Language;
7-
using HotChocolate.Language.Visitors;
86
using HotChocolate.Types;
97
using HotChocolate.Types.Attributes;
108
using HotChocolate.Types.Descriptors;
@@ -46,18 +44,19 @@ public void ApplyConfiguration(ParameterInfo parameter, ObjectFieldDescriptor de
4644
var definition = descriptor.Extend().Definition;
4745
definition.Configurations.Add(
4846
new CompleteConfiguration((ctx, def) =>
49-
{
50-
if(!ctx.DescriptorContext.ContextData.TryGetValue(WellKnownContextData.PatternValidationTasks, out var value))
5147
{
52-
value = new List<IsSelectedPattern>();
53-
ctx.DescriptorContext.ContextData[WellKnownContextData.PatternValidationTasks] = value;
54-
}
55-
56-
var patterns = (List<IsSelectedPattern>)value!;
57-
patterns.Add(new IsSelectedPattern((ObjectType)ctx.Type, def.Name, attribute.Fields));
58-
},
59-
definition,
60-
ApplyConfigurationOn.AfterCompletion));
48+
if (!ctx.DescriptorContext.ContextData.TryGetValue(WellKnownContextData.PatternValidationTasks,
49+
out var value))
50+
{
51+
value = new List<IsSelectedPattern>();
52+
ctx.DescriptorContext.ContextData[WellKnownContextData.PatternValidationTasks] = value;
53+
}
54+
55+
var patterns = (List<IsSelectedPattern>)value!;
56+
patterns.Add(new IsSelectedPattern((ObjectType)ctx.Type, def.Name, attribute.Fields));
57+
},
58+
definition,
59+
ApplyConfigurationOn.AfterCompletion));
6160

6261
descriptor.Use(
6362
next => async ctx =>
@@ -133,77 +132,6 @@ public void ApplyConfiguration(ParameterInfo parameter, ObjectFieldDescriptor de
133132
}
134133
}
135134

136-
private sealed class IsSelectedVisitor : SyntaxWalker<IsSelectedContext>
137-
{
138-
protected override ISyntaxVisitorAction Enter(FieldNode node, IsSelectedContext context)
139-
{
140-
var selections = context.Selections.Peek();
141-
var responseName = node.Alias?.Value ?? node.Name.Value;
142-
143-
if (!selections.IsSelected(responseName))
144-
{
145-
context.AllSelected = false;
146-
return Break;
147-
}
148-
149-
if (node.SelectionSet is not null)
150-
{
151-
context.Selections.Push(selections.Select(responseName));
152-
}
153-
154-
return base.Enter(node, context);
155-
}
156-
157-
protected override ISyntaxVisitorAction Leave(FieldNode node, IsSelectedContext context)
158-
{
159-
if (node.SelectionSet is not null)
160-
{
161-
context.Selections.Pop();
162-
}
163-
164-
return base.Leave(node, context);
165-
}
166-
167-
protected override ISyntaxVisitorAction Enter(InlineFragmentNode node, IsSelectedContext context)
168-
{
169-
if (node.TypeCondition is not null)
170-
{
171-
var typeContext = context.Schema.GetType<INamedType>(node.TypeCondition.Name.Value);
172-
var selections = context.Selections.Peek();
173-
context.Selections.Push(selections.Select(typeContext));
174-
}
175-
176-
return base.Enter(node, context);
177-
}
178-
179-
protected override ISyntaxVisitorAction Leave(InlineFragmentNode node, IsSelectedContext context)
180-
{
181-
if (node.TypeCondition is not null)
182-
{
183-
context.Selections.Pop();
184-
}
185-
186-
return base.Leave(node, context);
187-
}
188-
189-
public static IsSelectedVisitor Instance { get; } = new();
190-
}
191-
192-
private sealed class IsSelectedContext
193-
{
194-
public IsSelectedContext(ISchema schema, ISelectionCollection selections)
195-
{
196-
Schema = schema;
197-
Selections.Push(selections);
198-
}
199-
200-
public ISchema Schema { get; }
201-
202-
public Stack<ISelectionCollection> Selections { get; } = new();
203-
204-
public bool AllSelected { get; set; } = true;
205-
}
206-
207135
private class IsSelectedBinding(string key) : IParameterBinding
208136
{
209137
public ArgumentKind Kind => ArgumentKind.LocalState;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
namespace HotChocolate.Resolvers;
2+
3+
/// <summary>
4+
/// Represents the context of the <see cref="IsSelectedVisitor"/>.
5+
/// </summary>
6+
public sealed class IsSelectedContext
7+
{
8+
/// <summary>
9+
/// Initializes a new instance of <see cref="IsSelectedContext"/> with the
10+
/// </summary>
11+
/// <param name="schema">
12+
/// The schema that is used to resolve the type of the selection set.
13+
/// </param>
14+
/// <param name="selections">
15+
/// The selection set that is used to determine if a field is selected.
16+
/// </param>
17+
public IsSelectedContext(ISchema schema, ISelectionCollection selections)
18+
{
19+
Schema = schema;
20+
Selections.Push(selections);
21+
}
22+
23+
/// <summary>
24+
/// Gets the schema that is used to resolve the type of the selection set.
25+
/// </summary>
26+
public ISchema Schema { get; }
27+
28+
/// <summary>
29+
/// Gets the selections that is used to determine if a field is selected.
30+
/// </summary>
31+
public Stack<ISelectionCollection> Selections { get; } = new();
32+
33+
/// <summary>
34+
/// Defines if all fields of the SelectionSet are selected.
35+
/// </summary>
36+
public bool AllSelected { get; set; } = true;
37+
}

0 commit comments

Comments
 (0)