Skip to content

Commit

Permalink
Coverage for anonymous traversals and anonymous traversal source
Browse files Browse the repository at this point in the history
The anonymous traversal source with() syntax is in 4.x now and is a bit more streamlined than what was there before. There may yet be some spots that were missed, but most should be covered. This also covers a lot of Graph API removal. #265 #259
  • Loading branch information
spmallette committed Dec 5, 2023
1 parent 8512ffd commit cef11f9
Show file tree
Hide file tree
Showing 36 changed files with 254 additions and 82 deletions.
2 changes: 1 addition & 1 deletion book/Practical-Gremlin.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ v2-001-preview, October 1st 2023
:icons: font
//:pdf-page-size: Letter
:draftdate: October 1st, 2023
:tpvercheck: 3.7.0
:tpvercheck: 4.0.0
//
// Some notes about the Asciidoc files and processor
// -------------------------------------------------
Expand Down
5 changes: 3 additions & 2 deletions book/Section-Getting-Started.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ the following command from inside the Gremlin console.
----
// What version of Gremlin console am I running?
gremlin> Gremlin.version()
==>3.7.0
==>4.0.0
----

One thing that is not at all obvious or apparent is that the Gremlin console quietly
Expand Down Expand Up @@ -411,10 +411,11 @@ g = TinkerGraph.open().traversal()
----

which is shorthand for

[source,groovy]
----
graph = TinkerGraph.open()
g = graph.traversal()
g = traversal().with(graph)
----

In general you will not need access to the 'graph' object, and most operations can be
Expand Down
28 changes: 15 additions & 13 deletions book/Section-Introducing-Gremlin-Server.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -528,15 +528,15 @@ We have included examples of the different types of JSON result that you are lik
to have to process in the "<<serverjson>>" section that is coming up soon.

[[javagsclient]]
Connecting to a Gremlin Server from Java using 'withRemote'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connecting to a Gremlin Server from Java using 'with'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

While it is perfectly possible to work directly with the JSON returned from a Gremlin
Server it is often more desirable to have the results placed directly into variables
of the appropriate type. If the appropriate Gremlin language driver exists for the
programming language that you are using, this is quite easy to setup. In this section
we will look at connecting to a Gremlin Server from a Java application and taking
advantage of the 'withRemote' capability that TinkerPop provides.
advantage of the remote connection capability that TinkerPop provides.

NOTE: The source code in this section comes from the 'RemoteClient.java' sample
located at https://github.com/krlawrence/graph/tree/main/sample-code/java.
Expand Down Expand Up @@ -592,20 +592,22 @@ Once the 'Cluster.Builder' instance has been setup we can use it to create our
Lastly, we need to setup a 'GraphTraversalSource' object for the Gremlin Server
hosted graph that we will be working with. This object is often named "g" by
convention and is typically created using the statically imported 'traversal()'
method which in turn allows the call to 'withRemote()' which is called to establish
the remote connection. Note that the cluster instance that we just created is passed
in as a parameter. While this looks a little complicated it is really not a lot
different than when we connect to a local graph using the Gremlin Console. The only
difference is that by setting up the remote connection this way, when we start to
issue queries against the graph, rather than getting JSON objects back, the results
will automatically be serialized into Java variables for us. This makes our code a
lot easier to write and essentially is the same code from this point onwards that
would also work with a local graph that we are directly connected to.
method which in turn allows the call to 'with()' to bind the traversal source to a
graph. We've seen 'with()' take a graph instance before, but this time we will use it
with a reference to a remote connection to a graph in Gremlin Server. Note that the
cluster instance that we just created is passed in as a parameter. While this looks a
little complicated it is really not a lot different than when we connect to a local
graph using the Gremlin Console. The only difference is that by setting up the remote
connection this way, when we start to issue queries against the graph, rather than
getting JSON objects back, the results will automatically be serialized into Java
variables for us. This makes our code a lot easier to write and essentially is the
same code from this point onwards that would also work with a local graph that we are
directly connected to.

[source,groovy]
----
GraphTraversalSource g = traversal().
withRemote(DriverRemoteConnection.using(cluster));
with(DriverRemoteConnection.using(cluster));
----

We can now use our new 'GraphTraversalSource' object to issue a Gremlin query. The
Expand Down
2 changes: 1 addition & 1 deletion book/Section-Janus-Graph.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ otherwise configuring a graph.

[[janusmgmt]]
The JanusGraph management API
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

JanusGraph includes a management API that is made available via the ManagementSystem
class. You can use the management API to perform various important functions that
Expand Down
2 changes: 1 addition & 1 deletion book/Section-Moving-Beyond.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,7 @@ And here is the sort of output we should get back.

[source,groovy]
----
Gremlin version is 3.7.0
Gremlin version is 4.0.0
Loading the air-routes graph...
[country:[US], code:[AUS], longest:[12250], city:[Austin], elev:[542], icao:[KAUS], lon:[-97.6698989868164], type:[airport], region:[US-TX], runways:[2], lat:[30.1944999694824], desc:[Austin Bergstrom International Airport]]
Expand Down
183 changes: 171 additions & 12 deletions book/Section-Writing-Gremlin-Queries.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -797,17 +797,14 @@ representing the number of runways the second airport has.
----

It is also possible to use a traversal inside of a 'by' modulator. Such traversals
are known as '"anonymous traversals"' as they do not include a beginning 'V' or 'E'
step.

NOTE: Traversals that do not start with a 'V' or 'E' step are referred to as
'"anonymous traversals"'.
are known as '"anonymous traversals"' and they are discussed in greater details in
the <<anonymoustraversals,What are Anonymous Traversals?>> section.

This capability allows us to do things like combine multiple values together as part
of a path result. The example below finds five routes that start in Austin and
creates a path result containing the airport code and city name for both the source
and destination airports. In this case, the anonymous traversal contained within the
'by' modulator is applied to each element in the path.
For now, it is enough to know that they allow us to do things like combine multiple
values together as part of a path result. The example below finds five routes that
start in Austin and creates a path result containing the airport code and city name
for both the source and destination airports. In this case, the anonymous traversal
contained within the 'by' modulator is applied to each element in the path.

[source,groovy]
----
Expand All @@ -826,7 +823,7 @@ source and destination airports as part of generating the 'path' result.

[source,groovy]
----
g.V(3).out().limit(5).path().by(out().count())
g.V(3).out().limit(5).path().by(outE().count())
[59,181]
[59,191]
Expand Down Expand Up @@ -1388,6 +1385,7 @@ such as 'outE', 'inE', 'outV' and 'inV'.
[[limit]]
Limiting the amount of data returned
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

It is sometimes useful, especially when dealing with large graphs, to limit
the amount of data that is returned from a query. As shown in the examples
below, this can be done using the 'limit' and 'tail' steps. A little later in
Expand Down Expand Up @@ -1987,7 +1985,6 @@ IN={id=47, label=airport}
OUT={id=3, label=airport}
----


[[var]]
Assigning query results to a variable with a terminal step
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -2206,6 +2203,168 @@ println s
[2, 7, 5, 3, 4, 1]
----

[[deepdivetraversals]]
Deep dive on traversal terminology
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In Gremlin, we use the word "traversal" often and there is a fair bit of terminology
associated with it. Understanding that terminology will greatly help with your
understanding of Gremlin. To get started in this learning, let's return to how we
create "g", known as the 'GraphTraversalSource', which is the connection to the graph
from which we can write Gremlin to query our data.

[source,groovy]
----
// create the Graph object which in this case is a TinkerGraph
graph = TinkerGraph.open()
// create the GraphTraversalSource object
g = traversal().with(graph)
----

In looking at the above code, you might wonder where the 'traversal()' method comes
from. This function is a member of the 'AnonymousTraversalSource' class and it is
considered good form to statically import it so that it can be called without the
class. Calling 'traversal()' constructs an anonymous traversal source which is like
having a graph traversal source without a connection to a graph. Calling 'with()' on
this object binds it to a particular graph data source and then "g" is ready for you
to write your queries.

You may see examples in this book, blog posts, or other documentation that shorthand
the creation of 'g' like

[source,groovy]
----
g = TinkerGraph.open().traversal()
----

While you can take this approach, it does preclude you from having reference to the
'Graph' object. While this is typically not a problem for TinkerGraph, it can be
an issue for other TinkerPop-enabled graphs that might require that reference to
release resources or access provider specific APIs. You will find this the case later
in this book when you read the "<<janusmgmt,The JanusGraph management API>>"
section.

Another tempting shorthand is to skip the creation of "g" like

[source,groovy]
----
graph = TinkerGraph.open()
graph.traversal().V()
----

If you take this approach, you unnecessarily create 'GraphTraversalSource' objects
which will just end up in garbage collection. Typically, you create 'g' once and then
reuse it. One of the other benefits to creating 'g' once is that it allows you to set
configurations on the graph traversal source once rather than for each traversal you
execute.

Configuration steps are used to setup options on the graph traversal source. These
steps are prefixed with 'with' and work in a builder pattern style as follows

[source,groovy]
----
graph = TinkerGraph.open()
g = traversal().with(graph)
// construct a GraphTraversalSource configured with ReadOnlyStrategy and a
// List side-effect named "d"
g = g.withStrategies(ReadOnlyStrategy.instance()).
withSideEffect('d', [1,2,3])
----

Once 'g' is configured you can then use start steps to spawn a 'Traversal'. In
TinkerPop, a "traversal" is a term we consider synonymous with "query". In the
context of querying graphs, the word "traversal" implies movement which aligns with
the visual image of traversing about the vertices and edges of a graph to get the
data that you need.

While Gremlin has dozens of steps, not all of them can be used to spawn a
'Traversal'. Only those found on the 'GraphTraversalSource' can be used to do that.
The most commonly used start step is 'V', but there are a number of others, like
'E' to start by traversing edges or 'addV' to add a new vertex to the graph. It is
important to realize that a 'Traversal' object spawned from 'g' does not do much on
its own and requires that a terminal step be added to get the query results. This
point was introduced in the "<<var,Assigning query results to a variable with a
terminal step>>" section but it is worth revisiting again as it is a common mistake
made by new Gremlin users. It is easy to illustrate this point clearly with an
example in Java.

[source,java]
----
// create the Traversal object - "t" is just a reference to the traversal and is
// not the result of "g.V()" being executed
Traversal t = g.V();
// to get the result we need a terminal step, in this case toList()
List<Vertex> l1 = t.toList();
// more commonly the traversal has the terminal step added right when it is spawned
List<Vertex> l2 = g.V().toList();
----

There is one more piece of terminology to consider and we've touched on them earlier.
Anonymous traversals were first introduced in the "<<pathintro,What vertices and
edges did I visit? - Introducing 'path'>>" section, where they were used to modify
'Path' objects returned from the 'path' step. One of those examples demonstrated how
a 'by' modulator could be given an anonymous traversal to convert vertices in the
returned path into a count of the number of outgoing routes from airports.

[source,groovy]
----
g.V(3).out().limit(5).path().by(outE().count())
[59,181]
[59,191]
[59,272]
[59,105]
[59,54]
----

We often see anonymous traversals used with 'by' but they can actually be used as an
argument for a great many Gremlin steps making them an integral part of the language.
Let's take a step back from usage and examine anonymous traversals as a concept.
Like the anonymous traversal source, an anonymous traversal is one that is not bound
to a graph. Instead of spawning from 'g', an anonymous traversal spawns from a class
named with a double-underscore.

[source,groovy]
----
// spawns a traversal bound to a graph
g.V().out()
// spawns an anonymous traversal without binding to a graph
__.V().out()
// the preferred approach is to statically import V() so that you can omit "__."
// the following anonymous traversal is the same as the one above
V().out()
----

As shown earlier with 'by', an anonymous traversal can be used as an argument for
many different traversal steps.

[source,groovy]
----
g.V().has('airport','code','AUS').
repeat(timeLimit(10).out()).until(has('code','LHR')).path().by('code')
----

Without driving too deeply into the details of the example above, note that we've
used an anonymous traversal inside of both 'repeat' and 'until'.

In this section, we learned about Gremlin terminology. In summary, here are the key
points to remember:

* A 'Traversal' is a graph query.
* A 'GraphTraversalSource' is usually named "g" by convention and spawns 'Traversal'
objects.
* When either of these objects are referred to as "anonymous" they are not connected
to any graph.
Now that you've learned all of this important Gremlin terminology you are well
prepared for the more advanced topics to come.

[[wid]]
Working with IDs
~~~~~~~~~~~~~~~~
Expand Down
2 changes: 1 addition & 1 deletion sample-code/dotnet/RemoteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ static void Main(string[] args)
gremlinClient = new GremlinClient(gremlinServer, connectionPoolSettings:cpSettings);

var remoteConnection = new DriverRemoteConnection(gremlinClient, "g");
g = Traversal().WithRemote(remoteConnection);
g = Traversal().With(remoteConnection);
}
catch( Exception e)
{
Expand Down
2 changes: 1 addition & 1 deletion sample-code/golang/go-basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func createConnection(host string, port int) (*GraphTraversalSource, *DriverRemo
if err != nil {
log.Fatalln(err)
} else {
g = Traversal().WithRemote(drc)
g = Traversal().With(drc)
}
return g, drc, err
}
Expand Down
6 changes: 4 additions & 2 deletions sample-code/groovy/CreateGraph.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSo
import org.apache.tinkerpop.gremlin.structure.T
import org.apache.tinkerpop.gremlin.tinkergraph.structure.*

import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;

// Create a new (empty) TinkerGrap
TinkerGraph tg = TinkerGraph.open()
TinkerGraph graph = TinkerGraph.open()

// Create a Traversal source object
GraphTraversalSource g = tg.traversal()
GraphTraversalSource g = traversal().with(graph)

// Add some nodes and vertices - Note the use of "iterate".
g.addV("airport").property("code", "AUS").as("aus").
Expand Down
8 changes: 5 additions & 3 deletions sample-code/groovy/GraphRegion.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__
import org.apache.tinkerpop.gremlin.tinkergraph.structure.*
import org.apache.tinkerpop.gremlin.util.Gremlin

import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;

import java.io.IOException

class RegionTest {
private TinkerGraph tg
private TinkerGraph graph
private GraphTraversalSource g

// Try to create a new graph and load the specified GraphML file
def loadGraph(name) {
tg = TinkerGraph.open()
g = tg.traversal()
graph = TinkerGraph.open()
g = traversal().with(graph)

try {
g.io(name).read().iterate()
Expand Down
8 changes: 5 additions & 3 deletions sample-code/groovy/ShortestRoutes.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ import org.apache.tinkerpop.gremlin.process.traversal.*
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource
import org.apache.tinkerpop.gremlin.tinkergraph.structure.*

import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;

import java.io.IOException

class Routes {
private TinkerGraph tg
private TinkerGraph graph
private GraphTraversalSource g

// Try to create a new graph and load the specified GraphML file
def loadGraph(name) {
tg = TinkerGraph.open()
g = tg.traversal()
graph = TinkerGraph.open()
g = traversal().with(graph)

try {
g.io(name).read().iterate()
Expand Down
Loading

0 comments on commit cef11f9

Please sign in to comment.