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));
+            }
+        }
+    }
+}

Reply via email to