From ad7199e1d9895040b1cf64ed0535097fad9072f6 Mon Sep 17 00:00:00 2001 From: jrtechs Date: Sat, 28 Jul 2018 14:13:01 -0400 Subject: [PATCH] Finished Gremlin in 10 Minutes blog post --- README.md | 4 +- ...10-minutes.md => gremlin-in-10-minutes.md} | 143 +++++++++--------- img/posts/gremlinConsole.png | Bin 0 -> 19102 bytes 3 files changed, 75 insertions(+), 72 deletions(-) rename entries/programming/{gremlin-graph-database-in-10-minutes.md => gremlin-in-10-minutes.md} (59%) create mode 100644 img/posts/gremlinConsole.png diff --git a/README.md b/README.md index 0940a59..a17ca88 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ unless otherwise stated. ![](blogSql.svg) -```SQL +```mysql create database jrtechs_blog; use jrtechs_blog; @@ -105,7 +105,7 @@ npm install memory-cache --save The color scheme has been changing a lot recently. -[Adobe Color Wheel](https://color.adobe.com/create/color-wheel/?copy=true&base=2&rule=Custom&selected=3&name=Copy%20of%20Site&mode=rgb&rgbvalues=0.231,0.325499999999957,0.42,0,0.7450980392156863,0.6980392156862745,0.10196078431372549,0.36470588235294116,0.38823529411764707,0.8235294117647058,0.7529411764705882,1,0.3165071770335184,0.24148325358851674,0.49&swatchOrder=0,1,2,3,4) +[Adobe Color Wheel](https://color.adobe.com/create/color-wheel/?copy=true&base=2&rule=Custom&selected=4&name=Copy%20of%20Site&mode=cmyk&rgbvalues=0.17254901960784313,0.24313725490196078,0.3137254901960784,0.28627450980392155,0.5607843137254902,0.7450980392156863,0.5329137283008958,0.7301501780381741,1,0.8235294117647058,0.7529411764705882,1,0.042420144797897574,0,0.17000000000000004&swatchOrder=0,1,2,3,4) current: top 2C3E50 diff --git a/entries/programming/gremlin-graph-database-in-10-minutes.md b/entries/programming/gremlin-in-10-minutes.md similarity index 59% rename from entries/programming/gremlin-graph-database-in-10-minutes.md rename to entries/programming/gremlin-in-10-minutes.md index 378a2a8..bb484b2 100644 --- a/entries/programming/gremlin-graph-database-in-10-minutes.md +++ b/entries/programming/gremlin-in-10-minutes.md @@ -1,23 +1,25 @@ -## Graph Data Base Basics +## What is Gremlin? + +Gremlin is a graph traversal language: think of Gremlin as the SQL for graph databases. Gremlin is not +a graph database server, it is a language; but, there is a Gremlin Server and a Gremlin Console available for +interacting with graph databases. It is possible to use Gremlin on large database platforms +like [Titan](https://www.digitalocean.com/community/tutorials/how-to-set-up-the-titan-graph-database-with-cassandra-and-elasticsearch-on-ubuntu-16-04) + and [HBase](https://docs.janusgraph.org/latest/hbase.html). -A graph database is based on graph structures. A graph is composed of nodes, edges, and properties. A key -object/component in a graph database is stored as a node. Nodes are connected together via edges representing -relationships. For example, you may represent people as nodes and have edges representing who people are friends -with. You can assign properties to both nodes and edges. A person (node) may have the properties of age and name, -where a friendship (edge) may have a start date property. -#### Why Graph Databases? +## Graph Data Base Basics -Graph databases are great for modeling data where the value lies in the shape of the graph, or, it would be difficult -to model in a traditional table based database. +A graph database is based on graph theory. A graph is composed of nodes, edges, and properties. A key +object/component in a graph database is stored as a node. Nodes are connected via edges representing +relationships. For example, you may represent people as nodes and have edges representing friendships. +You can assign properties to both nodes and edges. A person (node) may have the properties of age and name, +where a friendship (edge) may have a start date property. -#### What is Gremlin? +## Why Graph Databases? -Gremlin is a graph traversal language; think of Gremlin as SQL but for graph databases. Gremlin is not -a graph database server, it is a language; but, there is a Gremlin Server and Gremlin Console available for -interacting with graph databases using Gremlin. It is possible to use Gremlin on large database platforms -like [Titan](https://www.digitalocean.com/community/tutorials/how-to-set-up-the-titan-graph-database-with-cassandra-and-elasticsearch-on-ubuntu-16-04) - and [HBase](https://docs.janusgraph.org/latest/hbase.html). +Graph databases are great for modeling data where the value lies in the shape of the graph. Graph databases +also allow to to model more complex relationships which would be difficult to model in a normal table-based +database. ## Gremlin Installation @@ -25,17 +27,19 @@ Download and extract the following: - [Gremlin Console](https://www.apache.org/dyn/closer.lua/tinkerpop/3.3.3/apache-tinkerpop-gremlin-console-3.3.3-bin.zip) - [Gremlin Server](https://www.apache.org/dyn/closer.lua/tinkerpop/3.3.3/apache-tinkerpop-gremlin-server-3.3.3-bin.zip) -Start the Gremlin server by running it with the start script in the bin folder. +Start the Gremlin server by running it with the start script in the bin folder. As a prerequisite for running gremlin, you +must have Java installed on your computer. ``` ./gremlin-server.sh ``` -Start the Gremlin console by running the gremlin.sh or gremlin.bat script in the bin folder of the apache-tinkerpop folder. +Start the Gremlin console by running the gremlin.sh or gremlin.bat script in the bin folder. ``` ./gremlin.sh ``` -Now you need to instantiate a new graph on the server to use. To to that, execute the following commands. -```gremlin +Now you need to instantiate a new graph on the server to use. To to that, execute the following commands in +the Gremlin console. +```java #Creates a empty graph gremlin> graph = EmptyGraph.instance() ==>emptygraph[empty] @@ -54,80 +58,81 @@ gremlin> g = graph.traversal().withRemote(DriverRemoteConnection.using(cluster, Now that you have your gremlin server and console set up, you are ready to start executing Gremlin queries. -#### Add a Vertex +### Adding a Vertex -For Gremlin, nodes are referred to as "Vertexes". To add a node/vertex to the graph, you simply use the -command addV('node label') on your graph traversal source. For consistency, most people and documentation -use "g" as their default graph traversal source. To append properties to your your node, you string a series of -.property('property_name', 'property_value') to the queries. +In Gremlin nodes are referred to as "Vertexes". To add a node/vertex to the graph, you simply use the +command addV() on your graph traversal source. For consistency, most people +use "g" as their default graph traversal source. To append properties to your your vertex, you add a series of +".property('property_name', 'property_value')" strings to the add vertex query. -ex: -```gremlin +EX: +```java g.addV('student').property('name', 'Jeffery').property('GPA', 4.0); ``` -#### Update a Property +### Updating a Property -Unlike SQL, you are not limited to a specific schema for a graph database. If you want to add or change -a property of a vertex or edge, you simply call its .property('property_name', 'property_value'). -The g.V(1) in the example refers to a specific node with the primary id of 1, these ids are auto assigned by the graph database. -You can replace g.V(1) with a command to select a specific node. +Unlike SQL, you are not limited to a specific schema in a graph database. If you want to add or change +a property on a vertex or edge, you simply use the property command again. +The "g.V(1)" in the following example refers to a specific vertex with the primary id of 1-- the graph database auto assigns these ids. +You can replace "g.V(1)" with a command to select a specific vertex or edge. -```gremlin +```java g.V(1).property('name', 'Jeffery R'); ``` -#### Selection +### Selection -Selecting nodes and edges is the most complicated part of Gremlin. The concept is not particularly hard but, there -are dozens of ways to do traversals and selections. I will cover the most common and helpful ways to traverse a -graph. +Selecting nodes and edges is the most complicated part of Gremlin. The concept is not particularly hard, but, there +are dozens of ways to do graph traversals and selections. I will cover the most common aways to traverse a graph. -This example will select all vertexes which have the label "student". The .valueMap() appended to the end means -that it will returns a map of all the properties of the nodes it returns. -```gremlin +This example will select all vertexes which have the label "student". The ".valueMap()" command appended to the end of the query +makes Gremlin return a map of all the objects it returns with their properties. +```java g.V().hasLabel('student').valueMap(); ``` -In this example instead of returning a ValueMap of values, we are just returning the names of the students +In this following example, instead of returning a ValueMap of values, we are just returning the names of the students in the graph. -```gremlin +```java g.V().hasLabel('student').values('name'); ``` This example will return the GPA of the student with the name "Jeffery R". -```gremlin +```java g.V().hasLabel('student').has('name', 'Jeffery R').values('gpa'); ``` -This command will all the students in order of their GPA. -```gremlin +This command will return all the students in order of their GPA. +```java g.V().hasLabel('student').order().by('gpa', decr).value('name') ``` -#### Adding Edges +### Adding Edges -If you want to add a edge (relationship/connection) between two nodes, the easiest way (my opinion) to do it in Gremlin is by -using something called aliasing. In this example we select two nodes and give them a name, in this case it is "a", and "b". -After we have selected two edges, we can add an edge to them using the addE('Relation_Name') command. The syntax of this is -nice because we know that "a" is friends with "b"-- it is easy to tell the direction +The easiest way (my opinion) to add edges in Gremlin is by +using aliasing. In this example we select two nodes and assign them a name: in this case it is "a", and "b". +After we have selected two edges, we can add an edge to them using the "addE()" command. The syntax of this is +nice because we know that "a" is friends with "b"-- it is easy to tell the direction of the edge. -```gremlin +```java g.V(0).as('a').V(1).as('b').addE('knows') .from('a').to('b'); ``` -## Using Gremlin With Java +## Using Gremlin with Java -Now that you know the syntax of Gremlin, you are ready to use it somewhere other than just the Gremlin console. If you -are trying to use Gremlin with Java, there is a nice Maven dependency for TinkerPop and Gremlin. If you want to quickly -connect to your server with Java, make sure your server is set up exactly as it was before this tutorial started discussing -Gremlin Syntax. -```maven +Now that you know the basic syntax of Gremlin, you are ready to use it somewhere other than the Gremlin console. If you +are trying to use Gremlin with Java, there is a great Maven dependency for TinkerPop and Gremlin. If you want to quickly +connect to your Gremlin server with Java, make sure your server is set up exactly as it was before this tutorial started discussing +Gremlin syntax. + +#### Maven dependency for Java: +```html com.tinkerpop @@ -150,7 +155,7 @@ Gremlin Syntax. ``` It is helpful to wrap everything relating to the graph database connection into a single Java class. This is roughly -the code that I usually use to interact with a Gremlin Server, anybody is free to use it. +the code that I usually use to interact with a Gremlin Server-- anybody is free to use it. ```java public class GraphConnection @@ -182,7 +187,7 @@ public class GraphConnection } ``` -ex GraphConnection Usage: +#### Basic GraphConnection.java Usage: ```java RemoteConnection con = new RemoteConnection() String query = "g.V().hasLabel('player')" + @@ -193,14 +198,13 @@ String query = "g.V().hasLabel('player')" + ".as('p2')" + ".addE('friends')" + ".from('p1').to('p2')"; -//System.out.println(query); this.con.queryGraph(query); ``` -Overly complex usage with lambda example. +#### Overly complex usage with a lambda statement ```java /** - * Fetches a list of friends from the graph database + * Fetches the list of a player's friends. * * @param id steam id * @return list of friends @@ -225,11 +229,11 @@ private List getFriendsFromGraph(String id) ``` The most important thing to do while playing around with Gremlin in Java is to keep an eye on the -return type. From experience, I can say that it is often easier to return the node/edges from your -query rather than doing a valueMap. +return type. From experience, I can say that it is often easier to return the vertex from your +query rather than returning the valueMap. -Without adding valueMap()/values() to the end of the query, you now can directly access the vertex or edge in the result rather than -doing some voodoo witchcraft and casting between ArrayLists and HashMaps. +Without returning the valueMap in the query, you can directly access the vertex +in the result rather than doing some voodoo witchcraft and casting between ArrayLists and HashMaps. The previous example could be re-written as this: ```java @@ -242,16 +246,15 @@ String query = "g.V().hasLabel('player')" + for(Result r: this.con.queryGraph(query)) { friends.add(new Player(r.getVertex("name").value().toString), - r.getVertex("id").value().toString),) + r.getVertex("id").value().toString)); } ``` -Now you know enough to be dangerous with Gremlin. Yay! If you want to do more than basic things with Gremlin, -I highly suggest that you take a look at the tutorial [SQL 2 Gremlin](http://sql2gremlin.com/). -If you plan on deploying this to production, it is recommended to use HBase with JanusGraph for a persistent back end storage +You now know enough about Gremlin to be dangerous with it. Yay! If you want to do more than basic things with Gremlin, +I highly suggest that you look at the tutorial [SQL 2 Gremlin](http://sql2gremlin.com/). +If you plan on deploying this to production, it is recommended that you use HBase for a persistent back end storage server. - ## Resources - [SQL 2 Gremlin](http://sql2gremlin.com/) diff --git a/img/posts/gremlinConsole.png b/img/posts/gremlinConsole.png new file mode 100644 index 0000000000000000000000000000000000000000..325844fa971ba7560c06ab6d9081206e9b29d67f GIT binary patch literal 19102 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 Dv~O1A literal 0 HcmV?d00001