diff --git a/README.md b/README.md index 01038bfa..f0f9c6f3 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,11 @@ When using `UseFieldResolvers` schema option, a struct field will be used *only* - a struct field does not implement an interface method - a struct field does not have arguments -The method has up to two arguments: +The method has up to three arguments: - Optional `context.Context` argument. - Mandatory `*struct { ... }` argument if the corresponding GraphQL field has arguments. The names of the struct fields have to be [exported](https://golang.org/ref/spec#Exported_identifiers) and have to match the names of the GraphQL arguments in a non-case-sensitive way. +- Optional `[]selected.SelectedField` argument to receive the tree of selected subfields in the GraphQL query (useful for preloading of database relations) The method has up to two results: diff --git a/internal/exec/exec.go b/internal/exec/exec.go index f6ec84fb..df55923b 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -15,6 +15,7 @@ import ( "github.com/graph-gophers/graphql-go/internal/query" "github.com/graph-gophers/graphql-go/internal/schema" "github.com/graph-gophers/graphql-go/log" + pubselected "github.com/graph-gophers/graphql-go/selected" "github.com/graph-gophers/graphql-go/trace" ) @@ -160,6 +161,24 @@ func typeOf(tf *selected.TypenameField, resolver reflect.Value) string { return "" } +func selectionToSelectedFields(sels []selected.Selection) []pubselected.SelectedField { + var selectedFields []pubselected.SelectedField + selsLen := len(sels) + if selsLen != 0 { + selectedFields = make([]pubselected.SelectedField, 0, selsLen) + for _, sel := range sels { + selField, ok := sel.(*selected.SchemaField) + if ok { + selectedFields = append(selectedFields, pubselected.SelectedField{ + Name: selField.Field.Name, + Selected: selectionToSelectedFields(selField.Sels), + }) + } + } + } + return selectedFields +} + func execFieldSelection(ctx context.Context, r *Request, f *fieldToExec, path *pathSegment, applyLimiter bool) { if applyLimiter { r.Limiter <- struct{}{} @@ -200,6 +219,9 @@ func execFieldSelection(ctx context.Context, r *Request, f *fieldToExec, path *p if f.field.ArgsPacker != nil { in = append(in, f.field.PackedArgs) } + if f.field.HasSelected { + in = append(in, reflect.ValueOf(selectionToSelectedFields(f.sels))) + } callOut := res.Method(f.field.MethodIndex).Call(in) result = callOut[0] if f.field.HasError && !callOut[1].IsNil() { diff --git a/internal/exec/resolvable/resolvable.go b/internal/exec/resolvable/resolvable.go index 27809230..06a74f4a 100644 --- a/internal/exec/resolvable/resolvable.go +++ b/internal/exec/resolvable/resolvable.go @@ -9,6 +9,7 @@ import ( "github.com/graph-gophers/graphql-go/internal/common" "github.com/graph-gophers/graphql-go/internal/exec/packer" "github.com/graph-gophers/graphql-go/internal/schema" + pubselected "github.com/graph-gophers/graphql-go/selected" ) type Schema struct { @@ -36,6 +37,7 @@ type Field struct { FieldIndex int HasContext bool HasError bool + HasSelected bool ArgsPacker *packer.StructPacker ValueExec Resolvable TraceLabel string @@ -281,6 +283,7 @@ func (b *execBuilder) makeObjectExec(typeName string, fields schema.FieldList, p } var contextType = reflect.TypeOf((*context.Context)(nil)).Elem() +var selectedType = reflect.TypeOf([]pubselected.SelectedField(nil)) var errorType = reflect.TypeOf((*error)(nil)).Elem() func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect.Method, sf reflect.StructField, @@ -289,6 +292,7 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect. var argsPacker *packer.StructPacker var hasError bool var hasContext bool + var hasSelected bool // Validate resolver method only when there is one if methodIndex != -1 { @@ -317,6 +321,11 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect. in = in[1:] } + hasSelected = len(in) > 0 && in[0] == selectedType + if hasSelected { + in = in[1:] + } + if len(in) > 0 { return nil, fmt.Errorf("too many parameters") } @@ -344,6 +353,7 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect. MethodIndex: methodIndex, FieldIndex: fieldIndex, HasContext: hasContext, + HasSelected: hasSelected, ArgsPacker: argsPacker, HasError: hasError, TraceLabel: fmt.Sprintf("GraphQL field: %s.%s", typeName, f.Name), diff --git a/selected/selected.go b/selected/selected.go new file mode 100644 index 00000000..89faa86d --- /dev/null +++ b/selected/selected.go @@ -0,0 +1,6 @@ +package selected + +type SelectedField struct { + Name string + Selected []SelectedField +}