This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch GValueStepInterfaces
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/GValueStepInterfaces by this
push:
new c2b369385f qpc wip
c2b369385f is described below
commit c2b369385fd149c18283b7c7b0868cb5114ff920
Author: Stephen Mallette <[email protected]>
AuthorDate: Wed Jul 2 13:02:17 2025 -0400
qpc wip
---
gremlin-core/pom.xml | 4 +
.../GremlinLangCustomizer.java} | 30 +--
.../gremlin/jsr223/GremlinLangPlugin.java | 71 +++++++
.../gremlin/jsr223/GremlinLangScriptEngine.java | 80 +++++++-
.../gremlin/process/traversal/Bytecode.java | 4 +-
.../traversal/dsl/graph/GraphTraversalSource.java | 9 +-
.../traversal/lambda/GValueConstantTraversal.java | 3 +-
.../process/traversal/step/GValueHolder.java | 6 +
.../process/traversal/step/HasContainerHolder.java | 3 +-
.../step/filter/RangeGlobalStepPlaceholder.java | 1 +
.../step/map/AddVertexStartStepPlaceholder.java | 4 +-
.../traversal/step/map/VertexStepPlaceholder.java | 5 +-
gremlin-go/driver/cucumber/gremlin.go | 22 +--
gremlin-groovy/pom.xml | 1 -
.../gremlin/groovy/engine/GremlinExecutor.java | 4 +-
neo4j-gremlin/pom.xml | 1 -
pom.xml | 5 +
.../TinkerGraphGremlinLangScriptEngineTest.java | 216 +++++++++++++++++++++
18 files changed, 423 insertions(+), 46 deletions(-)
diff --git a/gremlin-core/pom.xml b/gremlin-core/pom.xml
index 92377a12d1..6a27e28fb8 100644
--- a/gremlin-core/pom.xml
+++ b/gremlin-core/pom.xml
@@ -79,6 +79,10 @@ limitations under the License.
<artifactId>exp4j</artifactId>
<version>${exp4j.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ </dependency>
<!-- TESTING -->
<dependency>
<groupId>junit</groupId>
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueHolder.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangCustomizer.java
similarity index 55%
copy from
gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueHolder.java
copy to
gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangCustomizer.java
index 2596a4c26a..4abbb31b77 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueHolder.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangCustomizer.java
@@ -16,24 +16,28 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.tinkerpop.gremlin.process.traversal.step;
+package org.apache.tinkerpop.gremlin.jsr223;
-import org.apache.tinkerpop.gremlin.process.traversal.Step;
-import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
+import com.github.benmanes.caffeine.cache.Caffeine;
-import java.util.Collection;
+/**
+ * Designed to provide basic configuration options to the {@link
GremlinLangScriptEngine}.
+ */
+public class GremlinLangCustomizer implements Customizer {
-public interface GValueHolder<S, E> extends Step<S, E> {
+ private final boolean cacheEnabled;
+ private final Caffeine cacheBuilder;
- public default void reduce() {
- TraversalHelper.replaceStep(this, this.asConcreteStep(),
this.getTraversal());
+ public GremlinLangCustomizer(final boolean cacheEnabled, final Caffeine
cacheBuilder) {
+ this.cacheEnabled = cacheEnabled;
+ this.cacheBuilder = cacheBuilder;
}
- public Step<S, E> asConcreteStep();
-
- public boolean isParameterized();
-
- public void updateVariable(String name, Object value);
+ public boolean isCacheEnabled() {
+ return cacheEnabled;
+ }
- public Collection<GValue<?>> getGValues();
+ public Caffeine getCacheMaker() {
+ return cacheBuilder;
+ }
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangPlugin.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangPlugin.java
new file mode 100644
index 0000000000..a2d925a8ce
--- /dev/null
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangPlugin.java
@@ -0,0 +1,71 @@
+/*
+ * 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.jsr223;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.CaffeineSpec;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Exposes {@link Customizer} implementations for configuring the {@link
GremlinLangScriptEngine}.
+ */
+public class GremlinLangPlugin extends AbstractGremlinPlugin {
+ private static final String NAME = "tinkerpop.gremlin-language";
+ private static final GremlinLangScriptEngineFactory factory = new
GremlinLangScriptEngineFactory();
+ private static final Set<String> APPLIES_TO =
Collections.singleton(factory.getEngineName());
+
+ private GremlinLangPlugin(final Builder builder) {
+ super(NAME, APPLIES_TO,
+ new GremlinLangCustomizer(builder.cacheEnabled,
+ builder.caffeineSpec != null ?
Caffeine.from(builder.caffeineSpec) : Caffeine.newBuilder()));
+ }
+
+ /**
+ * Builds a set of static bindings.
+ */
+ public static GremlinLangPlugin.Builder build() {
+ return new GremlinLangPlugin.Builder();
+ }
+
+ public static class Builder {
+ private boolean cacheEnabled = false;
+ private CaffeineSpec caffeineSpec = null;
+
+ public Builder cacheEnabled(final boolean cacheEnabled) {
+ this.cacheEnabled = cacheEnabled;
+ return this;
+ }
+
+ /**
+ * Provide a Caffeine spec formatted cache definition to configure the
cache. For example,
+ * "maximumWeight=1000, expireAfterWrite=10m", see
+ * <a
href="https://github.com/ben-manes/caffeine/wiki/Specification">Caffeine
Documatation</a> for details.
+ */
+ public Builder caffeine(final String spec) {
+ this.caffeineSpec = CaffeineSpec.parse(spec);
+ return this;
+ }
+
+ public GremlinLangPlugin create() {
+ return new GremlinLangPlugin(this);
+ }
+ }
+}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangScriptEngine.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangScriptEngine.java
index 6ef141e8e5..67b558ba72 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangScriptEngine.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangScriptEngine.java
@@ -18,13 +18,18 @@
*/
package org.apache.tinkerpop.gremlin.jsr223;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.CaffeineSpec;
import org.apache.tinkerpop.gremlin.language.grammar.GremlinAntlrToJava;
import org.apache.tinkerpop.gremlin.language.grammar.GremlinQueryParser;
import org.apache.tinkerpop.gremlin.language.grammar.VariableResolver;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.GValueManager;
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.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.step.GValue;
import javax.script.AbstractScriptEngine;
import javax.script.Bindings;
@@ -37,14 +42,16 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
* A {@link GremlinScriptEngine} implementation that evaluates Gremlin scripts
using {@code gremlin-language}. As it
* uses {@code gremlin-language} and thus the ANTLR parser, it is not capable
of process arbitrary scripts as the
* {@code GremlinGroovyScriptEngine} can and is therefore a more secure
Gremlin evaluator. It is obviously restricted
- * to the capabilities of the ANTLR grammar so therefore syntax that includes
things like lambdas are not supported.
- * For bytecode evaluation it simply uses the {@link JavaTranslator}.
+ * to the capabilities of the ANTLR grammar so syntax that includes things
like lambdas are not supported. For bytecode
+ * evaluation it simply uses the {@link JavaTranslator}.
* <p/>
* As an internal note, technically, this is an incomplete implementation of
the {@link GremlinScriptEngine} in the
* traditional sense as a drop-in replacement for something like the {@code
GremlinGroovyScriptEngine}. As a result,
@@ -53,12 +60,29 @@ import java.util.function.Function;
* implementation represents the first step to changes in what it means to
have a {@link GremlinScriptEngine}. In some
* sense, there is question why a {@link GremlinScriptEngine} approach is
necessary at all except for easily plugging
* into the existing internals of Gremlin Server or more specifically the
{@code GremlinExecutor}.
+ * <p/>
+ * The engine includes a cache that can help spare repeated parsing of the
same traversal. It is keyed to each
+ * configuration of the {@link GraphTraversalSource} that initially requested
its execution via the bindings given to
+ * the {@code eval} method. Each cache is keyed on the original Gremlin string
and holds the initialized parsed
+ * traversal for its value. If that Gremlin string is matched again in the
future, it will use the one from the cache
+ * rather than parse again via the ANTLR grammar. In addition, if {@link
GValue} instances were used in formation of
+ * the traversal, it will substitute in new bindings given with the {@code
eval}. This cache only applies to scripts,
+ * not to {@link Bytecode}.
*/
public class GremlinLangScriptEngine extends AbstractScriptEngine implements
GremlinScriptEngine {
private volatile GremlinScriptEngineFactory factory;
private final Function<Map<String, Object>, VariableResolver>
variableResolverMaker;
+ private final Map<GraphTraversalSource, Cache<String,
Traversal.Admin<?,?>>> traversalCaches = new ConcurrentHashMap<>();
+
+ /**
+ * Determines if the traversal cache is enabled or not. This is {@code
false} by default.
+ */
+ private final boolean cacheEnabled;
+
+ private final Caffeine<String, Traversal<?,?>> caffeine;
+
/**
* Creates a new instance using no {@link Customizer}.
*/
@@ -71,11 +95,24 @@ public class GremlinLangScriptEngine extends
AbstractScriptEngine implements Gre
// this ScriptEngine really only supports the
VariableResolverCustomizer to configure the VariableResolver
// and can't configure it more than once. first one wins
- final Optional<Customizer> opt = listOfCustomizers.stream().filter(c
-> c instanceof VariableResolverCustomizer).findFirst();
+ final Optional<Customizer> opt = listOfCustomizers.stream().
+ filter(c -> c instanceof
VariableResolverCustomizer).findFirst();
variableResolverMaker = opt.isPresent() ?
((VariableResolverCustomizer)
opt.get()).getVariableResolverMaker() :
VariableResolver.DirectVariableResolver::new;
+ // cache customization
+ final Optional<GremlinLangCustomizer> gremlinLangCustomizer =
listOfCustomizers.stream().
+ filter(c -> c instanceof GremlinLangCustomizer).
+ map(c -> (GremlinLangCustomizer) c).findFirst();
+ if (gremlinLangCustomizer.isPresent()) {
+ final GremlinLangCustomizer customizer =
gremlinLangCustomizer.get();
+ cacheEnabled = customizer.isCacheEnabled();
+ caffeine = customizer.getCacheMaker();
+ } else {
+ cacheEnabled = false;
+ caffeine = null;
+ }
}
@Override
@@ -121,12 +158,47 @@ public class GremlinLangScriptEngine extends
AbstractScriptEngine implements Gre
if (!(o instanceof GraphTraversalSource))
throw new IllegalArgumentException("g is of type " +
o.getClass().getSimpleName() + " and is not an instance of TraversalSource");
+ final GraphTraversalSource g = (GraphTraversalSource) o;
final Map<String, Object> m =
context.getBindings(ScriptContext.ENGINE_SCOPE);
+
+ Cache<String, Traversal.Admin<?, ?>> traversalCache = null;
+ if (cacheEnabled) {
+ // find the property cache for the g that was passed in so that we
have the right Graph instance/context
+ traversalCache = traversalCaches.computeIfAbsent(g, k ->
caffeine.build());
+
+ // if found in cache, clone the traversal, apply new bindings and
then return the clone
+ final Traversal cachedTraversal =
traversalCache.getIfPresent(script);
+ if (cachedTraversal != null) {
+ final Traversal.Admin<?, ?> clonedTraversal =
cachedTraversal.asAdmin().clone();
+ final GValueManager manager =
clonedTraversal.getGValueManager();
+ final Set<String> variables = manager.getVariableNames();
+
+ // every variable the traversal has should match a binding
+ for (String variable : variables) {
+ if (!m.containsKey(variable)) {
+ throw new IllegalArgumentException(variable + "
binding is not found");
+ }
+
+ manager.track(GValue.of(variable, m.get(variable)));
+ }
+
+ return clonedTraversal;
+ }
+ }
+
+ // if not in cache or cache not enabled, parse the script
final GremlinAntlrToJava antlr = new
GremlinAntlrToJava((GraphTraversalSource) o,
variableResolverMaker.apply(m));
try {
- return GremlinQueryParser.parse(script, antlr);
+ // parse the script
+ final Traversal.Admin<?,?> traversal = (Traversal.Admin)
GremlinQueryParser.parse(script, antlr);
+
+ // add the traversal to the cache in its executable form - note
that the cache is really just sparing us
+ // traversal execution
+ if (cacheEnabled) traversalCache.put(script, traversal.clone());
+
+ return traversal;
} catch (Exception ex) {
throw new ScriptException(ex);
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
index a1caff1a27..12bbfb1e72 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
@@ -340,8 +340,8 @@ public class Bytecode implements Cloneable, Serializable {
}
return set;
} else if (argument instanceof GValue) {
- String variable = ((GValue<?>) argument).getName();
- Object value = ((GValue<?>) argument).get();
+ final String variable = ((GValue<?>) argument).getName();
+ final Object value = ((GValue<?>) argument).get();
return variable == null
? convertArgument(value, searchBindings)
: new Binding<>(variable, convertArgument(value, false));
//TODO:: can you put a binding in a GValue?
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 13935928e4..8aebf80103 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
@@ -30,7 +30,6 @@ import
org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.step.GValue;
import org.apache.tinkerpop.gremlin.process.traversal.step.branch.UnionStep;
import
org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStartStepPlaceholder;
-import
org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStartStep;
import
org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStartStepPlaceholder;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.CallStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep;
@@ -525,9 +524,9 @@ public class GraphTraversalSource implements
TraversalSource {
final GraphTraversal.Admin<Vertex, Vertex> traversal = new
DefaultGraphTraversal<>(clone);
GraphStepInterface<Vertex, Vertex> step;
if (GValue.containsGValues(ids)) {
- step = new GraphStepPlaceholder<>(traversal, Vertex.class, false,
GValue.ensureGValues(ids));
+ step = new GraphStepPlaceholder<>(traversal, Vertex.class, true,
GValue.ensureGValues(ids));
} else {
- step = new GraphStep<>(traversal, Vertex.class, false, ids);
+ step = new GraphStep<>(traversal, Vertex.class, true, ids);
}
return traversal.addStep(step);
}
@@ -546,9 +545,9 @@ public class GraphTraversalSource implements
TraversalSource {
final GraphTraversal.Admin<Edge, Edge> traversal = new
DefaultGraphTraversal<>(clone);
GraphStepInterface<Edge, Edge> step;
if (GValue.containsGValues(ids)) {
- step = new GraphStepPlaceholder<>(traversal, Edge.class, false,
GValue.ensureGValues(ids));
+ step = new GraphStepPlaceholder<>(traversal, Edge.class, true,
GValue.ensureGValues(ids));
} else {
- step = new GraphStep<>(traversal, Edge.class, false, ids);
+ step = new GraphStep<>(traversal, Edge.class, true, ids);
}
return traversal.addStep(step);
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/GValueConstantTraversal.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/GValueConstantTraversal.java
index ef0df7a3ed..eeefae4f70 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/GValueConstantTraversal.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/GValueConstantTraversal.java
@@ -31,7 +31,7 @@ import java.util.Objects;
public final class GValueConstantTraversal<S, E> extends
AbstractLambdaTraversal<S, E> {
private GValue<E> end;
- private final ConstantTraversal<S, E> constantTraversal;
+ private ConstantTraversal<S, E> constantTraversal;
public GValueConstantTraversal(final GValue<E> end) {
this.end = end;
@@ -75,6 +75,7 @@ public final class GValueConstantTraversal<S, E> extends
AbstractLambdaTraversal
if (name.equals(end.getName())) {
//TODO type check?
end = (GValue<E>) GValue.of(name, value);
+ this.constantTraversal = new ConstantTraversal<>(end.get());
}
}
}
\ No newline at end of file
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueHolder.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueHolder.java
index 2596a4c26a..5e1b77ea39 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueHolder.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueHolder.java
@@ -18,6 +18,7 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.step;
+import org.apache.tinkerpop.gremlin.process.traversal.GValueManager;
import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
@@ -26,6 +27,11 @@ import java.util.Collection;
public interface GValueHolder<S, E> extends Step<S, E> {
public default void reduce() {
+ // todo: maybe check to see if GValueManager was updated after
traversal construction to spare this updateVariable
+ final GValueManager manger = this.getTraversal().getGValueManager();
+ manger.getGValues().forEach(gValue -> {
+ updateVariable(gValue.getName(), gValue.get());
+ });
TraversalHelper.replaceStep(this, this.asConcreteStep(),
this.getTraversal());
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/HasContainerHolder.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/HasContainerHolder.java
index ae0be17252..a772cb7ecf 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/HasContainerHolder.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/HasContainerHolder.java
@@ -54,9 +54,8 @@ public interface HasContainerHolder<S, E> extends
GValueHolder<S, E> { //TODO ra
}
public default void updateVariable(final String name, final Object value) {
- getPredicates().stream().map((p) -> {
+ getPredicates().forEach((p) -> {
p.updateVariable(name, value);
- return p;
});
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/RangeGlobalStepPlaceholder.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/RangeGlobalStepPlaceholder.java
index ca71f5cb63..c05117f829 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/RangeGlobalStepPlaceholder.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/RangeGlobalStepPlaceholder.java
@@ -64,6 +64,7 @@ public class RangeGlobalStepPlaceholder<S> extends
AbstractStep<S,S> implements
}
if (name.equals(high.getName())) {
+ // todo: chill on the Long and allow Integer???
if (!(value instanceof Long)) {
throw new IllegalArgumentException("The variable " + name + "
must have a value of type Long");
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStepPlaceholder.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStepPlaceholder.java
index 24bce59c58..80d11f5ace 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStepPlaceholder.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStepPlaceholder.java
@@ -52,11 +52,11 @@ public class AddVertexStartStepPlaceholder extends
AbstractStep<Vertex, Vertex>
private GValue<Object> elementId;
public AddVertexStartStepPlaceholder(final Traversal.Admin traversal,
final String label) {
- this(traversal, new ConstantTraversal<>(label));
+ this(traversal, null == label ? null : new
ConstantTraversal<>(label));
}
public AddVertexStartStepPlaceholder(final Traversal.Admin traversal,
final GValue<String> label) {
- this(traversal, new GValueConstantTraversal<>(label));
+ this(traversal, null == label ? null : new
GValueConstantTraversal<>(label));
}
public AddVertexStartStepPlaceholder(final Traversal.Admin traversal,
final Traversal.Admin<?,String> vertexLabelTraversal) {
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStepPlaceholder.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStepPlaceholder.java
index 81d2370ebe..ff6455c9b1 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStepPlaceholder.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStepPlaceholder.java
@@ -114,6 +114,7 @@ public class VertexStepPlaceholder<E extends Element>
extends AbstractStep<Verte
return StringFactory.stepString(this, this.direction,
Arrays.asList(this.edgeLabels), this.returnClass.getSimpleName().toLowerCase());
}
+ // todo: why commented out?
// @Override
// public int hashCode() {
// int result = Objects.hash(super.hashCode(), direction, returnClass);
@@ -135,7 +136,9 @@ public class VertexStepPlaceholder<E extends Element>
extends AbstractStep<Verte
@Override
public Step<Vertex, E> asConcreteStep() {
- return new VertexStep<>(traversal, returnClass, direction, (String[])
GValue.resolveToValues(edgeLabels));
+ return new VertexStep<>(traversal, returnClass, direction,
Arrays.stream(GValue.resolveToValues(edgeLabels))
+ .map(String.class::cast)
+ .toArray(String[]::new));
}
@Override
diff --git a/gremlin-go/driver/cucumber/gremlin.go
b/gremlin-go/driver/cucumber/gremlin.go
index d45c9153e0..1feed8a54c 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -437,7 +437,7 @@ var translationMap = map[string][]func(g
*gremlingo.GraphTraversalSource, p map[
"g_withoutStrategiesXMatchPredicateStrategyX_V_matchXa_created_lop_b__b_0created_29_cX_whereXc_repeatXoutX_timesX2XX_selectXa_b_cX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.MatchPredicateStrategy()).V().Match(gremlingo.T__.As("a").Out("created").Has("name",
"lop").As("b"), gremlingo.T__.As("b").In("created").Has("age",
29).As("c")).Where(gremlingo.T__.As("c").Repeat(gremlingo.T__.Out()).Times(2
[...]
"g_withStrategiesXMessagePassingReductionStrategyX_V": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.MessagePassingReductionStrategy()).V()}},
"g_withoutStrategiesXMessagePassingReductionStrategyX_V": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.MessagePassingReductionStrategy()).V()}},
- "g_V_coworker": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().HasLabel("person").Filter(gremlingo.T__.OutE("created")).Aggregate("p").As("p1").Values("name").As("p1n").Select("p").Unfold().Where(gremlingo.P.Neq("p1")).As("p2").Values("name").As("p2n").Select("p2").Out("created").Choose(gremlingo.T__.In("created").Where(gremlingo.P.Eq("p1")),
gremlingo.T__.Values("name"),
gremlingo.T__.Constant(p["xx1"])).Group().By(gremling [...]
+ "g_V_coworker": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().HasLabel("person").Filter(gremlingo.T__.OutE("created")).Aggregate("p").As("p1").Values("name").As("p1n").Select("p").Unfold().Where(gremlingo.P.Neq("p1")).As("p2").Values("name").As("p2n").Select("p2").Out("created").Choose(gremlingo.T__.In("created").Where(gremlingo.P.Eq("p1")),
gremlingo.T__.Values("name"),
gremlingo.T__.Constant([]interface{}{})).Group().By(g [...]
"g_V_coworker_with_midV": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().HasLabel("person").Filter(gremlingo.T__.OutE("created")).As("p1").V().HasLabel("person").Where(gremlingo.P.Neq("p1")).Filter(gremlingo.T__.OutE("created")).As("p2").Map(gremlingo.T__.Out("created").Where(gremlingo.T__.In("created").As("p1")).Values("name").Fold()).Group().By(gremlingo.T__.Select("p1").By("name")).By(gremlingo.T__.Group().By(gremlingo.T_
[...]
"g_withStrategiesXOptionsStrategyX_V": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.OptionsStrategy()).V()}},
"g_withStrategiesXOptionsStrategyXmyVar_myValueXX_V": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.OptionsStrategy(map[string]interface{}{"myVar":
"myValue"})).V()}},
@@ -771,15 +771,15 @@ var translationMap = map[string][]func(g
*gremlingo.GraphTraversalSource, p map[
"g_injectXa_null_bX_disjunctXa_cX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.Inject(p["xx1"]).Disjunct([]interface{}{"a", "c"})}},
"g_injectXa_null_bX_disjunctXa_null_cX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.Inject(p["xx1"]).Disjunct([]interface{}{"a", nil, "c"})}},
"g_injectX3_threeX_disjunctXfive_three_7X": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.Inject(p["xx1"]).Disjunct([]interface{}{"five", "three", 7})}},
- "g_E": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.E()}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.E()}},
- "g_EX11X": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["eid11"])},
func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.E(p["eid11"])}},
- "g_EX11AsStringX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["eid11"])},
func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.E(p["eid11"])}},
- "g_EXe11X": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["e11"])},
func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.E(p["e11"])}},
- "g_EXe7_e11X": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["e7"],
p["e11"])}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.E(p["e7"], p["e11"])}},
- "g_EXlistXe7_e11XX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["xx1"])},
func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.E(p["xx1"])}},
- "g_EXnullX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(nil)}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.E(nil)}},
- "g_EXlistXnullXX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["xx1"])},
func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.E(p["xx1"])}},
- "g_EX11_nullX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["eid11"],
nil)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.E(p["eid11"], nil)}},
+ "g_E": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.E()}},
+ "g_EX11X": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["eid11"])}},
+ "g_EX11AsStringX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["eid11"])}},
+ "g_EXe11X": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["e11"])}},
+ "g_EXe7_e11X": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["e7"],
p["e11"])}},
+ "g_EXlistXe7_e11XX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["xx1"])}},
+ "g_EXnullX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(nil)}},
+ "g_EXlistXnullXX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["xx1"])}},
+ "g_EX11_nullX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.E(p["eid11"],
nil)}},
"g_V_EX11X": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().E(p["eid11"])}},
"g_EX11X_E": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.E(p["eid11"]).E()}},
"g_V_EXnullX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.V().E(nil)}},
@@ -1649,7 +1649,7 @@ var translationMap = map[string][]func(g
*gremlingo.GraphTraversalSource, p map[
"g_withSackX1_sumX_VX1X_localXoutXknowsX_barrierXnormSackXX_inXknowsX_barrier_sack":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.WithSack(1.0,
gremlingo.Operator.Sum).V(p["vid1"]).Local(gremlingo.T__.Out("knows").Barrier(gremlingo.Barrier.NormSack)).In("knows").Barrier().Sack()}},
"g_V_sackXassignX_byXageX_sack": {func(g *gremlingo.GraphTraversalSource,
p map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().Sack(gremlingo.Operator.Assign).By("age").Sack()}},
"g_withSackXBigInteger_TEN_powX1000X_assignX_V_localXoutXknowsX_barrierXnormSackXX_inXknowsX_barrier_sack":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithSack(1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
[...]
- "g_withSackX2X_V_sackXdivX_byXconstantX4_0XX_sack": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithSack(2).V().Sack(gremlingo.Operator.Div).By(gremlingo.T__.Constant(p["xx1"])).Sack()}},
+ "g_withSackX2X_V_sackXdivX_byXconstantX4_0XX_sack": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithSack(2).V().Sack(gremlingo.Operator.Div).By(gremlingo.T__.Constant(4.0)).Sack()}},
"g_V_sackXassignX_byXageX_byXnameX_sack": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V().Sack(gremlingo.Operator.Assign).By("age").By("name").Sack()}},
"g_V_sideEffectXidentityX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().SideEffect(gremlingo.T__.Identity())}},
"g_V_sideEffectXidentity_valuesXnameXX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V().SideEffect(gremlingo.T__.Identity().Values("name"))}},
diff --git a/gremlin-groovy/pom.xml b/gremlin-groovy/pom.xml
index 21d66962ef..fe26daec62 100644
--- a/gremlin-groovy/pom.xml
+++ b/gremlin-groovy/pom.xml
@@ -81,7 +81,6 @@ limitations under the License.
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
- <version>${caffeine.version}</version>
</dependency>
<!-- TEST -->
<dependency>
diff --git
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
index 9010578e53..383b7fb05f 100644
---
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
+++
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
@@ -312,8 +312,7 @@ public class GremlinExecutor implements AutoCloseable {
Object o;
if (gremlin instanceof String) {
o =
gremlinScriptEngineManager.getEngineByName(lang).eval((String) gremlin,
bindings);
- }
- else if (gremlin instanceof Bytecode) {
+ } else if (gremlin instanceof Bytecode) {
final Bytecode bytecode = (Bytecode) gremlin;
final Traversal.Admin<?, ?> traversal = eval(
bytecode, bindings,
BytecodeHelper.getLambdaLanguage(bytecode).orElse("gremlin-groovy"), "g");
@@ -324,7 +323,6 @@ public class GremlinExecutor implements AutoCloseable {
throw new IllegalArgumentException("Invalid gremlin
type.");
}
-
// apply a transformation before sending back the result -
useful when trying to force serialization
// in the same thread that the eval took place given
ThreadLocal nature of graphs as well as some
// transactional constraints
diff --git a/neo4j-gremlin/pom.xml b/neo4j-gremlin/pom.xml
index ed4b6b2e72..bc0e0023d4 100644
--- a/neo4j-gremlin/pom.xml
+++ b/neo4j-gremlin/pom.xml
@@ -201,7 +201,6 @@ limitations under the License.
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
- <version>2.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
diff --git a/pom.xml b/pom.xml
index e6275c43bb..c5b08dcf41 100644
--- a/pom.xml
+++ b/pom.xml
@@ -752,6 +752,11 @@ limitations under the License.
<dependencyManagement>
<dependencies>
+ <dependency>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ <version>${caffeine.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
diff --git
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/jsr223/TinkerGraphGremlinLangScriptEngineTest.java
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/jsr223/TinkerGraphGremlinLangScriptEngineTest.java
new file mode 100644
index 0000000000..1428523eb7
--- /dev/null
+++
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/jsr223/TinkerGraphGremlinLangScriptEngineTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.tinkergraph.jsr223;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.tinkerpop.gremlin.jsr223.GremlinLangCustomizer;
+import org.apache.tinkerpop.gremlin.jsr223.GremlinLangScriptEngine;
+import org.apache.tinkerpop.gremlin.jsr223.VariableResolverCustomizer;
+import org.apache.tinkerpop.gremlin.language.grammar.VariableResolver;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
+import
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.step.GValue;
+import
org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization.GValueReductionStrategy;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.util.CollectionUtil;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import javax.script.Bindings;
+import javax.script.ScriptException;
+import javax.script.SimpleBindings;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static
org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Concrete testing of {@link GremlinLangScriptEngine} evaluations,
particularly around its traversal cache.
+ */
+@RunWith(Parameterized.class)
+public class TinkerGraphGremlinLangScriptEngineTest {
+
+ @Parameterized.Parameter(value = 0)
+ public String gremlinScript;
+
+ @Parameterized.Parameter(value = 1)
+ public List<Pair<Bindings, List<Object>>> bindingsAndResults;
+
+ @Parameterized.Parameter(value = 2)
+ public TinkerGraph graph;
+
+ private GremlinLangScriptEngine scriptEngine;
+ private GraphTraversalSource g;
+
+ /**
+ * Use {@link GValue} instance when resolving variables in the parser.
+ */
+ private final VariableResolverCustomizer variableResolverCustomizer = new
VariableResolverCustomizer(
+ VariableResolver.DefaultVariableResolver::new);
+
+ /**
+ * Enable caching
+ */
+ private final GremlinLangCustomizer gremlinLangCustomizer = new
GremlinLangCustomizer(true, Caffeine.newBuilder());
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable<Object[]> generateTestParameters() {
+ return Arrays.asList(new Object[][]{
+ {
+ "g.V().limit(x).count()",
+ Arrays.asList(
+ Pair.of(createBindings("x", 1L), Arrays.asList(1L)),
+ Pair.of(createBindings("x", 2L), Arrays.asList(2L)),
+ Pair.of(createBindings("x", 4L), Arrays.asList(4L))
+ ),
+ TinkerFactory.createModern()
+ },
+ {
+ "g.V().has('name', x).values('age')",
+ Arrays.asList(
+ Pair.of(createBindings("x", "vadas"),
Arrays.asList(27)),
+ Pair.of(createBindings("x", "marko"),
Arrays.asList(29))
+ ),
+ TinkerFactory.createModern()
+ },
+ {
+ "g.V().has('name',
x).out(y).values('name').order().by(asc)",
+ Arrays.asList(
+ Pair.of(createBindings("x", "josh", "y", "created"),
Arrays.asList("lop" ,"ripple")),
+ Pair.of(createBindings("x", "marko", "y", "knows"),
Arrays.asList("josh", "vadas"))
+ ),
+ TinkerFactory.createModern()
+ },
+ {
+ "g.V().has('name',
x).in(y).values('name').order().by(asc)",
+ Arrays.asList(
+ Pair.of(createBindings("x", "lop", "y", "created"),
Arrays.asList("josh" ,"marko")),
+ Pair.of(createBindings("x", "vadas", "y", "knows"),
Arrays.asList("marko"))
+ ),
+ TinkerFactory.createModern()
+ },
+ {
+ "g.V().has('name',
x).both(y,z).values('name').order().by(asc)",
+ Arrays.asList(
+ Pair.of(createBindings("x", "marko", "y", "created",
"z", "knows"), Arrays.asList("josh" ,"lop", "vadas")),
+ Pair.of(createBindings("x", "vadas", "y", "knows",
"z", "created"), Arrays.asList("marko"))
+ ),
+ TinkerFactory.createModern()
+ },
+ {
+ "g.V().has('age', gt(x)).values('name')",
+ Arrays.asList(
+ Pair.of(createBindings("x", 31), Arrays.asList("josh"
,"peter")),
+ Pair.of(createBindings("x", 34),
Arrays.asList("peter"))
+ ),
+ TinkerFactory.createModern()
+ },
+ {
+ "g.V().has('age', gt(x).and(lt(y))).values('name')",
+ Arrays.asList(
+ Pair.of(createBindings("x", 26, "y", 30),
Arrays.asList("marko" ,"vadas")),
+ Pair.of(createBindings("x", 26, "y", 29),
Arrays.asList("vadas"))
+ ),
+ TinkerFactory.createModern()
+ },
+ {
+ "g.V().has('name', x).union(both(y),
both(z)).values('name').order().by(asc)",
+ Arrays.asList(
+ Pair.of(createBindings("x", "marko", "y", "created",
"z", "knows"), Arrays.asList("josh" ,"lop", "vadas")),
+ Pair.of(createBindings("x", "vadas", "y", "knows",
"z", "created"), Arrays.asList("marko"))
+ ),
+ TinkerFactory.createModern()
+ },
+ {
+ "g.addV(x).label()",
+ Arrays.asList(
+ Pair.of(createBindings("x", "software"),
Arrays.asList("software")),
+ Pair.of(createBindings("x", "person"),
Arrays.asList("person"))
+ ),
+ TinkerGraph.open()
+ },
+ {
+ "g.addV().property(T.id, x).id()",
+ Arrays.asList(
+ Pair.of(createBindings("x", "abc"),
Arrays.asList("abc")),
+ Pair.of(createBindings("x", "xyz"),
Arrays.asList("xyz"))
+ ),
+ TinkerGraph.open(),
+ },
+ });
+ }
+
+ private static Bindings createBindings(final Object... pairs) {
+ final Bindings bindings = new SimpleBindings();
+ final Map<String,Object> args = CollectionUtil.asMap(pairs);
+ args.forEach(bindings::put);
+ return bindings;
+ }
+
+ @Before
+ public void setup() {
+ // create a new engine each time to keep the cache clear
+ scriptEngine = new GremlinLangScriptEngine(variableResolverCustomizer,
gremlinLangCustomizer);
+ g = traversal().with(graph);
+ }
+
+ @Test
+ public void shouldUseCacheForRepeatedScriptsWithVars() throws
ScriptException {
+ // store all traversal results to verify they are different instances
+ final List<Object> results = Arrays.asList(new
Object[bindingsAndResults.size()]);
+
+ // execute the script with each set of bindings and store the results
+ for (int i = 0; i < bindingsAndResults.size(); i++) {
+ final Pair<Bindings, List<Object>> pair =
bindingsAndResults.get(i);
+ final Bindings bindings = pair.getLeft();
+ final List<Object> expectedResults = pair.getRight();
+ bindings.put("g", g);
+
+ // execute the script
+ final Object result = scriptEngine.eval(gremlinScript, bindings);
+ assertThat(result, instanceOf(Traversal.Admin.class));
+
+ // store the result for later comparison
+ results.set(i, result);
+
+ // verify the result matches the expected value
+ for (Object expected : expectedResults) {
+ assertEquals(expected, ((Traversal) result).next());
+ }
+ }
+
+ // verify that each traversal instance is unique
+ for (int i = 0; i < results.size(); i++) {
+ for (int j = i + 1; j < results.size(); j++) {
+ assertThat(results.get(i) != results.get(j), is(true));
+ }
+ }
+ }
+}