You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2021/09/02 18:44:25 UTC
[tinkerpop] 01/01: TINKERPOP-2605 Fixed issues with null and
inject()
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch TINKERPOP-2605
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 8503708f229091c21044e9e3d2a7bd5cbd82c1a1
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Mon Aug 30 15:10:11 2021 -0400
TINKERPOP-2605 Fixed issues with null and inject()
---
CHANGELOG.asciidoc | 3 +
.../traversal/dsl/graph/GraphTraversal.java | 6 +-
.../traversal/dsl/graph/GraphTraversalSource.java | 6 +-
.../traversal/translator/DotNetTranslator.java | 9 +-
.../traversal/translator/GroovyTranslator.java | 27 ++++--
.../gremlin/structure/util/StringFactory.java | 6 +-
.../gremlin/util/iterator/ArrayIterator.java | 5 +-
.../traversal/translator/DotNetTranslatorTest.java | 14 +++-
.../translator/JavascriptTranslatorTest.java | 5 ++
.../gremlin/util/iterator/ArrayIteratorTest.java | 32 +++++++
gremlin-dotnet/build/generate.groovy | 98 ++++++++++++----------
.../Process/Traversal/GraphTraversal.cs | 15 +++-
.../Process/Traversal/GraphTraversalSource.cs | 16 +++-
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 6 ++
gremlin-javascript/build/generate.groovy | 52 +++++++-----
.../gremlin-javascript/test/cucumber/gremlin.js | 6 ++
gremlin-python/build/generate.groovy | 55 +++++++-----
gremlin-python/src/main/python/radish/gremlin.py | 6 ++
gremlin-test/features/sideEffect/Inject.feature | 75 ++++++++++++++++-
.../traversal/step/sideEffect/InjectTest.java | 88 +++++++++++++++++++
20 files changed, 419 insertions(+), 111 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 6557c8b..0155cfc 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -33,6 +33,9 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Improved handling of `null` when passed to `P` predicates.
* Handled `null` for mathematical reducing operations of `sum()`, `mean()`, `max()` and `min()`.
* Allowed `null` values in `Memory` for `GraphComputer`.
+* Fixed problems with `inject(null)` variations where `null` was the only value being submitted.
+* Fixed problem with `GroovyTranslator` and `inject(null,null)` which could be interpreted as the Groovy JDK extension `inject(Object,Closure).
+* Fixed error where certain variants of `inject()` with `null` might not properly construct a traversal in .NET.
* Refined `DotNetTranslator` to be more explicit with `null` arguments to ensure that the right overloads are called.
* Created `GremlinParser` to construct `Traversal` objects from `gremlin-language`.
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 63a394e..f52c30f 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
@@ -1284,8 +1284,10 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
* @since 3.0.0-incubating
*/
public default GraphTraversal<S, E> inject(final E... injections) {
- this.asAdmin().getBytecode().addStep(Symbols.inject, injections);
- return this.asAdmin().addStep(new InjectStep<>(this.asAdmin(), injections));
+ // a single null is [null]
+ final E[] s = null == injections ? (E[]) new Object[] { null } : injections;
+ this.asAdmin().getBytecode().addStep(Symbols.inject, s);
+ return this.asAdmin().addStep(new InjectStep<>(this.asAdmin(), s));
}
/**
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
index 64fff5d..b7471b1 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
@@ -352,10 +352,12 @@ public class GraphTraversalSource implements TraversalSource {
* Spawns a {@link GraphTraversal} starting it with arbitrary values.
*/
public <S> GraphTraversal<S, S> inject(S... starts) {
+ // a single null is [null]
+ final S[] s = null == starts ? (S[]) new Object[] { null } : starts;
final GraphTraversalSource clone = this.clone();
- clone.bytecode.addStep(GraphTraversal.Symbols.inject, starts);
+ clone.bytecode.addStep(GraphTraversal.Symbols.inject, s);
final GraphTraversal.Admin<S, S> traversal = new DefaultGraphTraversal<>(clone);
- return traversal.addStep(new InjectStep<S>(traversal, starts));
+ return traversal.addStep(new InjectStep<S>(traversal, s));
}
/**
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
index d4dcc35..d6ac2eb 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
@@ -304,18 +304,22 @@ public final class DotNetTranslator implements Translator.ScriptTranslator {
@Override
protected Script produceScript(final String traversalSource, final Bytecode o) {
script.append(traversalSource);
+ int instructionPosition = 0;
for (final Bytecode.Instruction instruction : o.getInstructions()) {
final String methodName = instruction.getOperator();
// perhaps too many if/then conditions for specifying generics. doesnt' seem like there is a clear
// way to refactor this more nicely though.
+ //
+ // inject() only has types when called with it when its used as a start step
if (0 == instruction.getArguments().length) {
- if (methodName.equals(GraphTraversal.Symbols.fold) && o.getSourceInstructions().size() + o.getStepInstructions().size() > 1)
+ if (methodName.equals(GraphTraversal.Symbols.fold) && o.getSourceInstructions().size() + o.getStepInstructions().size() > 1 ||
+ (methodName.equals(GraphTraversal.Symbols.inject) && instructionPosition > 0))
script.append(".").append(resolveSymbol(methodName).replace("<object>", "")).append("()");
else
script.append(".").append(resolveSymbol(methodName)).append("()");
} else {
if (methodsWithArgsNotNeedingGeneric.contains(methodName) ||
- (methodName.equals(GraphTraversal.Symbols.inject) && Arrays.stream(instruction.getArguments()).noneMatch(Objects::isNull)))
+ (methodName.equals(GraphTraversal.Symbols.inject) && (Arrays.stream(instruction.getArguments()).noneMatch(Objects::isNull) || instructionPosition > 0)))
script.append(".").append(resolveSymbol(methodName).replace("<object>", "").replace("<object,object>", "")).append("(");
else
script.append(".").append(resolveSymbol(methodName)).append("(");
@@ -359,6 +363,7 @@ public final class DotNetTranslator implements Translator.ScriptTranslator {
}
script.setCharAtEnd(')');
}
+ instructionPosition++;
}
return script;
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
index f11378b..c0a9f08 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
@@ -28,6 +28,9 @@ import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Script;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
import org.apache.tinkerpop.gremlin.process.traversal.Translator;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
@@ -311,21 +314,31 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
}
@Override
- protected Script produceScript(final String traversalSource, final Bytecode o) {
+ protected Script produceScript(final String traversalSource, final Bytecode bytecode) {
script.append(traversalSource);
- for (final Bytecode.Instruction instruction : o.getInstructions()) {
+ for (final Bytecode.Instruction instruction : bytecode.getInstructions()) {
final String methodName = instruction.getOperator();
if (0 == instruction.getArguments().length) {
script.append(".").append(methodName).append("()");
} else {
script.append(".").append(methodName).append("(");
- final Iterator<Object> itty = Arrays.stream(instruction.getArguments()).iterator();
- while(itty.hasNext()) {
- convertToScript(itty.next());
- if (itty.hasNext()) script.append(",");
+ // special case inject(null, null) or else groovy might guess the JDK collection extension form
+ if (methodName.equals(GraphTraversal.Symbols.inject)) {
+ final Iterator<Object> itty = Arrays.stream(instruction.getArguments()).iterator();
+ while (itty.hasNext()) {
+ final Object o = itty.next();
+ if (null == o) script.append("(Object)");
+ convertToScript(o);
+ if (itty.hasNext()) script.append(",");
+ }
+ } else {
+ final Iterator<Object> itty = Arrays.stream(instruction.getArguments()).iterator();
+ while (itty.hasNext()) {
+ convertToScript(itty.next());
+ if (itty.hasNext()) script.append(",");
+ }
}
-
script.append(")");
}
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/StringFactory.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/StringFactory.java
index dfab163..49206cf 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/StringFactory.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/StringFactory.java
@@ -218,7 +218,6 @@ public final class StringFactory {
public static String stepString(final Step<?, ?> step, final Object... arguments) {
final StringBuilder builder = new StringBuilder(step.getClass().getSimpleName());
final List<String> strings = Stream.of(arguments)
- .filter(o -> null != o)
.filter(o -> {
if (o instanceof TraversalRing)
return !((TraversalRing) o).isEmpty();
@@ -227,10 +226,10 @@ public final class StringFactory {
else if (o instanceof Map)
return !((Map) o).isEmpty();
else
- return !o.toString().isEmpty();
+ return !Objects.toString(o).isEmpty();
})
.map(o -> {
- final String string = o.toString();
+ final String string = Objects.toString(o);
return hasLambda(string) ? "lambda" : string;
}).collect(Collectors.toList());
if (!strings.isEmpty()) {
@@ -239,7 +238,6 @@ public final class StringFactory {
builder.append(')');
}
if (!step.getLabels().isEmpty()) builder.append('@').append(step.getLabels());
- //builder.append("^").append(step.getId());
return builder.toString();
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIterator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIterator.java
index 7d5f959..3d87f34 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIterator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIterator.java
@@ -23,6 +23,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementExce
import java.io.Serializable;
import java.util.Arrays;
import java.util.Iterator;
+import java.util.Objects;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
@@ -33,7 +34,7 @@ public final class ArrayIterator<T> implements Iterator<T>, Serializable {
private int current = 0;
public ArrayIterator(final T[] array) {
- this.array = array;
+ this.array = Objects.requireNonNull(array);
}
@Override
@@ -53,6 +54,6 @@ public final class ArrayIterator<T> implements Iterator<T>, Serializable {
@Override
public String toString() {
- return Arrays.asList(array).toString();
+ return array.length == 1 && null == array[0] ? "[null]" : Arrays.asList(array).toString();
}
}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java
index 3e5654c..1b0fd71 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java
@@ -88,8 +88,20 @@ public class DotNetTranslatorTest {
@Test
public void shouldTranslateInject() {
- final String script = translator.translate(g.inject(10,20,null,20,10,10).asAdmin().getBytecode()).getScript();
+ String script = translator.translate(g.inject(10,20,null,20,10,10).asAdmin().getBytecode()).getScript();
assertEquals("g.Inject<object>(10,20,null,20,10,10)", script);
+ script = translator.translate(g.inject().asAdmin().getBytecode()).getScript();
+ assertEquals("g.Inject<object>()", script);
+ script = translator.translate(g.inject((Object) null).asAdmin().getBytecode()).getScript();
+ assertEquals("g.Inject<object>(null)", script);
+ script = translator.translate(g.inject(null, null).asAdmin().getBytecode()).getScript();
+ assertEquals("g.Inject<object>(null,null)", script);
+ script = translator.translate(g.V().values("age").inject().asAdmin().getBytecode()).getScript();
+ assertEquals("g.V().Values<object>(\"age\").Inject()", script);
+ script = translator.translate(g.V().values("age").inject(null).asAdmin().getBytecode()).getScript();
+ assertEquals("g.V().Values<object>(\"age\").Inject(null)", script);
+ script = translator.translate(g.V().values("age").inject(null, null).asAdmin().getBytecode()).getScript();
+ assertEquals("g.V().Values<object>(\"age\").Inject(null,null)", script);
}
@Test
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java
index 5990418..8ae9f4c 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java
@@ -115,6 +115,11 @@ public class JavascriptTranslatorTest {
}
@Test
+ public void shouldHaveNull() {
+ translator.translate(g.inject(null, null).asAdmin().getBytecode()).getScript();
+ }
+
+ @Test
public void shouldEscapeStrings() {
final String script = translator.translate(g.addV("customer")
.property("customer_id", 501L)
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java
index 2293277..d2027e3 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java
@@ -78,4 +78,36 @@ public class ArrayIteratorTest {
final Iterator<String> itty = new ArrayIterator<>(arr);
assertEquals("[test1, test2, test3]", itty.toString());
}
+
+ @Test(expected = NullPointerException.class)
+ public void shouldNotAllowNullConstruction() {
+ new ArrayIterator<>(null);
+ }
+
+ @Test
+ public void shouldAllowNullInArray() {
+ final String[] arr = new String[3];
+ arr[0] = "test1";
+ arr[1] = null;
+ arr[2] = "test3";
+
+ final Iterator<String> itty = new ArrayIterator<>(arr);
+ assertEquals("[test1, null, test3]", itty.toString());
+ }
+
+ @Test
+ public void shouldAllowSingleNullInArray() {
+ final String[] arr = new String[1];
+
+ final Iterator<String> itty = new ArrayIterator<>(arr);
+ assertEquals("[null]", itty.toString());
+ }
+
+ @Test
+ public void shouldAllowZeroNullInArray() {
+ final String[] arr = new String[0];
+
+ final Iterator<String> itty = new ArrayIterator<>(arr);
+ assertEquals("[]", itty.toString());
+ }
}
diff --git a/gremlin-dotnet/build/generate.groovy b/gremlin-dotnet/build/generate.groovy
index ad7a160..5fa64fa 100644
--- a/gremlin-dotnet/build/generate.groovy
+++ b/gremlin-dotnet/build/generate.groovy
@@ -104,52 +104,64 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
' new Dictionary<string, List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>>\n' +
' {')
+ // Groovy can't process certain null oriented calls because it gets confused with the right overload to call
+ // at runtime. using this approach for now as these are the only such situations encountered so far. a better
+ // solution may become necessary as testing of nulls expands.
+ def staticTranslate = [
+ g_injectXnull_nullX: " {\"g_injectXnull_nullX\", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,null)}}, ",
+ g_VX1X_valuesXageX_injectXnull_nullX: " {\"g_VX1X_valuesXageX_injectXnull_nullX\", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p[\"xx1\"]).Values<object>(\"age\").Inject(null,null)}}, "
+ ]
+
gremlins.each { k,v ->
- writer.write(" {\"")
- writer.write(k)
- writer.write("\", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {")
- def collected = v.collect{
- def t = gremlinGroovyScriptEngine.eval(it, bindings)
- [t, t.bytecode.bindings.keySet()]
- }
+ if (staticTranslate.containsKey(k)) {
+ writer.writeLine(staticTranslate[k])
+ } else {
+ writer.write(" {\"")
+ writer.write(k)
+ writer.write("\", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {")
+ def collected = v.collect {
+ def t = gremlinGroovyScriptEngine.eval(it, bindings)
+ [t, t.bytecode.bindings.keySet()]
+ }
- def gremlinItty = collected.iterator()
- while (gremlinItty.hasNext()) {
- def t = gremlinItty.next()[0]
- writer.write("(g,p) =>")
- writer.write(translator.translate(t.bytecode).script.
- replace("xx1", "p[\"xx1\"]").
- replace("xx2", "p[\"xx2\"]").
- replace("xx3", "p[\"xx3\"]").
- replace("v1", "(Vertex) p[\"v1\"]").
- replace("v2", "(Vertex) p[\"v2\"]").
- replace("v3", "(Vertex) p[\"v3\"]").
- replace("v4", "(Vertex) p[\"v4\"]").
- replace("v5", "(Vertex) p[\"v5\"]").
- replace("v6", "(Vertex) p[\"v6\"]").
- replace("vid1", "p[\"vid1\"]").
- replace("vid2", "p[\"vid2\"]").
- replace("vid3", "p[\"vid3\"]").
- replace("vid4", "p[\"vid4\"]").
- replace("vid5", "p[\"vid5\"]").
- replace("vid6", "p[\"vid6\"]").
- replace("e7", "p[\"e7\"]").
- replace("e10", "p[\"e10\"]").
- replace("e11", "p[\"e11\"]").
- replace("eid7", "p[\"eid7\"]").
- replace("eid10", "p[\"eid10\"]").
- replace("eid11", "p[\"eid11\"]").
- replace("l1", "(IFunction) p[\"l1\"]").
- replace("l2", "(IFunction) p[\"l2\"]").
- replace("pred1", "(IPredicate) p[\"pred1\"]").
- replace("c1", "(IComparator) p[\"c1\"]").
- replace("c2", "(IComparator) p[\"c2\"]"))
- if (gremlinItty.hasNext())
- writer.write(', ')
- else
- writer.write("}")
+ def gremlinItty = collected.iterator()
+ while (gremlinItty.hasNext()) {
+ def t = gremlinItty.next()[0]
+ writer.write("(g,p) =>")
+ writer.write(translator.translate(t.bytecode).script.
+ replace("xx1", "p[\"xx1\"]").
+ replace("xx2", "p[\"xx2\"]").
+ replace("xx3", "p[\"xx3\"]").
+ replace("v1", "(Vertex) p[\"v1\"]").
+ replace("v2", "(Vertex) p[\"v2\"]").
+ replace("v3", "(Vertex) p[\"v3\"]").
+ replace("v4", "(Vertex) p[\"v4\"]").
+ replace("v5", "(Vertex) p[\"v5\"]").
+ replace("v6", "(Vertex) p[\"v6\"]").
+ replace("vid1", "p[\"vid1\"]").
+ replace("vid2", "p[\"vid2\"]").
+ replace("vid3", "p[\"vid3\"]").
+ replace("vid4", "p[\"vid4\"]").
+ replace("vid5", "p[\"vid5\"]").
+ replace("vid6", "p[\"vid6\"]").
+ replace("e7", "p[\"e7\"]").
+ replace("e10", "p[\"e10\"]").
+ replace("e11", "p[\"e11\"]").
+ replace("eid7", "p[\"eid7\"]").
+ replace("eid10", "p[\"eid10\"]").
+ replace("eid11", "p[\"eid11\"]").
+ replace("l1", "(IFunction) p[\"l1\"]").
+ replace("l2", "(IFunction) p[\"l2\"]").
+ replace("pred1", "(IPredicate) p[\"pred1\"]").
+ replace("c1", "(IComparator) p[\"c1\"]").
+ replace("c2", "(IComparator) p[\"c2\"]"))
+ if (gremlinItty.hasNext())
+ writer.write(', ')
+ else
+ writer.write("}")
+ }
+ writer.writeLine('}, ')
}
- writer.writeLine('}, ')
}
writer.writeLine(' };\n')
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
index 8f6df56..2bc4de8 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
@@ -885,9 +885,18 @@ namespace Gremlin.Net.Process.Traversal
/// </summary>
public GraphTraversal<S, E> Inject (params E[] injections)
{
- var args = new List<object>(0 + injections.Length) {};
- args.AddRange(injections.Cast<object>());
- Bytecode.AddStep("inject", args.ToArray());
+ // null injections is treated as g.inject(null) meaning inject a single null traverser
+ if (injections == null)
+ {
+ Bytecode.AddStep("inject", new object[1] { null });
+ }
+ else
+ {
+ var args = new List<object>(0 + injections.Length) {};
+ args.AddRange(injections.Cast<object>());
+ Bytecode.AddStep("inject", args.ToArray());
+ }
+
return Wrap<S, E>(this);
}
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
index db4c521..c74e826 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
@@ -351,9 +351,19 @@ namespace Gremlin.Net.Process.Traversal
public GraphTraversal<S, S> Inject<S>(params S[] starts)
{
var traversal = new GraphTraversal<S, S>(TraversalStrategies, new Bytecode(Bytecode));
- var args = new List<object>(0 + starts.Length) {};
- args.AddRange(starts.Cast<object>());
- traversal.Bytecode.AddStep("inject", args.ToArray());
+
+ // null starts is treated as g.inject(null) meaning inject a single null traverser
+ if (starts == null)
+ {
+ traversal.Bytecode.AddStep("inject", new object[1] { null });
+ }
+ else
+ {
+ var args = new List<object>(0 + starts.Length) {};
+ args.AddRange(starts.Cast<object>());
+ traversal.Bytecode.AddStep("inject", args.ToArray());
+ }
+
return traversal;
}
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index cc043f2..dac2353 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -711,6 +711,12 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_injectXnull_1_3_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,1,3,null)}},
{"g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(10,20,null,20,10,10).GroupCount("x").Dedup().As("y").Project<object>("a","b").By().By(__.Select<object>("x").Select<object>(__.Select<object>("y")))}},
{"g_injectXname_marko_age_nullX_selectXname_ageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Select<object>("name","age")}},
+ {"g_injectXnull_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,null)}},
+ {"g_injectXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null)}},
+ {"g_inject", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>()}},
+ {"g_VX1X_valuesXageX_injectXnull_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["xx1"]).Values<object>("age").Inject(null,null)}},
+ {"g_VX1X_valuesXageX_injectXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["xx1"]).Values<object>("age").Inject(null)}},
+ {"g_VX1X_valuesXageX_inject", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["xx1"]).Values<object>("age").Inject()}},
{"g_io_readXkryoX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Io<object>("data/tinkerpop-modern.kryo").Read(), (g,p) =>g.V(), (g,p) =>g.E()}},
{"g_io_read_withXreader_gryoX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Io<object>("data/tinkerpop-modern.kryo").With("~tinkerpop.io.reader","gryo").Read(), (g,p) =>g.V(), (g,p) =>g.E()}},
{"g_io_readXgraphsonX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Io<object>("data/tinkerpop-modern.json").Read(), (g,p) =>g.V(), (g,p) =>g.E()}},
diff --git a/gremlin-javascript/build/generate.groovy b/gremlin-javascript/build/generate.groovy
index 7a374c0..c82b14d 100644
--- a/gremlin-javascript/build/generate.groovy
+++ b/gremlin-javascript/build/generate.groovy
@@ -93,30 +93,42 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
'const TextP = traversalModule.TextP\n' +
'const WithOptions = traversalModule.withOptions\n')
+ // Groovy can't process certain null oriented calls because it gets confused with the right overload to call
+ // at runtime. using this approach for now as these are the only such situations encountered so far. a better
+ // solution may become necessary as testing of nulls expands.
+ def staticTranslate = [
+ g_injectXnull_nullX: " g_injectXnull_nullX: [function({g}) { return g.inject(null,null) }], ",
+ g_VX1X_valuesXageX_injectXnull_nullX: " g_VX1X_valuesXageX_injectXnull_nullX: [function({g, xx1}) { return g.V(xx1).values(\"age\").inject(null,null) }], "
+ ]
+
writer.writeLine('const gremlins = {')
gremlins.each { k,v ->
- writer.write(" ")
- writer.write(k)
- writer.write(": [")
- def collected = v.collect{
- def t = gremlinGroovyScriptEngine.eval(it, bindings)
- [t, t.bytecode.bindings.keySet()]
- }
- def uniqueBindings = collected.collect{it[1]}.flatten().unique()
- def gremlinItty = collected.iterator()
- while (gremlinItty.hasNext()) {
- def t = gremlinItty.next()[0]
- writer.write("function({g")
- if (!uniqueBindings.isEmpty()) {
- writer.write(", ")
- writer.write(uniqueBindings.join(", "))
+ if (staticTranslate.containsKey(k)) {
+ writer.writeLine(staticTranslate[k])
+ } else {
+ writer.write(" ")
+ writer.write(k)
+ writer.write(": [")
+ def collected = v.collect {
+ def t = gremlinGroovyScriptEngine.eval(it, bindings)
+ [t, t.bytecode.bindings.keySet()]
+ }
+ def uniqueBindings = collected.collect { it[1] }.flatten().unique()
+ def gremlinItty = collected.iterator()
+ while (gremlinItty.hasNext()) {
+ def t = gremlinItty.next()[0]
+ writer.write("function({g")
+ if (!uniqueBindings.isEmpty()) {
+ writer.write(", ")
+ writer.write(uniqueBindings.join(", "))
+ }
+ writer.write("}) { return ")
+ writer.write(translator.translate(t.bytecode).script)
+ writer.write(" }")
+ if (gremlinItty.hasNext()) writer.write(', ')
}
- writer.write("}) { return ")
- writer.write(translator.translate(t.bytecode).script)
- writer.write(" }")
- if (gremlinItty.hasNext()) writer.write(', ')
+ writer.writeLine('], ')
}
- writer.writeLine('], ')
}
writer.writeLine('}\n')
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 7548afe..feb52ff 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
@@ -700,6 +700,12 @@ const gremlins = {
g_injectXnull_1_3_nullX: [function({g}) { return g.inject(null,1,3,null) }],
g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX: [function({g}) { return g.inject(10,20,null,20,10,10).groupCount("x").dedup().as("y").project("a","b").by().by(__.select("x").select(__.select("y"))) }],
g_injectXname_marko_age_nullX_selectXname_ageX: [function({g, xx1}) { return g.inject(xx1).select("name","age") }],
+ g_injectXnull_nullX: [function({g}) { return g.inject(null,null) }],
+ g_injectXnullX: [function({g}) { return g.inject(null) }],
+ g_inject: [function({g}) { return g.inject() }],
+ g_VX1X_valuesXageX_injectXnull_nullX: [function({g, xx1}) { return g.V(xx1).values("age").inject(null,null) }],
+ g_VX1X_valuesXageX_injectXnullX: [function({g, xx1}) { return g.V(xx1).values("age").inject(null) }],
+ g_VX1X_valuesXageX_inject: [function({g, xx1}) { return g.V(xx1).values("age").inject() }],
g_io_readXkryoX: [function({g}) { return g.io("data/tinkerpop-modern.kryo").read() }, function({g}) { return g.V() }, function({g}) { return g.E() }],
g_io_read_withXreader_gryoX: [function({g}) { return g.io("data/tinkerpop-modern.kryo").with_("~tinkerpop.io.reader","gryo").read() }, function({g}) { return g.V() }, function({g}) { return g.E() }],
g_io_readXgraphsonX: [function({g}) { return g.io("data/tinkerpop-modern.json").read() }, function({g}) { return g.V() }, function({g}) { return g.E() }],
diff --git a/gremlin-python/build/generate.groovy b/gremlin-python/build/generate.groovy
index 05507d5..bac3d1b 100644
--- a/gremlin-python/build/generate.groovy
+++ b/gremlin-python/build/generate.groovy
@@ -77,31 +77,44 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
'from gremlin_python.process.graph_traversal import __\n' +
'from gremlin_python.structure.graph import Graph\n' +
'from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, T, Pick, Operator, IO, WithOptions\n')
+
+ // Groovy can't process certain null oriented calls because it gets confused with the right overload to call
+ // at runtime. using this approach for now as these are the only such situations encountered so far. a better
+ // solution may become necessary as testing of nulls expands.
+ def staticTranslate = [
+ g_injectXnull_nullX: " 'g_injectXnull_nullX': [(lambda g: g.inject(None,None))], ",
+ g_VX1X_valuesXageX_injectXnull_nullX: " 'g_VX1X_valuesXageX_injectXnull_nullX': [(lambda g, xx1=None: g.V(xx1).values('age').inject(None,None))], "
+ ]
+
writer.writeLine('world.gremlins = {')
gremlins.each { k,v ->
- writer.write(" '")
- writer.write(k)
- writer.write("': [")
- def collected = v.collect{
- def t = gremlinGroovyScriptEngine.eval(it, bindings)
- [t, t.bytecode.bindings.keySet()]
- }
- def uniqueBindings = collected.collect{it[1]}.flatten().unique()
- def gremlinItty = collected.iterator()
- while (gremlinItty.hasNext()) {
- def t = gremlinItty.next()[0]
- writer.write("(lambda g")
- if (!uniqueBindings.isEmpty()) {
- writer.write(", ")
- writer.write(uniqueBindings.join("=None,"))
- writer.write("=None")
+ if (staticTranslate.containsKey(k)) {
+ writer.writeLine(staticTranslate[k])
+ } else {
+ writer.write(" '")
+ writer.write(k)
+ writer.write("': [")
+ def collected = v.collect {
+ def t = gremlinGroovyScriptEngine.eval(it, bindings)
+ [t, t.bytecode.bindings.keySet()]
+ }
+ def uniqueBindings = collected.collect { it[1] }.flatten().unique()
+ def gremlinItty = collected.iterator()
+ while (gremlinItty.hasNext()) {
+ def t = gremlinItty.next()[0]
+ writer.write("(lambda g")
+ if (!uniqueBindings.isEmpty()) {
+ writer.write(", ")
+ writer.write(uniqueBindings.join("=None,"))
+ writer.write("=None")
+ }
+ writer.write(":")
+ writer.write(translator.translate(t.bytecode).script)
+ writer.write(")")
+ if (gremlinItty.hasNext()) writer.write(', ')
}
- writer.write(":")
- writer.write(translator.translate(t.bytecode).script)
- writer.write(")")
- if (gremlinItty.hasNext()) writer.write(', ')
+ writer.writeLine('], ')
}
- writer.writeLine('], ')
}
writer.writeLine('}')
}
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index b4e6d81..defbf6b 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -685,6 +685,12 @@ world.gremlins = {
'g_injectXnull_1_3_nullX': [(lambda g:g.inject(None,1,3,None))],
'g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX': [(lambda g:g.inject(10,20,None,20,10,10).groupCount('x').dedup().as_('y').project('a','b').by().by(__.select('x').select(__.select('y'))))],
'g_injectXname_marko_age_nullX_selectXname_ageX': [(lambda g, xx1=None:g.inject(xx1).select('name','age'))],
+ 'g_injectXnull_nullX': [(lambda g: g.inject(None,None))],
+ 'g_injectXnullX': [(lambda g:g.inject(None))],
+ 'g_inject': [(lambda g:g.inject())],
+ 'g_VX1X_valuesXageX_injectXnull_nullX': [(lambda g, xx1=None: g.V(xx1).values('age').inject(None,None))],
+ 'g_VX1X_valuesXageX_injectXnullX': [(lambda g, xx1=None:g.V(xx1).age.inject(None))],
+ 'g_VX1X_valuesXageX_inject': [(lambda g, xx1=None:g.V(xx1).age.inject())],
'g_io_readXkryoX': [(lambda g:g.io('data/tinkerpop-modern.kryo').read()), (lambda g:g.V()), (lambda g:g.E())],
'g_io_read_withXreader_gryoX': [(lambda g:g.io('data/tinkerpop-modern.kryo').with_('~tinkerpop.io.reader','gryo').read()), (lambda g:g.V()), (lambda g:g.E())],
'g_io_readXgraphsonX': [(lambda g:g.io('data/tinkerpop-modern.json').read()), (lambda g:g.V()), (lambda g:g.E())],
diff --git a/gremlin-test/features/sideEffect/Inject.feature b/gremlin-test/features/sideEffect/Inject.feature
index dc5e717..49d8569 100644
--- a/gremlin-test/features/sideEffect/Inject.feature
+++ b/gremlin-test/features/sideEffect/Inject.feature
@@ -104,4 +104,77 @@ Feature: Step - inject()
When iterated to list
Then the result should be unordered
| result |
- | m[{"name":"marko", "age":null}] |
\ No newline at end of file
+ | m[{"name":"marko", "age":null}] |
+
+ Scenario: g_injectXnull_nullX
+ Given the empty graph
+ And the traversal of
+ """
+ g.inject(null, null)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | null |
+ | null |
+
+ Scenario: g_injectXnullX
+ Given the empty graph
+ And the traversal of
+ """
+ g.inject(null)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | null |
+
+
+ Scenario: g_inject
+ Given the empty graph
+ And the traversal of
+ """
+ g.inject()
+ """
+ When iterated to list
+ Then the result should be empty
+
+ Scenario: g_VX1X_valuesXageX_injectXnull_nullX
+ Given the modern graph
+ And using the parameter xx1 defined as "v[marko].id"
+ And the traversal of
+ """
+ g.V(xx1).values("age").inject(null, null)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[29].i |
+ | null |
+ | null |
+
+ Scenario: g_VX1X_valuesXageX_injectXnullX
+ Given the modern graph
+ And using the parameter xx1 defined as "v[marko].id"
+ And the traversal of
+ """
+ g.V(xx1).values("age").inject(null)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[29].i |
+ | null |
+
+
+ Scenario: g_VX1X_valuesXageX_inject
+ Given the modern graph
+ And using the parameter xx1 defined as "v[marko].id"
+ And the traversal of
+ """
+ g.V(xx1).values("age").inject()
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[29].i |
\ No newline at end of file
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
index fd752d2..8fb3eb4 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
@@ -30,6 +30,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -51,6 +52,18 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
public abstract Traversal<Integer, Integer> get_g_injectXnull_1_3_nullX();
+ public abstract Traversal<Integer, Integer> get_g_injectXnull_nullX();
+
+ public abstract Traversal<Integer, Integer> get_g_injectXnullX();
+
+ public abstract Traversal<Integer, Integer> get_g_inject();
+
+ public abstract Traversal<Vertex, Object> get_g_VX1X_valuesXageX_injectXnull_nullX(final Object vid1);
+
+ public abstract Traversal<Vertex, Object> get_g_VX1X_valuesXageX_injectXnullX(final Object vid1);
+
+ public abstract Traversal<Vertex, Object> get_g_VX1X_valuesXageX_inject(final Object vid1);
+
public abstract Traversal<Integer, Map<String, Object>> get_g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX();
public abstract Traversal<Map<String,Object>, Map<String, Object>> get_g_injectXname_marko_age_nullX_selectXname_ageX();
@@ -99,6 +112,51 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
}
@Test
+ public void g_injectXnull_nullX() {
+ final Traversal<Integer, Integer> traversal = get_g_injectXnull_nullX();
+ printTraversalForm(traversal);
+ checkResults(Arrays.asList(null, null), traversal);
+ }
+
+ @Test
+ public void g_injectXnullX() {
+ final Traversal<Integer, Integer> traversal = get_g_injectXnullX();
+ printTraversalForm(traversal);
+ checkResults(Collections.singletonList(null), traversal);
+ }
+
+ @Test
+ public void g_inject() {
+ final Traversal<Integer, Integer> traversal = get_g_inject();
+ printTraversalForm(traversal);
+ assertFalse(traversal.hasNext());
+ }
+
+ @Test
+ @LoadGraphWith(MODERN)
+ public void g_VX1X_valuesXageX_injectXnull_nullX() {
+ final Traversal<Vertex, Object> traversal = get_g_VX1X_valuesXageX_injectXnull_nullX(convertToVertexId("marko"));
+ printTraversalForm(traversal);
+ checkResults(Arrays.asList(29, null, null), traversal);
+ }
+
+ @Test
+ @LoadGraphWith(MODERN)
+ public void g_VX1X_valuesXageX_injectXnullX() {
+ final Traversal<Vertex, Object> traversal = get_g_VX1X_valuesXageX_injectXnullX(convertToVertexId("marko"));
+ printTraversalForm(traversal);
+ checkResults(Arrays.asList(29, null), traversal);
+ }
+
+ @Test
+ @LoadGraphWith(MODERN)
+ public void g_VX1X_valuesXageX_inject() {
+ final Traversal<Vertex, Object> traversal = get_g_VX1X_valuesXageX_inject(convertToVertexId("marko"));
+ printTraversalForm(traversal);
+ checkResults(Arrays.asList(29), traversal);
+ }
+
+ @Test
public void g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX() {
final Traversal<Integer, Map<String, Object>> traversal = get_g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX();
printTraversalForm(traversal);
@@ -146,6 +204,36 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
}
@Override
+ public Traversal<Integer, Integer> get_g_injectXnull_nullX() {
+ return g.inject(null, null);
+ }
+
+ @Override
+ public Traversal<Integer, Integer> get_g_injectXnullX() {
+ return g.inject(null);
+ }
+
+ @Override
+ public Traversal<Integer, Integer> get_g_inject() {
+ return g.inject();
+ }
+
+ @Override
+ public Traversal<Vertex, Object> get_g_VX1X_valuesXageX_injectXnull_nullX(final Object vid1) {
+ return g.V(vid1).values("age").inject(null, null);
+ }
+
+ @Override
+ public Traversal<Vertex, Object> get_g_VX1X_valuesXageX_injectXnullX(final Object vid1) {
+ return g.V(vid1).values("age").inject(null);
+ }
+
+ @Override
+ public Traversal<Vertex, Object> get_g_VX1X_valuesXageX_inject(final Object vid1) {
+ return g.V(vid1).values("age").inject();
+ }
+
+ @Override
public Traversal<Integer, Map<String, Object>> get_g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX() {
return g.inject(10,20,null,20,10,10).groupCount("x").
dedup().as("y").