Compose Database-as-a-Service Help and Documentation

Everything you need to know about Compose, Hosted or Enterprise, is here in our help system. Whether you run one database for your businesses' sole application or six different databases to support an entire corporation, we've got the information you need.

Creating and Accessing Graphs

📘

The JanusGraph Sandbox

All Gremlin commands are executed in a sandbox for security. This means, though, that there is no passing of variables and results of queries between command calls. Sandboxes also restrict what functions are available to Gremlin scripts.

Be aware that most examples of Gremlin usage that you find outside Compose are created without the sandbox and may use restricted functions or rely on variables persisting between requests.

The exception is WebSocket sessions and Gremlin Console sessions. With them, variables declared with the def keyword will be retained for the lifetime of the session.

More information on the differences the sandbox creates, see the JanusGraph Hosted Notes page.

Creating a graph

JanusGraph on Compose has a dedicated graph factory for creating, opening and closing graphs. Unlike other JanusGraph implementations, this factory - ConfiguredGraphFactory - removes the need to know about underlying storage mechanisms making it simply a matter of naming your new graph.

def graph=ConfiguredGraphFactory.create("example");
curl -XPOST -d '{"gremlin": "def graph=ConfiguredGraphFactory.create(\"example\");0;"}'

The JanusGraph sandbox requires that we declare all variables we are using. This means using the def keyword to declare the graph variable here.

Janusgraph on Compose requires that graph names include only alphanumeric and the underscore character.

Note that the curl examples in this section are incomplete; they lack a URL or authentication. We've done this to provide a clearer view of the commands being used. To find out how to get add URL and authentication, consult Connecting to JanusGraph.

Notice that in the curl example, the command ends with ";0;". This is a workaround as the HTTP request API emits an error ({"message":"Cannot get namespace of root","Exception-Class":"java.lang.IllegalArgumentException"}) even though it has completed the operation correctly. This only affects the graph type when code tries to return it.

This is all you need to do to create a graph. Let's move on and start working with it.

Opening a graph

To work with a graph, you have to open it. This is simply a matter of calling open() on JanusGraphConfiguredFactory.

def graph=ConfiguredGraphFactory.open("example");

With the JanusGraph sandbox in operation, you will need to precede all your scripts and commands with this.

For an example of this, there is a ready-made graph example called GraphOfTheGods which can be loaded into a graph to give a user something to traverse and explore. Here's what the model looks like, with all its vertices, edges and properties.

The Graph Of The Gods

On Compose's JanusGraph, you call the GraphOfTheGods.loadWithoutMixedIndex() method, passing it an open graph, and that model is created for you.

def graph=ConfiguredGraphFactory.open("example");
GraphOfTheGodsFactory.loadWithoutMixedIndex(graph,true);
curl -XPOST -d '{"gremlin": "def graph=JanusGraphConfiguredFactory.open(\"example\"); GraphOfTheGodsFactory.loadWithoutMixedIndex(graph,true);"}'

We'll use this Graph of the Gods for following examples. You can read about this Graph in the JanusGraph - Getting Started.

Traversing

To move around a graph, you need a traversal source. This enables you to query the graph and is obtained be calling traversal() on the graph object. This would mean the full prefix for any command would be

def graph=ConfiguredGraphFactory.open("example")
def g=graph.traversal()

The use of graph for the graph and g for the traversal source is common. If you just needed the traversal source, you could compress this down to:

def g=ConfiguredGraphFactory.open("example").traversal();

Now we can start traversing the graph.

Traversing by Global Index - People and Relationships

In the Graph of the Gods graph, it creates a global index of name properties. This kind of index is often your first step in getting to a particular point in the graph. For example, say we wish to find "saturn" in the graph:

def g=ConfiguredGraphFactory.open("example").traversal()
def saturn=g.V().has("name", "saturn").next()
curl -XPOST -d '{"gremlin": "def g=ConfiguredGraphFactory.open(\"example\").traversal(); def saturn=g.V().has(\"name\", \"saturn\").next()"}'

It returns the vertex id that has the property name: saturn. Like this: v[4248].

To further the example, using the "saturn" vertex by defining it in a variable 'saturn', you can ask which vertices store an edge with the 'father' label pointing to it. Using the in("father") method selects those incoming edges, and using values("name") returns the value of the name property of that vertex.

def g=ConfiguredGraphFactory.open("example").traversal();
def saturn=g.V().has("name","saturn").next();
g.V(saturn).in("father").values("name");
curl -XPOST -d '{"gremlin": "def g=ConfiguredGraphFactory.open(\"example\").traversal(); def saturn=g.V().has(\"name\", \"saturn\").next(); ;g.V(saturn).in(\"father\").values(\"name\")"}'

This returns the names of the children of saturn, although there just the only child: "jupiter".

Repeat the in("father") operation a second time and you are looking at the Saturn vertex's grandchild.

def g=ConfiguredGraphFactory.open("example").traversal();
def saturn=g.V().has("name", "saturn").next();
g.V(saturn).in("father").in("father").values("name");
curl -XPOST -d '{"gremlin": "def g=ConfiguredGraphFactory.open(\"example\").traversal(); def saturn=g.V().has(\"name\", \"saturn\").next(); ;g.V(saturn).in(\"father\").in(\"father\").values(\"name\")"}'

This returns "hercules" if you want to try it yourself.

Traversing by Global Index - Location

In the GraphOfTheGods, some edges have a "place" property which is globally indexed. It has latitude and longitude and can be used for geolocating events. Edges labeled "battled" use the place property to represent where battles took place. To find all the battles that took place within 50km of Athens (latitude:37.97 and long:23.72), the edges of the graph are searched for any value of the property "place" that falls within the radius of those coordinates. Using has("place", geoWithin(Geoshape.circle(37.97, 23.72, 50))) selects those edges.

def g=ConfiguredGraphFactory.open("example").traversal();
g.E().has("place", geoWithin(Geoshape.circle(37.97, 23.72, 50)));
curl -XPOST -d '{"gremlin": "def g=ConfiguredGraphFactory.open(\"example\").traversal(); g.E().has(\"place\", geoWithin(Geoshape.circle(37.97, 23.72, 50)))"}'

It returns the edge label along with the identifiers for the incoming and outgoing vertices, like so: e[36k-3c0-7x1-368][4320-battled->4112]
e[2sc-3c0-7x1-3ag][4320-battled->4264]

To find out who participated in these two battles, we can use as() to stash values and select() to retrieve values, the command gets the incoming and outgoing vertices and by("name") extracts the names of the gods involved.

def g=ConfiguredGraphFactory.open("example").traversal();
g.E().has("place", geoWithin(Geoshape.circle(37.97, 23.72, 50))).as("source").inV().as("god2").select("source").outV().as("god1").select("god1", "god2").by("name");
curl -XPOST -d '{"gremlin": "def g=ConfiguredGraphFactory.open(\"example\").traversal(); g.E().has(\"place\", geoWithin(Geoshape.circle(37.97, 23.72, 50))).as(\"source\").inV().as(\"god2\").select(\"source\").outV().as(\"god1\").select(\"god1\", \"god2\").by(\"name\")"}'

(This query can be broken down into: Use as a source the .inV as 'god2', use as a source the .outV as 'god1', then use god1, god2 and get the value of their 'name' property.)

This would output:
{god1=hercules, god2=hydra}
{god1=hercules, god2=nemean}
We learned that Hercules fought both the Lernaean Hydra and the Nemean Lion within 50km of Athens.

Traversing the Vertices

Earlier we used twoin() elements to identify that Hercules was the grandchild of Saturn. This can also be expressed as a loop. Gremlin can repeat a predicate:

def g=ConfiguredGraphFactory.open("example").traversal();
def saturn=g.V().has("name", "saturn").next(); 
def hercules=g.V(saturn).repeat(__.in("father")).times(2).next();
curl -XPOST -d '{"gremlin": "def g=ConfiguredGraphFactory.open(\"example\").traversal(); def saturn=g.V().has(\"name\", \"saturn\").next(); def hercules=g.V(saturn).repeat(__.in(\"father\")).times(2).next()"}'

Once we have the Hercules vertex we can, for example, traverse the edges labeled "father" and "mother" and determine the "type" of parents Hercules had, or traverse all the "battled" edges to see who he fought:

def parents=g.V(hercules).out("father", "mother").label();
[
  "god",
  "human"
]

def fought=g.V(hercules).out("battled").values("name");
[
  "cerberus",
  "hydra",
  "nemean"
]

def fought_once=g.V(hercules).outE("battled").has("time", gt(1)).inV().values("name");
[
  "cerberus",
  "hydra"
]
# The cURL version of those commands is performed as three completely separate operations:

$ curl -XPOST -d '{"gremlin": "def g=ConfiguredGraphFactory.open(\"exampleg\").traversal(); def saturn=g.V().has(\"name\", \"saturn\").next(); def hercules=g.V(saturn).repeat(__.in(\"father\")).times(2).next(); g.V(hercules).out(\"father\", \"mother\").label();" }' ...

{"requestId":"b2f4b7c6-26a1-47aa-b0da-7b3b1b7dfdba","status":{"message":"","code":200,"attributes":{}},"result":{"data":["god","human"],"meta":{}}}

$ curl -XPOST -d '{"gremlin": "def g=ConfiguredGraphFactory.open(\"exampleg\").traversal(); def saturn=g.V().has(\"name\", \"saturn\").next(); def hercules=g.V(saturn).repeat(__.in(\"father\")).times(2).next(); g.V(hercules).out(\"battled\").valueMap()" }' ...

{"requestId":"46c57cff-1600-431c-afb5-03a3228ffafb","status":{"message":"","code":200,"attributes":{}},"result":{"data":[{"name":["hydra"]},{"name":["cerberus"]},{"name":["nemean"]}],"meta":{}}}

$ curl -XPOST -d '{"gremlin": "def g=ConfiguredGraphFactory.open(\"exampleg\").traversal(); def saturn=g.V().has(\"name\", \"saturn\").next(); def hercules=g.V(saturn).repeat(__.in(\"father\")).times(2).next(); g.V(hercules).outE(\"battled\").has(\"time\", gt(1)).inV().values(\"name\")" }'

{"requestId":"b42453c7-f1c8-4220-a4fd-0fee55b444d1","status":{"message":"","code":200,"attributes":{}},"result":{"data":["cerberus","hydra"],"meta":{}}}

$

The last example being who he fought once, by asking for those edges where the "time" property = 1.

Creating your own graph

In this section, we'll create a new graph and populate it from the Gremlin console. If you haven't yet installed it, check out the Websockets and Gremlin page for installation and setup instructions. We're replicating the process from the Tinkerpop documentation: Getting Started: Creating a Graph to highlight differences you may find.

First, we'll create our graph and open it, being sure to commit the operation so that the graph persists:

gremlin> def graph=ConfiguredGraphFactory.create("mygraph")
==>standardjanusgraph[astyanax:[10.210.171.132, 10.210.171.131, 10.210.171.130]]
gremlin> def graph=ConfiguredGraphFactory.open("mygraph")
==>standardjanusgraph[astyanax:[10.210.171.132, 10.210.171.131, 10.210.171.130]]
gremlin> graph.tx().commit()
==>null

If we close our session now, the new graph will be there when we come back. Next, we open and add two vertices to the graph. A person, named "marko" and a piece of software, named "lop".

gremlin> def v1 = graph.addVertex(T.label, "person", "name", "marko", "age", 29)
==>v[4304]
gremlin> def v2 = graph.addVertex(T.label, "software", "name", "lop", "lang", "java")
==>v[4208]
gremlin> graph.tx().commit()
==>null

If we want to find one of the vertices, say the one with the name "marko", we first traverse the graph and then use the has() method and it returns the identifier of the vertex. If we want to reassign our vertices to the variables v1 and v2, say if we have closed our old session and started a new one, we use next().

gremlin> def g=graph.traversal()
==>graphtraversalsource[standardjanusgraph[astyanax:[10.176.29.132, 10.176.29.131, 10.176.29.130]], standard]
gremlin> g.V().has("name","marko")
==>v[4304]
def v1 = g.V().has("name","marko").next()
==>v[4304]
def v2 = g.V().has("name","lop").next()
==>v[4232]

Next we can add a weighted edge between out two vertices. It is labeled "created" and has a weight of 0.4. Taking note that you do need to cast the number as a decimal, using 0.4d.

gremlin> v1.addEdge("created", v2, "weight", 0.4d)
===>e[odxqy-3bk-27th-39k][4304-created->4232]
gremlin> graph.tx().commit()
===>null

We now have a very simple graph to query. Using this graph, we can ask for all the software that Marko has created.

gremlin> g.V().has("name","marko").out("created").values("name")
===>lop

So far only the one! But he's young. Adding more "software" vertices, "people" vertices, and "created" edges will result in a much more elaborate and useful map of software engineers and their projects.


Still Need Help?

If this article didn't solve things, summon a human and get some help!

Updated about a year ago


Creating and Accessing Graphs


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.