github.com/dgraph-io/dgraph@v1.2.8/graphql/resolve/mutation.go (about)

     1  /*
     2   * Copyright 2019 Dgraph Labs, Inc. and Contributors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package resolve
    18  
    19  import (
    20  	"context"
    21  
    22  	dgoapi "github.com/dgraph-io/dgo/v2/protos/api"
    23  	"github.com/dgraph-io/dgraph/gql"
    24  	"github.com/dgraph-io/dgraph/graphql/schema"
    25  	"github.com/dgraph-io/dgraph/x"
    26  	otrace "go.opencensus.io/trace"
    27  )
    28  
    29  // Mutations come in like this with variables:
    30  //
    31  // mutation themutation($post: PostInput!) {
    32  //   addPost(input: $post) { ... some query ...}
    33  // }
    34  // - with variable payload
    35  // { "post":
    36  //   { "title": "My Post",
    37  //     "author": { authorID: 0x123 },
    38  //     ...
    39  //   }
    40  // }
    41  //
    42  //
    43  // Or, like this with the payload in the mutation arguments
    44  //
    45  // mutation themutation {
    46  //   addPost(input: { title: ... }) { ... some query ...}
    47  // }
    48  //
    49  //
    50  // Either way we build up a Dgraph json mutation to add the object
    51  //
    52  // For now, all mutations are only 1 level deep (cause of how we build the
    53  // input objects) and only create a single node (again cause of inputs)
    54  
    55  // A MutationResolver can resolve a single mutation.
    56  type MutationResolver interface {
    57  	Resolve(ctx context.Context, mutation schema.Mutation) (*Resolved, bool)
    58  }
    59  
    60  // A MutationRewriter can transform a GraphQL mutation into a Dgraph mutation and
    61  // can build a Dgraph gql.GraphQuery to follow a GraphQL mutation.
    62  //
    63  // Mutations come in like:
    64  //
    65  // mutation addAuthor($auth: AuthorInput!) {
    66  //   addAuthor(input: $auth) {
    67  // 	   author {
    68  // 	     id
    69  // 	     name
    70  // 	   }
    71  //   }
    72  // }
    73  //
    74  // Where `addAuthor(input: $auth)` implies a mutation that must get run - written
    75  // to a Dgraph mutation by Rewrite.  The GraphQL following `addAuthor(...)`implies
    76  // a query to run and return the newly created author, so the
    77  // mutation query rewriting is dependent on the context set up by the result of
    78  // the mutation.
    79  type MutationRewriter interface {
    80  	// Rewrite rewrites GraphQL mutation m into a Dgraph mutation - that could
    81  	// be as simple as a single DelNquads, or could be a Dgraph upsert mutation
    82  	// with a query and multiple mutations guarded by conditions.
    83  	Rewrite(m schema.Mutation) (*gql.GraphQuery, []*dgoapi.Mutation, error)
    84  
    85  	// FromMutationResult takes a GraphQL mutation and the results of a Dgraph
    86  	// mutation and constructs a Dgraph query.  It's used to find the return
    87  	// value from a GraphQL mutation - i.e. we've run the mutation indicated by m
    88  	// now we need to query Dgraph to satisfy all the result fields in m.
    89  	FromMutationResult(
    90  		m schema.Mutation,
    91  		assigned map[string]string,
    92  		result map[string]interface{}) (*gql.GraphQuery, error)
    93  }
    94  
    95  // A MutationExecutor can execute a mutation and returns the assigned map, the
    96  // mutated map and any errors.
    97  type MutationExecutor interface {
    98  	// Mutate performs the actual mutation and returns a map of newly assigned nodes,
    99  	// a map of variable->[]uid from upsert mutations and any errors.  If an error
   100  	// occurs, that indicates that the mutation failed in some way significant enough
   101  	// way as to not continue procissing this mutation or others in the same request.
   102  	Mutate(
   103  		ctx context.Context,
   104  		query *gql.GraphQuery,
   105  		mutations []*dgoapi.Mutation) (map[string]string, map[string]interface{}, error)
   106  }
   107  
   108  // MutationResolverFunc is an adapter that allows to build a MutationResolver from
   109  // a function.  Based on the http.HandlerFunc pattern.
   110  type MutationResolverFunc func(ctx context.Context, mutation schema.Mutation) (*Resolved, bool)
   111  
   112  // MutationExecutionFunc is an adapter that allows us to compose mutation execution and build a
   113  // MutationExecuter from a function.  Based on the http.HandlerFunc pattern.
   114  type MutationExecutionFunc func(
   115  	ctx context.Context,
   116  	query *gql.GraphQuery,
   117  	mutations []*dgoapi.Mutation) (map[string]string, map[string][]string, error)
   118  
   119  // Resolve calls mr(ctx, mutation)
   120  func (mr MutationResolverFunc) Resolve(
   121  	ctx context.Context,
   122  	mutation schema.Mutation) (*Resolved, bool) {
   123  
   124  	return mr(ctx, mutation)
   125  }
   126  
   127  // Mutate calls me(ctx, query, mutations)
   128  func (me MutationExecutionFunc) Mutate(
   129  	ctx context.Context,
   130  	query *gql.GraphQuery,
   131  	mutations []*dgoapi.Mutation) (map[string]string, map[string][]string, error) {
   132  	return me(ctx, query, mutations)
   133  }
   134  
   135  // NewMutationResolver creates a new mutation resolver.  The resolver runs the pipeline:
   136  // 1) rewrite the mutation using mr (return error if failed)
   137  // 2) execute the mutation with me (return error if failed)
   138  // 3) write a query for the mutation with mr (return error if failed)
   139  // 4) execute the query with qe (return error if failed)
   140  // 5) process the result with rc
   141  func NewMutationResolver(
   142  	mr MutationRewriter,
   143  	qe QueryExecutor,
   144  	me MutationExecutor,
   145  	rc ResultCompleter) MutationResolver {
   146  	return &mutationResolver{
   147  		mutationRewriter: mr,
   148  		queryExecutor:    qe,
   149  		mutationExecutor: me,
   150  		resultCompleter:  rc,
   151  	}
   152  }
   153  
   154  // mutationResolver can resolve a single GraphQL mutation field
   155  type mutationResolver struct {
   156  	mutationRewriter MutationRewriter
   157  	queryExecutor    QueryExecutor
   158  	mutationExecutor MutationExecutor
   159  	resultCompleter  ResultCompleter
   160  }
   161  
   162  func (mr *mutationResolver) Resolve(
   163  	ctx context.Context, mutation schema.Mutation) (*Resolved, bool) {
   164  
   165  	span := otrace.FromContext(ctx)
   166  	stop := x.SpanTimer(span, "resolveMutation")
   167  	defer stop()
   168  	if span != nil {
   169  		span.Annotatef(nil, "mutation alias: [%s] type: [%s]", mutation.Alias(),
   170  			mutation.MutationType())
   171  	}
   172  
   173  	res, success, err := mr.rewriteAndExecute(ctx, mutation)
   174  
   175  	completed, err := mr.resultCompleter.Complete(ctx, mutation.QueryField(), res, err)
   176  	return &Resolved{
   177  		Data: completed,
   178  		Err:  err,
   179  	}, success
   180  }
   181  
   182  func (mr *mutationResolver) rewriteAndExecute(
   183  	ctx context.Context, mutation schema.Mutation) ([]byte, bool, error) {
   184  
   185  	query, mutations, err := mr.mutationRewriter.Rewrite(mutation)
   186  	if err != nil {
   187  		return nil, resolverFailed,
   188  			schema.GQLWrapf(err, "couldn't rewrite mutation %s", mutation.Name())
   189  	}
   190  
   191  	assigned, result, err := mr.mutationExecutor.Mutate(ctx, query, mutations)
   192  	if err != nil {
   193  		return nil, resolverFailed,
   194  			schema.GQLWrapLocationf(err, mutation.Location(), "mutation %s failed", mutation.Name())
   195  	}
   196  
   197  	var errs error
   198  	dgQuery, err := mr.mutationRewriter.FromMutationResult(mutation, assigned, result)
   199  	errs = schema.AppendGQLErrs(errs, schema.GQLWrapf(err,
   200  		"couldn't rewrite query for mutation %s", mutation.Name()))
   201  
   202  	if dgQuery == nil && err != nil {
   203  		return nil, resolverFailed, errs
   204  	}
   205  
   206  	resp, err := mr.queryExecutor.Query(ctx, dgQuery)
   207  	errs = schema.AppendGQLErrs(errs, schema.GQLWrapf(err,
   208  		"couldn't rewrite query for mutation %s", mutation.Name()))
   209  
   210  	return resp, resolverSucceeded, errs
   211  }
   212  
   213  // deleteCompletion returns `{ "msg": "Deleted" }`
   214  // FIXME: after upsert mutations changes are done, it will return info about
   215  // the result of a deletion.
   216  func deleteCompletion() CompletionFunc {
   217  	return CompletionFunc(func(
   218  		ctx context.Context, field schema.Field, result []byte, err error) ([]byte, error) {
   219  
   220  		return []byte(`{ "msg": "Deleted" }`), err
   221  	})
   222  }