| //// |
| 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.TlsConfig = &tls.Config{InsecureSkipVerify: true} |
| settings.RequestInterceptors = []gremlingo.RequestInterceptor{ |
| gremlingo.BasicAuth("username", "password"), |
| } |
| }) |
| |
| // SigV4 authentication |
| remote, err := gremlingo.NewDriverRemoteConnection("https://localhost:8182/gremlin", |
| func(settings *gremlingo.DriverRemoteConnectionSettings) { |
| settings.RequestInterceptors = []gremlingo.RequestInterceptor{ |
| gremlingo.SigV4Auth("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. This does not apply to direct `Client.Submit()` calls, where `bulkResults` must be |
| set explicitly if desired. |
| |
| 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 |
| |TlsConfig |TLS configuration. |empty |
| |ConnectionTimeout | Timeout for establishing connection. |15 seconds |
| |MaximumConcurrentConnections | Maximum number of concurrent TCP connections to the server. |128 |
| |MaxIdleConnections | Maximum number of idle (keep-alive) connections in the pool. |8 |
| |IdleConnectionTimeout | How long idle connections remain in the pool before being closed. |180 seconds |
| |KeepAliveInterval | TCP keep-alive probe interval. |30 seconds |
| |EnableCompression |Flag to enable compression. |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 |
| |RequestInterceptors |Functions that modify HTTP requests before sending. Used for authentication and custom headers. |empty |
| |========================================================= |
| |
| [[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 |
| |
| IMPORTANT: Transaction support over HTTP is not yet implemented in Gremlin-Go. This will be addressed by the official |
| 4.0.0 release. |
| |
| [[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. |
| |
| [[gremlin-go-aliases]] |
| === Aliases |
| To make the code more readable and close to the Gremlin query language), you can use aliases. These aliases can be named with capital letters to be consistent with non-aliased steps but will result in exported variables which could be problematic if not being used in a top-level program (i.e. not a redistributable package). |
| [source,go] |
| ---- |
| var __ = gremlingo.T__ |
| var gt = gremlingo.P.Gt |
| var order = gremlingo.Order |
| |
| results, err := g.V().HasLabel("person").Has("age", __.Is(gt(30))).Order().By("age", order.Desc).ToList() |
| ---- |
| |
| ==== List of useful aliases |
| [source,go] |
| ---- |
| // common |
| var __ = gremlingo.T__ |
| var TextP = gremlingo.TextP |
| |
| // predicates |
| var between = gremlingo.P.Between |
| var eq = gremlingo.P.Eq |
| var gt = gremlingo.P.Gt |
| var gte = gremlingo.P.Gte |
| var inside = gremlingo.P.Inside |
| var lt = gremlingo.P.Lt |
| var lte = gremlingo.P.Lte |
| var neq = gremlingo.P.Neq |
| var not = gremlingo.P.Not |
| var outside = gremlingo.P.Outside |
| var test = gremlingo.P.Test |
| var within = gremlingo.P.Within |
| var without = gremlingo.P.Without |
| var and = gremlingo.P.And |
| var or = gremlingo.P.Or |
| |
| // sorting |
| var order = gremlingo.Order |
| ---- |
| |
| Finally, the enum construct for `Cardinality` cannot have functions attached to it the way it can be done in Java, |
| therefore cardinality functions that take a value like `list()`, `set()`, and `single()` are referenced from a |
| `CardinalityValue` class rather than `Cardinality` itself. |
| |
| [[gremlin-go-limitations]] |
| === Limitations |
| |
| * There is no default `set` type in Go. Any set type code from server will be deserialized into slices with the list |
| type implementation. To input a set into Gremlin-Go, a custom struct which implements the `gremlingo.Set` interface |
| will be serialized as a set. `gremlingo.NewSimpleSet` is a basic implementation of a set that is provided by Gremlin-Go |
| that can be used to fulfill the `gremlingo.Set` interface if desired. |
| |
| * Go does not support ordered maps natively as the built-in `map` type does not guarantee iteration order. Traversal |
| results which contain maps may not preserve original ordering when deserialized into Go's native map types. |
| |
| [[gremlin-go-examples]] |
| === Application Examples |
| |
| The TinkerPop source code contains some sample applications that demonstrate the basics of Gremlin-Go. They |
| can be found in GitHub link:https://github.com/apache/tinkerpop/tree/x.y.z/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-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.connectionSetupTimeoutMillis | Duration of time in milliseconds provided for connection setup to complete which includes the SSL handshake. |15000 |
| |connectionPool.enableSsl |Determines if SSL should be enabled or not. If enabled on the server then it must be enabled on the client. |false |
| |connectionPool.idleConnectionTimeout | Duration of time in milliseconds that the driver will allow a channel to not receive read or writes before it automatically closes. |180000 |
| |connectionPool.keyStore |The private key in JKS or PKCS#12 format. |_none_ |
| |connectionPool.keyStorePassword |The password of the `keyStore` if it is password-protected. |_none_ |
| |connectionPool.keyStoreType |`PKCS12` |_none_ |
| |connectionPool.maxResponseContentLength |The maximum length in bytes that a message can be received from the server. |2147483647 |
| |connectionPool.maxSize |The maximum size of a connection pool for a host. |128 |
| |connectionPool.maxWaitForConnection |The amount of time in milliseconds to wait for a new connection before timing out. |3000 |
| |connectionPool.maxWaitForClose |The amount of time in milliseconds to wait for pending messages to be returned from the server before closing the connection. |3000 |
| |connectionPool.reconnectInterval |The amount of time in milliseconds to wait before trying to reconnect to a dead host. |1000 |
| |connectionPool.resultIterationBatchSize |The override value for the size of the result batches to be returned from the server. |64 |
| |connectionPool.sslCipherSuites |The list of JSSE ciphers to support for SSL connections. If specified, only the ciphers that are listed and supported will be enabled. If not specified, the JVM default is used. |_none_ |
| |connectionPool.sslEnabledProtocols |The list of SSL protocols to support for SSL connections. If specified, only the protocols that are listed and supported will be enabled. If not specified, the JVM default is used. |_none_ |
| |connectionPool.sslSkipCertValidation |Configures the `TrustManager` to trust all certs without any validation. Should not be used in production.|false |
| |connectionPool.trustStore |File location for a SSL Certificate Chain to use when SSL is enabled. If this value is not provided and SSL is enabled, the default `TrustManager` will be used. |_none_ |
| |connectionPool.trustStorePassword |The password of the `trustStore` if it is password-protected |_none_ |
| |connectionPool.validationRequest |A script that is used to test server connectivity. A good script to use is one that evaluates quickly and returns no data. The default simply returns an empty string, but if a graph is required by a particular provider, a good traversal might be `g.inject()`. |_''_ |
| |bulkResults |Sets whether the server should attempt to get bulk results or not. |false |
| |enableUserAgentOnConnect |Enables sending a user agent to the server during connection requests. More details can be found in provider docs link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#_graph_driver_provider_requirements[here].|true |
| |hosts |The list of hosts that the driver will connect to. |localhost |
| |nioPoolSize |Size of the pool for handling request/response operations. |available processors |
| |password |The password to submit on requests that require authentication. |_none_ |
| |path |The URL path to the Gremlin Server. |_/gremlin_ |
| |port |The port of the Gremlin Server to connect to. The same port will be applied for all hosts. |8192 |
| |serializer.className |The fully qualified class name of the `MessageSerializer` that will be used to deserialize responses from the server. Note that the serializer configured on the client should be supported by the server configuration. |_none_ |
| |serializer.config |A `Map` of configuration settings for the serializer. |_none_ |
| |username |The username to submit on requests that require authentication. |_none_ |
| |workerPoolSize |Size of the pool for handling background work. |available processors * 2 |
| |========================================================= |
| |
| Please see the link:https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/driver/Cluster.Builder.html[Cluster.Builder javadoc] to get more information on these settings. |
| |
| [[gremlin-java-transactions]] |
| === Transactions |
| |
| Gremlin-Java supports remote transactions over HTTP. There are two ways to create a transaction: from a |
| `GraphTraversalSource` or directly from a `Cluster` instance. |
| |
| ==== Via GraphTraversalSource |
| |
| 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. |
| |
| ==== Via Cluster |
| |
| 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 commit. This can be changed: |
| |
| [source,java] |
| ---- |
| RemoteTransaction tx = cluster.transact("g"); |
| tx.onClose(Transaction.CLOSE_BEHAVIOR.ROLLBACK); |
| tx.begin(); |
| // ... do work ... |
| tx.close(); // rolls back instead of committing |
| ---- |
| |
| [[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 a TinkerPop-enabled |
| server without having to use interceptors. This is intended for cases where the server has special capabilities. |
| |
| A `RequestInterceptor` is simply a `UnaryOperator`. A list of these are maintained and will be run sequentially for |
| each request. When building a `Cluster` instance, the methods `addInterceptorAfter()`, `addInterceptorBefore()`, |
| `addInterceptor()`, and `removeInterceptor()` can be used to add or remove interceptors. It's important to remember |
| that order matters so if one interceptor depends on another's output then ensure they are added in the correct order. |
| Note that `Auth` is also implemented using interceptors, and `Auth` is always run last after your list of interceptors |
| has already ran. By default, the `PayloadSerializingInterceptor` with the name `serializer` is added to your list of |
| interceptors. This interceptor is used for serializing the body of the request. The first interceptor is provided with |
| a `org.apache.tinkerpop.gremlin.driver.HttpRequest` that contains a `RequestMessage` in the body. As a reminder |
| `RequestMessage` is immutable and only certain keys can be added to them. If you want to customize the body by adding |
| other fields, you will need to make a different copy of the `RequestMessage` or completely change the body to contain a |
| different data type. The very last interceptor should have a `org.apache.tinkerpop.gremlin.driver.HttpRequest` that |
| contains a byte[] in the body. |
| |
| For an example of a simple `RequestInterceptor` that only modifies the header of the request take a look at |
| link:https://github.com/apache/tinkerpop/blob/x.y.z/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/auth/Basic.java[basic authentication]. |
| |
| [[gremlin-java-dsl]] |
| === Domain Specific Languages |
| |
| Creating a <<dsl,Domain Specific Language>> (DSL) in Java requires the `@GremlinDsl` Java annotation in the |
| `gremlin-annotations` module. This annotation should be applied to a "DSL interface" that extends |
| `GraphTraversal.Admin`: |
| |
| [source,xml] |
| ---- |
| <dependency> |
| <groupId>org.apache.tinkerpop</groupId> |
| <artifactId>gremlin-annotations</artifactId> |
| <version>x.y.z</version> |
| </dependency> |
| ---- |
| |
| [source,java] |
| ---- |
| @GremlinDsl |
| public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> { |
| } |
| ---- |
| |
| IMPORTANT: The name of the DSL interface should be suffixed with "TraversalDSL". All characters in the interface name |
| before that become the "name" of the DSL. |
| |
| In this interface, define the methods that the DSL will be composed of: |
| |
| [source,java] |
| ---- |
| @GremlinDsl |
| public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> { |
| public default GraphTraversal<S, Vertex> knows(String personName) { |
| return out("knows").hasLabel("person").has("name", personName); |
| } |
| |
| public default <E2 extends Number> GraphTraversal<S, E2> youngestFriendsAge() { |
| return out("knows").hasLabel("person").values("age").min(); |
| } |
| |
| public default GraphTraversal<S, Long> createdAtLeast(int number) { |
| return outE("created").count().is(P.gte(number)); |
| } |
| } |
| ---- |
| |
| IMPORTANT: Follow the TinkerPop convention of using `<S,E>` in naming generics as those conventions are taken into |
| account when generating the anonymous traversal class. The processor attempts to infer the appropriate type parameters |
| when generating the anonymous traversal class. If it cannot do it correctly, it is possible to avoid the inference by |
| using the `GremlinDsl.AnonymousMethod` annotation on the DSL method. It allows explicit specification of the types to |
| use. |
| |
| The `@GremlinDsl` annotation is used by the link:https://docs.oracle.com/javase/8/docs/api/index.html?javax/annotation/processing/Processor.html[Java Annotation Processor] |
| to generate the boilerplate class structure required to properly use the DSL within the TinkerPop framework. These |
| classes can be generated and maintained by hand, but it would be time consuming, monotonous and error-prone to do so. |
| Typically, the Java compilation process is automatically configured to detect annotation processors on the classpath |
| and will automatically use them when found. If that does not happen, it may be necessary to make configuration changes |
| to the build to allow for the compilation process to be aware of the following `javax.annotation.processing.Processor` |
| implementation: |
| |
| [source,java] |
| ---- |
| org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDslProcessor |
| ---- |
| |
| The annotation processor will generate several classes for the DSL: |
| |
| * `SocialTraversal` - A `Traversal` interface that extends the `SocialTraversalDsl` proxying methods to its underlying |
| interfaces (such as `GraphTraversal`) to instead return a `SocialTraversal` |
| * `DefaultSocialTraversal` - A default implementation of `SocialTraversal` (typically not used directly by the user) |
| * `SocialTraversalSource` - Spawns `DefaultSocialTraversal` instances. |
| * `__` - Spawns anonymous `DefaultSocialTraversal` instances. |
| |
| Using the DSL then just involves telling the `Graph` to use it: |
| |
| [source,java] |
| ---- |
| SocialTraversalSource social = traversal(SocialTraversalSource.class).with(graph); |
| social.V().has("name","marko").knows("josh"); |
| ---- |
| |
| The `SocialTraversalSource` can also be customized with DSL functions. As an additional step, include a class that |
| extends from `GraphTraversalSource` and with a name that is suffixed with "TraversalSourceDsl". Include in this class, |
| any custom methods required by the DSL: |
| |
| [source,java] |
| ---- |
| public class SocialTraversalSourceDsl extends GraphTraversalSource { |
| |
| public SocialTraversalSourceDsl(Graph graph, TraversalStrategies traversalStrategies) { |
| super(graph, traversalStrategies); |
| } |
| |
| public SocialTraversalSourceDsl(Graph graph) { |
| super(graph); |
| } |
| |
| public SocialTraversalSourceDsl(RemoteConnection connection) { |
| super(connection); |
| } |
| |
| public GraphTraversal<Vertex, Vertex> persons(String... names) { |
| GraphTraversalSource clone = this.clone(); |
| |
| // Manually add a "start" step for the traversal in this case the equivalent of V(). GraphStep is marked |
| // as a "start" step by passing "true" in the constructor. |
| clone.getBytecode().addStep(GraphTraversal.Symbols.V); |
| GraphTraversal<Vertex, Vertex> traversal = new DefaultGraphTraversal<>(clone); |
| traversal.asAdmin().addStep(new GraphStep<>(traversal.asAdmin(), Vertex.class, true)); |
| |
| traversal = traversal.hasLabel("person"); |
| if (names.length > 0) traversal = traversal.has("name", P.within(names)); |
| |
| return traversal; |
| } |
| } |
| ---- |
| |
| Then, back in the `SocialTraversal` interface, update the `GremlinDsl` annotation with the `traversalSource` argument |
| to point to the fully qualified class name of the `SocialTraversalSourceDsl`: |
| |
| [source,java] |
| ---- |
| @GremlinDsl(traversalSource = "com.company.SocialTraversalSourceDsl") |
| public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> { |
| ... |
| } |
| ---- |
| |
| It is then possible to use the `persons()` method to start traversals: |
| |
| [source,java] |
| ---- |
| SocialTraversalSource social = traversal(SocialTraversalSource.class).with(graph); |
| social.persons("marko").knows("josh"); |
| ---- |
| |
| NOTE: Using Maven makes developing DSLs with the annotation processor straightforward in that it sets up appropriate |
| paths to the generated code automatically. |
| |
| [[gremlin-java-troubleshooting]] |
| === Troubleshooting |
| |
| *Response exceeded X bytes.* |
| |
| This error occurs when the driver attempts to process a response that exceeds the configured maximum response size. |
| The most direct way to fix this problem is to increase the `maxResponseContentLength` setting in the driver. |
| |
| *TimeoutException* |
| |
| A `TimeoutException` is thrown by the driver when the time limit assigned by the `maxWaitForConnection` is exceeded |
| when trying to borrow a connection from the connection pool for a particular host. There are generally two scenarios |
| where this occurs: |
| |
| 1. The server has actually reached its maximum capacity or the driver has just learned that the server is unreachable. |
| 2. The client is throttling requests when the pool is exhausted. |
| |
| The latter of the two can be addressed from the driver side in the following ways: |
| |
| * Increase the `maxWaitForConnection` allowing the client to wait a bit longer for a connection to become available. |
| * Increase the number of requests allowed per connection by increasing the `maxSimultaneousUsagePerConnection` and |
| `maxInProcessPerConnection` settings. |
| * Increase the number of connections available in the connection pool by increasing the `maxConnectionPoolSize`. |
| |
| The exception and logs (assuming they are enabled) should contain information about the state of the connection pool |
| along with its connections which can help shed more light on which of these scenarios caused the problem. Some examples |
| of these messages and their meaning are shown below: |
| |
| _The server is unavailable_ |
| |
| [source,text] |
| ---- |
| Timed-out (500 MILLISECONDS) waiting for connection on Host{address=localhost/127.0.0.1:45940, hostUri=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-javascript]] |
| == Gremlin-JavaScript |
| |
| image:gremlin-js.png[width=130,float=right] Apache TinkerPop's Gremlin-JavaScript implements Gremlin within the |
| JavaScript language. It targets Node.js runtime and can be used on different operating systems on any Node.js 22 or |
| above. Since the JavaScript naming conventions are very similar to that of Java, it should be very easy to switch |
| between Gremlin-Java and Gremlin-JavaScript. |
| |
| [source,bash] |
| npm install gremlin |
| |
| The following table outlines recommended runtime versions by the release in which their support began: |
| |
| [cols="1,1,1",options="header"] |
| |=== |
| |Version |Min Node.js |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 |10.8.2 |
| |3.8.0 |≥20 |10.8.2 |
| |4.0.0-beta.2 |≥22 |10.8.2 |
| |=== |
| |
| [[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.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.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 |
| |========================================================= |
| |
| [[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. |
| |
| IMPORTANT: 4.0.0-beta.2 Release - Transactions are not yet implemented for the HTTP driver. This functionality is |
| planned for a future release. |
| |
| [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-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-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. |
| * `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. |
| * 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 is not supported by any variant that is not running on the Java Virtual Machine as there is |
| no `Graph` instance to deserialize a result into on the client-side. A workaround is to replace the step with |
| `aggregate(local)` and then convert those results to something the client can use locally. |
| |
| [[gremlin-javascript-examples]] |
| === Application Examples |
| |
| The TinkerPop source code contains some sample applications that demonstrate the basics of Gremlin-JavaScript. They |
| can be found in GitHub link:https://github.com/apache/tinkerpop/tree/x.y.z/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 |
| ---- |
| |
| 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 `BasicAuth()` and `SigV4Auth()` 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.BasicAuth("username", "password") }); |
| |
| // SigV4 authentication |
| var server = new GremlinServer("localhost", 8182, enableSsl: true); |
| using var client = new GremlinClient(server, |
| interceptors: new[] { Auth.SigV4Auth("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 |
| |ConnectionTimeout |The TCP connection timeout. |15 s |
| |IdleConnectionTimeout |How long idle connections stay in the pool before being closed. |180 s |
| |MaxConnectionsPerServer |The maximum concurrent connections to a single server. |128 |
| |KeepAliveInterval |The TCP keep-alive probe interval. |30 s |
| |EnableCompression |Whether to request deflate compression. |false |
| |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. |false |
| |========================================================= |
| |
| ==== 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_ |
| |========================================================= |
| |
| [[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 |
| |
| IMPORTANT: Transaction support over HTTP is not yet implemented in Gremlin.Net. This will be addressed by the official |
| 4.0.0 release. |
| |
| [[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.BasicAuth("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 is not supported by any variant that is not running on the Java Virtual Machine as there is |
| no `Graph` instance to deserialize a result into on the client-side. A workaround is to replace the step with |
| `aggregate(local)` and then convert those results to something the client can use locally. |
| * `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-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 |Min 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 |aiohttp |
| |3.7.3 |≥3.9 |aiohttp |
| |3.8.0 |≥3.10 |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 in the HTTP connection, you can pass an optional `headers` parameter |
| to the `DriverRemoteConnection` constructor. |
| |
| [source,python] |
| ---- |
| g = traversal().with_(DriverRemoteConnection( |
| 'http://localhost:8182/gremlin', 'g', headers={'Header':'Value'})) |
| ---- |
| |
| Gremlin-Python contains an `auth` module that 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 `pool_size` and `max_workers` parameters to 1. |
| See the <<python-configuration,Configuration>> section just below. Examples where this could apply are serverless cloud functions or WSGI |
| worker processes. |
| |
| Some connection options can also be set on individual requests made through the using `with()` step on the |
| `TraversalSource`. For instance to set request timeout to 500 milliseconds: |
| |
| [source,python] |
| ---- |
| vertices = g.with_('evaluationTimeout', 500).V().out('knows').to_list() |
| ---- |
| |
| The following options are allowed on a per-request basis in this fashion: `batchSize`, `bulkResults`, `language`, |
| `materializeProperties`, `userAgent`, and `evaluationTimeout`. |
| |
| 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 |
| |headers |Additional headers that will be added to each request message. |`None` |
| |max_workers |Maximum number of worker threads. |Number of CPUs * 5 |
| |request_serializer |The request serializer implementation.|`gremlin_python.driver.serializer.GraphBinarySerializersV4` |
| |response_serializer |The response serializer implementation.|`gremlin_python.driver.serializer.GraphBinarySerializersV4` |
| |interceptors |The request interceptors to run after request serialization.|`None` |
| |auth |The authentication scheme to use when submitting requests that require authentication. |`None` |
| |pool_size |The number of connections used by the pool. |4 |
| |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 |
| |========================================================= |
| |
| 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_options=ssl.create_default_context(), |
| read_timeout=30)) |
| ---- |
| |
| [[gremlin-python-strategies]] |
| === Traversal Strategies |
| |
| In order to add and remove <<traversalstrategy,traversal strategies>> from a traversal source, Gremlin-Python has a |
| `TraversalStrategy` class along with a collection of subclasses that mirror the standard Gremlin-Java strategies. |
| |
| [source,python] |
| ---- |
| >>> g = g.with_strategies(SubgraphStrategy(vertices=has_label('person'),edges=has('weight',gt(0.5)))) |
| >>> g.V().name.to_list() |
| ['marko', 'vadas', 'josh', 'peter'] |
| >>> g.V().out_e().element_map().to_list() |
| [{<T.id: 1>: 8, <T.label: 4>: 'knows', <Direction.IN: 2>: {<T.id: 1>: 4, <T.label: 4>: 'person'}, <Direction.OUT: 3>: {<T.id: 1>: 1, <T.label: 4>: 'person'}, 'weight': 1.0}] |
| >>> g = g.without_strategies(SubgraphStrategy) |
| >>> g.V().name.to_list() |
| ['marko', 'vadas', 'lop', 'josh', 'ripple', 'peter'] |
| >>> g.V().out_e().element_map().to_list() |
| [{<T.id: 1>: 9, <T.label: 4>: 'created', <Direction.IN: 2>: {<T.id: 1>: 3, <T.label: 4>: 'software'}, <Direction.OUT: 3>: {<T.id: 1>: 1, <T.label: 4>: 'person'}, 'weight': 0.4}, {<T.id: 1>: 7, <T.label: 4>: 'knows', <Direction.IN: 2>: {<T.id: 1>: 2, <T.label: 4>: 'person'}, <Direction.OUT: 3>: {<T.id: 1>: 1, <T.label: 4>: 'person'}, 'weight': 0.5}, {<T.id: 1>: 8, <T.label: 4>: 'knows', <Direction.IN: 2>: {<T.id: 1>: 4, <T.label: 4>: 'person'}, <Direction.OUT: 3>: {<T.id: 1>: 1, <T.label: 4>: 'person'}, 'weight': 1.0}, {<T.id: 1>: 10, <T.label: 4>: 'created', <Direction.IN: 2>: {<T.id: 1>: 5, <T.label: 4>: 'software'}, <Direction.OUT: 3>: {<T.id: 1>: 4, <T.label: 4>: 'person'}, 'weight': 1.0}, {<T.id: 1>: 11, <T.label: 4>: 'created', <Direction.IN: 2>: {<T.id: 1>: 3, <T.label: 4>: 'software'}, <Direction.OUT: 3>: {<T.id: 1>: 4, <T.label: 4>: 'person'}, 'weight': 0.4}, {<T.id: 1>: 12, <T.label: 4>: 'created', <Direction.IN: 2>: {<T.id: 1>: 3, <T.label: 4>: 'software'}, <Direction.OUT: 3>: {<T.id: 1>: 6, <T.label: 4>: 'person'}, 'weight': 0.2}] |
| >>> g = g.with_computer(workers=2,vertices=has('name','marko')) |
| >>> g.V().name.to_list() |
| ['marko'] |
| >>> g.V().out_e().value_map().with_(WithOptions.tokens).to_list() |
| [{<T.id: 1>: 9, <T.label: 4>: 'created', 'weight': 0.4}, {<T.id: 1>: 7, <T.label: 4>: 'knows', 'weight': 0.5}, {<T.id: 1>: 8, <T.label: 4>: 'knows', 'weight': 1.0}] |
| ---- |
| |
| NOTE: Many of the `TraversalStrategy` classes in Gremlin-Python are proxies to the respective strategy on |
| Apache TinkerPop's JVM-based Gremlin traversal machine. As such, their `apply(Traversal)` method does nothing. However, |
| the strategy is encoded in the Gremlin-Python bytecode and transmitted to the Gremlin traversal machine for |
| re-construction machine-side. |
| |
| [[gremlin-python-transactions]] |
| === Transactions |
| |
| IMPORTANT: Transaction support over HTTP is not yet implemented in Gremlin-Python. This will be addressed by the |
| official 4.0.0 release. |
| |
| [[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 is not supported by any variant that is not running on the Java Virtual Machine as there is |
| no `Graph` instance to deserialize a result into on the client-side. A workaround is to replace the step with |
| `aggregate(local)` and then convert those results to something the client can use locally. |
| * Use of the aiohttp library in the default transport requires the use of asyncio's event loop to run the async functions. |
| This can be an issue in situations where the application calling Gremlin-Python is already using an event loop. |
| Certain types of event loops can be patched using nest-asyncio which allows Gremlin-Python to proceed without an error like |
| "Cannot run the event loop while another loop is running". This is the preferred approach to avoiding the issue and can be |
| enabled by passing `call_from_event_loop=True` to the `AiohttpTransport` class. |
| |
| + |
| However, in situations where the loop cannot be patched (e.g. uvloop), then the current suggested workaround is to run |
| Gremlin-Python in a separate thread. This is not ideal for asynchronous web servers as the number of concurrent connections |
| will be limited by the number of threads the system can handle. The following snippet shows how Gremlin-Python can be called |
| from asynchronous code using a thread. |
| |
| + |
| [source,python] |
| ---- |
| def print_vertices(): |
| g = traversal().with(DriverRemoteConnection("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 |
| ---- |