Reverse Edge not Working as expected

To facilitate better answering of questions, if you have a question, please fill in the following info. Otherwise, please delete the template.

What I want to do

I want to create a Edge between from one node to 2 other nodes that can be traversaled in reverse.
Start with a new fresh Docker and load the following Schema via

curl -X POST localhost:8080/admin/schema -d ‘@schema.graphql

schema.graphql:

type Company {
    id: ID!
    Name: String
    Location: String
    Shareholder: [Person] @hasInverse(field: "Share")
}
type Person{
    id: ID!
    FirstName: String
    LastName: String
    Share: [Company] @hasInverse(field: "Shareholder")
}

Result:

Initial Data:

{
    "set":
    [
        {
            "dgraph.type": "Company",
            "Company.Name": "Company A",
            "Company.Location": "Texas"
        },
        {
            "dgraph.type": "Company",
            "Company.Name": "Company B",
            "Company.Location": "Florida"
        },
        {
            "dgraph.type": "Person",
            "Person.FirstName": "Mike",
            "Person.LastName": "Timberland"
        }
    ]
}

the i get success with 3 new Id´s.

   "uids": {
      "dg.2159301538.151": "0x4f4f",
      "dg.2159301538.152": "0x4f50",
      "dg.2159301538.153": "0x4f51"
    }

With these ID´s i will create the Edges like the following:

{
    "set":
    [
        {
            "uid": "0x4f51",
  					"dgraph.type": "Person",
  					"Person.Share":
  					[
							{
								"uid": "0x4f4f",
								"dgraph.type": "Company"
								},
                {
								"uid": "0x4f50",
                "dgraph.type": "Company"
								}
						]
				}
    ]
}

and again

  "data": {
    "code": "Success",
    "message": "Done",
    "queries": null,
    "uids": {}
  },

So now if i look at at the Data:

{
  q(func: has(Person.FirstName)) {	
	  uid
    expand(_all_) {
      uid
      expand(_all_)
    }
  }
}

Result:

  "data": {
    "q": [
      {
        "uid": "0x4f51",
        "Person.Share": [
          {
            "uid": "0x4f4f",
            "Company.Name": "Company A",
            "Company.Location": "Texas"
          },
          {
            "uid": "0x4f50",
            "Company.Name": "Company B",
            "Company.Location": "Florida"
          }
        ],
        "Person.FirstName": "Mike",
        "Person.LastName": "Timberland"
      }
    ]
  

This Works great. But if want to resolve from the Company:

{
  q(func: has(Company.Name)) {	
	  uid
    expand(_all_) {
      uid
      expand(_all_)
    }
  }
}

Result

  "data": {
    "q": [
      {
        "uid": "0x4f4f",
        "Company.Name": "Company A",
        "Company.Location": "Texas"
      },
      {
        "uid": "0x4f50",
        "Company.Name": "Company B",
        "Company.Location": "Florida"
      }
    ]
  

i dont see the shareholder. if i go expicit

{
  q(func: has(Company.Name)) {	
	  uid,
    Company.Name
		Company.Shareholder
    {
			Person.FirstName
    },
    ~Company.Shareholder
    {
			Person.FirstName
    }
  }
}

i get an error -Predicate Company.Shareholder doesn’t have reverse edge-
If i fix this in Ratel UI with checking Reverse on the Edge.
The Result doesent Contain the Person.

Result:

  "data": {
    "q": [
      {
        "uid": "0x4f4f",
        "Company.Name": "Company A"
      },
      {
        "uid": "0x4f50",
        "Company.Name": "Company B"
      }
    ]
  }

So now what am I doing wrong?
The result I expect, that in the Company node is a list of Shareholders with Mike Timberland.

You are mixing two different concepts. @hasInverse and Reverse index (from DQL) are two different things. And they won’t work together. Also, I don’t know if Reverse index would work in the GraphQL side. If you dataset is DQL based, you can simply add the directive @reverse in the Company.Shareholder predicate in your schema.

Hi MichelDiz,

i am not sure if i understand correctly, when i look at my Schema i see the reverse.

<Company.Location>: string .
<Company.Name>: string .
<Company.Shareholder>: [uid] @reverse .
<Person.FirstName>: string .
<Person.LastName>: string .
<Person.Share>: [uid] .
<dgraph.drop.op>: string .
<dgraph.graphql.p_query>: string @index(sha256) .
<dgraph.graphql.schema>: string .
<dgraph.graphql.xid>: string @index(exact) @upsert .
type <Company> {
	Company.Name
	Company.Location
	Company.Shareholder
}
type <Person> {
	Person.FirstName
	Person.LastName
	Person.Share
}
type <dgraph.graphql> {
	dgraph.graphql.schema
	dgraph.graphql.xid
}
type <dgraph.graphql.persisted_query> {
	dgraph.graphql.p_query
}

so i need do use DQL instead of ?

I’m not seeing Company.Shareholder edge in your dataset. Your dataset (in DQL) needs to reflect the relationship of the nodes. Otherwise there’s no connection to traverse in any direction.

If using reverse, yes. Only DQL.

What you are seeing is a data entry problem. In the GraphQL API when you add data on the predicate with an hasInverse directive on either side, the mutation rewriter in Dgraph writes this data inversely to both predicates, but when you mutate data outside of the GraphQL API endpoint such as you did with the mutate endpoint using DQL, then you bypass this control. You could have fixed this by either correcting the inverse relationships or by correctly handing inverse edges manually when using DQL.

Or the correct DQL initial mutation:

{
  "set": [
    {
      "uid": "0x4f51",
      "dgraph.type": "Person",
      "Person.Share": [
        {
          "uid": "0x4f4f",
          "dgraph.type": "Company",
          "Company.Shareholder": [{"uid":"0x4f51"}]
        },
        {
          "uid": "0x4f50",
          "dgraph.type": "Company",
          "Company.Shareholder": [{"uid":"0x4f51"}]
        }
      ]
    }
  ]
}

Edit: To fix syntax for Company.Shareholder edge

This syntax is wrong. It should be like:

"Company.Shareholder": [{"0x4f51"}]

@whiterabbit you can also just ignore Company.Shareholder completely and use the ~Person.Share

e.g:

{
  q(func: has(Company.Name)) {	
	  uid
    Company.Name
    Company.Shareholder : ~Person.Share {
			Person.FirstName
    }
  }
}

So, above, the Company.Shareholder is just an alias for ~Person.Share.

yes, sorry.

Unless… you plan at some time later on to use the GraphQL endpoint at which point in time the hasInverse directive will not have what you think it should have.

@amaster507 & @MichelDiz first of all thanks for the help.
If i understood correctly, i use in my Schema a GraphQL API specific feature (hasinverse) and save my Data with DQL, that will bypass the API and does not use the hasinverse feature.

If i use GraphQL API or DQL from start till end it should work as expected.

whatever i can query the Edge “Person.Share” reversal, because the Node has a two way Edge to Person. (Defined in the Schema)

if this is right, you helped me a lot.

yes, I believe you are on point.

  • If you are never going to use the GraphQL API endpoint then only use a DQL schema and don’t worry about @hasInverse at all.

  • If you are only going to use the GraphQL API endpoint, then don’t worry about @reverse at all. (gotcha if importing data, see next)

  • If you are going to use DQL to only mutate data (live/bulk loader) and the GraphQL API endpoint, then you will need to worry about how to correctly mutate data in DQL to mimic the API hasInverse logic, but don’t worry about @reverse at all.

  • If you are going to use DQL to mutate and query data, and you also want to be able to use the GraphQL API endpoint, then you will need to worry about how to correctly mutate data in DQL to mimic the API hasInverse logic, and you can also benefit by applying @reverse indexes.

ok, cool i think i got it @amaster507. There is one more thing that cross my mind, kind of which approach to use.

i want to give the edge (Person.Share) some information about how high the Share is. For Example 50.0

With my actual knowledge there are 2 Approaches.

First:
Use DQL all the way and use the feature Facets for the contextual data of the edge. In this path i would use the @reverse in the Schema and use the query example from @MichelDiz to get the Edge in reversal from Company to Person.So i don’t want to mimic the @hasInverse functionality. Thats the DQL Path.

Second:
As far i know GraphQL API has no concept like Facets. My Approach here is to create a 3rd type called Share and Connect Person and Company with the new Share node in the middle. But here i would use the @hasinverse feature from GraphQL Api and stay on the GraphQL path.

My problem is to choose which a approach is more modular and has more flexibility for future changes.
I hope is not Off topic, if it is i can make a new Thread.
Thanks for the patience for the beginner Questions.

IMO, I would try to stay away from facets most of the time for the simple reason that they are not first class citizens of the graph like predicates are. This may change later on but I don’t think in the near future. I believe the more flexible approach would be to use a new type of Share. And to back this up, think about what if later on you want to branch off this share into other nodes for whatever data means. You cannot link another node on a facet limiting some flexibility.

I take the latter approach to modal contact’s addresses:

type Contact {
  id: ID
  hasAddresses: [HasAddress] @hasInverse(...)
}
type HasAddress {
  id: ID
  address: Address @hasInverse(...)
  forContacts: Contact @hasInverse(...)
  isPrimary: Boolean
  isMailing: Boolean
  isPhysical: Boolean
  isDoNotDisturb: Boolean
}
type Address {
  id: ID
  access: [ACL]
  line1: String
  line2: String
  city: City @hasInverse(...)
  state: State @hasInverse(...)
  zip: Zip @hasInverse(...)
  country: Country @hasInverse(...)
  isVerified: Boolean
}
type City {
  id: ID
  name: String
  inState: State! @hasInverse(...)
  usedBy: [Address] @hasInverse(...)
}
type State {
  id: ID
  name: String! @id
  inCountry: Country! @hasInverse(...)
  hasCities: [City] @hasInverse(...)
  usedBy: [Address] @hasInverse(...)
}
...

This schema gets long, but the point is that this enables so much flexibility by even putting a linking node between a Contact and an Address, for lack of better name I call that type HasAddress, but the point is that I can now link the same Address to multiple Contacts and sometimes it can be a primary address for one contact but not for another. My use case is that if a family member leaves home their primary address should change but that primary address should not change for the whole family, but if the whole family moves a single address change changes the primary address for each family member. This could be modeled in many different ways, though. Another advantage of breaking down the address parts to their own types is that I can easily get all address by a city/state/zip/etc. and then easily work with those as a group in my application. The more “linking-nodes” the more flexible it makes the schema, but at the same time the more complicated it can make filtering and searching and ordering becomes impossible at the database level (I cannot order addresses by state/city).

I think the rule of thumb in a graph model is don’t worry about making too many nested types most of the time and just model it as deep as it naturally seems fit. Think in advance, is there other data that may connect to this field later on? Then maybe this field should be it’s own type.

Given our schema above, we have integrated with some census data directly connecting our Cities and States to this data. This enables us then to query census data → city → address → address_link → contact. This is pretty powerful if we want to find all people who live in an city (physical address) that the population is within a range.

@amaster507 Thanks for the insights in your head :slight_smile: So if i understood correctly, if two Person (Wife and Husband) living in the same address, in the Database there are 6 nodes.

2 Person Nodes
2 hasAddress Nodes
2 Address Nodes