blob: 604c3a36c0127cecf512e5266f00092db8eeec6d [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 which is easily accessed with a JVM programming language. If however the programming
language is not built for the JVM then it will require <<connecting-gremlin-server,Gremlin Server>> in the architecture
as well.
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 traversals
over this HTTP 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.
The preferred approach is to use traversal-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]
The following sections describe each language variant and driver that is officially TinkerPop a part of the project,
providing more detailed information about usage, configuration and known limitations.
[[gremlin-go]]
== Gremlin-Go
IMPORTANT: 4.0.0-beta.1 Release - Gremlin-Go is not available in this beta, please consider testing with Java or
Python.
image:gremlin-go.png[width=130,float=right] Apache TinkerPop's Gremlin-Go implements Gremlin within the link:https://go.dev/[Go] language and can therefore be used on different operating systems. Go's syntax has the similar constructs as Java including
"dot notation" for function chaining (`a.b.c`) and round bracket function arguments (`a(b,c)`). Something unlike Java is that Gremlin-Go requires a
`gremlingo` prefix when using the namespace (`a(b())` vs `gremlingo.a(gremlingo.T__.b())`). Anyone familiar with Gremlin-Java will be able to work
with Gremlin-Go with relative ease. Moreover, there are a few added constructs to Gremlin-Go that make traversals a bit more
succinct.
To install the Gremlin-Go as a dependency for your project, run the following in the root directory of your project that contains your `go.mod` file:
[source,bash]
----
go get github.com/apache/tinkerpop/gremlin-go/v3[optionally append @<version>, such as @v3.5.3]
----
[[gremlin-go-connecting]]
=== 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_()`.
[source,go]
----
remote, err := gremlingo.NewDriverRemoteConnection("ws://localhost:8182/gremlin")
g := gremlingo.Traversal_().With(remote)
----
If you need to additional parameters to connection setup, you can pass in a configuration function.
[source,go]
----
remote, err := gremlingo.NewDriverRemoteConnection("ws://localhost:8182/gremlin",
func(settings *DriverRemoteConnectionSettings) {
settings.TraversalSource = "gmodern"
})
----
Gremlin-go supports plain text authentication. It can be set in the connection function.
[source,go]
----
remote, err := gremlingo.NewDriverRemoteConnection("ws://localhost:8182/gremlin",
func(settings *DriverRemoteConnectionSettings) {
settings.TlsConfig = &tls.Config{InsecureSkipVerify: true}
settings.AuthInfo = gremlingo.BasicAuthInfo("login", "password")
})
----
If you authenticate to a remote <<connecting-gremlin-server,Gremlin Server>> or
<<connecting-rgp,Remote Gremlin Provider>>, this server normally has SSL activated and the websockets url will start
with 'wss://'.
Some connection options can also be set on individual requests made through the using `With()` step on the
`TraversalSource`. For instance to set request timeout to 500 milliseconds:
[source,go]
----
results, err := g.With("evaluationTimeout", 500).V().Out("knows").ToList()
----
The following options are allowed on a per-request basis in this fashion: `batchSize`, `requestId`, `userAgent` and
`evaluationTimeout`.
anchor:go-imports[]
[[gremlin-go-imports]]
=== Common Imports
There are a number of classes, functions and tokens that are typically used with Gremlin. The following import
provide most of the typical functionality required to use Gremlin:
[source,go]
----
import (
"github.com/apache/tinkerpop/gremlin-go/driver"
)
----
These can be used analogously to how they are used in Gremlin-Java.
[source,go]
----
results, err := g.V().HasLabel("person").Has("age", gremlingo.T__.Is(gremlingo.P.Gt(30))).Order().By("age", gremlingo.Desc).ToList()
[v[6], v[4]]
----
anchor:go-configuration[]
[[gremlin-go-configuration]]
=== Configuration
The following table describes the various configuration options for the Gremlin-go Driver. They
can be passed to the `NewClient` or `NewDriverRemoteConnection` functions as configuration function arguments:
[width="100%",cols="3,10,^2",options="header"]
|=========================================================
|Key |Description |Default
|TraversalSource |Traversal source. |"g"
|TransporterType |Transporter type. |Gorilla
|LogVerbosity |Log verbosity.|gremlingo.INFO
|Logger |Instance of logger. |log
|Language |Language used for logging messages. |language.English
|AuthInfo |Authentification info, can be build with BasicAuthInfo() or HeaderAuthInfo(). |empty
|TlsConfig |TLS configuration. |empty
|KeepAliveInterval |Keep connection alive interval. |5 seconds
|WriteDeadline |Write deadline. |3 seconds
|ConnectionTimeout | Timeout for establishing connection. |45 seconds
|NewConnectionThreshold | Minimum amount of concurrent active traversals on a connection to trigger creation of a new connection. |4
|MaximumConcurrentConnections | Maximum number of concurrent connections. |number of runtime processors
|EnableCompression |Flag to enable compression. |false
|ReadBufferSize |Specify I/O buffer sizes in bytes. If a buffer size is zero, then a useful default size is used |0
|WriteBufferSize |Specify I/O buffer sizes in bytes. If a buffer size is zero, then a useful default size is used |0
|Session |Session ID. |""
|EnableUserAgentOnConnect |Enables sending a user agent to the server during connection requests.
More details can be found in provider docs
link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#_graph_driver_provider_requirements[here].|true
|=========================================================
[[gremlin-go-strategies]]
=== Traversal Strategies
In order to add and remove <<traversalstrategy,traversal strategies>> from a traversal source, Gremlin-Go has a
`TraversalStrategy` interface along with a collection of functions that mirror the standard Gremlin-Java strategies.
[source,go]
----
promise := g.WithStrategies(gremlingo.ReadOnlyStrategy()).AddV("person").Property("name", "foo").Iterate()
----
NOTE: Many of the `TraversalStrategy` classes in Gremlin-Go 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-Go bytecode and transmitted to the Gremlin traversal machine for
re-construction machine-side.
[[gremlin-go-transactions]]
=== Transactions
To get a full understanding of this section, it would be good to start by reading the <<transactions,Transactions>>
section of this documentation, which discusses transactions in the general context of TinkerPop itself. This section
builds on that content by demonstrating the transactional syntax for Go.
[source,go]
----
remote, err := NewDriverRemoteConnection("ws://localhost:8182/gremlin")
g := gremlingo.Traversal_().With(remote)
// Create a Transaction.
tx := g.Tx()
// Spawn a new GraphTraversalSource, binding all traversals established from it to tx.
gtx, _ := tx.Begin()
// Execute a traversal within the transaction.
promise := g.AddV("person").Property("name", "Lyndon").Iterate()
err := <-promise
if err != nil {
// Rollback the transaction if an error occurs.
tx.rollback()
} else {
// Commit the transaction. The transaction can no longer be used and cannot be re-used.
// A new transaction can be spawned through g.Tx().
tx.Commit()
}
----
[[gremlin-go-lambda]]
=== 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-Go, a Gremlin lambda should
be represented as a zero-arg callable that returns a string representation of the lambda expected for use in the
traversal. The lambda should be written as a `Gremlin-Groovy` string. 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,go]
----
r, err := g.V().Out().Map(&gremlingo.Lambda{Script: "it.get().value('name').length()", Language: ""}).Sum().ToList()
----
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
`Script: "it.get().value('name')", Language: "gremlin-groovy"`, prefer `Script: "x -> x.get().value('name')", Language: "gremlin-groovy"`.
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.
[source,go]
----
r, err := g.V((&gremlingo.Bindings{}).Of("x", 1)).Out("created").Map(&gremlingo.Lambda{Script: "it.get().value('name').length()", Language: ""}).Sum().ToList()
// 3
r, err := g.V((&gremlingo.Bindings{}).Of("x", 4)).Out("created").Map(&gremlingo.Lambda{Script: "it.get().value('name').length()", Language: ""}).Sum().ToList()
// 9
----
WARNING: As explained throughout the documentation, when possible <<a-note-on-lambdas,avoid>> lambdas.
[[gremlin-go-scripts]]
=== Submitting Scripts
The `Client` class implementation/interface is based on the Java Driver, with some restrictions. Most notably,
Gremlin-go does not yet implement the `Cluster` class. Instead, `Client` is instantiated directly.
Usage is as follows:
[source,go]
----
import "github.com/apache/tinkerpop/gremlin-go/v3/driver" <1>
client, err := gremlingo.NewClient("ws://localhost:8182/gremlin") <2>
----
<1> Import the Gremlin-Go 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,go]
----
resultSet, err := client.Submit("g.V().count()") <1>
result, err := resultSet.All() <2>
fmt.Println(result[0].GetString()) <3>
----
<1> Submit a script that simply returns a Count of vertexes.
<2> Get results from resultSet. Block until the script is evaluated and results are sent back by the server.
<3> Use the result.
==== Per Request Settings
Both the `Client` and `DriverRemoteConnection` types have a `SubmitWithOptions(traversalString, requestOptions)` variant
of the standard `Submit()` method. These methods allow a `RequestOptions` struct to be passed in which will augment the
execution on the server. `RequestOptions` can be constructed
using `RequestOptionsBuilder`. A good use-case for this feature is to set a per-request override to the
`evaluationTimeout` so that it only applies to the current request.
[source,go]
----
options := new(RequestOptionsBuilder).
SetEvaluationTimeout(5000).
SetBatchSize(32).
SetMaterializeProperties("tokens").
AddBinding("x", 100).
Create()
resultSet, err := client.SubmitWithOptions("g.V(x).count()", options)
----
The following options are allowed on a per-request basis in this fashion: `batchSize`, `requestId`, `userAgent`, `evaluationTimeout` and `materializeProperties`.
`RequestOptions` may also contain a map of variable `bindings` to be applied to the supplied
traversal string.
IMPORTANT: The preferred method for setting a per-request timeout for scripts is demonstrated above, but those familiar
with bytecode may try `g.with("evaluationTimeout", 500)` within a script. Scripts with multiple traversals and multiple
timeouts will be interpreted as a sum of all timeouts identified in the script for that request.
[source,go]
----
resultSet, err := client.SubmitWithOptions("g.with('evaluationTimeout', 500).addV().iterate();"+
"g.addV().iterate();"+
"g.with('evaluationTimeout', 500).addV();", new(RequestOptionsBuilder).SetEvaluationTimeout(500).Create())
results, err := resultSet.All()
----
In the above example, defines a timeout of 500 milliseconds, but the script has three traversals with
two internal settings for the timeout using `with()`. The request timeout used by the server will therefore be 1000
milliseconds (overriding the 500 which itself was an override for whatever configuration was on the server).
[[gremlin-go-dsl]]
=== Domain Specific Languages
Writing a Gremlin <<dsl,Domain Specific Language>> (DSL) in Go requires embedding of several structs and interfaces:
* `GraphTraversal` - which exposes the various steps used in traversal writing
* `GraphTraversalSource` - which spawns `GraphTraversal` instances
* `AnonymousTraversal` - which spawns anonymous traversals from steps
The Social DSL based on the link:https://tinkerpop.apache.org/docs/x.y.z/images/tinkerpop-modern.png["modern" toy graph]
might look like this:
[source,go]
----
// Optional syntactic sugar.
var __ = gremlingo.T__
var P = gremlingo.P
var gt = gremlingo.P.Gt
// Optional alias for import convenience.
type GraphTraversal = gremlingo.GraphTraversal
type GraphTraversalSource = gremlingo.GraphTraversalSource
type AnonymousTraversal = gremlingo.AnonymousTraversal
// Embed Graph traversal inside custom traversal struct to add custom traversal functions.
// In go, capitalizing the first letter exports (makes public) the struct/method to outside of package, for this example
// we have defined everything package private. In actual usage, please see fit to your application.
type socialTraversal struct {
*GraphTraversal
}
func (s *socialTraversal) knows(personName string) *socialTraversal {
return &socialTraversal{s.Out("knows").HasLabel("person").Has("name", personName)}
}
func (s *socialTraversal) youngestFriendsAge() *socialTraversal {
return &socialTraversal{s.Out("knows").HasLabel("person").Values("age").Min()}
}
func (s *socialTraversal) createdAtLeast(number int) *socialTraversal {
return &socialTraversal{s.OutE("created").Count().Is(gt(number))}
}
// Add custom social traversal source to spaw custom traversals.
type socialTraversalSource struct {
*GraphTraversalSource
}
// Define the source step function by adding steps to the bytecode.
func (sts *socialTraversalSource) persons(personNames ...interface{}) *socialTraversal {
t := sts.GetGraphTraversal()
t.Bytecode.AddStep("V")
t.Bytecode.AddStep("hasLabel", "person")
if personNames != nil {
t.Bytecode.AddStep("has", "name", P.Within(personNames...))
}
return &socialTraversal{t}
}
// Create the social anonymous traversal interface to embed and extend the anonymous traversal functions.
type iSocialAnonymousTraversal interface {
AnonymousTraversal
knows(personName string) *GraphTraversal
youngestFriendsAge() *GraphTraversal
createdAtLeast(number int) *GraphTraversal
}
// Add the struct to implement the iSocialAnonymousTraversal interface.
type socialAnonymousTraversal struct {
AnonymousTraversal
socialTraversal func() *socialTraversal
}
// Add the variable s__ to call anonymous traversal step functions in place of __.
var s__ iSocialAnonymousTraversal = &socialAnonymousTraversal{
__,
func() *socialTraversal {
return &socialTraversal{gremlingo.NewGraphTraversal(nil, gremlingo.NewBytecode(nil), nil)}
},
}
// Extended anonymous traversal functions need to return GraphTraversal for serialization purposes
func (sat *socialAnonymousTraversal) knows(personName string) *GraphTraversal {
return sat.socialTraversal().knows(personName).GraphTraversal
}
func (sat *socialAnonymousTraversal) youngestFriendsAge() *GraphTraversal {
return sat.socialTraversal().youngestFriendsAge().GraphTraversal
}
func (sat *socialAnonymousTraversal) createdAtLeast(number int) *GraphTraversal {
return sat.socialTraversal().createdAtLeast(number).GraphTraversal
}
----
Using the DSL requires a social traversal source to be created from the default traversal source:
[source,go]
----
// Creating the driver remote connection as regular.
driverRemoteConnection, _ := gremlingo.NewDriverRemoteConnection("ws://localhost:8182/gremlin",
func(settings *gremlingo.DriverRemoteConnectionSettings) {
settings.TraversalSource = "gmodern"
})
defer driverRemoteConnection.Close()
// Create social traversal source from graph traversal source.
social := &socialTraversalSource{gremlingo.Traversal_().With(driverRemoteConnection)}
// We can now use the social traversal source as well as traversal steps
resBool, _ := social.persons("marko", "stephen").knows("josh").HasNext()
fmt.Println(resBool)
// Using the createdAtLeast step.
resCreated, _ := social.persons().createdAtLeast(1).Next()
fmt.Println(resCreated.GetString())
// Using the social anonymous traversal.
resAnon, _ := social.persons().Filter(s__.createdAtLeast(1)).Count().Next()
fmt.Println(resAnon.GetString())
// Note that error handling has been omitted with _ from the above examples.
----
[[gremlin-go-differences]]
=== Differences
All step names start with a capital letter which is consistent with the idiomatic style for Go. This use of Pascal-case
extends to enums like `Direction`, e.g. `Direction.OUT` is `Direction.Out` in Go.
[[gremlin-go-aliases]]
=== Aliases
To make the code more readable and close to the Gremlin query language), you can use aliases. These aliases can be named with capital letters to be consistent with non-aliased steps but will result in exported variables which could be problematic if not being used in a top-level program (i.e. not a redistributable package).
[source,go]
----
var __ = gremlingo.T__
var gt = gremlingo.P.Gt
var order = gremlingo.Order
results, err := g.V().HasLabel("person").Has("age", __.Is(gt(30))).Order().By("age", order.Desc).ToList()
----
==== List of useful aliases
[source,go]
----
// common
var __ = gremlingo.T__
var TextP = gremlingo.TextP
// predicates
var between = gremlingo.P.Between
var eq = gremlingo.P.Eq
var gt = gremlingo.P.Gt
var gte = gremlingo.P.Gte
var inside = gremlingo.P.Inside
var lt = gremlingo.P.Lt
var lte = gremlingo.P.Lte
var neq = gremlingo.P.Neq
var not = gremlingo.P.Not
var outside = gremlingo.P.Outside
var test = gremlingo.P.Test
var within = gremlingo.P.Within
var without = gremlingo.P.Without
var and = gremlingo.P.And
var or = gremlingo.P.Or
// sorting
var order = gremlingo.Order
----
Finally, the enum construct for `Cardinality` cannot have functions attached to it the way it can be done in Java,
therefore cardinality functions that take a value like `list()`, `set()`, and `single()` are referenced from a
`CardinalityValue` class rather than `Cardinality` itself.
[[gremlin-go-limitations]]
=== Limitations
* There is no default `set` type in Go. Any set type code from server will be deserialized into slices with the list
type implementation. To input a set into Gremlin-Go, a custom struct which implements the `gremlingo.Set` interface
will be serialized as a set. `gremlingo.NewSimpleSet` is a basic implementation of a set that is provided by Gremlin-Go
that can be used to fulfill the `gremlingo.Set` interface if desired.
* Go does not support ordered maps natively as the built-in `map` type does not guarantee iteration order. Traversal
results which contain maps may not preserve original ordering when deserialized into Go's native map types.
[[gremlin-go-examples]]
=== Application Examples
The TinkerPop source code contains some sample applications that demonstrate the basics of Gremlin-Go. They
can be found in GitHub link:https://github.com/apache/tinkerpop/tree/x.y.z/glv-examples/gremlin-go/[here]
and are designed to connect to a running <<gremlin-server,Gremlin Server>> configured with the
`conf/gremlin-server.yaml` and `conf/gremlin-server-modern.yaml` files as included with the standard release packaging.
This guide assumes Gremlin Server will be executed using Docker. Alternatively, Gremlin Server can run locally (see
the <<gremlin-server,Gremlin Server>> documentation for this option).
To start Gremlin Server using Docker, first download an image of Gremlin Server from Docker Hub:
[source,shell]
----
docker pull tinkerpop/gremlin-server
----
Clean server:
[source,shell]
----
docker run -d -p 8182:8182 tinkerpop/gremlin-server
----
Modern toy graph server:
[source,shell]
----
docker run -d -p 8182:8182 tinkerpop/gremlin-server conf/gremlin-server-modern.yaml
----
The remote connection and basic Gremlin examples can be run on a clean server, while traversal examples should be
run on a server with the Modern graph preloaded.
==== Prerequisites
- Compatible Go installed (see <<development-environment,Development Environment>> for supported versions)
Navigate to the examples directory:
[source,shell]
----
cd glv-examples/gremlin-go
----
Run the examples:
[source,shell]
----
go run connections.go
go run basic_gremlin.go
go run modern_traversals.go
----
[[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,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: 'x.y.z'
compile group: 'org.apache.tinkerpop', name: 'gremlin-driver', version: 'x.y.z'
----
[[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')))+`
Care needs to be taken when using the `any(P)` step as you may accidentally invoke Groovy's `any(Closure)` method. This
typically happens when calling `any()` without arguments. You can tell if Groovy's `any` has been called if the return
value is a boolean.
Since Groovy has access to the full JVM as Java does, it is possible to construct `Date`-like objects directly, but
the Gremlin language does offer a `datetime()` function that is exposed in the Gremlin Console and as a function for
Gremlin scripts sent to Gremlin Server. The function accepts the following forms of dates and times using a default
time zone offset of UTC(+00:00):
* `2018-03-22`
* `2018-03-22T00:35:44`
* `2018-03-22T00:35:44Z`
* `2018-03-22T00:35:44.741`
* `2018-03-22T00:35:44.741Z`
* `2018-03-22T00:35:44.741+1600`
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>
<!--
alternatively the driver is packaged as an uberjar with shaded non-optional dependencies including gremlin-core and
tinkergraph-gremlin which are not shaded.
-->
<dependency>
<groupId>org.apache.tinkerpop</groupId>
<artifactId>gremlin-driver</artifactId>
<version>x.y.z</version>
<classifier>shaded</classifier>
<!-- The shaded JAR uses the original POM, therefore conflicts may still need resolution -->
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
----
[[gremlin-java-connecting]]
=== 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 = traversal().with(graph);
----
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().with(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().with('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 `with()` 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().with(DriverRemoteConnection.using(cluster, "g"));
----
Please consider the following example:
[gremlin-groovy]
----
g = traversal().with('conf/remote-graph.properties')
g.V().elementMap()
g.close()
----
[source,java]
----
GraphTraversalSource g = traversal().with("conf/remote-graph.properties");
List<Map> list = g.V().elementMap();
g.close();
----
Note the call to `close()` above. The call to `with()` 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 network
resources associated with `g`.
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.yaml')
client = cluster.connect()
g = traversal().with(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`.
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().with(conf);
List<Vertex> vertices = g.with(Tokens.ARGS_EVAL_TIMEOUT, 500L).V().out("knows").toList()
----
The following options are allowed on a per-request basis in this fashion: `batchSize`, `requestId`, `userAgent` and
`evaluationTimeout` (formerly `scriptEvaluationTimeout` which is also supported but now deprecated). Use of `Tokens`
to reference these options is preferred.
anchor:java-imports[]
[[gremlin-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.__.*;
----
[[gremlin-java-configuration]]
=== Configuration
The following table describes the various configuration options for the Gremlin Driver:
[width="100%",cols="3,10,^2",options="header"]
|=========================================================
|Key |Description |Default
|auth.type |Type of Auth to submit on requests that require authentication. Can be: `basic` or `sigv4`. |_""_
|auth.username |The username to submit on requests that require basic authentication. |_none_
|auth.password |The password to submit on requests that require basic authentication. |_none_
|auth.region |The region setting for sigv4 authentication. |_none_
|auth.serviceName |The service name setting for sigv4 authentication. |_none_
|connectionPool.connectionSetupTimeoutMillis | Duration of time in milliseconds provided for connection setup to complete which includes the SSL handshake. |15000
|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.idleConnectionTimeout | Duration of time in milliseconds that the driver will allow a channel to not receive read or writes before it automatically closes. |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 |`PKCS12` |_none_
|connectionPool.maxResponseContentLength |The maximum length in bytes that a message can be received from the server. |2147483647
|connectionPool.maxSize |The maximum size of a connection pool for a host. |128
|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.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()`. |_''_
|bulkResults |Sets whether the server should attempt to get bulk results or not. |false
|enableUserAgentOnConnect |Enables sending a user agent to the server during connection requests. More details can be found in provider docs link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#_graph_driver_provider_requirements[here].|true
|hosts |The list of hosts that the driver will connect to. |localhost
|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
|serializer.className |The fully qualified class name of the `MessageSerializer` that will be used to deserialize responses from 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.
[[gremlin-java-transactions]]
=== Transactions
IMPORTANT: 4.0.0-beta.1 Release - Transactions with Java isn't currently supported.
[[gremlin-java-serialization]]
=== 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 is a single option for serialization: GraphBinary.
IMPORTANT: 4.0.0-beta.1 Release - There is temporary support for GraphSON in the Java driver which will help with
testing, but it is expected that the drivers will only support GraphBinary when 4.0 is fully released.
It is important that the client and server have the same serializers configured in the same way 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 different graph systems may automatically include serializers on
the server-side, thus leaving the client to be configured manually. As an example:
[source,java]
----
IoRegistry registry = ...; // an IoRegistry instance exposed by a specific graph provider
TypeSerializerRegistry typeSerializerRegistry = TypeSerializerRegistry.build().addRegistry(registry).create();
MessageSerializer serializer = new GraphBinaryMessageSerializerV4(typeSerializerRegistry);
Cluster cluster = Cluster.build().
serializer(serializer).
create();
Client client = cluster.connect();
GraphTraversalSource g = traversal().with(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 using
the appropriate `MessageSerializer` (e.g. `GraphSONMessageSerializerV4` or `GraphBinaryMessageSerializerV4` respectively)
in the same way and building that into the `Cluster` object.
[[gremlin-java-gvalue]]
=== GValue Parameterization
A `GValue` is an encapsulation of a parameter name and value. The GValue class has a series of static methods to
construct GValues of various types from a given parameter name and value. Some of the most common examples are listed
below, see the
link:++https://tinkerpop.apache.org/javadocs/x.y.z/full/org/apache/tinkerpop/gremlin/process/traversal/step/GValue.html#method.summary++[Javadocs]
for a complete listing.
[source,java]
----
GValue<String> stringArg = GValue.ofString("name", "value");
GValue<Integer> intArg = GValue.ofInteger("name", 1);
GValue<Map> mapArg = GValue.ofMap("name", Collections.emptyMap());
GValue<?> autoTypedArg = GValue.of("name", "value"); // GValue will attempt to automatically detect correct type
----
A <<traversal-parameterization,subset of gremlin steps>> are able to accept `GValues`. When constructing a
`GraphTraversal` with such steps in Java, a GValue may be passed in the traversal to utilize a parameter in place of a
literal.
[source,java]
----
g.V().has("name", GValue.ofString("name", "marko"));
g.mergeV(GValue.ofMap("vertexPattern", Collections.singletonMap("name", "marko")));
----
[[gremlin-java-gvalue]]
=== GValue Parameterization
A `GValue` is an encapsulation of a parameter name and value. The GValue class has a series of static methods to
construct GValues of various types from a given parameter name and value. Some of the most common examples are listed
below, see the
link:++https://tinkerpop.apache.org/javadocs/x.y.z/full/org/apache/tinkerpop/gremlin/process/traversal/step/GValue.html#method.summary++[Javadocs]
for a complete listing.
[source,java]
----
GValue<String> stringArg = GValue.ofString("name", "value");
GValue<Integer> intArg = GValue.ofInteger("name", 1);
GValue<Map> mapArg = GValue.ofMap("name", Collections.emptyMap());
GValue<?> autoTypedArg = GValue.of("name", "value"); // GValue will attempt to automatically detect correct type
----
A <<traversal-parameterization,subset of gremlin steps>> are able to accept `GValues`. When constructing a
`GraphTraversal` with such steps in Java, a GValue may be passed in the traversal to utilize a parameter in place of a
literal.
[source,java]
----
g.V().has("name", GValue.ofString("name", "marko"));
g.mergeV(GValue.ofMap("vertexPattern", Collections.singletonMap("name", "marko")));
----
[[gremlin-java-lambda]]
=== 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.
[[gremlin-java-scripts]]
=== Submitting Scripts
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 `evaluationTimeout` 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().repeat(both()).times(100)", options).all().get();
----
The preferred method for setting a per-request timeout for scripts is demonstrated above, but those familiar with
bytecode may try `g.with(EVALUATION_TIMEOUT, 500)` within a script. Gremlin Server will respect timeouts set this way
in scripts as well. With scripts of course, it is possible to send multiple traversals at once in the same script.
In such events, the timeout for the request is interpreted as a sum of all timeouts identified in the script.
[source,java]
----
RequestOptions options = RequestOptions.build().timeout(500).create();
List<Result> result = client.submit("g.with(EVALUATION_TIMEOUT, 500).addV().iterate();" +
"g.addV().iterate();" +
"g.with(EVALUATION_TIMEOUT, 500).addV();", options).all().get();
----
In the above example, `RequestOptions` defines a timeout of 500 milliseconds, but the script has three traversals with
two internal settings for the timeout using `with()`. The request timeout used by the server will therefore be 1000
milliseconds (overriding the 500 which itself was an override for whatever configuration was on the server).
==== 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.
==== RequestInterceptor
Gremlin-Java allows for modification of the underlying HTTP request through the use of `RequestInteceptors`. This is
intended to be an advanced feature which means that you will need to understand how the implementation works in order
to safely utilize it. Gremlin-Java is written in a way that you should be able to interact with a TinkerPop-enabled
server without having to use interceptors. This is intended for cases where the server has special capabilities.
A `RequestInterceptor` is simply a `UnaryOperator`. A list of these are maintained and will be run sequentially for
each request. When building a `Cluster` instance, the methods `addInterceptorAfter()`, `addInterceptorBefore()`,
`addInterceptor()`, and `removeInterceptor()` can be used to add or remove interceptors. It's important to remember
that order matters so if one interceptor depends on another's output then ensure they are added in the correct order.
Note that `Auth` is also implemented using interceptors, and `Auth` is always run last after your list of interceptors
has already ran. By default, the `PayloadSerializingInterceptor` with the name `serializer` is added to your list of
interceptors. This interceptor is used for serializing the body of the request. The first interceptor is provided with
a `org.apache.tinkerpop.gremlin.driver.HttpRequest` that contains a `RequestMessage` in the body. As a reminder
`RequestMessage` is immutable and only certain keys can be added to them. If you want to customize the body by adding
other fields, you will need to make a different copy of the `RequestMessage` or completely change the body to contain a
different data type. The very last interceptor should have a `org.apache.tinkerpop.gremlin.driver.HttpRequest` that
contains a byte[] in the body.
For an example of a simple `RequestInterceptor` that only modifies the header of the request take a look at
link:https://github.com/apache/tinkerpop/blob/x.y.z/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/auth/Basic.java[basic authentication].
[[gremlin-java-dsl]]
=== Domain Specific Languages
Creating a <<dsl,Domain Specific Language>> (DSL) in Java requires the `@GremlinDsl` Java annotation in the
`gremlin-annotations` module. This annotation should be applied to a "DSL interface" that extends
`GraphTraversal.Admin`:
[source,xml]
----
<dependency>
<groupId>org.apache.tinkerpop</groupId>
<artifactId>gremlin-annotations</artifactId>
<version>x.y.z</version>
</dependency>
----
[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 = traversal(SocialTraversalSource.class).with(graph);
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 SocialTraversalSourceDsl(RemoteConnection connection) {
super(connection);
}
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 = traversal(SocialTraversalSource.class).with(graph);
social.persons("marko").knows("josh");
----
NOTE: Using Maven makes developing DSLs with the annotation processor straightforward in that it sets up appropriate
paths to the generated code automatically.
[[gremlin-java-troubleshooting]]
=== Troubleshooting
*Response exceeded X bytes.*
This error occurs when the driver attempts to process a response that exceeds the configured maximum response size.
The most direct way to fix this problem is to increase the `maxResponseContentLength` setting in the driver.
*TimeoutException*
A `TimeoutException` is thrown by the driver when the time limit assigned by the `maxWaitForConnection` is exceeded
when trying to borrow a connection from the connection pool for a particular host. There are generally two scenarios
where this occurs:
1. The server has actually reached its maximum capacity or the driver has just learned that the server is unreachable.
2. The client is throttling requests when the pool is exhausted.
The latter of the two can be addressed from the driver side in the following ways:
* Increase the `maxWaitForConnection` allowing the client to wait a bit longer for a connection to become available.
* Increase the number of requests allowed per connection by increasing the `maxSimultaneousUsagePerConnection` and
`maxInProcessPerConnection` settings.
* Increase the number of connections available in the connection pool by increasing the `maxConnectionPoolSize`.
The exception and logs (assuming they are enabled) should contain information about the state of the connection pool
along with its connections which can help shed more light on which of these scenarios caused the problem. Some examples
of these messages and their meaning are shown below:
_The server is unavailable_
[source,text]
----
Timed-out (500 MILLISECONDS) waiting for connection on Host{address=localhost/127.0.0.1:45940, hostUri=ws://localhost:45940/gremlin}. Potential Cause: Connection refused: no further information
> ConnectionPool (Host{address=localhost/127.0.0.1:45940, hostUri=ws://localhost:45940/gremlin})- no connections in pool
----
_Client is likely issuing more requests than the pool size can handle_
[source,text]
----
Timed-out (150 MILLISECONDS) waiting for connection on Host{address=localhost/127.0.0.1:45940, hostUri=ws://localhost:45940/gremlin}. Potential Cause: Number of active requests exceeds pool size. Consider increasing the value for maxConnectionPoolSize.
ConnectionPool (Host{address=localhost/127.0.0.1:45940, hostUri=ws://localhost:45940/gremlin})
Connection Pool Status (size=1 max=1 min=1 toCreate=0 bin=0)
> Connection{channel=5a859d62 isDead=false borrowed=1 pending=1 markedReplaced=false closing=false created=2022-12-19T21:08:21.569613100Z thread=gremlin-driver-conn-scheduler-1}
-- bin --
----
_Network traffic is slow and the websocket handshake does not complete in time_
[source,text]
----
Timed-out (250 MILLISECONDS) waiting for connection on Host{address=localhost/127.0.0.1:45940, hostUri=ws://localhost:45940/gremlin}. Potential Cause: WebSocket handshake not completed in stipulated time=[100]ms
ConnectionPool (Host{address=localhost/127.0.0.1:45940, hostUri=ws://localhost:45940/gremlin})
Connection Pool Status (size=1 max=5 min=1 toCreate=0 bin=0)
> Connection{channel=205fc8d2 isDead=false borrowed=1 pending=1 markedReplaced=false closing=false created=2022-12-19T21:10:04.692921600Z thread=gremlin-driver-conn-scheduler-1}
-- bin --
----
anchor:java-application-examples[]
[[gremlin-java-examples]]
=== Application Examples
The TinkerPop source code contains some sample applications that demonstrate the basics of Gremlin-Java. They
can be found in GitHub link:https://github.com/apache/tinkerpop/tree/x.y.z/glv-examples/gremlin-java/[here]
and are designed to connect to a running <<gremlin-server,Gremlin Server>> configured with the
`conf/gremlin-server.yaml` and `conf/gremlin-server-modern.yaml` files as included with the standard release packaging.
This guide assumes Gremlin Server will be executed using Docker. Alternatively, Gremlin Server can run locally (see
the <<gremlin-server,Gremlin Server>> documentation for this option).
To start Gremlin Server using Docker, first download an image of Gremlin Server from Docker Hub:
[source,shell]
----
docker pull tinkerpop/gremlin-server
----
Clean server:
[source,shell]
----
docker run -d -p 8182:8182 tinkerpop/gremlin-server
----
Modern toy graph server:
[source,shell]
----
docker run -d -p 8182:8182 tinkerpop/gremlin-server conf/gremlin-server-modern.yaml
----
The remote connection and basic Gremlin examples can be run on a clean server, while traversal examples should be
run on a server with the Modern graph preloaded.
==== Prerequisites
- Compatible JDK installed (see <<development-environment,Development Environment>> for supported versions)
- Maven installed
Navigate to the examples directory:
[source,shell]
----
cd glv-examples/gremlin-java
----
Build the examples with Maven:
[source,shell]
----
mvn clean install
----
Run the examples:
[source,shell]
----
java -cp target/run-examples-shaded.jar examples.Connections
java -cp target/run-examples-shaded.jar examples.BasicGremlin
java -cp target/run-examples-shaded.jar examples.ModernTraversals
----
[[gremlin-javascript]]
== Gremlin-JavaScript
IMPORTANT: 4.0.0-beta.1 Release - Gremlin-JavaScript is not available in this beta, please consider testing with
Java or Python.
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
[[gremlin-javascript-connecting]]
=== 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().with(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().with(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);
----
Some connection options can also be set on individual requests made through the using `with()` step on the
`TraversalSource`. For instance to set request timeout to 500 milliseconds:
[source,javascript]
----
const vertices = await g.with_('evaluationTimeout', 500).V().out('knows').toList()
----
The following options are allowed on a per-request basis in this fashion: `batchSize`, `requestId`, `userAgent` and
`evaluationTimeout` (formerly `scriptEvaluationTimeout` which is also supported but now deprecated).
[[gremlin-javascript-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,javascript]
----
const gremlin = require('gremlin');
const traversal = gremlin.process.AnonymousTraversalSource.traversal;
const __ = gremlin.process.statics;
const DriverRemoteConnection = gremlin.driver.DriverRemoteConnection;
const column = gremlin.process.column
const direction = gremlin.process.direction
const Direction = {
BOTH: direction.both,
IN: direction.in,
OUT: direction.out,
from_: direction.out,
to: direction.in,
}
const p = gremlin.process.P
const textp = gremlin.process.TextP
const pick = gremlin.process.pick
const pop = gremlin.process.pop
const order = gremlin.process.order
const scope = gremlin.process.scope
const t = gremlin.process.t
const cardinality = gremlin.process.cardinality
const CardinalityValue = gremlin.process.CardinalityValue
----
By defining these imports it becomes possible to write Gremlin in the more shorthand, canonical style that is
demonstrated in most examples found here in the documentation:
[source,javascript]
----
const { P: { gt } } = gremlin.process;
const { order: { desc } } = gremlin.process;
g.V().hasLabel('person').has('age',gt(30)).order().by('age',desc).toList()
----
[[gremlin-javascript-configuration]]
=== Configuration
The following table describes the various configuration options for the Gremlin-Javascript Driver. They
can be passed in the constructor of a new `Client` or `DriverRemoteConnection` :
[width="100%",cols="3,3,10,^2",options="header"]
|=========================================================
|Key |Type |Description |Default
|url |String |The resource uri. |None
|options |Object |The connection options. |{}
|options.ca |Array |Trusted certificates. |undefined
|options.cert |String/Array/Buffer |The certificate key. |undefined
|options.mimeType |String |The mime type to use. |'application/vnd.gremlin-v3.0+json'
|options.pfx |String/Buffer |The private key, certificate, and CA certs. |undefined
|options.reader |GraphSONReader/GraphBinaryReader |The reader to use. |select reader according to mimeType
|options.writer |GraphSONWriter |The writer to use. |select writer according to mimeType
|options.rejectUnauthorized |Boolean |Determines whether to verify or not the server certificate. |undefined
|options.traversalSource |String |The traversal source. |'g'
|options.authenticator |Authenticator |The authentication handler to use. |undefined
|options.processor |String |The name of the opProcessor to use, leave it undefined or set 'session' when session mode. |undefined
|options.session |String |The sessionId of Client in session mode. undefined means session-less Client. |undefined
|options.enableUserAgentOnConnect |Boolean |Determines if a user agent will be sent during connection handshake. |true
|options.headers |Object |An associative array containing the additional header key/values for the initial request. |undefined
|options.pingEnabled |Boolean |Setup ping interval. |true
|options.pingInterval |Number |Ping request interval in ms if ping enabled. |60000
|options.pongTimeout |Number |Timeout of pong response in ms after sending a ping. |30000
|=========================================================
[[gremlin-javascript-strategies]]
=== Traversal Strategies
In order to add and remove <<traversalstrategy,traversal strategies>> from a traversal source, Gremlin-Javascript has a
`TraversalStrategy` class along with a collection of subclasses that mirror the standard Gremlin-Java strategies.
[source,javascript]
----
// use the class name for the strategy directly for withoutStrategies()
g.withoutStrategies(InlineFilterStrategy);
// use an instance for withStrategies()
g.withStrategies(new InlineFilterStrategy());
// if the strategies has arguments, specify them as named arguments
g.withStrategies(new SeedStrategy({seed: 99999}))
----
NOTE: Many of the `TraversalStrategy` classes in Gremlin-Javascript 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-Javascript bytecode and transmitted to the Gremlin traversal machine for
re-construction machine-side.
[[gremlin-javascript-transactions]]
=== Transactions
To get a full understanding of this section, it would be good to start by reading the <<transactions,Transactions>>
section of this documentation, which discusses transactions in the general context of TinkerPop itself. This section
builds on that content by demonstrating the transactional syntax for Javascript.
[source,javascript]
----
const g = traversal().with(new DriverRemoteConnection('ws://localhost:8182/gremlin'));
const tx = g.tx(); // create a Transaction
// spawn a new GraphTraversalSource binding all traversals established from it to tx
const gtx = tx.begin();
// execute traversals using gtx occur within the scope of the transaction held by tx. the
// tx is closed after calls to commit or rollback and cannot be re-used. simply spawn a
// new Transaction from g.tx() to create a new one as needed. the g context remains
// accessible through all this as a sessionless connection.
Promise.all([
gtx.addV("person").property("name", "jorge").iterate(),
gtx.addV("person").property("name", "josh").iterate()
]).then(() => {
return tx.commit();
}).catch(() => {
return tx.rollback();
});
----
[[gremlin-javascript-lambda]]
=== 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-Javascript, a Gremlin lambda
should be represented as a zero-arg callable that returns a string representation of the lambda expected for use in the
traversal. The returned lambda should be written as a Gremlin-Groovy string. 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,javascript]
----
g.V().out().
map(() => "it.get().value('name').length()").
sum().
toList().then(total => console.log(total))
----
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
`() => "it.get().value('name')"`, prefer `() => "x -> x.get().value('name')"`.
WARNING: As explained throughout the documentation, when possible <<a-note-on-lambdas,avoid>> lambdas.
[[gremlin-javascript-scripts]]
=== Submitting Scripts
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);
}
----
==== Per Request Settings
The `client.submit()` functions accept a `requestOptions` which expects a dictionary. 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 `evaluationTimeout` so that it only applies to the current
request.
[source,javascript]
----
const result = await client.submit("g.V().repeat(both()).times(100)", null, { evaluationTimeout: 5000 })
----
The following options are allowed on a per-request basis in this fashion: `batchSize`, `requestId`, `userAgent`, `materializeProperties` and
`evaluationTimeout` (formerly `scriptEvaluationTimeout` which is also supported but now deprecated).
IMPORTANT: The preferred method for setting a per-request timeout for scripts is demonstrated above, but those familiar
with bytecode may try `g.with(EVALUATION_TIMEOUT, 500)` within a script. Scripts with multiple traversals and multiple
timeouts will be interpreted as a sum of all timeouts identified in the script for that request.
==== Processing results as they are returned from the Gremlin server
The Gremlin JavaScript driver maintains a WebSocket connection to the Gremlin server and receives messages according to the `batchSize` parameter on the per request settings or the `resultIterationBatchSize` value configured for the Gremlin server. When submitting scripts the default behavior is to wait for the entire result set to be returned from a query before allowing any processing on the result set.
The following examples assume that you have 100 vertices in your graph.
[source,javascript]
----
const result = await client.submit("g.V()");
console.log(result.toArray()); // 100 - all the vertices in your graph
----
When working with larger result sets it may be beneficial for memory management to process each chunk of data as it is returned from the gremlin server. The Gremlin JavaScript driver can return a readable stream instead of waiting for the entire result set to be loaded.
[source,javascript]
----
const readable = client.stream("g.V()", {}, { batchSize: 25 });
readable.on('data', (data) => {
console.log(data.toArray()); // 25 vertices
})
readable.on('error', (error) => {
console.log(error); // errors returned from gremlin server
})
readable.on('end', () => {
console.log('query complete'); // when the end event is received then all the results have been processed
})
----
If you are using NodeJS >= 10.0, you can asynchronously iterate readable streams:
[source,javascript]
----
const readable = client.stream("g.V()", {}, { batchSize: 25 });
try {
for await (const result of readable) {
console.log('data', result.toArray()); // 25 vertices
}
} catch (err) {
console.log(err);
}
----
[[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).with(connection);
g.person('marko').aged(29).values('name').toList().
then(names => console.log(names));
----
[[javascript-differences]]
[[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_()>>
*Tokens* - `Direction.from_`
In addition, the enum construct for `Cardinality` cannot have functions attached to it the way it can be done in Java,
therefore cardinality functions that take a value like `list()`, `set()`, and `single()` are referenced from a
`CardinalityValue` class rather than `Cardinality` itself.
Gremlin allows for `Map` instances to include `null` keys, but `null` keys in Javascript have some interesting behavior
as in:
[source,text]
----
> var a = { null: 'something', 'b': 'else' };
> JSON.stringify(a)
'{"null":"something","b":"else"}'
> JSON.parse(JSON.stringify(a))
{ null: 'something', b: 'else' }
> a[null]
'something'
> a['null']
'something'
----
This behavior needs to be considered when using Gremlin to return such results. A typical situation where this might
happen is with `group()` or `groupCount()` as in:
[source,javascript]
----
g.V().groupCount().by('age')
----
where "age" is not a valid key for all vertices. In these cases, it will return `null` for that key and group on that.
It may bet better in Javascript to filter away those vertices to avoid the return of `null` in the returned `Map`:
[source,javascript]
----
g.V().has('age').groupCount().by('age')
g.V().hasLabel('person').groupCount().by('age')
----
Either of the above two options accomplishes the desired goal as both prevent `groupCount()` from having to process
the possibility of `null`.
[[gremlin-javascript-limitations]]
=== Limitations
* The `subgraph()`-step is not supported by any variant that is not running on the Java Virtual Machine as there is
no `Graph` instance to deserialize a result into on the client-side. A workaround is to replace the step with
`aggregate(local)` and then convert those results to something the client can use locally.
[[gremlin-javascript-examples]]
=== Application Examples
The TinkerPop source code contains some sample applications that demonstrate the basics of Gremlin-JavaScript. They
can be found in GitHub link:https://github.com/apache/tinkerpop/tree/x.y.z/glv-examples/gremlin-javascript/[here]
and are designed to connect to a running <<gremlin-server,Gremlin Server>> configured with the
`conf/gremlin-server.yaml` and `conf/gremlin-server-modern.yaml` files as included with the standard release packaging.
This guide assumes Gremlin Server will be executed using Docker. Alternatively, Gremlin Server can run locally (see
the <<gremlin-server,Gremlin Server>> documentation for this option).
To start Gremlin Server using Docker, first download an image of Gremlin Server from Docker Hub:
[source,shell]
----
docker pull tinkerpop/gremlin-server
----
Clean server:
[source,shell]
----
docker run -d -p 8182:8182 tinkerpop/gremlin-server
----
Modern toy graph server:
[source,shell]
----
docker run -d -p 8182:8182 tinkerpop/gremlin-server conf/gremlin-server-modern.yaml
----
The remote connection and basic Gremlin examples can be run on a clean server, while traversal examples should be
run on a server with the Modern graph preloaded.
==== Prerequisites
- Compatible Node.js installed (see <<development-environment,Development Environment>> for supported versions)
- npm installed
Navigate to the examples directory:
[source,shell]
----
cd glv-examples/gremlin-javascript
----
Install the dependencies:
[source,shell]
----
npm install
----
Run the examples:
[source,shell]
----
node connections.js
node basic-gremlin.js
node modern-traversals.js
----
anchor:gremlin-DotNet[]
[[gremlin-dotnet]]
== Gremlin.Net
IMPORTANT: 4.0.0-beta.1 Release - Gremlin.Net is not available in this beta, please consider testing with Java or
Python.
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 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
[[gremlin-dotnet-connecting]]
=== 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]
----
Some connection options can also be set on individual requests using the `With()` step on the `TraversalSource`.
For instance to set request timeout to 500 milliseconds:
[source,csharp]
----
var l = g.With(Tokens.ArgsEvalTimeout, 500).V().Out("knows").Count().ToList();
----
The following options are allowed on a per-request basis in this fashion: `batchSize`, `requestId`, `userAgent` and
`evaluationTimeout` (formerly `scriptEvaluationTimeout` which is also supported but now deprecated). These options are
available as constants on the `Gremlin.Net.Driver.Tokens` class.
[[gremlin-dotnet-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,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=commonImports]
----
[[gremlin-dotnet-configuration]]
=== 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
|ReconnectionAttempts |The number of attempts to get an open connection from the pool to submit a request. |4
|ReconnectionBaseDelay |The base delay used for the exponential backoff for the reconnection attempts. |1 s
|EnableUserAgentOnConnect |Enables sending a user agent to the server during connection requests.
More details can be found in provider docs
link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#_graph_driver_provider_requirements[here].|true
|=========================================================
A `NoConnectionAvailableException` is thrown if all connections have reached the `MaxInProcessPerConnection` limit
when a new request comes in.
A `ServerUnavailableException` is thrown if no connection is available to the server to submit a request after
`ReconnectionAttempts` retries.
==== WebSocket Configuration
The WebSocket connections can also be configured, directly as parameters of the `GremlinClient` constructor. It takes
an optional delegate `webSocketConfiguration` that will be invoked for each connection. This makes it possible to
configure more advanced options like the `KeepAliveInterval` or client certificates.
Starting with .NET 6, it is also possible to use compression for WebSockets. This is enabled by default starting with
TinkerPop 3.5.3 (again, only on .NET 6 or higher). Note that compression might make an application susceptible to
attacks like CRIME/BREACH. Compression should therefore be turned off if the application sends sensitive data to the
server as well as data that could potentially be controlled by an untrusted user. Compression can be disabled via the
`disableCompression` parameter.
[[gremlin-dotnet-logging]]
=== Logging
It is possible to enable logging for the Gremlin.Net driver by providing an `ILoggerFactory` (from the
`Microsoft.Extensions.Logging.Abstractions` package) to the `GremlinClient` constructor:
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=logging]
----
[[gremlin-dotnet-serialization]]
=== Serialization
The Gremlin.Net driver uses by default GraphBinary but it is also possible to use another serialization format by passing a message serializer when creating the `GremlinClient`.
GraphSON 3.0 can be configured like this:
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=serializationGraphSon3]
----
and GraphSON 2.0 like this:
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=serializationGraphSon]
----
[[gremlin-dotnet-strategies]]
=== 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.
[[gremlin-dotnet-transactions]]
=== Transactions
To get a full understanding of this section, it would be good to start by reading the <<transactions,Transactions>>
section of this documentation, which discusses transactions in the general context of TinkerPop itself. This section
builds on that content by demonstrating the transactional syntax for C#.
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=transactions]
----
[[gremlin-dotnet-lambda]]
=== 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'))`.
[[gremlin-dotnet-scripts]]
=== Submitting Scripts
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]
----
==== Per Request Settings
The `GremlinClient.Submit()` functions accept an option to build a raw `RequestMessage`. A good use-case for this
feature is to set a per-request override to the `evaluationTimeout` so that it only applies to the current request.
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=submittingScriptsWithTimeout]
----
The following options are allowed on a per-request basis in this fashion: `batchSize`, `requestId`, `userAgent`, `materializeProperties` and
`evaluationTimeout` (formerly `scriptEvaluationTimeout` which is also supported but now deprecated). These options are
available as constants on the `Gremlin.Net.Driver.Tokens` class.
IMPORTANT: The preferred method for setting a per-request timeout for scripts is demonstrated above, but those familiar
with bytecode may try `g.with(EVALUATION_TIMEOUT, 500)` within a script. Scripts with multiple traversals and multiple
timeouts will be interpreted as a sum of all timeouts identified in the script for that request.
anchor:gremlin-net-dsl[]
[[gremlin-dotnet-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-net-differences[]
[[gremlin-dotnet-differences]]
=== Differences
The biggest difference between Gremlin in .NET and the canonical version in Java is the casing of steps. Canonical
Gremlin utilizes `camelCase` as is typical in Java for function names, but C# utilizes `PascalCase` as it is more
typical in that language. Therefore, when viewing a typical Gremlin example written in Gremlin Console, the conversion
to C# usually just requires capitalization of the first letter in the step name, thus the following example in Groovy:
[source,groovy]
----
g.V().has('person','name','marko').
out('knows').
elementMap().toList()
----
would become the following in C#:
[source,csharp]
----
g.V().Has("Person","name","marko").
Out("knows").
ElementMap().ToList();
----
In addition to the uppercase change, also note the conversion of the single quotes to double quotes as is expected for
declaring string values in C# and the addition of the semi-colon at the end of the line. In short, don't forget to
apply the common syntax expectations for C# when trying to convert an example of Gremlin from a different language.
Another common conversion issues lies in having to explicitly define generics, which can make canonical Gremlin appear
much more complex in C# where type erasure is not a feature of the language. For example, the following example in
Groovy:
[source,groovy]
----
g.V().repeat(__.out()).times(2).values('name')
----
must be written as:
[source,csharp]
----
g.V().Repeat(__.Out()).Times(2).Values<string>("name");
----
Gremlin allows for `Map` instances to include `null` keys, but `null` keys in C# `Dictionary` instances are not allowed.
It is therefore necessary to rewrite a traversal such as:
[source,csharp]
----
g.V().GroupCount<object>().By("age")
----
where "age" is not a valid key for all vertices in a way that will remove the need for a `null` to be returned.
Finally, the enum construct for `Cardinality` cannot have functions attached to it the way it can be done in Java,
therefore cardinality functions that take a value like `list()`, `set()`, and `single()` are referenced from a
`CardinalityValue` class rather than `Cardinality` itself.
[source,csharp]
----
g.V().Has("age").GroupCount<object>().By("age")
g.V().HasLabel("person").GroupCount<object>().By("age")
----
Either of the above two options accomplishes the desired goal as both prevent `groupCount()` from having to process
the possibility of `null`.
anchor:gremlin-net-limitations[]
[[gremlin-dotnet-limitations]]
=== Limitations
* The `subgraph()`-step is not supported by any variant that is not running on the Java Virtual Machine as there is
no `Graph` instance to deserialize a result into on the client-side. A workaround is to replace the step with
`aggregate(local)` and then convert those results to something the client can use locally.
anchor:gremlin-dotnet-template[]
anchor:dotnet-application-examples[]
anchor:gremlin-net-examples[]
[[gremlin-dotnet-startup]]
=== Getting Started
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-dotnet-examples]]
=== Application Examples
The TinkerPop source code contains some sample applications that demonstrate the basics of Gremlin-Dotnet. They
can be found in GitHub link:https://github.com/apache/tinkerpop/tree/x.y.z/glv-examples/gremlin-dotnet/[here]
and are designed to connect to a running <<gremlin-server,Gremlin Server>> configured with the
`conf/gremlin-server.yaml` and `conf/gremlin-server-modern.yaml` files as included with the standard release packaging.
This guide assumes Gremlin Server will be executed using Docker. Alternatively, Gremlin Server can run locally (see
the <<gremlin-server,Gremlin Server>> documentation for this option).
To start Gremlin Server using Docker, first download an image of Gremlin Server from Docker Hub:
[source,shell]
----
docker pull tinkerpop/gremlin-server
----
Clean server:
[source,shell]
----
docker run -d -p 8182:8182 tinkerpop/gremlin-server
----
Modern toy graph server:
[source,shell]
----
docker run -d -p 8182:8182 tinkerpop/gremlin-server conf/gremlin-server-modern.yaml
----
The remote connection and basic Gremlin examples can be run on a clean server, while traversal examples should be
run on a server with the Modern graph preloaded.
==== Prerequisites
- Compatible .NET SDK installed (see <<development-environment,Development Environment>> for supported versions)
Navigate to the examples directory:
[source,shell]
----
cd glv-examples/gremlin-dotnet
----
Build all projects:
[source,shell]
----
dotnet build Examples.sln
----
Run specific examples:
[source,shell]
----
dotnet run --project BasicGremlin
dotnet run --project Connections
dotnet run --project ModernTraversals
----
[[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
----
[[gremlin-python-connecting]]
=== 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().with_(DriverRemoteConnection('http://localhost:8182/gremlin','g'))
----
If you need to send additional headers in the HTTP connection, you can pass an optional `headers` parameter
to the `DriverRemoteConnection` constructor.
[source,python]
----
g = traversal().with_(DriverRemoteConnection(
'http://localhost:8182/gremlin', 'g', headers={'Header':'Value'}))
----
Gremlin-Python contains an `auth` module that allows custom authentication implementation, with plain text and SigV4
authentication provided as reference.
[source,python]
----
# Plain text authentication
g = traversal().with_(DriverRemoteConnection(
'ws://localhost:8182/gremlin', 'g', auth=basic('stephen', 'password')))
# SigV4 authentication
g = traversal().with_(DriverRemoteConnection(
'ws://localhost:8182/gremlin', 'g', auth=sigv4(service-region, service-name)))
----
If you authenticate to a remote <<connecting-gremlin-server,Gremlin Server>> or
<<connecting-rgp,Remote Gremlin Provider>>, this server normally has SSL activated and the HTTP url will start
with 'https://'. If Gremlin-Server uses a self-signed certificate for SSL, Gremlin-Python needs access to a local copy of
the CA certificate file (in openssl .pem format), to be specified in the SSL_CERT_FILE environment variable.
NOTE: If connecting from an inherently single-threaded Python process where blocking while waiting for Gremlin
traversals to complete is acceptable, it might be helpful to set `pool_size` and `max_workers` parameters to 1.
See the <<python-configuration,Configuration>> section just below. Examples where this could apply are serverless cloud functions or WSGI
worker processes.
Some connection options can also be set on individual requests made through the using `with()` step on the
`TraversalSource`. For instance to set request timeout to 500 milliseconds:
[source,python]
----
vertices = g.with_('evaluationTimeout', 500).V().out('knows').to_list()
----
The following options are allowed on a per-request basis in this fashion: `batchSize`, `bulkResults`, `language`, `materializeProperties`,
`userAgent`, and `evaluationTimeout`.
anchor:python-imports[]
[[gremlin-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 CardinalityValue
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 TextP
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.
[source,python]
----
>>> g.V().has_label('person').has('age',P.gt(30)).order().by('age',Order.desc).to_list()
[v[6], v[4]]
----
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.
[source,python]
----
>>> g.V().has_label('person').has('age',gt(30)).order().by('age',desc).to_list()
[v[6], v[4]]
----
Statics includes all the `+__+`-methods and thus, anonymous traversals like `+__.out()+` can be expressed as below.
That is, without the `+__+`-prefix.
[source,python]
----
>>> g.V().repeat(out()).times(2).name.fold().to_list()
[['ripple', 'lop']]
----
There may be situations where certain graphs may want a more exact data type than what Python will allow as a language.
To support these situations `gremlin-python` has a few special type classes that can be imported from `statics`. They
include:
[source,python]
----
from gremlin_python.statics import long # Java long
from gremlin_python.statics import timestamp # Java timestamp
from gremlin_python.statics import SingleByte # Java byte type
from gremlin_python.statics import SingleChar # Java char type
from gremlin_python.statics import GremlinType # Java Class
----
anchor:python-configuration[]
[[gremlin-python-configuration]]
=== 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
|headers |Additional headers that will be added to each request message. |`None`
|max_workers |Maximum number of worker threads. |Number of CPUs * 5
|request_serializer |The request serializer implementation.|`gremlin_python.driver.serializer.GraphBinarySerializersV4`
|response_serializer |The response serializer implementation.|`gremlin_python.driver.serializer.GraphBinarySerializersV4`
|interceptors |The request interceptors to run after request serialization.|`None`
|auth |The authentication scheme to use when submitting requests that require authentication. |`None`
|pool_size |The number of connections used by the pool. |4
|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.aiohttp.transport.AiohttpTransport`
|enable_user_agent_on_connect |Enables sending a user agent to the server during connection requests.
More details can be found in provider docs
link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#_graph_driver_provider_requirements[here].|True
|bulk_results |Enables bulking of results on the server. |False
|=========================================================
Note that the `transport_factory` can allow for additional configuration of the `AiohttpTransport`, which allows
pass through of the named parameters available in
link:https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.ClientSession[AIOHTTP's HTTP ClientSession],
and the ability to call the api from an event loop:
[source,python]
----
import ssl
...
g = traversal().with_(
DriverRemoteConnection('http://localhost:8182/gremlin','g',
transport_factory=lambda: AiohttpTransport(timeout=60,
max_line_size=100*1024*1024,
ssl_options=ssl.create_default_context(Purpose.CLIENT_AUTH))))
----
[[gremlin-python-strategies]]
=== 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.
[source,python]
----
>>> g = g.with_strategies(SubgraphStrategy(vertices=has_label('person'),edges=has('weight',gt(0.5))))
>>> g.V().name.to_list()
['marko', 'vadas', 'josh', 'peter']
>>> g.V().out_e().element_map().to_list()
[{<T.id: 1>: 8, <T.label: 4>: 'knows', <Direction.IN: 2>: {<T.id: 1>: 4, <T.label: 4>: 'person'}, <Direction.OUT: 3>: {<T.id: 1>: 1, <T.label: 4>: 'person'}, 'weight': 1.0}]
>>> g = g.without_strategies(SubgraphStrategy)
>>> g.V().name.to_list()
['marko', 'vadas', 'lop', 'josh', 'ripple', 'peter']
>>> g.V().out_e().element_map().to_list()
[{<T.id: 1>: 9, <T.label: 4>: 'created', <Direction.IN: 2>: {<T.id: 1>: 3, <T.label: 4>: 'software'}, <Direction.OUT: 3>: {<T.id: 1>: 1, <T.label: 4>: 'person'}, 'weight': 0.4}, {<T.id: 1>: 7, <T.label: 4>: 'knows', <Direction.IN: 2>: {<T.id: 1>: 2, <T.label: 4>: 'person'}, <Direction.OUT: 3>: {<T.id: 1>: 1, <T.label: 4>: 'person'}, 'weight': 0.5}, {<T.id: 1>: 8, <T.label: 4>: 'knows', <Direction.IN: 2>: {<T.id: 1>: 4, <T.label: 4>: 'person'}, <Direction.OUT: 3>: {<T.id: 1>: 1, <T.label: 4>: 'person'}, 'weight': 1.0}, {<T.id: 1>: 10, <T.label: 4>: 'created', <Direction.IN: 2>: {<T.id: 1>: 5, <T.label: 4>: 'software'}, <Direction.OUT: 3>: {<T.id: 1>: 4, <T.label: 4>: 'person'}, 'weight': 1.0}, {<T.id: 1>: 11, <T.label: 4>: 'created', <Direction.IN: 2>: {<T.id: 1>: 3, <T.label: 4>: 'software'}, <Direction.OUT: 3>: {<T.id: 1>: 4, <T.label: 4>: 'person'}, 'weight': 0.4}, {<T.id: 1>: 12, <T.label: 4>: 'created', <Direction.IN: 2>: {<T.id: 1>: 3, <T.label: 4>: 'software'}, <Direction.OUT: 3>: {<T.id: 1>: 6, <T.label: 4>: 'person'}, 'weight': 0.2}]
>>> g = g.with_computer(workers=2,vertices=has('name','marko'))
>>> g.V().name.to_list()
['marko']
>>> g.V().out_e().value_map().with_(WithOptions.tokens).to_list()
[{<T.id: 1>: 9, <T.label: 4>: 'created', 'weight': 0.4}, {<T.id: 1>: 7, <T.label: 4>: 'knows', 'weight': 0.5}, {<T.id: 1>: 8, <T.label: 4>: 'knows', 'weight': 1.0}]
----
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.
[[gremlin-python-transactions]]
=== Transactions
IMPORTANT: 4.0.0-beta.1 Release - Transactions are currently disabled in this beta release.
To get a full understanding of this section, it would be good to start by reading the <<transactions,Transactions>>
section of this documentation, which discusses transactions in the general context of TinkerPop itself. This section
builds on that content by demonstrating the transactional syntax for Python.
[source,python]
----
g = traversal().with_remote(DriverRemoteConnection('ws://localhost:8182/gremlin'))
# Create a Transaction.
tx = g.tx()
# Spawn a new GraphTraversalSource, binding all traversals established from it to tx.
gtx = tx.begin()
try:
# Execute a traversal within the transaction.
gtx.add_v("person").property("name", "Lyndon").iterate(),
# Commit the transaction. The transaction can no longer be used and cannot be re-used.
# A new transaction can be spawned through g.tx().
# The context of g remains sessionless throughout the process.
tx.commit()
except Exception as e:
# Rollback the transaction if an error occurs.
tx.rollback()
----
[[gremlin-python-gvalue]]
=== GValue Parameterization
A `GValue` is an encapsulation of a parameter name and value. A <<traversal-parameterization,subset of gremlin steps>>
are able to accept GValues. When constructing a `GraphTraversal` with such steps in Python, a GValue may be passed in
the traversal to utilize a parameter in place of a literal.
[source,python]
----
g.V().has('name', GValue('name', 'marko'))
g.merge_v(GValue('vertexPattern', {'name': 'marko'}))
----
[[gremlin-python-scripts]]
=== Submitting Scripts
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('http://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.submit_async('[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.
==== Per Request Settings
The `client.submit()` functions accept a `request_options` which expects a dictionary. The `request_options`
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 `evaluationTimeout` so that it only applies to the current
request.
[source,python]
----
result_set = client.submit('g.V().repeat(both()).times(100)', request_options={'evaluationTimeout': 5000})
----
The following options are allowed on a per-request basis in this fashion: `batchSize`, `requestId`, `userAgent`, `materializeProperties` and
`evaluationTimeout` (formerly `scriptEvaluationTimeout` which is also supported but now deprecated).
IMPORTANT: The preferred method for setting a per-request timeout for scripts is demonstrated above, but those familiar
with bytecode may try `g.with(EVALUATION_TIMEOUT, 500)` within a script. Scripts with multiple traversals and multiple
timeouts will be interpreted as a sum of all timeouts identified in the script for that request.
[source,java]
----
RequestOptions options = RequestOptions.build().timeout(500).create();
List<Result> result = client.submit("g.with(EVALUATION_TIMEOUT, 500).addV().iterate();" +
"g.addV().iterate();
"g.with(EVALUATION_TIMEOUT, 500).addV();", options).all().get();
----
In the above example, `RequestOptions` defines a timeout of 500 milliseconds, but the script has three traversals with
two internal settings for the timeout using `with()`. The request timeout used by the server will therefore be 1000
milliseconds (overriding the 500 which itself was an override for whatever configuration was on the server).
[[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/x.y.z/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').has_label('person').has('name', person_name)
def youngest_friends_age(self):
return self.out('knows').has_label('person').values('age').min()
def created_at_least(self, number):
return self.out_e('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 youngest_friends_age(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).youngest_friends_age(*args)
@classmethod
def created_at_least(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).created_at_least(*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 = traversal(SocialTraversalSource).with_remote(DriverRemoteConnection('ws://localhost:8182/gremlin','g'))
social.persons('marko').knows('josh')
social.persons('marko').youngest_friends_age()
social.persons().filter(__.created_at_least(2)).count()
----
[[gremlin-python-sugar]]
=== 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.
[source,python]
----
>>> g.V().both()[1:3].to_list()
[v[2], v[4]]
>>> g.V().both()[1].to_list()
[v[2]]
>>> g.V().both().name.to_list()
['lop', 'lop', 'lop', 'vadas', 'josh', 'josh', 'josh', 'marko', 'marko', 'marko', 'peter', 'ripple']
----
[[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* - <<all-step,all_()>>, <<and-step,and_()>>, <<any-step,any_()>>, <<as-step,as_()>>, <<filter-step,filter_()>>, <<from-step,from_()>>,
<<id-step,id_()>>, <<is-step,is_()>>, <<in-step,in_()>>, <<max-step,max_()>>,
<<min-step,min_()>>, <<not-step,not_()>>, <<or-step,or_()>>, <<range-step,range_()>>, <<sum-step,sum_()>>,
<<with-step,with_()>>
*Tokens* - <<a-note-on-scopes,Scope.global_>>, `Direction.from_`, `Operator.sum_`
In addition, the enum construct for `Cardinality` cannot have functions attached to it the way it can be done in Java,
therefore cardinality functions that take a value like `list()`, `set()`, and `single()` are referenced from a
`CardinalityValue` class rather than `Cardinality` itself.
[[gremlin-python-limitations]]
=== 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. Using GraphSON 3.0 or GraphBinary (after 3.5.0) makes it possible to return
such results. However, it may not be possible to serialize these maps so they can't be re-inserted (or round tripped).
In all other cases, Gremlin that returns such results will need to be re-written to avoid that sort of key.
* The `float` type in Python is a double precision floating point number which is commonly referred to in other
languages as a `double`. This means that single precision floating point values will be deserialized into a double, so
there will be a precision difference.
* Gremlin supports multiple fixed-width integers such as byte (1-byte), short (2-byte), and long (8-byte). These are
deserialized into Python's variable size `int` type. These numbers can't be exactly round tripped because the original
type information is lost during deserialization. During serialization, these numbers will try to be converted into a
4-byte integer which may throw exceptions if the value is too large or waste space if the value is very small.
* The Gremlin Char type is deserialized as a string and therefore can't be round tripped as it will it will attempt to
be serialized as a string.
* Date and Duration types in Gremlin are deserialized as `datetime.datetime` and `datetime.timedelta` respectively in
`gremlin-python`. This can lead to errors for large values because they exceed the maximum size allowed for `datetime`
and `timedelta`.
* In Gremlin, 1 isn't equal to the boolean true value and 0 isn't equal to the boolean false value, but they are equal
in Python. This means that in `gremlin-python` if these values are in a `Set`, you will get a different behavior than
what is intended by Gremlin, since it follows Python's behavior.
* The `subgraph()`-step is not supported by any variant that is not running on the Java Virtual Machine as there is
no `Graph` instance to deserialize a result into on the client-side. A workaround is to replace the step with
`aggregate(local)` and then convert those results to something the client can use locally.
* Use of the aiohttp library in the default transport requires the use of asyncio's event loop to run the async functions.
This can be an issue in situations where the application calling Gremlin-Python is already using an event loop.
Certain types of event loops can be patched using nest-asyncio which allows Gremlin-Python to proceed without an error like
"Cannot run the event loop while another loop is running". This is the preferred approach to avoiding the issue and can be
enabled by passing `call_from_event_loop=True` to the `AiohttpTransport` class.
+
However, in situations where the loop cannot be patched (e.g. uvloop), then the current suggested workaround is to run
Gremlin-Python in a separate thread. This is not ideal for asynchronous web servers as the number of concurrent connections
will be limited by the number of threads the system can handle. The following snippet shows how Gremlin-Python can be called
from asynchronous code using a thread.
+
[source,python]
----
def print_vertices():
g = traversal().with(DriverRemoteConnection("ws://localhost:8182/gremlin"))
# Do your traversal.
async def run_in_thread():
running_loop = asyncio.get_running_loop()
with ThreadPoolExecutor() as pool:
await running_loop.run_in_executor(pool, print_vertices)
----
[[gremlin-python-examples]]
=== Application Examples
The TinkerPop source code contains some sample applications that demonstrate the basics of Gremlin-Python. They
can be found in GitHub link:https://github.com/apache/tinkerpop/tree/x.y.z/glv-examples/gremlin-python/[here]
and are designed to connect to a running <<gremlin-server,Gremlin Server>> configured with the
`conf/gremlin-server.yaml` and `conf/gremlin-server-modern.yaml` files as included with the standard release packaging.
This guide assumes Gremlin Server will be executed using Docker. Alternatively, Gremlin Server can run locally (see
the <<gremlin-server,Gremlin Server>> documentation for this option).
To start Gremlin Server using Docker, first download an image of Gremlin Server from Docker Hub:
[source,shell]
----
docker pull tinkerpop/gremlin-server
----
Clean server:
[source,shell]
----
docker run -d -p 8182:8182 tinkerpop/gremlin-server
----
Modern toy graph server:
[source,shell]
----
docker run -d -p 8182:8182 tinkerpop/gremlin-server conf/gremlin-server-modern.yaml
----
The remote connection and basic Gremlin examples can be run on a clean server, while traversal examples should be
run on a server with the Modern graph preloaded.
==== Prerequisites
- Compatible Python installed (see <<development-environment,Development Environment>> for supported versions)
- pip installed
NOTE: On some systems, you may need to use `python3` and `pip3` instead of `python` and `pip`.
Navigate to the examples directory:
[source,shell]
----
cd glv-examples/gremlin-python
----
Install the requirements:
[source,shell]
----
pip install -r requirements.txt
----
Run the examples:
[source,shell]
----
python connections.py
python basic_gremlin.py
python modern_traversals.py
----