blob: 6e96938a456da50528442c0d7ce6076c94edd0b0 [file]
////
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, <<canonical-gremlin-definition,Canonical Gremlin>>). 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.
[[canonical-gremlin-definition]]
=== Canonical Gremlin and GremlinLang
"Canonical Gremlin" refers to the standard, language-neutral string representation of a Gremlin traversal as defined by
the ANTLR grammar in the `gremlin-language` module. This grammar is the single source of truth for valid Gremlin syntax
and is what the `gremlin-lang` script engine on Gremlin Server uses to parse incoming requests.
Each GLV driver produces canonical Gremlin strings through a class called `GremlinLang`. When a traversal is built in
any language (Java, Python, Go, .NET, JavaScript), the driver translates it into a canonical Gremlin string along with a
map of named parameters. This string is what gets sent to the server over HTTP. Because all drivers produce the same
canonical format, the wire protocol is human-readable and consistent across languages.
For example, the following traversals in different languages all produce the same canonical Gremlin string
`g.V().hasLabel("person")`:
[source,java]
----
// Java
GraphTraversal t = g.V().hasLabel("person");
String script = t.asAdmin().getGremlinLang().getGremlin();
----
[source,python]
----
# Python
t = g.V().has_label("person")
script = t.gremlin_lang.get_gremlin()
----
When documentation or code refers to a "gremlin-lang string", "canonical Gremlin", or "canonical format", it means a
traversal string that conforms to this ANTLR grammar. The `GremlinTranslator` class can convert canonical Gremlin into
language-specific syntax for any supported GLV (see <<translators, Translating Gremlin>>).
==== String Escaping
When serializing string values into canonical Gremlin, all GLVs escape the following characters so that the resulting
string is valid within the ANTLR grammar:
[cols="1,1",options="header"]
|===
|Character |Escape Sequence
|\ |\\
|" |\"
|newline |\n
|carriage return |\r
|tab |\t
|backspace |\b
|form feed |\f
|===
For example, a string value `say "hello"` would be serialized as `"say \"hello\""` in the canonical Gremlin string.
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
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.
The Gremlin-Go driver communicates with Gremlin Server over HTTP using the GraphBinary 4.0 serialization format.
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.8.0]
----
The following table outlines recommended runtime versions by the release in which their support began:
[cols="1,1",options="header"]
|===
|Version |Recommended Go Version
|3.4.0 |N/A
|3.4.13 |N/A
|3.5.0 |N/A
|3.5.4 |≥1.17
|3.5.8 |≥1.20
|3.6.0 |≥1.17
|3.6.7 |≥1.21
|3.6.8 |≥1.22
|3.7.0 |≥1.20
|3.7.2 |≥1.21
|3.7.3 |≥1.22
|3.7.4 |≥1.24
|3.7.6 |≥1.25
|3.8.0 |≥1.25
|===
[[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("http://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("http://localhost:8182/gremlin",
func(settings *DriverRemoteConnectionSettings) {
settings.TraversalSource = "gmodern"
})
----
Gremlin-Go contains an `auth` module with built-in request interceptors. Interceptors
are functions that modify the outgoing HTTP request before it is sent — they are used for authentication, custom
headers, or request signing. Plain text and SigV4 authentication are provided as built-in interceptors.
[source,go]
----
// Plain text authentication
remote, err := gremlingo.NewDriverRemoteConnection("https://localhost:8182/gremlin",
func(settings *gremlingo.DriverRemoteConnectionSettings) {
settings.Ssl = &tls.Config{InsecureSkipVerify: true}
settings.Interceptors = []gremlingo.RequestInterceptor{
auth.Basic("username", "password"),
}
})
// SigV4 authentication
remote, err := gremlingo.NewDriverRemoteConnection("https://localhost:8182/gremlin",
func(settings *gremlingo.DriverRemoteConnectionSettings) {
settings.Interceptors = []gremlingo.RequestInterceptor{
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://'.
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`, `bulkResults`, `userAgent` and
`evaluationTimeout`.
NOTE: When submitting traversals through `DriverRemoteConnection`, `bulkResults` defaults to `true` per-request
to optimize result transfer. For direct `Client.Submit()` calls, set the connection-level `BulkResults` option to
apply a default to every request, or set `bulkResults` per-request to override it.
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"
|LogVerbosity |Log verbosity.|gremlingo.INFO
|Logger |Instance of logger. |log
|Language |Language used for logging messages. |language.English
|Ssl |TLS configuration. |empty
|ConnectTimeoutMillis | Timeout in milliseconds for establishing the connection (TCP connect plus TLS handshake). Also settable as `ConnectTimeout` (a `time.Duration`); set only one. |5000
|MaxConnections | Maximum number of concurrent TCP connections to the server. |128
|MaxIdleConnections | Maximum number of idle (keep-alive) connections in the pool. |8
|IdleTimeoutMillis | How long in milliseconds idle connections remain in the pool before being closed. Also settable as `IdleTimeout` (a `time.Duration`); set only one. |180000
|KeepAliveTimeMillis | Idle time in milliseconds before TCP keep-alive probes begin. Also settable as `KeepAliveTime` (a `time.Duration`); set only one. |30000
|ReadTimeoutMillis | Idle-read timeout in milliseconds reset on each read of the response body. Set to `0` to disable. Also settable as `ReadTimeout` (a `time.Duration`); set only one. |0
|Compression |The wire compression negotiated with the server (`gremlingo.CompressionNone` or `gremlingo.CompressionDeflate`). |gremlingo.CompressionDeflate
|BatchSize |The connection-level default batch size used when a request does not specify one. |64
|BulkResults |Connection-level default for `bulkResults`, applied to every request unless overridden per-request. The `DriverRemoteConnection` traversal path defaults to `true` regardless of this setting. |false
|MaxResponseHeaderBytes |Limit on the number of response header bytes the client will read. Maps to `http.Transport.MaxResponseHeaderBytes`. |0 (net/http default)
|Proxy |Function returning the proxy URL to use for a request. When `nil`, uses `http.ProxyFromEnvironment`. |nil
|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
|Interceptors |Functions that modify HTTP requests before sending. Used for authentication and custom headers. |empty
|PDTRegistry |A `*PDTRegistry` for hydrating and dehydrating <<gremlin-go-pdt,Provider Defined Types>>. |`nil`
|Auth |A single RequestInterceptor for authentication (e.g. `auth.Basic`). Always appended to the end of the interceptor list so it runs last. |nil
|=========================================================
Note that no driver timeout bounds the *total* duration of a request once it is under way. `ReadTimeout` only bounds
the gap between response chunks, so a response that keeps producing chunks will not time out no matter how long it
runs overall, and there is no client-side "overall" request timeout. If you need an absolute deadline, impose it in
your application around the call. Because `Submit` does not accept a `context.Context`, run it in a goroutine and
bound it with a `select`:
[source,go]
----
// bound the entire request (submit plus full result iteration) to 30 seconds
type outcome struct {
results []*gremlingo.Result
err error
}
done := make(chan outcome, 1)
go func() {
rs, err := client.Submit("g.V().out().out()")
if err != nil {
done <- outcome{nil, err}
return
}
results, err := rs.All()
done <- outcome{results, err}
}()
select {
case o := <-done:
// use o.results / o.err
case <-time.After(30 * time.Second):
// deadline exceeded; stop waiting on the request
}
----
[[gremlin-go-interceptors]]
=== RequestInterceptor
Gremlin-Go allows modification of the underlying HTTP request through `RequestInterceptor` functions. 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-Go is written in a way that you should be able to interact with most TinkerPop-enabled
servers without having to use interceptors. This is intended for cases where the server has special capabilities.
A `RequestInterceptor` is a function with the signature `func(*HttpRequest) error` that mutates the `HttpRequest`
in place. A slice of these is maintained and will be run sequentially for each request. When creating a
`DriverRemoteConnection` or `Client`, the `Interceptors` field on the settings struct accepts an ordered
slice of interceptors. Order matters, so if one interceptor depends on another's output, ensure they are added in
the correct order. Note that authentication (e.g. `auth.Basic`, `auth.SigV4`) is also implemented using interceptors.
The `auth` convenience on connection settings appends the auth interceptor to the end of the list so it runs last.
[source,go]
----
remote, err := gremlingo.NewDriverRemoteConnection("http://localhost:8182/gremlin",
func(settings *gremlingo.DriverRemoteConnectionSettings) {
settings.Interceptors = []gremlingo.RequestInterceptor{
auth.Basic("username", "password"),
func(req *gremlingo.HttpRequest) error {
req.Headers.Set("X-Custom-Header", "value")
return nil
},
}
})
----
Each interceptor receives a `*gremlingo.HttpRequest` whose `Body` field initially contains a `*RequestMessage`.
The `RequestMessage` struct has mutable fields (`Gremlin string` and `Fields map[string]interface{}`), so an
interceptor can modify them directly or replace the body entirely. By default, the driver serializes the body
to JSON (`application/json`) by calling `HttpRequest.SerializeBody()` after all interceptors have run. An
interceptor that needs the serialized bytes (for example, to compute a signature hash) can call
`SerializeBody()` itself; the method is idempotent. If you require a GraphBinary-encoded request body instead
of JSON, you can write a custom interceptor that serializes the `RequestMessage` with the GraphBinary serializer
and sets the resulting `[]byte` as the body.
For an example of a simple `RequestInterceptor` that only modifies the header of the request, see
link:https://github.com/apache/tinkerpop/blob/x.y.z/gremlin-go/driver/auth.go[basic authentication].
[[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 GremlinLang and transmitted to the Gremlin traversal machine for
re-construction machine-side.
[[gremlin-go-transactions]]
=== Transactions
Gremlin-Go supports remote transactions over HTTP via two entry points: the Traversal API (`g.Tx()`) and the
Driver API (`client.Transact()`).
==== Traversal API
[source,go]
----
g := gremlingo.Traversal_().With(remote)
tx := g.Tx()
gtx, err := tx.Begin()
if err != nil { log.Fatal(err) }
_, err = gtx.AddV("person").Property("name", "alice").Iterate()
if err != nil { tx.Rollback(); log.Fatal(err) }
err = tx.Commit()
if err != nil { log.Fatal(err) }
----
==== Managed Transaction Blocks
To avoid the manual begin/commit/rollback boilerplate, `g` provides closure methods that manage the
lifecycle: the closure receives the transaction-bound `gtx` and the transaction is committed when it returns a `nil`
error or rolled back if it returns an error (or panics). `ExecuteInTx` is for the common no-result case; `EvaluateInTx`
returns the value the body produces as `interface{}`, which the caller type-asserts:
[source,go]
----
g := gremlingo.Traversal_().With(remote)
// ExecuteInTx: no return value
err := g.ExecuteInTx(func(gtx *gremlingo.GraphTraversalSource) error {
_, e := gtx.AddV("person").Property("name", "alice").Iterate()
return <-e
})
// EvaluateInTx: returns the body's value as interface{}
v, err := g.EvaluateInTx(func(gtx *gremlingo.GraphTraversalSource) (interface{}, error) {
return gtx.V().Count().Next()
})
count := v.(int64)
----
The transaction runs exactly once (no automatic retry). The body's error is returned after the rollback; if the commit
fails, a rollback is still attempted and the commit error is returned. A panic in the body rolls back and then
re-panics.
==== Driver API
[source,go]
----
client, _ := gremlingo.NewClient(url, func(settings *gremlingo.ClientSettings) {
settings.TraversalSource = "g"
})
tx := client.Transact()
tx.Begin()
tx.Submit("g.addV('person').property('name','alice')")
tx.Commit()
----
NOTE: Transactions are not thread-safe. All operations within a transaction must be sequential. The default behavior
of `Close()` is to rollback. Transactions are single-use and cannot be reused after commit or rollback.
[[gremlin-go-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 Go, a GValue may be passed in
the traversal to utilize a parameter in place of a literal.
[source,go]
----
g.V().Has("name", gremlingo.NewGValue("name", "marko"))
----
[[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("http://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`, `bulkResults`, `userAgent`,
`evaluationTimeout` and `materializeProperties`.
`RequestOptions` may also contain a map of variable `bindings` to be applied to the supplied
traversal string.
[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 GremlinLang.
func (sts *socialTraversalSource) persons(personNames ...interface{}) *socialTraversal {
t := sts.GetGraphTraversal()
t.GremlinLang.AddStep("V")
t.GremlinLang.AddStep("hasLabel", "person")
if personNames != nil {
t.GremlinLang.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.NewGremlinLang(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("http://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.
Because Go does not support method overloading, the batched form of `next(n)` available in other GLVs is exposed as a
distinct method `NextN(n)` on `Traversal`. `Next()` returns a single `*Result`, while `NextN(n)` returns up to `n`
results as `[]*Result`.
[[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.
* The `subgraph()`-step returns a detached `*Graph` data container exposing
`Vertices map[interface{}]*Vertex` and `Edges map[interface{}]*Edge`. The result is not a live `Graph` instance:
mutating the maps has no effect on the source graph, and it cannot be passed to `traversal().with(...)`. To
re-query subgraph elements against the original graph, extract their `Id` and use `g.V(id)` / `g.E(id)` on the
original `GraphTraversalSource`.
[[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/gremlin-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 gremlin-examples/gremlin-go
----
Run the examples:
[source,shell]
----
go run connections.go
go run basic_gremlin.go
go run modern_traversals.go
----
[[gremlin-go-pdt]]
=== Provider Defined Types
Provider Defined Types (PDTs) allow graph providers to expose custom types through the driver. PDT values arrive as
`*ProviderDefinedType` structs containing a `Name` and `Fields` map without any configuration.
Consult your graph provider's documentation for the list of PDTs they support.
[source,go]
----
results, err := g.V().Has("location").Values("location").ToList()
pdt := results[0].GetInterface().(*gremlingo.ProviderDefinedType)
fmt.Println(pdt.Name) // "x:Point"
fmt.Println(pdt.Fields) // map[x:1.0 y:2.0]
----
Working with raw `*ProviderDefinedType` values is always available. Using a `PDTRegistry` is an optional
convenience that automates conversion between PDT values and application types on both the request and response paths.
Using a `PDTRegistry` for hydration and dehydration:
[source,go]
----
registry := gremlingo.NewPDTRegistry()
registry.RegisterFuncsWithType("x:Point", reflect.TypeOf(Point{}),
// hydrate: convert incoming PDT fields map to a Go type
func(fields map[string]interface{}) (interface{}, error) {
return &Point{X: fields["x"].(float64), Y: fields["y"].(float64)}, nil
},
// dehydrate: convert a Go type to a PDT fields map for sending
func(obj interface{}) (map[string]interface{}, error) {
p := obj.(*Point)
return map[string]interface{}{"x": p.X, "y": p.Y}, nil
},
)
remote, _ := gremlingo.NewDriverRemoteConnection("http://localhost:8182/gremlin",
func(settings *gremlingo.DriverRemoteConnectionSettings) {
settings.PDTRegistry = registry
})
g := gremlingo.Traversal_().With(remote)
----
[[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>
----
The following table outlines recommended runtime versions by the release in which their support began:
[cols="1,2",options="header"]
|===
|Version |Supported Java Versions
|3.4.0 | Java 8
|3.5.0 | Java 8, 11
|3.6.0 | Java 8, 11
|3.7.0 | Java 8, 11, 17
|3.8.0 | Java 11, 17
|===
[[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`, `bulkResults`, `userAgent`,
`materializeProperties` and `evaluationTimeout`. Use of `Tokens` to reference these options is preferred.
NOTE: When submitting traversals through `DriverRemoteConnection`, `bulkResults` defaults to `true` per-request
to optimize result transfer. This does not apply to direct `Client.submit()` calls, where `bulkResults` must be
set explicitly if desired.
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.compression |The wire compression algorithm negotiated with the server. Can be: `NONE` or `DEFLATE`. |DEFLATE
|connectionPool.connectTimeoutMillis | Duration of time in milliseconds that bounds TCP connection establishment (transport setup, including the SSL handshake). On `Cluster.Builder` also settable as `connectTimeout(Duration)`. |5000
|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.idleTimeoutMillis | Duration of time in milliseconds that the driver will allow a channel to not receive read or writes before it automatically closes. On `Cluster.Builder` also settable as `idleTimeout(Duration)`. |180000
|connectionPool.keepAliveTimeMillis | Idle time in milliseconds before TCP keep-alive probes begin on an otherwise idle connection. Enables `SO_KEEPALIVE` on the socket and, where supported (JDK 11+ on Linux/macOS via `TCP_KEEPIDLE`), sets the per-socket idle time. Set to `0` to disable. On `Cluster.Builder` also settable as `keepAliveTime(Duration)`. |30000
|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.maxConnections |The maximum size of a connection pool for a host. |128
|connectionPool.maxResponseHeaderBytes |The maximum size in bytes allowed for the HTTP response headers. |8192
|connectionPool.maxWaitForConnection |The amount of time in milliseconds to wait for a new connection before timing out. |16000
|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.batchSize |The default value for the per-request batch size used when a request does not specify one. |64
|connectionPool.readTimeoutMillis |Idle-read timeout in milliseconds that bounds the time between inbound response chunks. Set to `0` to disable. On `Cluster.Builder` also settable as `readTimeout(Duration)`. |0
|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 `g.inject(0)` runs a trivial traversal that returns immediately. |_g.inject(0)_
|auth |An authentication `RequestInterceptor` (e.g. `Auth.basic()`). Always appended to the end of the interceptor list so it runs last. |_none_
|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
|interceptors |A list of `RequestInterceptor` instances that modify the HTTP request before sending. |empty
|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. |8182
|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
Gremlin-Java supports remote transactions over HTTP. There are two ways to create a transaction: the Traversal API
(`g.tx()`) and the Driver API (`cluster.transact()`).
==== Traversal API
The recommended approach uses the standard `g.tx()` pattern:
[source,java]
----
GraphTraversalSource g = traversal().with(DriverRemoteConnection.using("localhost", 8182, "g"));
Transaction tx = g.tx();
GraphTraversalSource gtx = tx.begin();
try {
gtx.addV("person").property("name", "jorge").iterate();
gtx.addV("person").property("name", "josh").iterate();
tx.commit();
} catch (Exception ex) {
tx.rollback();
}
----
Traversals spawned from `gtx` are bound to the transaction. The driver handles host pinning and transaction ID
propagation automatically.
==== Managed Transaction Blocks
To avoid the manual begin/commit/rollback boilerplate, `g` also provides closure methods that manage the
transaction lifecycle. The closure receives the transaction-bound `gtx`, and the transaction is committed when the
closure completes normally or rolled back if it throws. Use `executeInTx` when the body returns nothing and
`evaluateInTx` when it returns a value:
[source,java]
----
GraphTraversalSource g = traversal().with(DriverRemoteConnection.using("localhost", 8182, "g"));
// executeInTx: no return value
g.executeInTx(gtx -> {
gtx.addV("person").property("name", "jorge").iterate();
gtx.addV("person").property("name", "josh").iterate();
});
// evaluateInTx: returns the body's value
long count = g.evaluateInTx(gtx -> gtx.V().count().next());
----
The transaction runs exactly once (no automatic retry). If the closure throws, the original exception is re-thrown
after the rollback; if the commit fails, a rollback is still attempted to release server-side resources and the commit
error propagates. These methods are also available on embedded graphs that support transactions.
==== Driver API
For script-based usage or when working with the `Client` API directly, transactions can be created from the `Cluster`:
[source,java]
----
Cluster cluster = Cluster.build("localhost").create();
RemoteTransaction tx = cluster.transact("g");
tx.begin();
try {
tx.submit("g.addV('person').property('name','alice')");
tx.submit("g.addV('person').property('name','bob')");
tx.commit();
} catch (Exception ex) {
tx.rollback();
}
cluster.close();
----
The `RemoteTransaction` returned by `cluster.transact()` provides `submit()` methods that accept Gremlin strings
and optional `RequestOptions` or parameter maps. All requests are pinned to a single host and include the transaction
ID automatically.
NOTE: `RemoteTransaction` is synchronous and not thread-safe. All requests within a transaction are executed
sequentially.
==== Close Behavior
The default behavior of `close()` on a transaction is to rollback. This can be changed:
[source,java]
----
RemoteTransaction tx = cluster.transact("g");
tx.onClose(Transaction.CLOSE_BEHAVIOR.COMMIT);
tx.begin();
// ... do work ...
tx.close(); // commits instead of rolling back
----
[[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.2 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-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 `RequestInterceptors`. 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 most TinkerPop-enabled
servers without having to use interceptors. This is intended for cases where the server has special capabilities.
A `RequestInterceptor` is a function that mutates the `HttpRequest` in place (it returns nothing).
A list of these is maintained and will be run sequentially for each request. When building a
`Cluster` instance, the `interceptors()` method accepts an ordered list of 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.
The `auth()` convenience on the `Cluster.Builder` appends the auth interceptor to the end of the list so it runs last.
Each 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. By default, the driver serializes the body to JSON (`application/json`) by calling
`HttpRequest.serializeBody()` after all interceptors have run. An interceptor that needs the
serialized bytes (for example, to compute a signature hash) can call `serializeBody()` itself; the
method is idempotent. If you require a GraphBinary-encoded request body instead of JSON, you can
write a custom interceptor that serializes the `RequestMessage` with the GraphBinary serializer and
sets the resulting `byte[]` as 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.
Responses are now streamed rather than buffered to a fixed size, so this client-side cap no longer exists. If large
responses are causing timeouts, increase the `readTimeout` on the driver and consider applying server-side limits
(such as result iteration or serialization limits) to constrain the size of responses being returned.
Note that no driver timeout bounds the *total* duration of a request once it is under way. `readTimeout` only bounds
the gap between response chunks, so a response that keeps producing chunks will not time out no matter how long it
runs overall, and there is no client-side "overall" request timeout. If you need an absolute deadline, impose it in
your application around the asynchronous API, for example by wrapping a `CompletableFuture` with a timeout:
[source,java]
----
// bound the entire request (submit plus full result iteration) to 30 seconds
final List<Result> results = client.submitAsync("g.V().out().out()")
.thenCompose(ResultSet::all)
.orTimeout(30, TimeUnit.SECONDS)
.get();
----
*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=http://localhost:45940/gremlin}. Potential Cause: Connection refused: no further information
> ConnectionPool (Host{address=localhost/127.0.0.1:45940, hostUri=http://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=http://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=http://localhost:45940/gremlin})
Connection Pool Status (size=1 available=1 max=1 toCreate=0 bin=0 waiter=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 connection setup does not complete in time_
[source,text]
----
Timed-out (250 MILLISECONDS) waiting for connection on Host{address=localhost/127.0.0.1:45940, hostUri=http://localhost:45940/gremlin}. Potential Cause: Connection setup not completed in stipulated time=[100]ms
ConnectionPool (Host{address=localhost/127.0.0.1:45940, hostUri=http://localhost:45940/gremlin})
Connection Pool Status (size=1 available=1 max=5 toCreate=0 bin=0 waiter=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 --
----
[[gremlin-java-differences]]
=== Differences
Gremlin-Java provides additional syntactic sugar that leverages Java's type system for the `P.typeOf()` predicate,
which accepts Java `Class` objects directly, providing a more natural way to perform type checking:
[gremlin-groovy,modern]
----
// Java-specific syntax using Class objects
g.V().values("age").is(P.typeOf(Integer.class))
g.V().values("name").is(P.typeOf(String.class))
// Further simplification with Groovy sugar syntax
g.E().has("weight", P.typeOf(Double))
----
This is equivalent to using `GType` enums. Other Gremlin language variants must use the canonical `GType` enum approach:
[gremlin-groovy,modern]
----
// Canonical syntax available in all languages
g.V().values("age").is(P.typeOf(GType.INT))
g.V().values("name").is(P.typeOf(GType.STRING))
----
Any valid Java class accepted in the Console and with embedded Java is also accepted by `P.typeOf()`, as they are not
restricted by the grammar or serialization.
[gremlin-groovy,modern]
----
// Using java.awt.Color for example
g.inject(java.awt.Color.red)
g.inject(java.awt.Color.red, "hi", 123).is(P.typeOf(java.awt.Color))
----
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/gremlin-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 gremlin-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-java-pdt]]
=== Provider Defined Types
Provider Defined Types (PDTs) allow graph providers to expose custom types through the driver. PDT values arrive as
`ProviderDefinedType` objects containing a name and fields map without any configuration.
Consult your graph provider's documentation for the list of PDTs they support.
Receiving a raw PDT:
[source,java]
----
ProviderDefinedType pdt = (ProviderDefinedType) g.V().has("location").values("location").next();
String typeName = pdt.getName(); // "x:Point"
Map<String, Object> fields = pdt.getFields(); // {x: 1.0, y: 2.0}
----
Working with raw `ProviderDefinedType` objects is always available. The following two approaches are optional
conveniences that automate conversion between PDT values and application types on both the request and response paths.
Using a `ProviderDefinedTypeRegistry` for hydration and dehydration:
----
public class PointAdapter implements ProviderDefinedTypeAdapter<Point> {
@Override public String typeName() { return "x:Point"; }
@Override public Class<Point> targetClass() { return Point.class; }
@Override public Map<String, Object> toFields(Point p) { return Map.of("x", p.getX(), "y", p.getY()); }
@Override public Point fromFields(Map<String, Object> m) { return new Point((double) m.get("x"), (double) m.get("y")); }
}
----
Register adapters via ServiceLoader by adding the fully qualified class name to
`META-INF/services/org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter`. The driver discovers
adapters on the classpath and automatically hydrates/dehydrates.
For simpler cases where you own the type, annotate it directly to avoid writing an adapter:
Annotation-based conversion with `@ProviderDefined`:
[source,java]
----
// includedFields: only serialize the listed fields
@ProviderDefined(name = "x:Point", includedFields = {"x", "y"})
public class Point {
private final double x;
private final double y;
private final String internalId; // not serialized
// constructor, getters...
}
// excludedFields: serialize all fields except those listed
@ProviderDefined(name = "x:Timestamped", excludedFields = {"createdAt"})
public class Timestamped {
private final String value;
private final long createdAt; // not serialized
// constructor, getters...
}
// send: Point is automatically dehydrated
g.inject(new Point(1.0, 2.0, "internal")).iterate();
// receive: PDT is automatically hydrated back to Point
Point p = (Point) g.V().has("location").values("location").next();
----
Classes annotated with `@ProviderDefined` are automatically dehydrated when passed as traversal arguments and
hydrated on deserialization without explicit registry configuration.
[[gremlin-javascript]]
== Gremlin-JavaScript
image:gremlin-js.png[width=130,float=right] Apache TinkerPop's Gremlin-JavaScript implements Gremlin within the
JavaScript language. It targets Node.js runtime and can be used on different operating systems. 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
The following table outlines recommended runtime versions by the release in which their support began:
[cols="1,1,1",options="header"]
|===
|Version |Recommended Node.js Version |npm Version
|3.4.0 |6 |N/A
|3.5.0 |10 |6.14.16
|3.5.7 |16 |8.19.4
|3.6.0 |10 |8.1.2
|3.6.5 |16 |8.19.4
|3.6.7 |18 |9.6.7
|3.7.0 |18 |9.6.7
|3.7.4 |20, 22, 24 |10.8.2
|3.7.7 |20, 22, 24, 26 |10.8.2
|3.8.0 |20, 22, 24 |10.8.2
|3.8.2 |20, 22, 24, 26 |10.8.2
|4.0.0 |22, 24, 26 |10.9.7
|===
[[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('http://localhost:8182/gremlin'));
----
Gremlin-JavaScript supports basic HTTP authentication via request interceptors. The `auth.basic()` function returns an
interceptor that adds an `Authorization` header to each request.
[source,javascript]
----
const { auth } = gremlin.driver;
const g = traversal().with_(new DriverRemoteConnection('http://localhost:8182/gremlin', {
interceptors: auth.basic('myuser', 'mypassword')
}));
----
Signature Version 4 authentication is also supported via `auth.sigv4()`. This requires the
`@smithy/signature-v4`, `@smithy/hash-node`, and optionally `@aws-sdk/credential-providers` packages to be installed.
When no `credentialsProvider` is supplied, the default credential provider chain is used.
[source,javascript]
----
const { auth } = gremlin.driver;
const g = traversal().with_(new DriverRemoteConnection('http://localhost:8182/gremlin', {
interceptors: auth.sigv4("service-region", "service-name")
}));
----
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.next(amount)`: Returns a `Promise` with an `Array` of up to `amount` result values, the batched form
of `next(n)` available in the other GLVs. The `Array` is always returned when an `amount` is supplied, so `next(1)`
resolves to an `Array` of one value, and an `amount` of zero or less resolves to an empty `Array`.
* `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`,
`bulkResults`, `materializeProperties` and `evaluationTimeout`.
NOTE: When submitting traversals through `DriverRemoteConnection`, `bulkResults` defaults to `true` per-request
to optimize result transfer. This does not apply to direct `Client.submit()` calls, where `bulkResults` must be
set explicitly if desired.
[[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.
==== ES Modules
[source,javascript]
----
import gremlin from 'gremlin';
const traversal = gremlin.process.AnonymousTraversalSource.traversal;
const __ = gremlin.process.statics;
const DriverRemoteConnection = gremlin.driver.DriverRemoteConnection;
const { P: p, TextP: textp, column, direction, pick, pop, order, scope, t, cardinality, CardinalityValue } = gremlin.process;
----
==== CommonJS
[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.traversalSource |String |The traversal source. |'g'
|options.headers |Object |Additional HTTP header key/values included with each request. |undefined
|options.interceptors |RequestInterceptor/RequestInterceptor[] |One or more functions that can modify the HTTP request before it is sent. |undefined
|options.auth |RequestInterceptor |An auth interceptor that is always appended to the end of the interceptor list so it runs last. |undefined
|options.preciseNumbers |Boolean |When `true`, wraps deserialized numbers in typed wrappers that preserve the server's original type. |undefined
|options.reader |GraphBinaryReader |The reader to use for deserializing responses. |GraphBinaryReader
|options.writer |GraphBinaryWriter |The writer to use for serializing requests. |GraphBinaryWriter
|options.enableUserAgentOnConnect |Boolean |Determines if a user agent header will be sent with requests. |true
|options.agent |Agent |A custom `node:http` or `node:https` Agent for connection pooling or proxy configuration. |undefined
|options.pdtRegistry |ProviderDefinedTypeRegistry |A registry for hydrating and dehydrating <<gremlin-javascript-pdt,Provider Defined Types>>. |undefined
|=========================================================
[[gremlin-javascript-interceptors]]
=== RequestInterceptor
Gremlin-JavaScript allows modification of the underlying HTTP request through `RequestInterceptor` functions. 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-JavaScript is written in a way that you should be able to interact with most
TinkerPop-enabled servers without having to use interceptors. This is intended for cases where the server has special
capabilities.
A `RequestInterceptor` is a function with the signature `(request: HttpRequest) => void | Promise<void>` that mutates
the `HttpRequest` in place (it returns nothing). A list of these is maintained and will be run sequentially for each
request. When creating a `DriverRemoteConnection` or `Client`, the `interceptors` option accepts one interceptor or an
array of interceptors. Order matters, so if one interceptor depends on another's output, ensure they are added in the
correct order. Note that authentication (e.g. `auth.basic()`, `auth.sigv4()`) is also implemented using interceptors.
The `auth` convenience on connection options appends the auth interceptor to the end of the list so it runs last.
[source,javascript]
----
const { auth } = gremlin.driver;
const g = traversal().with_(new DriverRemoteConnection('http://localhost:8182/gremlin', {
interceptors: [
auth.basic('myuser', 'mypassword'),
(request) => {
request.headers['X-Custom-Header'] = 'value';
}
]
}));
----
Each interceptor receives an `HttpRequest` whose `body` field initially contains a `RequestMessage`. The
`RequestMessage` class is immutable (fields are private, accessed via getters). To customize the body by adding other
fields, you must build a new `RequestMessage` via `RequestMessage.build(gremlin)` and its builder methods, or set a
completely different body (such as a `Buffer`). By default, the driver serializes the body to JSON
(`application/json`) by calling `HttpRequest.serializeBody()` after all interceptors have run. An interceptor that
needs the serialized bytes (for example, to compute a signature hash) can call `serializeBody()` itself; the method
is idempotent. If you require a GraphBinary-encoded request body instead of JSON, you can write a custom interceptor
that serializes the `RequestMessage` with the GraphBinary writer and sets the resulting `Buffer` as the body.
For an example of a simple `RequestInterceptor` that only modifies the header of the request, see
link:https://github.com/apache/tinkerpop/blob/x.y.z/gremlin-js/gremlin-javascript/lib/driver/auth.ts[basic authentication].
[[gremlin-javascript-logging]]
=== Logging
IMPORTANT: 4.0.0-beta.2 Release - Event-based logging is not yet implemented for the HTTP driver. Listeners can be registered
but will not currently receive any events. This functionality is planned for a future release.
The Gremlin-JavaScript driver emits connection events for logging and monitoring, but these events are silently ignored unless a listener is attached. Use the `addListener()` method to subscribe to events after creating a `DriverRemoteConnection` or `Client`:
[source,javascript]
----
// Using DriverRemoteConnection
const dc = new DriverRemoteConnection('http://localhost:8182/gremlin');
dc.addListener('log', (message) => {
console.log('[Gremlin Log]', message);
});
dc.addListener('error', (err) => {
console.error('[Gremlin Error]', err);
});
const g = traversal().with_(dc);
// Using Client
const client = new Client('http://localhost:8182/gremlin', { traversalSource: 'g' });
client.addListener('log', (message) => {
console.log('[Gremlin Log]', message);
});
client.addListener('error', (err) => {
console.error('[Gremlin Error]', err);
});
----
Available events:
* `log` - General connection logs and diagnostics
* `error` - Request errors
To remove a listener, use `removeListener(event, handler)` with the same handler reference.
[[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.
Gremlin-Javascript supports remote transactions over HTTP via two entry points: the Traversal API (`g.tx()`) and the
Driver API (`client.transact()`).
==== Traversal API
[source,javascript]
----
const g = traversal().with_(new DriverRemoteConnection('http://localhost:8182/gremlin'));
const tx = g.tx();
const gtx = await tx.begin();
await gtx.addV("person").property("name", "jorge").iterate();
await gtx.addV("person").property("name", "josh").iterate();
await tx.commit();
----
==== Managed Transaction Blocks
To avoid the manual begin/commit/rollback boilerplate, `g` provides an `executeInTx` method that manages the
lifecycle. The callback receives the transaction-bound `gtx` and may be `async`; the transaction is committed when the
callback resolves or rolled back if it throws. `executeInTx` resolves to whatever the callback returns:
[source,javascript]
----
const g = traversal().with_(new DriverRemoteConnection('http://localhost:8182/gremlin'));
// no return value
await g.executeInTx(async (gtx) => {
await gtx.addV("person").property("name", "jorge").iterate();
await gtx.addV("person").property("name", "josh").iterate();
});
// returns the callback's value
const count = await g.executeInTx((gtx) => gtx.V().count().next());
----
The transaction runs exactly once (no automatic retry). If the callback rejects, the original error is re-thrown after
the rollback; if the commit fails, a rollback is still attempted and the commit error propagates.
==== Driver API
[source,javascript]
----
const client = new Client('http://localhost:8182/gremlin', { traversalSource: 'g' });
const tx = client.transact();
await tx.begin();
await tx.submit("g.addV('person').property('name','alice')");
await tx.commit();
----
IMPORTANT: Each traversal must be `await`ed before submitting the next. Transactions are not thread-safe. The default
behavior of `close()` is to rollback. Transactions are single-use and cannot be reused after commit or rollback.
[source,javascript]
----
const g = traversal().with_(new DriverRemoteConnection('http://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-tiny-gremlin]]
=== Tiny Gremlin
<<tiny-gremlin-traversal, Tiny Gremlin>> is a subset of the Gremlin language that can execute locally and in-process
against a detached `Graph` held in memory. No Gremlin Server or remote connection is involved. A local traversal source
is created by passing a `Graph` instance to `with_()` in place of a `DriverRemoteConnection`:
[source,javascript]
----
const Graph = gremlin.structure.Graph;
const graph = new Graph();
const g = traversal().with_(graph);
----
The supported steps and the precise semantics of each, including the `by()` modulators available on `path()`, are
described in the link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin] provider
documentation. A step outside the subset raises a `LocalExecutionError`, which is exported from the `driver`
namespace.
==== Building a Graph
A `Graph` is populated with the mutation steps `addV()`, `addE()` and `property()`. Each traversal is terminated
with `iterate()` so that its mutation is applied to the in-memory graph:
[source,javascript]
----
const graph = new Graph();
const g = traversal().with_(graph);
await g.addV('person').property('name', 'marko').property('age', 29).iterate();
await g.addV('person').property('name', 'vadas').property('age', 27).iterate();
await g.addE('knows')
.from_(__.V().has('name', 'marko'))
.to(__.V().has('name', 'vadas'))
.property('weight', 0.5)
.iterate();
----
==== Querying a Graph
Once a `Graph` holds data, it is navigated and filtered with the supported read steps. Alongside the usual
terminal methods such as `toList()` and `next()`, Tiny Gremlin adds two terminals that consume results with a
native JavaScript function, `forEach()` and `reduce()`:
[source,javascript]
----
// list the names reachable from marko
const friends = await g.V().has('name', 'marko').out('knows').values('name').toList();
// iterate results with a side-effecting function
await g.V().hasLabel('person').values('name').forEach((name) => console.log(name));
// fold results into a single value with a seeded accumulator
const totalAge = await g.V().hasLabel('person').values('age').reduce((acc, age) => acc + age, 0);
// path() projects each element through by()
const paths = await g.V().has('name', 'marko').out().path().by('name').toList();
// repeat() loops a child traversal, here two hops out from marko
const reached = await g.V().has('name', 'marko').repeat(__.out()).times(2).values('name').toList();
----
==== Working within the subset
Because Tiny Gremlin is a subset, reshaping and reducing steps such as `project()`, `group()`, `groupCount()` and
`sum()` are not available. The intended pattern is to perform everything the subset supports within the traversal,
including sorting with `order().by()`, terminate with `toList()`, and then complete the work in JavaScript with
the standard array methods:
[source,javascript]
----
// project() is unavailable: order in the traversal, then map() the materialized result
const people = (await g.V().hasLabel('person').order().by('name').elementMap().toList())
.map((m) => ({ name: m.get('name'), age: m.get('age') }));
// groupCount() is unavailable: tally the labels with Array.reduce()
const counts = (await g.V().label().toList())
.reduce((acc, label) => { acc[label] = (acc[label] || 0) + 1; return acc; }, {});
----
==== Processing a subgraph
The primary use case for Tiny Gremlin is the local exploration of a subgraph extracted from a remote graph. The
`subgraph()`-step returns a detached `Graph` to the client, and passing that `Graph` to `with_()` makes its
contents available for in-process traversal without further calls to the server:
[source,javascript]
----
// pull a subgraph from a remote graph
const remote = traversal().with_(new DriverRemoteConnection('http://localhost:8182/gremlin'));
const sg = (await remote.E().hasLabel('created').subgraph('sg').cap('sg').next()).value;
// query the detached subgraph locally
const lg = traversal().with_(sg);
const creators = await lg.V().hasLabel('software').in_('created').values('name').toList();
----
[[gremlin-javascript-gvalue]]
=== GValue Parameterization
IMPORTANT: 4.0.0-beta.2 Release - `GValue` parameterization is not yet implemented for the JavaScript driver. This
functionality is planned for a future release.
[[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('http://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`,
`bulkResults`, `materializeProperties` and `evaluationTimeout`.
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.
==== Streaming Results
`client.submit()` buffers the entire response before returning a `ResultSet`. When working with larger result sets it
may be beneficial to process results incrementally as they are deserialized from the server response.
`client.stream()` returns an `AsyncGenerator` that yields individual result items as they arrive, without waiting for
the full response.
[source,javascript]
----
for await (const item of client.stream('g.V()', null)) {
console.log(item);
}
----
Breaking out of the loop aborts the underlying HTTP request, releasing the connection immediately:
[source,javascript]
----
for await (const item of client.stream('g.V()', null)) {
console.log(item);
if (someCondition) break; // stops reading from the HTTP stream
}
----
`client.stream()` accepts the same parameters as `client.submit()`: a script string, optional bindings, and optional
per-request settings.
[source,javascript]
----
try {
for await (const item of client.stream('g.V().has("age", gt(age))', { age: 30 }, { evaluationTimeout: 5000 })) {
console.log(item);
}
} catch (err) {
console.error(err); // ResponseError from the server
}
----
[[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-numeric-types]]
=== Numeric Types
JavaScript has a single `Number` type (IEEE 754 double) which cannot distinguish between Gremlin numeric types. The driver
provides typed wrapper classes and factory functions that give explicit control over serialization and deserialization.
Wrapping a value selects the GremlinLang type suffix and GraphBinary type code sent to the server. Without wrappers the
driver infers types automatically, so existing code is unaffected.
[source,javascript]
----
const { toLong, toInt, toFloat, toDouble, toShort, toByte } = gremlin.structure;
g.V().has('age', toInt(29)).next();
g.V().has('score', toFloat(3.14)).next();
g.V().has('id', toLong('9007199254740993')).next();
----
`toLong()` accepts `number`, `string`, or `bigint`. String and bigint inputs support the full signed 64-bit range;
number inputs must be within the safe integer range (`RangeError` is thrown otherwise).
By default, the driver deserializes all numeric values as plain `Number` (or `BigInt` for large longs). To preserve the
server's original type, set `preciseNumbers` to `true` on the connection:
[source,javascript]
----
const g = traversal().with_(new DriverRemoteConnection('http://localhost:8182/gremlin', {
preciseNumbers: true
}));
const v = await g.V(1).elementMap().next();
const age = v.value.get('age'); // Int { value: 29, type: 'int' }
age + 1; // 30 — wrappers support arithmetic via valueOf()
----
The `unwrap()` helper extracts the raw value from any wrapper, passing non-wrapper values through unchanged:
[source,javascript]
----
const { unwrap } = gremlin.structure;
unwrap(toInt(29)); // 29
unwrap('hello'); // 'hello'
----
[[gremlin-javascript-limitations]]
=== Limitations
* JavaScript's `Number` type is an IEEE 754 double-precision float. `Float`, `Byte`, and `Short` values from the server
are deserialized as `Number` and lose their original type information. Use `preciseNumbers: true` to preserve the
original types — see <<gremlin-javascript-numeric-types>>.
* `Long` values outside the safe integer range (|n| > 2^53 - 1) are deserialized as `BigInt` to preserve precision.
Values within the safe range are deserialized as `Number`. The same server type may produce different JavaScript types.
* `Number.isInteger(1.0)` is `true` in JavaScript, so the driver cannot distinguish integer values from whole-number
doubles. `BigDecimal` is not implemented. Typed wrappers (e.g. `toInt()`, `toDouble()`) can be used to control the
exact type sent to the server — see <<gremlin-javascript-numeric-types>>.
* The driver applies GremlinLang type suffixes automatically based on value characteristics: integers within the 32-bit
signed range are unsuffixed (Int), integers beyond that up to `Number.MAX_SAFE_INTEGER` use the `L` suffix (Long),
non-integer numbers and integers beyond the safe range use the `D` suffix (Double), and `BigInt` values use the `N`
suffix (BigInteger).
* The `subgraph()`-step returns a detached `Graph` data container exposing
`vertices: Map<any, Vertex>` and `edges: Map<any, Edge>`. The result is not a live `Graph` instance: mutating the
collections has no effect on the source graph, and it cannot be passed to `traversal().with(...)`. To re-query
subgraph elements against the original graph, extract their `id` and use `g.V(id)` / `g.E(id)` on the original
`GraphTraversalSource`.
[[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/gremlin-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 gremlin-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
----
[[gremlin-javascript-pdt]]
=== Provider Defined Types
Provider Defined Types (PDTs) allow graph providers to expose custom types through the driver. PDT values arrive as
`ProviderDefinedType` objects containing a `name` and `fields` map without any configuration.
Consult your graph provider's documentation for the list of PDTs they support.
Receiving a raw PDT:
[source,javascript]
----
const results = await g.V().has('location').values('location').toList();
const pdt = results[0];
console.log(pdt.name); // "x:Point"
console.log(pdt.fields); // { x: 1.0, y: 2.0 }
----
Working with raw `ProviderDefinedType` objects is always available. Using a `ProviderDefinedTypeRegistry` is an
optional convenience that automates conversion between PDT values and application types on both the request and
response paths.
[source,javascript]
----
const { ProviderDefinedTypeRegistry } = require('gremlin');
const registry = new ProviderDefinedTypeRegistry();
registry.register('x:Point', {
serialize: (point) => ({ x: point.x, y: point.y }),
deserialize: (fields) => new Point(fields.x, fields.y)
}, Point);
const g = traversal().with_(new DriverRemoteConnection('http://localhost:8182/gremlin', {
pdtRegistry: registry
}));
----
anchor:gremlin-DotNet[]
[[gremlin-dotnet]]
== Gremlin.Net
image:gremlin-dotnet-logo.png[width=371,float=right] Apache TinkerPop's Gremlin.Net implements Gremlin within the C#
language. It targets .NET 8.0 and can therefore be used on different operating systems. 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.
The Gremlin.Net driver communicates with Gremlin Server over HTTP using the GraphBinary 4.0 serialization format.
Response deserialization is streaming — results are yielded incrementally as they arrive from the server, rather than
buffered entirely before processing.
[source,powershell]
nuget install Gremlin.Net
The following table outlines recommended runtime versions by the release in which their support began:
[cols="1,2",options="header"]
|===
|Version |Target Frameworks
|3.4.0 |.NET Standard 1.3; .NET Standard 2.0
|3.5.0 |.NET Standard 2.0
|3.5.3 |.NET Standard 2.0; .NET 6.0
|3.6.0 |.NET Standard 2.0; .NET 6.0
|3.7.0 |.NET Standard 2.0; .NET6.0
|3.8.0 |.NET Standard 2.0; .NET6.0
|4.0.0 |.NET 8.0
|===
[[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]
----
Authentication is handled through request interceptors. Interceptors are functions that modify the outgoing HTTP
request before it is sent — they are used for authentication, custom headers, or request signing. The `Auth` class
provides `Basic()` and `SigV4()` as built-in interceptors:
[source,csharp]
----
// Basic authentication
var server = new GremlinServer("localhost", 8182, enableSsl: true);
using var client = new GremlinClient(server,
connectionSettings: new ConnectionSettings { SkipCertificateValidation = true },
interceptors: new[] { Auth.Basic("username", "password") });
// SigV4 authentication
var server = new GremlinServer("localhost", 8182, enableSsl: true);
using var client = new GremlinClient(server,
interceptors: new[] { 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://'.
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`, `bulkResults`, `userAgent`,
`materializeProperties`, and `evaluationTimeout`. These options are available as constants on the
`Gremlin.Net.Driver.Tokens` class.
NOTE: When submitting traversals through `DriverRemoteConnection`, `bulkResults` defaults to `true` per-request
to optimize result transfer. This does not apply to direct `GremlinClient.SubmitAsync()` calls, where `bulkResults`
must be set explicitly if desired.
[[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 `GremlinServer` class defines the server endpoint:
[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
|path |The path to the Gremlin endpoint on the server. |/gremlin
|=========================================================
==== Connection Settings
The HTTP connection can be configured through the `ConnectionSettings` class, which is passed to the `GremlinClient`
constructor:
[width="100%",cols="3,10,^2",options="header"]
|=========================================================
|Key |Description |Default
|ConnectTimeoutMillis |The TCP connect timeout in milliseconds (transport establishment, i.e. TCP connect plus TLS handshake where applicable, not an HTTP request timeout). Also settable as `ConnectTimeout` (a `TimeSpan`). |5000
|IdleTimeoutMillis |How long in milliseconds idle connections stay in the pool before being closed. Also settable as `IdleTimeout` (a `TimeSpan`). |180000
|MaxConnections |The maximum concurrent connections to a single server. |128
|KeepAliveTimeMillis |Idle time in milliseconds before TCP keep-alive probes begin on an otherwise idle connection. Enables `SO_KEEPALIVE` on the socket and sets the per-socket idle time on Windows, Linux, and macOS; on other platforms keep-alive stays enabled at the OS default idle time. Also settable as `KeepAliveTime` (a `TimeSpan`). |30000
|Compression |The response compression algorithm (`Compression.None` or `Compression.Deflate`). |`Compression.Deflate`
|BatchSize |The connection-level default batch size used to fill the per-request batch size when it is unset. |64
|Ssl |The `SslClientAuthenticationOptions` used for HTTPS connections (client certificates, custom CA, protocols, etc.). `SkipCertificateValidation` is applied to an internal copy of these options rather than mutating the object you provide. |`null`
|MaxResponseHeaderBytes |The maximum allowed size, in bytes, of the response headers. `0` leaves the handler default unchanged (converted internally to the handler's native kilobyte unit). |0
|ReadTimeoutMillis |The idle-read timeout in milliseconds applied to each individual read of the response stream. It resets per chunk. `0` (the default) disables it. Also settable as `ReadTimeout` (a `TimeSpan`; `Timeout.InfiniteTimeSpan` disables). |0
|Proxy |The `IWebProxy` used for connections. |`null`
|EnableUserAgentOnConnect |Enables sending a user agent to the server on 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
|BulkResults |Whether to send the bulkResults header on all requests. |false
|SkipCertificateValidation |Whether to skip SSL certificate validation. Only use for testing with self-signed certificates. When `Ssl` is also provided, the accept-all callback is set on an internal copy so the supplied options object is never mutated. |false
|=========================================================
Note that no driver timeout bounds the *total* duration of a request once it is under way. `ReadTimeout` only bounds
the gap between response chunks, so a response that keeps producing chunks will not time out no matter how long it
runs overall, and there is no client-side "overall" request timeout. If you need an absolute deadline, impose it in
your application by passing a cancellation token that cancels after the deadline:
[source,csharp]
----
// bound the entire request (submit plus full result iteration) to 30 seconds
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var results = await client.SubmitAsync<Vertex>(
"g.V().out().out()", cancellationToken: cts.Token);
----
==== GremlinClient Settings
The following options can be passed to the `GremlinClient` constructor:
[width="100%",cols="3,10,^2",options="header"]
|=========================================================
|Key |Description |Default
|messageSerializer |A single `IMessageSerializer` used for both request and response. |`GraphBinary4MessageSerializer`
|requestSerializer |The serializer for outgoing requests. When `null`, interceptors must serialize the body. |`GraphBinary4MessageSerializer`
|responseSerializer |The serializer for incoming responses. Always required. |`GraphBinary4MessageSerializer`
|connectionSettings |The `ConnectionSettings` for the HTTP connection. |default `ConnectionSettings`
|loggerFactory |An `ILoggerFactory` for logging. |`NullLoggerFactory`
|interceptors |A list of `Func<HttpRequestContext, Task>` that modify HTTP requests before sending. |_none_
|pdtRegistry |A `ProviderDefinedTypeRegistry` for hydrating and dehydrating <<gremlin-dotnet-pdt,Provider Defined Types>>. |`null`
|auth |A single `Func<HttpRequestContext, Task>` for authentication. Always appended to the end of the interceptor list so it runs last. |_none_
|=========================================================
[[gremlin-dotnet-interceptors]]
=== RequestInterceptor
Gremlin.Net allows modification of the underlying HTTP request through request interceptors. 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.Net is written in a way that you should be able to interact with most TinkerPop-enabled
servers without having to use interceptors. This is intended for cases where the server has special capabilities.
A request interceptor is a `Func<HttpRequestContext, Task>` that mutates the `HttpRequestContext` in place (it returns
`Task` for async support but does not produce a value). A list of these is maintained and will be run sequentially for
each request. When creating a `GremlinClient`, the `interceptors` parameter accepts an ordered collection of
interceptors. Order matters, so if one interceptor depends on another's output, ensure they are added in the correct
order. Note that authentication (e.g. `Auth.Basic()`, `Auth.Sigv4()`) is also implemented using interceptors.
These factory methods return interceptor delegates that can be included in the `interceptors` list. Alternatively, the
`auth` parameter on `GremlinClient` appends the auth interceptor to the end of the list so it runs last.
[source,csharp]
----
var server = new GremlinServer("localhost", 8182);
using var client = new GremlinClient(server,
interceptors: new Func<HttpRequestContext, Task>[]
{
Auth.Basic("username", "password"),
context =>
{
context.Headers["X-Custom-Header"] = "value";
return Task.CompletedTask;
}
});
----
Each interceptor receives an `HttpRequestContext` whose `Body` property initially contains a `RequestMessage`.
The `RequestMessage` has an immutable `Gremlin` property and a mutable `Fields` dictionary, so an interceptor can
add or modify fields directly. Alternatively, you can replace the body entirely. By default, the driver serializes
the body to JSON (`application/json`) by calling `HttpRequestContext.SerializeBody()` after all interceptors have
run. An interceptor that needs the serialized bytes (for example, to compute a signature hash) can call
`SerializeBody()` itself; the method is idempotent. If you require a GraphBinary-encoded request body instead of
JSON, you can write a custom interceptor that serializes the `RequestMessage` with the GraphBinary serializer and
sets the resulting `byte[]` as the body.
For an example of a simple request interceptor that only modifies the header of the request, see
link:https://github.com/apache/tinkerpop/blob/x.y.z/gremlin-dotnet/src/Gremlin.Net/Driver/Auth.cs[basic authentication].
[[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 GraphBinary 4.0 by default. A custom serializer can be provided when creating the
`GremlinClient`. The driver supports separate request and response serializers, or a single serializer for both:
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=serializationBinary]
----
[[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 GremlinLang and transmitted to the Gremlin traversal machine for re-construction machine-side.
[[gremlin-dotnet-transactions]]
=== Transactions
Gremlin.Net supports remote transactions over HTTP via two entry points: the Traversal API (`g.Tx()`) and the
Driver API (`client.Transact()`).
==== Traversal API
[source,csharp]
----
var g = AnonymousTraversalSource.Traversal().With(new DriverRemoteConnection("localhost", 8182));
var tx = g.Tx();
var gtx = await tx.BeginAsync();
await gtx.AddV("person").Property("name", "jorge").Promise(t => t.Iterate());
await gtx.AddV("person").Property("name", "josh").Promise(t => t.Iterate());
await tx.CommitAsync();
----
==== Managed Transaction Blocks
To avoid the manual begin/commit/rollback boilerplate, `g` provides closure methods that manage the
lifecycle. The callback receives the transaction-bound `gtx` and the transaction is committed when it completes or
rolled back if it throws. Use `ExecuteInTxAsync` when the body returns nothing and `EvaluateInTxAsync` when it returns
a value; both accept an optional `CancellationToken`:
[source,csharp]
----
var g = AnonymousTraversalSource.Traversal().With(new DriverRemoteConnection("localhost", 8182));
// ExecuteInTxAsync: no return value
await g.ExecuteInTxAsync(async gtx =>
{
await gtx.AddV("person").Property("name", "jorge").Promise(t => t.Iterate());
await gtx.AddV("person").Property("name", "josh").Promise(t => t.Iterate());
});
// EvaluateInTxAsync: returns the body's value
var count = await g.EvaluateInTxAsync(gtx => gtx.V().Count().Promise(t => t.Next()));
----
The transaction runs exactly once (no automatic retry). If the callback throws, the original exception is re-thrown
after the rollback; if the commit fails, a rollback is still attempted and the commit error propagates.
==== Driver API
[source,csharp]
----
using var client = new GremlinClient(new GremlinServer("localhost", 8182));
var tx = client.Transact("g");
await tx.BeginAsync();
await tx.SubmitAsync<object>("g.addV('person').property('name','alice')");
await tx.CommitAsync();
----
NOTE: Submissions within a transaction are serialized internally via a semaphore to guarantee the server receives
requests in order. Transactions are not thread-safe. The default behavior of `DisposeAsync()` is to rollback. Use
`await using` for automatic cleanup. Transactions are single-use and cannot be reused after commit or rollback.
[[gremlin-dotnet-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 C#, a GValue may be passed in
the traversal to utilize a parameter in place of a literal.
[source,csharp]
----
g.V().Has("name", new GValue<string>("name", "marko"));
----
[[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]
----
Authentication is handled through request interceptors. For basic authentication with SSL:
[source,csharp]
----
include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=submittingScriptsWithAuthentication]
----
==== Per Request Settings
A `RequestMessage` can be built with additional fields using the builder pattern. 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`, `bulkResults`, `userAgent`, `materializeProperties`
and `evaluationTimeout`. These options are available as constants on the `Gremlin.Net.Driver.Tokens` class.
==== Request Interceptors
The `GremlinClient` supports request interceptors that can modify the HTTP request before it is sent. Each interceptor
receives a mutable `HttpRequestContext` and can modify headers, body, URI, and method. Interceptors are executed in
order and are useful for authentication, custom headers, or request signing.
[source,csharp]
----
var client = new GremlinClient(new GremlinServer("localhost", 8182),
interceptors: new[] { Auth.Basic("username", "password") });
----
When `requestSerializer` is set to `null`, the request body is passed as a `RequestMessage` to interceptors, and an
interceptor is responsible for serializing it to `byte[]` and setting the `Content-Type` header. This follows the
Python driver's `request_serializer=None` pattern and is useful for custom serialization workflows.
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 returns a detached `Graph` data container exposing
`Vertices: IDictionary<object, Vertex>` and `Edges: IDictionary<object, Edge>`. The result is not a live `Graph`
instance: mutating the collections has no effect on the source graph, and it cannot be passed to
`traversal().with(...)`. To re-query subgraph elements against the original graph, extract their `Id` and use
`g.V(id)` / `g.E(id)` on the original `GraphTraversalSource`.
* `DateTimeOffset` cannot represent the extreme values of Gremlin's `OffsetDateTime` maximum and minimum,
so offset date-time values at those boundaries will fail to deserialize.
* Gremlin's `Duration` type has a much larger range than C#'s `TimeSpan`, so extreme duration values (such as
`Duration.FOREVER`) that exceed `TimeSpan.MaxValue` or `TimeSpan.MinValue` will fail to deserialize.
* Gremlin's `BigDecimal` supports up to 33 digits of precision while C#'s `decimal` type is limited to 28-29
significant digits, so high-precision values may lose accuracy or fail to deserialize.
* C# `Dictionary` does not allow `null` keys, so `Map` results with `null` keys (e.g. from `group()` or
`groupCount()` on a missing property) will fail during deserialization.
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/gremlin-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 gremlin-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-dotnet-pdt]]
=== Provider Defined Types
Provider Defined Types (PDTs) allow graph providers to expose custom types through the driver. PDT values arrive as
`ProviderDefinedType` objects containing a `Name` and `Fields` dictionary without any configuration.
Consult your graph provider's documentation for the list of PDTs they support.
Receiving a raw PDT:
[source,csharp]
----
var pdt = (ProviderDefinedType) g.V().Has("location").Values<object>("location").Next();
Console.WriteLine(pdt.Name); // "x:Point"
Console.WriteLine(pdt.Fields); // { x: 1.0, y: 2.0 }
----
Working with raw `ProviderDefinedType` objects is always available. The following two approaches are optional
conveniences that automate conversion between PDT values and application types on both the request and response paths.
Using a `ProviderDefinedTypeRegistry` for hydration and dehydration:
[source,csharp]
----
public class PointAdapter : IProviderDefinedTypeAdapter<Point>
{
public string TypeName => "x:Point";
public Point FromFields(IReadOnlyDictionary<string, object?> fields) =>
new Point((double)fields["x"], (double)fields["y"]);
public IReadOnlyDictionary<string, object?> ToFields(Point value) =>
new Dictionary<string, object?> { ["x"] = value.X, ["y"] = value.Y };
}
var registry = new ProviderDefinedTypeRegistry();
registry.Register(new PointAdapter());
using var client = new GremlinClient(new GremlinServer("localhost", 8182), pdtRegistry: registry);
----
The `ProviderDefinedTypeRegistry.Create()` method scans loaded assemblies for `IProviderDefinedTypeAdapter<T>`
implementations and registers them automatically.
For simpler cases where you own the type, annotate it directly to avoid writing an adapter:
Attribute-based conversion with `[ProviderDefined]`:
[source,csharp]
----
// IncludedFields: only serialize the listed fields
[ProviderDefined(Name = "x:Point", IncludedFields = new[] { "X", "Y" })]
public class Point
{
public double X { get; set; }
public double Y { get; set; }
public string InternalId { get; set; } // not serialized
}
// ExcludedFields: serialize all fields except those listed
[ProviderDefined(Name = "x:Timestamped", ExcludedFields = new[] { "CreatedAt" })]
public class Timestamped
{
public string Value { get; set; }
public long CreatedAt { get; set; } // not serialized
}
// send: Point is automatically dehydrated
await g.Inject(new Point { X = 1.0, Y = 2.0 }).Promise(t => t.Iterate());
// receive: PDT is automatically hydrated back to Point
var p = (Point) await g.V().Has("location").Values<object>("location").Promise(t => t.Next());
----
Classes annotated with `[ProviderDefined]` are automatically dehydrated when passed as traversal arguments and
hydrated on deserialization without explicit registry configuration.
[[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
----
The following table outlines recommended runtime versions by the release in which their support began:
[cols="1,1,1",options="header"]
|===
|Version |Recommended Python |Key Dependencies
|3.4.0 |2.7 |tornado
|3.5.0 |3.0 |aiohttp
|3.6.0 |3.8 |aiohttp
|3.6.8 |3.9 |aiohttp
|3.7.0 |3.8-3.13 |aiohttp
|3.7.3 |3.9-3.13 |aiohttp
|3.8.0 |3.10-3.13 |aiohttp
|===
[[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 on each HTTP request, set them via a request interceptor rather than a
connection parameter. Pass a callable to the `interceptors` keyword argument that mutates the request headers in
place. See <<gremlin-python-interceptors, RequestInterceptor>> for details.
[source,python]
----
g = traversal().with_(DriverRemoteConnection(
'http://localhost:8182/gremlin', 'g',
interceptors=[lambda req: req.headers.update({'Header': 'Value'})]))
----
Gremlin-Python contains an `auth` module that provides built-in authentication functions. These are passed to the
`auth` parameter of `DriverRemoteConnection` and add the appropriate HTTP headers to each request. Plain text and
SigV4 authentication are provided as built-in options.
[source,python]
----
# Plain text authentication
g = traversal().with_(DriverRemoteConnection(
'http://localhost:8182/gremlin', 'g', auth=basic('stephen', 'password')))
# SigV4 authentication
g = traversal().with_(DriverRemoteConnection(
'http://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 `max_connections` 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`.
NOTE: When submitting traversals through `DriverRemoteConnection`, `bulkResults` defaults to `True` per-request
to optimize result transfer. This does not apply to direct `Client.submit()` calls, where `bulkResults` must be
set explicitly if desired.
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
|max_workers |Maximum number of worker threads. |Same as `max_connections` (128)
|response_serializer |The response serializer implementation.|`gremlin_python.driver.serializer.GraphBinarySerializersV4`
|interceptors |The request interceptors to run before the request body is serialized.|`None`
|auth |An authentication interceptor. Always appended to the end of the interceptor list so it runs last. |`None`
|max_connections |The maximum number of connections used by the pool. |128
|connect_timeout_millis |Timeout in milliseconds for establishing the connection (TCP connect plus TLS handshake). Also settable as `connect_timeout` (in seconds). |5000
|read_timeout_millis |Per-read idle timeout in milliseconds applied while streaming a response. Resets per chunk. Also settable as `read_timeout` (in seconds). |`None`
|write_timeout |Timeout in seconds for writing a request to the transport. |`None`
|ssl |An `ssl.SSLContext` used for TLS connections. |`None`
|idle_timeout_millis |How long in milliseconds an idle connection remains in the pool before being closed. Also settable as `idle_timeout` (in seconds). |180000
|keep_alive_time_millis |TCP keep-alive idle time in milliseconds before probes begin on an otherwise idle connection. Also settable as `keep_alive_time` (in seconds). |30000
|compression |The wire compression negotiated with the server (`'none'` or `'deflate'`). |`'deflate'`
|batch_size |The connection-level default batch size used when a request does not specify one. |64
|proxy |HTTP proxy URL routed through the underlying `ClientSession`. |`None`
|trust_env |Whether to trust environment variables for proxy and SSL configuration. |False
|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
|pdt_registry |A `ProviderDefinedTypeRegistry` for hydrating and dehydrating <<gremlin-python-pdt,Provider Defined Types>>. |`None`
|=========================================================
Transport options such as SSL and timeouts can be passed as keyword arguments to `Client` or
`DriverRemoteConnection`. These are forwarded to the underlying `AiohttpHTTPTransport`:
[source,python]
----
import ssl
...
g = traversal().with_(
DriverRemoteConnection('https://localhost:8182/gremlin','g',
ssl=ssl.create_default_context(),
read_timeout=30))
----
Note that no driver timeout bounds the *total* duration of a request once it is under way. `read_timeout` only bounds
the gap between response chunks, so a response that keeps producing chunks will not time out no matter how long it
runs overall, and there is no client-side "overall" request timeout. If you need an absolute deadline, impose it in
your application. `submit_async()` and `ResultSet.all()` return `concurrent.futures.Future` objects, so pass a
`timeout` to `result()`:
[source,python]
----
from concurrent.futures import TimeoutError
# bound the entire request (submit plus full result iteration) to 30 seconds
try:
result_set = client.submit_async("g.V().out().out()").result(timeout=30)
results = result_set.all().result(timeout=30)
except TimeoutError:
# deadline exceeded; stop waiting on the request
...
----
[[gremlin-python-interceptors]]
=== RequestInterceptor
Gremlin-Python allows modification of the underlying HTTP request through request interceptors. 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-Python is written in a way that you should be able to interact with most TinkerPop-enabled
servers without having to use interceptors. This is intended for cases where the server has special capabilities.
A request interceptor is a callable that receives an `HttpRequest` and mutates it in place (it returns nothing).
A list of these is maintained and will be run sequentially for each request. When creating a
`DriverRemoteConnection` or `Client`, the `interceptors` keyword argument accepts an ordered list of interceptors.
Order matters, so if one interceptor depends on another's output, ensure they are added in the correct order. Note
that authentication (e.g. `basic()`, `sigv4()`) is also implemented using interceptors. The `auth` convenience
parameter on `DriverRemoteConnection` and `Client` appends the auth interceptor to the end of the list so it
runs last.
[source,python]
----
from gremlin_python.driver.auth import basic
g = traversal().with_(DriverRemoteConnection(
'http://localhost:8182/gremlin', 'g',
auth=basic('username', 'password'),
interceptors=[
lambda req: req.headers.update({'X-Custom-Header': 'value'})
]))
----
Each interceptor receives an `HttpRequest` whose `body` attribute initially contains a `RequestMessage`.
The `RequestMessage` is a named tuple with mutable `fields` (a `dict`) and a `gremlin` string. Because
`fields` is a regular dictionary, an interceptor can add or modify entries directly. To replace the body
entirely, assign a new value to `req.body`. By default, the driver serializes the body to JSON
(`application/json`) by calling `HttpRequest.serialize_body()` after all interceptors have run. An interceptor
that needs the serialized bytes (for example, to compute a signature hash) can call `serialize_body()` itself;
the method is idempotent. If you require a GraphBinary-encoded request body instead of JSON, you can write a
custom interceptor that serializes the `RequestMessage` with the GraphBinary serializer and sets the resulting
`bytes` as the body.
For an example of a simple request interceptor that only modifies the header of the request, see
link:https://github.com/apache/tinkerpop/blob/x.y.z/gremlin-python/src/main/python/gremlin_python/driver/auth.py[basic authentication].
[[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
Gremlin-Python supports remote transactions over HTTP via two entry points: the Traversal API (`g.tx()`) and the
Driver API (`client.transact()`).
==== Traversal API
[source,python]
----
g = traversal().with_remote(DriverRemoteConnection('http://localhost:8182/gremlin', 'g'))
tx = g.tx()
gtx = tx.begin()
gtx.addV('person').property('name', 'jorge').iterate()
gtx.addV('person').property('name', 'josh').iterate()
tx.commit()
----
==== Managed Transaction Blocks
To avoid the manual begin/commit/rollback boilerplate, `g` provides an `execute_in_tx` method that manages the
lifecycle. The function receives the transaction-bound `gtx` and the transaction is committed when it returns or rolled
back if it raises. `execute_in_tx` returns whatever the function returns:
[source,python]
----
g = traversal().with_remote(DriverRemoteConnection('http://localhost:8182/gremlin', 'g'))
# no return value
g.execute_in_tx(lambda gtx: gtx.addV('person').property('name', 'jorge').iterate())
# returns the function's value
count = g.execute_in_tx(lambda gtx: gtx.V().count().next())
----
The transaction runs exactly once (no automatic retry). If the function raises, the original exception is re-raised
after the rollback; if the commit fails, a rollback is still attempted and the commit error propagates.
==== Driver API
[source,python]
----
client = Client('http://localhost:8182/gremlin', 'g')
tx = client.transact()
tx.begin()
tx.submit("g.addV('person').property('name','alice')")
tx.commit()
----
==== Context Manager
[source,python]
----
with client.transact() as tx:
tx.begin()
tx.submit("g.addV('person').property('name','alice')")
tx.commit()
# if commit() not called or exception occurs, rollback happens on exit
----
NOTE: Transactions are not thread-safe. All operations must be sequential. The default behavior of `close()` is to
rollback. Transactions are single-use and cannot be reused after commit or 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`, `bulkResults`, `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('http://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` may be coerced to a `List` in Python in two cases. First, when the `Set` contains
mixed numeric types (e.g. `int` and `float`), because Python number equality differs from the JVM — a Java `Set` of
`[1, 1.0d]` has two elements, but Python considers `1 == 1.0` and would collapse them to one, so the `Set` is coerced to
a `List` to preserve all elements consistently across languages. Second, when the `Set` contains non-hashable items such
as `Dictionary`, `Set`, or `List`, because Python requires set elements to be hashable while Gremlin does not, the `Set`
is also coerced to a `List`. For this case, if a `Set` is needed, convert elements to hashable equivalents manually
(e.g. `dict` to `HashableDict`, `list` to `tuple`, `set` to `frozenset`).
* 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 returns a detached `Graph` data container exposing `vertices: dict` and `edges: dict`
keyed by element id. The result is not a live `Graph` instance: mutating the dicts has no effect on the source
graph, and it cannot be passed to `traversal().with(...)`. To re-query subgraph elements against the original
graph, extract their `id` and use `g.V(id)` / `g.E(id)` on the original `GraphTraversalSource`.
* 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("http://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/gremlin-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 gremlin-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
----
[[gremlin-python-pdt]]
=== Provider Defined Types
Provider Defined Types (PDTs) allow graph providers to expose custom types through the driver. PDT values arrive as
`ProviderDefinedType` objects containing a `name` and `fields` dict without any configuration.
Consult your graph provider's documentation for the list of PDTs they support.
Receiving a raw PDT:
[source,python]
----
pdt = g.V().has('location').values('location').next()
print(pdt.name) # "x:Point"
print(pdt.fields) # {'x': 1.0, 'y': 2.0}
----
Working with raw `ProviderDefinedType` objects is always available. The following two approaches are optional
conveniences that automate conversion between PDT values and application types on both the request and response paths.
Using a `ProviderDefinedTypeRegistry` for hydration and dehydration:
[source,python]
----
from gremlin_python.structure.graph import ProviderDefinedTypeRegistry
registry = ProviderDefinedTypeRegistry()
registry.register('x:Point',
deserialize_fn=lambda fields: Point(fields['x'], fields['y']),
serialize_fn=lambda p: {'x': p.x, 'y': p.y},
target_class=Point)
g = traversal().with_(DriverRemoteConnection('http://localhost:8182/gremlin', 'g',
pdt_registry=registry))
----
The `ProviderDefinedTypeRegistry.create()` class method discovers adapters via `entry_points` in `pyproject.toml`
under the `tinkerpop.pdt` group.
For simpler cases where you own the type, the `@provider_defined` decorator enables automatic round-trip conversion
without an explicit registry:
Decorator-based conversion with `@provider_defined`:
[source,python]
----
from gremlin_python.structure.graph import provider_defined
# included_fields: only serialize the listed fields
@provider_defined(name='x:Point', included_fields=['x', 'y'])
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
self._internal_id = None # not serialized
# excluded_fields: serialize all instance fields except those listed
@provider_defined(name='x:Timestamped', excluded_fields=['created_at'])
class Timestamped:
def __init__(self, value, created_at):
self.value = value
self.created_at = created_at # not serialized
# send: Point is automatically dehydrated
g.inject(Point(1.0, 2.0)).iterate()
# receive: PDT is automatically hydrated back to Point
p = g.V().has('location').values('location').next()
# type(p) is Point
----
Objects decorated with `@provider_defined` are automatically dehydrated when passed as traversal arguments and
hydrated back into the same type when received in responses.