RFC: Upsert Mutations

Motivation

With the support of external IDs using @id directive, users of Dgraph GraphQL often face a situation where the existence of a particular ID in database decides on whether a particular mutation should be carried out or not. One such common example is described below:

Schema:

type Person {
    id: String! @id
    name: String!
}

Suppose, a user wants to perform a mutation to set name field of Person with id "2" to "New Name", the current way to do this using GraphQL would involve following steps:

  1. Use queryPerson with appropriate filters to find out if a there exists Person with id = "2"
  2. If there exists a Person with id = "2", carry out an updatePerson mutation.
  3. If there does not exist any such Person, create a new Person with id = "2" and set its name to "New Name" using addPerson mutation.

All these steps need to be performed from Client side. This proves out to be cumbersome.

A GraphQL mutation which will replace this 3 step process has been requested by Users many times.
Here are some of the related discuss posts:

  1. How to upsert using GraphQL?
  2. GraphQL equivalient to Insert on Duplicate Key Update
  3. Converting update query to an upsert query
  4. Upsert in gql
  5. Upsert equivalent on dgraph

To help address these issues, we propose Upsert Mutations using GraphQL

Proposal

We propose adding another variable, upsert to the input of add GraphQL Mutations.

Current addPerson Mutation:

mutation{
    addPerson(input: [AddPersonInput!]!): AddPersonPayload
}

Proposed addPerson Mutation after adding upsert as input variable:

mutation{
    addPerson(input: [AddPersonInput!]!, upsert: Boolean): AddPersonPayload
}

Along with providing input value to add Mutation, this will enable Users to provide an optional upsert Boolean value. Setting upsert to True while carrying out add mutation will change the behaviour of add Mutation to an upsert.

The default value of upsert will be False. This is done to ensure backward compatibility.

In the above discussed case, the three steps could then be replaced by a single add mutation:

addPerson(input: [ { "id": "2", name: "New Name" } ] , upsert: True)

Limitations

  1. Deep GraphQL mutations:
    The current behaviour of Add and Update Mutations is such that other than at root level, existing nodes with XID (@id directive) are not updated. This behaviour will continue to maintain backward capability. Add mutations with upsert set to True will only affect the root level nodes and will update the values (if already existing) at root level only.
  2. Setting upsert value to True in an Add mutation for a Type which does not contain any @id will have not effect. upsert will only be relevant for Types containing an @id field.

@amaster507, @iyinoluwaayoola, @andrewluetgers, @phillip, @billybobthorton We will like to know more from you on whether this fits your use case.

3 Likes

I believe it does. @pbassham and I were just discussing this yesterday privately.

The deep limitation is expected right now and follows the way the deep mutations work right now across the board.

This will greatly help when using GraphQL to update data from a third party where we may already have partial data.

2 Likes

My needs are more around the need to do a group of transactions atomically. While I think it is a good improvement, it doesn’t affect me as much.

This feature has been implemented and has been merged to master, https://github.com/dgraph-io/dgraph/pull/7433 . It will be a part of 21.03 release.

@rajas Thanks for picking this up. For my understanding, why can’t the nested nodes be updated as well? That can save a lot of update requests especially when the data source is in that format (ie., JSON). In that case, I’ll expect upsert to also affect nested nodes and the upsert parameter is propagated across all transactions.

1 Like

Hi @iyinoluwaayoola,

We wanted to mimic the behaviour of Update Mutations when the node exists in case of Upsert . It is not possible to update nested nodes but it is possible to add new nested nodes (just like Update does).

Yes, this is true. We would also be adding support for nested level upserts as well at a later time. We plan to have an upsert variable at each nested level to allow this to happen. This will also offer more flexibility to Users.

3 Likes