Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Renamed the request `bindings` field to `parameters` across the HTTP request body, GraphBinary, and GraphSON, standardizing terminology for query parameters.
* Modified HTTP API to expect gremlin-lang strings for parameters and update all GLVs to send requests in new format.
* Added string parameter parsing to `GremlinServer` to prevent traversal injection and excessive nesting depths.
* Modified how the server honors per-request options embedded in a script via `with()`, extending `GremlinScriptChecker` to recognize `language`, `batchSize`, and `bulkResults` (in addition to `timeoutMillis` and `materializeProperties`) with uniform precedence.
* Modified all GLVs to detect unsupported types in `GremlinLang` and throw consistent error for that case.
* Extended `GValue` parameterization to all GLVs (`gremlin-python`, `gremlin-go`, `gremlin-javascript`, `gremlin-dotnet`), allowing parameters to be passed directly in traversals as in Java.
* Relaxed `GValue` name validation by removing the reserved leading underscore restriction.
Expand Down
12 changes: 11 additions & 1 deletion docs/src/dev/provider/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -1261,11 +1261,21 @@ the serializer specified by the `Content-Type` header. The following are the key
|parameters |A gremlin-lang string that encodes a map of key/value pairs used during query execution. Its usage depends on "language". For "gremlin-groovy", these are applied as script variable bindings. For "gremlin-lang", these are the query parameters. |String containing a gremlin-lang map literal |No
|g |The name of the graph traversal source to which the query applies. Default: "g" |String containing traversal source name |No
|language |The name of the ScriptEngine to use to parse the gremlin query. Default: "gremlin-lang" |String containing ScriptEngine name |No
|batchSize |The number of results to include in each response chunk, overriding the server configured `resultIterationBatchSize`. |Number between 1 and 2^31-1 |No
|materializeProperties |Whether to include all properties for results. One of "tokens" or "all". |String |No
|bulkResults |Whether the results should be bulked by the server (only applies to GraphBinary) |Boolean |No
|transactionId |A server-generated UUID that identifies an active transaction. Must be included in all non-begin requests within that transaction. Omit for non-transactional requests and for the initial begin request. |String |No
|=========================================================

Several of these options may alternatively be supplied inside the Gremlin query itself using the `with()` source step
(for example `g.with('timeoutMillis', 500).V()`) when submitting a script. The server recognizes the following options
this way: `timeoutMillis`, `materializeProperties`, `batchSize`, `bulkResults`, and `language`. When the same option is
supplied both in the query via `with()` and as a request field, the value in the query takes precedence. The full
precedence order is: value embedded in the query via `with()`, then the request field, then the server/connection
default. The `parameters`, `transactionId`, `g`, and `gremlin` keys cannot be set through `with()` and must be supplied
as request fields; `g` is excluded because of the complex interactions between different script engines and
transactions.

==== HTTP Response

When Gremlin Server receives that request, it will decode it given the "mime type", and execute it using the
Expand Down Expand Up @@ -2033,4 +2043,4 @@ in use with the Gremlin Console plugin host. Simply instantiate and return a `R

include::gremlin-semantics.asciidoc[]

include::policies.asciidoc[]
include::policies.asciidoc[]

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public class GremlinScriptCheckerTest {
public void shouldNotFindAResult() {
final GremlinScriptChecker.Result r = GremlinScriptChecker.parse("g.with(true).V().out('knows')");
assertEquals(Optional.empty(), r.getTimeout());
assertEquals(Optional.empty(), r.getRequestId());
assertEquals(Optional.empty(), r.getMaterializeProperties());
}

Expand All @@ -43,7 +42,6 @@ public void shouldReturnEmpty() {
final GremlinScriptChecker.Result r = GremlinScriptChecker.parse("");
assertSame(EMPTY_RESULT, r);
assertEquals(Optional.empty(), r.getTimeout());
assertEquals(Optional.empty(), r.getRequestId());
assertEquals(Optional.empty(), r.getMaterializeProperties());
}

Expand All @@ -64,24 +62,6 @@ public void shouldIdentifyTimeoutWithOddSpacing() {
getTimeout().get().longValue());
}

@Test
public void shouldIdentifyRequestIdWithOddSpacing() {
assertEquals("4F53FB59-CFC9-4984-B477-452073A352FD", GremlinScriptChecker.parse("g.with('requestId' , '4F53FB59-CFC9-4984-B477-452073A352FD').with(true).V().out('knows')").
getRequestId().get());
assertEquals("4F53FB59-CFC9-4984-B477-452073A352FD", GremlinScriptChecker.parse("g.with('requestId' , '4F53FB59-CFC9-4984-B477-452073A352FD').with(true).V().out('knows')").
getRequestId().get());
assertEquals("4F53FB59-CFC9-4984-B477-452073A352FD", GremlinScriptChecker.parse("g.with('requestId','4F53FB59-CFC9-4984-B477-452073A352FD').with(true).V().out('knows')").
getRequestId().get());
}

@Test
public void shouldIdentifyRequestIdWithEmbeddedQuote() {
assertEquals("te\"st", GremlinScriptChecker.parse("g.with('requestId','te\"st').with(true).V().out('knows')").
getRequestId().get());
assertEquals("te\\\"st", GremlinScriptChecker.parse("g.with('requestId', \"te\\\"st\").with(true).V().out('knows')").
getRequestId().get());
}

@Test
public void shouldIdentifyTimeoutWithLowerL() {
assertEquals(1000, GremlinScriptChecker.parse("g.with('timeoutMillis', 1000l).with(true).V().out('knows')").
Expand All @@ -106,28 +86,12 @@ public void shouldIdentifyTimeoutAsStringKeySingleQuoted() {
getTimeout().get().longValue());
}

@Test
public void shouldIdentifyRequestIdAsStringKeySingleQuoted() {
assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with('requestId', 'db024fca-ed15-4375-95de-4c6106aef895').with(true).V().out('knows')").
getRequestId().get());
assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(\"requestId\", 'db024fca-ed15-4375-95de-4c6106aef895').with(true).V().out('knows')").
getRequestId().get());
}

@Test
public void shouldIdentifyTimeoutAsStringKeyDoubleQuoted() {
assertEquals(1000, GremlinScriptChecker.parse("g.with(\"timeoutMillis\", 1000L).with(true).V().out('knows')").
getTimeout().get().longValue());
}

@Test
public void shouldIdentifyRequestIdAsStringKeyDoubleQuoted() {
assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(\"requestId\", \"db024fca-ed15-4375-95de-4c6106aef895\").with(true).V().out('knows')").
getRequestId().get());
assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(\"requestId\", \"db024fca-ed15-4375-95de-4c6106aef895\").with(true).V().out('knows')").
getRequestId().get());
}

@Test
public void shouldIdentifyTimeoutAsTokenKey() {
assertEquals(1000, GremlinScriptChecker.parse("g.with(Tokens.TIMEOUT_MILLIS, 1000L).with(true).V().out('knows')").
Expand All @@ -136,14 +100,6 @@ public void shouldIdentifyTimeoutAsTokenKey() {
getTimeout().get().longValue());
}

@Test
public void shouldIdentifyRequestIdAsTokenKey() {
assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(Tokens.REQUEST_ID, \"db024fca-ed15-4375-95de-4c6106aef895\").with(true).V().out('knows')").
getRequestId().get());
assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(Tokens.REQUEST_ID, \"db024fca-ed15-4375-95de-4c6106aef895\").with(true).V().out('knows')").
getRequestId().get());
}

@Test
public void shouldIdentifyTimeoutAsTokenKeyWithoutClassName() {
assertEquals(1000, GremlinScriptChecker.parse("g.with(TIMEOUT_MILLIS, 1000L).with(true).V().out('knows')").
Expand All @@ -152,14 +108,6 @@ public void shouldIdentifyTimeoutAsTokenKeyWithoutClassName() {
getTimeout().get().longValue());
}

@Test
public void shouldIdentifyRequestIdAsTokenKeyWithoutClassName() {
assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(REQUEST_ID, \"db024fca-ed15-4375-95de-4c6106aef895\").with(true).V().out('knows')").
getRequestId().get());
assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(REQUEST_ID, 'db024fca-ed15-4375-95de-4c6106aef895').with(true).V().out('knows')").
getRequestId().get());
}

@Test
public void shouldIdentifyMultipleTimeouts() {
assertEquals(6000, GremlinScriptChecker.parse("g.with('timeoutMillis', 1000L).with(true).V().out('knows');" +
Expand All @@ -176,22 +124,6 @@ public void shouldIdentifyMultipleTimeouts() {
getTimeout().get().longValue());
}

@Test
public void shouldIdentifyMultipleRequestIds() {
assertEquals("test9", GremlinScriptChecker.parse("g.with('requestId', 'test1').with(true).V().out('knows');" +
"g.with('requestId', 'test2').with(true).V().out('knows');\n" +
" //g.with('requestId', 'test3').with(true).V().out('knows');\n" +
" /* g.with('requestId', 'test4').with(true).V().out('knows');*/\n" +
" /* \n" +
"g.with('requestId', 'test5').with(true).V().out('knows'); \n" +
"*/ \n" +
" g.with('requestId', 'test6').with(true).V().out('knows');\n" +
" g.with(Tokens.REQUEST_ID, 'test7').with(true).V().out('knows');\n" +
" g.with(REQUEST_ID, 'test8').with(true).V().out('knows');\n" +
" g.with('requestId', 'test9').with(true).V().out('knows');").
getRequestId().get());
}

@Test
public void shouldIdentifyMaterializePropertiesSingleQuoted() {
assertEquals("all", GremlinScriptChecker.parse("g.with('materializeProperties', 'all').with(true).V().out('knows')").
Expand Down Expand Up @@ -239,9 +171,8 @@ public void shouldIdentifyMultipleMaterializeProperties() {
@Test
public void shouldFindAllResults() {
final GremlinScriptChecker.Result r = GremlinScriptChecker.parse(
"g.with('timeoutMillis', 1000).with(true).with(REQUEST_ID, \"db024fca-ed15-4375-95de-4c6106aef895\").with(\"materializeProperties\", 'all').V().out('knows')");
"g.with('timeoutMillis', 1000).with(true).with(\"materializeProperties\", 'all').V().out('knows')");
assertEquals(1000, r.getTimeout().get().longValue());
assertEquals("db024fca-ed15-4375-95de-4c6106aef895", r.getRequestId().get());
assertEquals("all", r.getMaterializeProperties().get());
}

Expand Down Expand Up @@ -449,4 +380,141 @@ public void shouldParseLong() {
" addE('bridge').from('o').to('r').iterate()").
getTimeout().get().longValue());
}

@Test
public void shouldIdentifyLanguageStringKey() {
assertEquals("gremlin-groovy", GremlinScriptChecker.parse("g.with('language', 'gremlin-groovy').V().out('knows')").
getLanguage().get());
assertEquals("gremlin-groovy", GremlinScriptChecker.parse("g.with(\"language\", \"gremlin-groovy\").V().out('knows')").
getLanguage().get());
}

@Test
public void shouldIdentifyLanguageAsTokenKey() {
assertEquals("gremlin-groovy", GremlinScriptChecker.parse("g.with(ARGS_LANGUAGE, 'gremlin-groovy').V().out('knows')").
getLanguage().get());
assertEquals("gremlin-groovy", GremlinScriptChecker.parse("g.with(Tokens.ARGS_LANGUAGE, 'gremlin-groovy').V().out('knows')").
getLanguage().get());
}

@Test
public void shouldIdentifyMultipleLanguagesUsingLast() {
assertEquals("gremlin-lang", GremlinScriptChecker.parse("g.with('language', 'gremlin-groovy').with('language', 'gremlin-lang').V()").
getLanguage().get());
}

@Test
public void shouldIdentifyBatchSizeStringKey() {
assertEquals("10", GremlinScriptChecker.parse("g.with('batchSize', 10).V().out('knows')").
getBatchSize().get());
assertEquals("10", GremlinScriptChecker.parse("g.with(\"batchSize\", 10).V().out('knows')").
getBatchSize().get());
}

@Test
public void shouldIdentifyBatchSizeAsTokenKey() {
assertEquals("25", GremlinScriptChecker.parse("g.with(ARGS_BATCH_SIZE, 25).V().out('knows')").
getBatchSize().get());
assertEquals("25", GremlinScriptChecker.parse("g.with(Tokens.ARGS_BATCH_SIZE, 25).V().out('knows')").
getBatchSize().get());
}

@Test
public void shouldIdentifyMultipleBatchSizesUsingLast() {
assertEquals("64", GremlinScriptChecker.parse("g.with('batchSize', 10).with('batchSize', 64).V()").
getBatchSize().get());
}

@Test
public void shouldCaptureBatchSizeAboveIntegerMaxUnparsed() {
// the checker captures the raw string without parsing, so a value above Integer.MAX_VALUE is returned as-is
// (rather than throwing) and the server can reject it as a bad request.
assertEquals("2147483648", GremlinScriptChecker.parse("g.with('batchSize', 2147483648).V()").
getBatchSize().get());
}

@Test
public void shouldCaptureNegativeBatchSizeUnparsed() {
assertEquals("-1", GremlinScriptChecker.parse("g.with('batchSize', -1).V()").
getBatchSize().get());
}

@Test
public void shouldIdentifyBulkResultsStringKey() {
assertEquals(Boolean.TRUE, GremlinScriptChecker.parse("g.with('bulkResults', true).V().out('knows')").
getBulkResults().get());
assertEquals(Boolean.FALSE, GremlinScriptChecker.parse("g.with(\"bulkResults\", false).V().out('knows')").
getBulkResults().get());
}

@Test
public void shouldIdentifyBulkResultsCaseInsensitive() {
assertEquals(Boolean.TRUE, GremlinScriptChecker.parse("g.with('bulkResults', TRUE).V().out('knows')").
getBulkResults().get());
assertEquals(Boolean.FALSE, GremlinScriptChecker.parse("g.with('bulkResults', False).V().out('knows')").
getBulkResults().get());
}

@Test
public void shouldIdentifyBulkResultsAsTokenKey() {
assertEquals(Boolean.TRUE, GremlinScriptChecker.parse("g.with(BULK_RESULTS, true).V().out('knows')").
getBulkResults().get());
assertEquals(Boolean.TRUE, GremlinScriptChecker.parse("g.with(Tokens.BULK_RESULTS, true).V().out('knows')").
getBulkResults().get());
}

@Test
public void shouldIdentifyMultipleBulkResultsUsingLast() {
assertEquals(Boolean.FALSE, GremlinScriptChecker.parse("g.with('bulkResults', true).with('bulkResults', false).V()").
getBulkResults().get());
}

@Test
public void shouldNotIdentifyInvalidBulkResults() {
assertEquals(Optional.empty(), GremlinScriptChecker.parse("g.with('bulkResults', maybe).V()").
getBulkResults());
}

@Test
public void shouldNotIdentifyBindingsFromScript() {
// parameters is intentionally NOT scraped from a script (field/header-only)
final GremlinScriptChecker.Result r = GremlinScriptChecker.parse("g.with('parameters', '[\"x\":1]').V(x)");
assertEquals(Optional.empty(), r.getTimeout());
assertEquals(Optional.empty(), r.getLanguage());
assertEquals(Optional.empty(), r.getBatchSize());
assertEquals(Optional.empty(), r.getBulkResults());
}

@Test
public void shouldNotIdentifyTransactionIdFromScript() {
// transactionId is intentionally NOT scraped from a script (field/header-only)
final GremlinScriptChecker.Result r = GremlinScriptChecker.parse("g.with('transactionId', 'abc').V()");
assertEquals(Optional.empty(), r.getTimeout());
assertEquals(Optional.empty(), r.getLanguage());
assertEquals(Optional.empty(), r.getBatchSize());
assertEquals(Optional.empty(), r.getBulkResults());
}

@Test
public void shouldNotIdentifyGFromScript() {
// g is intentionally NOT scraped from a script due to the complex interactions between different script
// engines and transactions - it must be supplied as a request field.
final GremlinScriptChecker.Result r = GremlinScriptChecker.parse("g.with('g', 'myGraph').V().out('knows')");
assertEquals(Optional.empty(), r.getTimeout());
assertEquals(Optional.empty(), r.getLanguage());
assertEquals(Optional.empty(), r.getBatchSize());
assertEquals(Optional.empty(), r.getBulkResults());
}

@Test
public void shouldFindAllFiveResults() {
final GremlinScriptChecker.Result r = GremlinScriptChecker.parse(
"g.with('timeoutMillis', 1000).with('materializeProperties', 'all').with('language', 'gremlin-groovy')." +
"with('batchSize', 32).with('bulkResults', true).V().out('knows')");
assertEquals(1000, r.getTimeout().get().longValue());
assertEquals("all", r.getMaterializeProperties().get());
assertEquals("gremlin-groovy", r.getLanguage().get());
assertEquals("32", r.getBatchSize().get());
assertEquals(Boolean.TRUE, r.getBulkResults().get());
}
}
Loading
Loading