In September 2019, Graph Query Language is accepted as a new database query language in a vote by the International SQL Standards Committee. The unification of GQL takes time. In this post, we’ve selected some mainstream graph query languages and compared the CRUD usage in these languages respectively. The Graph Query Languages to Be Compared Gremlin is a graph traversal language developed by Apache TinkerPop and has been adopted by a lot of graph database solutions. It can be either or . Gremlin declarative imperative Gremlin is Groovy-based, but has many language variants that allow developers to in many modern programming languages such as Java, JavaScript, Python, Scala, Clojure and Groovy. write Gremlin queries natively : Janus Graph, InfiniteGraph, Cosmos DB, DataStax Enterprise(5.0+) and Amazon Neptune. Supported graph databases Cypher is a declarative graph query language that allows expressive and efficient data querying in a property graph. Cypher The language was designed with the power and capability of SQL. The keywords of the Cypher language are not case sensitive, but attributes, labels, relationship types and variables are case sensitive. : Neo4j, AgensGraph and RedisGraph Supported graph databases nGQL introduces its own query language, , which is a declarative, textual query language like SQL, but designed for graphs. Nebula Graph nGQL The keywords of the nGQL language are case sensitive and it support statement composition so that there’s no need for statement embedding. : Nebula Graph Support graph databases Terms Comparison Before comparing the three graph query languages, let’s take a look at their terms and concepts first. The table below explains how these languages define nodes and edges: Syntax Comparison - CRUD After understanding the common terms in Gremlin, Cypher, and nGQL, let’s take a look at the general syntax of these graph query languages. This section will walk you through the basic CRUD syntax for Gremlin, Cypher and nGQL respectively. Graph Refer to the following example on how to create a graph space. We omitted Cypher since you don’t need to create a graph space before you can add any data to the graph databases. g = TinkerGraph. ().traversal() CREATE gods # Create a graph that Gremlin can traverse open # Create a graph space in nGQL SPACE Vertex We all know that graph consists of nodes and edges. A node in Cypher is called a vertex in Gremlin and nGQL. And an edge is the connection among two nodes. Refer to the following example on inserting new vertex in these query languages respectively. # vertex Gremlin g.addV(vertexLabel).property() # vertex Cypher (:nodeLabel {property}) # vertex nGQL VERTEX tagName (propNameList) vid:(tagKey propValue) Insert in Insert in CREATE Insert in INSERT VALUES Vertex Type The nodes/vertices can have types. They are called labels in Gremlin and Cypher, and tags in nGQL. A vertex type can have multiple properties. For example, vertex type have two properties, i.e. and . Person name age Create Vertex Type Refer to the following example on vertex type creation. We omitted Cypher since you don’t need a label before inserting data. g.addV(vertexLabel).property() CREATE tagName(PropNameList) # Create vertex Gremlin type in # Create vertex nGQL type in Note that Gremlin and nGQL both support . This keyword automatically detects if the corresponding vertex type exists. If it does not exist, a new one is created. Otherwise, no vertex type is created. IF NOT EXISTS Show Vertex Types When the vertex types are created, you can show them with the following queries. They will list all the labels/tags rather than certain labels/tags. # vertex Gremlin g.V().label().dedup(); # vertex Cypher MATCH (n) labels(n) # vertex Cypher db.labels(); # vertex nGQL TAGS Show types in Show types in method 1 RETURN DISTINCT Show types in method 2 CALL Show types in SHOW CRUD on Vertices This section introduces the basic CRUD operations on vertices using the three query languages. Insert Vertices # vertex certain Gremlin g.addV(String vertexLabel).property() # vertex certain Cypher (node:label) # vertex certain nGQL VERTEX <tag_name> (prop_name_list) <vid>:(prop_value_list) Insert of type in Insert of type in CREATE Insert of type in INSERT VALUES Get Vertices # vertices Gremlin g.V(<vid>) # vertices Cypher MATCH (n) condition properties(n) # vertices nGQL PROP <tag_name> <vid> Fetch in Fetch in WHERE RETURN Fetch in FETCH ON Delete Vertices g.V( ).drop() MATCH ( ) DETACH DELETE vertex nGQL DELETE VERTEX # Delete vertex in Gremlin <vid> # Delete a vertex in Cypher node :label node # Delete in <vid> Update a Vertex’s Property This section shows you how to update properties for vertex. # vertex Gremlin .V(<vid>).property() # vertex Cypher . = V # vertex nGQL VERTEX <vid> <update_columns> Update in g Update in SET n prop Update in UPDATE SET Both Cypher and nGQL use keyword to set the vertex type, except that the keyword is added in nGQL to identify the operation. The operation of Gremlin is similar to the above-mentioned fetching vertex, except that it adds operations for changing property. SET UPDATE Edges This section introduces the basic CRUD operations on edges. Edge Types Like vertices, edges can also have types. g.edgeLabel() CREATE EDGE edgeTypeName(propNameList) # Create an edge Gremlin type in # Create an edge nGQL type in CRUD on Edges Insert Edges of Certain Types Inserting an edge is similar to inserting vertices. Cypher uses and and nGQL uses to represent edges respectively. Gremlin uses the keyword to indicate edge direction. -[]-> -> to() Edges are by default directed in the three languages. The chart on the left below is a directed edge while the one on the right is an undirected edge. # edge certain Gremlin g.addE(String edgeLabel). (v1). (v2).property() # edge certain Cypher (<node1- >:<label1- >)- [(<relationship- >:<relationship-label- >)] ->(<node2- >:<label2- >) # edge certain nGQL EDGE <edge_name> ( <prop_name_list> ) <src_vid> -> <dst_vid>: ( <prop_value_list> ) Insert of type in from to Insert of type in CREATE name name name name name name Insert of type in INSERT VALUES Delete Edges # edge Gremlin g.E(<eid>). () # edge Cypher MATCH (<node1- >:<label1- >)-[r:relationship-label- ]->() r # edge nGQL EDGE <edge_type> <src_vid> -> <dst_vid> Delete in drop Delete in name name name DELETE Delete in DELETE Fetch Edges # edges Gremlin g.E(<eid>) # edges Cypher MATCH (n)-[r:label]->() condition properties(r) # edges nGQL PROP <edge_name> <src_vid> -> <dst_vid> Fetch in Fetch in WHERE RETURN Fetch in FETCH ON Other Operations In addition to the common CRUD on vertices and edges, we will also show you some combined queries in the three graph query languages. Traverse edges # Traverse edges specified vertices Gremlin g.V(<vid>).outE(<edge>) # Traverse edges specified vertices Cypher Match (n)->[r:label]->[] WHERE id(n) = vid RETURN r # Traverse edges specified vertices nGQL GO FROM <vid> OVER <edge> with in with in with in Traverse edges reversely In reverse traverse, Gremlin uses to indicate reversion, Cypher uses . nGQL uses keyword . in <- REVERSELY # Traverse edges reversely specified vertices Gremlin g.V(<vid>). (<edge>) # Traverse edges reversely specified vertices Cypher MATCH (n)<-[r:label]-() # Traverse edges reversely specified vertices nGQL GO FROM <vid> OVER <edge> REVERSELY with in with with Traverse edges bidirectionally If the edge direction is (either direction is acceptable), Gremlin uses , Cypher use , and nGQL use keyword . irrelevant bothE() -[]- BIDIRECT g.V( ).bothE( ) MATCH (n)-[r: ]-() GO FROM OVER BIDIRECT # Traverse edges reversely with specified vertices Gremlin <vid> <edge> # Traverse edges reversely with specified vertices Cypher label # Traverse edges reversely with specified vertices nGQL <vid> <edge> Query N hops along specified edge Gremlin and nGQL use and respectively to represent N hops. Cypher uses . times STEP relationship*N # hops along specified edge Gremlin .V(<vid>). ( (<edge>)).times( ) # hops along specified edge Cypher MATCH ( )-[r: * ]->() WHERE condition # hops along specified edge nGQL GO STEPS FROM <vid> OVER <edge> Query N in g repeat out N Query N in n label N RETURN r Query N in N Find paths between two vertices # Find paths two vertices Gremlin g.V(<vid>).repeat( ()). (<vid>).path() # Find paths two vertices Cypher MATCH p =(a)-[.*]->(b) condition p # Find paths two vertices nGQL FIND <vid> <vid> * between in out until between in WHERE RETURN between in ALL PATH FROM TO OVER Example queries This section introduces some demonstration queries. Demo model: The Graphs of Gods The examples in this section make extensive use of the toy graph distributed with called as diagrammed below。 Janus Graph The Graphs of Gods This example describes the relationships between the beings and places of the Roman pantheon. Inserting data ==>v[1] ==>v[2] ==>v[31] ==>v[32] ==>e[13][2-father->1] # Inserting vertices # # nGQL nebula> INSERT VERTEX character(name, age, ) VALUES ( ):( , 10000, ), ( ):( , 5000, ); type hash "saturn" "saturn" "titan" hash "jupiter" "jupiter" "god" # # Gremlin gremlin> saturn = g.addV( ).property(T.id, 1).property( , ).property( , 10000).property( , ).next(); "character" 'name' 'saturn' 'age' 'type' 'titan' gremlin> jupiter = g.addV( ).property(T.id, 2).property( , ).property( , 5000).property( , ).next(); "character" 'name' 'jupiter' 'age' 'type' 'god' gremlin> prometheus = g.addV( ).property(T.id, 31).property( , ).property( , 1000).property( , ).next(); "character" 'name' 'prometheus' 'age' 'type' 'god' gremlin> jesus = g.addV( ).property(T.id, 32).property( , ).property( , 5000).property( , ).next(); "character" 'name' 'jesus' 'age' 'type' 'god' # # Cypher cypher> CREATE (src:character {name: , age: 10000, : }) "saturn" type "titan" cypher> CREATE (dst:character {name: , age: 5000, : }) "jupiter" type "god" # Inserting edges # # nGQL nebula> INSERT EDGE father() VALUES ( )-> ( ):(); hash "jupiter" hash "saturn" # # Gremlin gremlin> g.addE( ).from(jupiter).to(saturn).property(T.id, 13); "father" # # Cypher cypher> CREATE (src)-[rel:father]->(dst) Deleting # nGQL nebula> DELETE VERTEX ( ); hash "prometheus" # Gremlin gremlin> g.V(prometheus).drop(); # Cypher cypher> MATCH (n:character {name: }) DETACH DELETE n "prometheus" Updating ==>v[32] # nGQL nebula> UPDATE VERTEX ( ) SET character.type = ; hash "jesus" 'titan' # Gremlin gremlin> g.V(jesus).property( , 6000); 'age' # Cypher cypher> MATCH (n:character {name: }) SET n.type = ; "jesus" 'titan' Fetching/Reading # nGQL # Gremlin gremlin> g.V(saturn).valueMap(); ==>[name:[saturn],type:[titan],age:[10000]] # Cypher cypher> MATCH (n:character {name:"saturn"}) RETURN properties(n) nebula> FETCH PROP ON character hash("saturn"); =================================================== | character.name | character.age | character.type | =================================================== | saturn | 10000 | titan | --------------------------------------------------- ╒════════════════════════════════════════════╕ │"properties(n)" │ ╞════════════════════════════════════════════╡ │{"name":"saturn","type":"titan","age":10000}│ └────────────────────────────────────────────┘ Finding the name of Hercules’s Father # nGQL nebula> LOOKUP .name == | \ -> GO $-.VertexID father YIELD $$ .character.name; ===================== | $$ .character.name | ===================== | jupiter | # Gremlin gremlin> g.V().hasLabel( ).has( , ). ( ). ( ); ==>jupiter # Cypher cypher> MATCH (src: { :"hercules"})-[:father]->(dst: ) dst.name ╒══════════╕ │"dst.name"│ ╞══════════╡ │"jupiter" │ └──────────┘ ON character WHERE character 'hercules' FROM OVER --------------------- 'character' 'name' 'hercules' out 'father' values 'name' character name character RETURN Finding the name of Hercules’s Grandfather # nGQL nebula> LOOKUP .name == | \ -> GO STEPS $-.VertexID father YIELD $$ .character.name; ===================== | $$ .character.name | ===================== | saturn | # Gremlin gremlin> g.V().hasLabel( ).has( , ). ( ). ( ). ( ); ==>saturn # Cypher cypher> MATCH (src: { :"hercules"})-[:father* ]->(dst: ) dst.name ╒══════════╕ │"dst.name"│ ╞══════════╡ │"saturn" │ └──────────┘ ON character WHERE character 'hercules' 2 FROM OVER --------------------- 'character' 'name' 'hercules' out 'father' out 'father' values 'name' character name 2 character RETURN Find the characters with age > 100 # nGQL # Gremlin gremlin> g.V().hasLabel( ).has( ,gt(100)).values( ); ==>saturn ==>jupiter ==>neptune ==>pluto # Cypher cypher> MATCH (src:character) WHERE src.age > 100 RETURN src.name nebula> LOOKUP ON character WHERE character.age > 100 YIELD character.name, character.age; ========================================================= | VertexID | character.name | character.age | ========================================================= | 6761447489613431910 | pluto | 4000 | --------------------------------------------------------- | -5860788569139907963 | neptune | 4500 | --------------------------------------------------------- | 4863977009196259577 | jupiter | 5000 | --------------------------------------------------------- | -4316810810681305233 | saturn | 10000 | --------------------------------------------------------- 'character' 'age' 'name' ╒═══════════╕ │"src.name" │ ╞═══════════╡ │ "saturn" │ ├───────────┤ │ "jupiter" │ ├───────────┤ │ "neptune" │ │───────────│ │ "pluto" │ └───────────┘ Find who are Pluto’s cohabitants, excluding Pluto himself # nGQL nebula> GO hash("pluto") lives YIELD lives._dst place | GO $-.place lives REVERSELY \ $$ .character.name != "pluto" YIELD $$ .character.name cohabitants; =============== | cohabitants | =============== | cerberus | # Gremlin gremlin> g.V(pluto). ( ). ( ). ( (neq(pluto))). ( ); ==>cerberus # Cypher cypher> MATCH (src: { :"pluto"})-[:lives]->()<-[:lives]-(dst: ) dst.name ╒══════════╕ │"dst.name"│ ╞══════════╡ │"cerberus"│ └──────────┘ FROM OVER AS FROM OVER WHERE AS --------------- out 'lives' in 'lives' where is values 'name' character name character RETURN Pluto’s Brothers # which brother lives which place? ## nGQL nebula> GO hash("pluto") brother YIELD brother._dst god | \ GO $-.god lives YIELD $^.character.name Brother, $$ . .name Habitations; ========================= | Brother | Habitations | ========================= | jupiter | sky | | neptune | sea | ## Gremlin gremlin> g.V(pluto). ( ). ( ). ( ). ( ). ( , ). ( ); ==>[god:jupiter, place:sky] ==>[god:neptune, place:sea] ## Cypher cypher> MATCH (src: { :"pluto"})-[:brother]->(bro: )-[:lives]->(dst) bro.name, dst.name ╒═════════════════════════╕ │"bro.name" │"dst.name"│ ╞═════════════════════════╡ │ "jupiter" │ "sky" │ ├─────────────────────────┤ │ "neptune" │ "sea" │ └─────────────────────────┘ in FROM OVER AS FROM OVER AS location AS ------------------------- ------------------------- out 'brother' as 'god' out 'lives' as 'place' select 'god' 'place' by 'name' Character name Character RETURN In addition to the basic operations in the three graph query languages, we will work on another piece of comparison of advanced operations in these languages. Stay tuned! Previously published at https://nebula-graph.io/posts/graph-query-language-comparison-cypher-gremlin-ngql/