I cannot map a reverse edge in GraphQL using @dgraph directive

Experience Report for Feature Request

What you wanted to do

Map a reverse edge to a GraphQL field instead of using a balanced @hasInverse field.

type User {
  username: String! @id
  posts: [Post] @dgraph(pred: "~Post.author")
}
type Post {
  id: ID!
  content: String
  author: User!
}

Why do I need to do this, because I iterated upon my schema and added an inverse edge that I did not have before, see real life use case:

What you actually did

Use a script like Fixing Inverse Relationships to fix my missing inverse edges, since there is no other way in GraphQL schema mapping.

Why that wasn’t great, with examples

Because I had to write this script, install a node environment to run the script, open up a DQL connection, which if I was not familiar with DQL would be an even bigger burden.

Any external references to support your case

Umm, idk, ne04j supports defining relationships with either IN or OUT but it is nice in Dgraph not having to define a relationship directive for a simple outbound relationship in GraphQL. Pros/Cons of both.

https://neo4j.com/docs/graphql-manual/current/type-definitions/relationships/

But this IN and OUT syntax is supported because neo4j supports bidirectional edges better described at:

TigerGraph’s GraphQL implementation (still in infancy Query only) has a weird handling of reverse edges using what I would term linking types in GraphQL:


All Dgraph would need to do is allow some kind of mapping directive which not only uses the ~ reverse syntax of DQL, but also then for mutations knows to write the predicates in reverse. So instead when you run the mutation:

// GraphQL Schema
type User {
  username: String! @id
  posts: [Post] @reverse(edge:"author")
}
type Post {
  id: ID!
  content: String
  author: User!
}
mutation AddPostToUser {
  updateUser(input: {
    filter: { username: { eq: "foo" } }
    set: {
      posts: [
        {
          content: "New Post"
        }
      ]
    }
  }) {
    numUIds
  }
}

Would equate to the upsert:

upsert {
  query {
    var_1 as var(func: eq(User.username,"foo")) @type(User)
  }
  set {
    _:new_1 <Dgraph.type> "Post" .
    _:new_1 <Post.content> "New Post" .
    _:new_1 <Post.author> uid(var_1) .
  }
}

And queries:

query {
  queryUser {
    username
    posts {
      id
      content
    }
  }
  queryPost {
    id
    content
    author {
      username
    }
  }
}

would equate to DQL:

{
  queryUser(func: type(User)) {
    username: User.username
    posts: ~Post.author {
      id: uid
      content: Post.content
    }
  }
  queryPost(func: type(Post)) {
    id: uid
    content: Post.content
    author: Post.author {
      username: User.username
    }
  }
}

This might become a little challenging when looking at aggregate functions, but I think it would be simple enough possibly?

1 Like

Maybe the @core-devs could link to any relative discussions on why the choice was made to use two edges outbound from each type instead of using the inverse edge on one side. I am guessing it was for schema iteration where in case the reverse edge was no longer part of the GraphQL schema the inverse edge would still be an actual edge, but maybe there is more to it? Performance? Ease of Implementation? Lazy Programming (nothing wrong with this for quick iterations)?