github.com/dgraph-io/dgraph@v1.2.8/graphql/resolve/mutation_rewriter.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  	"encoding/json"
    21  	"fmt"
    22  	"strconv"
    23  	"strings"
    24  
    25  	dgoapi "github.com/dgraph-io/dgo/v2/protos/api"
    26  	"github.com/dgraph-io/dgraph/gql"
    27  	"github.com/dgraph-io/dgraph/graphql/schema"
    28  	"github.com/dgraph-io/dgraph/x"
    29  	"github.com/pkg/errors"
    30  )
    31  
    32  const (
    33  	mutationQueryVar        = "x"
    34  	deleteUIDVarMutation    = `{ "uid": "uid(x)" }`
    35  	updateMutationCondition = `gt(len(x), 0)`
    36  )
    37  
    38  type addRewriter struct {
    39  	frags [][]*mutationFragment
    40  }
    41  type updateRewriter struct {
    42  	setFrags []*mutationFragment
    43  	delFrags []*mutationFragment
    44  }
    45  type deleteRewriter struct{}
    46  
    47  // A mutationFragment is a partially built Dgraph mutation.  Given a GraphQL
    48  // mutation input, we traverse the input data and build a Dgraph mutation.  That
    49  // mutation might require queries (e.g. to check types), conditions (to guard the
    50  // upsert mutation to only run in the right conditions), post mutation checks (
    51  // so we can investigate the mutation result and know what guarded mutations
    52  // actually ran.
    53  //
    54  // In the case of XIDs a mutation might result in two fragments - one for the case
    55  // of add a new object for the XID and another for link to an existing XID, depending
    56  // on what condition evaluates to true in the upsert.
    57  type mutationFragment struct {
    58  	queries    []*gql.GraphQuery
    59  	conditions []string
    60  	fragment   interface{}
    61  	deletes    []interface{} // TODO: functionality for next PR
    62  	check      resultChecker
    63  	err        error
    64  }
    65  
    66  // A mutationBuilder can build a json mutation []byte from a mutationFragment
    67  type mutationBuilder func(frag *mutationFragment) ([]byte, error)
    68  
    69  // A resultChecker checks an upsert (query) result and returns an error if the
    70  // result indicates that the upsert didn't succeed.
    71  type resultChecker func(map[string]interface{}) error
    72  
    73  type counter int
    74  
    75  func (c *counter) next() int {
    76  	*c++
    77  	return int(*c)
    78  }
    79  
    80  // NewAddRewriter returns new MutationRewriter for add & update mutations.
    81  func NewAddRewriter() MutationRewriter {
    82  	return &addRewriter{}
    83  }
    84  
    85  // NewUpdateRewriter returns new MutationRewriter for add & update mutations.
    86  func NewUpdateRewriter() MutationRewriter {
    87  	return &updateRewriter{}
    88  }
    89  
    90  // NewDeleteRewriter returns new MutationRewriter for delete mutations..
    91  func NewDeleteRewriter() MutationRewriter {
    92  	return &deleteRewriter{}
    93  }
    94  
    95  // Rewrite takes a GraphQL schema.Mutation add and builds a Dgraph upsert mutation.
    96  // m must have a single argument called 'input' that carries the mutation data.
    97  //
    98  // That argument could have been passed in the mutation like:
    99  //
   100  // addPost(input: { title: "...", ... })
   101  //
   102  // or be passed in a GraphQL variable like:
   103  //
   104  // addPost(input: $newPost)
   105  //
   106  // Either way, the data needs to have type information added and have some rewriting
   107  // done - for example, rewriting field names from the GraphQL view to what's stored
   108  // in Dgraph, and rewriting ID fields from their names to uid.
   109  //
   110  // For example, a GraphQL add mutation to add an object of type Author,
   111  // with GraphQL input object (where country code is @id) :
   112  //
   113  // {
   114  //   name: "A.N. Author",
   115  //   country: { code: "ind", name: "India" },
   116  //   posts: [ { title: "A Post", text: "Some text" }]
   117  //   friends: [ { id: "0x123" } ]
   118  // }
   119  //
   120  // becomes a guarded upsert with two possible paths - one if "ind" already exists
   121  // and the other if we create "ind" as part of the mutation.
   122  //
   123  // Query:
   124  // query {
   125  //   Author4 as Author4(func: uid(0x123)) @filter(type(Author)) {
   126  //     uid
   127  //   }
   128  //   Country2 as Country2(func: eq(Country.code, "ind")) @filter(type(Country)) {
   129  //     uid
   130  //   }
   131  // }
   132  //
   133  // And two conditional mutations.  Both create a new post and check that the linked
   134  // friend is an Author.  One links to India if it exists, the other creates it
   135  //
   136  // "@if(eq(len(Country2), 0) AND eq(len(Author4), 1))"
   137  // {
   138  //   "uid":"_:Author1"
   139  //   "dgraph.type":["Author"],
   140  //   "Author.name":"A.N. Author",
   141  //   "Author.country":{
   142  //     "uid":"_:Country2",
   143  //     "dgraph.type":["Country"],
   144  //     "Country.code":"ind",
   145  //     "Country.name":"India"
   146  //   },
   147  //   "Author.posts": [ {
   148  //     "uid":"_:Post3"
   149  //     "dgraph.type":["Post"],
   150  //     "Post.text":"Some text",
   151  //     "Post.title":"A Post",
   152  //   } ],
   153  //   "Author.friends":[ {"uid":"0x123"} ],
   154  // }
   155  //
   156  // and @if(eq(len(Country2), 1) AND eq(len(Author4), 1))
   157  // {
   158  //   "uid":"_:Author1",
   159  //   "dgraph.type":["Author"],
   160  //   "Author.name":"A.N. Author",
   161  //   "Author.country": {
   162  //     "uid":"uid(Country2)"
   163  //   },
   164  //   "Author.posts": [ {
   165  //     "uid":"_:Post3"
   166  //     "dgraph.type":["Post"],
   167  //     "Post.text":"Some text",
   168  //     "Post.title":"A Post",
   169  //   } ],
   170  //   "Author.friends":[ {"uid":"0x123"} ],
   171  // }
   172  func (mrw *addRewriter) Rewrite(
   173  	m schema.Mutation) (*gql.GraphQuery, []*dgoapi.Mutation, error) {
   174  
   175  	mutatedType := m.MutatedType()
   176  
   177  	if m.IsArgListType(schema.InputArgName) {
   178  		return mrw.handleMultipleMutations(m)
   179  	}
   180  
   181  	val := m.ArgValue(schema.InputArgName).(map[string]interface{})
   182  	counter := counter(0)
   183  	mrw.frags = [][]*mutationFragment{rewriteObject(mutatedType, nil, "", &counter, val)}
   184  	mutations, err := mutationsFromFragments(
   185  		mrw.frags[0],
   186  		func(frag *mutationFragment) ([]byte, error) {
   187  			return json.Marshal(frag.fragment)
   188  		},
   189  		func(frag *mutationFragment) ([]byte, error) {
   190  			if len(frag.deletes) > 0 {
   191  				return json.Marshal(frag.deletes)
   192  			}
   193  			return nil, nil
   194  		})
   195  
   196  	return queryFromFragments(mrw.frags[0]),
   197  		mutations,
   198  		schema.GQLWrapf(err, "failed to rewrite mutation payload")
   199  }
   200  
   201  func (mrw *addRewriter) handleMultipleMutations(
   202  	m schema.Mutation) (*gql.GraphQuery, []*dgoapi.Mutation, error) {
   203  	mutatedType := m.MutatedType()
   204  	val, _ := m.ArgValue(schema.InputArgName).([]interface{})
   205  
   206  	counter := counter(0)
   207  	var errs error
   208  	var mutationsAll []*dgoapi.Mutation
   209  	queries := &gql.GraphQuery{}
   210  
   211  	for _, i := range val {
   212  		obj := i.(map[string]interface{})
   213  		frag := rewriteObject(mutatedType, nil, "", &counter, obj)
   214  		mrw.frags = append(mrw.frags, frag)
   215  
   216  		mutations, err := mutationsFromFragments(
   217  			frag,
   218  			func(frag *mutationFragment) ([]byte, error) {
   219  				return json.Marshal(frag.fragment)
   220  			},
   221  			func(frag *mutationFragment) ([]byte, error) {
   222  				if len(frag.deletes) > 0 {
   223  					return json.Marshal(frag.deletes)
   224  				}
   225  				return nil, nil
   226  			})
   227  
   228  		errs = schema.AppendGQLErrs(errs, schema.GQLWrapf(err,
   229  			"failed to rewrite mutation payload"))
   230  
   231  		mutationsAll = append(mutationsAll, mutations...)
   232  		qry := queryFromFragments(frag)
   233  		if qry != nil {
   234  			queries.Children = append(queries.Children, qry.Children...)
   235  		}
   236  	}
   237  
   238  	if len(queries.Children) == 0 {
   239  		queries = nil
   240  	}
   241  
   242  	return queries, mutationsAll, errs
   243  }
   244  
   245  // FromMutationResult rewrites the query part of a GraphQL add mutation into a Dgraph query.
   246  func (mrw *addRewriter) FromMutationResult(
   247  	mutation schema.Mutation,
   248  	assigned map[string]string,
   249  	result map[string]interface{}) (*gql.GraphQuery, error) {
   250  
   251  	var errs error
   252  
   253  	uids := make([]uint64, 0)
   254  
   255  	for _, frag := range mrw.frags {
   256  		err := checkResult(frag, result)
   257  		errs = schema.AppendGQLErrs(errs, err)
   258  		if err != nil {
   259  			continue
   260  		}
   261  
   262  		node := strings.TrimPrefix(frag[0].
   263  			fragment.(map[string]interface{})["uid"].(string), "_:")
   264  		val, ok := assigned[node]
   265  		if !ok {
   266  			continue
   267  		}
   268  		uid, err := strconv.ParseUint(val, 0, 64)
   269  		if err != nil {
   270  			errs = schema.AppendGQLErrs(errs, schema.GQLWrapf(err,
   271  				"received %s as an assigned uid from Dgraph,"+
   272  					" but couldn't parse it as uint64",
   273  				assigned[node]))
   274  		}
   275  
   276  		uids = append(uids, uid)
   277  	}
   278  
   279  	if len(assigned) == 0 && errs == nil {
   280  		errs = schema.AsGQLErrors(fmt.Errorf("No new node was created"))
   281  	}
   282  
   283  	return rewriteAsQueryByIds(mutation.QueryField(), uids), errs
   284  }
   285  
   286  // Rewrite rewrites set and remove update patches into GraphQL+- upsert mutations.
   287  // The GraphQL updates look like:
   288  //
   289  // input UpdateAuthorInput {
   290  // 	filter: AuthorFilter!
   291  // 	set: PatchAuthor
   292  // 	remove: PatchAuthor
   293  // }
   294  //
   295  // which gets rewritten in to a Dgraph upsert mutation
   296  // - filter becomes the query
   297  // - set becomes the Dgraph set mutation
   298  // - remove becomes the Dgraph delete mutation
   299  //
   300  // The semantics is the same as the Dgraph mutation semantics.
   301  // - Any values in set become the new values for those predicates (or add to the existing
   302  //   values for lists)
   303  // - Any nulls in set are ignored.
   304  // - Explicit values in remove mean delete this if it is the actual value
   305  // - Nulls in remove become like delete * for the corresponding predicate.
   306  //
   307  // See addRewriter for how the set and remove fragments get created.
   308  func (urw *updateRewriter) Rewrite(
   309  	m schema.Mutation) (*gql.GraphQuery, []*dgoapi.Mutation, error) {
   310  
   311  	mutatedType := m.MutatedType()
   312  
   313  	inp := m.ArgValue(schema.InputArgName).(map[string]interface{})
   314  	setArg := inp["set"]
   315  	delArg := inp["remove"]
   316  
   317  	if setArg == nil && delArg == nil {
   318  		return nil, nil, nil
   319  	}
   320  
   321  	upsertQuery := rewriteUpsertQueryFromMutation(m)
   322  	srcUID := fmt.Sprintf("uid(%s)", mutationQueryVar)
   323  
   324  	var errSet, errDel error
   325  	var mutSet, mutDel []*dgoapi.Mutation
   326  	counter := counter(0)
   327  
   328  	if setArg != nil {
   329  		urw.setFrags =
   330  			rewriteObject(mutatedType, nil, srcUID, &counter, setArg.(map[string]interface{}))
   331  		addUpdateCondition(urw.setFrags)
   332  		mutSet, errSet = mutationsFromFragments(
   333  			urw.setFrags,
   334  			func(frag *mutationFragment) ([]byte, error) {
   335  				return json.Marshal(frag.fragment)
   336  			},
   337  			func(frag *mutationFragment) ([]byte, error) {
   338  				if len(frag.deletes) > 0 {
   339  					return json.Marshal(frag.deletes)
   340  				}
   341  				return nil, nil
   342  			})
   343  	}
   344  
   345  	if delArg != nil {
   346  		urw.delFrags =
   347  			rewriteObject(mutatedType, nil, srcUID, &counter, delArg.(map[string]interface{}))
   348  		addUpdateCondition(urw.delFrags)
   349  		mutDel, errDel = mutationsFromFragments(
   350  			urw.delFrags,
   351  			func(frag *mutationFragment) ([]byte, error) {
   352  				return nil, nil
   353  			},
   354  			func(frag *mutationFragment) ([]byte, error) {
   355  				return json.Marshal(frag.fragment)
   356  			})
   357  	}
   358  
   359  	queries := []*gql.GraphQuery{upsertQuery}
   360  
   361  	q1 := queryFromFragments(urw.setFrags)
   362  	if q1 != nil {
   363  		queries = append(queries, q1.Children...)
   364  	}
   365  
   366  	q2 := queryFromFragments(urw.delFrags)
   367  	if q2 != nil {
   368  		queries = append(queries, q2.Children...)
   369  	}
   370  
   371  	return &gql.GraphQuery{Children: queries},
   372  		append(mutSet, mutDel...),
   373  		schema.GQLWrapf(schema.AppendGQLErrs(errSet, errDel), "failed to rewrite mutation payload")
   374  }
   375  
   376  // FromMutationResult rewrites the query part of a GraphQL update mutation into a Dgraph query.
   377  func (urw *updateRewriter) FromMutationResult(
   378  	mutation schema.Mutation,
   379  	assigned map[string]string,
   380  	result map[string]interface{}) (*gql.GraphQuery, error) {
   381  
   382  	err := checkResult(urw.setFrags, result)
   383  	if err != nil {
   384  		return nil, err
   385  	}
   386  	err = checkResult(urw.delFrags, result)
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  
   391  	mutated := extractMutated(result, mutation.ResponseName())
   392  
   393  	var uids []uint64
   394  	if len(mutated) > 0 {
   395  		// This is the case of a conditional upsert where we should get uids from mutated.
   396  		for _, id := range mutated {
   397  			uid, err := strconv.ParseUint(id, 0, 64)
   398  			if err != nil {
   399  				return nil, schema.GQLWrapf(err,
   400  					"received %s as an updated uid from Dgraph, but couldn't parse it as "+
   401  						"uint64", id)
   402  			}
   403  			uids = append(uids, uid)
   404  		}
   405  	}
   406  
   407  	return rewriteAsQueryByIds(mutation.QueryField(), uids), nil
   408  }
   409  
   410  func extractMutated(result map[string]interface{}, mutatedField string) []string {
   411  	var mutated []string
   412  
   413  	if val, ok := result[mutatedField].([]interface{}); ok {
   414  		for _, v := range val {
   415  			if obj, vok := v.(map[string]interface{}); vok {
   416  				if uid, uok := obj["uid"].(string); uok {
   417  					mutated = append(mutated, uid)
   418  				}
   419  			}
   420  		}
   421  	}
   422  
   423  	return mutated
   424  }
   425  
   426  func addUpdateCondition(frags []*mutationFragment) {
   427  	for _, frag := range frags {
   428  		frag.conditions = append(frag.conditions, updateMutationCondition)
   429  	}
   430  }
   431  
   432  // checkResult checks if any mutationFragment in frags was successful in result.
   433  // If any one of the frags (which correspond to conditional mutations) succeeded,
   434  // then the mutation ran through ok.  Otherwise return an error showing why
   435  // at least one of the mutations failed.
   436  func checkResult(frags []*mutationFragment, result map[string]interface{}) error {
   437  	if len(frags) == 0 {
   438  		return nil
   439  	}
   440  
   441  	if result == nil {
   442  		return nil
   443  	}
   444  
   445  	var err error
   446  	for _, frag := range frags {
   447  		err = frag.check(result)
   448  		if err == nil {
   449  			return nil
   450  		}
   451  	}
   452  
   453  	return err
   454  }
   455  
   456  func extractFilter(m schema.Mutation) map[string]interface{} {
   457  	var filter map[string]interface{}
   458  	mutationType := m.MutationType()
   459  	if mutationType == schema.UpdateMutation {
   460  		input, ok := m.ArgValue("input").(map[string]interface{})
   461  		if ok {
   462  			filter, _ = input["filter"].(map[string]interface{})
   463  		}
   464  	} else if mutationType == schema.DeleteMutation {
   465  		filter, _ = m.ArgValue("filter").(map[string]interface{})
   466  	}
   467  	return filter
   468  }
   469  
   470  func rewriteUpsertQueryFromMutation(m schema.Mutation) *gql.GraphQuery {
   471  	// The query needs to assign the results to a variable, so that the mutation can use them.
   472  	dgQuery := &gql.GraphQuery{
   473  		Var:  mutationQueryVar,
   474  		Attr: m.ResponseName(),
   475  	}
   476  	// Add uid child to the upsert query, so that we can get the list of nodes upserted.
   477  	dgQuery.Children = append(dgQuery.Children, &gql.GraphQuery{
   478  		Attr: "uid",
   479  	})
   480  
   481  	// TODO - Cache this instead of this being a loop to find the IDField.
   482  	if ids := idFilter(m, m.MutatedType().IDField()); ids != nil {
   483  		addUIDFunc(dgQuery, ids)
   484  	} else {
   485  		addTypeFunc(dgQuery, m.MutatedType().DgraphName())
   486  	}
   487  
   488  	filter := extractFilter(m)
   489  	addFilter(dgQuery, m.MutatedType(), filter)
   490  	return dgQuery
   491  }
   492  
   493  func (drw *deleteRewriter) Rewrite(m schema.Mutation) (
   494  	*gql.GraphQuery, []*dgoapi.Mutation, error) {
   495  	if m.MutationType() != schema.DeleteMutation {
   496  
   497  		return nil, nil, errors.Errorf(
   498  			"(internal error) call to build delete mutation for %s mutation type",
   499  			m.MutationType())
   500  	}
   501  
   502  	return rewriteUpsertQueryFromMutation(m),
   503  		[]*dgoapi.Mutation{{
   504  			DeleteJson: []byte(deleteUIDVarMutation),
   505  		}},
   506  		nil
   507  }
   508  
   509  func (drw *deleteRewriter) FromMutationResult(
   510  	mutation schema.Mutation,
   511  	assigned map[string]string,
   512  	result map[string]interface{}) (*gql.GraphQuery, error) {
   513  
   514  	// There's no query that follows a delete
   515  	return nil, nil
   516  }
   517  
   518  func asUID(val interface{}) (uint64, error) {
   519  	if val == nil {
   520  		return 0, errors.Errorf("ID value was null")
   521  	}
   522  
   523  	id, ok := val.(string)
   524  	uid, err := strconv.ParseUint(id, 0, 64)
   525  
   526  	if !ok || err != nil {
   527  		return 0, errors.Errorf("ID argument (%s) was not able to be parsed", id)
   528  	}
   529  
   530  	return uid, nil
   531  }
   532  
   533  func mutationsFromFragments(
   534  	frags []*mutationFragment,
   535  	setBuilder mutationBuilder,
   536  	delBuilder mutationBuilder) ([]*dgoapi.Mutation, error) {
   537  
   538  	mutations := make([]*dgoapi.Mutation, 0, len(frags))
   539  	var errs x.GqlErrorList
   540  
   541  	for _, frag := range frags {
   542  		if frag.err != nil {
   543  			errs = append(errs, schema.AsGQLErrors(frag.err)...)
   544  			continue
   545  		}
   546  
   547  		var conditions string
   548  		if len(frag.conditions) > 0 {
   549  			conditions = fmt.Sprintf("@if(%s)", strings.Join(frag.conditions, " AND "))
   550  		}
   551  
   552  		set, err := setBuilder(frag)
   553  		if err != nil {
   554  			errs = append(errs, schema.AsGQLErrors(err)...)
   555  			continue
   556  		}
   557  
   558  		del, err := delBuilder(frag)
   559  		if err != nil {
   560  			errs = append(errs, schema.AsGQLErrors(err)...)
   561  			continue
   562  		}
   563  
   564  		mutations = append(mutations, &dgoapi.Mutation{
   565  			SetJson:    set,
   566  			DeleteJson: del,
   567  			Cond:       conditions,
   568  		})
   569  	}
   570  
   571  	var err error
   572  	if len(errs) > 0 {
   573  		err = errs
   574  	}
   575  	return mutations, err
   576  }
   577  
   578  func queryFromFragments(frags []*mutationFragment) *gql.GraphQuery {
   579  	qry := &gql.GraphQuery{}
   580  	for _, frag := range frags {
   581  		qry.Children = append(qry.Children, frag.queries...)
   582  	}
   583  
   584  	if len(qry.Children) == 0 {
   585  		return nil
   586  	}
   587  
   588  	return qry
   589  }
   590  
   591  // rewriteObject rewrites obj to a list of mutation fragments.  See addRewriter.Rewrite
   592  // for a description of what those fragments look like.
   593  //
   594  // GraphQL validation has already ensured that the types of arguments (or variables)
   595  // are correct and has ensured that non-nullables are not null.  But for deep mutations
   596  // that's not quite enough, and we have add some extra checking on the reference
   597  // types.
   598  //
   599  // Currently adds enforce the schema ! restrictions, but updates don't.
   600  // e.g. a Post might have `title: String!`` in the schema, but,  a Post update could
   601  // set that to to null. ATM we allow this and it'll just triggers GraphQL error propagation
   602  // when that is in a query result.  This is the same case as deletes: e.g. deleting
   603  // an author might make the `author: Author!` field of a bunch of Posts invalid.
   604  // (That might actually be helpful if you want to run one mutation to remove something
   605  // and then another to correct it.)
   606  func rewriteObject(
   607  	typ schema.Type,
   608  	srcField schema.FieldDefinition,
   609  	srcUID string,
   610  	counter *counter,
   611  	obj map[string]interface{}) []*mutationFragment {
   612  
   613  	atTopLevel := srcField == nil
   614  	topLevelAdd := srcUID == ""
   615  
   616  	variable := fmt.Sprintf("%s%v", typ.Name(), counter.next())
   617  
   618  	id := typ.IDField()
   619  	if id != nil {
   620  		if idVal, ok := obj[id.Name()]; ok {
   621  			if idVal != nil {
   622  				return []*mutationFragment{asIDReference(idVal, srcField, srcUID, variable)}
   623  			}
   624  			delete(obj, id.Name())
   625  		}
   626  	}
   627  
   628  	var xidFrag *mutationFragment
   629  	var xidString string
   630  	xid := typ.XIDField()
   631  	if xid != nil {
   632  		if xidVal, ok := obj[xid.Name()]; ok && xidVal != nil {
   633  			xidString, ok = xidVal.(string)
   634  			if !ok {
   635  				errFrag := newFragment(nil)
   636  				errFrag.err = errors.New("encountered an XID that isn't a string")
   637  				return []*mutationFragment{errFrag}
   638  			}
   639  		}
   640  	}
   641  
   642  	if !atTopLevel { // top level is never a reference - it's adding/updating
   643  		if xid != nil && xidString != "" {
   644  			xidFrag =
   645  				asXIDReference(srcField, srcUID, typ, xid.Name(), xidString, variable)
   646  		}
   647  	}
   648  
   649  	if !atTopLevel { // top level mutations are fully checked by GraphQL validation
   650  		exclude := ""
   651  		if srcField != nil {
   652  			invType, invField := srcField.Inverse()
   653  			if invType != nil && invField != nil {
   654  				exclude = invField.Name()
   655  			}
   656  		}
   657  		if err := typ.EnsureNonNulls(obj, exclude); err != nil {
   658  			// This object is either an invalid deep mutation or it's an xid reference
   659  			// and asXIDReference must to apply or it's an error.
   660  			return invalidObjectFragment(err, xidFrag, variable, xidString)
   661  		}
   662  	}
   663  
   664  	var newObj map[string]interface{}
   665  	var myUID string
   666  	if !atTopLevel || topLevelAdd {
   667  		newObj = make(map[string]interface{}, len(obj)+3)
   668  		dgraphTypes := []string{typ.DgraphName()}
   669  		dgraphTypes = append(dgraphTypes, typ.Interfaces()...)
   670  		newObj["dgraph.type"] = dgraphTypes
   671  		myUID = fmt.Sprintf("_:%s", variable)
   672  
   673  		addInverseLink(newObj, srcField, srcUID)
   674  
   675  	} else { // it's the top level of an update add/remove
   676  		newObj = make(map[string]interface{}, len(obj))
   677  		myUID = srcUID
   678  	}
   679  	newObj["uid"] = myUID
   680  
   681  	frag := newFragment(newObj)
   682  	results := []*mutationFragment{frag}
   683  
   684  	// if xidString != "", then we are adding with an xid.  In which case, we have to ensure
   685  	// as part of the upsert that the xid doesn't already exist.
   686  	if xidString != "" {
   687  		if atTopLevel {
   688  			// If not at top level, the query is already added by asXIDReference
   689  			frag.queries = []*gql.GraphQuery{
   690  				xidQuery(variable, xidString, xid.Name(), typ),
   691  			}
   692  		}
   693  		frag.conditions = []string{fmt.Sprintf("eq(len(%s), 0)", variable)}
   694  		frag.check = checkQueryResult(variable,
   695  			x.GqlErrorf("id %s already exists for type %s", xidString, typ.Name()),
   696  			nil)
   697  	}
   698  
   699  	for field, val := range obj {
   700  		var frags []*mutationFragment
   701  
   702  		fieldDef := typ.Field(field)
   703  		fieldName := typ.DgraphPredicate(field)
   704  
   705  		switch val := val.(type) {
   706  		case map[string]interface{}:
   707  			// This field is another GraphQL object, which could either be linking to an
   708  			// existing node by it's ID
   709  			// { "title": "...", "author": { "id": "0x123" }
   710  			//          like here ^^
   711  			// or giving the data to create the object as part of a deep mutation
   712  			// { "title": "...", "author": { "username": "new user", "dob": "...", ... }
   713  			//          like here ^^
   714  			frags = rewriteObject(fieldDef.Type(), fieldDef, myUID, counter, val)
   715  		case []interface{}:
   716  			// This field is either:
   717  			// 1) A list of objects: e.g. if the schema said `categories: [Categories]`
   718  			//   Which can be references to existing objects
   719  			//   { "title": "...", "categories": [ { "id": "0x123" }, { "id": "0x321" }, ...] }
   720  			//            like here ^^                ^^
   721  			//   Or a deep mutation that creates new objects
   722  			//   { "title": "...", "categories": [ { "name": "new category", ... }, ... ] }
   723  			//            like here ^^                ^^
   724  			// 2) Or a list of scalars - e.g. if schema said `scores: [Float]`
   725  			//   { "title": "...", "scores": [10.5, 9.3, ... ]
   726  			//            like here ^^
   727  			frags = rewriteList(fieldDef.Type(), fieldDef, myUID, counter, val)
   728  		default:
   729  			// This field is either:
   730  			// 1) a scalar value: e.g.
   731  			//   { "title": "My Post", ... }
   732  			// 2) a JSON null: e.g.
   733  			//   { "text": null, ... }
   734  			//   e.g. to remove the text or
   735  			//   { "friends": null, ... }
   736  			//   to remove all friends
   737  			frags = []*mutationFragment{newFragment(val)}
   738  		}
   739  
   740  		results = squashFragments(squashIntoObject(fieldName), results, frags)
   741  	}
   742  
   743  	if xidFrag != nil {
   744  		results = append(results, xidFrag)
   745  	}
   746  
   747  	return results
   748  }
   749  
   750  func invalidObjectFragment(
   751  	err error,
   752  	xidFrag *mutationFragment,
   753  	variable, xidString string) []*mutationFragment {
   754  
   755  	if xidFrag != nil {
   756  		xidFrag.check =
   757  			checkQueryResult(variable,
   758  				nil,
   759  				schema.GQLWrapf(err, "xid \"%s\" doesn't exist and input object not well formed", xidString))
   760  
   761  		return []*mutationFragment{xidFrag}
   762  	}
   763  	return []*mutationFragment{{err: err}}
   764  }
   765  
   766  func checkQueryResult(qry string, yes, no error) resultChecker {
   767  	return func(m map[string]interface{}) error {
   768  		if val, exists := m[qry]; exists && val != nil {
   769  			if data, ok := val.([]interface{}); ok && len(data) > 0 {
   770  				return yes
   771  			}
   772  		}
   773  		return no
   774  	}
   775  }
   776  
   777  // asIDReference makes a mutation fragment that resolves a reference to the uid in val.  There's
   778  // a bit of extra mutation to build if the original mutation contains a reference to
   779  // another node: e.g it was say adding a Post with:
   780  // { "title": "...", "author": { "id": "0x123" }, ... }
   781  // and we'd gotten to here        ^^
   782  // in rewriteObject with srcField = "author" srcUID = "XYZ"
   783  // and the schema says that Post.author and Author.Posts are inverses of each other, then we need
   784  // to make sure that inverse link is added/removed.  We have to make sure the Dgraph upsert
   785  // mutation ends up like:
   786  //
   787  // query :
   788  // Author1 as Author1(func: uid(0x123)) @filter(type(Author)) { uid }
   789  // condition :
   790  // len(Author1) > 0
   791  // mutation :
   792  // { "uid": "XYZ", "title": "...", "author": { "id": "0x123", "posts": [ { "uid": "XYZ" } ] }, ... }
   793  // asIDReference builds the fragment
   794  // { "id": "0x123", "posts": [ { "uid": "XYZ" } ] }
   795  func asIDReference(
   796  	val interface{},
   797  	srcField schema.FieldDefinition,
   798  	srcUID string,
   799  	variable string) *mutationFragment {
   800  
   801  	result := make(map[string]interface{}, 2)
   802  	frag := newFragment(result)
   803  
   804  	uid, err := asUID(val)
   805  	if err != nil {
   806  		frag.err = err
   807  		return frag
   808  	}
   809  
   810  	result["uid"] = val
   811  
   812  	addInverseLink(result, srcField, srcUID)
   813  
   814  	qry := &gql.GraphQuery{
   815  		Var:      variable,
   816  		Attr:     variable,
   817  		UID:      []uint64{uid},
   818  		Children: []*gql.GraphQuery{{Attr: "uid"}},
   819  	}
   820  	addTypeFilter(qry, srcField.Type())
   821  	addUIDFunc(qry, []uint64{uid})
   822  
   823  	frag.queries = []*gql.GraphQuery{qry}
   824  	frag.conditions = []string{fmt.Sprintf("eq(len(%s), 1)", variable)}
   825  	frag.check =
   826  		checkQueryResult(variable,
   827  			nil,
   828  			errors.Errorf("ID \"%#x\" isn't a %s", uid, srcField.Type().Name()))
   829  
   830  	return frag
   831  
   832  	// FIXME: if this is an update we also need to add a query that checks if
   833  	// an author exists, and add a mutation to remove this post from that author
   834  	// query(func: uid(XYZ)) { a as author }
   835  	// +delete mutation
   836  	// { uid: uid(a), posts: [ uid: "XYZ"] }
   837  	// this can only occur at top level, not deep
   838  	//
   839  	// mutation was
   840  	// { "title": "...", "author": { "id": "0x123" }, ... }
   841  	//
   842  	// we'll build
   843  	// query XYZ = ...
   844  	// query is 123 an author
   845  	// query(func: uid(XYZ)) { a as author(and not 123) }
   846  	// { "uid": "XYZ", "title": "...",
   847  	//   "author": { "id": "0x123", "posts": [ { "uid": "XYZ" } ] }, ... }
   848  	// also
   849  	// delete { uid: uid(a), posts: [ uid: uid(XYZ)] }
   850  	//
   851  	// ** only if author is single  - other wise it's always adding to existing edges. **
   852  	// ** only if update set mutation **
   853  	// ** Also in an add that links to an existing node **
   854  	// same sort of thing if it's xid, not id
   855  	//
   856  	// should go in some sort of deletes list
   857  	//
   858  	// Can tell by the type ???
   859  }
   860  
   861  // asXIDReference makes a mutation fragment that resolves a reference to an XID.  There's
   862  // a bit of extra mutation to build since if the original mutation contains a reference to
   863  // another node, e.g it was say adding a Post with:
   864  // { "title": "...", "author": { "username": "A-user" }, ... }
   865  // and we'd gotten to here        ^^
   866  // in rewriteObject with srcField = "author" srcUID = "XYZ"
   867  // and the schema says that Post.author and Author.Posts are inverses of each other, then we need
   868  // to make sure that inverse link is added/removed.  We have to make sure the Dgraph upsert
   869  // mutation ends up like:
   870  //
   871  // query :
   872  // Author1 as Author1(func: eq(username, "A-user")) @filter(type(Author)) { uid }
   873  // condition :
   874  // len(Author1) > 0
   875  // mutation :
   876  // { "uid": "XYZ", "title": "...", "author": { "id": "uid(Author1)", "posts": ...
   877  // where asXIDReference builds the fragment
   878  // { "id": "uid(Author1)", "posts": [ { "uid": "XYZ" } ] }
   879  func asXIDReference(
   880  	srcField schema.FieldDefinition,
   881  	srcUID string,
   882  	typ schema.Type,
   883  	xidFieldName, xidString, xidVariable string) *mutationFragment {
   884  
   885  	result := make(map[string]interface{}, 2)
   886  	frag := newFragment(result)
   887  
   888  	result["uid"] = fmt.Sprintf("uid(%s)", xidVariable)
   889  
   890  	addInverseLink(result, srcField, srcUID)
   891  
   892  	frag.queries = []*gql.GraphQuery{xidQuery(xidVariable, xidString, xidFieldName, typ)}
   893  	frag.conditions = []string{fmt.Sprintf("eq(len(%s), 1)", xidVariable)}
   894  	frag.check = checkQueryResult(xidVariable,
   895  		nil,
   896  		errors.Errorf("ID \"%s\" isn't a %s", xidString, srcField.Type().Name()))
   897  
   898  	// FIXME: and remove any existing
   899  
   900  	return frag
   901  }
   902  
   903  func addInverseLink(obj map[string]interface{}, srcField schema.FieldDefinition, srcUID string) {
   904  	if srcField != nil {
   905  		invType, invField := srcField.Inverse()
   906  		if invType != nil && invField != nil {
   907  			if invField.Type().ListType() != nil {
   908  				obj[invType.DgraphPredicate(invField.Name())] =
   909  					[]interface{}{map[string]interface{}{"uid": srcUID}}
   910  			} else {
   911  				obj[invType.DgraphPredicate(invField.Name())] =
   912  					map[string]interface{}{"uid": srcUID}
   913  			}
   914  		}
   915  	}
   916  }
   917  
   918  func xidQuery(xidVariable, xidString, xidPredicate string, typ schema.Type) *gql.GraphQuery {
   919  	qry := &gql.GraphQuery{
   920  		Var:  xidVariable,
   921  		Attr: xidVariable,
   922  		Func: &gql.Function{
   923  			Name: "eq",
   924  			Args: []gql.Arg{
   925  				{Value: typ.DgraphPredicate(xidPredicate)},
   926  				{Value: maybeQuoteArg("eq", xidString)},
   927  			},
   928  		},
   929  		Children: []*gql.GraphQuery{{Attr: "uid"}},
   930  	}
   931  	addTypeFilter(qry, typ)
   932  	return qry
   933  }
   934  
   935  func rewriteList(
   936  	typ schema.Type,
   937  	srcField schema.FieldDefinition,
   938  	srcUID string,
   939  	counter *counter,
   940  	objects []interface{}) []*mutationFragment {
   941  
   942  	frags := []*mutationFragment{newFragment(make([]interface{}, 0))}
   943  
   944  	for _, obj := range objects {
   945  		switch obj := obj.(type) {
   946  		case map[string]interface{}:
   947  			frags = squashFragments(squashIntoList, frags,
   948  				rewriteObject(typ, srcField, srcUID, counter, obj))
   949  		default:
   950  			// All objects in the list must be of the same type.  GraphQL validation makes sure
   951  			// of that. So this must be a list of scalar values (lists of lists aren't allowed).
   952  			return []*mutationFragment{
   953  				newFragment(objects),
   954  			}
   955  		}
   956  	}
   957  
   958  	return frags
   959  }
   960  
   961  func newFragment(f interface{}) *mutationFragment {
   962  	return &mutationFragment{
   963  		fragment: f,
   964  		check:    func(m map[string]interface{}) error { return nil },
   965  	}
   966  }
   967  
   968  func squashIntoList(list, v interface{}, makeCopy bool) interface{} {
   969  	if list == nil {
   970  		return []interface{}{v}
   971  	}
   972  	asList := list.([]interface{})
   973  	if makeCopy {
   974  		cpy := make([]interface{}, len(asList), len(asList)+1)
   975  		copy(cpy, asList)
   976  		asList = cpy
   977  	}
   978  	return append(asList, v)
   979  }
   980  
   981  func squashIntoObject(label string) func(interface{}, interface{}, bool) interface{} {
   982  	return func(object, v interface{}, makeCopy bool) interface{} {
   983  		asObject := object.(map[string]interface{})
   984  		if makeCopy {
   985  			cpy := make(map[string]interface{}, len(asObject)+1)
   986  			for k, v := range asObject {
   987  				cpy[k] = v
   988  			}
   989  			asObject = cpy
   990  		}
   991  		asObject[label] = v
   992  		return asObject
   993  	}
   994  }
   995  
   996  // squashFragments takes two lists of mutationFragments and produces a single list
   997  // that has all the right fragments squashed into the left.
   998  //
   999  // In most cases, this is len(left) == 1 and len(right) == 1 and the result is a
  1000  // single fragment.  For example, if left is what we have built so far for adding a
  1001  // new author and to original input contained:
  1002  // {
  1003  //   ...
  1004  //   country: { id: "0x123" }
  1005  // }
  1006  // rewriteObject is called on `{ id: "0x123" }` to create a fragment with
  1007  // Query: CountryXYZ as CountryXYZ(func: uid(0x123)) @filter(type(Country)) { uid }
  1008  // Condition: eq(len(CountryXYZ), 1)
  1009  // Fragment: { id: "0x123" }
  1010  // In this case, we just need to add `country: { id: "0x123" }`, the query and condition
  1011  // to the left fragment and the result is a single fragment.  If there are no XIDs
  1012  // in the schema, only 1 fragment can ever be generated.  We can always tell if the
  1013  // mutation means to link to an existing object (because the ID value is present),
  1014  // or if the intention is to create a new object (because the ID value isn't there,
  1015  // that means it's not known client side), so there's never any need for more than
  1016  // one conditional mutation.
  1017  //
  1018  // However, if there are XIDs, there can be multiple possible mutations.
  1019  // For example, if schema has `Type Country { code: String! @id, name: String! ... }`
  1020  // and the mutation input is
  1021  // {
  1022  //   ...
  1023  //   country: { code: "ind", name: "India" }
  1024  // }
  1025  // we can't tell from the mutation text if this mutation means to link to an existing
  1026  // country or if it's a deep add on the XID `code: "ind"`.  If the mutation was
  1027  // `country: { code: "ind" }`, we'd know it's a link because they didn't supply
  1028  // all the ! fields to correctly create a new country, but from
  1029  // `country: { code: "ind", name: "India" }` we have to go to the DB to check.
  1030  // So rewriteObject called on `{ code: "ind", name: "India" }` produces two fragments
  1031  //
  1032  // Query: CountryXYZ as CountryXYZ(func: eq(code, "ind")) @filter(type(Country)) { uid }
  1033  //
  1034  // Fragment1 (if "ind" already exists)
  1035  //  Cond: eq(len(CountryXYZ), 1)
  1036  //  Fragment: { uid: uid(CountryXYZ) }
  1037  //
  1038  // and
  1039  //
  1040  // Fragment2 (if "ind" doesn't exist)
  1041  //  Cond eq(len(CountryXYZ), 0)
  1042  //  Fragment: { uid: uid(CountryXYZ), code: "ind", name: "India" }
  1043  //
  1044  // Now we have to squash this into what we've already built for the author (left
  1045  // mutationFragment).  That'll end up as a result with two fragments (two possible
  1046  // mutations guarded by conditions on if the country exists), and to do
  1047  // that, we'll need to make some copies, e.g., because we'll end up with
  1048  // country: { uid: uid(CountryXYZ) }
  1049  // in one fragment, and
  1050  // country: { uid: uid(CountryXYZ), code: "ind", name: "India" }
  1051  // in the other we need to copy what we've already built for the author to represent
  1052  // the different mutation payloads.  Same goes for the conditions.
  1053  func squashFragments(
  1054  	combiner func(interface{}, interface{}, bool) interface{},
  1055  	left, right []*mutationFragment) []*mutationFragment {
  1056  
  1057  	if len(left) == 0 {
  1058  		return right
  1059  	}
  1060  
  1061  	if len(right) == 0 {
  1062  		return left
  1063  	}
  1064  
  1065  	result := make([]*mutationFragment, 0, len(left)*len(right))
  1066  	for _, l := range left {
  1067  		for _, r := range right {
  1068  			var conds []string
  1069  
  1070  			if len(l.conditions) > 0 {
  1071  				conds = make([]string, len(l.conditions), len(l.conditions)+len(r.conditions))
  1072  				copy(conds, l.conditions)
  1073  			}
  1074  
  1075  			result = append(result, &mutationFragment{
  1076  				conditions: append(conds, r.conditions...),
  1077  				fragment:   combiner(l.fragment, r.fragment, len(right) > 1),
  1078  				check: func(lcheck, rcheck resultChecker) resultChecker {
  1079  					return func(m map[string]interface{}) error {
  1080  						return schema.AppendGQLErrs(lcheck(m), rcheck(m))
  1081  					}
  1082  				}(l.check, r.check),
  1083  				err: schema.AppendGQLErrs(l.err, r.err),
  1084  			})
  1085  		}
  1086  	}
  1087  
  1088  	// queries don't need copying, they just need to be all collected at the end, so
  1089  	// accumulate them all into one of the result fragments
  1090  	var queries []*gql.GraphQuery
  1091  	for _, l := range left {
  1092  		queries = append(queries, l.queries...)
  1093  	}
  1094  	for _, r := range right {
  1095  		queries = append(queries, r.queries...)
  1096  	}
  1097  	result[0].queries = queries
  1098  
  1099  	return result
  1100  }