(GraphQL API) Bidirectional edge not generated for interface field

Moved from GitHub dgraph/5393

Posted by CosmicPangolin:

What version of Dgraph are you using?
20.03.1

Have you tried reproducing the issue with the latest release?
Yep

What is the hardware spec (RAM, OS)?
N/A

Steps to reproduce the issue (command/config used to run Dgraph).
Consider the following schema:

interface File {
     users: [User!] @hasInverse(field: files)
}

type Image implements File {
}

type User {
     files: [File!]
}

Expected behaviour and actual result.
When I save an Image with a User, I would expect to be able to traverse from User back to that Image via User.files. Currently only the Image —> User edge is created

vadavallipavan commented :

i am having the same issue
Below is the graphQL schema:
type Book {
id: ID!
name : String! @search(by: [term])
tags : [Tag!]! @hasInverse(field:books)
description : String
}type Tag {
id: ID!
text: String! @search(by: [hash])
books:[Book!]!
}
Below is the mutation
{
set {
_:awsbook <Book.name> “awesomebook1” .
_:awsbook <dgraph.type> “Book” .
_:awsbook <Book.tags> _:awstag .
_:awstag <Tag.text> “AWESOME” .
_:awstag <dgraph.type> “Tag” .
}
}
Below is the reverse query : 0x2 --> uid of generated tag
{
booksFound (func: uid(0x2)) {
uid
Tag.text
dgraph.type
Tag.books {
uid
Book.name
}
}
}

Tag.books node is always empty
However the forward query works
{
nooksFound (func: uid(0x3)) {
uid
Book.name
dgraph.type
Book.tags {
uid
Tag.text

		}

}
}

Can you please advise

vadavallipavan commented :

Update:
After discussing with Pawan in slack, the conclusion is graphQL mutations will take care of generating the reverse edges when needed , we cant really mix graphql± queries/mutations with graphql schema.

pawanrawal commented :

@vadavallipavan was facing a different issue which is not a bug. We still need to investigate the issue that @CosmicPangolin is facing. @JatinDevDG is currently investigating it.

JatinDevDG commented :

Given schema was not working, so i fixed it and added details to it

interface File {
id: ID!
name:String !
users: [User!] @hasInverse(field: files)

}
type Image implements File {
description:String
}
type User {
name:String !
files: [File!]
}

we can access users from images directly.But the reason you were not able to access images from user is that index is created in File interface and it can’t access image fields.

But even then we can access file fields from users and even image fields which are in files by first creating images and use there id’s whille creating users.

In above mutation, id is the id of image already created. In result we can see the detail of that image, but we can’t see description from here because that is in image type.

But if you really want to access image fields ,we have to add index in image type like belows and refer to image in users.

interface File {
id: ID!
name:String !

}
type Image implements File {
users: [User!] @hasInverse(field: files)
description:String !
}
type User {
name:String !
files: [Image!]
}

Below mutation adds image and user.

Below mutation create a user and add a new image into it.Now, you can also access description in the result.

CosmicPangolin commented :

@JatinDevDG

Thanks for checking this out - just now taking a look.

Is the thought that your second solution is sufficent (pushing the users predicate into the concrete types and using a concrete type instead of interface in the schema)? Those actions seems to defeat the purpose of having an interface to begin with: we lose the ability to consolidate shared predicates into the interface and the ability to use multiple concrete types with a given predicate (which is very core).

I would expect to be able to run a query on User.files with ‘…on Image {}’ syntax with the first schema. Is this already possible? I actually think I might have written this issue before fully grasping the query syntax for interfaces :slight_smile:

MichaelJCompton commented :

Hi, I’ve been trying this out and it works as I expected.

Here’s what I did.

schema:

interface File {
    filename: String
    users: [User!] @hasInverse(field: files)
}

type Image implements File {
    format: String # gif, etc
}

type User {
    id: ID!
    name: String
    files: [File!]
}

Once that was added, I ran this mutation:

mutation {
  addUser(input: [ {name: "michael"} ]) {
    user {
      id
      name
    }
  }
}

it gives me back

  "data": {
    "addUser": {
      "user": [
        {
          "id": "0x2",
          "name": "michael"
        }
      ]
    }
  }

Then I add an image and link it to this user

mutation {
 addImage(input: [ { 
  filename: "my file", 
  format: "png", 
  users: [ { id: "0x2" } ]} ]) {
    image {
      filename
      format
      users {
        name
      }
    }
  }
}

which returns

  "data": {
    "addImage": {
      "image": [
        {
          "filename": "my file",
          "format": "png",
          "users": [
            {
              "name": "michael"
            }
          ]
        }
      ]
    }
  }

Then I can query through the user, and use a fragment on the type (format only applies to images, not all files)

query {
  queryUser {
    id
    name
    files {
      filename
      ... on Image {
        format
      }
    }
  }
}

that gives me what I expect

  "data": {
    "queryUser": [
      {
        "id": "0x2",
        "name": "michael",
        "files": [
          {
            "filename": "my file",
            "format": "png"
          }
        ]
      }
    ]
  }

@CosmicPangolin, get back to me and let me know if it is something else that you are trying to do.