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 }