Hello,
ScriptEngine’s prefer to have compile scripts with bindings as a compiled
script will execute much faster than one that needs to be compiled. Thus, for a
traversal that will be run over and over again, but with for instance different
g.V(id) sources, its all about using ScriptEngine bindings. How do we support
bindings in a language agnostic way?
The big problem is Java. The GraphTraversal API is strongly typed and thus, we
can’t simply do stuff like g.V(bind(“id”,2)). Or can we?
b = new Bindings();
g = graph.traversal().withBindings(b);
g.V(b.of(“id”,2)).out(“created”).values(“name”)
What is this all about?
When you do b.of(“id”,2), it returns 2 (Java API is happy). However, the
underlying Bytecode will know when a new argument has been passed to it. It
will ask Bindings, "was a binding for 2 just added?" If so, then the Bytecode
rewrites itself such that the argument is binding(“id”,2).
Then, when the traversal is sent to GremlinServer to be evaluated (lets say via
Gremlin-Groovy), the Bytecode translator will make the Groovy String script
look as:
g.V(id).out(“created”).values(“name”)
And it will have JSR223 ScriptEngine bindings of {id:2}.
https://github.com/apache/tinkerpop/blob/TINKERPOP-1278/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/java/translator/GroovyTranslator.java
<https://github.com/apache/tinkerpop/blob/TINKERPOP-1278/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/java/translator/GroovyTranslator.java>
Thus, TinkerPop Bindings become a first class citizen in Gremlin. Next, what
about when Gremlin-Java is translating Bytecode. Well, Java doesn’t care about
script compilation speed as its Bytecode translator doesn’t generate a String,
but a Traversal via reflection.
https://github.com/apache/tinkerpop/blob/TINKERPOP-1278/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/JavaTranslator.java
<https://github.com/apache/tinkerpop/blob/TINKERPOP-1278/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/JavaTranslator.java>
If JavaTranslator sees a binding in the Bytecode is ignores the binding key,
and only uses its value:
g.V(2).out(“created”).values(“name”)
So this can get awkward for users. However, we can make bindings reusable.
b = new Bindings();
g = graph.traversal().withBindings(b);
g.V(b.of(“id”,2)).out(“created”).values(“name”)
b.clear()
g.V().out(b.of(“a”,”knows”))
b.clear()
In fact, it would be possible to automatically reset the bindings every time a
traversal is compiled. Thus,
b = new Bindings();
g = graph.traversal().withBindings(b);
g.V(b.of(“id”,2)).out(“created”).values(“name”)
g.V().out(b.of(“a”,”knows”))
Now, you may say — “well, aren’t traversal sources suppose to be thread safe.”
To that I say: “yes and if you do it like this, you can only execute one
traversal at a time.” If you want thread safety:
g = graph.traversal();
b = new Bindings();
g.withBindings(b).V(b.of(“id”,2)).out(“created”).values(“name”)
g.withBindings(b).V().out(b.of(“a”,”knows”))
For most people, they will have a RemoteGraph traversal source and not worry
about thread safety here. On GremlinServer, thread safety isn’t an issue as
withBindings() is a TraversalSource step and thus, part of the Bytecode
reconstructed at compile time.
Anywho, that is the best I could come up with that will work for Java, Groovy,
and Python. Unfortunately Java (and Groovy) are statically typed so we have to
work around this.
Thoughts?,
Marko.
http://markorodriguez.com