Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to rollback all mutations if only one of them falls? #944

Closed
blasterpistol opened this issue Dec 22, 2018 · 13 comments
Closed

How to rollback all mutations if only one of them falls? #944

blasterpistol opened this issue Dec 22, 2018 · 13 comments

Comments

@blasterpistol
Copy link

This is useful for applications with large client logic, they will be able to manipulate mutations as Lego cubes, without creating separate custom mutations just for the sake of combining them under one transaction.

@benjie
Copy link
Member

benjie commented Dec 22, 2018

Sorry, this would not adhere to the GraphQL specification, so is not something that we plan to implement. There is no dependency between mutations (other than their serial nature) and it’s valid for one to fail but the others to succeed.

https://facebook.github.io/graphql/draft/#sec-Mutation
https://facebook.github.io/graphql/draft/#sec-Errors-and-Non-Nullability

One option within the GraphQL spec would be to make the mutation payloads non-nullable, but this has consequences (and goes against best practices).

To handle this, the intention is to do one “transaction” per mutation. You may also be interested in a GraphQL proposal that relate to this:

graphql/graphql-spec#252

(Full disclosure: your request is currently possible by wrapping mutation resolvers and aborting the parent transaction when an error occurs; however this behaviour will be changing in a future release and I strongly recommend you do not do it.)

Closing as wontfix; but feel free to keep discussing below.

@langpavel
Copy link
Contributor

I fully agree with @benjie
But you can pack multiple input types together into single mutation @DenisNeustroev
Then you can response once - with one type.
It will be atomic and you can get needed behaviour.

@langpavel
Copy link
Contributor

langpavel commented Dec 22, 2018

Oh, this is postgraphile..

  • You should write own function in postgres.
  • You should do this for all atomic required non trivial operations.
  • You can use triggers too
  • You can create view with triggers

@blasterpistol
Copy link
Author

Thanks for the quick response! Your excellent library allowed us to remove more than 20 thousand lines of golang code.

Can you tell me if it’s possible to delete savepoints using a plugin or plugin+directives? If so, what plugin methods will help to do this?

@benjie
Copy link
Member

benjie commented Dec 22, 2018

It’s not possible to delete them, but you can wrap them with further save points and rollback to those 😁

https://www.graphile.org/postgraphile/make-wrap-resolvers-plugin/

@benjie
Copy link
Member

benjie commented Dec 22, 2018

(This will not work in version 5, because each mutation will use its own independent transaction.)

@mitswan
Copy link

mitswan commented Feb 20, 2020

Does anyone have an example of how they solved this? 🙏

I understand the rationale for wontfix but there are legitimate cases where you would want to make a collection of mutations atomic

@benjie
Copy link
Member

benjie commented Feb 20, 2020

The correct way to do this in GraphQL is to make the atomic operation a single field.

@mitswan
Copy link

mitswan commented Feb 20, 2020

Yes but say for example I have 2 people with a balance account, and I want to move funds from one to another. If the second were to fail, it all needs to fail.

To my knowledge the only way to force that into a single mutation is by using the nested mutations plugin. Is there another way?

@mitswan
Copy link

mitswan commented Feb 20, 2020

Specifically my example is I need to update 10 items but it needs to be all or nothing

@benjie
Copy link
Member

benjie commented Feb 20, 2020

To expand on my previous answer, the correct way to do this in GraphQL would be with a mutation like this that achieves the full atomic operation in a single field:

mutation {
  transferBalance(input: {amount: 10, fromId: 27, toId: 93}) {
    success
  }
}

In PostGraphile you could do this with a Custom Mutation such as:

create function transfer_balance(amount int, from_id int, to_id int) returns boolean as $$
begin
  update users set balance = balance - amount where id = from_id;
  update users set balance = balance + amount where id = to_id;
  return true;
end;
$$ language plpgsql volatile;

or via makeExtendSchemaPlugin.

@tshelver
Copy link

tshelver commented Feb 20, 2020

Just my 2cents from a Postgres perspective. I am working on a system where we detect a change in an external object, which then needs to be updated / inserted or deleted in PG.
All the data is passed into a function, as Benjie is doing. The difference is that I pass it in as a single JSONB parameter, which we then deconstruct in the function.
A single object insert / update can result in 12 (or up to 20) table inserts / updates. These all have to be kept in sync.

Much faster and more reliable to do it this way than issuing individual statements from the calling program.

All mutations are handled through functions, no direct table access.
The vast majority of data reads are also handled by functions.

@mitswan
Copy link

mitswan commented Feb 20, 2020

Fantastic, thanks for that I'll have a play around with some pg functions to handle this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants