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  }