github.com/dgraph-io/dgraph@v1.2.8/graphql/admin/schema.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 admin 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 24 "github.com/golang/glog" 25 26 dgoapi "github.com/dgraph-io/dgo/v2/protos/api" 27 "github.com/dgraph-io/dgraph/edgraph" 28 "github.com/dgraph-io/dgraph/gql" 29 "github.com/dgraph-io/dgraph/graphql/api" 30 "github.com/dgraph-io/dgraph/graphql/resolve" 31 "github.com/dgraph-io/dgraph/graphql/schema" 32 "github.com/dgraph-io/dgraph/x" 33 ) 34 35 // A updateSchemaResolver serves as the mutation rewriter and executor in handling 36 // the updateGQLSchema mutation. 37 type updateSchemaResolver struct { 38 admin *adminServer 39 40 mutation schema.Mutation 41 42 // schema that is generated from the mutation input 43 newGQLSchema schema.Schema 44 newDgraphSchema string 45 newSchema gqlSchema 46 47 // The underlying executor and rewriter that persist the schema into Dgraph as 48 // GraphQL metadata 49 baseAddRewriter resolve.MutationRewriter 50 baseMutationRewriter resolve.MutationRewriter 51 baseMutationExecutor resolve.MutationExecutor 52 } 53 54 type getSchemaResolver struct { 55 admin *adminServer 56 57 gqlQuery schema.Query 58 baseRewriter resolve.QueryRewriter 59 baseExecutor resolve.QueryExecutor 60 } 61 62 type updateGQLSchemaInput struct { 63 Set gqlSchema `json:"set,omitempty"` 64 } 65 66 func (asr *updateSchemaResolver) Rewrite( 67 m schema.Mutation) (*gql.GraphQuery, []*dgoapi.Mutation, error) { 68 69 input, err := getSchemaInput(m) 70 if err != nil { 71 return nil, nil, err 72 } 73 74 asr.newSchema.Schema = input.Set.Schema 75 schHandler, err := schema.NewHandler(asr.newSchema.Schema) 76 if err != nil { 77 return nil, nil, err 78 } 79 80 asr.newSchema.GeneratedSchema = schHandler.GQLSchema() 81 asr.newGQLSchema, err = schema.FromString(asr.newSchema.GeneratedSchema) 82 if err != nil { 83 return nil, nil, err 84 } 85 86 asr.newDgraphSchema = schHandler.DGSchema() 87 88 if asr.admin.schema.ID == "" { 89 // There's never been a GraphQL schema in this Dgraph before so rewrite this into 90 // an add 91 m.SetArgTo(schema.InputArgName, map[string]interface{}{"schema": asr.newSchema.Schema}) 92 return asr.baseAddRewriter.Rewrite(m) 93 } 94 95 // there's already a value, just continue with the GraphQL update 96 m.SetArgTo(schema.InputArgName, 97 map[string]interface{}{ 98 "filter": map[string]interface{}{"ids": []interface{}{asr.admin.schema.ID}}, 99 "set": map[string]interface{}{"schema": asr.newSchema.Schema}, 100 }) 101 return asr.baseMutationRewriter.Rewrite(m) 102 } 103 104 func (asr *updateSchemaResolver) FromMutationResult( 105 mutation schema.Mutation, 106 assigned map[string]string, 107 result map[string]interface{}) (*gql.GraphQuery, error) { 108 109 asr.mutation = mutation 110 return nil, nil 111 } 112 113 func (asr *updateSchemaResolver) Mutate( 114 ctx context.Context, 115 query *gql.GraphQuery, 116 mutations []*dgoapi.Mutation) (map[string]string, map[string]interface{}, error) { 117 118 asr.admin.mux.Lock() 119 defer asr.admin.mux.Unlock() 120 121 assigned, result, err := asr.baseMutationExecutor.Mutate(ctx, query, mutations) 122 if err != nil { 123 return nil, nil, err 124 } 125 126 if asr.admin.schema.ID == "" { 127 // should only be 1 assigned, but we don't know the name 128 for _, v := range assigned { 129 asr.newSchema.ID = v 130 } 131 } else { 132 asr.newSchema.ID = asr.admin.schema.ID 133 } 134 135 glog.Infof("[%s] Altering Dgraph schema.", api.RequestID(ctx)) 136 if glog.V(3) { 137 glog.Infof("[%s] New schema Dgraph:\n\n%s\n", api.RequestID(ctx), asr.newDgraphSchema) 138 } 139 140 _, err = (&edgraph.Server{}).Alter(ctx, &dgoapi.Operation{Schema: asr.newDgraphSchema}) 141 if err != nil { 142 return nil, nil, schema.GQLWrapf(err, 143 "succeeded in saving GraphQL schema but failed to alter Dgraph schema "+ 144 "(you should retry)") 145 } 146 147 asr.admin.resetSchema(asr.newGQLSchema) 148 asr.admin.schema = asr.newSchema 149 150 glog.Infof("[%s] Successfully loaded new GraphQL schema. Serving New GraphQL API.", 151 api.RequestID(ctx)) 152 153 return assigned, result, nil 154 } 155 156 func (asr *updateSchemaResolver) Query(ctx context.Context, query *gql.GraphQuery) ([]byte, error) { 157 return doQuery(asr.admin.schema, asr.mutation.SelectionSet()[0]) 158 } 159 160 func (gsr *getSchemaResolver) Rewrite(gqlQuery schema.Query) (*gql.GraphQuery, error) { 161 gsr.gqlQuery = gqlQuery 162 gqlQuery.Rename("queryGQLSchema") 163 return gsr.baseRewriter.Rewrite(gqlQuery) 164 } 165 166 func (gsr *getSchemaResolver) Query(ctx context.Context, query *gql.GraphQuery) ([]byte, error) { 167 if gsr.admin.schema.ID == "" { 168 return gsr.baseExecutor.Query(ctx, query) 169 } 170 171 return doQuery(gsr.admin.schema, gsr.gqlQuery) 172 } 173 174 func doQuery(gql gqlSchema, field schema.Field) ([]byte, error) { 175 176 var buf bytes.Buffer 177 x.Check2(buf.WriteString(`{ "`)) 178 x.Check2(buf.WriteString(field.ResponseName())) 179 if gql.ID == "" { 180 x.Check2(buf.WriteString(`": null }`)) 181 return buf.Bytes(), nil 182 } 183 184 x.Check2(buf.WriteString(`": [{`)) 185 for i, sel := range field.SelectionSet() { 186 var val []byte 187 var err error 188 switch sel.Name() { 189 case "id": 190 val, err = json.Marshal(gql.ID) 191 case "schema": 192 val, err = json.Marshal(gql.Schema) 193 case "generatedSchema": 194 val, err = json.Marshal(gql.GeneratedSchema) 195 } 196 x.Check2(val, err) 197 198 if i != 0 { 199 x.Check2(buf.WriteString(",")) 200 } 201 x.Check2(buf.WriteString(`"`)) 202 x.Check2(buf.WriteString(sel.ResponseName())) 203 x.Check2(buf.WriteString(`":`)) 204 x.Check2(buf.Write(val)) 205 } 206 x.Check2(buf.WriteString("}]}")) 207 208 return buf.Bytes(), nil 209 } 210 211 func getSchemaInput(m schema.Mutation) (*updateGQLSchemaInput, error) { 212 inputArg := m.ArgValue(schema.InputArgName) 213 inputByts, err := json.Marshal(inputArg) 214 if err != nil { 215 return nil, schema.GQLWrapf(err, "couldn't get input argument") 216 } 217 218 var input updateGQLSchemaInput 219 err = json.Unmarshal(inputByts, &input) 220 return &input, schema.GQLWrapf(err, "couldn't get input argument") 221 }