Postgraphile: How to rollback all mutations if only one of them falls?

Created on 22 Dec 2018  ·  13Comments  ·  Source: graphile/postgraphile

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.

✖️ invalid

Most helpful comment

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?

All 13 comments

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:

https://github.com/facebook/graphql/issues/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.

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.

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

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?

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/

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

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

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

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?

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

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.

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.

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tonyhschu picture tonyhschu  ·  3Comments

outsidenote picture outsidenote  ·  4Comments

k-ogawa-1988 picture k-ogawa-1988  ·  3Comments

giacomorebonato picture giacomorebonato  ·  3Comments

tazsingh picture tazsingh  ·  3Comments