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.
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.
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.
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.
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.
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)
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.
@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, theerrors
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?
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.
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"
}
]
}
]
}
}
Atomic is critical, I am in the same position
It’s on the roadmap and coming 21.03 AFAIK