Can I update fields with the @id directive?

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

I have tried

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

but I get the error that $id is not defined

You have two inputs, just input one object with exactly what you want.

{
  "uid": $input.userId
  "email": $input.email
  "role": {
    roleId: $input.roleId
  }
}

or whatever it may be…

J

Sorry, I might be mistaken here but how would I get the input parameter if I don’t pass it to dql.mutate? I could do something like

{
  "uid": `${input.userId}`
  "email": `${input.email}`
  "role": {
    roleId: `${input.roleId}`
  }
}

but unfortunately this does not work either.

Well, I gave up on this issue and now do 2 different (actually 3) different mutations. I guess this is not the way it is supposed to be but at least it works for me…

For completeness, I post my solution. Let’s assume that we have:

  • an external ID (email with @id directive)
  • an Edge Role on the parent node User with an Enum Type
  • an Edge Organisation on the parent node User as an Array of nodes

The Setup

type User {
  id: ID!
  email: String! @id
  role: Role!
  organisations: [Organisation!]!
}

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

enum RoleType {
  "ADMIN"
  "MEMBER"
}

type Organisation {
  id: ID!
  name: String!
}

In order to update the email field with the @id directive, we need to execute a DQL mutation. I do this with

const dqlUpdate = await dql.mutate(`{
  set {
    <${input.userId}> <User.email> "${input.email}" .
  }
}`);

Since I could not find out how to update the edges of the parent node with DQL, I do this with a GraphQL mutation. Let’s start with the role field first since updating arrays is still a bit more complicated

const graphQlUpdate = await graphql(`
 mutation(
   $id: [ID!]
   $roleId: ID!
 ) {
   updateUser( input: {
     filter: { id: $id },
     set: {
       role: {
         id: $roleId
       }
     }
   }) {
     numUids
   }
 }
`, { ...input });

When updating arrays of edges (or references), we have to remove and add the references within the mutation. Since my array isn’t that large I always remove the entire array first and then add my new array. This mean I can extend the above mutation

const graphQlUpdate = await graphql(`
  mutation(
    $id: [ID!]
    $roleId: ID!
    $addOrgs: [OrganisationRef!]
    $removeOrgs: [OrganisationRef!]
  ) {
    first: updateUser( input: {
      filter: { id: $id },
      remove: { organisations: $removeOrgs },
      set: {
        role: {
          id: $roleId
        }
      }
    }) { numUids }
    second: updateUser( input: {
      filter: { id: $id },
      set: {
        organisations: $addOrgs
      }
   }) { numUids }
  }
`, { ...input });

The complete lambda would then be

async function amendUser({ args, dql, graphql }) {
  const { input } = args;
  
  const dqlUpdate = await dql.mutate(`{
    set {
      <${input.id}> <User.email> "${input.email}" .
    }
  }`);

  const graphQlUpdate = await graphql(`
    mutation(
      $id: [ID!]
      $roleId: ID!
      $addOrgs: [OrganisationRef!]
      $removeOrgs: [OrganisationRef!]
    ) {
      first: updateUser( input: {
        filter: { id: $id },
        remove: { organisations: $removeOrgs },
        set: {
          role: {
            id: $roleId
          }
        }
      }) { numUids }
      second: updateUser( input: {
        filter: { id: $id },
        set: {
          organisations: $addOrgs
        }
     }) { numUids }
   }
`, { ...input });

return { success: "Yay!" }
}

I still don’t think this is the right way of doing this but it works… Any suggestions welcome!

That is a different question entirely.

That is the same think as just $input.userId. The args are automatically passed to the lambda function like so:

async function newPost({args, dql, authHeader}) {

Either the event object in a lambda webhook, or the args object in a lambda mutation.

Here is a full example of a lambda mutation.

J

Hi! I have tried this but since I have to use Webpack (because of additional external packages) for building my Lamda Function, your example won’t compile.

But thanks for the effort! I hope that updating fields with the @id directive will be supported by graphql Mutations soon.