Conditional Upsert - Mutations

The upsert block also allows specifying conditional mutation blocks using an @if directive. The mutation is executed only when the specified condition is true. If the condition is false, the mutation is silently ignored. The general structure of Conditional Upsert looks like as follows:

upsert {
  query <query block>
  [fragment <fragment block>]
  mutation [@if(<condition>)] <mutation block 1>
  [mutation [@if(<condition>)] <mutation block 2>]
  ...
}

The @if directive accepts a condition on variables defined in the query block and can be connected using AND, OR and NOT.

Example of Conditional Upsert

Let’s say in our previous example, we know the company1 has less than 100 employees. For safety, we want the mutation to execute only when the variable v stores less than 100 but greater than 50 UIDs in it. This can be achieved as follows:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d  $'
upsert {
  query {
    v as var(func: regexp(email, /.*@company1.io$/))
  }
  mutation @if(lt(len(v), 100) AND gt(len(v), 50)) {
    delete {
      uid(v) <name> * .
      uid(v) <email> * .
      uid(v) <age> * .
    }
  }
}' | jq

We can achieve the same result using json dataset as follows:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{
  "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }",
  "cond": "@if(lt(len(v), 100) AND gt(len(v), 50))",
  "delete": {
    "uid": "uid(v)",
    "name": null,
    "email": null,
    "age": null
  }
}' | jq

Example of Multiple Mutation Blocks

Consider an example with the following schema:

curl localhost:8080/alter -X POST -d $'
  name: string @index(term) .
  email: [string] @index(exact) @upsert .' | jq

Let’s say, we have many users stored in our database each having one or more than one email Addresses. Now, we get two email Addresses that belong to the same user. If the email Addresses belong to the different nodes in the database, we want to delete the existing nodes and create a new node with both the emails attached to this new node. Otherwise, we create/update the new/existing node with both the emails.

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
upsert {
  query {
    # filter is needed to ensure that we do not get same UIDs in u1 and u2
    q1(func: eq(email, "[email protected]")) @filter(not(eq(email, "[email protected]"))) {
      u1 as uid
    }
    q2(func: eq(email, "[email protected]")) @filter(not(eq(email, "[email protected]"))) {
      u2 as uid
    }
    q3(func: eq(email, "[email protected]")) @filter(eq(email, "[email protected]")) {
      u3 as uid
    }
  }
  # case when both emails do not exist
  mutation @if(eq(len(u1), 0) AND eq(len(u2), 0) AND eq(len(u3), 0)) {
    set {
      _:user <name> "user" .
      _:user <dgraph.type> "Person" .
      _:user <email> "[email protected]" .
      _:user <email> "[email protected]" .
    }
  }
  # case when email1 exists but email2 does not
  mutation @if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0)) {
    set {
      uid(u1) <email> "[email protected]" .
    }
  }
  # case when email1 does not exist but email2 exists
  mutation @if(eq(len(u1), 0) AND eq(len(u2), 1) AND eq(len(u3), 0)) {
    set {
      uid(u2) <email> "[email protected]" .
    }
  }
  # case when both emails exist and needs merging
  mutation @if(eq(len(u1), 1) AND eq(len(u2), 1) AND eq(len(u3), 0)) {
    set {
      _:user <name> "user" .
      _:user <dgraph.type> "Person" .
      _:user <email> "[email protected]" .
      _:user <email> "[email protected]" .
    }
    delete {
      uid(u1) <name> * .
      uid(u1) <email> * .
      uid(u2) <name> * .
      uid(u2) <email> * .
    }
  }
}' | jq

Result (when database is empty):

{
  "data": {
    "q1": [],
    "q2": [],
    "q3": [],
    "code": "Success",
    "message": "Done",
    "uids": {
      "user": "0x1"
    }
  },
  "extensions": {...}
}

Result (both emails exist and are attached to different nodes):

{
  "data": {
    "q1": [
      {
        "uid": "0x2"
      }
    ],
    "q2": [
      {
        "uid": "0x3"
      }
    ],
    "q3": [],
    "code": "Success",
    "message": "Done",
    "uids": {
      "user": "0x4"
    }
  },
  "extensions": {...}
}

Result (when both emails exist and are already attached to the same node):

{
  "data": {
    "q1": [],
    "q2": [],
    "q3": [
      {
        "uid": "0x4"
      }
    ],
    "code": "Success",
    "message": "Done",
    "uids": {}
  },
  "extensions": {...}
}

We can achieve the same result using json dataset as follows:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{
  "query": "{q1(func: eq(email, \"[email protected]\")) @filter(not(eq(email, \"[email protected]\"))) {u1 as uid} \n q2(func: eq(email, \"[email protected]\")) @filter(not(eq(email, \"[email protected]\"))) {u2 as uid} \n q3(func: eq(email, \"[email protected]\")) @filter(eq(email, \"[email protected]\")) {u3 as uid}}",
  "mutations": [
    {
      "cond": "@if(eq(len(u1), 0) AND eq(len(u2), 0) AND eq(len(u3), 0))",
      "set": [
        {
          "uid": "_:user",
          "name": "user",
          "dgraph.type": "Person"
        },
        {
          "uid": "_:user",
          "email": "[email protected]",
          "dgraph.type": "Person"
        },
        {
          "uid": "_:user",
          "email": "[email protected]",
          "dgraph.type": "Person"
        }
      ]
    },
    {
      "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0))",
      "set": [
        {
          "uid": "uid(u1)",
          "email": "[email protected]",
          "dgraph.type": "Person"
        }
      ]
    },
    {
      "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0))",
      "set": [
        {
          "uid": "uid(u2)",
          "email": "[email protected]",
          "dgraph.type": "Person"
        }
      ]
    },
    {
      "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 1) AND eq(len(u3), 0))",
      "set": [
        {
          "uid": "_:user",
          "name": "user",
          "dgraph.type": "Person"
        },
        {
          "uid": "_:user",
          "email": "[email protected]",
          "dgraph.type": "Person"
        },
        {
          "uid": "_:user",
          "email": "[email protected]",
          "dgraph.type": "Person"
        }
      ],
      "delete": [
        {
          "uid": "uid(u1)",
          "name": null,
          "email": null
        },
        {
          "uid": "uid(u2)",
          "name": null,
          "email": null
        }
      ]
    }
  ]
}' | jq

This is a companion discussion topic for the original entry at https://dgraph.io/docs/mutations/conditional-upsert/

Is there any way to abstract this condition at a schema level so as to prevent execution of mutations without the condition?