From c68f55e1a30ca820f14339c068cfe2226325682c Mon Sep 17 00:00:00 2001 From: Mirya Date: Mon, 26 Feb 2018 00:35:55 +0100 Subject: [PATCH 1/3] Expose the tree of field subselections to the resolver on demand --- README.md | 3 ++- internal/exec/exec.go | 22 ++++++++++++++++++++++ internal/exec/resolvable/resolvable.go | 10 ++++++++++ selected/selected.go | 6 ++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 selected/selected.go diff --git a/README.md b/README.md index eead9b08..8a3d56fa 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,11 @@ The project is under heavy development. It is stable enough so we use it in prod A resolver must have one method for each field of the GraphQL type it resolves. The method name has to be [exported](https://golang.org/ref/spec#Exported_identifiers) and match the field's name in a non-case-sensitive way. -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 39b6456a..fde93924 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -14,6 +14,7 @@ import ( "github.com/neelance/graphql-go/internal/query" "github.com/neelance/graphql-go/internal/schema" "github.com/neelance/graphql-go/log" + pubselected "github.com/neelance/graphql-go/selected" "github.com/neelance/graphql-go/trace" ) @@ -147,6 +148,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{}{} @@ -185,6 +204,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 := f.resolver.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 c681cf20..7dfcb576 100644 --- a/internal/exec/resolvable/resolvable.go +++ b/internal/exec/resolvable/resolvable.go @@ -9,6 +9,7 @@ import ( "github.com/neelance/graphql-go/internal/common" "github.com/neelance/graphql-go/internal/exec/packer" "github.com/neelance/graphql-go/internal/schema" + pubselected "github.com/neelance/graphql-go/selected" ) type Schema struct { @@ -34,6 +35,7 @@ type Field struct { MethodIndex int HasContext bool ArgsPacker *packer.StructPacker + HasSelected bool HasError bool ValueExec Resolvable TraceLabel string @@ -251,6 +253,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, methodIndex int, methodHasReceiver bool) (*Field, error) { @@ -280,6 +283,12 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect. in = in[1:] } + // NOTE: we're passing selected.Selection[], not checking the specific type due to resolvable <-> selected circular dependency + hasSelected := len(in) > 0 && in[0] == selectedType + if hasSelected { + in = in[1:] + } + if len(in) > 0 { return nil, fmt.Errorf("too many parameters") } @@ -300,6 +309,7 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect. TypeName: typeName, MethodIndex: methodIndex, 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 +} From c9d96554c00b3bcb0919c76840c02d10994fedb2 Mon Sep 17 00:00:00 2001 From: Mirya Date: Tue, 6 Mar 2018 10:34:58 +0100 Subject: [PATCH 2/3] Fix padding --- internal/exec/resolvable/resolvable.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/exec/resolvable/resolvable.go b/internal/exec/resolvable/resolvable.go index 6dfd6439..253fb6d0 100644 --- a/internal/exec/resolvable/resolvable.go +++ b/internal/exec/resolvable/resolvable.go @@ -9,7 +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" + pubselected "github.com/graph-gophers/graphql-go/selected" ) type Schema struct { @@ -35,7 +35,7 @@ type Field struct { MethodIndex int HasContext bool HasError bool - HasSelected bool + HasSelected bool ArgsPacker *packer.StructPacker ValueExec Resolvable TraceLabel string From 3e1e6094b021d01e795064a250e94666d7cf2237 Mon Sep 17 00:00:00 2001 From: Mirya Date: Tue, 6 Mar 2018 10:37:24 +0100 Subject: [PATCH 3/3] The NOTE no longer applies --- internal/exec/resolvable/resolvable.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/exec/resolvable/resolvable.go b/internal/exec/resolvable/resolvable.go index 253fb6d0..cdd9f54a 100644 --- a/internal/exec/resolvable/resolvable.go +++ b/internal/exec/resolvable/resolvable.go @@ -283,7 +283,6 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect. in = in[1:] } - // NOTE: we're passing selected.Selection[], not checking the specific type due to resolvable <-> selected circular dependency hasSelected := len(in) > 0 && in[0] == selectedType if hasSelected { in = in[1:]