Skip to content

Commit 03fdc9c

Browse files
committed
Refactor field resolution closer to schema types
Much of the work of executing resolvers relates to the resolvable portion of fields, rather than the selection made for the request. With the introduction of directive visitors, these were being recreated for each request, instead of being built once, and re-used. By pushing the resolution of fields down into the `resolvable.Field`, directives applied to the field definition can be setup just once when the schema is created, and re-used across all requests. Small exceptions to this exist for field types that embed this to provide request specific values (resolver instance and arguments), which get passed along with the call. The chain of directive visitor functions still needs to be recreated for each resolve call, because the inner resolve function needs to accept the request specific field resolver instance, but other per-request overheads should be removed.
1 parent 1ade750 commit 03fdc9c

File tree

3 files changed

+171
-150
lines changed

3 files changed

+171
-150
lines changed

internal/exec/exec.go

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,6 @@ func (r *Request) Execute(ctx context.Context, s *resolvable.Schema, op *types.O
6464
return out.Bytes(), r.Errs
6565
}
6666

67-
type resolverFunc func(ctx context.Context, args interface{}) (output interface{}, err error)
68-
69-
func (f resolverFunc) Resolve(ctx context.Context, args interface{}) (output interface{}, err error) {
70-
return f(ctx, args)
71-
}
72-
7367
type fieldToExec struct {
7468
field *selected.SchemaField
7569
sels []selected.Selection
@@ -78,58 +72,7 @@ type fieldToExec struct {
7872
}
7973

8074
func (f *fieldToExec) resolve(ctx context.Context) (output interface{}, err error) {
81-
var args interface{}
82-
83-
if f.field.ArgsPacker != nil {
84-
args = f.field.PackedArgs.Interface()
85-
}
86-
87-
currResolver := f.resolveField
88-
89-
for _, pd := range f.field.PackedDirectives {
90-
pd := pd // Needed to avoid passing only the last directive, since we're closing over this loop var pointer
91-
innerResolver := currResolver
92-
93-
currResolver = func(ctx context.Context, args interface{}) (output interface{}, err error) {
94-
return pd.Resolve(ctx, args, resolverFunc(innerResolver))
95-
}
96-
}
97-
98-
return currResolver(ctx, args)
99-
}
100-
101-
func (f *fieldToExec) resolveField(ctx context.Context, args interface{}) (output interface{}, err error) {
102-
if !f.field.UseMethodResolver() {
103-
res := f.resolver
104-
105-
// TODO extract out unwrapping ptr logic to a common place
106-
if res.Kind() == reflect.Ptr {
107-
res = res.Elem()
108-
}
109-
110-
return res.FieldByIndex(f.field.FieldIndex).Interface(), nil
111-
}
112-
113-
var in []reflect.Value
114-
var callOut []reflect.Value
115-
116-
if f.field.HasContext {
117-
in = append(in, reflect.ValueOf(ctx))
118-
}
119-
120-
if f.field.ArgsPacker != nil {
121-
in = append(in, reflect.ValueOf(args))
122-
}
123-
124-
callOut = f.resolver.Method(f.field.MethodIndex).Call(in)
125-
result := callOut[0]
126-
127-
if f.field.HasError && !callOut[1].IsNil() {
128-
resolverErr := callOut[1].Interface().(error)
129-
return result.Interface(), resolverErr
130-
}
131-
132-
return result.Interface(), nil
75+
return f.field.Resolve(ctx, f.resolver)
13376
}
13477

13578
func resolvedToNull(b *bytes.Buffer) bool {

internal/exec/resolvable/resolvable.go

Lines changed: 147 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ type Field struct {
4747
HasContext bool
4848
HasError bool
4949
ArgsPacker *packer.StructPacker
50-
DirectivesPackers map[string]*packer.StructPacker
50+
DirectiveVisitors []directives.ResolverInterceptor
5151
ValueExec Resolvable
5252
TraceLabel string
5353
}
@@ -56,6 +56,69 @@ func (f *Field) UseMethodResolver() bool {
5656
return len(f.FieldIndex) == 0
5757
}
5858

59+
func (f *Field) Resolve(ctx context.Context, resolver reflect.Value, args interface{}) (output interface{}, err error) {
60+
// Short circuit case to avoid wrapping functions
61+
// TODO: confirm performance / memory difference, is it needed?
62+
if len(f.DirectiveVisitors) == 0 {
63+
return f.resolveInternal(ctx, resolver, args)
64+
}
65+
66+
currResolver := func(ctx context.Context, args interface{}) (output interface{}, err error) {
67+
return f.resolveInternal(ctx, resolver, args)
68+
}
69+
70+
for _, pd := range f.DirectiveVisitors {
71+
pd := pd // Needed to avoid passing only the last directive, since we're closing over this loop var pointer
72+
innerResolver := currResolver
73+
74+
currResolver = func(ctx context.Context, args interface{}) (output interface{}, err error) {
75+
return pd.Resolve(ctx, args, resolverFunc(innerResolver))
76+
}
77+
}
78+
79+
return currResolver(ctx, args)
80+
}
81+
82+
func (f *Field) resolveInternal(ctx context.Context, resolver reflect.Value, args interface{}) (output interface{}, err error) {
83+
if !f.UseMethodResolver() {
84+
res := resolver
85+
86+
// TODO extract out unwrapping ptr logic to a common place
87+
if res.Kind() == reflect.Ptr {
88+
res = res.Elem()
89+
}
90+
91+
return res.FieldByIndex(f.FieldIndex).Interface(), nil
92+
}
93+
94+
var in []reflect.Value
95+
var callOut []reflect.Value
96+
97+
if f.HasContext {
98+
in = append(in, reflect.ValueOf(ctx))
99+
}
100+
101+
if f.ArgsPacker != nil {
102+
in = append(in, reflect.ValueOf(args))
103+
}
104+
105+
callOut = resolver.Method(f.MethodIndex).Call(in)
106+
result := callOut[0]
107+
108+
if f.HasError && !callOut[1].IsNil() {
109+
resolverErr := callOut[1].Interface().(error)
110+
return result.Interface(), resolverErr
111+
}
112+
113+
return result.Interface(), nil
114+
}
115+
116+
type resolverFunc func(ctx context.Context, args interface{}) (output interface{}, err error)
117+
118+
func (f resolverFunc) Resolve(ctx context.Context, args interface{}) (output interface{}, err error) {
119+
return f(ctx, args)
120+
}
121+
59122
type TypeAssertion struct {
60123
MethodIndex int
61124
TypeExec Resolvable
@@ -81,7 +144,12 @@ func ApplyResolver(s *types.Schema, resolver interface{}, dirVisitors []directiv
81144
return nil, err
82145
}
83146

84-
b := newBuilder(s, ds)
147+
directivePackers, err := buildDirectivePackers(s, ds)
148+
if err != nil {
149+
return nil, err
150+
}
151+
152+
b := newBuilder(s, directivePackers)
85153

86154
var query, mutation, subscription Resolvable
87155

@@ -158,6 +226,44 @@ func ApplyResolver(s *types.Schema, resolver interface{}, dirVisitors []directiv
158226
}, nil
159227
}
160228

229+
func buildDirectivePackers(s *types.Schema, visitors map[string]directives.Directive) (map[string]*packer.StructPacker, error) {
230+
// Directive packers need to use a dedicated builder which is ready ('finish()' called) while
231+
// schema fields (and their argument packers) are still being built
232+
builder := packer.NewBuilder()
233+
234+
packers := map[string]*packer.StructPacker{}
235+
for _, d := range s.Directives {
236+
n := d.Name
237+
238+
v, ok := visitors[n]
239+
if !ok {
240+
// Directives which need visitors have already been checked
241+
// Anything without a visitor now is an in-built directive without a packer.
242+
continue
243+
}
244+
245+
if _, ok := v.(directives.ResolverInterceptor); !ok {
246+
// Directive doesn't apply at field resolution time, skip it
247+
continue
248+
}
249+
250+
r := reflect.TypeOf(v)
251+
252+
p, err := builder.MakeStructPacker(d.Arguments, r)
253+
if err != nil {
254+
return nil, err
255+
}
256+
257+
packers[n] = p
258+
}
259+
260+
if err := builder.Finish(); err != nil {
261+
return nil, err
262+
}
263+
264+
return packers, nil
265+
}
266+
161267
func applyDirectives(s *types.Schema, visitors []directives.Directive) (map[string]directives.Directive, error) {
162268
byName := make(map[string]directives.Directive, len(s.Directives))
163269

@@ -207,10 +313,10 @@ func applyDirectives(s *types.Schema, visitors []directives.Directive) (map[stri
207313
}
208314

209315
type execBuilder struct {
210-
schema *types.Schema
211-
resMap map[typePair]*resMapEntry
212-
directives map[string]directives.Directive
213-
packerBuilder *packer.Builder
316+
schema *types.Schema
317+
resMap map[typePair]*resMapEntry
318+
directivePackers map[string]*packer.StructPacker
319+
packerBuilder *packer.Builder
214320
}
215321

216322
type typePair struct {
@@ -223,12 +329,12 @@ type resMapEntry struct {
223329
targets []*Resolvable
224330
}
225331

226-
func newBuilder(s *types.Schema, directives map[string]directives.Directive) *execBuilder {
332+
func newBuilder(s *types.Schema, directivePackers map[string]*packer.StructPacker) *execBuilder {
227333
return &execBuilder{
228-
schema: s,
229-
resMap: make(map[typePair]*resMapEntry),
230-
directives: directives,
231-
packerBuilder: packer.NewBuilder(),
334+
schema: s,
335+
resMap: make(map[typePair]*resMapEntry),
336+
directivePackers: directivePackers,
337+
packerBuilder: packer.NewBuilder(),
232338
}
233339
}
234340

@@ -469,39 +575,9 @@ func (b *execBuilder) makeFieldExec(typeName string, f *types.FieldDefinition, m
469575
}
470576
}
471577

472-
directivesPackers := map[string]*packer.StructPacker{}
473-
for _, d := range f.Directives {
474-
n := d.Name.Name
475-
476-
// skip special directives without packers
477-
if n == "deprecated" {
478-
continue
479-
}
480-
481-
v, ok := b.directives[n]
482-
if !ok {
483-
return nil, fmt.Errorf("directive %q on field %q does not have a visitor registered with the schema", n, f.Name)
484-
}
485-
486-
if _, ok := v.(directives.ResolverInterceptor); !ok {
487-
// Directive doesn't apply at field resolution time, skip it
488-
continue
489-
}
490-
491-
r := reflect.TypeOf(v)
492-
493-
// The directive definition is needed here in order to get the arguments definition list.
494-
// d.Arguments wouldn't work in this case because it does not contain args type information.
495-
dd, ok := b.schema.Directives[n]
496-
if !ok {
497-
return nil, fmt.Errorf("directive definition %q is not defined in the schema", n)
498-
}
499-
p, err := b.packerBuilder.MakeStructPacker(dd.Arguments, r)
500-
if err != nil {
501-
return nil, err
502-
}
503-
504-
directivesPackers[n] = p
578+
ds, err := packDirectives(f.Directives, b.directivePackers)
579+
if err != nil {
580+
return nil, err
505581
}
506582

507583
fe := &Field{
@@ -511,7 +587,7 @@ func (b *execBuilder) makeFieldExec(typeName string, f *types.FieldDefinition, m
511587
FieldIndex: fieldIndex,
512588
HasContext: hasContext,
513589
ArgsPacker: argsPacker,
514-
DirectivesPackers: directivesPackers,
590+
DirectiveVisitors: ds,
515591
HasError: hasError,
516592
TraceLabel: fmt.Sprintf("GraphQL field: %s.%s", typeName, f.Name),
517593
}
@@ -533,6 +609,32 @@ func (b *execBuilder) makeFieldExec(typeName string, f *types.FieldDefinition, m
533609
return fe, nil
534610
}
535611

612+
func packDirectives(ds types.DirectiveList, packers map[string]*packer.StructPacker) ([]directives.ResolverInterceptor, error) {
613+
packed := make([]directives.ResolverInterceptor, 0, len(ds))
614+
for _, d := range ds {
615+
dp, ok := packers[d.Name.Name]
616+
if !ok {
617+
continue // skip directives without packers
618+
}
619+
620+
args := make(map[string]interface{})
621+
for _, arg := range d.Arguments {
622+
args[arg.Name.Name] = arg.Value.Deserialize(nil)
623+
}
624+
625+
p, err := dp.Pack(args)
626+
if err != nil {
627+
return nil, err
628+
}
629+
630+
v := p.Interface().(directives.ResolverInterceptor)
631+
632+
packed = append(packed, v)
633+
}
634+
635+
return packed, nil
636+
}
637+
536638
func findMethod(t reflect.Type, name string) int {
537639
for i := 0; i < t.NumMethod(); i++ {
538640
if strings.EqualFold(stripUnderscore(name), stripUnderscore(t.Method(i).Name)) {

0 commit comments

Comments
 (0)