Upsert with val() not working

What I want to do

Run an upsert mutation similar to this:

query lookup($0: string!) { CLASS_1 as var(func: type(Class)) @filter(eq(slug, $0)) { CLASS_1_LABEL as label }
  'set': {
    'uid': 'uid(NODE_1)', 
    'modified': '2023-09-21T00:34:41.648434+00:00', 
    'value': 'val(CLASS_1_LABEL)', 
    'class': {'uid': 'uid(CLASS_1)'}
  }

The part 'value': 'val(CLASS_1_LABEL)' “silently fails”; it doesn’t update the predicate “value”.

What I did

I’ve run the query part of the mutation separately to verify the query and the variables, both seem fine.

If I change it to something like 'value': 'static value' it’s reflected in the graph.

Dgraph metadata

dgraph version v21.12.0

References

The DQL / Mutation / val function section in the docs has a JSON example, but it’s using a var that’s not defined (incorrect) https://dgraph.io/docs/dql/mutations/val-upsert/

Could you clarify what NODE_1 is in your query?
val(CLASS_1_LABEL) is retrieving the value of ‘label’ predicate for the given uid which is uid(NODE_1) in your case.
But CLASS_1_LABEL is a map of uid->label for all uids matching (func: type(Class)) @filter(eq(slug, $0)).

If uid(NODE_1) is not in this list of uids of CLASS_1_LABEL map then val(…) is not defined and so the predicate is ignored.

1 Like

Thanks for getting back to me @Raphael.
Sure. The mutation is part of an upsert:

{
  'query': 'query lookup($0: string!, $1: string!) {
    CLASS_1 as var(func: type(Class)) @filter(eq(slug, $0)) { CLASS_1_LABEL as label }
    NODE_1 as var(func: type(Node)) @filter(uid_in(parents, $1) AND uid_in(class, uid(CLASS_1))) 
  }',
  'mutations': [
    {
      'cond': '@if(eq(len(CLASS_1), 1) AND eq(len(NODE_1), 1))',
      'set': {
        'uid': 'uid(NODE_1)', 
        'modified': '2023-09-21T00:34:41.648434+00:00', 
        'value': 'val(CLASS_1_LABEL)', 
        'class': {'uid': 'uid(CLASS_1)'}
    },
    {
      'cond': '@if(eq(len(CLASS_1), 1) AND eq(len(NODE_1), 0))',
      'set': {
        # data to create node
      }
    },
  }

For reference the schema is something like

type Class {
  label
  slug
}
type Node {
  class -> Class
  parents -> [Node]
  value
  modified
}

My intent with val(CLASS_1_LABEL) is to copy the value from CLASS_1.label into NODE_1.value. Is this possible?

This approach will not work because val(CLASS_1_LABEL) is evaluated from a map and using the uid(NODE_1): there is no value associated with the uid for CLASS_1_LABEL.

If I understand correctly you are looking for children of a give nodes that have a relation to a class and want to set the value predicate of those node to the label of the class.

I would do something like this in the query

var(func:uid($0)) @cascade {
   # parent node
   ~parents {
     # child node
      class @filter(eq(slud,$1))  {
         CLASS_LABEL as label
       }
   # aggregate the class label at the node level
   NODE_LABEL as max(val(CLASS_LABEL))
   }
}

# now use uid(NODE_LABEL) and val(NODE_LABEL) for the mutation.

Thinking in terms of graph is not an easy task and there are usually different ways to see the same problem. @cascade is an interesting feature to do “graph pattern matching”. Then var is a good way to extract some node in the path. The aggregation (max in this case) is a way to aggregate (propagate) some values to the upper level in the path.

In this approach @cascade is used to query only the node that can reach the class.
The max(val()) is used to ‘propagate’ the value at the level of the node ( a node has only one class so the max is the val but we need an aggregation function).
Now your NODE_LABEL is a correct map between the node uid and the value of the label you need.
I’m also using ~parents: you may have to index your predicate to use the reverse edge!

Can’t test the query without your data, but I hope it gives you enough insight to make it work. Let me know.

1 Like

Thanks for taking the time explaining it. Without seeing the data you led me to a solution, thanks a lot!

These are the changes I had to make:

  1. { CLASS_1_LABEL as label } is removed from CLASS_1 query.
  2. This is added to NODE_1 query:
{
  class {
    CLASS_1_LABEL as label
  }
  NODE_LABEL as max(val(CLASS_1_LABEL))
}
  1. In set, CLASS_1_LABEL is changed to NODE_LABEL.

A follow-up question. Is there a solution or work-around for the case where NODE doesn’t exist, i.e. uid(NODE) is blank? Since it’s an upsert I’m trying to accomplish.

Assuming it doesn’t work because the variable is a map for existing uid>value.