blob: 9a01d6ceabb765751045369194e5e0f692bd2617 [file] [log] [blame]
////
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.
////
anchor:gremlin-variants[]
[[gremlin-drivers-variants]]
= Gremlin Drivers and Variants
image::gremlin-house-of-mirrors.png[width=1024]
At this point, readers should be well familiar with the <<intro,Introduction>> to this Reference Documentation and
will likely be thinking about implementation details specific to the graph provider they have selected as well as
the programming language they intend to use. The choice of programming language could have implications to the
architecture and design of the application and the choice itself may have limits imposed upon it by the chosen graph
provider. For example, a <<connecting-rgp,Remote Gremlin Provider>> will require the selection of a driver to interact
with it. On the other hand, a graph system that is designed for embedded use, like TinkerGraph, needs the Java
Virtual Machine (JVM) environment so will require <<connecting-gremlin-server,Gremlin Server>>, if using a programming
language that is not on the JVM and will further require driver selection.
TinkerPop provides an array of drivers in different programming languages as a way to connect to a remote Gremlin
Server or Remote Gremlin Provider. Drivers allow the developer to make requests to that remote system and get back
results from the TinkerPop-enabled graphs hosted within. A driver can submit Gremlin strings and Gremlin bytecode
over this sub-protocol. Gremlin strings are written in the scripting language made available by the remote system that
the driver is connecting to (typically, Groovy-based). This connection approach is quite similar to what developers
are likely familiar with when using JDBC and SQL. While it is familiar, it is not recommended and for TinkerPop it is
considered an out-dated concept and is largely still present for the purpose of supporting applications that might
still be using that method of interaction.
The preferred approach is to use bytecode-based requests, which essentially allows the ability to craft Gremlin
directly in the programming language of choice. As Gremlin makes use of two fundamental programming constructs:
link:https://en.wikipedia.org/wiki/Function_composition[function composition] and
link:https://en.wikipedia.org/wiki/Nested_function[function nesting]. it is possible to embed the Gremlin language
in any modern programming language. It is a far more natural way to program, because it enables IDE interaction,
compile time checks, and language level checks that can help prevent errors prior to execution. The differences
between these two approaches were outlined in the <<connecting-via-drivers,Connecting Via Drivers>> Section, which
applies to Gremlin Server, but also to Remote Gremlin Providers.
In addition to the languages and drivers that TinkerPop supports, there are also third-party implementations, as well
as extensions to the Gremlin language that might be specific to a particular graph provider. That listing can be
found on the TinkerPop link:https://tinkerpop.apache.org/#graph-systems[home page]. Their description is beyond the
scope of this documentation.
TIP: When possible, it is typically best to align the version of TinkerPop used on the client with the version
supported on the server. While it is not impossible to have a different version between client and server, it may
require additional configuration and/or a deeper knowledge of that changes introduced between versions. It's simply
safer to avoid the conflict, when allowed to do so.
IMPORTANT: Gremlin-Java is the canonical representation of Gremlin and any (proper) Gremlin language variant will
emulate its structure as best as possible given the constructs of the host language. A strong correspondence between
variants ensures that the general Gremlin reference documentation is applicable to all variants and that users moving
between development languages can easily adopt the Gremlin variant for that language.
image::gremlin-variant-architecture.png[width=650,float=left]
NOTE: The information herein describes how to use the Gremlin language variants distributed
with Apache TinkerPop. For information on how to build a Gremlin language variant, please review the
link:https://tinkerpop.apache.org/docs/x.y.z/tutorials/gremlin-language-variants/[Gremlin Language Variants] tutorial.
The following sections describe each language variant and driver that is officially TinkerPop a part of the project,
provided more detailed information about usage, configuration and known limitations.
anchor:connecting-via-remotegraph[]
anchor:connecting-via-java[]
[[gremlin-java]]
== Gremlin-Java
image:gremlin-java-drawing.png[width=130,float=right] Apache TinkerPop's Gremlin-Java implements Gremlin within the
Java language and can be used by any Java Virtual Machine. Gremlin-Java is considered the canonical, reference
implementation of Gremlin and serves as the foundation by which all other Gremlin language variants should emulate.
As the Gremlin Traversal Machine that processes Gremlin queries is also written in Java, it can be used in all three
connection methods described in the <<connecting-gremlin,Connecting Gremlin>> Section.
[source,xml]
----
<dependency>
<groupId>org.apache.tinkerpop</groupId>
<artifactId>gremlin-core</artifactId>
<version>x.y.z</version>
</dependency>
<!-- when using Gremlin Server or Remote Gremlin Provider a driver is required -->
<dependency>
<groupId>org.apache.tinkerpop</groupId>
<artifactId>gremlin-driver</artifactId>
<version>x.y.z</version>
</dependency>
----
=== Connecting
The pattern for connecting is described in <<connecting-gremlin,Connecting Gremlin>> and it basically distills down
to creating a `GraphTraversalSource`. For <<connecting-embedded,embedded>> mode, this involves first creating a
`Graph` and then spawning the `GraphTraversalSource`:
[source,java]
----
Graph graph = ...;
GraphTraversalSource g = graph.traversal();
----
Using "g" it is then possible to start writing Gremlin. The "g" allows for the setting of many configuration options
which affect traversal execution. The <<traversal, Traversal>> Section describes some of these options and some are
only suitable with <<connecting-embedded,embedded>> style usage. For remote options however there are some added
configurations to consider and this section looks to address those.
When connecting to <<connecting-gremlin-server,Gremlin Server>> or <<connecting-rgp,Remote Gremlin Providers>> it
is possible to configure the `DriverRemoteConnection` manually as shown in earlier examples where the host and port
are provided as follows:
[source,java]
----
GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using("localhost",8182,"g"));
----
It is also possible to create it from a configuration. The most basic way to do so involves the following line of code:
[source,java]
----
GraphTraversalSource g = traversal().withRemote('conf/remote-graph.properties');
----
The `remote-graph.properties` file simply provides connection information to the `GraphTraversalSource` which is used
to configure a `RemoteConnection`. That file looks like this:
[source,text]
----
gremlin.remote.remoteConnectionClass=org.apache.tinkerpop.gremlin.driver.remote.DriverRemoteConnection
gremlin.remote.driver.clusterFile=conf/remote-objects.yaml
gremlin.remote.driver.sourceName=g
----
The `RemoteConnection` is an interface that provides the transport mechanism for "g" and makes it possible to for
that mechanism to be altered (typically by graph providers who have their own protocols). TinkerPop provides one such
implementation called the `DriverRemoteConnection` which enables transport over Gremlin Server protocols using the
TinkerPop driver. The driver is configured by the specified `gremlin.remote.driver.clusterFile` and the local "g" is
bound to the `GraphTraversalSource` on the remote end with `gremlin.remote.driver.sourceName` which in this case is
also "g".
There are other ways to configure the traversal using `withRemote()` as it has other overloads. It can take an
Apache Commons `Configuration` object which would have keys similar to those shown in the properties file and it
can also take a `RemoteConnection` instance directly. The latter is interesting in that it means it is possible to
programmatically construct all aspects of the `RemoteConnection`. For TinkerPop usage, that might mean directly
constructing the `DriverRemoteConnection` and the driver instance that supplies the transport mechanism. For example,
the command shown above could be re-written using programmatic construction as follows:
[source,java]
----
Cluster cluster = Cluster.open();
GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster, "g"));
----
Please consider the following example:
[gremlin-groovy]
----
g = traversal().withRemote('conf/remote-graph.properties')
g.V().elementMap()
g.close()
----
[source,java]
----
GraphTraversalSource g = traversal().withRemote("conf/remote-graph.properties");
List<Map> list = g.V().elementMap();
g.close();
----
Note the call to `close()` above. The call to `withRemote()` internally instantiates a connection via the driver that
can only be released by "closing" the `GraphTraversalSource`. It is important to take that step to release resources
created in that step.
If working with multiple remote `TraversalSource` instances it is more efficient to construct `Cluster` and `Client
objects and then re-use them.
[gremlin-groovy]
----
cluster = Cluster.open('conf/remote-objects.yaml')
client = cluster.connect()
g = traversal().withRemote(DriverRemoteConnection.using(client, "g"))
g.V().elementMap()
g.close()
client.close()
cluster.close()
----
If the `Client` instance is supplied externally, as is shown above, then it is not closed implicitly by the close of
"g". Closing "g" will have no effect on "client" or "cluster". When supplying them externally, the `Client` and
`Cluster` objects must also be closed explicitly. It's worth noting that the close of a `Cluster` will close all
`Client` instances spawned by the `Cluster`.
IMPORTANT: Bytecode-based traversals use the `TraversalOpProcessor` in Gremlin Server which requires a cache to enable
the retrieval of side-effects (if the `Traversal` produces any). That cache can be configured (e.g. controlling
eviction times and sizing) in the Gremlin Server configuration file as described <<traversalopprocessor, here>>.
Some connection options can also be set on individual requests made through the Java driver using `with()` step
on the `TraversalSource`. For instance to set request timeout to 500 milliseconds:
[source,java]
----
GraphTraversalSource g = traversal().withRemote(conf);
List<Vertex> vertices = g.with(Tokens.ARGS_EVAL_TIMEOUT, 500L).V().out("knows").toList()
----
[[java-imports]]
=== Common Imports
There are a number of classes, functions and tokens that are typically used with Gremlin. The following imports
provide most of the common functionality required to use Gremlin:
[source,java]
----
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.IO;
import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;
import static org.apache.tinkerpop.gremlin.process.traversal.Operator.*;
import static org.apache.tinkerpop.gremlin.process.traversal.Order.*;
import static org.apache.tinkerpop.gremlin.process.traversal.P.*;
import static org.apache.tinkerpop.gremlin.process.traversal.Pop.*;
import static org.apache.tinkerpop.gremlin.process.traversal.SackFunctions.*;
import static org.apache.tinkerpop.gremlin.process.traversal.Scope.*;
import static org.apache.tinkerpop.gremlin.process.traversal.TextP.*;
import static org.apache.tinkerpop.gremlin.structure.Column.*;
import static org.apache.tinkerpop.gremlin.structure.Direction.*;
import static org.apache.tinkerpop.gremlin.structure.T.*;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.*;
----
=== Configuration
The following table describes the various configuration options for the Gremlin Driver:
[width="100%",cols="3,10,^2",options="header"]
|=========================================================
|Key |Description |Default
|connectionPool.channelizer |The fully qualified classname of the client `Channelizer` that defines how to connect to the server. |`Channelizer.WebSocketChannelizer`
|connectionPool.enableSsl |Determines if SSL should be enabled or not. If enabled on the server then it must be enabled on the client. |false
|connectionPool.keepAliveInterval |Length of time in milliseconds to wait on an idle connection before sending a keep-alive request. Set to zero to disable this feature. |180000
|connectionPool.keyStore |The private key in JKS or PKCS#12 format. |_none_
|connectionPool.keyStorePassword |The password of the `keyStore` if it is password-protected. |_none_
|connectionPool.keyStoreType |`JKS` (Java 8 default) or `PKCS12` (Java 9+ default)|_none_
|connectionPool.maxContentLength |The maximum length in bytes that a message can be sent to the server. This number can be no greater than the setting of the same name in the server configuration. |65536
|connectionPool.maxInProcessPerConnection |The maximum number of in-flight requests that can occur on a connection. |4
|connectionPool.maxSimultaneousUsagePerConnection |The maximum number of times that a connection can be borrowed from the pool simultaneously. |16
|connectionPool.maxSize |The maximum size of a connection pool for a host. |8
|connectionPool.maxWaitForConnection |The amount of time in milliseconds to wait for a new connection before timing out. |3000
|connectionPool.maxWaitForClose |The amount of time in milliseconds to wait for pending messages to be returned from the server before closing the connection. |3000
|connectionPool.minInProcessPerConnection |The minimum number of in-flight requests that can occur on a connection. |1
|connectionPool.minSimultaneousUsagePerConnection |The maximum number of times that a connection can be borrowed from the pool simultaneously. |8
|connectionPool.minSize |The minimum size of a connection pool for a host. |2
|connectionPool.reconnectInterval |The amount of time in milliseconds to wait before trying to reconnect to a dead host. |1000
|connectionPool.resultIterationBatchSize |The override value for the size of the result batches to be returned from the server. |64
|connectionPool.sslCipherSuites |The list of JSSE ciphers to support for SSL connections. If specified, only the ciphers that are listed and supported will be enabled. If not specified, the JVM default is used. |_none_
|connectionPool.sslEnabledProtocols |The list of SSL protocols to support for SSL connections. If specified, only the protocols that are listed and supported will be enabled. If not specified, the JVM default is used. |_none_
|connectionPool.sslSkipCertValidation |Configures the `TrustManager` to trust all certs without any validation. Should not be used in production.|false
|connectionPool.trustStore |File location for a SSL Certificate Chain to use when SSL is enabled. If this value is not provided and SSL is enabled, the default `TrustManager` will be used. |_none_
|connectionPool.trustStorePassword |The password of the `trustStore` if it is password-protected |_none_
|connectionPool.validationRequest |A script that is used to test server connectivity. A good script to use is one that evaluates quickly and returns no data. The default simply returns an empty string, but if a graph is required by a particular provider, a good traversal might be `g.inject()`. |_''_
|hosts |The list of hosts that the driver will connect to. |localhost
|jaasEntry |Sets the `AuthProperties.Property.JAAS_ENTRY` properties for authentication to Gremlin Server. |_none_
|nioPoolSize |Size of the pool for handling request/response operations. |available processors
|password |The password to submit on requests that require authentication. |_none_
|path |The URL path to the Gremlin Server. |_/gremlin_
|port |The port of the Gremlin Server to connect to. The same port will be applied for all hosts. |8192
|protocol |Sets the `AuthProperties.Property.PROTOCOL` properties for authentication to Gremlin Server. |_none_
|serializer.className |The fully qualified class name of the `MessageSerializer` that will be used to communicate with the server. Note that the serializer configured on the client should be supported by the server configuration. |_none_
|serializer.config |A `Map` of configuration settings for the serializer. |_none_
|username |The username to submit on requests that require authentication. |_none_
|workerPoolSize |Size of the pool for handling background work. |available processors * 2
|=========================================================
Please see the link:https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/driver/Cluster.Builder.html[Cluster.Builder javadoc] to get more information on these settings.
=== Serialization
Remote systems like Gremlin Server and Remote Gremlin Providers respond to requests made in a particular serialization
format and respond by serializing results to some format to be interpreted by the client. For JVM-based languages,
there are three options for serialization: Gryo, GraphSON and GraphBinary. When using Gryo serialization (the default
serializer for the Java driver), it is important that the client and server have the same serializers configured or
else one or the other will experience serialization exceptions and fail to always communicate. Discrepancy in
serializer registration between client and server can happen fairly easily as graphs will automatically include
serializers on the server-side, thus leaving the client to be configured manually. This can be done manually as
follows:
[source,java]
----
IoRegistry registry = ...; // an IoRegistry instance exposed by a specific graph provider
GryoMapper kryo = GryoMapper.build().addRegistry(registry).create();
MessageSerializer serializer = new GryoMessageSerializerV3d0(kryo);
Cluster cluster = Cluster.build().
serializer(serializer).
create();
Client client = cluster.connect();
GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(client, "g"));
----
The `IoRegistry` tells the serializer what classes from the graph provider to auto-register during serialization.
Gremlin Server roughly uses this same approach when it configures its serializers, so using this same model will
ensure compatibility when making requests. Obviously, it is possible to switch to GraphSON or GraphBinary by building
the appropriate `MessageSerializer` (`GraphSONMessageSerializerV3d0` or `GraphBinaryMessageSerializerV1` respectively)
in the same way and building that into the `Cluster` object.
=== The Lambda Solution
Supporting link:https://en.wikipedia.org/wiki/Anonymous_function[anonymous functions] across languages is difficult as
most languages do not support lambda introspection and thus, code analysis. In Gremlin-Java and with
<<connecting-embedded,embedded>> usage, lambdas can be leveraged directly:
[source,java]
----
g.V().out("knows").map(t -> t.get().value("name") + " is the friend name") <1>
g.V().out("knows").sideEffect(System.out::println) <2>
g.V().as("a").out("knows").as("b").select("b").by((Function<Vertex, Integer>) v -> v.<String>value("name").length()) <3>
----
<1> A Java `Function` is used to map a `Traverser<S>` to an object `E`.
<2> Gremlin steps that take consumer arguments can be passed Java method references.
<3> Gremlin-Java may sometimes require explicit lambda typing when types can not be automatically inferred.
When sending traversals remotely to <<connecting-gremlin-server,Gremlin Server>> or
<<connecting-rgp,Remote Gremlin Providers>>, the static methods of `Lambda` should be used and should denote a
particular JSR-223 `ScriptEngine` that is available on the remote end (typically, this is Groovy). `Lambda` creates a
string-based lambda that is then converted into a lambda/closure/anonymous-function/etc. by the respective lambda
language's JSR-223 `ScriptEngine` implementation.
[source,java]
----
g.V().out("knows").map(Lambda.function("it.get().value('name') + ' is the friend name'"))
g.V().out("knows").sideEffect(Lambda.consumer("println it"))
g.V().as("a").out("knows").as("b").select("b").by(Lambda.<Vertex,Integer>function("it.value('name').length()"))
----
Finally, Gremlin `Bytecode` that includes lambdas requires that the traversal be processed by the
`ScriptEngine`. To avoid continued recompilation costs, it supports the encoding of bindings, which allow Gremlin
Server to cache traversals that will be reused over and over again save that some parameterization may change. Thus,
instead of translating, compiling, and then executing each submitted bytecode request, it is possible to simply
execute. To express bindings in Java, use `Bindings`.
[source,java]
----
b = Bindings.instance()
g.V(b.of('id',1)).out('created').values('name').map{t -> "name: " + t.get() }
g.V(b.of('id',4)).out('created').values('name').map{t -> "name: " + t.get() }
g.V(b.of('id',4)).out('created').values('name').getBytecode()
g.V(b.of('id',4)).out('created').values('name').getBytecode().getBindings()
cluster.close()
----
Both traversals are abstractly defined as `g.V(id).out('created').values('name').map{t -> "name: " + t.get() }` and
thus, the first submission can be cached for faster evaluation on the next submission.
WARNING: It is generally advised to avoid lambda usage. Please consider <<a-note-on-lambdas,A Note On Lambdas>> for
more information.
=== Submitting Scripts
WARNING: TinkerPop does not recommend submitting script-based requests and generally continues to support this feature
for legacy reasons and corner use cases which are still not completely addressed by the Gremlin language. Please
consider using bytecode-based requests instead when possible.
image:gremlin-java.png[width=175,float=left] TinkerPop comes equipped with a reference client for Java-based
applications. It is referred to as `gremlin-driver`, which enables applications to send requests to Gremlin Server
and get back results.
Gremlin scripts are sent to the server from a `Client` instance. A `Client` is created as follows:
[source,java]
----
Cluster cluster = Cluster.open(); <1>
Client client = cluster.connect(); <2>
----
<1> Opens a reference to `localhost` - note that there are many configuration options available in defining a `Cluster` object.
<2> Creates a `Client` given the configuration options of the `Cluster`.
Once a `Client` instance is ready, it is possible to issue some Gremlin Groovy scripts:
[source,java]
----
ResultSet results = client.submit("[1,2,3,4]"); <1>
results.stream().map(i -> i.get(Integer.class) * 2); <2>
CompletableFuture<List<Result>> results = client.submit("[1,2,3,4]").all(); <3>
CompletableFuture<ResultSet> future = client.submitAsync("[1,2,3,4]"); <4>
Map<String,Object> params = new HashMap<>();
params.put("x",4);
client.submit("[1,2,3,x]", params); <5>
----
<1> Submits a script that simply returns a `List` of integers. This method blocks until the request is written to
the server and a `ResultSet` is constructed.
<2> Even though the `ResultSet` is constructed, it does not mean that the server has sent back the results (or even
evaluated the script potentially). The `ResultSet` is just a holder that is awaiting the results from the server.
In this case, they are streamed from the server as they arrive.
<3> Submit a script, get a `ResultSet`, then return a `CompletableFuture` that will be called when all results have been returned.
<4> Submit a script asynchronously without waiting for the request to be written to the server.
<5> Parameterized request are considered the most efficient way to send Gremlin to the server as they can be cached,
which will boost performance and reduce resources required on the server.
==== Per Request Settings
There are a number of overloads to `Client.submit()` that accept a `RequestOptions` object. The `RequestOptions`
provide a way to include options that are specific to the request made with the call to `submit()`. A good use-case for
this feature is to set a per-request override to the `scriptEvaluationTimeout` so that it only applies to the current
request.
[source,java]
----
Cluster cluster = Cluster.open();
Client client = cluster.connect();
RequestOptions options = RequestOptions.build().timeout(500).create();
List<Result> result = client.submit("g.V()", options).all().get();
----
==== Aliases
Scripts submitted to Gremlin Server automatically have the globally configured `Graph` and `TraversalSource` instances
made available to them. Therefore, if Gremlin Server configures two `TraversalSource` instances called "g1" and "g2"
a script can simply reference them directly as:
[source,java]
----
client.submit("g1.V()")
client.submit("g2.V()")
----
While this is an acceptable way to submit scripts, it has the downside of forcing the client to encode the server-side
variable name directly into the script being sent. If the server configuration ever changed such that "g1" became
"g100", the client-side code might have to see a significant amount of change. Decoupling the script code from the
server configuration can be managed by the `alias` method on `Client` as follows:
[source,java]
----
Client g1Client = client.alias("g1")
Client g2Client = client.alias("g2")
g1Client.submit("g.V()")
g2Client.submit("g.V()")
----
The above code demonstrates how the `alias` method can be used such that the script need only contain a reference
to "g" and "g1" and "g2" are automatically rebound into "g" on the server-side.
[[gremlin-java-dsl]]
=== Domain Specific Languages
Creating a <<dsl,Domain Specific Language>> (DSL) in Java requires the `@GremlinDsl` Java annotation in `gremlin-core`.
This annotation should be applied to a "DSL interface" that extends `GraphTraversal.Admin`:
[source,java]
----
@GremlinDsl
public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> {
}
----
IMPORTANT: The name of the DSL interface should be suffixed with "TraversalDSL". All characters in the interface name
before that become the "name" of the DSL.
In this interface, define the methods that the DSL will be composed of:
[source,java]
----
@GremlinDsl
public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> {
public default GraphTraversal<S, Vertex> knows(String personName) {
return out("knows").hasLabel("person").has("name", personName);
}
public default <E2 extends Number> GraphTraversal<S, E2> youngestFriendsAge() {
return out("knows").hasLabel("person").values("age").min();
}
public default GraphTraversal<S, Long> createdAtLeast(int number) {
return outE("created").count().is(P.gte(number));
}
}
----
IMPORTANT: Follow the TinkerPop convention of using `<S,E>` in naming generics as those conventions are taken into
account when generating the anonymous traversal class. The processor attempts to infer the appropriate type parameters
when generating the anonymous traversal class. If it cannot do it correctly, it is possible to avoid the inference by
using the `GremlinDsl.AnonymousMethod` annotation on the DSL method. It allows explicit specification of the types to
use.
The `@GremlinDsl` annotation is used by the link:https://docs.oracle.com/javase/8/docs/api/index.html?javax/annotation/processing/Processor.html[Java Annotation Processor]
to generate the boilerplate class structure required to properly use the DSL within the TinkerPop framework. These
classes can be generated and maintained by hand, but it would be time consuming, monotonous and error-prone to do so.
Typically, the Java compilation process is automatically configured to detect annotation processors on the classpath
and will automatically use them when found. If that does not happen, it may be necessary to make configuration changes
to the build to allow for the compilation process to be aware of the following `javax.annotation.processing.Processor`
implementation:
[source,java]
----
org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDslProcessor
----
The annotation processor will generate several classes for the DSL:
* `SocialTraversal` - A `Traversal` interface that extends the `SocialTraversalDsl` proxying methods to its underlying
interfaces (such as `GraphTraversal`) to instead return a `SocialTraversal`
* `DefaultSocialTraversal` - A default implementation of `SocialTraversal` (typically not used directly by the user)
* `SocialTraversalSource` - Spawns `DefaultSocialTraversal` instances.
* `__` - Spawns anonymous `DefaultSocialTraversal` instances.
Using the DSL then just involves telling the `Graph` to use it:
[source,java]
----
SocialTraversalSource social = graph.traversal(SocialTraversalSource.class);
social.V().has("name","marko").knows("josh");
----
The `SocialTraversalSource` can also be customized with DSL functions. As an additional step, include a class that
extends from `GraphTraversalSource` and with a name that is suffixed with "TraversalSourceDsl". Include in this class,
any custom methods required by the DSL:
[source,java]
----
public class SocialTraversalSourceDsl extends GraphTraversalSource {
public SocialTraversalSourceDsl(Graph graph, TraversalStrategies traversalStrategies) {
super(graph, traversalStrategies);
}
public SocialTraversalSourceDsl(Graph graph) {
super(graph);
}
public GraphTraversal<Vertex, Vertex> persons(String... names) {
GraphTraversalSource clone = this.clone();
// Manually add a "start" step for the traversal in this case the equivalent of V(). GraphStep is marked
// as a "start" step by passing "true" in the constructor.
clone.getBytecode().addStep(GraphTraversal.Symbols.V);
GraphTraversal<Vertex, Vertex> traversal = new DefaultGraphTraversal<>(clone);
traversal.asAdmin().addStep(new GraphStep<>(traversal.asAdmin(), Vertex.class, true));
traversal = traversal.hasLabel("person");
if (names.length > 0) traversal = traversal.has("name", P.within(names));
return traversal;
}
}
----
Then, back in the `SocialTraversal` interface, update the `GremlinDsl` annotation with the `traversalSource` argument
to point to the fully qualified class name of the `SocialTraversalSourceDsl`:
[source,java]
----
@GremlinDsl(traversalSource = "com.company.SocialTraversalSourceDsl")
public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> {
...
}
----
It is then possible to use the `persons()` method to start traversals:
[source,java]
----
SocialTraversalSource social = graph.traversal(SocialTraversalSource.class);
social.persons("marko").knows("josh");
----
NOTE: Using Maven, as shown in the `gremlin-archetype-dsl` module, makes developing DSLs with the annotation processor
straightforward in that it sets up appropriate paths to the generated code automatically.
anchor:java-application-examples[]
[[gremlin-archetypes]]
=== Application Examples
The available link:https://maven.apache.org/guides/introduction/introduction-to-archetypes.html[Maven archetypes] are
as follows:
* `gremlin-archetype-dsl` - An example project that demonstrates how to build Domain Specific Languages with Gremlin
in Java.
* `gremlin-archetype-server` - An example project that demonstrates the basic structure of a
<<gremlin-server,Gremlin Server>> project, how to connect with the Gremlin Driver, and how to embed Gremlin Server in
a testing framework.
* `gremlin-archetype-tinkergraph` - A basic example of how to structure a TinkerPop project with Maven.
Use Maven to generate these example projects with a command like:
[source,shell]
$ mvn archetype:generate -DarchetypeGroupId=org.apache.tinkerpop -DarchetypeArtifactId=gremlin-archetype-server \
-DarchetypeVersion=x.y.z -DgroupId=com.my -DartifactId=app -Dversion=0.1 -DinteractiveMode=false
This command will generate a new Maven project in a directory called "app" with a `pom.xml` specifying a `groupId` of
`com.my`. Please see the `README.asciidoc` in the root of each generated project for information on how to build and
execute it.
[[gremlin-groovy]]
== Gremlin-Groovy
image:gremlin-groovy-drawing.png[width=130,float=right] Apache TinkerPop's Gremlin-Groovy implements Gremlin within the
link:http://groovy.apache.org[Apache Groovy] language. As a JVM-based language variant, Gremlin-Groovy is backed by
Gremlin-Java constructs. Moreover, given its scripting nature, Gremlin-Groovy serves as the language of
<<gremlin-console,Gremlin Console>> and <<gremlin-server,Gremlin Server>>.
[source,groovy]
----
compile group: 'org.apache.tinkerpop', name: 'gremlin-core', version: '3.3.4'
compile group: 'org.apache.tinkerpop', name: 'gremlin-driver', version: '3.3.4'
----
[[gremlin-groovy-differences]]
=== Differences
In Groovy, `as`, `in`, and `not` are reserved words. Gremlin-Groovy does not allow these steps to be called
statically from the anonymous traversal `+__+` and therefore, must always be prefixed with `+__.+` For instance:
`+g.V().as('a').in().as('b').where(__.not(__.as('a').out().as('b')))+`
[[gremlin-python]]
== Gremlin-Python
image:gremlin-python-drawing.png[width=130,float=right] Apache TinkerPop's Gremlin-Python implements Gremlin within
the link:https://www.python.org/[Python] language and can be used on any Python virtual machine including the popular
link:https://en.wikipedia.org/wiki/CPython[CPython] machine. Python's syntax has the same constructs as Java including
"dot notation" for function chaining (`a.b.c`), round bracket function arguments (`a(b,c)`), and support for global
namespaces (`a(b())` vs `a(__.b())`). As such, anyone familiar with Gremlin-Java will immediately be able to work
with Gremlin-Python. Moreover, there are a few added constructs to Gremlin-Python that make traversals a bit more
succinct.
To install Gremlin-Python, use Python's link:https://en.wikipedia.org/wiki/Pip_(package_manager)[pip] package manager.
[source,bash]
----
pip install gremlinpython
----
=== Connecting
The pattern for connecting is described in <<connecting-gremlin,Connecting Gremlin>> and it basically distills down to
creating a `GraphTraversalSource`. A `GraphTraversalSource` is created from the anonymous `traversal()` method where
the "g" provided to the `DriverRemoteConnection` corresponds to the name of a `GraphTraversalSource` on the remote end.
[source,python]
----
g = traversal().withRemote(DriverRemoteConnection('ws://localhost:8182/gremlin','g'))
----
If you need to send additional headers in the websockets connection, you can pass an optional `headers` parameter
to the `DriverRemoteConnection` constructor.
[source,python]
----
g = traversal().withRemote(DriverRemoteConnection('ws://localhost:8182/gremlin','g',headers={'Header':'Value'}))
----
[[python-imports]]
=== Common Imports
There are a number of classes, functions and tokens that are typically used with Gremlin. The following imports
provide most of the typical functionality required to use Gremlin:
[source,python]
----
from gremlin_python import statics
from gremlin_python.process.anonymous_traversal import traversal
from gremlin_python.process.graph_traversal import __
from gremlin_python.process.strategies import *
from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection
from gremlin_python.process.traversal import T
from gremlin_python.process.traversal import Order
from gremlin_python.process.traversal import Cardinality
from gremlin_python.process.traversal import Column
from gremlin_python.process.traversal import Direction
from gremlin_python.process.traversal import Operator
from gremlin_python.process.traversal import P
from gremlin_python.process.traversal import Pop
from gremlin_python.process.traversal import Scope
from gremlin_python.process.traversal import Barrier
from gremlin_python.process.traversal import Bindings
from gremlin_python.process.traversal import WithOptions
----
These can be used analogously to how they are used in Gremlin-Java.
[gremlin-python,modern]
----
g.V().hasLabel('person').has('age',P.gt(30)).order().by('age',Order.desc).toList()
----
Moreover, by importing the `statics` of Gremlin-Python, the class prefixes can be omitted.
[source,python]
----
>>> statics.load_statics(globals())
----
With statics loaded its possible to represent the above traversal as below.
[gremlin-python,modern]
----
g.V().hasLabel('person').has('age',gt(30)).order().by('age',desc).toList()
----
Finally, statics includes all the `+__+`-methods and thus, anonymous traversals like `+__.out()+` can be expressed as below.
That is, without the `+__+`-prefix.
[gremlin-python,modern]
----
g.V().repeat(out()).times(2).name.fold().toList()
----
=== Configuration
The following table describes the various configuration options for the Gremlin-Python Driver. They
can be passed to the `Client` or `DriverRemoteConnection` instance as keyword arguments:
[width="100%",cols="3,10,^2",options="header"]
|=========================================================
|Key |Description |Default
|protocol_factory |A callable that returns an instance of `AbstractBaseProtocol`. |`gremlin_python.driver.protocol.GremlinServerWSProtocol`
|transport_factory |A callable that returns an instance of `AbstractBaseTransport`. |`gremlin_python.driver.tornado.transport.TornadoTransport`
|pool_size |The number of connections used by the pool. |4
|max_workers |Maximum number of worker threads. |Number of CPUs * 5
|message_serializer |The message serializer implementation.|`gremlin_python.driver.serializer.GraphSONMessageSerializer`
|password |The password to submit on requests that require authentication. |""
|username |The username to submit on requests that require authentication. |""
|session | A unique string-based identifier (typically a UUID) to enable a <<sessions,session-based connection>>. This is not a valid configuration for `DriverRemoteConnection`. |None
|=========================================================
=== Traversal Strategies
In order to add and remove <<traversalstrategy,traversal strategies>> from a traversal source, Gremlin-Python has a
`TraversalStrategy` class along with a collection of subclasses that mirror the standard Gremlin-Java strategies.
[gremlin-python,modern]
----
g = g.withStrategies(SubgraphStrategy(vertices=hasLabel('person'),edges=has('weight',gt(0.5))))
g.V().name.toList()
g.V().outE().elementMap().toList()
g = g.withoutStrategies(SubgraphStrategy)
g.V().name.toList()
g.V().outE().elementMap().toList()
g = g.withComputer(workers=2,vertices=has('name','marko'))
g.V().name.toList()
g.V().outE().valueMap().with_(WithOptions.tokens).toList()
----
NOTE: Many of the `TraversalStrategy` classes in Gremlin-Python are proxies to the respective strategy on
Apache TinkerPop's JVM-based Gremlin traversal machine. As such, their `apply(Traversal)` method does nothing. However,
the strategy is encoded in the Gremlin-Python bytecode and transmitted to the Gremlin traversal machine for
re-construction machine-side.
=== The Lambda Solution
Supporting link:https://en.wikipedia.org/wiki/Anonymous_function[anonymous functions] across languages is difficult as
most languages do not support lambda introspection and thus, code analysis. In Gremlin-Python,
a link:https://docs.python.org/2/reference/expressions.html#lambda[Python lambda] should be represented as a zero-arg
callable that returns a string representation of a lambda. The default lambda language is `gremlin-python` and can be
changed via `gremlin_python.statics.default_lambda_language`. When the lambda is represented in `Bytecode` its language
is encoded such that the remote connection host can infer which translator and ultimate execution engine to use.
[gremlin-python,modern]
----
g.V().out().map(lambda: "lambda x: len(x.get().value('name'))").sum().toList() <1>
statics.default_lambda_language <2>
g.V().out().map(lambda: ("it.get().value('name').length()", "gremlin-groovy")).sum().toList() <3>
statics.default_lambda_language = 'gremlin-groovy' <4>
g.V().out().map(lambda: "it.get().value('name').length()").sum().toList() <5>
g.V().out().map(lambda: ("lambda x: len(x.get().value('name'))", "gremlin-python")).sum().toList() <6>
statics.default_lambda_language = 'gremlin-python' <7>
g.V().out().map(lambda: "x: len(x.get().value('name'))").sum().toList() <8>
----
<1> A zero-arg lambda yields a string representation of a lambda in Gremlin-Python.
<2> The default lambda language is currently Gremlin-Python.
<3> A zero-arg lambda yields a 2-tuple where the second element is the language of the lambda (Gremlin-Groovy).
<4> The default lambda language can be statically changed.
<5> A zero-arg lambda yields a string representation of a closure in Gremlin-Groovy.
<6> A zero-arg lambda yields a 2-tuple where the second element is the language of the lambda (Gremlin-Python).
<7> The default lambda language is changed back to Gremlin-Python.
<8> If the `lambda`-prefix is not provided, then it is appended automatically in order to give a more natural look to the expression.
TIP: When running into situations where Groovy cannot properly discern a method signature based on the `Lambda`
instance created, it will help to fully define the closure in the lambda expression - so rather than
`lambda: ("it.get().value("name')","gremlin-groovy")`, prefer `lambda: ("x -> x.get().value("name"),"gremlin-groovy")`.
WARNING: Jython support has been deprecated as for 3.3.10 and will be removed in 3.5.0. Gremlin-Python will at that
point default to Groovy for lambda processing and Python lambdas will not be supported.
Finally, Gremlin `Bytecode` that includes lambdas requires that the traversal be processed by the
`ScriptEngine`. To avoid continued recompilation costs, it supports the encoding of bindings, which allow a remote
engine to to cache traversals that will be reused over and over again save that some parameterization may change. Thus,
instead of translating, compiling, and then executing each submitted bytecode, it is possible to simply execute.
[gremlin-python,modern]
----
g.V(Bindings.of('id',1)).out('created').map(lambda: ("it.get().value('name').length()", "gremlin-groovy")).sum().toList()
g.V(Bindings.of('id',4)).out('created').map(lambda: ("it.get().value('name').length()", "gremlin-groovy")).sum().toList()
----
==== Native Python Lambdas
To process lambdas in Python, the `GremlinJythonScriptEngine` must be enabled on the remote end. If that remote is
Gremlin Server, then these instructions can help configuration it. As an example, the
`conf/gremlin-server-modern-py.yaml` configuration maintains a `GremlinJythonScriptEngine`.
[source,bash]
----
$ bin/gremlin-server.sh install org.apache.tinkerpop gremlin-python x.y.z
$ bin/gremlin-server.sh conf/gremlin-server-modern-py.yaml
[INFO] GremlinServer -
\,,,/
(o o)
---oOOo-(3)-oOOo---
[INFO] GremlinServer - Configuring Gremlin Server from conf/gremlin-server-modern-py.yaml
[INFO] MetricManager - Configured Metrics Slf4jReporter configured with interval=180000ms and loggerName=org.apache.tinkerpop.gremlin.server.Settings$Slf4jReporterMetrics
[INFO] GraphManager - Graph [graph] was successfully configured via [conf/tinkergraph-empty.properties].
[INFO] ServerGremlinExecutor - Initialized Gremlin thread pool. Threads in pool named with pattern gremlin-*
[INFO] ScriptEngines - Loaded gremlin-jython ScriptEngine
[INFO] ScriptEngines - Loaded gremlin-python ScriptEngine
[INFO] ScriptEngines - Loaded gremlin-groovy ScriptEngine
[INFO] GremlinExecutor - Initialized gremlin-groovy ScriptEngine with scripts/generate-modern.groovy
[INFO] ServerGremlinExecutor - Initialized GremlinExecutor and configured ScriptEngines.
[INFO] ServerGremlinExecutor - A GraphTraversalSource is now bound to [g] with graphtraversalsource[tinkergraph[vertices:0 edges:0], standard]
[INFO] OpLoader - Adding the standard OpProcessor.
[INFO] OpLoader - Adding the session OpProcessor.
[INFO] OpLoader - Adding the traversal OpProcessor.
[INFO] TraversalOpProcessor - Initialized cache for TraversalOpProcessor with size 1000 and expiration time of 600000 ms
[INFO] GremlinServer - Executing start up LifeCycleHook
[INFO] Logger$info - Loading 'modern' graph data.
[WARN] AbstractChannelizer - The org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0 serialization class is deprecated.
[INFO] AbstractChannelizer - Configured application/vnd.gremlin-v3.0+gryo with org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0
[WARN] AbstractChannelizer - The org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0 serialization class is deprecated.
[INFO] AbstractChannelizer - Configured application/vnd.gremlin-v3.0+gryo-stringd with org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0
[INFO] AbstractChannelizer - Configured application/vnd.gremlin-v3.0+json with org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0
[INFO] AbstractChannelizer - Configured application/json with org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0
[INFO] AbstractChannelizer - Configured application/vnd.graphbinary-v1.0 with org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1
[INFO] AbstractChannelizer - Configured application/vnd.graphbinary-v1.0-stringd with org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1
[INFO] GremlinServer$1 - Gremlin Server configured with worker thread pool of 1, gremlin pool of 8 and boss thread pool of 1.
[INFO] GremlinServer$1 - Channel started at port 8182.
----
NOTE: The command to use `install` need only be executed once to gather `gremlin-python` dependencies into Gremlin Servers'
path. Future starts of Gremlin Server will not require that command.
WARNING: As explained throughout the documentation, when possible <<a-note-on-lambdas,avoid>> lambdas. If lambdas
must be used, then consider submitting Groovy lambdas as opposed to Python-based ones. The `GremlinGroovyScriptEngine`
is far more featured and performant than its Jython sibling and will likely yield better results.
=== Submitting Scripts
WARNING: TinkerPop does not recommend submitting script-based requests and generally continues to support this feature
for legacy reasons and corner use cases which are still not completely addressed by the Gremlin language. Please
consider using bytecode-based requests instead when possible.
The `Client` class implementation/interface is based on the Java Driver, with some restrictions. Most notably,
Gremlin-Python does not yet implement the `Cluster` class. Instead, `Client` is instantiated directly.
Usage is as follows:
[source,python]
----
from gremlin_python.driver import client <1>
client = client.Client('ws://localhost:8182/gremlin', 'g') <2>
----
<1> Import the Gremlin-Python `client` module.
<2> Opens a reference to `localhost` - note that there are various configuration options that can be passed
to the `Client` object upon instantiation as keyword arguments.
Once a `Client` instance is ready, it is possible to issue some Gremlin:
[source,python]
----
result_set = client.submit("[1,2,3,4]") <1>
future_results = result_set.all() <2>
results = future_results.result() <3>
assert results == [1, 2, 3, 4] <4>
future_result_set = client.submitAsync("[1,2,3,4]") <5>
result_set = future_result_set.result() <6>
result = result_set.one() <7>
assert results == [1, 2, 3, 4] <8>
assert result_set.done.done() <9>
client.close() <10>
----
<1> Submit a script that simply returns a `List` of integers. This method blocks until the request is written to
the server and a `ResultSet` is constructed.
<2> Even though the `ResultSet` is constructed, it does not mean that the server has sent back the results (or even
evaluated the script potentially). The `ResultSet` is just a holder that is awaiting the results from the server. The `all` method
returns a `concurrent.futures.Future` that resolves to a list when it is complete.
<3> Block until the the script is evaluated and results are sent back by the server.
<4> Verify the result.
<5> Submit the same script to the server but don't block.
<6> Wait until request is written to the server and `ResultSet` is constructed.
<7> Read a single result off the result stream.
<8> Again, verify the result.
<9> Verify that the all results have been read and stream is closed.
<10> Close client and underlying pool connections.
[[gremlin-python-dsl]]
=== Domain Specific Languages
Writing a Gremlin <<dsl,Domain Specific Language>> (DSL) in Python simply requires direct extension of several classes:
* `GraphTraversal` - which exposes the various steps used in traversal writing
* `__` - which spawns anonymous traversals from steps
* `GraphTraversalSource` - which spawns `GraphTraversal` instances
The Social DSL based on the link:https://tinkerpop.apache.org/docs/current/images/tinkerpop-modern.png["modern" toy graph]
might look like this:
[source,python]
----
class SocialTraversal(GraphTraversal):
def knows(self, person_name):
return self.out("knows").hasLabel("person").has("name", person_name)
def youngestFriendsAge(self):
return self.out("knows").hasLabel("person").values("age").min()
def createdAtLeast(self, number):
return self.outE("created").count().is_(P.gte(number))
class __(AnonymousTraversal):
graph_traversal = SocialTraversal
@classmethod
def knows(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).knows(*args)
@classmethod
def youngestFriendsAge(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).youngestFriendsAge(*args)
@classmethod
def createdAtLeast(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).createdAtLeast(*args)
class SocialTraversalSource(GraphTraversalSource):
def __init__(self, *args, **kwargs):
super(SocialTraversalSource, self).__init__(*args, **kwargs)
self.graph_traversal = SocialTraversal
def persons(self, *args):
traversal = self.get_graph_traversal()
traversal.bytecode.add_step("V")
traversal.bytecode.add_step("hasLabel", "person")
if len(args) > 0:
traversal.bytecode.add_step("has", "name", P.within(args))
return traversal
----
NOTE: The `AnonymousTraversal` class above is just an alias for `+__+` as in
`+from gremlin_python.process.graph_traversal import __ as AnonymousTraversal+`
Using the DSL is straightforward and just requires that the graph instance know the `SocialTraversalSource` should
be used:
[source,python]
----
social = Graph().traversal(SocialTraversalSource).withRemote(DriverRemoteConnection('ws://localhost:8182/gremlin','g'))
social.persons("marko").knows("josh")
social.persons("marko").youngestFriendsAge()
social.persons().filter(__.createdAtLeast(2)).count()
----
=== Syntactic Sugar
Python supports meta-programming and operator overloading. There are three uses of these techniques in Gremlin-Python
that makes traversals a bit more concise.
[gremlin-python,modern]
----
g.V().both()[1:3].toList()
g.V().both()[1].toList()
g.V().both().name.toList()
----
[[gremlin-python-differences]]
=== Differences
In situations where Python reserved words and global functions overlap with standard Gremlin steps and tokens, those
bits of conflicting Gremlin get an underscore appended as a suffix:
*Steps* - <<and-step,and_()>>, <<as-step,as_()>>, <<from-step,from_()>>, <<is-step,is_()>>, <<in-step,in_()>>,
<<not-step,not_()>>, <<or-step,or_()>>, <<with-step,with_()>>
*Tokens* - <<a-note-on-scopes,Scope.global_>>
=== Limitations
* Traversals that return a `Set` *might* be coerced to a `List` in Python. In the case of Python, number equality
is different from JVM languages which produces different `Set` results when those types are in use. When this case
is detected during deserialization, the `Set` is coerced to a `List` so that traversals return consistent
results within a collection across different languages. If a `Set` is needed then convert `List` results
to `Set` manually.
* Gremlin is capable of returning `Dictionary` results that use non-hashable keys (e.g. Dictionary as a key) and Python
does not support that at a language level. Gremlin that returns such results will need to be re-written to avoid that.
=== Application Examples
The TinkerPop source code contains a simple Python script that shows a basic example of how gremlinpython works. It
can be found in GitHub link:https://github.com/apache/tinkerpop/tree/xx.yy.zz/gremlin-python/src/main/jython/example.py[here]
and is designed to work best with a running <<gremlin-server,Gremlin Server>> configured with the default
`conf/gremlin-server.yaml` file as included with the standard release packaging.
[source,shell]
----
pip install gremlinpython
pip install tornado
python example.py
----
anchor:gremlin-DotNet[]
[[gremlin-dotnet]]
== Gremlin.Net
image:gremlin-dotnet-logo.png[width=371,float=right] Apache TinkerPop's Gremlin.Net implements Gremlin within the C#
language. It targets .NET Standard and can therefore be used on different operating systems and with different .NET
frameworks, such as .NET Framework and link:https://www.microsoft.com/net/core[.NET Core]. Since the C# syntax is very
similar to that of Java, it should be very easy to switch between Gremlin-Java and Gremlin.Net. The only major
syntactical difference is that all method names in Gremlin.Net use PascalCase as opposed to camelCase in Gremlin-Java
in order to comply with .NET conventions.
[source,powershell]
nuget install Gremlin.Net
=== Connecting
The pattern for connecting is described in <<connecting-gremlin,Connecting Gremlin>> and it basically distills down to
creating a `GraphTraversalSource`. A `GraphTraversalSource` is created from the `AnonymousTraversalSource.traversal()`
method where the "g" provided to the `DriverRemoteConnection` corresponds to the name of a `GraphTraversalSource` on
the remote end.
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=connecting]
----
=== Common Imports
There are a number of classes, functions and tokens that are typically used with Gremlin. The following imports
provide most of the typical functionality required to use Gremlin:
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=commonImports]
----
=== Configuration
The connection properties for the Gremlin.Net driver can be passed to the `GremlinServer` instance as keyword arguments:
[width="100%",cols="3,10,^2",options="header"]
|=========================================================
|Key |Description |Default
|hostname |The hostname that the driver will connect to. |localhost
|port |The port on which Gremlin Server can be reached. |8182
|enableSsl |Determines if SSL should be enabled or not. If enabled on the server then it must be enabled on the client. |false
|username |The username to submit on requests that require authentication. |_none_
|password |The password to submit on requests that require authentication. |_none_
|=========================================================
==== Connection Pool
It is also possible to configure the `ConnectionPool` of the Gremlin.Net driver.
These configuration options can be set as properties
on the `ConnectionPoolSettings` instance that can be passed to the `GremlinClient`:
[width="100%",cols="3,10,^2",options="header"]
|=========================================================
|Key |Description |Default
|PoolSize |The size of the connection pool. |4
|MaxInProcessPerConnection |The maximum number of in-flight requests that can occur on a connection. |32
|=========================================================
A `NoConnectionAvailableException` is thrown if all connections have reached the `MaxInProcessPerConnection` limit
when a new request comes in.
==== Serialization
The Gremlin.Net driver uses by default GraphSON 3.0 but it is also possible to use GraphSON 2.0 which can be necessary
when the server does not support GraphSON 3.0 yet:
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=serialization]
----
=== Traversal Strategies
In order to add and remove traversal strategies from a traversal source, Gremlin.Net has an `AbstractTraversalStrategy`
class along with a collection of subclasses that mirror the standard Gremlin-Java strategies.
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=traversalStrategies]
----
NOTE: Many of the TraversalStrategy classes in Gremlin.Net are proxies to the respective strategy on Apache TinkerPop’s
JVM-based Gremlin traversal machine. As such, their `Apply(ITraversal)` method does nothing. However, the strategy is
encoded in the Gremlin.Net bytecode and transmitted to the Gremlin traversal machine for re-construction machine-side.
=== The Lambda Solution
Supporting link:https://en.wikipedia.org/wiki/Anonymous_function[anonymous functions] across languages is difficult as
most languages do not support lambda introspection and thus, code analysis. While Gremlin.Net doesn't support C# lambdas, it
is still able to represent lambdas in other languages. When the lambda is represented in `Bytecode` its language is encoded
such that the remote connection host can infer which translator and ultimate execution engine to use.
[source,csharp]
----
g.V().Out().Map<int>(Lambda.Groovy("it.get().value('name').length()")).Sum<int>().ToList(); <1>
g.V().Out().Map<int>(Lambda.Python("lambda x: len(x.get().value('name'))")).Sum<int>().ToList(); <2>
----
<1> `Lambda.Groovy()` can be used to create a Groovy lambda.
<2> `Lambda.Python()` can be used to create a Python lambda.
The `ILambda` interface returned by these two methods inherits interfaces like `IFunction` and `IPredicate` that mirror
their Java counterparts which makes it possible to use lambdas with Gremlin.Net for the same steps as in Gremlin-Java.
TIP: When running into situations where Groovy cannot properly discern a method signature based on the `Lambda`
instance created, it will help to fully define the closure in the lambda expression - so rather than
`Lambda.Groovy("it.get().value('name'))`, prefer `Lambda.Groovy("x -> x.get().value('name'))`.
=== Submitting Scripts
WARNING: TinkerPop does not recommend submitting script-based requests and generally continues to support this feature
for legacy reasons and corner use cases which are still not completely addressed by the Gremlin language. Please
consider using bytecode-based requests instead when possible.
Gremlin scripts are sent to the server from a `IGremlinClient` instance. A `IGremlinClient` is created as follows:
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=submittingScripts]
----
If the remote system has authentication and SSL enabled, then the `GremlinServer` object can be configured as follows:
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=submittingScriptsWithAuthentication]
----
It is also possible to initialize the `Client` to use <<sessions,sessions>>:
[source,csharp]
----
var gremlinServer = new GremlinServer("localhost", 8182);
var client = new GremlinClient(gremlinServer, sessionId: Guid.NewGuid().ToString()))
----
[[gremlin-net-dsl]]
=== Domain Specific Languages
Developing a <<dsl,Domain Specific Language>> (DSL) for .Net is most easily implemented using
link:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods[Extension Methods]
as they don't require direct extension of classes in the TinkerPop hierarchy. Extension Method classes simply need to
be constructed for the `GraphTraversal` and the `GraphTraversalSource`. Unfortunately, anonymous traversals (spawned
from `+__+`) can't use the Extension Method approach as they do not work for static classes and static classes can't be
extended. The only option is to re-implement the methods of `+__+` as a wrapper in the anonymous traversal for the DSL
or to simply create a static class for the DSL and use the two anonymous traversals creators independently. The
following example uses the latter approach as it saves a lot of boilerplate code with the minor annoyance of having a
second static class to deal with when writing traversals rather than just calling `+__+` for everything.
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsDsl.cs[tags=dsl]
----
Note the creation of `__Social` as the Social DSL's "extension" to the available ways in which to spawn anonymous
traversals. The use of the double underscore prefix in the name is just a convention to consider using and is not a
requirement. To use the DSL, bring it into scope with the `using` directive:
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsDslTests.cs[tags=dslUsing]
----
and then it can be called from the application as follows:
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsDslTests.cs[tags=dslExamples]
----
anchor:gremlin-dotnet-template[]
[[dotnet-application-examples]]
=== Application Examples
This link:https://docs.microsoft.com/dotnet/core/tools/custom-templates[dotnet template] helps getting started with
<<gremlin-dotnet,Gremlin.Net>>. It creates a new C# console project that shows how to connect to a
<<gremlin-server,Gremlin Server>> with Gremlin.Net.
You can install the template with the dotnet CLI tool:
[source,shell]
dotnet new -i Gremlin.Net.Template
After the template is installed, a new project based on this template can be installed:
[source,shell]
dotnet new gremlin
Specify the output directory for the new project which will then also be used as the name of the created project:
[source,shell]
dotnet new gremlin -o MyFirstGremlinProject
[[gremlin-javascript]]
== Gremlin-JavaScript
image:gremlin-js.png[width=130,float=right] Apache TinkerPop's Gremlin-JavaScript implements Gremlin within the
JavaScript language. It targets Node.js runtime and can be used on different operating systems on any Node.js 6 or
above. Since the JavaScript naming conventions are very similar to that of Java, it should be very easy to switch
between Gremlin-Java and Gremlin-JavaScript.
[source,bash]
npm install gremlin
=== Connecting
The pattern for connecting is described in <<connecting-gremlin,Connecting Gremlin>> and it basically distills down to
creating a `GraphTraversalSource`. A `GraphTraversalSource` is created from the `AnonymousTraversalSource.traversal()`
method where the "g" provided to the `DriverRemoteConnection` corresponds to the name of a `GraphTraversalSource` on
the remote end.
[source,javascript]
----
const g = traversal().withRemote(new DriverRemoteConnection('ws://localhost:8182/gremlin'));
----
Gremlin-JavaScript supports plain text SASL authentication, you can set it on the connection options.
[source,javascript]
----
const authenticator = new gremlin.driver.auth.PlainTextSaslAuthenticator('myuser', 'mypassword');
const g = traversal().withRemote(new DriverRemoteConnection('ws://localhost:8182/gremlin', { authenticator });
----
Given that I/O operations in Node.js are asynchronous by default, <<terminal-steps,Terminal Steps>> return a `Promise`:
* `Traversal.toList()`: Returns a `Promise` with an `Array` as result value.
* `Traversal.next()`: Returns a `Promise` with a `{ value, done }` tuple as result value, according to the
link:https://github.com/tc39/proposal-async-iteration[async iterator proposal].
* `Traversal.iterate()`: Returns a `Promise` without a value.
For example:
[source,javascript]
----
g.V().hasLabel('person').values('name').toList()
.then(names => console.log(names));
----
When using `async` functions it is possible to `await` the promises:
[source,javascript]
----
const names = await g.V().hasLabel('person').values('name').toList();
console.log(names);
----
=== Common Imports
There are a number of classes, functions and tokens that are typically used with Gremlin. The following imports
provide most of the typical functionality required to use Gremlin:
[source,javascript]
----
const gremlin = require('gremlin');
const traversal = gremlin.process.AnonymousTraversalSource.traversal;
const __ = gremlin.process.statics;
const DriverRemoteConnection = gremlin.driver.DriverRemoteConnection;
const column = gremlin.process.traversal.column
const direction = gremlin.process.traversal.direction
const p = gremlin.process.traversal.P
const pick = gremlin.process.traversal.pick
const pop = gremlin.process.traversal.pop
const order = gremlin.process.traversal.order
const scope = gremlin.process.traversal.scope
const t = gremlin.process.traversal.t
----
=== Submitting Scripts
WARNING: TinkerPop does not recommend submitting script-based requests and generally continues to support this feature
for legacy reasons and corner use cases which are still not completely addressed by the Gremlin language. Please
consider using bytecode-based requests instead when possible.
It is possible to submit parametrized Gremlin scripts to the server as strings, using the `Client` class:
[source,javascript]
----
const gremlin = require('gremlin');
const client = new gremlin.driver.Client('ws://localhost:8182/gremlin', { traversalSource: 'g' });
const result1 = await client.submit('g.V(vid)', { vid: 1 });
const vertex = result1.first();
const result2 = await client.submit('g.V().hasLabel(label).tail(n)', { label: 'person', n: 3 });
// ResultSet is an iterable
for (const vertex of result2) {
console.log(vertex.id);
}
----
It is also possible to initialize the `Client` to use <<sessions,sessions>>:
[source,javascript]
----
const client = new gremlin.driver.Client('ws://localhost:8182/gremlin', { traversalSource: 'g', 'session': 'unique-string-id' });
----
With this configuration, the state of variables within scripts are preserved between requests.
[[gremlin-javascript-dsl]]
=== Domain Specific Languages
Developing Gremlin DSLs in JavaScript largely requires extension of existing core classes with use of standalone
functions for anonymous traversal spawning. The pattern is demonstrated in the following example:
[source,javascript]
----
class SocialTraversal extends GraphTraversal {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode);
}
aged(age) {
return this.has('person', 'age', age);
}
}
class SocialTraversalSource extends GraphTraversalSource {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode, SocialTraversalSource, SocialTraversal);
}
person(name) {
return this.V().has('person', 'name', name);
}
}
function anonymous() {
return new SocialTraversal(null, null, new Bytecode());
}
function aged(age) {
return anonymous().aged(age);
}
----
`SocialTraversal` extends the core `GraphTraversal` class and has a three argument constructor which is immediately
proxied to the `GraphTraversal` constructor. New DSL steps are then added to this class using available steps to
construct the underlying traversal to execute as demonstrated in the `aged()` step.
The `SocialTraversal` is spawned from a `SocialTraversalSource` which is extended from `GraphTraversalSource`. Steps
added here are meant to be start steps. In the above case, the `person()` start step find a "person" vertex to begin
the traversal from.
Typically, steps that are made available on a `GraphTraversal` (i.e. SocialTraversal in this example) should also be
made available as spawns for anonymous traversals. The recommendation is that these steps be exposed in the module
as standalone functions. In the example above, the standalone `aged()` step creates an anonymous traversal through
an `anonymous()` utility function. The method for creating these standalone functions can be handled in other ways if
desired.
To use the DSL, simply initialize the `g` as follows:
[source,javascript]
----
const g = traversal(SocialTraversalSource).withRemote(connection);
g.person('marko').aged(29).values('name').toList().
then(names => console.log(names));
----
[[gremlin-javascript-differences]]
=== Differences
In situations where Javascript reserved words and global functions overlap with standard Gremlin steps and tokens, those
bits of conflicting Gremlin get an underscore appended as a suffix:
*Steps* - <<from-step,from_()>>, <<in-step,in_()>>, <<with-step,with_()>>