JanusGraph Support (janusgraph 0.2.x)
Configure Auto Graph Management
If you are a user of janusgraph - a distributed graph database supporting cassandra as backend, then express-cassandra provides built in graph management functionality for you. If enabled, express-cassandra will automatically create and manage a graph with the name as your keyspace name appended with the word graph keyspacename_graph
. Express-cassandra will create graph relations and graph indexes for your tables accroding to a graph_mapping
defined in your model schema. Also you can use tinkerpop3 gremlin queries to traverse your graph using built in model methods. To enable automatic graph management, just set manageGraphs: true
in the ormOptions like the following:
{
clientOptions: {
// omitted other options for clarity
},
ormOptions: {
// omitted other options for clarity
migration: 'alter',
manageGraphs: true,
}
}
Note that janusgraph must be configured to run Gremlin Server with ConfigurationManagementGraph
setting. Otherwise express-cassandra cannot manage the graph for you. Download the pre-configured elassandra+janusgraph setup for a working configuration.
You can optionally provide connection options for gremlin server in the clientOptions like the following. If omitted, then the cassandra contactPoints
first host will be used as default host address with port: 8182
as default configuration for gremlin client.
{
clientOptions: {
// omitted other options for clarity
gremlin: {
host: '127.0.0.1',
port: 8182,
storage: {
backend: 'cassandrathrift',
hostname: '127.0.0.1',
port: 9160,
},
index: {
search: {
backend: 'elasticsearch',
hostname: '127.0.0.1',
port: 9200,
},
},
options: {
user: '...',
password: '...',
},
},
},
ormOptions: {
// omitted other options for clarity
migration: 'alter',
manageGraphs: true,
}
}
Note that any config option gremlin client supports can be used in the above gremlin configuration block.
Define JanusGraph Mapping for a Table Schema:
To create a graph mapping for your schema, use the graph_mapping
option from janusgraph like the following schema for an example User
model:
// User model
export default {
fields: {
id: 'int',
name: 'text',
age: 'int'
},
key: ['id'],
graph_mapping: {
relations: {
follow: 'MULTI',
mother: 'MANY2ONE',
},
properties: {
userId: {
type: 'Integer',
cardinality: 'SINGLE',
},
name: {
type: 'String',
cardinality: 'SINGLE',
},
age: {
type: 'Integer',
cardinality: 'SINGLE',
},
followedAt: {
type: 'Long',
cardinality: 'SINGLE',
},
},
indexes: {
byUserIdComposite: {
type: 'Composite',
keys: ['userId'],
unique: true,
},
byNameComposite: {
type: 'Composite',
keys: ['name'],
},
byNameAgeComposite: {
type: 'Composite',
keys: ['name', 'age'],
},
byUserIdNameAgeMixed: {
type: 'Mixed',
keys: ['userId', 'name', 'age'],
},
byFollowedAtVertexCentric: {
type: 'VertexCentric',
keys: ['followedAt'],
label: 'follow',
direction: 'BOTH',
order: 'decr',
},
},
},
};
-
relations
defines the edge relations in your graph. For example, here two relations namedfollow
andmother
are defined with theirmultiplicity
values. For details on what they mean, visit the janusgraph schema and data modeling docs. -
properties
defines the vertex/edge property keys with their janusgraph type and cardinality. Here we have defined userId, name and age properties for our vertices and followedAt property for our follow edge relation. Visit the janusgraph schema and data modeling docs for details of these concepts. -
indexes
defines the graph indexes for improving performance of your graph queries. Here we have defined the index names with the index properties for each of them. For details about index properties, visit the janusgraph indexing docs. Following is a brief description for the index properties that can be used:-
type
defines the index type. Possible values areComposite
,Mixed
orVertexCentric
. -
keys
defines an array of the property keys to index. Must be a valid property key defined in properties. -
unique
defines an optional boolean to enable unique contraint for the index. HereuserId
is defined to be unique. -
label
defines the edge label for VertexCentric (Edge) indexes. Here thefollow
edge relation is defined as the label. -
direction
defines the edge direction for which to create the VertexCentric index. Possible values areBOTH
,IN
andOUT
. -
order
defines the sort order of the VertexCentric index. Possible values areincr
ordecr
.
-
By default all indexes are label contrained to the schema table name, that means the indexes are always bound to the vertices and edges defined by the current model schema.
Create vertex:
To create a vertex use the createVertex method with the property values like the following:
models.instance.User.createVertex({ userId: 1234, name: 'john', age: 32 }, function(err, response) {
if (err) throw err;
// response contains the created vertex object
// response.id contains vertex id
// response.properties contains it's properties
console.log(response);
});
Note that the createVertex method creates a labeled vertex with the table name of your model schema by default. So the above operation creates an user labeled vertex.
Get vertex:
To get a vertex use the getVertex method with the vertex id like the following:
models.instance.User.getVertex(vertex.id, function(err, response) {
if (err) throw err;
// response contains the vertex object with the vertex id
console.log(response);
});
Update vertex:
To update a vertex use the updateVertex method with the vertex id and updated values object like the following:
models.instance.User.updateVertex(vertex.id, { age: 33 }, function(err, response) {
if (err) throw err;
// response contains the updated vertex object
console.log(response);
});
Delete vertex:
To delete a vertex use the deleteVertex method with the id of the vertex to be deleted:
models.instance.User.deleteVertex(vertex.id, function(err) {
if (err) throw err;
});
Create edge (relation between vertices):
To create an edge relation between two vertices use the createEdge method with the relation_name, from_vertex_id and to_vertex_id along with an optional properties object for defining edge properties.
For example, to create a follow relation between two user vertices with a followedAt property:
models.instance.User.createEdge('follow', followerVertex.id, followeeVertex.id, { followedAt: Date.now() }, function(err, response) {
if (err) throw err;
// response contains the created edge object
// response.id contains edge id
// response.properties contains it's properties
console.log(response);
});
Another example could be creating a mother edge from one user to another. Note that we can skip the properties for the edge in this case:
models.instance.User.createEdge('mother', childVertex.id, motherVertex.id, function(err, response) {
if (err) throw err;
});
Get edge:
To get an edge use the getEdge method with the edge id like the following:
models.instance.User.getEdge(edge.id, function(err, response) {
if (err) throw err;
// response contains the edge object with the edge id
console.log(response);
});
Update edge:
To update an edge property, use the updateEdge method with the updated properties object:
models.instance.User.updateEdge(edge.id, { followedAt: Date.now() }, function(err, response) {
if (err) throw err;
// response contains the updated edge object
console.log(response);
});
Delete edge:
To delete an edge relation, use the deleteEdge method with the id of the edge to be deleted:
models.instance.User.deleteEdge(edge.id, function(err) {
if (err) throw err;
});
Traversing graph using gremlin queries:
After creating some vertices and edges you can search/traverse your graph using gremlin queries. You need to use the graphQuery(gremlin_query, params, callback)
method to do that.
For example for the above user graph, you can now query to find the followers of the user named john:
models.instance.User.graphQuery('vertices.has("name", name).in("follow")', { name: 'john' }, function(err, response){
if (err) throw err;
// response contains an array of vertices containing the followers of john
console.log(response);
});
Note that graphQuery function takes a gremlin query as the first parameter. The second parameter defines the value of a variable inside the gremlin query. In this case name
is a variable defined inside the gremlin query and you have provided the value of the variable in the params object.
Default global variables for gremlin queries
You will get some default variables already defined for you while using the graphQuery
method. The variables graph
, g
and vertices
are defined globally for you. These variables represent the following gremlin queries:
graph = ConfiguredGraphFactory.open(__graphName);
g = graph.traversal();
vertices = g.V().hasLabel(__vertexLabel);
-
graph
variable contains the opened graph instance. By default __graphName contains the graph name (your keyspace name + '_graph') managed by express-cassandra. -
g
variable contains the gremlin traversal root. -
vertices
variable contains the vertices created using the current model instance. By default __vertexLabel contains the table name for your model schema. For example if you callmodels.instance.User.graphQuery
then vertices variable will contain only the user labeled vertices created bymodels.instance.User.createVertex
.
Querying the whole graph
For the previous query, our search was bounded to only the user labeled vertices, because we were using the vertices variable. Let us do another gremlin query to find all the vertices with a particular name be it a user name or the name of another schema label. Say we also have a company schema with a name property in company vertices. So to find all the companies and users with a particular name in the whole graph:
models.instance.User.graphQuery('g.V().has("name", name)', { name: 'myname' }, function(err, response){
if (err) throw err;
// response contains an array of vertices containing the name toyota
console.log(response);
});
Using the raw gremlin client
You could also use the get_gremlin_client
method to get the gremlin client instance and do any operation that the node gremlin module supports:
const gemlintClient = models.instance.User.get_gremlin_client();
gemlintClient.execute('g.V().has("name", name)', { name: 'john' }, function(err, results) {
if (err) {
return console.error(err)
}
console.log(results);
});