From 17a9c79d166384bfa731fc6063e593dc2aae8d04 Mon Sep 17 00:00:00 2001 From: Michael Compton Date: Mon, 20 Apr 2020 18:34:50 +1000 Subject: [PATCH] graphql: execution refactor (#5244) --- graphql/admin/add_group.go | 11 ++- graphql/admin/admin.go | 65 ++++++------ graphql/admin/schema.go | 52 +++++----- graphql/admin/update_group.go | 21 ++-- graphql/dgraph/execute.go | 71 +++---------- graphql/e2e/common/error.go | 18 +--- graphql/resolve/mutation.go | 132 ++++++++++++++----------- graphql/resolve/mutation_rewriter.go | 58 +++++++---- graphql/resolve/mutation_test.go | 9 +- graphql/resolve/query.go | 93 +++++------------ graphql/resolve/resolver.go | 94 ++++++------------ graphql/resolve/resolver_error_test.go | 74 +++++++------- 12 files changed, 304 insertions(+), 394 deletions(-) diff --git a/graphql/admin/add_group.go b/graphql/admin/add_group.go index eb8d7fde0e4..02c77ff1a69 100644 --- a/graphql/admin/add_group.go +++ b/graphql/admin/add_group.go @@ -1,9 +1,9 @@ package admin import ( + "context" "fmt" - dgoapi "github.com/dgraph-io/dgo/v200/protos/api" "github.com/dgraph-io/dgraph/gql" "github.com/dgraph-io/dgraph/graphql/resolve" "github.com/dgraph-io/dgraph/graphql/schema" @@ -20,7 +20,9 @@ func NewAddGroupRewriter() resolve.MutationRewriter { // It ensures that only the last rule out of all duplicate rules in input is preserved. // A rule is duplicate if it has same predicate name as another rule. func (mrw *addGroupRewriter) Rewrite( - m schema.Mutation) (*gql.GraphQuery, []*dgoapi.Mutation, error) { + ctx context.Context, + m schema.Mutation) (*resolve.UpsertMutation, error) { + addGroupInput, _ := m.ArgValue(schema.InputArgName).([]interface{}) // remove rules with same predicate name for each group input @@ -32,16 +34,17 @@ func (mrw *addGroupRewriter) Rewrite( m.SetArgTo(schema.InputArgName, addGroupInput) - return ((*resolve.AddRewriter)(mrw)).Rewrite(m) + return ((*resolve.AddRewriter)(mrw)).Rewrite(ctx, m) } // FromMutationResult rewrites the query part of a GraphQL add mutation into a Dgraph query. func (mrw *addGroupRewriter) FromMutationResult( + ctx context.Context, mutation schema.Mutation, assigned map[string]string, result map[string]interface{}) (*gql.GraphQuery, error) { - return ((*resolve.AddRewriter)(mrw)).FromMutationResult(mutation, assigned, result) + return ((*resolve.AddRewriter)(mrw)).FromMutationResult(ctx, mutation, assigned, result) } // removeDuplicateRuleRef removes duplicate rules based on predicate value. diff --git a/graphql/admin/admin.go b/graphql/admin/admin.go index 28bbb5a217b..1433c6d82dd 100644 --- a/graphql/admin/admin.go +++ b/graphql/admin/admin.go @@ -18,6 +18,7 @@ package admin import ( "context" + "encoding/json" "fmt" "sync" "sync/atomic" @@ -31,6 +32,7 @@ import ( badgerpb "github.com/dgraph-io/badger/v2/pb" "github.com/dgraph-io/badger/v2/y" + "github.com/dgraph-io/dgraph/graphql/dgraph" "github.com/dgraph-io/dgraph/graphql/resolve" "github.com/dgraph-io/dgraph/graphql/schema" "github.com/dgraph-io/dgraph/graphql/web" @@ -310,6 +312,7 @@ func NewServers(withIntrospection bool, globalEpoch *uint64, closer *y.Closer) ( Arw: resolve.NewAddRewriter, Urw: resolve.NewUpdateRewriter, Drw: resolve.NewDeleteRewriter(), + Ex: resolve.NewDgraphExecutor(), } adminResolvers := newAdminResolver(mainServer, fns, withIntrospection, globalEpoch, closer) adminServer := web.NewServer(globalEpoch, adminResolvers) @@ -480,18 +483,23 @@ func upsertEmptyGQLSchema() (*gqlSchema, error) { }, } - assigned, result, _, err := resolve.AdminMutationExecutor().Mutate(context.Background(), qry, - mutations) + resp, err := resolve.NewAdminExecutor().Execute(context.Background(), + &dgoapi.Request{Query: dgraph.AsString(qry), Mutations: mutations, CommitNow: true}) if err != nil { return nil, err } // the Alpha which created the gql schema node will get the uid here - uid, ok := assigned[varName] + uid, ok := resp.GetUids()[varName] if ok { return &gqlSchema{ID: uid}, nil } + result := make(map[string]interface{}) + if err := json.Unmarshal(resp.GetJson(), &result); err != nil { + return nil, schema.GQLWrapf(err, "Couldn't unmarshal response from Dgraph mutation") + } + // the Alphas which didn't create the gql schema node, will get the uid here. gqlSchemaNode := result[varName].([]interface{})[0].(map[string]interface{}) return &gqlSchema{ @@ -567,22 +575,17 @@ func (as *adminServer) addConnectedAdminResolvers() { qryRw := resolve.NewQueryRewriter() updRw := resolve.NewUpdateRewriter() - qryExec := resolve.DgraphAsQueryExecutor() - mutExec := resolve.DgraphAsMutationExecutor() - - as.fns.Qe = qryExec - as.fns.Me = mutExec + dgEx := resolve.NewDgraphExecutor() as.rf.WithMutationResolver("updateGQLSchema", func(m schema.Mutation) resolve.MutationResolver { updResolver := &updateSchemaResolver{ admin: as, baseMutationRewriter: updRw, - baseMutationExecutor: mutExec, + baseMutationExecutor: dgEx, } - return resolve.NewMutationResolver( - updResolver, + return resolve.NewDgraphResolver( updResolver, updResolver, resolve.StdMutationCompletion(m.Name())) @@ -602,21 +605,21 @@ func (as *adminServer) addConnectedAdminResolvers() { func(q schema.Query) resolve.QueryResolver { return resolve.NewQueryResolver( qryRw, - qryExec, + dgEx, resolve.StdQueryCompletion()) }). WithQueryResolver("queryUser", func(q schema.Query) resolve.QueryResolver { return resolve.NewQueryResolver( qryRw, - qryExec, + dgEx, resolve.StdQueryCompletion()) }). WithQueryResolver("getGroup", func(q schema.Query) resolve.QueryResolver { return resolve.NewQueryResolver( qryRw, - qryExec, + dgEx, resolve.StdQueryCompletion()) }). WithQueryResolver("getCurrentUser", @@ -627,62 +630,56 @@ func (as *adminServer) addConnectedAdminResolvers() { return resolve.NewQueryResolver( cuResolver, - qryExec, + dgEx, resolve.StdQueryCompletion()) }). WithQueryResolver("getUser", func(q schema.Query) resolve.QueryResolver { return resolve.NewQueryResolver( qryRw, - qryExec, + dgEx, resolve.StdQueryCompletion()) }). WithMutationResolver("addUser", func(m schema.Mutation) resolve.MutationResolver { - return resolve.NewMutationResolver( + return resolve.NewDgraphResolver( resolve.NewAddRewriter(), - resolve.DgraphAsQueryExecutor(), - resolve.DgraphAsMutationExecutor(), + dgEx, resolve.StdMutationCompletion(m.Name())) }). WithMutationResolver("addGroup", func(m schema.Mutation) resolve.MutationResolver { - return resolve.NewMutationResolver( + return resolve.NewDgraphResolver( NewAddGroupRewriter(), - resolve.DgraphAsQueryExecutor(), - resolve.DgraphAsMutationExecutor(), + dgEx, resolve.StdMutationCompletion(m.Name())) }). WithMutationResolver("updateUser", func(m schema.Mutation) resolve.MutationResolver { - return resolve.NewMutationResolver( + return resolve.NewDgraphResolver( resolve.NewUpdateRewriter(), - resolve.DgraphAsQueryExecutor(), - resolve.DgraphAsMutationExecutor(), + dgEx, resolve.StdMutationCompletion(m.Name())) }). WithMutationResolver("updateGroup", func(m schema.Mutation) resolve.MutationResolver { - return resolve.NewMutationResolver( + return resolve.NewDgraphResolver( NewUpdateGroupRewriter(), - resolve.DgraphAsQueryExecutor(), - resolve.DgraphAsMutationExecutor(), + dgEx, resolve.StdMutationCompletion(m.Name())) }). WithMutationResolver("deleteUser", func(m schema.Mutation) resolve.MutationResolver { - return resolve.NewMutationResolver( + return resolve.NewDgraphResolver( resolve.NewDeleteRewriter(), - resolve.NoOpQueryExecution(), - resolve.DgraphAsMutationExecutor(), + dgEx, resolve.StdDeleteCompletion(m.Name())) }). WithMutationResolver("deleteGroup", func(m schema.Mutation) resolve.MutationResolver { - return resolve.NewMutationResolver( + return resolve.NewDgraphResolver( resolve.NewDeleteRewriter(), - resolve.NoOpQueryExecution(), - resolve.DgraphAsMutationExecutor(), + dgEx, resolve.StdDeleteCompletion(m.Name())) }) } diff --git a/graphql/admin/schema.go b/graphql/admin/schema.go index 0210a4eb1b4..7dc2165bea1 100644 --- a/graphql/admin/schema.go +++ b/graphql/admin/schema.go @@ -44,7 +44,7 @@ type updateSchemaResolver struct { // The underlying executor and rewriter that persist the schema into Dgraph as // GraphQL metadata baseMutationRewriter resolve.MutationRewriter - baseMutationExecutor resolve.MutationExecutor + baseMutationExecutor resolve.DgraphExecutor } type getSchemaResolver struct { @@ -58,18 +58,19 @@ type updateGQLSchemaInput struct { } func (asr *updateSchemaResolver) Rewrite( - m schema.Mutation) (*gql.GraphQuery, []*dgoapi.Mutation, error) { + ctx context.Context, + m schema.Mutation) (*resolve.UpsertMutation, error) { glog.Info("Got updateGQLSchema request") input, err := getSchemaInput(m) if err != nil { - return nil, nil, err + return nil, err } schHandler, err := schema.NewHandler(input.Set.Schema) if err != nil { - return nil, nil, err + return nil, err } asr.newDgraphSchema = schHandler.DGSchema() @@ -81,10 +82,11 @@ func (asr *updateSchemaResolver) Rewrite( "filter": map[string]interface{}{"ids": []interface{}{asr.admin.schema.ID}}, "set": map[string]interface{}{"schema": input.Set.Schema}, }) - return asr.baseMutationRewriter.Rewrite(m) + return asr.baseMutationRewriter.Rewrite(ctx, m) } func (asr *updateSchemaResolver) FromMutationResult( + ctx context.Context, mutation schema.Mutation, assigned map[string]string, result map[string]interface{}) (*gql.GraphQuery, error) { @@ -93,28 +95,29 @@ func (asr *updateSchemaResolver) FromMutationResult( return nil, nil } -func (asr *updateSchemaResolver) Mutate( +func (asr *updateSchemaResolver) Execute( ctx context.Context, - query *gql.GraphQuery, - mutations []*dgoapi.Mutation) (map[string]string, map[string]interface{}, *schema.Extensions, - error) { - assigned, result, ext, err := asr.baseMutationExecutor.Mutate(ctx, query, mutations) + req *dgoapi.Request) (*dgoapi.Response, error) { + + if req == nil || (req.Query == "" && len(req.Mutations) == 0) { + // For schema updates, Execute will get called twice. Once for the + // mutation and once for the following query. This is the query case. + b, err := doQuery(asr.admin.schema, asr.mutation.QueryField()) + return &dgoapi.Response{Json: b}, err + } + + resp, err := asr.baseMutationExecutor.Execute(ctx, req) if err != nil { - return nil, nil, ext, err + return nil, err } _, err = (&edgraph.Server{}).Alter(ctx, &dgoapi.Operation{Schema: asr.newDgraphSchema}) if err != nil { - return nil, nil, ext, schema.GQLWrapf(err, + return nil, schema.GQLWrapf(err, "succeeded in saving GraphQL schema but failed to alter Dgraph schema ") } - return assigned, result, ext, nil -} - -func (asr *updateSchemaResolver) Query(ctx context.Context, query *gql.GraphQuery) ([]byte, - *schema.Extensions, error) { - return doQuery(asr.admin.schema, asr.mutation.QueryField()) + return resp, nil } func (gsr *getSchemaResolver) Rewrite(ctx context.Context, @@ -123,12 +126,15 @@ func (gsr *getSchemaResolver) Rewrite(ctx context.Context, return nil, nil } -func (gsr *getSchemaResolver) Query(ctx context.Context, query *gql.GraphQuery) ([]byte, - *schema.Extensions, error) { - return doQuery(gsr.admin.schema, gsr.gqlQuery) +func (gsr *getSchemaResolver) Execute( + ctx context.Context, + req *dgoapi.Request) (*dgoapi.Response, error) { + + b, err := doQuery(gsr.admin.schema, gsr.gqlQuery) + return &dgoapi.Response{Json: b}, err } -func doQuery(gql *gqlSchema, field schema.Field) ([]byte, *schema.Extensions, error) { +func doQuery(gql *gqlSchema, field schema.Field) ([]byte, error) { var buf bytes.Buffer x.Check2(buf.WriteString(`{ "`)) @@ -158,7 +164,7 @@ func doQuery(gql *gqlSchema, field schema.Field) ([]byte, *schema.Extensions, er } x.Check2(buf.WriteString("}]}")) - return buf.Bytes(), nil, nil + return buf.Bytes(), nil } func getSchemaInput(m schema.Mutation) (*updateGQLSchemaInput, error) { diff --git a/graphql/admin/update_group.go b/graphql/admin/update_group.go index 91eb4f485f9..9317fbae76c 100644 --- a/graphql/admin/update_group.go +++ b/graphql/admin/update_group.go @@ -1,6 +1,7 @@ package admin import ( + "context" "fmt" dgoapi "github.com/dgraph-io/dgo/v200/protos/api" @@ -21,14 +22,16 @@ func NewUpdateGroupRewriter() resolve.MutationRewriter { // otherwise, it is created. It also ensures that only the last rule out of all // duplicate rules in input is preserved. A rule is duplicate if it has same predicate // name as another rule. -func (urw *updateGroupRewriter) Rewrite(m schema.Mutation) (*gql.GraphQuery, - []*dgoapi.Mutation, error) { +func (urw *updateGroupRewriter) Rewrite( + ctx context.Context, + m schema.Mutation) (*resolve.UpsertMutation, error) { + inp := m.ArgValue(schema.InputArgName).(map[string]interface{}) setArg := inp["set"] delArg := inp["remove"] if setArg == nil && delArg == nil { - return nil, nil, nil + return nil, nil } upsertQuery := resolve.RewriteUpsertQueryFromMutation(m) @@ -120,21 +123,23 @@ func (urw *updateGroupRewriter) Rewrite(m schema.Mutation) (*gql.GraphQuery, // if there is no mutation being performed as a result of some specific input, // then we don't need to do the upsertQuery for group if len(mutSet) == 0 && len(mutDel) == 0 { - return nil, nil, nil + return nil, nil } - return &gql.GraphQuery{Children: []*gql.GraphQuery{upsertQuery}}, - append(mutSet, mutDel...), - schema.GQLWrapf(schema.AppendGQLErrs(errSet, errDel), "failed to rewrite mutation payload") + return &resolve.UpsertMutation{ + Query: &gql.GraphQuery{Children: []*gql.GraphQuery{upsertQuery}}, + Mutations: append(mutSet, mutDel...), + }, schema.GQLWrapf(schema.AppendGQLErrs(errSet, errDel), "failed to rewrite mutation payload") } // FromMutationResult rewrites the query part of a GraphQL update mutation into a Dgraph query. func (urw *updateGroupRewriter) FromMutationResult( + ctx context.Context, mutation schema.Mutation, assigned map[string]string, result map[string]interface{}) (*gql.GraphQuery, error) { - return ((*resolve.UpdateRewriter)(urw)).FromMutationResult(mutation, assigned, result) + return ((*resolve.UpdateRewriter)(urw)).FromMutationResult(ctx, mutation, assigned, result) } // addAclRuleQuery adds a *gql.GraphQuery to upsertQuery.Children to query a rule inside a group diff --git a/graphql/dgraph/execute.go b/graphql/dgraph/execute.go index ec50392cb59..b252763b3e2 100644 --- a/graphql/dgraph/execute.go +++ b/graphql/dgraph/execute.go @@ -18,7 +18,6 @@ package dgraph import ( "context" - "encoding/json" "strings" "github.com/golang/glog" @@ -26,83 +25,35 @@ import ( dgoapi "github.com/dgraph-io/dgo/v200/protos/api" "github.com/dgraph-io/dgraph/edgraph" - "github.com/dgraph-io/dgraph/gql" "github.com/dgraph-io/dgraph/graphql/schema" "github.com/dgraph-io/dgraph/x" ) -const touchedUidsKey = "_total" +type DgraphEx struct{} -// Query is the underlying dgraph implementation of QueryExecutor. -func Query(ctx context.Context, query *gql.GraphQuery) ([]byte, *schema.Extensions, error) { - span := trace.FromContext(ctx) - stop := x.SpanTimer(span, "dgraph.Query") - defer stop() - - queryStr := AsString(query) - - if glog.V(3) { - glog.Infof("Executing Dgraph query: \n%s\n", queryStr) - } - - req := &dgoapi.Request{ - Query: queryStr, - ReadOnly: true, - } - - ctx = context.WithValue(ctx, edgraph.IsGraphql, true) - resp, err := (&edgraph.Server{}).Query(ctx, req) - ext := &schema.Extensions{TouchedUids: resp.GetMetrics().GetNumUids()[touchedUidsKey]} - - return resp.GetJson(), ext, schema.GQLWrapf(err, "Dgraph query failed") -} - -// Mutate is the underlying dgraph implementation of MutationExecutor. -func Mutate( - ctx context.Context, - query *gql.GraphQuery, - mutations []*dgoapi.Mutation) (map[string]string, map[string]interface{}, *schema.Extensions, - error) { +// Execute is the underlying dgraph implementation of Dgraph execution. +func (dg *DgraphEx) Execute(ctx context.Context, req *dgoapi.Request) (*dgoapi.Response, error) { span := trace.FromContext(ctx) - stop := x.SpanTimer(span, "dgraph.Mutate") + stop := x.SpanTimer(span, "dgraph.Execute") defer stop() - if query == nil && len(mutations) == 0 { - return nil, nil, nil, nil + if req == nil || (req.Query == "" && len(req.Mutations) == 0) { + return nil, nil } - queryStr := AsString(query) - if glog.V(3) { - muts := make([]string, len(mutations)) - for i, m := range mutations { + muts := make([]string, len(req.Mutations)) + for i, m := range req.Mutations { muts[i] = m.String() } - glog.Infof("Executing Dgraph mutation; with\nQuery: \n%s\nMutations:%s", - queryStr, strings.Join(muts, "\n")) - } - - req := &dgoapi.Request{ - Query: queryStr, - CommitNow: true, - Mutations: mutations, + glog.Infof("Executing Dgraph request; with\nQuery: \n%s\nMutations:%s", + req.Query, strings.Join(muts, "\n")) } ctx = context.WithValue(ctx, edgraph.IsGraphql, true) resp, err := (&edgraph.Server{}).Query(ctx, req) - if err != nil { - return nil, nil, nil, schema.GQLWrapf(err, "Dgraph mutation failed") - } - ext := &schema.Extensions{TouchedUids: resp.GetMetrics().GetNumUids()[touchedUidsKey]} - result := make(map[string]interface{}) - if query != nil && len(resp.GetJson()) != 0 { - if err := json.Unmarshal(resp.GetJson(), &result); err != nil { - return nil, nil, ext, - schema.GQLWrapf(err, "Couldn't unmarshal response from Dgraph mutation") - } - } - return resp.GetUids(), result, ext, schema.GQLWrapf(err, "Dgraph mutation failed") + return resp, schema.GQLWrapf(err, "Dgraph execution failed") } diff --git a/graphql/e2e/common/error.go b/graphql/e2e/common/error.go index 8dd84b9761b..62f843941ea 100644 --- a/graphql/e2e/common/error.go +++ b/graphql/e2e/common/error.go @@ -29,7 +29,6 @@ import ( "github.com/dgraph-io/dgo/v200" "github.com/dgraph-io/dgo/v200/protos/api" dgoapi "github.com/dgraph-io/dgo/v200/protos/api" - "github.com/dgraph-io/dgraph/gql" "github.com/dgraph-io/dgraph/graphql/resolve" "github.com/dgraph-io/dgraph/graphql/schema" "github.com/dgraph-io/dgraph/graphql/test" @@ -236,8 +235,7 @@ func panicCatcher(t *testing.T) { Arw: resolve.NewAddRewriter, Urw: resolve.NewUpdateRewriter, Drw: resolve.NewDeleteRewriter(), - Qe: &panicClient{}, - Me: &panicClient{}} + Ex: &panicClient{}} resolverFactory := resolve.NewResolverFactory(nil, nil). WithConventionResolvers(gqlSchema, fns) @@ -265,19 +263,9 @@ func panicCatcher(t *testing.T) { type panicClient struct{} -func (dg *panicClient) Query(ctx context.Context, query *gql.GraphQuery) ([]byte, - *schema.Extensions, error) { +func (dg *panicClient) Execute(ctx context.Context, req *dgoapi.Request) (*dgoapi.Response, error) { x.Panic(errors.New(panicMsg)) - return nil, nil, nil -} - -func (dg *panicClient) Mutate( - ctx context.Context, - query *gql.GraphQuery, - mutations []*dgoapi.Mutation) (map[string]string, map[string]interface{}, - *schema.Extensions, error) { - x.Panic(errors.New(panicMsg)) - return nil, nil, nil, nil + return nil, nil } // clientInfoLogin check whether the client info(IP address) is propagated in the request. diff --git a/graphql/resolve/mutation.go b/graphql/resolve/mutation.go index 15e60da70fd..b35979634ac 100644 --- a/graphql/resolve/mutation.go +++ b/graphql/resolve/mutation.go @@ -18,14 +18,18 @@ package resolve import ( "context" + "encoding/json" dgoapi "github.com/dgraph-io/dgo/v200/protos/api" "github.com/dgraph-io/dgraph/gql" + "github.com/dgraph-io/dgraph/graphql/dgraph" "github.com/dgraph-io/dgraph/graphql/schema" "github.com/dgraph-io/dgraph/x" otrace "go.opencensus.io/trace" ) +const touchedUidsKey = "_total" + // Mutations come in like this with variables: // // mutation themutation($post: PostInput!) { @@ -80,100 +84,90 @@ type MutationRewriter interface { // Rewrite rewrites GraphQL mutation m into a Dgraph mutation - that could // be as simple as a single DelNquads, or could be a Dgraph upsert mutation // with a query and multiple mutations guarded by conditions. - Rewrite(m schema.Mutation) (*gql.GraphQuery, []*dgoapi.Mutation, error) + Rewrite(ctx context.Context, m schema.Mutation) (*UpsertMutation, error) // FromMutationResult takes a GraphQL mutation and the results of a Dgraph // mutation and constructs a Dgraph query. It's used to find the return // value from a GraphQL mutation - i.e. we've run the mutation indicated by m // now we need to query Dgraph to satisfy all the result fields in m. FromMutationResult( + ctx context.Context, m schema.Mutation, assigned map[string]string, result map[string]interface{}) (*gql.GraphQuery, error) } -// A MutationExecutor can execute a mutation and returns the assigned map, the -// mutated map and any errors. -type MutationExecutor interface { - // Mutate performs the actual mutation and returns a map of newly assigned nodes, - // a map of variable->[]uid from upsert mutations, extensions and any errors. If an error - // occurs, that indicates that the mutation failed in some way significant enough +// A DgraphExecutor can execute a mutation and returns the request response and any errors. +type DgraphExecutor interface { + // Execute performs the actual mutation and returns a Dgraph response. If an error + // occurs, that indicates that the execution failed in some way significant enough // way as to not continue processing this mutation or others in the same request. - Mutate( - ctx context.Context, - query *gql.GraphQuery, - mutations []*dgoapi.Mutation) (map[string]string, map[string]interface{}, - *schema.Extensions, error) + Execute(ctx context.Context, req *dgoapi.Request) (*dgoapi.Response, error) } -// MutationResolverFunc is an adapter that allows to build a MutationResolver from -// a function. Based on the http.HandlerFunc pattern. -type MutationResolverFunc func(ctx context.Context, mutation schema.Mutation) (*Resolved, bool) +// An UpsertMutation is the query and mutations needed for a Dgraph upsert. +// The node types is a blank node name -> Type mapping of nodes that could +// be created by the upsert. +type UpsertMutation struct { + Query *gql.GraphQuery + Mutations []*dgoapi.Mutation + NodeTypes map[string]schema.Type +} -// MutationExecutionFunc is an adapter that allows us to compose mutation execution and build a -// MutationExecuter from a function. Based on the http.HandlerFunc pattern. -type MutationExecutionFunc func( - ctx context.Context, - query *gql.GraphQuery, - mutations []*dgoapi.Mutation) (map[string]string, map[string][]string, *schema.Extensions, - error) +// DgraphExecutorFunc is an adapter that allows us to compose dgraph execution and +// build a QueryExecuter from a function. Based on the http.HandlerFunc pattern. +type DgraphExecutorFunc func(ctx context.Context, req *dgoapi.Request) (*dgoapi.Response, error) -// Resolve calls mr(ctx, mutation) -func (mr MutationResolverFunc) Resolve( +// Execute calls qe(ctx, query) +func (ex DgraphExecutorFunc) Execute( ctx context.Context, - mutation schema.Mutation) (*Resolved, bool) { + req *dgoapi.Request) (*dgoapi.Response, error) { - return mr(ctx, mutation) + return ex(ctx, req) } -// Mutate calls me(ctx, query, mutations) -func (me MutationExecutionFunc) Mutate( - ctx context.Context, - query *gql.GraphQuery, - mutations []*dgoapi.Mutation) (map[string]string, map[string][]string, *schema.Extensions, - error) { - return me(ctx, query, mutations) +// MutationResolverFunc is an adapter that allows to build a MutationResolver from +// a function. Based on the http.HandlerFunc pattern. +type MutationResolverFunc func(ctx context.Context, m schema.Mutation) (*Resolved, bool) + +// Resolve calls mr(ctx, mutation) +func (mr MutationResolverFunc) Resolve(ctx context.Context, m schema.Mutation) (*Resolved, bool) { + return mr(ctx, m) } -// NewMutationResolver creates a new mutation resolver. The resolver runs the pipeline: +// NewDgraphResolver creates a new mutation resolver. The resolver runs the pipeline: // 1) rewrite the mutation using mr (return error if failed) // 2) execute the mutation with me (return error if failed) // 3) write a query for the mutation with mr (return error if failed) // 4) execute the query with qe (return error if failed) // 5) process the result with rc -func NewMutationResolver( +func NewDgraphResolver( mr MutationRewriter, - qe QueryExecutor, - me MutationExecutor, + ex DgraphExecutor, rc ResultCompleter) MutationResolver { - return &mutationResolver{ + return &dgraphResolver{ mutationRewriter: mr, - queryExecutor: qe, - mutationExecutor: me, + executor: ex, resultCompleter: rc, } } // mutationResolver can resolve a single GraphQL mutation field -type mutationResolver struct { +type dgraphResolver struct { mutationRewriter MutationRewriter - queryExecutor QueryExecutor - mutationExecutor MutationExecutor + executor DgraphExecutor resultCompleter ResultCompleter } -func (mr *mutationResolver) Resolve( - ctx context.Context, mutation schema.Mutation) (*Resolved, bool) { - +func (mr *dgraphResolver) Resolve(ctx context.Context, m schema.Mutation) (*Resolved, bool) { span := otrace.FromContext(ctx) stop := x.SpanTimer(span, "resolveMutation") defer stop() if span != nil { - span.Annotatef(nil, "mutation alias: [%s] type: [%s]", mutation.Alias(), - mutation.MutationType()) + span.Annotatef(nil, "mutation alias: [%s] type: [%s]", m.Alias(), m.MutationType()) } - resolved, success := mr.rewriteAndExecute(ctx, mutation) + resolved, success := mr.rewriteAndExecute(ctx, m) mr.resultCompleter.Complete(ctx, resolved) return resolved, success } @@ -188,12 +182,10 @@ func getNumUids(m schema.Mutation, a map[string]string, r map[string]interface{} } } -func (mr *mutationResolver) rewriteAndExecute( +func (mr *dgraphResolver) rewriteAndExecute( ctx context.Context, mutation schema.Mutation) (*Resolved, bool) { - query, mutations, err := mr.mutationRewriter.Rewrite(mutation) - emptyResult := func(err error) *Resolved { return &Resolved{ Data: map[string]interface{}{mutation.ResponseName(): nil}, @@ -202,12 +194,19 @@ func (mr *mutationResolver) rewriteAndExecute( } } + upsert, err := mr.mutationRewriter.Rewrite(ctx, mutation) if err != nil { return emptyResult(schema.GQLWrapf(err, "couldn't rewrite mutation %s", mutation.Name())), resolverFailed } - assigned, result, extM, err := mr.mutationExecutor.Mutate(ctx, query, mutations) + req := &dgoapi.Request{ + Query: dgraph.AsString(upsert.Query), + CommitNow: true, + Mutations: upsert.Mutations, + } + + mutResp, err := mr.executor.Execute(ctx, req) if err != nil { gqlErr := schema.GQLWrapLocationf( err, mutation.Location(), "mutation %s failed", mutation.Name()) @@ -215,33 +214,47 @@ func (mr *mutationResolver) rewriteAndExecute( } - dgQuery, err := mr.mutationRewriter.FromMutationResult(mutation, assigned, result) - errs := schema.GQLWrapf(err, "couldn't rewrite query for mutation %s", mutation.Name()) + extM := &schema.Extensions{TouchedUids: mutResp.GetMetrics().GetNumUids()[touchedUidsKey]} + result := make(map[string]interface{}) + if req.Query != "" && len(mutResp.GetJson()) != 0 { + if err := json.Unmarshal(mutResp.GetJson(), &result); err != nil { + return emptyResult( + schema.GQLWrapf(err, "Couldn't unmarshal response from Dgraph mutation")), + resolverFailed + } + } + var errs error + dgQuery, err := mr.mutationRewriter.FromMutationResult(ctx, mutation, mutResp.GetUids(), result) + errs = schema.AppendGQLErrs(errs, schema.GQLWrapf(err, + "couldn't rewrite query for mutation %s", mutation.Name())) if dgQuery == nil && err != nil { return emptyResult(errs), resolverFailed } - resp, extQ, err := mr.queryExecutor.Query(ctx, dgQuery) + qryResp, err := mr.executor.Execute(ctx, + &dgoapi.Request{Query: dgraph.AsString(dgQuery), ReadOnly: true}) errs = schema.AppendGQLErrs(errs, schema.GQLWrapf(err, "couldn't rewrite query for mutation %s", mutation.Name())) + extQ := &schema.Extensions{TouchedUids: qryResp.GetMetrics().GetNumUids()[touchedUidsKey]} + numUidsField := mutation.NumUidsField() numUidsFieldRespName := schema.NumUid numUids := 0 if numUidsField != nil { numUidsFieldRespName = numUidsField.ResponseName() - numUids = getNumUids(mutation, assigned, result) + numUids = getNumUids(mutation, mutResp.Uids, result) } - // merge the extensions we got from .Mutate() and .Query() into extM + // merge the extensions we got from Mutate and Query into extM if extM == nil { extM = extQ } else { extM.Merge(extQ) } - resolved := completeDgraphResult(ctx, mutation.QueryField(), resp, errs) + resolved := completeDgraphResult(ctx, mutation.QueryField(), qryResp.GetJson(), errs) if resolved.Data == nil && resolved.Err != nil { return &Resolved{ Data: map[string]interface{}{ @@ -268,6 +281,7 @@ func (mr *mutationResolver) rewriteAndExecute( return resolved, resolverSucceeded } +// deleteCompletion returns `{ "msg": "Deleted" }` func deleteCompletion() CompletionFunc { return CompletionFunc(func(ctx context.Context, resolved *Resolved) { if fld, ok := resolved.Data.(map[string]interface{}); ok { diff --git a/graphql/resolve/mutation_rewriter.go b/graphql/resolve/mutation_rewriter.go index 8298b90542c..4ddac055382 100644 --- a/graphql/resolve/mutation_rewriter.go +++ b/graphql/resolve/mutation_rewriter.go @@ -17,6 +17,7 @@ package resolve import ( + "context" "encoding/json" "fmt" "reflect" @@ -224,8 +225,7 @@ func newXidMetadata() *xidMetadata { // } ], // "Author.friends":[ {"uid":"0x123"} ], // } -func (mrw *AddRewriter) Rewrite( - m schema.Mutation) (*gql.GraphQuery, []*dgoapi.Mutation, error) { +func (mrw *AddRewriter) Rewrite(ctx context.Context, m schema.Mutation) (*UpsertMutation, error) { mutatedType := m.MutatedType() @@ -250,13 +250,15 @@ func (mrw *AddRewriter) Rewrite( return nil, nil }) - return queryFromFragments(mrw.frags[0]), - mutations, - schema.GQLWrapf(err, "failed to rewrite mutation payload") + upsert := &UpsertMutation{ + Query: queryFromFragments(mrw.frags[0]), + Mutations: mutations, + } + + return upsert, schema.GQLWrapf(err, "failed to rewrite mutation payload") } -func (mrw *AddRewriter) handleMultipleMutations( - m schema.Mutation) (*gql.GraphQuery, []*dgoapi.Mutation, error) { +func (mrw *AddRewriter) handleMultipleMutations(m schema.Mutation) (*UpsertMutation, error) { mutatedType := m.MutatedType() val, _ := m.ArgValue(schema.InputArgName).([]interface{}) @@ -297,11 +299,17 @@ func (mrw *AddRewriter) handleMultipleMutations( queries = nil } - return queries, mutationsAll, errs + upsert := &UpsertMutation{ + Query: queries, + Mutations: mutationsAll, + } + + return upsert, errs } // FromMutationResult rewrites the query part of a GraphQL add mutation into a Dgraph query. func (mrw *AddRewriter) FromMutationResult( + ctx context.Context, mutation schema.Mutation, assigned map[string]string, result map[string]interface{}) (*gql.GraphQuery, error) { @@ -364,7 +372,8 @@ func (mrw *AddRewriter) FromMutationResult( // // See AddRewriter for how the set and remove fragments get created. func (urw *UpdateRewriter) Rewrite( - m schema.Mutation) (*gql.GraphQuery, []*dgoapi.Mutation, error) { + ctx context.Context, + m schema.Mutation) (*UpsertMutation, error) { mutatedType := m.MutatedType() @@ -373,7 +382,7 @@ func (urw *UpdateRewriter) Rewrite( delArg := inp["remove"] if setArg == nil && delArg == nil { - return nil, nil, nil + return nil, nil } upsertQuery := RewriteUpsertQueryFromMutation(m) @@ -429,13 +438,18 @@ func (urw *UpdateRewriter) Rewrite( queries = append(queries, q2.Children...) } - return &gql.GraphQuery{Children: queries}, - append(mutSet, mutDel...), + upsert := &UpsertMutation{ + Query: &gql.GraphQuery{Children: queries}, + Mutations: append(mutSet, mutDel...), + } + + return upsert, schema.GQLWrapf(schema.AppendGQLErrs(errSet, errDel), "failed to rewrite mutation payload") } // FromMutationResult rewrites the query part of a GraphQL update mutation into a Dgraph query. func (urw *UpdateRewriter) FromMutationResult( + ctx context.Context, mutation schema.Mutation, assigned map[string]string, result map[string]interface{}) (*gql.GraphQuery, error) { @@ -551,10 +565,12 @@ func RewriteUpsertQueryFromMutation(m schema.Mutation) *gql.GraphQuery { return dgQuery } -func (drw *deleteRewriter) Rewrite(m schema.Mutation) ( - *gql.GraphQuery, []*dgoapi.Mutation, error) { +func (drw *deleteRewriter) Rewrite( + ctx context.Context, + m schema.Mutation) (*UpsertMutation, error) { + if m.MutationType() != schema.DeleteMutation { - return nil, nil, errors.Errorf( + return nil, errors.Errorf( "(internal error) call to build delete mutation for %s mutation type", m.MutationType()) } @@ -600,14 +616,16 @@ func (drw *deleteRewriter) Rewrite(m schema.Mutation) ( b, err := json.Marshal(deletes) - return qry, - []*dgoapi.Mutation{{ - DeleteJson: b, - }}, - err + upsert := &UpsertMutation{ + Query: qry, + Mutations: []*dgoapi.Mutation{{DeleteJson: b}}, + } + + return upsert, err } func (drw *deleteRewriter) FromMutationResult( + ctx context.Context, mutation schema.Mutation, assigned map[string]string, result map[string]interface{}) (*gql.GraphQuery, error) { diff --git a/graphql/resolve/mutation_test.go b/graphql/resolve/mutation_test.go index f5bbda970ff..626d752dae5 100644 --- a/graphql/resolve/mutation_test.go +++ b/graphql/resolve/mutation_test.go @@ -17,6 +17,7 @@ package resolve import ( + "context" "encoding/json" "io/ioutil" "strings" @@ -130,7 +131,9 @@ func mutationRewriting(t *testing.T, file string, rewriterFactory func() Mutatio rewriterToTest := rewriterFactory() // -- Act -- - q, muts, err := rewriterToTest.Rewrite(mut) + upsert, err := rewriterToTest.Rewrite(context.Background(), mut) + q := upsert.Query + muts := upsert.Mutations // -- Assert -- if tcase.Error != nil || err != nil { @@ -202,12 +205,12 @@ func TestMutationQueryRewriting(t *testing.T) { }) require.NoError(t, err) gqlMutation := test.GetMutation(t, op) - _, _, err = rewriter.Rewrite(gqlMutation) + _, err = rewriter.Rewrite(context.Background(), gqlMutation) require.Nil(t, err) // -- Act -- dgQuery, err := rewriter.FromMutationResult( - gqlMutation, tt.assigned, tt.result) + context.Background(), gqlMutation, tt.assigned, tt.result) // -- Assert -- require.Nil(t, err) diff --git a/graphql/resolve/query.go b/graphql/resolve/query.go index b1c535dd39a..42c77ee5050 100644 --- a/graphql/resolve/query.go +++ b/graphql/resolve/query.go @@ -23,7 +23,9 @@ import ( "github.com/golang/glog" otrace "go.opencensus.io/trace" + dgoapi "github.com/dgraph-io/dgo/v200/protos/api" "github.com/dgraph-io/dgraph/gql" + "github.com/dgraph-io/dgraph/graphql/dgraph" "github.com/dgraph-io/dgraph/graphql/schema" "github.com/dgraph-io/dgraph/x" ) @@ -38,68 +40,27 @@ type QueryRewriter interface { Rewrite(ctx context.Context, q schema.Query) (*gql.GraphQuery, error) } -// A QueryExecutor can execute a gql.GraphQuery and return a result. The result of -// a QueryExecutor doesn't need to be valid GraphQL results. -type QueryExecutor interface { - Query(ctx context.Context, query *gql.GraphQuery) ([]byte, *schema.Extensions, error) -} - // QueryResolverFunc is an adapter that allows to build a QueryResolver from // a function. Based on the http.HandlerFunc pattern. type QueryResolverFunc func(ctx context.Context, query schema.Query) *Resolved -// QueryRewritingFunc is an adapter that allows us to build a QueryRewriter from -// a function. Based on the http.HandlerFunc pattern. -type QueryRewritingFunc func(ctx context.Context, q schema.Query) (*gql.GraphQuery, error) - -// QueryExecutionFunc is an adapter that allows us to compose query execution and -// build a QueryExecuter from a function. Based on the http.HandlerFunc pattern. -type QueryExecutionFunc func(ctx context.Context, query *gql.GraphQuery) ([]byte, - *schema.Extensions, error) - // Resolve calls qr(ctx, query) func (qr QueryResolverFunc) Resolve(ctx context.Context, query schema.Query) *Resolved { return qr(ctx, query) } -// Rewrite calls qr(q) -func (qr QueryRewritingFunc) Rewrite(ctx context.Context, q schema.Query) (*gql.GraphQuery, error) { - return qr(ctx, q) -} - -// Query calls qe(ctx, query) -func (qe QueryExecutionFunc) Query(ctx context.Context, query *gql.GraphQuery) ([]byte, - *schema.Extensions, error) { - return qe(ctx, query) -} - // NewQueryResolver creates a new query resolver. The resolver runs the pipeline: // 1) rewrite the query using qr (return error if failed) // 2) execute the rewritten query with qe (return error if failed) // 3) process the result with rc -func NewQueryResolver(qr QueryRewriter, qe QueryExecutor, rc ResultCompleter) QueryResolver { - return &queryResolver{queryRewriter: qr, queryExecutor: qe, resultCompleter: rc} -} - -// NoOpQueryExecution does nothing and returns nil. -func NoOpQueryExecution() QueryExecutionFunc { - return QueryExecutionFunc(func(ctx context.Context, query *gql.GraphQuery) ([]byte, - *schema.Extensions, error) { - return nil, nil, nil - }) -} - -// NoOpQueryRewrite does nothing and returns a nil rewriting. -func NoOpQueryRewrite() QueryRewritingFunc { - return QueryRewritingFunc(func(ctx context.Context, q schema.Query) (*gql.GraphQuery, error) { - return nil, nil - }) +func NewQueryResolver(qr QueryRewriter, ex DgraphExecutor, rc ResultCompleter) QueryResolver { + return &queryResolver{queryRewriter: qr, executor: ex, resultCompleter: rc} } // a queryResolver can resolve a single GraphQL query field. type queryResolver struct { queryRewriter QueryRewriter - queryExecutor QueryExecutor + executor DgraphExecutor resultCompleter ResultCompleter } @@ -133,38 +94,32 @@ func (qr *queryResolver) rewriteAndExecute(ctx context.Context, query schema.Que query.ResponseName())) } - resp, ext, err := qr.queryExecutor.Query(ctx, dgQuery) + resp, err := qr.executor.Execute(ctx, + &dgoapi.Request{Query: dgraph.AsString(dgQuery), ReadOnly: true}) if err != nil { glog.Infof("Dgraph query execution failed : %s", err) return emptyResult(schema.GQLWrapf(err, "Dgraph query failed")) } - // FIXME: just to get it running for now - this should have it's own .Resolve() - if query.QueryType() == schema.SchemaQuery { - var result map[string]interface{} - var err2 error - if len(resp) > 0 { - err2 = json.Unmarshal(resp, &result) - } - - return &Resolved{ - Data: result, - Field: query, - Err: schema.AppendGQLErrs(err, err2), - Extensions: ext, - } - } - - resolved := completeDgraphResult(ctx, query, resp, err) - resolved.Extensions = ext + resolved := completeDgraphResult(ctx, query, resp.GetJson(), err) + resolved.Extensions = + &schema.Extensions{TouchedUids: resp.GetMetrics().GetNumUids()[touchedUidsKey]} return resolved } -func introspectionExecution(q schema.Query) QueryExecutionFunc { - return QueryExecutionFunc(func(ctx context.Context, query *gql.GraphQuery) ([]byte, - *schema.Extensions, error) { - data, err := schema.Introspect(q) - return data, nil, err - }) +func resolveIntrospection(ctx context.Context, q schema.Query) *Resolved { + data, err := schema.Introspect(q) + + var result map[string]interface{} + var err2 error + if len(data) > 0 { + err2 = json.Unmarshal(data, &result) + } + + return &Resolved{ + Data: result, + Field: q, + Err: schema.AppendGQLErrs(err, err2), + } } diff --git a/graphql/resolve/resolver.go b/graphql/resolve/resolver.go index 016bb3d860a..3580002c558 100644 --- a/graphql/resolve/resolver.go +++ b/graphql/resolve/resolver.go @@ -27,7 +27,6 @@ import ( "github.com/dgraph-io/dgraph/graphql/dgraph" dgoapi "github.com/dgraph-io/dgo/v200/protos/api" - "github.com/dgraph-io/dgraph/gql" "github.com/dgraph-io/dgraph/graphql/api" "github.com/dgraph-io/dgraph/x" "github.com/pkg/errors" @@ -107,13 +106,13 @@ type ResolverFns struct { Arw func() MutationRewriter Urw func() MutationRewriter Drw MutationRewriter - Qe QueryExecutor - Me MutationExecutor + Ex DgraphExecutor } // dgraphExecutor is an implementation of both QueryExecutor and MutationExecutor // that proxies query/mutation resolution through Query method in dgraph server. type dgraphExecutor struct { + dg *dgraph.DgraphEx } // adminExecutor is an implementation of both QueryExecutor and MutationExecutor @@ -121,6 +120,7 @@ type dgraphExecutor struct { // it doesn't require authorization. Currently it's only used for querying // gqlschema during init. type adminExecutor struct { + dg *dgraph.DgraphEx } // A Resolved is the result of resolving a single field - generally a query or mutation. @@ -140,54 +140,29 @@ func (cf CompletionFunc) Complete(ctx context.Context, resolved *Resolved) { cf(ctx, resolved) } -// DgraphAsQueryExecutor builds a QueryExecutor for proxying requests through dgraph. -func DgraphAsQueryExecutor() QueryExecutor { - return &dgraphExecutor{} +// NewDgraphExecutor builds a DgraphExecutor for proxying requests through dgraph. +func NewDgraphExecutor() DgraphExecutor { + return newDgraphExecutor(&dgraph.DgraphEx{}) } -func AdminQueryExecutor() QueryExecutor { - return &adminExecutor{} +func newDgraphExecutor(dg *dgraph.DgraphEx) DgraphExecutor { + return &dgraphExecutor{dg: dg} } -func AdminMutationExecutor() MutationExecutor { - return &adminExecutor{} +// NewAdminExecutor builds a DgraphExecutor for proxying requests through dgraph. +func NewAdminExecutor() DgraphExecutor { + return &adminExecutor{dg: &dgraph.DgraphEx{}} } -// DgraphAsMutationExecutor builds a MutationExecutor. -func DgraphAsMutationExecutor() MutationExecutor { - return &dgraphExecutor{} -} - -func (de *adminExecutor) Query(ctx context.Context, query *gql.GraphQuery) ([]byte, - *schema.Extensions, error) { +func (aex *adminExecutor) Execute(ctx context.Context, req *dgoapi.Request) ( + *dgoapi.Response, error) { ctx = context.WithValue(ctx, edgraph.Authorize, false) - return dgraph.Query(ctx, query) -} - -// Mutates the queries/mutations given and returns a map of new nodes assigned and result of the -// performed queries/mutations -func (de *adminExecutor) Mutate( - ctx context.Context, - query *gql.GraphQuery, - mutations []*dgoapi.Mutation) (map[string]string, map[string]interface{}, - *schema.Extensions, error) { - ctx = context.WithValue(ctx, edgraph.Authorize, false) - return dgraph.Mutate(ctx, query, mutations) -} - -func (de *dgraphExecutor) Query(ctx context.Context, query *gql.GraphQuery) ([]byte, - *schema.Extensions, error) { - return dgraph.Query(ctx, query) + return aex.dg.Execute(ctx, req) } -// Mutates the queries/mutations given and returns a map of new nodes assigned and result of the -// performed queries/mutations -func (de *dgraphExecutor) Mutate( - ctx context.Context, - query *gql.GraphQuery, - mutations []*dgoapi.Mutation) (map[string]string, map[string]interface{}, - *schema.Extensions, error) { - return dgraph.Mutate(ctx, query, mutations) +func (de *dgraphExecutor) Execute(ctx context.Context, req *dgoapi.Request) ( + *dgoapi.Response, error) { + return de.dg.Execute(ctx, req) } func (rf *resolverFactory) WithQueryResolver( @@ -203,18 +178,15 @@ func (rf *resolverFactory) WithMutationResolver( } func (rf *resolverFactory) WithSchemaIntrospection() ResolverFactory { - introspect := func(q schema.Query) QueryResolver { - return &queryResolver{ - queryRewriter: NoOpQueryRewrite(), - queryExecutor: introspectionExecution(q), - resultCompleter: StdQueryCompletion(), - } - } - - rf.WithQueryResolver("__schema", introspect) - rf.WithQueryResolver("__type", introspect) - - return rf + return rf. + WithQueryResolver("__schema", + func(q schema.Query) QueryResolver { + return QueryResolverFunc(resolveIntrospection) + }). + WithQueryResolver("__type", + func(q schema.Query) QueryResolver { + return QueryResolverFunc(resolveIntrospection) + }) } func (rf *resolverFactory) WithConventionResolvers( @@ -224,29 +196,25 @@ func (rf *resolverFactory) WithConventionResolvers( queries = append(queries, s.Queries(schema.PasswordQuery)...) for _, q := range queries { rf.WithQueryResolver(q, func(q schema.Query) QueryResolver { - return NewQueryResolver(fns.Qrw, fns.Qe, - StdQueryCompletion()) + return NewQueryResolver(fns.Qrw, fns.Ex, StdQueryCompletion()) }) } for _, m := range s.Mutations(schema.AddMutation) { rf.WithMutationResolver(m, func(m schema.Mutation) MutationResolver { - return NewMutationResolver( - fns.Arw(), fns.Qe, fns.Me, StdMutationCompletion(m.ResponseName())) + return NewDgraphResolver(fns.Arw(), fns.Ex, StdMutationCompletion(m.ResponseName())) }) } for _, m := range s.Mutations(schema.UpdateMutation) { rf.WithMutationResolver(m, func(m schema.Mutation) MutationResolver { - return NewMutationResolver( - fns.Urw(), fns.Qe, fns.Me, StdMutationCompletion(m.ResponseName())) + return NewDgraphResolver(fns.Urw(), fns.Ex, StdMutationCompletion(m.ResponseName())) }) } for _, m := range s.Mutations(schema.DeleteMutation) { rf.WithMutationResolver(m, func(m schema.Mutation) MutationResolver { - return NewMutationResolver( - fns.Drw, NoOpQueryExecution(), fns.Me, deleteCompletion()) + return NewDgraphResolver(fns.Drw, fns.Ex, deleteCompletion()) }) } @@ -600,7 +568,7 @@ func completeDgraphResult( // // We'll continue and just try the first item to return some data. - glog.Error("Got a list of length %v from Dgraph when expecting a "+ + glog.Errorf("Got a list of length %v from Dgraph when expecting a "+ "one-item list.\n", len(val)) errs = append(errs, diff --git a/graphql/resolve/resolver_error_test.go b/graphql/resolve/resolver_error_test.go index ffb88c06b4b..d128958bc27 100644 --- a/graphql/resolve/resolver_error_test.go +++ b/graphql/resolve/resolver_error_test.go @@ -18,11 +18,11 @@ package resolve import ( "context" + "encoding/json" "io/ioutil" "testing" dgoapi "github.com/dgraph-io/dgo/v200/protos/api" - "github.com/dgraph-io/dgraph/gql" "github.com/dgraph-io/dgraph/graphql/schema" "github.com/dgraph-io/dgraph/graphql/test" "github.com/dgraph-io/dgraph/x" @@ -45,9 +45,9 @@ type executor struct { resp string assigned map[string]string result map[string]interface{} - // these will be returned as extensions by .Query() and .Mutate() respectively - queryExtensions *schema.Extensions - mutateExtensions *schema.Extensions + + queryTouched uint64 + mutationTouched uint64 // start reporting Dgraph fails at this point (0 = never fail, 1 = fail on // first request, 2 = succeed once and then fail on 2nd request, etc.) @@ -82,35 +82,38 @@ type Post { author: Author! }` -func (ex *executor) Query(ctx context.Context, query *gql.GraphQuery) ([]byte, - *schema.Extensions, error) { - ex.failQuery-- - if ex.failQuery == 0 { - return nil, nil, schema.GQLWrapf(errors.New("_bad stuff happend_"), "Dgraph query failed") - } - // give out a new copy everytime - ext := schema.Extensions{} - if ex.queryExtensions != nil { - ext.TouchedUids = ex.queryExtensions.TouchedUids +func (ex *executor) Execute(ctx context.Context, req *dgoapi.Request) (*dgoapi.Response, error) { + if len(req.Mutations) == 0 { + ex.failQuery-- + if ex.failQuery == 0 { + return nil, schema.GQLWrapf(errors.New("_bad stuff happend_"), "Dgraph query failed") + } + + return &dgoapi.Response{ + Json: []byte(ex.resp), + Metrics: &dgoapi.Metrics{ + NumUids: map[string]uint64{touchedUidsKey: ex.queryTouched}}, + }, nil } - return []byte(ex.resp), &ext, nil -} -func (ex *executor) Mutate(ctx context.Context, - query *gql.GraphQuery, - mutations []*dgoapi.Mutation) (map[string]string, map[string]interface{}, - *schema.Extensions, error) { ex.failMutation-- if ex.failMutation == 0 { - return nil, nil, nil, schema.GQLWrapf(errors.New("_bad stuff happend_"), + return nil, schema.GQLWrapf(errors.New("_bad stuff happend_"), "Dgraph mutation failed") } - // give out a new copy everytime - ext := schema.Extensions{} - if ex.mutateExtensions != nil { - ext.TouchedUids = ex.mutateExtensions.TouchedUids + + res, err := json.Marshal(ex.result) + if err != nil { + panic(err) } - return ex.assigned, ex.result, &ext, nil + + return &dgoapi.Response{ + Json: []byte(res), + Uids: ex.assigned, + Metrics: &dgoapi.Metrics{ + NumUids: map[string]uint64{touchedUidsKey: ex.mutationTouched}}, + }, nil + } // Tests in resolver_test.yaml are about what gets into a completed result (addition @@ -462,8 +465,8 @@ func TestQueriesPropagateExtensions(t *testing.T) { resp := resolveWithClient(gqlSchema, query, nil, &executor{ - queryExtensions: &schema.Extensions{TouchedUids: 2}, - mutateExtensions: &schema.Extensions{TouchedUids: 5}, + queryTouched: 2, + mutationTouched: 5, }) expectedExtensions := &schema.Extensions{TouchedUids: 2} @@ -490,8 +493,8 @@ func TestMultipleQueriesPropagateExtensionsCorrectly(t *testing.T) { resp := resolveWithClient(gqlSchema, query, nil, &executor{ - queryExtensions: &schema.Extensions{TouchedUids: 2}, - mutateExtensions: &schema.Extensions{TouchedUids: 5}, + queryTouched: 2, + mutationTouched: 5, }) expectedExtensions := &schema.Extensions{TouchedUids: 6} @@ -513,8 +516,8 @@ func TestMutationsPropagateExtensions(t *testing.T) { resp := resolveWithClient(gqlSchema, mutation, nil, &executor{ - queryExtensions: &schema.Extensions{TouchedUids: 2}, - mutateExtensions: &schema.Extensions{TouchedUids: 5}, + queryTouched: 2, + mutationTouched: 5, }) // as both .Mutate() and .Query() should get called, so we should get their merged result @@ -542,8 +545,8 @@ func TestMultipleMutationsPropagateExtensionsCorrectly(t *testing.T) { resp := resolveWithClient(gqlSchema, mutation, nil, &executor{ - queryExtensions: &schema.Extensions{TouchedUids: 2}, - mutateExtensions: &schema.Extensions{TouchedUids: 5}, + queryTouched: 2, + mutationTouched: 5, }) // as both .Mutate() and .Query() should get called, so we should get their merged result @@ -569,8 +572,7 @@ func resolveWithClient( Qrw: NewQueryRewriter(), Arw: NewAddRewriter, Urw: NewUpdateRewriter, - Qe: ex, - Me: ex, + Ex: ex, })) return resolver.Resolve(context.Background(), &schema.Request{Query: gqlQuery, Variables: vars})