Real inverse relation on GraphQL schema

I’m currently learning dgraph to implement a quite particular use case.
long story short I have a system in place which feeds a large quantity of data in dgraph and I’m aiming to serve the data through GraphQL.
Being GraphQL the only way to query data, I designed the GraphgQL schema first and i built a component that generates the correct RDF to match the GraphQL schema.

Tha major issue I faced using RDF mutation to insert data is the hasInverse directive.
Using this particular directive dgraph generates 2 separate predicate which are kept synced using GraphQL:

type Text {
	text: String @search(by: [hash])
	is_name: [Person!] @hasInverse(field: name)
}


type Person {
    name: Text!
} 

Generates


which is properly handled as long as the data is inserted though GraphQL, for instance, a mutation like

mutation {
  addPerson(input: [{name: {text: "paolo"}}]) {
    person {
      name {
        text
      }
    }
  }
}

creates both the relation Person.name and Text.is_name

However using RDFs it’s required to build the rdf to manually set both relation in order to keep the schema consistent

{
 set {
    _:paolo <dgraph.type> "Person" .
    _:name <dgraph.type> "Text" .
    _:name <Text.text> "Paolo" .
    _:paolo <Person.name> _:name .
  	_:name <Text.is_name> _:paolo .
 }
}

This becomes quite tricky if the schema grows or if updates / deletions are performed.

Exploiting the dgraph directive i figured out how to build a proper inverse relation in order to keep the “link” consistent in GraphQL without the need of update 2 relations:

Having a predicate in dgraph such as
Person.name uid reverse
is possible to insert a GraphQL schema like

type Text {
	text: String @search(by: [hash])
	is_name: [Person!] @dgraph(pred: "~Person.name")
}


type Person {
    name: Text @dgraph(pred: "Person.name")
} 

Updating / inserting / deleting the relation from RDF or GrapQL it stays consistent from the perspective of both dgraph and GraphQL

After 30 lines of context, i would like to ask three things:
Is this a proper to use dgraph?
Will the GraphQL implementation change in the near future so that this solution will break?
Why isn’t this the default behaviour of a GraphQL field with hasInverse directive instead of generating 2 different predicates which do not share any information and can lead to predicates pointing to empty / null nodes?

2 Likes

Hi @Luscha
The way you suggested if it works for your use case then go ahead, just remember in doing this way you won’t be able to do GraphQL mutation for that edge which has inverse.
No, it won’t break your current solution.

Hi, I’m happy @Luscha asked this question, it’s quite what was dancing in my head for some time but didn’t took time to phrase it :

Would it be possible, in the future, to have a more “native” reverse field in the graphql schema ? The solution proposed there is quite hacky and needs us to, after loading the schema, ask dgraph to add the “reverse” to Person.name.

Maybe change the default “reverse” to be able to specify a name for it, while still defaulting to “~predicate” ?

1 Like

@vardhanapoorv
I was expecting some “negative” implications from using this hack. Thank you for your reply.
I tried the inverse mutation in graphql and as you said it’s no possibile using the reverse edge. I then played around with rdf and i guess i is simply illegal to build an RDF mutation using a reverse edge

mutation {
  set {
     uid(t1) <~Person.name> uid(p1) .
  }
}

Leads to an error, as expected.

I then would like to ask why dgraph has not the capability to perform this kind of mutation.

I actually expected dgraph to be able to mutate an inverse relation, considering the fact that it is able to query it.

Hi, you’re actually bumping into some of the reasons why our GraphQL doesn’t use @reverse. Here’s the main reasons:

  • @reverse doesn’t allow mutations along the reverse edge. That was pretty much a show stopper in itself.
  • @reverse is always a list in the reverse direction. That creates some modelling issues in GraphQL and when combined with the point above, would restrict how you could model and mutate.
  • before we added the @dgraph directive, it would have added some complexity to query generation because it’d need a special case to work out that it’s a thing with a reverse and then to add the ~ edge instead of the real one.

I don’t think it’s ‘hacked in’ the way we did it. I actually think (because of the above) the other way of just leaning on reverse would have been the hack because it’d decrease what you can do with the API.
Where we’ve landed in the end, you get either choice.

I feel like there’s two things for us to do here:

  1. Seems like one of the biggest things we are missing is guidance. We don’t really have any good docs atm on how to handle running with GraphQL± and GraphQL at the same time. We also don’t have any docs on bulk loading with GraphQL.

  2. The GraphQL schema update has @dgraph but it doesn’t preserve some of the Dgraph things like @count and @reverse, so it wipes some bits of schema, which feels like what @Miaourt is bumping into. I wonder if the solution is to extend @dgraph so it can also take other args like @dgraph(pred: "Person.name", reverse: true) or @dgraph(pred: "Person.name", with: [reverse, count]).

Anything else that could help?

6 Likes

I guess that some guidance to bulk import using graphql would be nice.

Unfortunately today I also discovered that is not possible to use the reverse edge solution combined with interfaces.

type Object {
   ownedBy: Person @dgraph(pred: "Object.owner")
}

type BusinessMan implements Person {
  companyName: String
}

interface Person {
    name: String
    owns: [Object] @dgraph(pred: "~Object.owner")
} 

Leads to an error because when Car is expanded it expect “ownedBy”'s edge to be an edge of type BusinessMan

couldn't rewrite mutation updateGQLSchema because input:1: Type Object; Field ownedBy: should be of type BusinessMan to be compatible with @dgraph reverse directive but is of type Person.\n

Honestly i feel like interfaces present way too much issue when trying to use the full potential of dgraph combining GraphQL+/- and GraphQL

Hmmm, I feel like this one should be allowed. There’s some rules that get checked around this one at schema upload time.

I’ll check in with the team to see if there’s any reason why we aren’t letting this case through. I’ll let you know later if it needs to be logged as a bug.

1 Like

Thank you! I’ll wait then

I checked this and it looks like a bug to me. Seems like the restriction that we have here is too strict. We wanted to check that in case a type (BusinessMan) has a reverse field owns, then there should be a field in the reverse type Object that is of type BusinessMan but we don’t need that. The type of the field can be BusinessMan or any interface that BusinessMan implements like Person here.

Can you please file this as a bug on GitHub so that we can get it fixed?

2 Likes

Sure

Done Dgraph do not accept valid GraphQL with inverse @dgraph directive · Issue #5744 · dgraph-io/dgraph · GitHub

1 Like

This was fixed and should be available in the v20.07.0 release and also master.

2 Likes

It works, thank you

1 Like