Can I update fields with the @id directive?

Hi!

I was following the instructions here and it seems like that I cannot update fields with the @id directive. Is that so or am I doing something wrong? I understand that when having the @id directive only, updating basically the ID does not make sense. In my case however, I have defined id: ID! and email: String! @id and in this case it would be nice to be able update the email field.

See: https://discuss.dgraph.io/t/rfc-mutable-and-null-able-id-field-in-graphql/

Thanks! So if I got this right an @id field is not mutable but the functionality should have been included within 21.07. When will this be working or is there a workaround?

You can update the data using a DQL mutation. If you don’t have the uid mapped in GraphQL using ID then you can do an upsert using your field with @id.

type User {
  username: String @id
  isActive: Boolean
}
upsert {
  query {
    user as var(func: eq(User.username,"foo"))
  }
  mutation {
    set {
      uid(user) <User.username> "bar" .
    }
  }
}

Thanks for the answer! So if I have

type User {
  id: ID!
  email: String! @id
}

this does not work?

It does still work with upsert, but it would be even easier if you knew the id.

mutation {
  set {
    "0x2a" <User.username> "bar" .
  }
}

Hey @amaster507!

Sorry for answering super late on this topic, I got dumped with work in the last couple of days…

First of all, thank you so much for pushing me into the right direction with this issue! Unfortunately, I still do not fully understand what I have to do. I was reading this post and if I understand right then I should be able to do something like:

type User {
  email: String! @id
  userName: String
}

According to the post, Dgraph should generate an update mutation, which looks something like

updateUser(input: [UpdateUserInput!]!, upsert: Boolean): UpdateUserPayload

and I should be able to update the email field by

mutation($email: String, $id: ID) {
  updateUser(input:{ filter: { id: $id }, set: { email: $email } }, upsert: true) {
    numUids
  }
}

The post states

The IDs must be external IDs, defined using the @id directive in the schema.

which I guess means that I am not allowed to have an extra ID field in my User type, right? To clarify, the type

type User {
  id: ID!
  email: String! @id
  userName: String
}

is not valid if I want to use upsert?
I’m asking because my updateUser mutation (with the second type User), has no upsert defined!

1 Like

You have to use dql to update the field, as it is impossible in graphql for the moment.

Your options are pretty much limited to a @lambda custom mutation, as there are no dql custom mutations in Dgraph Graphql.

There is no 21.07, so you will probably have to wait until 21.09 at the end of this month if you need to do it in GraphQL.

J

1 Like

This is still valid schema. But you have to use the DQL endpoint and syntax to update the email field at this time. You can have both an @id field and an ID field. In fact you can have multiple fields with @id but they all work as unique “indexes” not compound.

Right now in GraphQL upserts are for insert or update logic, but not update the @id field.

1 Like

Thanks guys! This really helped! :raised_hands: I’ve got the basic example working. Just for completion I will post this here

The User type

type User {
  id: ID!
  email: String! @id
  userName: String
}

The Resolve Function

async function updateUserWithDql({ args, dql }) {
  const update = await dql.mutate(`{
    set {
      <${args.userId}> <User.email> "${args.email}" .
      <${args.userId}> <User.userName> "${args.userName}" .
    }
  }`);
}

This updates all fields fine! Unfortunately, I ran into another problem :see_no_evil: What if I have

enum RoleType {
  "ADMIN"
  "MEMBER"
}

type Role {
  id: ID!
  type: RoleType!
}

type User {
  id: ID!
  email: String! @id
  userName: String
  role: Role
}

I can perform deep mutations with a GraphQL query but it is not clear to me how I do this with DQL? I have read this but I guess I’m using it wrong since I only get errors that my input type uid is a scalar… Could someone explain me what I have to do if I want to update User.role?

Thanks!

1 Like

You can add data deeply, but at the moment you cannot update data deeply in GraphQL. But you can using var blocks and upserts in DQL.

The DQL would look like:

upsert {
  query {
    var(func: eq(User.username,"foo")) {
      x as role
    }
  }
  mutation {
    set {
      uid(x) <Role.type> "MEMBER" .
    }
  }
}

Couldn’t you just import using json format? (not a dql expert by any means)…

J

Hey @amaster507

Unfortunately, it seems I’m too dumb for this.

Maybe there was a misunderstanding: I do not want to update the Role but I want to reference it to another Role. Therefore, the GraphQL mutation was working nicely on User.role before.

I was trying something like:

const updateDgraphUser = await dql.mutate(`{
  set {
    <${input.userId}> <User.email> "${input.email}" .
    <${input.userId}> <User.userName> "${input.userName}" .
    <${input.userId}> <User.role.id> "${input.roleId}" .
  }	
}`);

and I get no errors but there is also no update on the User.role field. I I try with upsert I get an error message that there is a bad character somewhere…

I have tried to do the GraphQL mutation for all fields, except for email and do the email field with DQL. This works but this seems a bit of a really dirty hack…

Try it in JSON format:

set {
  "uid": $input.userId,
  "email": $input.userName,
  "role": {
    id: $input.roleId
  }
}

Just curious if this works…

J

Ah, sorry. This is not a deep mutation. You are just changing an edge of the parent node. So if the other role already exist then you will just need the id of the other role node.

Let’s assume the role you want to add is 0x6 to the user with od 0x3. If you want to add a new one not existing you can also do that.

Since it is a one-to-one relationship you can do it all in a single mutation.

mutation changeRole {
  updateUser(input: {
    filter: { id: ["0x3"] }
    set: {
      role: {
        id: "0x6"
      }
    }
  }) {
    user {
      id
      email
      userName
      role {
        id
        type
      }
    }
  }
}

Hi @jdgamble555!

I have tried this but it seems that this is not a valid syntax for DQL. I get the error that

“role”: { “id”: 0x3 } at line 2 column 7: Invalid character ‘{’ inside mutation text"

So it looks like the second opening bracket { on role is not allowed.

Hi @amaster507!

Thanks again for the reply. Originally I had the GraphQL mutation like you have suggested here. The problem was that I can not update the email field with GraphQL since it has the @id directive in the schema. You told me that this is only possible with DQL. Applying DQL works but unfortunately I do not know the right syntax to update the reference for role with DQL.

I have tried:

set {
  "role": {
    "id": ${input.roleId}
  }
}

where I get the error that \"role\": { \"id\": 0x3d3aa4e71 } at line 2 column 7: Invalid character '{' inside mutation text". So this does not seem to work. Then I’ve tried

set {
  <${input.userId}> <User.role.id> "${input.roleId}" .
}

which at least runs through without errors, updates all the other fields, but leaves role.id untouched.

I could obviously run the DQL mutation on email only and do the rest with a GraphQL mutation but this seems a bit hacky…

@Poolshark

Make sure to pass the variables as an object, not as a string (don’t use ``).

Let me know if that works.

J

Hi, I don’t quite understand what you mean. My DQL mutation looks like

const dqlUpdate = await dql.mutate(`{
  set {
    "uid": $id,
    "email": $email,
    "role": {
      "id": $roleId
    }
}`, { ..set });

where

set = {
  id: "0x3"
  email: "some@mail.com"
  roleId: "0x3"
}

You’re passing set as a string when you use ``

Try just:

const dqlUpdate = await dql.mutate({
  set {
    "uid": $id,
    "email": $email,
    "role": {
      "id": $roleId
    }
});

J