Cypher "translated" to GraphQL+-

Abstract

In my humble opinion. Cypher is not Graphs friendly. But it’s not that hard to understand. It tries to maintain concepts of SQL RDBMS. For me, it’s a waste of time. We should invest in a language similar to Graph’s own structure, to dive into the paradigm. Just like GraphQL does. And that’s where GraphQL+- is inspired from.

Another detail I see is that Neo4J is CSV dependent. Maybe it has some advantage for them to use CSV. But in part, this adds an extra layer of complexity for users to create relationships. For CSV, in general, has no relations. They are just tables. After you add datasets in CSV you need to create relations. You need to know Cypher well and your own dataset to create accurate relationships.

I am not an expert on Cypher. But that’s what it seems to me.

Unraveling Cypher Query

MATCH (m:Person {name: 'Mark'})
RETURN m

So, everything inside the parentheses is a “node” (e.g. CREATE (n),(m) = two nodes). Everything before : (double dots) is a “variable”. And you can directly consider it like our value variable(https://docs.dgraph.io/query-language/#value-variables).

After the double dots is the “type” of the Node itself, in Neo4j context is called “Label”. So you can directly consider it like our type system see https://docs.dgraph.io/query-language/#using-types-during-queries. So CREATE (n:Person:Swedish) would be a node with multiple types.

Inside the Curly brackets(braces{}) is the body of the query merged with the function. So to translate this you need to set the func to “eq(name, “Mark”)” and inside the graphql+- body use { name }.

In graphql+-

{
  user(func: type(Person)) @filter(eq(name, "Mark")) {
      name
  }
}

Continuing

MATCH (a:Person),(b:Person)
WHERE a.name = 'A' AND b.name = 'B'
CREATE (a)-[r:RELTYPE]->(b)
RETURN type(r)

So in this Cypher query we have 2 nodes to find. And it gonna create a relation between them. As you can see it is ( Node from var a )-[ Var : EDGE ]->( Node from var b). So everything in brackets will be an edge that follows a direction if you set ->. See our concet of Edges https://docs.dgraph.io/design-concepts/#edges

BTW, Every time you see the pattern “MATCH + CREATE or MERGE” in any Cypher context. This means she is analogous to Upsert Block.

In graphql+-

upsert {
  query {
  A as var(func: type(Person)) @filter(eq(name, "A"))
  B as var(func: type(Person)) @filter(eq(name, "B"))
}

  mutation {
    set {
      uid(A) <RELTYPE> uid(B) .
    }
  }
}

Node or Relationship Properties / Facets

MATCH (p:Person {name: 'Jennifer'})-[rel:IS_FRIENDS_WITH {since: 2018}]-> (X)
RETURN X
MATCH (n)-[:REL {status:"good"}]->()

Relationship property in Neo4J is analogous to the Facet we have in Dgraph. Both have the same purpose, but apparently work internally differently in Neo4J. By the way, Facets in Dgraph are bidirectional. So when you use Reverse Edges in Dgraph. There will be the same facets for both directions.

Well, to identify when Cypher uses properties in relationships. Just see that inside the edges there is a Curly brackets. In the first example, {since: 2018} is the “Facet” of this Cypher query. And {status:"good"} is the “Facet” of the second.

See more about Facets here.

Get directions

example 1

//data stored with this direction
CREATE (p:Person)-[:LIKES]->(t:Technology)

In RDF (Single direction p -> t)

{
  set {
    _:p <LIKES> _:t .
    _:p <dgraph.type> "Person" .

    _:t <dgraph.type> "Technology" .
  }
}

Use Reverse to know “Who likes Technology”.

Ref: https://docs.dgraph.io/query-language/#reverse-edges
In graphql+-

{
  q(func: type(Technology)) {
      fans_Of_tech : ~LIKES { 
         name #?
         age #?
         dgraph.type #it gonna be "Person".
      }
  }
}

example 2

//query relationship backwards will not return results
MATCH (p:Person)<-[:LIKES]-(t:Technology)

In Dgraph’s lang, we don’t have this concept of reverse direction to be the pivot of the relationship. We have only one direction which is p -> t. If you wanna see it as “reverse” you have to specify in the schema as @reverse. So you get what you want a sort of “p <- t”.

So if you still wanna do this, the direction p <- t in Dgraph is a bit odd (I think), cuz It is like “Technology likes Person”.

So reverse is better in case you just wanna “get known about the parent of that node” or a list of “users” who “like/knows/bought/checked/visited/view” that entity.

In any case In RDF (Single direction p <- t which is actually t -> p)

{
  set {
    _:p <dgraph.type> "Person" .

    _:t <LIKES> _:p .
    _:t <dgraph.type> "Technology" .
  }
}

In graphql+-

{
  q(func: type(Technology)) {
      LIKES { 
         dgraph.type
      }
  }
}

example 3

//better to query with undirected relationship unless sure of direction
MATCH (p:Person)-[:LIKES]-(t:Technology)

In RDF (double direction p <-> t)

{
  set {
    _:p <LIKES> _:t .
    _:p <dgraph.type> "Person" .

    _:t <LIKES> _:p .
    _:t <dgraph.type> "Technology" .
  }
}

In graphql+-

{
  q(func: type(Person)) {
      LIKES { 
        dgraph.type
        ~LIKES {
           dgraph.type #This will return the reverse of the Technology entity, which is the Person itself.
         } 
      }
  }
}

Or

{
  person(func: type(Person)) {
      dgraph.type
      LIKES { 
        dgraph.type
      }
  }

  Tech(func: type(Technology)) {
      dgraph.type
      LIKES { 
        dgraph.type
      }
  }
}

The Basics operations / Cypher Keywords

MATCH

It is the mix of query body and root function.

CREATE

Simple operation to create nodes.

MERGE

Is a kind of Upsert Query (And Bulk upsert). In Cypher terms it is like a combination of MATCH and CREATE.

RETURN

This concept doesn’t exist in GraphQL+-. But could be implemented in Upsert blocks.

DELETE

The delete operation depends on the Neo4J’s “Value Variable”. The whole Cypher is a kind of “Upsert Block like” query. It deletes the Node set in the “Value Variable”.

DETACH

This operation just remove edges between nodes.

DETACH DELETE

This does the same as the last one but deletes the node, set in the “Value Variable”. Also this “detach” incoming edges. In Dgraph’s terms It means, it removes the “reverse” edges at the same time it deletes the node.

REMOVE

This operation just remove values/predicates from the node.

SET

This operation adds or updates values/predicates in the node.

WHERE

It’s an extension of a set of params for the “root query”. Neo4J’s Cypher separate "where, Match and so on to keep the same logic line of operations in RDBMS.

Continuing

In this post, I will get some queries from Cypher and “translate” to GraphQL+-. It may help some people who are already familiar with Cypher to become familiar with GraphQL+-.

Well, If you have any query you wanna me to “translate”, please feel free to ask bellow.

ref: https://www.quackit.com/neo4j/tutorial/neo4j_query_language_cypher.cfm
In Neo4j

MATCH (p:Person { Name:"Homer Flinstone" })
RETURN p

In graphql+-

{
  q(func: type(Person)) @filter(eq(Name, "Homer Flinstone")){
    Name
  }
}

Ref: https://www.quackit.com/neo4j/tutorial/neo4j_create_a_node_using_cypher.cfm

CREATE (a:Artist { Name : "Strapping Young Lad" })

In RDF (you can also use JSON)

{
  set {
    _:Node <Name> "Strapping Young Lad" .
    _:Node <dgraph.type> "Artist" .
  }
}

And

CREATE (b:Album { Name : "Heavy as a Really Heavy Thing", Released : "1995" })
RETURN b

In RDF

{
  set {
    _:Node <Name> "Heavy as a Really Heavy Thing" .
    _:Node <Released> "1995" .
    _:Node <dgraph.type> "Album" .
  }
}

We don’t have a “return”. You need to do a query for it.

2 Likes

Creating Multiple Nodes

CREATE (a:Album { Name: "Killers"}), (b:Album { Name: "Fear of the Dark"}) 
RETURN a,b

In RDF

{
  set {

    _:NodeA <Name> "Killers" .
    _:NodeA <dgraph.type> "Album" .

    _:NodeB <Name> "Fear of the Dark" .
    _:NodeB <dgraph.type> "Album" .

  }
}

Create a Relationship

Ref: https://www.quackit.com/neo4j/tutorial/neo4j_create_a_relationship_using_cypher.cfm

In Neo4j

MATCH (a:Artist),(b:Album)
WHERE a.Name = "Strapping Young Lad" AND b.Name = "Heavy as a Really Heavy Thing"
CREATE (a)-[r:RELEASED]->(b)
RETURN r

In graphql+-

upsert {
  query {
    Artist as var(func: type(Artist)) @filter(eq(Name, "Strapping Young Lad"))
    Album as var(func: type(Album)) @filter(eq(Name, "Heavy as a Really Heavy Thing"))
  }

  mutation {
    set {
      uid(Artist) <RELEASED> uid(Album) .
    }
  }
}

Adding More Relationships

In Neo4j

CREATE (p:Person { Name: "Devin Townsend" })

In RDF

{
  set {
    _:NodeA <Name> "Devin Townsend" .
    _:NodeA <dgraph.type> "Person" .
  }
}

In Neo4j

MATCH (a:Artist),(b:Album),(p:Person)
WHERE a.Name = "Strapping Young Lad" AND b.Name = "Heavy as a Really Heavy Thing" AND p.Name = "Devin Townsend" 
CREATE (p)-[pr:PRODUCED]->(b), (p)-[pf:PERFORMED_ON]->(b), (p)-[pl:PLAYS_IN]->(a)
RETURN a,b,p

In graphql+-

upsert {
  query {
    Artist as var(func: type(Artist)) @filter(eq(Name, "Strapping Young Lad"))
    Album as var(func: type(Album)) @filter(eq(Name, "Heavy as a Really Heavy Thing"))
    Person as var(func: type(Artist)) @filter(eq(Name, "Devin Townsend"))
  }

  mutation {
    set {
      uid(Person) <PRODUCED> uid(Album) .
      uid(Person) <PERFORMED_ON> uid(Album) .
      uid(Person) <PLAYS_IN> uid(Artist) .
    }
  }
}

Create an Index

Ref: https://www.quackit.com/neo4j/tutorial/neo4j_create_an_index_using_cypher.cfm

In Neo4j

CREATE INDEX ON :Album(Name)

In Dgraph

curl -X POST localhost:8080/alter -d 'Name: string @index(exact) .'

View the Index

In Neo4j

:schema

In Dgraph (Query)

schema {}

https://docs.dgraph.io/query-language/#querying-schema

Selecting data with MATCH

Rer: https://www.quackit.com/neo4j/tutorial/neo4j_select_data_with_match_using_cypher.cfm

In Neo4j

MATCH (p:Person)
WHERE p.Name = "Devin Townsend"
RETURN p

In graphql+-

{
  q(func: type(Person)) @filter(eq(name, "Devin Townsend")){
    Name
  }
}

OR (if you really wanna do like this)

{
  P as var(func: type(Person))

  q(func: uid(P)) @filter(eq(name, "Devin Townsend")){
    Name
  }
}

In Neo4j

MATCH (a:Artist)-[:RELEASED]->(b:Album)
WHERE b.Name = "Heavy as a Really Heavy Thing" 
RETURN a

In graphql+-

{
  var(func: type(Album)) @filter(eq(name, "Heavy as a Really Heavy Thing")) {
    a as ~RELEASED
  }
   q(func: uid(a)) {
       Name
    }
}

Return all Nodes

We don’t have a func like that, but you can return all nodes by type

In graphql+-

{
  All_Artists(func: type(Artist))  {
    Name
  }
}

Limit the Results

MATCH (n) RETURN n 
LIMIT 5

In graphql+-

{
  All_Artists(func: type(Artist), first:5)  {
    Name
  }
}

Delete a Node

Ref: https://www.quackit.com/neo4j/tutorial/neo4j_delete_a_node_using_cypher.cfm

In Neo4j

MATCH (a:Album {Name: "Killers"}) DELETE a

In graphql+-

upsert {
  query {
    v as var(func: type(Album)) @filter(eq(Name, "Killers")
  }

  mutation {
    delete {
      uid(v) * *. # You need the type well defined.
    }
  }
}

Deleting Multiple Nodes

In Neo4j

MATCH (a:Artist {Name: "Iron Maiden"}), (b:Album {Name: "Powerslave"}) 
DELETE a, b

In graphql+-

upsert {
  query {
    v1 as var(func: type(Artist)) @filter(eq(Name, "Iron Maiden")
    v2 as var(func: type(Album)) @filter(eq(Name, "Powerslave")
  }

  mutation {
    delete {
      uid(v1) * *. 
      uid(v2) * *. 
    }
  }
}

Deleting All Nodes

We don’t have a way to delete all nodes via query. What you can do is drop you DB.

curl -X POST localhost:8080/alter -d '{"drop_op": "DATA"}'

This will clean your DB and left the Schema intact.

Others

In Neo4j

MATCH ()-[r:RELEASED]-() 
DELETE r

In graphql+-

upsert {
  query {
    v as var(func: type(Artist)) @filter(has(RELEASED)){ 
       R as RELEASED  
     }
  }

  mutation {
    delete {
      uid(v) <RELEASED> uid(R) . #This will only delete relationships. Not the nodes.
    }
  }
}

From parent is very easy.

In Neo4j

MATCH (:Artist)-[r:RELEASED]-(:Album) 
DELETE r

In graphql+-

In this query you need to verify the relationship between two nodes. It gets a little complicated as you will need to check reverse edges.

upsert {
  query {
    var(func: type(Artist)) @filter(has(RELEASED)) {
        R as RELEASED @filter(has(Album)) { 
             v as  ~RELEASED
# By this I am ensuring that I am deleting a relationship that comes from artist and goes to an album.
        }
    }
  }

  mutation {
    delete {
      uid(v) <RELEASED> uid(R) .
    }
  }
}

In Neo4j

MATCH (:Artist {Name: "Strapping Young Lad"})-[r:RELEASED]-(:Album {Name: "Heavy as a Really Heavy Thing"}) 
DELETE r

In graphql+-

upsert {
  query {
     var(func: type(Artist)) @filter(eq(Name, "Strapping Young Lad")) {
      R as RELEASED @filter(eq(Name, "Heavy as a Really Heavy Thing")){ 
             v as  ~RELEASED
        }
    }
  }

  mutation {
    delete {
      uid(v) <RELEASED> uid(R) .
    }
  }
}

In Neo4j

MATCH (a:Artist {Name: "Strapping Young Lad"}) DETACH DELETE a

In graphql+-

upsert {
  query {
    a as var(func: type(Artist)) @filter(eq(Name, "Strapping Young Lad"))
  }

  mutation {
    delete {
      uid(a) * * .
    }
  }
}

Note that this query deletes the artist node and its nested relations.

These look good. It’s worth a blog post.

1 Like

Adding more info (also updated the first text)

Create a full path

CREATE p =(andy { name:'Andy' })-[:WORKS_AT]->(neo)<-[:WORKS_AT]-(michael { name: 'Michael' })
RETURN p

In RDF

{
  set {
    _:andy <name> "Andy" .
    _:andy <WORKS_AT> _:neo .
    _:andy <dgraph.type> "Person" .

    _:michael <name> "Michael" .
    _:michael <WORKS_AT> _:neo .
    _:michael <dgraph.type> "Person" .

    _:neo <name> "Neo" .
    _:neo <dgraph.type> "Company" .
  }
}

In Dgraph’s JSON Mutation

{
   "set": [
      {
         "name":"Andy",
         "WORKS_AT":{
            "uid":"_:neo"
         },
         "dgraph.type":"Person"
      },
      {
         "name":"Michael",
         "WORKS_AT":{
            "uid":"_:neo"
         },
         "dgraph.type":"Person"
      },
      {
         "uid":"_:neo",
         "name":"Neo",
         "dgraph.type":"Company"
      }
   ]
}

Add later relations.

In Neo4j

MATCH (jennifer:Person {name: 'Jennifer'})
MATCH (mark:Person {name: 'Mark'})
CREATE (jennifer)-[rel:IS_FRIENDS_WITH]->(mark)

In this operation, you need that both Persons already exist in the DB. So in this example you gonna create them, and then create the relation between them.

In RDF (set both Persons)

{
  set {
    _:NodeA <name> "Jennifer" .
    _:NodeA <dgraph.type> "Person" .

    _:NodeB <name> "Mark" .
    _:NodeB <dgraph.type> "Person" .
  }
}

Then create a relation using Upsert.
In graphql+-

upsert {
  query {
    jennifer as var(func: type(Person)) @filter(eq(name, "Jennifer")
      mark   as var(func: type(Person)) @filter(eq(name, "Mark")
  }

  mutation {
    set {
      uid(jennifer) <IS_FRIENDS_WITH> uid(mark) .
        uid(mark)   <IS_FRIENDS_WITH> uid(jennifer) .
    }
  }
}

BUT! you can create the relation directly in the RDF.

{
  set {
    _:NodeA <name> "Jennifer" .
    _:NodeA <dgraph.type> "Person" .
    _:NodeA <IS_FRIENDS_WITH> _:NodeB . 

    _:NodeB <name> "Mark" .
    _:NodeB <dgraph.type> "Person" .
    _:NodeB <IS_FRIENDS_WITH> _:NodeA . 
  }
}

In Dgraph’s JSON Mutation

{
   "set": [
      {
         "uid":"_:NodeA",
         "name":"Jennifer",
         "IS_FRIENDS_WITH":{
            "uid":"_:NodeB"
         },
         "dgraph.type":"Person"
      },
      {
         "uid":"_:NodeB",
         "name":"Mark",
         "IS_FRIENDS_WITH":{
            "uid":"_:NodeA"
         },
         "dgraph.type":"Person"
      }
   ]
}

In JSON mutations you can use {"uid":"_:NodeB"} or [{"uid":"_:NodeB"}] as long you have set in your schema IS_FRIENDS_WITH: [uid] . (One to many relation).

Updating Data with Upsert

In Neo4j

MATCH (p:Person {name: 'Jennifer'})
SET p.birthdate = date('1980-01-01')
RETURN p

In graphql+-

upsert {
  query {
    jennifer as var(func: type(Person)) @filter(eq(name, "Jennifer")
  }

  mutation {
    set {
      uid(jennifer) <birthdate> "1980-01-01" .
    }
  }
}

In Neo4j

MATCH (:Person {name: 'Jennifer'})-[rel:WORKS_FOR]-(:Company {name: 'Neo4j'})
SET rel.startYear = date({year: 2018})
RETURN rel

As you can see, in this operation we have a “SET” operation adding values to the relation. In Dgraph we call it “Facet”.

In graphql+-

upsert {
  query {
    jennifer as var(func: type(Person)) @filter(eq(name, "Jennifer")
    Company as var(func: type(Company)) @filter(eq(name, "Neo4j")
  }

  mutation {
    set {
      uid(jennifer) <WORKS_FOR> uid(Company) (since="2018") .
    }
  }
}

Note:
This is an interesting pattern that is usual in Neo4J, but isn’t possible (as far as I know) in GraphQL +-.

how about develop a program to do translate from cypher to graphql± ?

1 Like

Would be nice, but not all Cypher features works in Dgraph.

1 Like