You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by xi...@apache.org on 2023/09/18 23:54:18 UTC
[tinkerpop] branch master updated: add replace(), split(), and substring() string functions (#2232)
This is an automated email from the ASF dual-hosted git repository.
xiazcy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/master by this push:
new 88a75a795c add replace(), split(), and substring() string functions (#2232)
88a75a795c is described below
commit 88a75a795cc906a580e18e9884aa66e649218bac
Author: Yang Xia <55...@users.noreply.github.com>
AuthorDate: Mon Sep 18 16:54:11 2023 -0700
add replace(), split(), and substring() string functions (#2232)
---
CHANGELOG.asciidoc | 1 +
docs/src/dev/provider/gremlin-semantics.asciidoc | 83 ++++++++++++++++
docs/src/reference/the-traversal.asciidoc | 66 ++++++++++++-
docs/src/upgrade/release-3.7.x.asciidoc | 72 ++++++++++++++
.../grammar/DefaultGremlinBaseVisitor.java | 20 ++++
.../language/grammar/TraversalMethodVisitor.java | 34 +++++++
.../traversal/dsl/graph/GraphTraversal.java | 70 +++++++++++++-
.../gremlin/process/traversal/dsl/graph/__.java | 28 ++++++
.../process/traversal/step/map/ReplaceStep.java | 67 +++++++++++++
.../process/traversal/step/map/SplitStep.java | 67 +++++++++++++
.../process/traversal/step/map/SubstringStep.java | 107 +++++++++++++++++++++
.../process/traversal/util/BytecodeHelper.java | 6 ++
.../grammar/TraversalMethodVisitorTest.java | 20 ++++
.../traversal/step/map/ReplaceStepTest.java | 59 ++++++++++++
.../process/traversal/step/map/SplitStepTest.java | 70 ++++++++++++++
.../traversal/step/map/SubstringStepTest.java | 62 ++++++++++++
.../Process/Traversal/GraphTraversal.cs | 45 ++++++++-
.../src/Gremlin.Net/Process/Traversal/__.cs | 44 +++++++--
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 15 +++
gremlin-go/driver/anonymousTraversal.go | 21 ++++
gremlin-go/driver/cucumber/gremlin.go | 15 +++
gremlin-go/driver/graphTraversal.go | 18 ++++
.../lib/process/graph-traversal.js | 33 +++++++
.../gremlin-javascript/test/cucumber/gremlin.js | 15 +++
gremlin-language/src/main/antlr4/Gremlin.g4 | 16 +++
.../gremlin_python/process/graph_traversal.py | 42 ++++++++
gremlin-python/src/main/python/radish/gremlin.py | 15 +++
.../gremlin/test/features/map/Replace.feature | 69 +++++++++++++
.../gremlin/test/features/map/Split.feature | 85 ++++++++++++++++
.../gremlin/test/features/map/Substring.feature | 94 ++++++++++++++++++
30 files changed, 1345 insertions(+), 14 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 5732ebac92..b12d1337ec 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -28,6 +28,7 @@ This release also includes changes from <<release-3-6-5, 3.6.6>> and <<release-3
* Added the `asString()`, `length()`, `toLower()`, and `toUpper()` steps to perform `String` manipulations.
* Added Gherkin parsing support for specific string results using `str[]`.
* Added the `trim()`, `lTrim()`, `rTrim()`, and `reverse()` steps to perform `String` manipulations.
+* Added `replace()`, `split()` and `substring()` steps to perform `String` manipulations.
[[release-3-7.0]]
diff --git a/docs/src/dev/provider/gremlin-semantics.asciidoc b/docs/src/dev/provider/gremlin-semantics.asciidoc
index bc4e850474..d24339d6a7 100644
--- a/docs/src/dev/provider/gremlin-semantics.asciidoc
+++ b/docs/src/dev/provider/gremlin-semantics.asciidoc
@@ -941,6 +941,35 @@ resolve to a `Map`.
See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#mergev-step[reference]
+[[replace-step]]
+=== replace()
+
+*Description:* Returns a string with the specified characters in the original string replaced with the new characters.
+
+*Syntax:* `replace(String, String)`
+
+[width="100%",options="header"]
+|=========================================================
+|Start Step |Mid Step |Modulated |Domain |Range
+|N |Y |N |`String` |`String`
+|=========================================================
+
+*Arguments:*
+
+* `String` - The string character(s) in the original string to be replaced. Nullable, a null input will be a no-op and
+the original string will be returned
+* `String` - The string character(s) to replace with. Nullable, a null input will be a no-op and the original string
+will be returned
+
+Null values from the incoming traverser are not processed and remain as null when returned.
+
+*Exceptions*
+* If the incoming traverser is a non-String value then an `IllegalArgumentException` will be thrown.
+
+See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStep.java[source],
+link:https://tinkerpop.apache.org/docs/x.y.z/reference/#replace-step[reference]
+
+
=== reverse()
*Description:* Returns a reversed string of the incoming string traverser
@@ -992,6 +1021,60 @@ Null values from the incoming traverser are not processed and remain as null whe
See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#rTrim-step[reference]
+[[split-step]]
+=== split()
+
+*Description:* Returns a list of strings created by splitting the incoming string traverser around the matches of the given separator.
+
+*Syntax:* `split(String)`
+
+[width="100%",options="header"]
+|=========================================================
+|Start Step |Mid Step |Modulated |Domain |Range
+|N |Y |N |`String` |`List`
+|=========================================================
+
+*Arguments:*
+
+* `String` - The string character(s) used as delimiter to split the input string. Nullable, a null separator will split on
+whitespaces.
+
+Null values from the incoming traverser are not processed and remain as null when returned.
+
+*Exceptions*
+* If the incoming traverser is a non-String value then an `IllegalArgumentException` will be thrown.
+
+See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStep.java[source],
+link:https://tinkerpop.apache.org/docs/x.y.z/reference/#split-step[reference]
+
+[[substring-step]]
+=== substring()
+
+*Description:* Returns a substring of the incoming string traverser with a 0-based start index (inclusive) and length specified.
+
+*Syntax:* `substring(long, long)`
+
+[width="100%",options="header"]
+|=========================================================
+|Start Step |Mid Step |Modulated |Domain |Range
+|N |Y |N |`String` |`String`
+|=========================================================
+
+*Arguments:*
+
+* `int` - The start index, 0 based. If the start index is negative then it will begin at the specified index counted
+from the end of the string, or 0 if it exceeds the string length.
+* `int` - The number of characters to return. Optional, if it is not specific or if it exceeds the length of the string
+then all remaining characters will be returned. Length ≤ 0 will return the empty string.
+
+Null values from the incoming traverser are not processed and remain as null when returned.
+
+*Exceptions*
+* If the incoming traverser is a non-String value then an `IllegalArgumentException` will be thrown.
+
+See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStep.java[source],
+link:https://tinkerpop.apache.org/docs/x.y.z/reference/#substring-step[reference]
+
[[toLower-step]]
=== toLower()
diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc
index d585b8095d..d6ea05dcab 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -3625,6 +3625,26 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
link:++https://tinkerpop.apache.org/docs/x.y.z/recipes/#looping[`Looping Recipes`]
+[[replace-step]]
+=== Replace Step
+The `replace()`-step (*map*) returns a string with the specified characters in the original string replaced with the new
+characters. Any null arguments will be a no-op and the original string is returned. Null values from the incoming
+traversers are not processed and remain as null when returned. If the incoming traverser is a non-String value then
+an IllegalArgumentException will be thrown.
+
+[gremlin-groovy,modern]
+----
+g.inject('that', 'this', 'test', null).replace('h', 'j') <1>
+g.V().hasLabel("software").values("name").replace("p", "g") <2>
+g.inject('hello world').replace(null, 'j') <3>
+----
+<1> Replace "h" in the strings with "j".
+<2> Return software names with "p" replaced by "g".
+<3> Null inputs are ignored and the original string is returned.
+
+*Additional References*
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#replace-java.lang.String-java.lang.String-++[`replace(String,String)`]
+
[[reverse-step]]
=== Reverse Step
@@ -3651,7 +3671,6 @@ g.inject(" hello ", " world ", null).rTrim()
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#rTrim--++[`rTrim()`]
-
[[sack-step]]
=== Sack Step
@@ -4103,6 +4122,26 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#skip-org.apache.tinkerpop.gremlin.process.traversal.Scope-long-++[`skip(Scope,long)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`]
+[[split-step]]
+=== Split Step
+The `split()`-step (*map*) returns a list of strings created by splitting the incoming string traverser around the
+matches of the given separator. A null separator will split the string by whitespaces. Null values from the incoming
+traversers are not processed and remain as null when returned. If the incoming traverser is a non-String value then an
+IllegalArgumentException will be thrown.
+
+[gremlin-groovy,modern]
+----
+g.inject("that", "this", "test", null).split("h") <1>
+g.V().hasLabel("person").values("name").split("a") <2>
+g.inject("helloworld", "hello world", "hello world").split(null) <3>
+----
+<1> Split the strings by "h".
+<2> Split person names by "a".
+<3> Splitting by null will split by whitespaces.
+
+*Additional References*
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#split-java.lang.String-++[`split(String)`]
+
[[subgraph-step]]
=== Subgraph Step
@@ -4188,6 +4227,30 @@ List<Vertex> vertices = g.V(v).out().toList();
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#subgraph-java.lang.String-++[`subgraph(String)`]
+[[substring-step]]
+=== Substring Step
+The `substring()`-step (*map*) returns a substring with a 0-based start index (inclusive) index and length specified.
+If the start index is negative then it will begin at the specified index counted from the end of the string, or 0 if
+exceeding the string length.
+
+Length is optional, if it is not specific or if it exceeds the length of the string then all remaining characters will
+be returned. Length ≤ 0 will return the empty string. Null values are not processed and remain as null
+when returned. If the incoming traverser is a non-String value then an `IllegalArgumentException` will be thrown.
+
+[gremlin-groovy,modern]
+----
+g.inject("test", "hello world", null).substring(1, 8)
+g.inject("hello world").substring(-4) <1>
+g.inject("hello world").substring(2, 0) <2>
+g.V().hasLabel("software").values("name").substring(2)
+----
+<1> Negative start index, the first character is read by counting from the end of the string
+<2> Length of 0 specified will return the empty string
+
+*Additional References*
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#substring-int-++[`substring(int)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#substring-int-int-++[`substring(int,int)`]
+
[[sum-step]]
=== Sum Step
@@ -4410,7 +4473,6 @@ g.inject(" hello ", " world ", null).trim()
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#trim--++[`trim()`]
-
[[unfold-step]]
=== Unfold Step
diff --git a/docs/src/upgrade/release-3.7.x.asciidoc b/docs/src/upgrade/release-3.7.x.asciidoc
index 0ffdc7125d..d905efd784 100644
--- a/docs/src/upgrade/release-3.7.x.asciidoc
+++ b/docs/src/upgrade/release-3.7.x.asciidoc
@@ -141,6 +141,78 @@ See: link:https://tinkerpop.apache.org/docs/x.y.z/reference/#rTrim-step[rTrim()-
See: link:https://tinkerpop.apache.org/docs/x.y.z/reference/#reverse-step[reverse()-step]
See: link:https://issues.apache.org/jira/browse/TINKERPOP-2672[TINKERPOP-2672]
+===== String Steps replace(), split(), substring()
+
+The following example demonstrates the use of a closure to perform `replace()` and `split()` functions:
+[source,text]
+----
+gremlin> g.V().hasLabel("software").values("name").map{it.get().replace("p", "g")}
+==>log
+==>riggle
+gremlin> g.V().hasLabel("person").values("name").map{it.get().split("a")}
+==>[m, rko]
+==>[v, d, s]
+==>[josh]
+==>[peter]
+----
+With these additional steps this operation can be performed with standard Gremlin syntax:
+[source,text]
+----
+gremlin> g.V().hasLabel("software").values("name").replace("p", "g")
+==>log
+==>riggle
+gremlin> g.V().hasLabel("person").values("name").split("a")
+==>[m,rko]
+==>[v,d,s]
+==>[josh]
+==>[peter]
+----
+
+For `substring()`, the new Gremlin step follows the SQL standard, taking parameters start index and desired length of substring,
+instead of start and end indices like Java/Groovy, enabling certain operations that would be complex to achieve with closure:
+[source,text]
+----
+gremlin> g.V().hasLabel("person").values("name").map{it.get().substring(1,3)}
+==>ar
+==>ad
+==>os
+==>et
+gremlin> g.V().hasLabel("person").values("name").map{it.get().substring(1)}
+==>arko
+==>adas
+==>osh
+==>eter
+gremlin> g.V().hasLabel("person").values("name").map{it.get().substring(-2)}
+String index out of range: -2
+Type ':help' or ':h' for help.
+----
+
+The `substring()`-step will begin at the start index and return a substring with the length specified. Negative start
+indices are allowed and will begin at the specified index counted from the end of the string:
+[source,text]
+----
+gremlin> g.V().hasLabel("person").values("name").substring(1,3)
+==>ark
+==>ada
+==>osh
+==>ete
+gremlin> g.V().hasLabel("person").values("name").substring(1)
+==>arko
+==>adas
+==>osh
+==>eter
+gremlin> g.V().hasLabel("person").values("name").substring(-2)
+==>ko
+==>as
+==>sh
+==>er
+----
+
+See: link:https://tinkerpop.apache.org/docs/x.y.z/reference/#replace-step[replace()-step]
+See: link:https://tinkerpop.apache.org/docs/x.y.z/reference/#split-step[split()-step]
+See: link:https://tinkerpop.apache.org/docs/x.y.z/reference/#substring-step[substring()-step]
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2672[TINKERPOP-2672]
+
=== Upgrading for Providers
==== Graph System Providers
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
index ecdfb94b7e..857a80dc80 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
@@ -957,6 +957,26 @@ public class DefaultGremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> im
*/
@Override
public T visitTraversalMethod_reverse_Empty(final GremlinParser.TraversalMethod_reverse_EmptyContext ctx) { notImplemented(ctx); return null; }
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public T visitTraversalMethod_replace_String_String(final GremlinParser.TraversalMethod_replace_String_StringContext ctx) { notImplemented(ctx); return null; }
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public T visitTraversalMethod_split_String(final GremlinParser.TraversalMethod_split_StringContext ctx) { notImplemented(ctx); return null; }
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public T visitTraversalMethod_substring_int(final GremlinParser.TraversalMethod_substring_intContext ctx) { notImplemented(ctx); return null; }
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public T visitTraversalMethod_substring_int_int(final GremlinParser.TraversalMethod_substring_int_intContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
index 26fccd341e..937c14396c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
@@ -1805,6 +1805,40 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal>
return graphTraversal.reverse();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public GraphTraversal visitTraversalMethod_replace_String_String(final GremlinParser.TraversalMethod_replace_String_StringContext ctx) {
+ return graphTraversal.replace(antlr.argumentVisitor.parseString(ctx.stringNullableArgument(0)),
+ antlr.argumentVisitor.parseString(ctx.stringNullableArgument(1)));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public GraphTraversal visitTraversalMethod_split_String(final GremlinParser.TraversalMethod_split_StringContext ctx) {
+ return graphTraversal.split(antlr.argumentVisitor.parseString(ctx.stringNullableArgument()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public GraphTraversal visitTraversalMethod_substring_int(final GremlinParser.TraversalMethod_substring_intContext ctx) {
+ return graphTraversal.substring(antlr.argumentVisitor.parseNumber(ctx.integerArgument()).intValue());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public GraphTraversal visitTraversalMethod_substring_int_int(final GremlinParser.TraversalMethod_substring_int_intContext ctx) {
+ return graphTraversal.substring(antlr.argumentVisitor.parseNumber(ctx.integerArgument(0)).intValue(),
+ antlr.argumentVisitor.parseNumber(ctx.integerArgument(1)).intValue());
+ }
+
public GraphTraversal[] getNestedTraversalList(final GremlinParser.NestedTraversalListContext ctx) {
return ctx.nestedTraversalExpr().nestedTraversal()
.stream()
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
index d9225c5506..43cd726dea 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
@@ -123,13 +123,16 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertiesStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertyKeyStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertyMapStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertyValueStep;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.RTrimStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.RangeLocalStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.ReplaceStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.ReverseStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.RTrimStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SackStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SampleLocalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectOneStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.SplitStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.SubstringStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumLocalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.TailLocalStep;
@@ -1552,6 +1555,68 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
return this.asAdmin().addStep(new ReverseStep<>(this.asAdmin()));
}
+ /**
+ * Returns a string with the specified characters in the original string replaced with the new characters. Any null
+ * arguments will be a no-op and the original string is returned. Null values from incoming traversers are not
+ * processed and remain as null when returned. If the incoming traverser is a non-String value then an
+ * {@code IllegalArgumentException} will be thrown.
+ *
+ * @return the traversal with an appended {@link ReplaceStep}.
+ * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#replace-step" target="_blank">Reference Documentation - Replace Step</a>
+ * @since 3.7.1
+ */
+ public default GraphTraversal<S, String> replace(final String oldChar, final String newChar) {
+ this.asAdmin().getBytecode().addStep(Symbols.replace, oldChar, newChar);
+ return this.asAdmin().addStep(new ReplaceStep<>(this.asAdmin(), oldChar, newChar));
+ }
+
+ /**
+ * Returns a list of strings created by splitting the incoming string traverser around the matches of the given separator.
+ * A null separator will split the string by whitespaces. Null values from incoming traversers are not processed
+ * and remain as null when returned. If the incoming traverser is a non-String value then an
+ * {@code IllegalArgumentException} will be thrown.
+ *
+ * @return the traversal with an appended {@link SplitStep}.
+ * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#split-step" target="_blank">Reference Documentation - Split Step</a>
+ * @since 3.7.1
+ */
+ public default GraphTraversal<S, List<String>> split(final String separator) {
+ this.asAdmin().getBytecode().addStep(Symbols.split, separator);
+ return this.asAdmin().addStep(new SplitStep<>(this.asAdmin(), separator));
+ }
+
+ /**
+ * Returns a substring of the incoming string traverser with a 0-based start index (inclusive) specified,
+ * to the end of the string. If the start index is negative then it will begin at the specified index counted from the
+ * end of the string, or 0 if exceeding the string length. Null values are not processed and remain as null when returned.
+ * If the incoming traverser is a non-String value then an {@code IllegalArgumentException} will be thrown.
+ *
+ * @return the traversal with an appended {@link SubstringStep}.
+ * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#replace-step" target="_blank">Reference Documentation - Substring Step</a>
+ * @since 3.7.1
+ */
+ public default GraphTraversal<S, String> substring(final int startIndex) {
+ this.asAdmin().getBytecode().addStep(Symbols.substring, startIndex);
+ return this.asAdmin().addStep(new SubstringStep<>(this.asAdmin(), startIndex));
+ }
+
+ /**
+ * Returns a substring of the incoming string traverser with a 0-based start index (inclusive) index and length
+ * specified. If the start index is negative then it will begin at the specified index counted from the end
+ * of the string, or 0 if exceeding the string length. Length is optional, if it is not specific or if it exceeds
+ * the length of the string then all remaining characters will be returned. Length <= 0 will return the empty string.
+ * Null values are not processed and remain as null when returned. If the incoming traverser is a non-String value then an
+ * {@code IllegalArgumentException} will be thrown.
+ *
+ * @return the traversal with an appended {@link SubstringStep}.
+ * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#replace-step" target="_blank">Reference Documentation - Substring Step</a>
+ * @since 3.7.1
+ */
+ public default GraphTraversal<S, String> substring(final int startIndex, final int length) {
+ this.asAdmin().getBytecode().addStep(Symbols.substring, startIndex, length);
+ return this.asAdmin().addStep(new SubstringStep<>(this.asAdmin(), startIndex, length));
+ }
+
///////////////////// FILTER STEPS /////////////////////
/**
@@ -3623,6 +3688,9 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
public static final String lTrim = "lTrim";
public static final String rTrim = "rTrim";
public static final String reverse = "reverse";
+ public static final String replace = "replace";
+ public static final String substring = "substring";
+ public static final String split = "split";
public static final String timeLimit = "timeLimit";
public static final String simplePath = "simplePath";
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
index ae5a607bbf..60fc630261 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
@@ -660,6 +660,34 @@ public class __ {
return __.<A>start().reverse();
}
+ /**
+ * @see GraphTraversal#replace(String, String)
+ */
+ public static <A> GraphTraversal<A, String> replace(final String oldChar, final String newChar) {
+ return __.<A>start().replace(oldChar, newChar);
+ }
+
+ /**
+ * @see GraphTraversal#split(String)
+ */
+ public static <A> GraphTraversal<A, List<String>> split(final String separator) {
+ return __.<A>start().split(separator);
+ }
+
+ /**
+ * @see GraphTraversal#substring(int)
+ */
+ public static <A> GraphTraversal<A, String> substring(final int startIndex) {
+ return __.<A>start().substring(startIndex);
+ }
+
+ /**
+ * @see GraphTraversal#substring(int, int)
+ */
+ public static <A> GraphTraversal<A, String> substring(final int startIndex, final int length) {
+ return __.<A>start().substring(startIndex, length);
+ }
+
///////////////////// FILTER STEPS /////////////////////
/**
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStep.java
new file mode 100644
index 0000000000..32e1607685
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStep.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Reference implementation for substring step, a mid-traversal step which returns a string with the specified
+ * characters in the original string replaced with the new characters. Any null arguments will be a no-op and the
+ * original string is returned. Null values are not processed and remain as null when returned. If the incoming
+ * traverser is a non-String value then an {@code IllegalArgumentException} will be thrown.
+ *
+ * @author David Bechberger (http://bechberger.com)
+ * @author Yang Xia (http://github.com/xiazcy)
+ */
+public final class ReplaceStep<S> extends ScalarMapStep<S, String> implements TraversalParent {
+
+ private final String oldChar;
+ private final String newChar;
+
+ public ReplaceStep(final Traversal.Admin traversal, final String oldChar, final String newChar ) {
+ super(traversal);
+ this.oldChar=oldChar;
+ this.newChar=newChar;
+ }
+
+ @Override
+ protected String map(final Traverser.Admin<S> traverser) {
+ final S item = traverser.get();
+ // throws when incoming traverser isn't a string
+ if (null != item && !(item instanceof String)) {
+ throw new IllegalArgumentException(
+ String.format("The replace() step can only take string as argument, encountered %s.", item.getClass()));
+ }
+
+ // we will pass null values to next step
+ return null == item? null : StringUtils.replace(((String) item), this.oldChar, this.newChar);
+ }
+
+ @Override
+ public Set<TraverserRequirement> getRequirements() {
+ return Collections.singleton(TraverserRequirement.OBJECT);
+ }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStep.java
new file mode 100644
index 0000000000..b827d8e013
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStep.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Reference implementation for substring step, a mid-traversal step which returns a list of strings created by
+ * splitting the incoming string traverser around the matches of the given separator. A null separator will split the
+ * string by whitespaces. Null values from incoming traversers are not processed and remain as null when returned.
+ * If the incoming traverser is a non-String value then an {@code IllegalArgumentException} will be thrown.
+ *
+ * @author David Bechberger (http://bechberger.com)
+ * @author Yang Xia (http://github.com/xiazcy)
+ */
+public final class SplitStep<S> extends ScalarMapStep<S, List<String>> implements TraversalParent {
+
+ private final String separator;
+
+ public SplitStep(final Traversal.Admin traversal, final String separator) {
+ super(traversal);
+ this.separator = separator;
+ }
+
+ @Override
+ protected List<String> map(final Traverser.Admin<S> traverser) {
+ final S item = traverser.get();
+ // throws when incoming traverser isn't a string
+ if (null != item && !(item instanceof String)) {
+ throw new IllegalArgumentException(
+ String.format("The split() step can only take string as argument, encountered %s.", item.getClass()));
+ }
+
+ // we will pass null values to next step
+ return null == item? null : Arrays.asList(StringUtils.splitByWholeSeparator((String) item, this.separator));
+ }
+
+ @Override
+ public Set<TraverserRequirement> getRequirements() {
+ return Collections.singleton(TraverserRequirement.OBJECT);
+ }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStep.java
new file mode 100644
index 0000000000..e28f40557e
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStep.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Reference implementation for substring step, a mid-traversal step which returns a substring of the incoming string
+ * traverser with a 0-based start index (inclusive) and length specified. If the start index is negative then it will
+ * begin at the specified index counted from the end of the string, or 0 if exceeding the string length.
+ * Length is optional, if it is not specific or if it exceeds the length of the string then all remaining characters will
+ * be returned. Length <= 0 will return the empty string. Null values are not processed and remain as null when returned.
+ * If the incoming traverser is a non-String value then an {@code IllegalArgumentException} will be thrown.
+ *
+ * @author David Bechberger (http://bechberger.com)
+ * @author Yang Xia (http://github.com/xiazcy)
+ */
+public final class SubstringStep<S> extends ScalarMapStep<S, String> {
+
+ private final Integer start;
+ private final Integer length;
+
+ public SubstringStep(final Traversal.Admin traversal, final Integer startIndex, final Integer length) {
+ super(traversal);
+ this.start = startIndex;
+ this.length = length;
+ }
+
+ public SubstringStep(final Traversal.Admin traversal, final Integer startIndex) {
+ this(traversal, startIndex, null);
+ }
+
+ @Override
+ protected String map(final Traverser.Admin<S> traverser) {
+ final S item = traverser.get();
+ // throws when incoming traverser isn't a string
+ if (null != item && !(item instanceof String)) {
+ throw new IllegalArgumentException(
+ String.format("The substring() step can only take string as argument, encountered %s", item.getClass()));
+ }
+
+ // to preserve null items
+ final String strItem = (String) item;
+
+ if (null == strItem)
+ return null;
+
+ final int newStart = processStartIndex(strItem.length());
+ if (null == this.length)
+ return strItem.substring(newStart);
+
+ // length < 0 will return the empty string.
+ if (this.length <= 0)
+ return "";
+
+ // if length specified exceeds the string length it is assumed to be equal to the length, which returns all
+ // remaining characters in the string.
+ return strItem.substring(newStart, Math.min(this.length + newStart, strItem.length()));
+ }
+
+ @Override
+ public Set<TraverserRequirement> getRequirements() {
+ return Collections.singleton(TraverserRequirement.OBJECT);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + this.start.hashCode();
+ result = 31 * result + (null != this.length ? this.length.hashCode() : 0);
+ return result;
+ }
+
+ // Helper function to process the start index, if it is negative (which counts from end of string) it is converted
+ // to the positive index position or 0 when negative index exceeds the string length, if it is positive and exceeds
+ // the length of the string, it is assumed to equal to the length, which means an empty string will be returned.
+ private int processStartIndex(int strLen) {
+ if (this.start < 0) {
+ return Math.max(0, (strLen + this.start) % strLen);
+ } else {
+ return Math.min(this.start, strLen);
+ }
+ }
+
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/BytecodeHelper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/BytecodeHelper.java
index 484b10620f..8d539e1268 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/BytecodeHelper.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/BytecodeHelper.java
@@ -106,11 +106,14 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertyMapStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertyValueStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.RTrimStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.RangeLocalStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.ReplaceStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.ReverseStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SackStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SampleLocalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectOneStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.SplitStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.SubstringStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumLocalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.TailLocalStep;
@@ -214,6 +217,9 @@ public final class BytecodeHelper {
put(GraphTraversal.Symbols.lTrim, Collections.singletonList(LTrimStep.class));
put(GraphTraversal.Symbols.rTrim, Collections.singletonList(RTrimStep.class));
put(GraphTraversal.Symbols.reverse, Collections.singletonList(ReverseStep.class));
+ put(GraphTraversal.Symbols.replace, Collections.singletonList(ReplaceStep.class));
+ put(GraphTraversal.Symbols.substring, Collections.singletonList(SubstringStep.class));
+ put(GraphTraversal.Symbols.split, Collections.singletonList(SplitStep.class));
put(GraphTraversal.Symbols.group, Arrays.asList(GroupStep.class, GroupSideEffectStep.class));
put(GraphTraversal.Symbols.groupCount, Arrays.asList(GroupCountStep.class, GroupCountSideEffectStep.class));
put(GraphTraversal.Symbols.tree, Arrays.asList(TreeStep.class, TreeSideEffectStep.class));
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java
index 657dcc9d8a..f89d047c3b 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java
@@ -1163,4 +1163,24 @@ public class TraversalMethodVisitorTest {
public void shouldParseTraversalMethod_length_Empty() throws Exception {
compare(g.V().length(), eval("g.V().length()"));
}
+
+ @Test
+ public void shouldParseTraversalMethod_replace_string_string() throws Exception {
+ compare(g.V().replace("a", "b"), eval("g.V().replace('a', 'b')"));
+ }
+
+ @Test
+ public void shouldParseTraversalMethod_split_string() throws Exception {
+ compare(g.V().split("a"), eval("g.V().split('a')"));
+ }
+
+ @Test
+ public void shouldParseTraversalMethod_substring_long() throws Exception {
+ compare(g.V().substring(1), eval("g.V().substring(1)"));
+ }
+
+ @Test
+ public void shouldParseTraversalMethod_substring_long_long() throws Exception {
+ compare(g.V().substring(1, 3), eval("g.V().substring(1, 3)"));
+ }
}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStepTest.java
new file mode 100644
index 0000000000..032eddeec3
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStepTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.step.StepTest;
+import org.junit.Test;
+import org.mockito.internal.util.StringUtil;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class ReplaceStepTest extends StepTest {
+
+ @Override
+ protected List<Traversal> getTraversals() {
+ return Collections.singletonList(__.replace("a", "b"));
+ }
+
+ @Test
+ public void testReturnTypes() {
+ assertEquals("halo", __.__("hello").replace("el", "a").next());
+ assertEquals("world", __.__("world").replace("el", "a").next());
+
+ assertEquals("hello", __.__("hello").replace(null, "a").next());
+ assertEquals("hello", __.__("hello").replace("el", null).next());
+ assertEquals("hello", __.__("hello").replace(null, null).next());
+
+ assertArrayEquals(new String[]{"hell0", "w0rld", null, ""},
+ __.__("hello", "world", null, "").replace("o", "0").toList().toArray());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldThrowWithIncomingArrayList() { __.__(Arrays.asList("a", "b", "c")).replace("a", "b").next(); }
+
+}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStepTest.java
new file mode 100644
index 0000000000..dafa63c5d2
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStepTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.step.StepTest;
+import org.junit.Test;
+import org.mockito.internal.util.StringUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+public class SplitStepTest extends StepTest {
+
+ @Override
+ protected List<Traversal> getTraversals() {
+ return Collections.singletonList(__.split("a"));
+ }
+
+ @Test
+ public void testReturnTypes() {
+ assertArrayEquals(new String[]{"h", "llo"}, __.__("hello").split("e").next().toArray());
+ assertArrayEquals(new String[]{"ab", "bc"}, __.__("abc|abc").split("c|a").next().toArray());
+
+ assertArrayEquals(new String[]{"helloworld"}, __.__("helloworld").split(null).next().toArray());
+ assertArrayEquals(new String[]{"hello", "world"}, __.__("hello world").split(null).next().toArray());
+ assertArrayEquals(new String[]{"hello", "world"}, __.__("hello world").split(null).next().toArray());
+ assertArrayEquals(new String[]{"hello", "world"}, __.__("hello world").split("").next().toArray());
+ assertArrayEquals(new String[]{"hello", "world"}, __.__("hello world").split(" ").next().toArray());
+ assertArrayEquals(new String[]{"hello world"}, __.__("hello world").split(" ").next().toArray());
+
+ List<List<String>> resList = new ArrayList<>();
+ resList.add(Arrays.asList("t", "is"));
+ resList.add(Arrays.asList("t", "at"));
+ resList.add(Collections.singletonList("test"));
+ resList.add(null);
+ resList.add(Collections.emptyList());
+ assertArrayEquals(resList.toArray(),
+ __.__("this", "that", "test", null, "").split("h").toList().toArray());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldThrowWithIncomingArrayList() {
+ __.__(Arrays.asList("a1a", "b1b", "c1c")).split("1").next();
+ }
+
+}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStepTest.java
new file mode 100644
index 0000000000..3af4e06e13
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStepTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.step.StepTest;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+public class SubstringStepTest extends StepTest {
+
+ @Override
+ protected List<Traversal> getTraversals() {
+ return Arrays.asList(
+ __.substring(1),
+ __.substring(1, 2));
+ }
+
+ @Test
+ public void testReturnTypes() {
+ assertEquals("ello wor", __.__("hello world").substring(1, 8).next());
+ assertEquals("ello", __.__("hello").substring(1, 8).next());
+ assertEquals("world", __.__("hello world").substring(6).next());
+
+ assertEquals("world", __.__("world").substring(-10).next());
+ assertEquals("orld", __.__("world").substring(-4).next());
+ assertEquals("orl", __.__("world").substring(-4, 3).next());
+ assertEquals("", __.__("world").substring(1, -1).next());
+
+ assertArrayEquals(new String[]{"st", "llo worl", null, ""},
+ __.__("test", "hello world", null, "").substring(2, 8).toList().toArray());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldThrowWithIncomingArrayList() {
+ __.__(Arrays.asList("aa", "bb", "cc")).substring(1).next();
+ }
+
+}
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
index d4e811db39..9a0a955700 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
@@ -1178,7 +1178,7 @@ namespace Gremlin.Net.Process.Traversal
Bytecode.AddStep("loops", loopName);
return Wrap<TStart, int>(this);
}
-
+
/// <summary>
/// Adds the lTrim step to this <see cref="GraphTraversal{SType, EType}" />.
/// </summary>
@@ -1653,7 +1653,16 @@ namespace Gremlin.Net.Process.Traversal
Bytecode.AddStep("repeat", repeatTraversal);
return Wrap<TStart, TEnd>(this);
}
-
+
+ /// <summary>
+ /// Adds the replace step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<TStart, string?> Replace (string? oldChar, string? newChar)
+ {
+ Bytecode.AddStep("replace", oldChar, newChar);
+ return Wrap<TStart, string?>(this);
+ }
+
/// <summary>
/// Adds the reverse step to this <see cref="GraphTraversal{SType, EType}" />.
/// </summary>
@@ -1662,7 +1671,7 @@ namespace Gremlin.Net.Process.Traversal
Bytecode.AddStep("reverse");
return Wrap<TStart, string?>(this);
}
-
+
/// <summary>
/// Adds the rTrim step to this <see cref="GraphTraversal{SType, EType}" />.
/// </summary>
@@ -1835,6 +1844,15 @@ namespace Gremlin.Net.Process.Traversal
return Wrap<TStart, TNewEnd>(this);
}
+ /// <summary>
+ /// Adds the split step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<TStart, List<string>?> Split (string? splitChar)
+ {
+ Bytecode.AddStep("split", splitChar);
+ return Wrap<TStart, List<string>?>(this);
+ }
+
/// <summary>
/// Adds the store step to this <see cref="GraphTraversal{SType, EType}" />.
/// </summary>
@@ -1853,6 +1871,24 @@ namespace Gremlin.Net.Process.Traversal
return Wrap<TStart, Edge>(this);
}
+ /// <summary>
+ /// Adds the subgraph step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<TStart, string?> Substring (int startIndex)
+ {
+ Bytecode.AddStep("substring", startIndex);
+ return Wrap<TStart, string?>(this);
+ }
+
+ /// <summary>
+ /// Adds the subgraph step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<TStart, string?> Substring (int startIndex, int length)
+ {
+ Bytecode.AddStep("substring", startIndex, length);
+ return Wrap<TStart, string?>(this);
+ }
+
/// <summary>
/// Adds the sum step to this <see cref="GraphTraversal{SType, EType}" />.
/// </summary>
@@ -2022,8 +2058,7 @@ namespace Gremlin.Net.Process.Traversal
Bytecode.AddStep("tree", sideEffectKey);
return Wrap<TStart, TEnd>(this);
}
-
-
+
/// <summary>
/// Adds the trim step to this <see cref="GraphTraversal{SType, EType}" />.
/// </summary>
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
index bcc24865ae..c1dc67e409 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
@@ -828,13 +828,13 @@ namespace Gremlin.Net.Process.Traversal
{
return new GraphTraversal<object, int>().Loops(loopName);
}
-
+
/// <summary>
/// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the lTrim step to that traversal.
/// </summary>
public static GraphTraversal<object, string?> LTrim()
{
- return new GraphTraversal<object, string?>().LTrim();
+ return new GraphTraversal<object, string?>().LTrim();
}
/// <summary>
@@ -1136,13 +1136,21 @@ namespace Gremlin.Net.Process.Traversal
{
return new GraphTraversal<object, object>().Repeat(traversal);
}
-
+
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the replace step to that traversal.
+ /// </summary>
+ public static GraphTraversal<object, string?> Replace(string? oldChar, string? newChar)
+ {
+ return new GraphTraversal<object, string?>().Replace(oldChar, newChar);
+ }
+
/// <summary>
/// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the reverse step to that traversal.
/// </summary>
public static GraphTraversal<object, string?> Reverse()
{
- return new GraphTraversal<object, string?>().Reverse();
+ return new GraphTraversal<object, string?>().Reverse();
}
/// <summary>
@@ -1150,7 +1158,7 @@ namespace Gremlin.Net.Process.Traversal
/// </summary>
public static GraphTraversal<object, string?> RTrim()
{
- return new GraphTraversal<object, string?>().RTrim();
+ return new GraphTraversal<object, string?>().RTrim();
}
/// <summary>
@@ -1289,6 +1297,14 @@ namespace Gremlin.Net.Process.Traversal
return new GraphTraversal<object, E2>().Skip<E2>(skip);
}
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the split step to that traversal.
+ /// </summary>
+ public static GraphTraversal<object, List<string>?> Split(string? splitChar)
+ {
+ return new GraphTraversal<object, List<string>?>().Split(splitChar);
+ }
+
/// <summary>
/// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the store step to that traversal.
/// </summary>
@@ -1305,6 +1321,22 @@ namespace Gremlin.Net.Process.Traversal
return new GraphTraversal<object, Edge>().Subgraph(sideEffectKey);
}
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the substring step to that traversal.
+ /// </summary>
+ public static GraphTraversal<object, string?> Substring(int startIndex)
+ {
+ return new GraphTraversal<object, string?>().Substring(startIndex);
+ }
+
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the substring step to that traversal.
+ /// </summary>
+ public static GraphTraversal<object, string?> Substring(int startIndex, int length)
+ {
+ return new GraphTraversal<object, string?>().Substring(startIndex, length);
+ }
+
/// <summary>
/// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the sum step to that traversal.
/// </summary>
@@ -1434,7 +1466,7 @@ namespace Gremlin.Net.Process.Traversal
/// </summary>
public static GraphTraversal<object, string?> Trim()
{
- return new GraphTraversal<object, string?>().Trim();
+ return new GraphTraversal<object, string?>().Trim();
}
/// <summary>
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 60beb195cc..93b5a6a11c 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -847,6 +847,10 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_injectX__feature__X_rTrim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(" feature ").RTrim()}},
{"g_injectXListXa_bXX_rTrim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).RTrim()}},
{"g_V_valuesXnameX_rTrim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name"," marko ").Property("age",29).As("marko").AddV("person").Property("name"," vadas ").Property("age",27).As("vadas").AddV("software").Property("name"," lop").Property("lang","java").As("lop").AddV("person").Property("name","josh ").Property("age",32).As("josh").AddV("software").Property("name"," ripple ").Property("lang"," [...]
+ {"g_injectXthat_this_testX_replaceXh_jX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("that","this","test",null).Replace("h","j")}},
+ {"g_injectXListXa_bXcX_replaceXa_bX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Replace("a","b")}},
+ {"g_V_hasLabelXsoftwareX_valueXnameX_replaceXnull_iX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Values<object>("name").Replace(null,"g")}},
+ {"g_V_hasLabelXsoftwareX_valueXnameX_replaceXa_iX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Values<object>("name").Replace("p","g")}},
{"g_injectXfeature_test_nullX_reverse", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("feature","test one",null).Reverse()}},
{"g_injectXListXa_bXX_reverse", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Reverse()}},
{"g_V_valuesXnameX_reverse", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Reverse()}},
@@ -932,6 +936,17 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_V_hasXsong_name_MIGHT_AS_WELLX_shortestPath_targetXhasXsong_name_MAYBE_YOU_KNOW_HOW_I_FEELXX_edgesXoutEXfollowedByXX_distanceXweightX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("song","name","MIGHT AS WELL").ShortestPath().With("~tinkerpop.shortestPath.target",__.Has("song","name","MAYBE YOU KNOW HOW I FEEL")).With("~tinkerpop.shortestPath.edges",__.OutE("followedBy")).With("~tinkerpop.shortestPath.distance","weig [...]
{"g_V_hasXname_markoX_shortestPath_maxDistanceX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name","marko").ShortestPath().With("~tinkerpop.shortestPath.maxDistance",1)}},
{"g_V_hasXname_vadasX_shortestPath_distanceXweightX_maxDistanceX1_3X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name","vadas").ShortestPath().With("~tinkerpop.shortestPath.distance","weight").With("~tinkerpop.shortestPath.maxDistance",1.3)}},
+ {"g_injectXthat_this_testX_spiltXhX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("that","this","test",null).Split("h")}},
+ {"g_injectXhello_worldX_spiltXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject("hello world").Split(null)}},
+ {"g_injectXListXa_bXcX_splitXa_bX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Split("a")}},
+ {"g_V_hasLabelXpersonX_valueXnameX_splitXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").Values<object>("name").Split(null)}},
+ {"g_V_hasLabelXpersonX_valueXnameX_splitXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").Values<object>("name").Split("a")}},
+ {"g_injectXthat_this_testX_substringX1_8X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("test","hello world",null).Substring(1,8)}},
+ {"g_injectXListXa_bXcX_substringX1_1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Substring(1,1)}},
+ {"g_V_hasLabelXpersonX_valueXnameX_substringX2X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Values<object>("name").Substring(2)}},
+ {"g_V_hasLabelXsoftwareX_valueXnameX_substringX1_3X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Values<object>("name").Substring(1,3)}},
+ {"g_V_hasLabelXsoftwareX_valueXnameX_substringX1_0X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Values<object>("name").Substring(1,0)}},
+ {"g_V_hasLabelXpersonX_valueXnameX_substringXneg3X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").Values<object>("name").Substring(-3)}},
{"g_V_age_sum", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Sum<object>()}},
{"g_V_foo_sum", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("foo").Sum<object>()}},
{"g_V_age_fold_sumXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Fold().Sum<object>(Scope.Local)}},
diff --git a/gremlin-go/driver/anonymousTraversal.go b/gremlin-go/driver/anonymousTraversal.go
index 3b1da9cc6d..1336830411 100644
--- a/gremlin-go/driver/anonymousTraversal.go
+++ b/gremlin-go/driver/anonymousTraversal.go
@@ -219,6 +219,8 @@ type AnonymousTraversal interface {
Read(args ...interface{}) *GraphTraversal
// Repeat adds the repeat step to the GraphTraversal.
Repeat(args ...interface{}) *GraphTraversal
+ // Replace adds the replace step to the GraphTraversal.
+ Replace(args ...interface{}) *GraphTraversal
// Reverse adds the reverse step to the GraphTraversal.
Reverse(args ...interface{}) *GraphTraversal
// RTrim adds the rTrim step to the GraphTraversal.
@@ -237,10 +239,14 @@ type AnonymousTraversal interface {
SimplePath(args ...interface{}) *GraphTraversal
// Skip adds the skip step to the GraphTraversal.
Skip(args ...interface{}) *GraphTraversal
+ // Split adds the split step to the GraphTraversal.
+ Split(args ...interface{}) *GraphTraversal
// Store adds the store step to the GraphTraversal.
Store(args ...interface{}) *GraphTraversal
// Subgraph adds the subgraph step to the GraphTraversal.
Subgraph(args ...interface{}) *GraphTraversal
+ // Substring adds the substring step to the GraphTraversal.
+ Substring(args ...interface{}) *GraphTraversal
// Sum adds the sum step to the GraphTraversal.
Sum(args ...interface{}) *GraphTraversal
// Tail adds the tail step to the GraphTraversal.
@@ -733,6 +739,11 @@ func (anonymousTraversal *anonymousTraversal) Repeat(args ...interface{}) *Graph
return anonymousTraversal.graphTraversal().Repeat(args...)
}
+// Replace adds the replace step to the GraphTraversal.
+func (anonymousTraversal *anonymousTraversal) Replace(args ...interface{}) *GraphTraversal {
+ return anonymousTraversal.graphTraversal().Replace(args...)
+}
+
// Reverse adds the reverse step to the GraphTraversal.
func (anonymousTraversal *anonymousTraversal) Reverse(args ...interface{}) *GraphTraversal {
return anonymousTraversal.graphTraversal().Reverse(args...)
@@ -778,6 +789,11 @@ func (anonymousTraversal *anonymousTraversal) Skip(args ...interface{}) *GraphTr
return anonymousTraversal.graphTraversal().Skip(args...)
}
+// Split adds the split step to the GraphTraversal.
+func (anonymousTraversal *anonymousTraversal) Split(args ...interface{}) *GraphTraversal {
+ return anonymousTraversal.graphTraversal().Split(args...)
+}
+
// Store adds the store step to the GraphTraversal.
func (anonymousTraversal *anonymousTraversal) Store(args ...interface{}) *GraphTraversal {
return anonymousTraversal.graphTraversal().Store(args...)
@@ -788,6 +804,11 @@ func (anonymousTraversal *anonymousTraversal) Subgraph(args ...interface{}) *Gra
return anonymousTraversal.graphTraversal().Subgraph(args...)
}
+// Substring adds the subgraph step to the GraphTraversal.
+func (anonymousTraversal *anonymousTraversal) Substring(args ...interface{}) *GraphTraversal {
+ return anonymousTraversal.graphTraversal().Substring(args...)
+}
+
// Sum adds the sum step to the GraphTraversal.
func (anonymousTraversal *anonymousTraversal) Sum(args ...interface{}) *GraphTraversal {
return anonymousTraversal.graphTraversal().Sum(args...)
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 4faf9c525e..6fea78109f 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -817,6 +817,10 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
"g_injectX__feature__X_rTrim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(" feature ").RTrim()}},
"g_injectXListXa_bXX_rTrim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).RTrim()}},
"g_V_valuesXnameX_rTrim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", " marko ").Property("age", 29).As("marko").AddV("person").Property("name", " vadas ").Property("age", 27).As("vadas").AddV("software").Property("name", " lop").Property("lang", "java").As("lop").AddV("person").Property("name", "josh ").Property("age", 32).As("josh").AddV("software").Property("name", " ripple ").Proper [...]
+ "g_injectXthat_this_testX_replaceXh_jX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("that", "this", "test", nil).Replace("h", "j")}},
+ "g_injectXListXa_bXcX_replaceXa_bX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Replace("a", "b")}},
+ "g_V_hasLabelXsoftwareX_valueXnameX_replaceXnull_iX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Values("name").Replace(nil, "g")}},
+ "g_V_hasLabelXsoftwareX_valueXnameX_replaceXa_iX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Values("name").Replace("p", "g")}},
"g_injectXfeature_test_nullX_reverse": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("feature", "test one", nil).Reverse()}},
"g_injectXListXa_bXX_reverse": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Reverse()}},
"g_V_valuesXnameX_reverse": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Reverse()}},
@@ -902,6 +906,17 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
"g_V_hasXsong_name_MIGHT_AS_WELLX_shortestPath_targetXhasXsong_name_MAYBE_YOU_KNOW_HOW_I_FEELXX_edgesXoutEXfollowedByXX_distanceXweightX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("song", "name", "MIGHT AS WELL").ShortestPath().With("~tinkerpop.shortestPath.target", gremlingo.T__.Has("song", "name", "MAYBE YOU KNOW HOW I FEEL")).With("~tinkerpop.shortestPath.edges", gremlingo.T__.OutE("followedBy")).With("~tinkerpo [...]
"g_V_hasXname_markoX_shortestPath_maxDistanceX1X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", "marko").ShortestPath().With("~tinkerpop.shortestPath.maxDistance", 1)}},
"g_V_hasXname_vadasX_shortestPath_distanceXweightX_maxDistanceX1_3X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", "vadas").ShortestPath().With("~tinkerpop.shortestPath.distance", "weight").With("~tinkerpop.shortestPath.maxDistance", 1.3)}},
+ "g_injectXthat_this_testX_spiltXhX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("that", "this", "test", nil).Split("h")}},
+ "g_injectXhello_worldX_spiltXnullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("hello world").Split(nil)}},
+ "g_injectXListXa_bXcX_splitXa_bX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Split("a")}},
+ "g_V_hasLabelXpersonX_valueXnameX_splitXnullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("person").Values("name").Split(nil)}},
+ "g_V_hasLabelXpersonX_valueXnameX_splitXaX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("person").Values("name").Split("a")}},
+ "g_injectXthat_this_testX_substringX1_8X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("test", "hello world", nil).Substring(1, 8)}},
+ "g_injectXListXa_bXcX_substringX1_1X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Substring(1, 1)}},
+ "g_V_hasLabelXpersonX_valueXnameX_substringX2X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Values("name").Substring(2)}},
+ "g_V_hasLabelXsoftwareX_valueXnameX_substringX1_3X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Values("name").Substring(1, 3)}},
+ "g_V_hasLabelXsoftwareX_valueXnameX_substringX1_0X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Values("name").Substring(1, 0)}},
+ "g_V_hasLabelXpersonX_valueXnameX_substringXneg3X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("person").Values("name").Substring(-3)}},
"g_V_age_sum": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Sum()}},
"g_V_foo_sum": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("foo").Sum()}},
"g_V_age_fold_sumXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Fold().Sum(gremlingo.Scope.Local)}},
diff --git a/gremlin-go/driver/graphTraversal.go b/gremlin-go/driver/graphTraversal.go
index 74a5e0c944..133dd29ab4 100644
--- a/gremlin-go/driver/graphTraversal.go
+++ b/gremlin-go/driver/graphTraversal.go
@@ -577,6 +577,12 @@ func (g *GraphTraversal) Repeat(args ...interface{}) *GraphTraversal {
return g
}
+// Replace adds the replace step to the GraphTraversal.
+func (g *GraphTraversal) Replace(args ...interface{}) *GraphTraversal {
+ g.Bytecode.AddStep("replace", args...)
+ return g
+}
+
// Reverse adds the reverse step to the GraphTraversal.
func (g *GraphTraversal) Reverse(args ...interface{}) *GraphTraversal {
g.Bytecode.AddStep("reverse", args...)
@@ -632,6 +638,12 @@ func (g *GraphTraversal) Skip(args ...interface{}) *GraphTraversal {
return g
}
+// Split adds the split step to the GraphTraversal.
+func (g *GraphTraversal) Split(args ...interface{}) *GraphTraversal {
+ g.Bytecode.AddStep("split", args...)
+ return g
+}
+
// Store adds the store step to the GraphTraversal.
func (g *GraphTraversal) Store(args ...interface{}) *GraphTraversal {
g.Bytecode.AddStep("store", args...)
@@ -644,6 +656,12 @@ func (g *GraphTraversal) Subgraph(args ...interface{}) *GraphTraversal {
return g
}
+// Substring adds the substring step to the GraphTraversal.
+func (g *GraphTraversal) Substring(args ...interface{}) *GraphTraversal {
+ g.Bytecode.AddStep("substring", int32Args(args)...)
+ return g
+}
+
// Sum adds the sum step to the GraphTraversal.
func (g *GraphTraversal) Sum(args ...interface{}) *GraphTraversal {
g.Bytecode.AddStep("sum", args...)
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
index ddd3cf850c..a5aa1c6999 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
@@ -1232,6 +1232,16 @@ class GraphTraversal extends Traversal {
return this;
}
+ /**
+ * Graph traversal replace method.
+ * @param {...Object} args
+ * @returns {GraphTraversal}
+ */
+ replace(...args) {
+ this.bytecode.addStep('replace', args);
+ return this;
+ }
+
/**
* Graph traversal reverse method.
* @param {...Object} args
@@ -1322,6 +1332,16 @@ class GraphTraversal extends Traversal {
return this;
}
+ /**
+ * Graph traversal split method.
+ * @param {...Object} args
+ * @returns {GraphTraversal}
+ */
+ split(...args) {
+ this.bytecode.addStep('split', args);
+ return this;
+ }
+
/**
* Graph traversal store method.
* @param {...Object} args
@@ -1342,6 +1362,16 @@ class GraphTraversal extends Traversal {
return this;
}
+ /**
+ * Graph traversal subgraph method.
+ * @param {...Object} args
+ * @returns {GraphTraversal}
+ */
+ substring(...args) {
+ this.bytecode.addStep('substring', args);
+ return this;
+ }
+
/**
* Graph traversal sum method.
* @param {...Object} args
@@ -1669,6 +1699,7 @@ const statics = {
propertyMap: (...args) => callOnEmptyTraversal('propertyMap', args),
range: (...args) => callOnEmptyTraversal('range', args),
repeat: (...args) => callOnEmptyTraversal('repeat', args),
+ replace: (...args) => callOnEmptyTraversal('replace', args),
reverse: (...args) => callOnEmptyTraversal('reverse', args),
rTrim: (...args) => callOnEmptyTraversal('rTrim', args),
sack: (...args) => callOnEmptyTraversal('sack', args),
@@ -1677,8 +1708,10 @@ const statics = {
sideEffect: (...args) => callOnEmptyTraversal('sideEffect', args),
simplePath: (...args) => callOnEmptyTraversal('simplePath', args),
skip: (...args) => callOnEmptyTraversal('skip', args),
+ split: (...args) => callOnEmptyTraversal('split', args),
store: (...args) => callOnEmptyTraversal('store', args),
subgraph: (...args) => callOnEmptyTraversal('subgraph', args),
+ substring: (...args) => callOnEmptyTraversal('substring', args),
sum: (...args) => callOnEmptyTraversal('sum', args),
tail: (...args) => callOnEmptyTraversal('tail', args),
timeLimit: (...args) => callOnEmptyTraversal('timeLimit', args),
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
index 8c1f8d0d4e..2fa4f7d787 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
@@ -837,6 +837,10 @@ const gremlins = {
g_injectX__feature__X_rTrim: [function({g}) { return g.inject(" feature ").rTrim() }],
g_injectXListXa_bXX_rTrim: [function({g, xx1}) { return g.inject(xx1).rTrim() }],
g_V_valuesXnameX_rTrim: [function({g}) { return g.addV("person").property("name"," marko ").property("age",29).as("marko").addV("person").property("name"," vadas ").property("age",27).as("vadas").addV("software").property("name"," lop").property("lang","java").as("lop").addV("person").property("name","josh ").property("age",32).as("josh").addV("software").property("name"," ripple ").property("lang","java").as("ripple").addV("person").property("name","peter").property("age",35 [...]
+ g_injectXthat_this_testX_replaceXh_jX: [function({g}) { return g.inject("that","this","test",null).replace("h","j") }],
+ g_injectXListXa_bXcX_replaceXa_bX: [function({g, xx1}) { return g.inject(xx1).replace("a","b") }],
+ g_V_hasLabelXsoftwareX_valueXnameX_replaceXnull_iX: [function({g}) { return g.V().hasLabel("software").values("name").replace(null,"g") }],
+ g_V_hasLabelXsoftwareX_valueXnameX_replaceXa_iX: [function({g}) { return g.V().hasLabel("software").values("name").replace("p","g") }],
g_injectXfeature_test_nullX_reverse: [function({g}) { return g.inject("feature","test one",null).reverse() }],
g_injectXListXa_bXX_reverse: [function({g, xx1}) { return g.inject(xx1).reverse() }],
g_V_valuesXnameX_reverse: [function({g}) { return g.V().values("name").reverse() }],
@@ -922,6 +926,17 @@ const gremlins = {
g_V_hasXsong_name_MIGHT_AS_WELLX_shortestPath_targetXhasXsong_name_MAYBE_YOU_KNOW_HOW_I_FEELXX_edgesXoutEXfollowedByXX_distanceXweightX: [function({g}) { return g.V().has("song","name","MIGHT AS WELL").shortestPath().with_("~tinkerpop.shortestPath.target",__.has("song","name","MAYBE YOU KNOW HOW I FEEL")).with_("~tinkerpop.shortestPath.edges",__.outE("followedBy")).with_("~tinkerpop.shortestPath.distance","weight") }],
g_V_hasXname_markoX_shortestPath_maxDistanceX1X: [function({g}) { return g.V().has("name","marko").shortestPath().with_("~tinkerpop.shortestPath.maxDistance",1) }],
g_V_hasXname_vadasX_shortestPath_distanceXweightX_maxDistanceX1_3X: [function({g}) { return g.V().has("name","vadas").shortestPath().with_("~tinkerpop.shortestPath.distance","weight").with_("~tinkerpop.shortestPath.maxDistance",1.3) }],
+ g_injectXthat_this_testX_spiltXhX: [function({g}) { return g.inject("that","this","test",null).split("h") }],
+ g_injectXhello_worldX_spiltXnullX: [function({g}) { return g.inject("hello world").split(null) }],
+ g_injectXListXa_bXcX_splitXa_bX: [function({g, xx1}) { return g.inject(xx1).split("a") }],
+ g_V_hasLabelXpersonX_valueXnameX_splitXnullX: [function({g}) { return g.V().hasLabel("person").values("name").split(null) }],
+ g_V_hasLabelXpersonX_valueXnameX_splitXaX: [function({g}) { return g.V().hasLabel("person").values("name").split("a") }],
+ g_injectXthat_this_testX_substringX1_8X: [function({g}) { return g.inject("test","hello world",null).substring(1,8) }],
+ g_injectXListXa_bXcX_substringX1_1X: [function({g, xx1}) { return g.inject(xx1).substring(1,1) }],
+ g_V_hasLabelXpersonX_valueXnameX_substringX2X: [function({g}) { return g.V().hasLabel("software").values("name").substring(2) }],
+ g_V_hasLabelXsoftwareX_valueXnameX_substringX1_3X: [function({g}) { return g.V().hasLabel("software").values("name").substring(1,3) }],
+ g_V_hasLabelXsoftwareX_valueXnameX_substringX1_0X: [function({g}) { return g.V().hasLabel("software").values("name").substring(1,0) }],
+ g_V_hasLabelXpersonX_valueXnameX_substringXneg3X: [function({g}) { return g.V().hasLabel("person").values("name").substring(-3) }],
g_V_age_sum: [function({g}) { return g.V().values("age").sum() }],
g_V_foo_sum: [function({g}) { return g.V().values("foo").sum() }],
g_V_age_fold_sumXlocalX: [function({g}) { return g.V().values("age").fold().sum(Scope.local) }],
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4
index d5b67666c9..8df663209c 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -292,6 +292,9 @@ traversalMethod
| traversalMethod_lTrim
| traversalMethod_rTrim
| traversalMethod_reverse
+ | traversalMethod_replace
+ | traversalMethod_split
+ | traversalMethod_substring
;
traversalMethod_V
@@ -850,6 +853,19 @@ traversalMethod_reverse
: 'reverse' LPAREN RPAREN #traversalMethod_reverse_Empty
;
+traversalMethod_replace
+ : 'replace' LPAREN stringNullableArgument COMMA stringNullableArgument RPAREN #traversalMethod_replace_String_String
+ ;
+
+traversalMethod_split
+ : 'split' LPAREN stringNullableArgument RPAREN #traversalMethod_split_String
+ ;
+
+traversalMethod_substring
+ : 'substring' LPAREN integerArgument RPAREN #traversalMethod_substring_int
+ | 'substring' LPAREN integerArgument COMMA integerArgument RPAREN #traversalMethod_substring_int_int
+ ;
+
/*********************************************
ARGUMENT AND TERMINAL RULES
**********************************************/
diff --git a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
index d10b6dba85..82a96c49c1 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
@@ -777,6 +777,10 @@ class GraphTraversal(Traversal):
self.bytecode.add_step("repeat", *args)
return self
+ def replace(self, *args):
+ self.bytecode.add_step("replace", *args)
+ return self
+
def reverse(self, *args):
self.bytecode.add_step("reverse", *args)
return self
@@ -834,6 +838,10 @@ class GraphTraversal(Traversal):
self.bytecode.add_step("skip", *args)
return self
+ def split(self, *args):
+ self.bytecode.add_step("split", *args)
+ return self
+
def store(self, *args):
self.bytecode.add_step("store", *args)
return self
@@ -842,6 +850,10 @@ class GraphTraversal(Traversal):
self.bytecode.add_step("subgraph", *args)
return self
+ def substring(self, *args):
+ self.bytecode.add_step("substring", *args)
+ return self
+
def sum_(self, *args):
self.bytecode.add_step("sum", *args)
return self
@@ -1431,6 +1443,10 @@ class __(object, metaclass=MagicType):
def repeat(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).repeat(*args)
+ @classmethod
+ def replace(cls, *args):
+ return cls.graph_traversal(None, None, Bytecode()).replace(*args)
+
@classmethod
def reverse(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).reverse(*args)
@@ -1479,6 +1495,10 @@ class __(object, metaclass=MagicType):
def skip(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).skip(*args)
+ @classmethod
+ def split(cls, *args):
+ return cls.graph_traversal(None, None, Bytecode()).split(*args)
+
@classmethod
def store(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).store(*args)
@@ -1487,6 +1507,10 @@ class __(object, metaclass=MagicType):
def subgraph(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).subgraph(*args)
+ @classmethod
+ def substring(cls, *args):
+ return cls.graph_traversal(None, None, Bytecode()).substring(*args)
+
@classmethod
def sum_(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).sum_(*args)
@@ -2051,6 +2075,10 @@ def repeat(*args):
return __.repeat(*args)
+def replace(*args):
+ return __.replace(*args)
+
+
def reverse(*args):
return __.reverse(*args)
@@ -2091,6 +2119,10 @@ def skip(*args):
return __.skip(*args)
+def split(*args):
+ return __.split(*args)
+
+
def store(*args):
return __.store(*args)
@@ -2099,6 +2131,10 @@ def subgraph(*args):
return __.subgraph(*args)
+def substring(*args):
+ return __.substring(*args)
+
+
def sum_(*args):
return __.sum_(*args)
@@ -2375,6 +2411,8 @@ statics.add_static('range_', range_)
statics.add_static('repeat', repeat)
+statics.add_static('replace', replace)
+
statics.add_static('reverse', reverse)
statics.add_static('rTrim', rTrim)
@@ -2395,10 +2433,14 @@ statics.add_static('simple_path', simple_path)
statics.add_static('skip', skip)
+statics.add_static('split', split)
+
statics.add_static('store', store)
statics.add_static('subgraph', subgraph)
+statics.add_static('substring', substring)
+
statics.add_static('sum_', sum_)
statics.add_static('tail', tail)
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 14bd88d482..f73e75888f 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -818,6 +818,10 @@ world.gremlins = {
'g_injectX__feature__X_rTrim': [(lambda g:g.inject(' feature ').rTrim())],
'g_injectXListXa_bXX_rTrim': [(lambda g, xx1=None:g.inject(xx1).rTrim())],
'g_V_valuesXnameX_rTrim': [(lambda g:g.addV('person').property('name',' marko ').property('age',29).as_('marko').addV('person').property('name',' vadas ').property('age',27).as_('vadas').addV('software').property('name',' lop').property('lang','java').as_('lop').addV('person').property('name','josh ').property('age',32).as_('josh').addV('software').property('name',' ripple ').property('lang','java').as_('ripple').addV('person').property('name','peter').property('age',35).as_( [...]
+ 'g_injectXthat_this_testX_replaceXh_jX': [(lambda g:g.inject('that','this','test',None).replace('h','j'))],
+ 'g_injectXListXa_bXcX_replaceXa_bX': [(lambda g, xx1=None:g.inject(xx1).replace('a','b'))],
+ 'g_V_hasLabelXsoftwareX_valueXnameX_replaceXnull_iX': [(lambda g:g.V().hasLabel('software').name.replace(None,'g'))],
+ 'g_V_hasLabelXsoftwareX_valueXnameX_replaceXa_iX': [(lambda g:g.V().hasLabel('software').name.replace('p','g'))],
'g_injectXfeature_test_nullX_reverse': [(lambda g:g.inject('feature','test one',None).reverse())],
'g_injectXListXa_bXX_reverse': [(lambda g, xx1=None:g.inject(xx1).reverse())],
'g_V_valuesXnameX_reverse': [(lambda g:g.V().name.reverse())],
@@ -903,6 +907,17 @@ world.gremlins = {
'g_V_hasXsong_name_MIGHT_AS_WELLX_shortestPath_targetXhasXsong_name_MAYBE_YOU_KNOW_HOW_I_FEELXX_edgesXoutEXfollowedByXX_distanceXweightX': [(lambda g:g.V().has('song','name','MIGHT AS WELL').shortestPath().with_('~tinkerpop.shortestPath.target',__.has('song','name','MAYBE YOU KNOW HOW I FEEL')).with_('~tinkerpop.shortestPath.edges',__.outE('followedBy')).with_('~tinkerpop.shortestPath.distance','weight'))],
'g_V_hasXname_markoX_shortestPath_maxDistanceX1X': [(lambda g:g.V().has('name','marko').shortestPath().with_('~tinkerpop.shortestPath.maxDistance',1))],
'g_V_hasXname_vadasX_shortestPath_distanceXweightX_maxDistanceX1_3X': [(lambda g:g.V().has('name','vadas').shortestPath().with_('~tinkerpop.shortestPath.distance','weight').with_('~tinkerpop.shortestPath.maxDistance',float(1.3)))],
+ 'g_injectXthat_this_testX_spiltXhX': [(lambda g:g.inject('that','this','test',None).split('h'))],
+ 'g_injectXhello_worldX_spiltXnullX': [(lambda g:g.inject('hello world').split(None))],
+ 'g_injectXListXa_bXcX_splitXa_bX': [(lambda g, xx1=None:g.inject(xx1).split('a'))],
+ 'g_V_hasLabelXpersonX_valueXnameX_splitXnullX': [(lambda g:g.V().hasLabel('person').name.split(None))],
+ 'g_V_hasLabelXpersonX_valueXnameX_splitXaX': [(lambda g:g.V().hasLabel('person').name.split('a'))],
+ 'g_injectXthat_this_testX_substringX1_8X': [(lambda g:g.inject('test','hello world',None).substring(1,8))],
+ 'g_injectXListXa_bXcX_substringX1_1X': [(lambda g, xx1=None:g.inject(xx1).substring(1,1))],
+ 'g_V_hasLabelXpersonX_valueXnameX_substringX2X': [(lambda g:g.V().hasLabel('software').name.substring(2))],
+ 'g_V_hasLabelXsoftwareX_valueXnameX_substringX1_3X': [(lambda g:g.V().hasLabel('software').name.substring(1,3))],
+ 'g_V_hasLabelXsoftwareX_valueXnameX_substringX1_0X': [(lambda g:g.V().hasLabel('software').name.substring(1,0))],
+ 'g_V_hasLabelXpersonX_valueXnameX_substringXneg3X': [(lambda g:g.V().hasLabel('person').name.substring(-3))],
'g_V_age_sum': [(lambda g:g.V().age.sum_())],
'g_V_foo_sum': [(lambda g:g.V().foo.sum_())],
'g_V_age_fold_sumXlocalX': [(lambda g:g.V().age.fold().sum_(Scope.local))],
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Replace.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Replace.feature
new file mode 100644
index 0000000000..6c5abfb1a7
--- /dev/null
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Replace.feature
@@ -0,0 +1,69 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+@StepClassMap @StepReplace
+Feature: Step - replace()
+
+ @GraphComputerVerificationInjectionNotSupported
+ Scenario: g_injectXthat_this_testX_replaceXh_jX
+ Given the empty graph
+ And the traversal of
+ """
+ g.inject("that", "this", "test", null).replace("h", "j")
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | tjat |
+ | tjis |
+ | test |
+ | null |
+
+ @GraphComputerVerificationInjectionNotSupported
+ Scenario: g_injectXListXa_bXcX_replaceXa_bX
+ Given the empty graph
+ And using the parameter xx1 defined as "l[a,b]"
+ And the traversal of
+ """
+ g.inject(xx1).replace("a", "b")
+ """
+ When iterated to list
+ Then the traversal will raise an error with message containing text of "The replace() step can only take string as argument"
+
+ Scenario: g_V_hasLabelXsoftwareX_valueXnameX_replaceXnull_iX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasLabel("software").values("name").replace(null, "g")
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | lop |
+ | ripple |
+
+ Scenario: g_V_hasLabelXsoftwareX_valueXnameX_replaceXa_iX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasLabel("software").values("name").replace("p", "g")
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | log |
+ | riggle |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Split.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Split.feature
new file mode 100644
index 0000000000..70477f2fc5
--- /dev/null
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Split.feature
@@ -0,0 +1,85 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+@StepClassMap @StepSplit
+Feature: Step - split()
+
+ @GraphComputerVerificationInjectionNotSupported
+ Scenario: g_injectXthat_this_testX_spiltXhX
+ Given the empty graph
+ And the traversal of
+ """
+ g.inject("that", "this", "test", null).split("h")
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | l[t,at] |
+ | l[t,is] |
+ | l[test] |
+ | null |
+
+ @GraphComputerVerificationInjectionNotSupported
+ Scenario: g_injectXhello_worldX_spiltXnullX
+ Given the empty graph
+ And the traversal of
+ """
+ g.inject("hello world").split(null)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | l[hello,world] |
+
+ @GraphComputerVerificationInjectionNotSupported
+ Scenario: g_injectXListXa_bXcX_splitXa_bX
+ Given the empty graph
+ And using the parameter xx1 defined as "l[a,b]"
+ And the traversal of
+ """
+ g.inject(xx1).split("a")
+ """
+ When iterated to list
+ Then the traversal will raise an error with message containing text of "The split() step can only take string as argument"
+
+ Scenario: g_V_hasLabelXpersonX_valueXnameX_splitXnullX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasLabel("person").values("name").split(null)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | l[marko] |
+ | l[vadas] |
+ | l[josh] |
+ | l[peter] |
+
+ Scenario: g_V_hasLabelXpersonX_valueXnameX_splitXaX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasLabel("person").values("name").split("a")
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | l[m,rko] |
+ | l[v,d,s] |
+ | l[josh] |
+ | l[peter] |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Substring.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Substring.feature
new file mode 100644
index 0000000000..7aac365d6c
--- /dev/null
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Substring.feature
@@ -0,0 +1,94 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+@StepClassMap @StepSubstring
+Feature: Step - substring()
+
+ @GraphComputerVerificationInjectionNotSupported
+ Scenario: g_injectXthat_this_testX_substringX1_8X
+ Given the empty graph
+ And the traversal of
+ """
+ g.inject("test", "hello world", null).substring(1, 8)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | est |
+ | ello wor |
+ | null |
+
+ @GraphComputerVerificationInjectionNotSupported
+ Scenario: g_injectXListXa_bXcX_substringX1_1X
+ Given the empty graph
+ And using the parameter xx1 defined as "l[aa,bb]"
+ And the traversal of
+ """
+ g.inject(xx1).substring(1, 1)
+ """
+ When iterated to list
+ Then the traversal will raise an error with message containing text of "The substring() step can only take string as argument"
+
+ Scenario: g_V_hasLabelXpersonX_valueXnameX_substringX2X
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasLabel("software").values("name").substring(2)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | p |
+ | pple |
+
+ Scenario: g_V_hasLabelXsoftwareX_valueXnameX_substringX1_3X
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasLabel("software").values("name").substring(1, 3)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | op |
+ | ipp |
+
+ Scenario: g_V_hasLabelXsoftwareX_valueXnameX_substringX1_0X
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasLabel("software").values("name").substring(1, 0)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | str[] |
+ | str[] |
+
+ Scenario: g_V_hasLabelXpersonX_valueXnameX_substringXneg3X
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasLabel("person").values("name").substring(-3)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | rko |
+ | osh |
+ | das |
+ | ter |