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

Unexpected error behaviour, because of failing transactions #375

Closed
EyMaddis opened this issue Feb 24, 2017 · 12 comments
Closed

Unexpected error behaviour, because of failing transactions #375

EyMaddis opened this issue Feb 24, 2017 · 12 comments
Milestone

Comments

@EyMaddis
Copy link
Contributor

Hi,
when writing a query composition and one of the queries has an error, the error of one part will break the transaction for another.
I used the forum example from this repository to test this.
In the following query, I will receive all posts (as expected), but will also force an error by using currentPerson without any JWT Token:

query getStuff {
  allPosts { # works just fine
    edges {
      node {
        id,
        summary,
        headline
      }
    }
  }
  currentPerson { # this will break, because there is no user
    id
  }
}

This query will result in a partial result of posts, notice the absence of the (computed) summary property. The result will also feature a huge error log, for each summary:

{
  "data": {
    "allPosts": {
      "edges": [
        {
          "node": {
            "id": 1,
            "summary": null,
            "headline": "Ameliorated optimal emulation"
          }
        },
        {
          "node": {
            "id": 2,
            "summary": null,
            "headline": "Open-source non-volatile protocol"
          }
        },
        {
          "node": {
            "id": 3,
            "summary": null,
            "headline": "Decentralized tangible circuit"
          }
        },
        {
          "node": {
            "id": 4,
            "summary": null,
            "headline": "Secured exuding challenge"
          }
        },
        {
          "node": {
            "id": 5,
            "summary": null,
            "headline": "Devolved empowering workforce"
          }
        },
        {
          "node": {
            "id": 6,
            "summary": null,
            "headline": "Optional actuating forecast"
          }
        },
        {
          "node": {
            "id": 7,
            "summary": null,
            "headline": "Profound reciprocal product"
          }
        },
        {
          "node": {
            "id": 8,
            "summary": null,
            "headline": "Balanced uniform complexity"
          }
        },
        {
          "node": {
            "id": 9,
            "summary": null,
            "headline": "Centralized bifurcated alliance"
          }
        },
        {
          "node": {
            "id": 10,
            "summary": null,
            "headline": "Self-enabling dynamic capacity"
          }
        },
        {
          "node": {
            "id": 11,
            "summary": null,
            "headline": "Proactive zero administration portal"
          }
        },
        {
          "node": {
            "id": 12,
            "summary": null,
            "headline": "Cloned interactive info-mediaries"
          }
        },
        {
          "node": {
            "id": 13,
            "summary": null,
            "headline": "Up-sized encompassing open architecture"
          }
        },
        {
          "node": {
            "id": 14,
            "summary": null,
            "headline": "Robust next generation project"
          }
        },
        {
          "node": {
            "id": 15,
            "summary": null,
            "headline": "Ameliorated systemic challenge"
          }
        },
        {
          "node": {
            "id": 16,
            "summary": null,
            "headline": "Streamlined uniform instruction set"
          }
        },
        {
          "node": {
            "id": 17,
            "summary": null,
            "headline": "Reactive asymmetric hierarchy"
          }
        },
        {
          "node": {
            "id": 18,
            "summary": null,
            "headline": "Function-based radical intranet"
          }
        },
        {
          "node": {
            "id": 19,
            "summary": null,
            "headline": "Ergonomic even-keeled firmware"
          }
        },
        {
          "node": {
            "id": 20,
            "summary": null,
            "headline": "Cross-platform hybrid support"
          }
        },
        {
          "node": {
            "id": 21,
            "summary": null,
            "headline": "Organic needs-based emulation"
          }
        },
        {
          "node": {
            "id": 22,
            "summary": null,
            "headline": "Monitored disintermediate flexibility"
          }
        },
        {
          "node": {
            "id": 23,
            "summary": null,
            "headline": "Secured 5th generation help-desk"
          }
        },
        {
          "node": {
            "id": 24,
            "summary": null,
            "headline": "Customizable intermediate framework"
          }
        },
        {
          "node": {
            "id": 25,
            "summary": null,
            "headline": "Seamless system-worthy info-mediaries"
          }
        },
        {
          "node": {
            "id": 26,
            "summary": null,
            "headline": "Integrated high-level circuit"
          }
        },
        {
          "node": {
            "id": 27,
            "summary": null,
            "headline": "Integrated needs-based matrices"
          }
        },
        {
          "node": {
            "id": 28,
            "summary": null,
            "headline": "User-friendly asynchronous emulation"
          }
        },
        {
          "node": {
            "id": 29,
            "summary": null,
            "headline": "Compatible needs-based implementation"
          }
        },
        {
          "node": {
            "id": 30,
            "summary": null,
            "headline": "Pre-emptive exuding algorithm"
          }
        }
      ]
    },
    "currentPerson": null
  },
  "errors": [
    {
      "message": "unrecognized configuration parameter \"jwt.claims.person_id\"",
      "locations": [
        {
          "line": 25,
          "column": 3
        }
      ],
      "path": [
        "currentPerson"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        0,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        1,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        2,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        3,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        4,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        5,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        6,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        7,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        8,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        9,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        10,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        11,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        12,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        13,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        14,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        15,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        16,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        17,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        18,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        19,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        20,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        21,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        22,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        23,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        24,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        25,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        26,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        27,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        28,
        "node",
        "summary"
      ]
    },
    {
      "message": "current transaction is aborted, commands ignored until end of transaction block",
      "locations": [
        {
          "line": 20,
          "column": 9
        }
      ],
      "path": [
        "allPosts",
        "edges",
        29,
        "node",
        "summary"
      ]
    }
  ]
}

I just wonder, why results are returned when the whole query should have failed and why the order is so (in my opinion) unexpected.

Just a sidenote: I can also slightly alter the query to completely crash, by getting the pageInfo.hasNextPage.

@calebmer
Copy link
Collaborator

calebmer commented Mar 5, 2017

Very interesting. I’ve thought about this before, but I wasn’t sure how often it would happen 😊

This may be an easy fix. We could modify client.query to set a savepoint before each query and then rollback to that savepoint if there is an error. @benjie do you know if there are performance implications to placing a lot of savepoints?

@calebmer
Copy link
Collaborator

calebmer commented Mar 5, 2017

If it isn’t too expensive to place many savepoints this is something we could merge. For more context on the Postgres feature see: SAVEPOINT.

I feel like I have seen an article somewhere which urges libraries to insert savepoints before each statement to handle errors, but I can’t remember where…

@benjie
Copy link
Member

benjie commented Mar 5, 2017

I'm not aware of any significant performance issues with savepoints; I think they allocate a small amount of memory, but you'd probably be creating and releasing them rapidly enough that it won't be an issue.

Also with #342 the number of savepoints required would be significantly reduced.

@EyMaddis
Copy link
Contributor Author

EyMaddis commented Mar 6, 2017

Or you could just ignore "expected issues"?
Are you trying to rollback only a specific command and handle the rest?
I think that it is actually quite great that multiple mutations in a query are group into an transaction. That behavior would break if I understood the plan to use savepoints correctly, wouldn't it?

@calebmer
Copy link
Collaborator

calebmer commented Mar 6, 2017

We’d only use savepoints in queries in not in mutations. If a mutation fails the entire document should fail as well. We could enhance the client perhaps in https://github.com/calebmer/postgraphql/blob/master/src/postgraphql/withPostGraphQLContext.ts where we would also accept an isMutation option? (I don’t really like duplicating the information)

Alternatively we could enhance in a schema level resolve function which is implemented similarly to addSchemaLevelResolveFunction in graphql-tools.

@chris-guidry
Copy link

chris-guidry commented May 4, 2017

To avoid having currentPerson throw an error, I added a default value:

ALTER DATABASE myDatabase set jwt.claims.person_id to 0;

GraphQL returns something like this:

{ "data": { "currentPerson": null } }

@benjie
Copy link
Member

benjie commented May 5, 2017

Another solution which doesn't require setting defaults on the database (which can be a challenge*) is to use the PG 9.6 feature of adding a second parameter true to the current_setting(setting, missing_ok) call:

create function current_person_id() returns integer as $$
  select current_setting('jwt.claims.person_id', true)::integer;
$$ language sql stable;
create function current_person() returns person as $$
  select * from person where id = current_person_id();
$$ language sql stable;

* setting database defaults can be a challenge where you cannot control the database name (e.g. Heroku auto-provisioned database names); in these cases you can work around it with a DO statement:

do $$
begin
execute 'alter database ' || current_database() || ' set jwt.claims.person_id to 0';
end;
$$ language plpgsql;

@chadfurman
Copy link
Collaborator

👍 for missing_okay. For other exceptions I'd like to run many queries and get the results where I can see them / the queries work, and to get null and errors for fields that give problems. Same would be true for computed columns

@benjie
Copy link
Member

benjie commented Oct 2, 2017

I think GraphQL sees the fields in the root selection set of a GraphQL mutation document as independent, so I’ve wrapped each of them in savepoints. If you believe this is incorrect behaviour speak now! graphile/graphile-engine#59

@chadfurman
Copy link
Collaborator

@benjie this is closed now yes?

@benjie
Copy link
Member

benjie commented Oct 24, 2017

Well, if summary fails now then the entire root-level field will fail... I'm not sure if that's an acceptable solution. It kind of goes against GraphQL's "nullable by default, failures happen" mentality, so I'm not super happy with it, but equally I'm not sure how to balance error capture with performance.

@benjie
Copy link
Member

benjie commented Nov 29, 2017

Since I've had no pushback on this, I'm going to state that the current behaviour is the desired behaviour for v4 and that your stable functions should not throw. If they do then failures are expected. We can revisit this in v5 if it causes issues for people (or even in a point release, since going the other way wouldn't really be a breaking change).

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