Upserting multiple objects fails with "Some variables are declared multiple times"

I have an upsert mutation that looks like the following:

mutation AddUsers($users: [AddUserInput!]!) {
  addUser(input: $users, upsert: true) {
    user {
      fbUid
    }
    numUids
  }
}

Which I then populate with a format that looks like this:

// Transform user format
const dgUsers = hsUsers.map((hsUser) => ({
  fbUid: hsUser.uid,
  private: {
    fbUid: hsUser.uid,
    playKey: hsUser.playKey,
  },
  connectCode: hsUser.connectCode ? {
    code: hsUser.connectCode,
  } : null,
  isOnlineEnabled: hsUser.isOnlineEnabled,
  displayName: hsUser.displayName,
  status: (hsUser.status || "ACTIVE").toUpperCase(),
}));

For inserting, this seems to work fine. For upserting, it also seems to work fine if there’s only one user at a time. When upserting with multiple users though, I get the error message mutation addUser failed because Dgraph execution failed because Some variables are declared multiple times

Here is my schema for reference:

interface DbObject @auth(
  add: { rule: "{$ROLE: { eq: \"SERVICE\" }}" },
  update: { rule: "{$ROLE: { eq: \"SERVICE\" }}" },
  delete: { rule: "{$ROLE: { eq: \"SERVICE\" }}" }
) {
  id: ID!
}

type User implements DbObject @key(fields: "fbUid") {
  fbUid: String! @id
  private: PrivateUserInfo @hasInverse(field: user)
  connectCode: ConnectCode @hasInverse(field: user)
  isOnlineEnabled: Boolean
  displayName: String @search(by: [hash, trigram])
  status: UserStatus!
}

# Currently using separate object to handle uniqueness constraint of connect code. If a unique
# constraint becomes available, maybe that would be better?
type ConnectCode implements DbObject @key(fields: "code") {
  code: String! @id @search(by: [hash, trigram])
  user: User!
}

enum UserStatus {
  """
  Indicates the account is in good standing
  """
  ACTIVE
  """
  User has been banned
  """
  BANNED
}

# Currently using separate object to manage field rules. Field-based auth was supposed to be
# available in 21.07 which doesn't seem to be out yet. Once it's out, consider using field-based
# auth
type PrivateUserInfo implements DbObject @key(fields: "fbUid") @auth(
  query: {
    or: [
      { rule: "{$ROLE: { eq: \"SERVICE\" }}" },
      { rule: """
          query ($USER: String!) {
            queryPrivateUserInfo {
              user(filter: { fbUid: {eq: $USER}}) {
                fbUid
              }
            }
          }"""
      }
    ]
  }
) {
  fbUid: String! @id
  playKey: String
  user: User!
}

Is this a bug? Or am I doing something wrong?

1 Like

That might be the problem. Not sure.

Why you are doing this? self pointing?

Are you sending an object at time?

As I have little experience with Dgraph’s GraphQL implementation. I’ll ping @aman-bansal

Regarding the private object, I put fbUid in it as a key because I was worried that otherwise the upsert wouldn’t work correctly and would instead create a new object? Not sure exactly atm whether or not that happens but before I was getting a different error.

Regarding what I’m sending: when upserting, sending one object at a time works fine, but sending more than one at a time to the addUser mutation fails, I’ve tried both.

I’m running into the same issue :confused: Sending an array with length > 1 (using apollo client) to a mutation of the form mutation UpsertSomething($input: [AddSomethingInput!]!) gives this error, even when the external IDs of the AddSomethingInputs being sent are different.

Edited to add:


Dgraph version   : v21.03.2
Dgraph codename  : rocket-2
Dgraph SHA-256   : 00a53ef6d874e376d5a53740341be9b822ef1721a4980e6e2fcb60986b3abfbf
Commit SHA-1     : b17395d33
Commit timestamp : 2021-08-26 01:11:38 -0700
Branch           : HEAD
Go version       : go1.16.2
jemalloc enabled : true

Mutations to add users can be an array if you set it as an array in the schema. Uperts on the other hand, can’t be an array as far as I understand the mechanism behind it. You can upsert one by one, instead of a list of objects. I can be wrong tho. Need someone more experienced with this.

Update works differently in the sense that it does not support multiple inputs. However upsert is still just an add which DOES support multiple inputs, if the input is an array of types. Here is an example in the dgraph github.

@Fizzi Do you have a mutation rule that you did not post that prevents you from modifying another user? I am not familiar with the Apollo Federation schema I must admit.

@elhil - I suspect your problem is different. We need your schema and whole input to debug.

J

Thanks for the reply. I provided everything related to users in my post.

Just to make sure I understand:

Upsert is expected to work for multiple users in the case where the users did not already exist. Is upsert expected to work in the case where all the upserted users already exist and the operation will end up serving as a batch update instead?

The error is happening when the users I’m trying to upsert already exist.

Yes it should work in both cases. However, it only works if you have an ID field. It will not work with an @id field. For that, you need to use update and add and figure out the difference.

J

I guess that would explain it then. I’m using an @id field for my users.

It has been a while since I wrote this, but I think I created a way to automatically differentiate which ones are updates and which ones are IDs by automatically creating an update instead of an add for those fields. You have to specify the idField.

J

This is still an issue with v21.12.0.

To reiterate the problem: when Thing defines @auth rule for add, inserting multiple Things at once using add mutation does not work.

So, for example, using the following schema and running the query below reproduces the issue:

type Thing
  @auth(
    add: {
      or: [
        { rule: "{ $integration: { eq: \"integration-name\" } }" }
        { rule: "{ $user_roles: { eq: \"administrator\" } }" }
      ]
    }
  ) {
  id: String! @id
}

mutation upsertThings($input: [AddThingInput!]!) {
  addThing(input: $input, upsert: true) {
    thing {
      id
    }
  }
}
{
  "input": [ ... multiple Thing objects ... ]
}

This causes the error “mutation upsertThings failed because Dgraph execution failed because Some variables are declared multiple times.”

Removing the add rule from @auth makes the error go away, but also makes the auth go away.

Inserting only one Thing in the array works in principle, but is not a workable solution.