-
Notifications
You must be signed in to change notification settings - Fork 856
Enable autocommit in GremlinServer #3423
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2242,10 +2242,14 @@ above with the use of the `maximumSize`. | |
| [[considering-transactions]] | ||
| ==== Considering Transactions | ||
|
|
||
| Non-transactional requests (those without a `transactionId`) behave as self-contained units of work where the graph's | ||
| own transaction semantics apply. Each traversal executes within its own transaction as managed by the graph | ||
| implementation itself. Transactional requests participate in a transaction opened via `g.tx().begin()`, where the | ||
| client explicitly controls the lifecycle through `g.tx().commit()` and `g.tx().rollback()`. | ||
| Non-transactional requests (those without a `transactionId`) behave as self-contained units of work. If the underlying | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the provider docs we wrote about "graphs that don't support transactions" and here we talk about "non-transactional". I wonder if we should avoid that language and just better introduce the notion of implicit and explicit transactions and talk only in those terms. In this way, all graphs have a transaction and they all support implicit (i.e. auto-commit, sessionless, non-transactional, etc. in the old parlance) and may support explicit (i.e. manual commit, session, transactional, etc. in the old parlance). In this way we don't drag back any of the older terminology. |
||
| graph supports transactions, the server automatically commits after successfully iterating the traversal and | ||
| automatically rolls back on any error. This means that a simple `g.addV('person')` will be persisted immediately upon | ||
| success without requiring an explicit `commit()`. Before processing each non-transactional request, the server also | ||
| rolls back any stale open transaction to prevent state leakage between requests. | ||
|
|
||
| Transactional requests participate in a transaction opened via `g.tx().begin()`, where the client explicitly controls | ||
| the lifecycle through `g.tx().commit()` and `g.tx().rollback()`. | ||
|
|
||
| IMPORTANT: Understand the transactional capabilities of the graph configured in Gremlin Server. For example, a basic | ||
| `TinkerGraph` does not support transactions. Use `TinkerTransactionGraph` or another transaction-capable graph | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,6 +111,8 @@ public Settings overrideSettings(final Settings settings) { | |
| settings.maxRequestContentLength = 31; | ||
| break; | ||
| case "should200OnPOSTTransactionalGraph": | ||
| case "shouldRollbackOnFailedMutatingTraversal": | ||
| case "shouldCommitMutatingTraversalWithEmptyResult": | ||
| useTinkerTransactionGraph(settings); | ||
| break; | ||
| case "should200OnPOSTTransactionalGraphInStrictMode": | ||
|
|
@@ -489,36 +491,97 @@ public void should200OnPOSTWithGremlinJsonEndcodedBodyForJavaTime() throws Excep | |
| } | ||
| } | ||
|
|
||
| /*@Test disabled for now as current implementation doesn't support implicit transactions. | ||
| @Test | ||
| public void should200OnPOSTTransactionalGraph() throws Exception { | ||
| final CloseableHttpClient httpclient = HttpClients.createDefault(); | ||
|
|
||
| // add a vertex without an explicit transaction - should be auto-committed | ||
| final HttpPost httppost = new HttpPost(TestClientFactory.createURLString()); | ||
| httppost.addHeader("Content-Type", "application/json"); | ||
| httppost.setEntity(new StringEntity("{\"gremlin\":\"graph.addVertex('name','stephen');g.V().count()\"}", Consts.UTF_8)); | ||
| httppost.setEntity(new StringEntity( | ||
| "{\"gremlin\":\"g.addV('person').property('name','stephen')\",\"g\":\"g\"}", Consts.UTF_8)); | ||
|
|
||
| try (final CloseableHttpResponse response = httpclient.execute(httppost)) { | ||
| assertEquals(200, response.getStatusLine().getStatusCode()); | ||
| assertEquals("application/json", response.getEntity().getContentType().getValue()); | ||
| final String json = EntityUtils.toString(response.getEntity()); | ||
| final JsonNode node = mapper.readTree(json); | ||
| assertEquals(1, node.get("result").get(TOKEN_DATA).get(GraphSONTokens.VALUEPROP).get(0).get(GraphSONTokens.VALUEPROP).intValue()); | ||
| EntityUtils.consume(response.getEntity()); | ||
| } | ||
|
|
||
| final HttpGet httpget = new HttpGet(TestClientFactory.createURLString("?gremlin=g.V().count()")); | ||
| httpget.addHeader("Accept", "application/json"); | ||
| // verify the vertex is visible on subsequent requests from potentially different threads | ||
| final HttpPost countPost = new HttpPost(TestClientFactory.createURLString()); | ||
| countPost.addHeader("Content-Type", "application/json"); | ||
| countPost.setEntity(new StringEntity("{\"gremlin\":\"g.V().count()\",\"g\":\"g\"}", Consts.UTF_8)); | ||
|
|
||
| // execute this a bunch of times so that there's a good chance a different thread on the server processes | ||
| // the request | ||
| for (int ix = 0; ix < 100; ix++) { | ||
| try (final CloseableHttpResponse response = httpclient.execute(httpget)) { | ||
| try (final CloseableHttpResponse response = httpclient.execute(countPost)) { | ||
| assertEquals(200, response.getStatusLine().getStatusCode()); | ||
| assertEquals("application/json", response.getEntity().getContentType().getValue()); | ||
| final String json = EntityUtils.toString(response.getEntity()); | ||
| final JsonNode node = mapper.readTree(json); | ||
| assertEquals(1, node.get("result").get(TOKEN_DATA).get(GraphSONTokens.VALUEPROP).get(0).get(GraphSONTokens.VALUEPROP).intValue()); | ||
| assertEquals(1, node.get("result").get(TOKEN_DATA) | ||
| .get(GraphSONTokens.VALUEPROP).get(0) | ||
| .get(GraphSONTokens.VALUEPROP).intValue()); | ||
| } | ||
| } | ||
| } */ | ||
| } | ||
|
|
||
| @Test | ||
| public void shouldRollbackOnFailedMutatingTraversal() throws Exception { | ||
| final CloseableHttpClient httpclient = HttpClients.createDefault(); | ||
|
|
||
| // submit a traversal that adds a vertex then fails - should be rolled back | ||
| final HttpPost httppost = new HttpPost(TestClientFactory.createURLString()); | ||
| httppost.addHeader("Content-Type", "application/json"); | ||
| httppost.setEntity(new StringEntity("{\"gremlin\":\"g.addV('person').fail()\",\"g\":\"g\"}", Consts.UTF_8)); | ||
|
|
||
| try (final CloseableHttpResponse response = httpclient.execute(httppost)) { | ||
| // the fail() error appears in the response body, not the HTTP status | ||
| } | ||
|
|
||
| // verify the vertex was not persisted | ||
| final HttpPost countPost = new HttpPost(TestClientFactory.createURLString()); | ||
| countPost.addHeader("Content-Type", "application/json"); | ||
| countPost.setEntity(new StringEntity( | ||
| "{\"gremlin\":\"g.V().hasLabel('person').count()\",\"g\":\"g\"}", Consts.UTF_8)); | ||
|
|
||
| try (final CloseableHttpResponse response = httpclient.execute(countPost)) { | ||
| assertEquals(200, response.getStatusLine().getStatusCode()); | ||
| final String json = EntityUtils.toString(response.getEntity()); | ||
| final JsonNode node = mapper.readTree(json); | ||
| assertEquals(0, node.get("result").get(TOKEN_DATA) | ||
| .get(GraphSONTokens.VALUEPROP).get(0) | ||
| .get(GraphSONTokens.VALUEPROP).intValue()); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void shouldCommitMutatingTraversalWithEmptyResult() throws Exception { | ||
| final CloseableHttpClient httpclient = HttpClients.createDefault(); | ||
|
|
||
| // g.addV() followed by iterate-style consumption returns no results but should still commit | ||
| final HttpPost httppost = new HttpPost(TestClientFactory.createURLString()); | ||
| httppost.addHeader("Content-Type", "application/json"); | ||
| httppost.setEntity(new StringEntity( | ||
| "{\"gremlin\":\"g.addV('person').property('name','p').iterate()\",\"g\":\"g\"}", Consts.UTF_8)); | ||
|
|
||
| try (final CloseableHttpResponse response = httpclient.execute(httppost)) { | ||
| assertEquals(200, response.getStatusLine().getStatusCode()); | ||
| EntityUtils.consume(response.getEntity()); | ||
| } | ||
|
|
||
| // verify the vertex was committed despite the empty result set | ||
| final HttpPost countPost2 = new HttpPost(TestClientFactory.createURLString()); | ||
| countPost2.addHeader("Content-Type", "application/json"); | ||
| countPost2.setEntity(new StringEntity( | ||
| "{\"gremlin\":\"g.V().hasLabel('person').count()\",\"g\":\"g\"}", Consts.UTF_8)); | ||
|
|
||
| try (final CloseableHttpResponse response = httpclient.execute(countPost2)) { | ||
| assertEquals(200, response.getStatusLine().getStatusCode()); | ||
| final String json = EntityUtils.toString(response.getEntity()); | ||
| final JsonNode node = mapper.readTree(json); | ||
| assertEquals(1, node.get("result").get(TOKEN_DATA) | ||
| .get(GraphSONTokens.VALUEPROP).get(0) | ||
| .get(GraphSONTokens.VALUEPROP).intValue()); | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there also a way to test stale open transactions? |
||
|
|
||
| @Test | ||
| public void should200OnPOSTTransactionalGraphInStrictMode() throws Exception { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are using "must", is there a consequence if they don't (Exception)? Can we document that?