Some predicates are not being created on Slash

Summary

We are using Slash as our hosted Dgraph Server. But we have been facing a slight issue with using it.

So we update our Slash schema using the updateGQLSchema mutation (as mentioned in the docs). And the schema does get updated correctly, as we can cross-check that on the Schema Editor page. But when we take a look at the predicates (using Ratel), all of them are not there. Some fields on some types do not have any corresponding predicate. This leads to an error being thrown whenever we try to add some data on those predicates.

More Details

A snippet of the relevant part of our schema:

interface Node @generate(
    query: {get: true, query: false, aggregate: false},
    mutation: {add: false, update: false, delete: false}
) {
    id: ID!
}

interface ProductChildNode @generate(
    query: {get: false, query: false, aggregate: false},
    mutation: {add: false, update: false, delete: false}
) {
    product: Product!
}

type Product implements Node @generate(
    query: {query: false},
    mutation: {add: false, update: false, delete: false}
) {
    id: ID!
    name: String! @id @search(by: [term])

    articles: [Article!]! @hasInverse(field: "product")
}

type Article implements Node & ProductChildNode @generate(
    query: {query: false},
    mutation: {add: false, update: false, delete: false}
) {
    id: ID!
    title: String!
   
    product: Product!
}

This is a simple schema with a Product having multiple Articles, and each Article pointing back to its Product.
The schema does get updated correctly, but we can’t see the predicate Article.Product on Ratel. This becomes a blocker because we use the pydgraph client to add data to Dgraph, and whenever we try to add an Article.product, it throws this error:

<_InactiveRpcError of RPC that terminated with:
        status = StatusCode.UNKNOWN
        details = "Schema not defined for predicate: Article.product."
        debug_error_string = "{"created":"@1612809947.370362755","description":"Error received from peer ipv4:15.207.255.202:443","file":"src/core/lib/surface/call.cc","file_line":1067,"grpc_message":"Schema not defined for predicate: Article.product.","grpc_status":2}"
>

Additional Notes

  1. This is happening for around 3 to 5 fields in our schema.
  2. Keeping the whole setup exactly the same, we do not face this issue when using dgraph/standalone:v20.11.0 image as our dgraph server. The issue only occurs when we switch to use Slash.

Would be great if anyone could help in resolving this issue.

Hi @sanchit-gupta-cn Is it possible for you to share the python code snippet of how you are updating Article.product ?

Sure @anand,

Here is the snippet. Please ignore any syntax errors since this is not the exact same code (we use utilities to wrap core pygraph functions) but the logic is effectively the same.

import pydgraph

client_stub = pydgraph.DgraphClientStub.from_slash_endpoint(f"{slash_url}/graphql", api_key)
client = pydgraph.DgraphClient(client_stub)

obj = [
    {
        "uid": "_:helloworldarticle",
        "Article.title": "Hello World",
        "Article.product": {"uid": "0x1234"},
        "dgraph.type": "Article",
    },
    {
        "uid": "_:pingpongarticle",
        "Article.title": "Ping Pong",
        "Article.product": {"uid": "0x1234"},
        "dgraph.type": "Article",
    },
    {
        "uid": "0x1234",
        "Product.articles": [
            {"uid": "_:helloworldarticle"},
            {"uid": "_:pingpongarticle"}
        ]
    }
]

txn = client.txn()
try:
    response = txn.mutate(set_obj=obj, commit_now=True)
except pydgraph.AbortedError as e:
    logger.error(e)
finally:
    txn.discard()

Please note that the exact same code/logic works fine when using a dgraph local image.

Hi, I am able to reproduce this. I am marking it as a bug and will further investigate this.

Hi @minhaj. Thanks a lot! Looking forward to the bug being fixed.

Hey @sanchit-gupta-cn.
This is not a bug.
For this, you need to understand how Dgraph predicates are generated from the given GraphQL schema.
Dgraph.Predicate = ObjectName.field.
Since field product is defined in the interface ProductChildNode, the corresponding Dgraph predicate will be ProductChildNode.product. Now your type Article simply inherits the field product from the ProductChildNode, there will not be an Article.product predicate generated in case of your schema.
So you need to change your obj defined above to:


obj = [
    {
        "uid": "_:helloworldarticle",
        "Article.title": "Hello World",
        "ProductChildNode.product": {"uid": "0x1234"},
        "dgraph.type": ["Article", "ProductChildNode", "Node"]
    },
    {
        "uid": "_:pingpongarticle",
        "Article.title": "Ping Pong",
        "ProductChildNode.product": {"uid": "0x1234"},
        "dgraph.type": ["Article", "ProductChildNode", "Node"]
    },
    {
        "uid": "0x1234",
        "Product.articles": [
            {"uid": "_:helloworldarticle"},
            {"uid": "_:pingpongarticle"}
        ]
    }
]

Notice that while defining dgraph.type you need to pass all the implemented interfaces and the type name as an argument.

Feel free to raise any concern regarding this approach.

1 Like

Hi @sanchit-gupta-cn,
I see that your backend is on GraphQL Mode. Could you please switch to Flexible mode? This issue does not happen in flexible mode.
In order to switch to flexible mode, go to Settings → Advanced . You can set to Flexible mode as below.

Please try and let us know.

2 Likes

Thanks a lot for your help @anand, @minhaj.

In the end, we went with a different solution.
@minhaj’s approach works but it was not feasible for us (at the moment) to modify our mutation code at many places in the codebase. Plus we had written some utilities that handled the logic to prepend fields with <typename>. and auto-add dgraph.type before performing any mutation. This logic would also require change when using interfaces, and we’ll need to keep some kind of map maintaining sets of fields inherited so that the generated dgraph.type are correct (as pointed out by Minhaj).

Given all this, it was much faster for us to just drop the interfaces, and directly mention the inherited fields in the respective types. Plus we were mainly using interfaces for better grouping of fields and not true to one of their main GraphQL use case. Since we were not losing anything by dropping them, we went with this approach.

Also, I have some observations. Listing them here in the hopes that they might help someone else:

  1. So first I tried out @anand’s suggestion. Switching Slash to Flexible mode didn’t throw any error, even if we performed mutations in the way we were doing before. I cross-checked the data by performing this DQL query:

    {
      article(func: uid("0x1234")) {
          id: uid
          Article.title
    	  Article.product {
            uid
          }
      }
    }
    

    This query works fine and the article’s product id is returned as expected.
    BUT when I try to get the same data using Dgraph’s autogenerated GraphQL query, it doesn’t work. I tried this GQL query:

    {
      getArticle(id: "0x1234"){
        title
        product {
          id
        }
      }
    }
    

    This triggers graphql error propagation since no data is returned for product id and that can’t be null as per our schema. So I guess graphql queries follow the inherited fields to their interface and then try to get the data?

  2. Behavior mentioned in point 1 is also observed when using local dgraph image dgraph/standalone:v20.11.0. There is just one difference which seemed a little strange. Keeping our schema the same (with interfaces there) and updating both schemas from a clean slate: the slash (flexible backend) schema and the local dgraph schema, I can see the predicate Article.product present on local dgraph but not on Slash. As per Minhaj’s explanation, I think this predicate shouldn’t be present on local too, right?

Again, thanks a lot guys for your super quick response!

1 Like

Hi @sanchit-gupta-cn,
Great to know that you are able to proceed! Thanks for letting us know.

Hi @sanchit-gupta-cn,
Please review the following points. These might help to understand the behavior you are seeing.

  1. In the python code shared, the dgraph.type is not being set for Product. It was only being set for Article. This could be the reason you saw issues during GraphQL traversals.
    Edit: It might be possible that the node with uid 0x1234 must be have been created previously without setting the dgraph.type.
  2. After applying the schema on the standalone image, we did not find the Article.product created. However, the standalone image does not prohibit additional predicates by default. So when we run the python code, we do see that Article.product predicate is indeed created.
    The slash backend on GraphQL mode runs with mutation=strict setting. This prevents addition of any new predicate (not already set via the GraphQL schema). The flexible mode removes this restriction.
2 Likes

But you would have found the ProductChildNode.product and that is the predicate that you should use with DQL and the GraphQL schema with types.

2 Likes

Yeah, that is how the predicate naming takes place.

Hi @anand, thanks for your inputs. Regarding the points:

  1. I re-checked the Product with uid 0x1234, and everything was okay. Specifically, its dgraph.type was correctly set.
  2. I do understand that the local image creates predicates on the fly (if they are missing while performing a mutation). The same goes for Slash with a Flexible backend. But updating a schema (with types inheriting fields) on a local dgraph did show predicates like Article.product on Ratel, even without performing any mutation. But maybe I am missing something. So I’d like to confirm this once more. I’ll get back to you on this.

Yeah @sanchit-gupta-cn, do confirm this because it is very unlikely that the Article.Product will be generated just by updating GraphQL schema only.

1 Like

@minhaj Is this covered in the docs anywhere? I couldn’t find anything about how GraphQL interfaces are mapped to a Dgraph schema on this page: https://dgraph.io/docs/migration/graphql-dgraph/. It only covers types.

1 Like

I have raised this issue internally. We need to cover all the cases. Apart from that there should be some sample mutation etc also.

1 Like