Count cannot be assigned to a value variable

Although this use case is stated in the official docs, assigning count to a value variable does not work.

Steps to reproduce

This behaviour can be reproduced on the Dgraph Cloud Free Tier.

Schema
type Course {
  id: ID!
  title: String!
  chapters: [Chapter!]!
}

type Chapter {
  id: ID!
  title: String!
  sequence: Int
}

Initial Mutation

{
  "set": {
    "dgraph.type": "Course",
    "Course.title": "Course 1",
    "Course.chapters": [
      {
        "dgraph.type": "Chapter",
        "Chapter.title": "Chapter 1",
        "Chapter.sequence": 0
      },
      {
        "dgraph.type": "Chapter",
        "Chapter.title": "Chapter 2",
        "Chapter.sequence": 1
      }
    ]
  } 
}

Upsert Muatation

{
  "query": "{ qTest(func: uid(0x1)) { u as uid c as count(Course.chapters) } }",
  "mutations": [
    {
      "set": {
        "uid": "uid(u)",
        "Course.chapters": [
          {
            "dgraph.type": "Chapter",
            "Chapter.title": "Test Chapter",
            "Chapter.sequence": "val(c)"
          }
        ]
      }
    }
  ]
}
Response
{
  data: {
    code: "Success"
    message: "Done"
    queries: {
      qTest: [
        0: {
          uid:"0x1"
          count(Course.chapters): 2
        }
      ]
    }
    uids: {
      dg.3162278161.22051:"0x1"
    }
}

Expected behaviour

We would expect that Chapter.sequence of the new Chapter Test Chapter would be 2 since count(Course.chapters) validates 2.

Actual behaviour

Chapter.sequence of the newly created Chapter Test Chapter remains undefined.

UPDATE

I have played around a bit now and I’m actually really confused now. The documentation states:

The upsert block allows performing queries and mutations in a single request. The upsert block contains one query block and one or more than one mutation blocks. Variables defined in the query block can be used in the mutation blocks using the uid and val function.

However, the documentation also says that the value variable is only defined in the context of the same UID:

It therefore only makes sense to use the values from a value variable in a context that matches the same UIDs - if used in a block matching different UIDs the value variable is undefined.

This led me to alter the schema to test this out.

type Course {
  id: ID!
  title: String!
  chapters: [Chapter!]!
  chapterCount: Int
}

type Chapter {
  id: ID!
  title: String!
  sequence: Int
}

and the new upsert block

{
  "query": "{ qTest(func: uid(0x1)) { u as uid c as count(Course.chapters) } }",
  "mutations": [
    {
      "set": {
        "uid": "uid(u)",
        "Course.chapterCount": "val(c)",
        "Course.chapters": [
          {
            "dgraph.type": "Chapter",
            "Chapter.title": "Test Chapter",
            "Chapter.sequence": "val(c)"
          }
        ]
      }
    }
  ]
}

This results in a Course.chapterCount: 2, which is exactly the result I was expecting for Chapter.sequence! So does this mean I cannot use val for nodes with a different UID?

This would be a disaster and basically makes the upsert mutation useless.

I have not tested the code, by my understanding is that all vars are actually maps, and val(c) is actually a map lookup using a particular key: specifically, the uid of the entity defined in the enclosing scope.

This is documented, if not prominently, at https://dgraph.io/docs/query-language/value-variables/ where it reads " Value variables are a map from the UIDs of the enclosing block to the corresponding values." This is why val(c) could also be used in a repeating structure if the query matched many Courses - c is not one value, but a map with one value per uid.

So because val(c) is inside a block defining a new Chapter, I expect the uid of the new node is used to access the c var (the map) and it gets undefined because that map only has an entry for 0x01 (the uid of the matching Course vs the new Chapter).

A possible workaround is here: DQL conditional upsert with nested query value variables

It appears that near the end there is an upsert using RDF where specific uids from the query portion are used. In your case, there is a new node, so you may need to see if blank nodes can work here (as it won’t be possible to get the uid of a new chapter that does not exist yet in the query portion).

Again, I have not replicated the scenario, so I may be incorrect.

Hi @Damon!

Thanks for the answer. So val gets mapped to the enclosing UID and thus can only be used for field manipulation for the same node. I have tried playing around using ideas from nested nodes but unfortunately this does not work since the Chapter uid for the chapter I want to create for Course does not yet exist and thus there is nothing val(c) can get mapped to.

In my opinion, this is a huge restriction and keeps upsert very limited (for my use cases actually completely useless). I thought that the entire idea of upsert is to handle everything in one request and not splitting up tasks.

However, guess I have to live with this right now. I will change this from Bug to confusing docu and change the bug report on Github to feature request.

I have shared a possible solution here [BUG]: Count cannot be assigned to a value variable · Issue #8688 · dgraph-io/dgraph · GitHub

Hi @MichelDiz

Thanks for the answer. I have replied to your answer in my Bug Report. Unfortunately it did not work.

To keep this topic in sync and readable for potential other people with the same problem, I copy and paste my answer here as well:

In my opinion, this contradicts with the user intention. I guess the intention of this mapping is that we can use the same aliastwice in two different blocks but I do not think that this is the user intention. A user can alway pre/postfix an alias to make sense for his use case (eg. by adding the corresponding typename, the query name, etc.). This way value variables could simply be stored as a key/value pair and used everywhere in an upsert mutation.

Therefore, since this is not a bug, we can safely resolve this issue. Maybe you could update the documentation slightly so people don’t get confused here.

Would it be possible that you show me where the actual mapping takes place in the Dgraph source? If I have time I could check if it would be worth a feature request / pull request.

I have change the post type from Issues/Bug Report to Users/Dgraph Cloud.

Cheers!

That was just a typo. Fix the typo and try again. I meant to use “max” instead of “mas”.