How to upsert using GraphQL?

Hi there,

Seems basic but I must be missing something… How can I perform an upsert operation using GraphQL? I can upsert using DQL but when I do, my inverse predicate doesn’t get linked, so I need a GraphQL upsert.

Thanks.

2 Likes

This was one of the reasons the developers of Dgraph needed to invent DQL (GraphQL±) you can read more about that at:

To answer your question directly,

You will have to do it in 2 or more queries/mutations with a client side script.

Ah, the downside of using DQL and GQL together. With DQL you have to define every single predicate and the GQL equivalent reverse predicate. Do if you are changing the author on a Post you would need to set both Post.author and Author.hasPosts to make it a full update. GQL generates alot of these extra steps needed in DQL for you making it easier, but again that has limitations as you are now aware.

1 Like

What is your objective? you could potentially use a custom DQL - But I believe that the “CRUD” like structure that the GraphQL does under the hood, do a kind of “upsert” behave.

As you have a “CRUD” like behavior based on your schema. I think you don’t need the upsert, unless you are doing something very specific and hard to accomplish with GraphQL alone.

@amaster507 Thanks for your response.

I understand there are current limitations in GQL but performing upsert operations should be atomic and are not desired in two steps, otherwise becomes a recipe for introducing inconsistencies in the database if there are two or more similar operations at once.

Yes, I’ve been experimenting with both and can see that a lot of in-between steps are performed by the GQL layer (great job). Hence, I expected the GQL layer to support upsert behaviour as well.

@MichelDiz thanks for addressing this.
I’m not sure if you mean I don’t need a custom DQL for upsert or don’t need upsert in general. My use case is simple, I’m receiving data from an external source and I transform them into Dgraph. If the node does not exist I add them, otherwise, I update them. Simple enough to use the Add and Update queries but I prefer to perform operations like this in an atomic form.

@MichelDiz Can you expand on this? When I use the Add mutation and the node with an @id directive exists, the operation is disallowed. Also, when I use the Update mutation and the node does not exist, then nothing gets updated. What I want is for either case to succeed when upserting.

2 Likes

Care to give your feedback on:

Sure @amaster507. Your post there outlines the same question I’ve raised. Indeed it gets increasingly complex when you have multiple updates that can either be an Add or Update operation. What I want here is to be able to perform these mutations in one request. If this works, there are great benefits when combined with external IDs (or when UIDs are known beforehand). It means I can perform deeply nested mutations in one request and not worry about the existence of leaf nodes.

For example:

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

type Vehicle{
    vehicleId: String! @id
    model: String!
    owner: Person
}

mutation {
    updateVehicle(input: {
        filter: {vehicleId: {eq: "thiscar"}},
        set: {
            model: "foo",
            owner: {
                personId: "thatperson",
                name: "bar"
            }
        }
    }
}

With an upsert operation, I’ll expect this query to succeed even if thiscar or thatperson does not exist and the same applies to the add operation. Currently, I’ll need to make at least 4 requests on the first run to achieve the same result. I can imagine situations where an upsert is not desired, but in theory, there can be a way to flag this behaviour.

1 Like

Yeah, I meant that. I thought that if Dgraph creates an “Update” it should behave as an “upsert”. But feels like it doesn’t. I haven’t used Dgraph’s GraphQL as much I should. I was assuming things that would make sense in my opinion and experience with GraphQL.

So, that’s exactly what a Custom DQL would do using the Upsert Block - So, you would create a specific GraphQL mutation for this.

Also, I think there is an internal ticket about Upsert Block natively in GraphQL, but not sure. I’ll check.

Humm, I was about to question that. It should at least throw an error in the Update mutation. Like “There is nothing to update”. That’s odd for me. Do we have a ticket about it? (on the issue/graphql part)

Update

I just have checked, there’s no ticket internally or here on discuss about the Upsert Behavior. Also, we should have one about the Update mutation, that it should throw an error in case the node doesn’t exists.

Indeed, thats what the Custom DQL will do but this route is not preferred because my GQL schema should enforce a database model/structure I.e., using directives like @hasInverse, @id, etc. If I now create a Custom DQL to perform operations like this, I will be bypassing the GQL layer that should enforce the schema, therefore potentially introducing major inconsistencies.

This seems sensible to me. So far I see, I don’t get any error when there is nothing to update.

2 Likes

@MichelDiz, although this is slightly out of the scope of the question here, after reconsidering your point, I think the current behaviour is right according to GQL specs since this is technically not an error.

GraphQL
If no errors were encountered during the requested operation, the errors entry should not be present in the result.

The user can query on numUids (the number of affected UIDs) and check if the operation actually performed an update.

What is the status of this, I’m a new user confused as to how the heck to accomplish basic functionality like add a new user with unique email. GQL enforcing schema, auto CRUD api are all great but it seems like a total bait and switch now it appears I have to drill into the advanced level of manually enforcing the schema constraints or accepting non-atomic multi-operation work-around? How is this not already a solved thing?

2 Likes

If you use the update op you should be fine. The upsert would be in cases like “if it doesn’t exist, create a new one”. GraphQL spec doesn’t cover that. You have to manually set up such verification and probably do 2 operations, one to query and verify on your end, another the create or update.

Also, there’s no news about this as this was a question. Not a request. Usually, popular requests are prioritized on the backlog.

Thanks @MichelDiz will move forward as you prescribe. In practice not having upsert is not a deal killer.

FYI we have internally made design docs for upserts in GraphQL, with @michaelcompton fearlessly leading the charge. It’ll be made public soon once we have hashed out the little deets.

1 Like

Hi, I could successfully use DQL upsert feature with GraphQL attributes including linked attributes.
Drawback is the need of handling inverse edges manually.

upsert {
  query {
    q(func: eq(User.username, "john.doe")) {
      v as uid
    }
    
    q2(func: eq(Department.number, "1")) {
      dp as uid
    }
  }

  mutation {
    set {
      uid(v) <User.username> "john.doe" .
      uid(v) <User.email> "user@company1.io" .
      uid(v) <User.department> uid(dp) .
      uid(dp) <Department.members> uid(v) .
      uid(v) <dgraph.type> "User" .
    }
  }
}

After that a GraphQL query provides the information of linked attributes: User.department and Department.members (inverse edge).

query {
  queryUser(){
    username
    department {
      number
    }
  }
  
  queryDepartment(){
    number 
    members { 
      username
    }
  }
}

Result:

{
  "data": {
    "queryUser": [
      {
        "username": "john.doe",
        "department": {
          "number": "1"
        }
      }
    ],
    "queryDepartment": [
      {
        "number": "1",
        "members": [
          {
            "username": "john.doe"
          }
        ]
      }
    ]
  }
}
1 Like

Atomic is critical, I am in the same position

It’s on the roadmap and coming 21.03 AFAIK

1 Like