@reverse dircetive

Hi,

@MichelDiz
I am using dql and I have two types user and membership
membership has a predicate ofUser of type uid and I linked it to the user type.
I also added the reverse directive.

My questions are:

  • will I be able to get the user from the membership and vice versa?
  • What should I do in order to create a many to one relationship between user and membership - each user has many memberships but a membership belongs only to one user
  • how would you implement this gql scheme in dql (using the reverse if necessary):
type Membership {
  id: ID!
  belongsTo: User! @hasInverse(field: memberships)
  in: Repository! @hasInverse(field: activeMemberships)
  role: Role! @search
}

type Repository {
  id: ID!
  name: String! @search(by: [regexp, exact])
  description: String @search(by: [fulltext])
  branches: [Branch!]
  activeMemberships: [Membership!]!
  isPublic: Boolean! @search
  tags: [Hashtag!] @hasInverse(field: appearsOn)
  createdAt: DateTime! @search(by: [month])
  numOfStars: Int
  visits: [RepositoryVisit!]
  dataSource: IDataSource!
}
type User {
  username: String! @search(by: [hash, regexp]) @id
  name: String
  email: String! @search(by: [hash, regexp])
  memberships: [Membership!] @hasInverse(field: belongsTo)
  hasCredentials: [Credentials!] @hasInverse(field: forUser)
  visits: [UserVisit!]
  joinedAt: DateTime @search(by: [month])
}

GraphQL Schema doesn’t support the reverse directive. The reverse and the @hasInverse are different approaches.

If it is a direct edge and you use the reverse directive in DQL. Yes.

That should be right memberships: [Membership!] @hasInverse(field: belongsTo) isn’t?

But it is different in DQL tho. The @hasInverse don’t create a relationship in the same field. So you can’t use the reverse index in that approach.

As I mentioned, not possible as far as I know. But I’m not using GraphQL in my day to day work. Just sometimes. Dgraph is evolving too fast and adding more features. It is hard to keep up - I can say that today I am more expert in DQL than GraphQL.

Thanks @MichelDiz,
I understand that the @reverse and @hasInverse are different.
We had a conversation about it a few months ago.
I wonder what I should do to create a bidirectional connection between some types. Let’s say that I want to know the memberships that a user has or if I have a membership id, which is the user that owns this membership. I also want to that with repositories and active memberships.

I create this scheme, but I think I have redundant fields, and it doesn’t take advantage of the capabilities of DQL:

<Membership.in_repository>: uid .
<Membership.of_user>: uid .
<Membership.role>: string @index(hash) .
<Repository.name>: string @index(hash, trigram) .
<Repository.description>: string @index(fulltext) .
<Repository.created_on>: datetime @index(day) .
<Repository.last_updated>: datetime @index(hour) .
<Repository.object_store>: uid .
<Repository.is_public>: bool .
<Repository.stars>: int .
<Repository.tags>: [uid] .
<Repository.active_memberships>: [uid] @count .
<User.username>: string @index(hash, trigram) @upsert .
<User.full_name>: string @index(term) @lang .
<User.email>: string @index(hash, trigram) @upsert .
<User.joined_on>: datetime @index(month) .
<User.last_modified>: datetime .
<User.memberships>: [uid] @count .
<User.api_key_sets>: uid .

type <Membership> {
    Membership.role
    Membership.of_user
    Membership.in_repository
}

type <Repository> {
    Repository.name
    Repository.is_public
    Repository.created_on
    Repository.last_updated
    Repository.stars
    Repository.tags
    Repository.active_memberships
    Repository.object_store
}

type <User> {
    User.username
    User.email
    User.name
    User.joined_on
    User.last_modified
    User.memberships
    User.api_key_sets
}

Notice that I have in each type the field that connects one type to the other.
Does the @revers directive help with this, or is there any better way to implement it?
How would you do that?

Hi @MichelDiz,

I still need help to figure out the @reverse directive and its usage.
I would be glad if you have a look at the scheme above and let me know how to better implement it using the reverse directive or another design pattern.
Having a connector from each type to its parent/child as Membership.in-repositoryand Repository.active_memebrships type seems a bit odd.

@spinelsun It depends on your needs.
there is a workaround in order to exploit the @reverse directive in the graphql schema as well, but you lose some benefits of the graphql implementation:

EG:

dql

User.memberships: [uid] @count @reverse .

type <Membership> {
    <~User.memberships> .
}

graphql

type Membership {
  belongsTo: [User!] @dgraph(pred: "~User.memberships")
}
type User {
  memberships: [Membership!] @hasInverse(field: belongsTo)
}

By doing so you only have one edge between User and Membership both in dql and graphql schema, but you lose the ability to perform mutation on Membership.belongsTo field (dgraph can’t perform mutations on inverse fields) and you have also to deal with multiple Users in the Membership.belongsTo relation (inverse relations are always [uid]).
If your use case do not care about this, this solution might help you to keep the dql schema simple, by the other hand if you don’t need to directly perform operations in dql (which means you will use the database mostly though graphql interface), then just let dgraph handle the double edge.

@Luscha thanks for your explanation.
I won’t use gql endpoint in my project only dql.
I need to have the ability to query a membership and find its owner (the user) or to query a user and find all of its membership.
Same thing for repository.
Ammm since a user can only have a membership inside a repo created the type of membership so I would have user-membership(role)-repo connection.
What I don’t know is how to do this in dql.
In gql it is easy by using the inverse directive and these three types where membership gets connection to both user and repository.
May you help me to create these types and connection between them using dql only (with or without the reverse directive, if it is not necessary)?
(I want to reduce the inverse filed if possible so I won’t need to manage them by my self)

Sorry for the delay.

If the direction doesn’t add new information about the relation. I would use a reverse index. Otherwise, I would create two edges that represent a concept. e.g: “if a user has an outgoing edge called <askfriendship> and an incoming edge <acceptedfriendship> - so this user has a friend”.

I personally use reverse edges just to traverse. Applying complex filtering.

Well, if you have a deep relation, traversing with the reverse directive is your tool.

That’s a normal traversing through the nested graph.

Normal direction

{
  var(func: eq(User.username, "Lucas")){
    User.memberships {
     REPOS as Membership.in_repository
    }
  } 
  q(func: uid(REPOS)){
    Name : Repository.name
  }
}

Reverse direction

{
  var(func: eq(Repository.name, "Something")){
    <~Membership.in_repository> {
     User as <~User.memberships>
    }
  } 
  q(func: uid(User)){
    Name : User.username
  }
}

Both <Membership.in_repository> and <User.memberships> have to have the reverse directive in the schema.

2 Likes

Great thanks.

This is what I did in the end based on your response:

<Repository.active_memberships>: [uid] @count @reverse .

<Membership.role>: string @index(hash) .
<Membership.created_at>: datetime .

<User.memberships>: [uid] @count @reverse .

Then I used this query:

{
  me(func: type("Membership")){
    Membership.role
    ~User.memberships{
    	User.full_name
  	}
  	~Repository.active_memberships{
  		Repository.name
    }
  }
}

It worked!

1 Like